mirror of
				https://github.com/go-delve/delve.git
				synced 2025-10-31 10:47:27 +08:00 
			
		
		
		
	
							
								
								
									
										25
									
								
								_fixtures/goroutinestackprog.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								_fixtures/goroutinestackprog.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | |||||||
|  | package main | ||||||
|  |  | ||||||
|  | import "runtime" | ||||||
|  |  | ||||||
|  | const N = 10 | ||||||
|  |  | ||||||
|  | func agoroutine(done chan<- struct{}) { | ||||||
|  | 	done <- struct{}{} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func stacktraceme() { | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func main() { | ||||||
|  | 	done := make(chan struct{}) | ||||||
|  | 	for i := 0; i < N; i++ { | ||||||
|  | 		go agoroutine(done) | ||||||
|  | 	} | ||||||
|  | 	runtime.Gosched() | ||||||
|  | 	stacktraceme() | ||||||
|  | 	for i := 0; i < N; i++ { | ||||||
|  | 		<-done | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										18
									
								
								_fixtures/stacktraceprog.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								_fixtures/stacktraceprog.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | |||||||
|  | package main | ||||||
|  |  | ||||||
|  | func stacktraceme() { | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func func1() { | ||||||
|  | 	stacktraceme() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func func2(f func()) { | ||||||
|  | 	f() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func main() { | ||||||
|  | 	func1() | ||||||
|  | 	func2(func1) | ||||||
|  | } | ||||||
							
								
								
									
										18
									
								
								proc/proc.go
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								proc/proc.go
									
									
									
									
									
								
							| @ -424,10 +424,21 @@ func (dbp *DebuggedProcess) SwitchThread(tid int) error { | |||||||
| // Delve cares about from the internal runtime G structure. | // Delve cares about from the internal runtime G structure. | ||||||
| func (dbp *DebuggedProcess) GoroutinesInfo() ([]*G, error) { | func (dbp *DebuggedProcess) GoroutinesInfo() ([]*G, error) { | ||||||
| 	var ( | 	var ( | ||||||
| 		allg []*G | 		threadg = map[int]*Thread{} | ||||||
| 		rdr  = dbp.DwarfReader() | 		allg    []*G | ||||||
|  | 		rdr     = dbp.DwarfReader() | ||||||
| 	) | 	) | ||||||
|  |  | ||||||
|  | 	for i := range dbp.Threads { | ||||||
|  | 		if dbp.Threads[i].blocked() { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		g, _ := dbp.Threads[i].getG() | ||||||
|  | 		if g != nil { | ||||||
|  | 			threadg[g.Id] = dbp.Threads[i] | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	addr, err := rdr.AddrFor("runtime.allglen") | 	addr, err := rdr.AddrFor("runtime.allglen") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @ -451,6 +462,9 @@ func (dbp *DebuggedProcess) GoroutinesInfo() ([]*G, error) { | |||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
|  | 		if thread, allocated := threadg[g.Id]; allocated { | ||||||
|  | 			g.thread = thread | ||||||
|  | 		} | ||||||
| 		allg = append(allg, g) | 		allg = append(allg, g) | ||||||
| 	} | 	} | ||||||
| 	return allg, nil | 	return allg, nil | ||||||
|  | |||||||
| @ -409,3 +409,113 @@ func TestSwitchThread(t *testing.T) { | |||||||
| 		} | 		} | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type loc struct { | ||||||
|  | 	line int | ||||||
|  | 	fn   string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (l1 *loc) match(l2 Location) bool { | ||||||
|  | 	if l1.line >= 0 { | ||||||
|  | 		if l1.line != l2.Line-1 { | ||||||
|  | 			return false | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return l1.fn == l2.Fn.Name | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestStacktrace(t *testing.T) { | ||||||
|  | 	stacks := [][]loc{ | ||||||
|  | 		[]loc{{8, "main.func1"}, {16, "main.main"}}, | ||||||
|  | 		[]loc{{8, "main.func1"}, {12, "main.func2"}, {17, "main.main"}}, | ||||||
|  | 	} | ||||||
|  | 	withTestProcess("stacktraceprog", t, func(p *DebuggedProcess, fixture protest.Fixture) { | ||||||
|  | 		bp, err := p.BreakByLocation("main.stacktraceme") | ||||||
|  | 		assertNoError(err, t, "BreakByLocation()") | ||||||
|  |  | ||||||
|  | 		for i := range stacks { | ||||||
|  | 			assertNoError(p.Continue(), t, "Continue()") | ||||||
|  | 			locations, err := p.CurrentThread.Stacktrace(40) | ||||||
|  | 			assertNoError(err, t, "Stacktrace()") | ||||||
|  |  | ||||||
|  | 			if len(locations) != len(stacks[i])+2 { | ||||||
|  | 				t.Fatalf("Wrong stack trace size %d %d\n", len(locations), len(stacks[i])+2) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			for j := range stacks[i] { | ||||||
|  | 				if !stacks[i][j].match(locations[j]) { | ||||||
|  | 					t.Fatalf("Wrong stack trace pos %d\n", j) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		p.Clear(bp.Addr) | ||||||
|  | 		p.Continue() | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func stackMatch(stack []loc, locations []Location) bool { | ||||||
|  | 	if len(stack) > len(locations) { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	for i := range stack { | ||||||
|  | 		if !stack[i].match(locations[i]) { | ||||||
|  | 			return false | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return true | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestStacktraceGoroutine(t *testing.T) { | ||||||
|  | 	mainStack := []loc{{21, "main.main"}} | ||||||
|  | 	agoroutineStack := []loc{{-1, "runtime.goparkunlock"}, {-1, "runtime.chansend"}, {-1, "runtime.chansend1"}, {8, "main.agoroutine"}} | ||||||
|  |  | ||||||
|  | 	withTestProcess("goroutinestackprog", t, func(p *DebuggedProcess, fixture protest.Fixture) { | ||||||
|  | 		bp, err := p.BreakByLocation("main.stacktraceme") | ||||||
|  | 		assertNoError(err, t, "BreakByLocation()") | ||||||
|  |  | ||||||
|  | 		assertNoError(p.Continue(), t, "Continue()") | ||||||
|  |  | ||||||
|  | 		gs, err := p.GoroutinesInfo() | ||||||
|  | 		assertNoError(err, t, "GoroutinesInfo") | ||||||
|  |  | ||||||
|  | 		agoroutineCount := 0 | ||||||
|  | 		mainCount := 0 | ||||||
|  |  | ||||||
|  | 		for _, g := range gs { | ||||||
|  | 			locations, _ := p.GoroutineStacktrace(g, 40) | ||||||
|  | 			assertNoError(err, t, "GoroutineStacktrace()") | ||||||
|  |  | ||||||
|  | 			if stackMatch(mainStack, locations) { | ||||||
|  | 				mainCount++ | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if stackMatch(agoroutineStack, locations) { | ||||||
|  | 				agoroutineCount++ | ||||||
|  | 			} else { | ||||||
|  | 				t.Logf("Non-goroutine stack: (%d)", len(locations)) | ||||||
|  | 				for i := range locations { | ||||||
|  | 					name := "" | ||||||
|  | 					if locations[i].Fn != nil { | ||||||
|  | 						name = locations[i].Fn.Name | ||||||
|  | 					} | ||||||
|  | 					t.Logf("\t%s:%d %s\n", locations[i].File, locations[i].Line, name) | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if mainCount != 1 { | ||||||
|  | 			t.Fatalf("Main goroutine stack not found") | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if agoroutineCount != 10 { | ||||||
|  | 			t.Fatalf("Goroutine stacks not found (%d)", agoroutineCount) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		p.Clear(bp.Addr) | ||||||
|  | 		p.Continue() | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | |||||||
| @ -1,29 +1,45 @@ | |||||||
| package proc | package proc | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"debug/gosym" |  | ||||||
| 	"encoding/binary" | 	"encoding/binary" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type stackLocation struct { |  | ||||||
| 	addr uint64 |  | ||||||
| 	file string |  | ||||||
| 	line int |  | ||||||
| 	fn   *gosym.Func |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Takes an offset from RSP and returns the address of the | // Takes an offset from RSP and returns the address of the | ||||||
| // instruction the currect function is going to return to. | // instruction the currect function is going to return to. | ||||||
| func (thread *Thread) ReturnAddress() (uint64, error) { | func (thread *Thread) ReturnAddress() (uint64, error) { | ||||||
|  | 	locations, err := thread.Stacktrace(1) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return 0, err | ||||||
|  | 	} | ||||||
|  | 	return locations[0].PC, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Returns the stack trace for thread | ||||||
|  | // Note that it doesn't include the current frame and the locations in the array are return addresses not call addresses | ||||||
|  | func (thread *Thread) Stacktrace(depth int) ([]Location, error) { | ||||||
| 	regs, err := thread.Registers() | 	regs, err := thread.Registers() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return 0, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	locations, err := thread.dbp.stacktrace(regs.PC(), regs.SP(), 1) | 	locations, err := thread.dbp.stacktrace(regs.PC(), regs.SP(), depth) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return 0, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	return locations[0].addr, nil | 	return locations, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Returns the stack trace for a goroutine | ||||||
|  | // Note that it doesn't include the current frame and the locations in the array are return addresses not call addresses | ||||||
|  | func (dbp *DebuggedProcess) GoroutineStacktrace(g *G, depth int) ([]Location, error) { | ||||||
|  | 	if g.thread != nil { | ||||||
|  | 		return g.thread.Stacktrace(depth) | ||||||
|  | 	} | ||||||
|  | 	return dbp.stacktrace(g.PC, g.SP, depth) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (dbp *DebuggedProcess) GoroutineLocation(g *G) *Location { | ||||||
|  | 	f, l, fn := dbp.PCToLine(g.PC) | ||||||
|  | 	return &Location{PC: g.PC, File: f, Line: l, Fn: fn} | ||||||
| } | } | ||||||
|  |  | ||||||
| type NullAddrError struct{} | type NullAddrError struct{} | ||||||
| @ -32,12 +48,12 @@ func (n NullAddrError) Error() string { | |||||||
| 	return "NULL address" | 	return "NULL address" | ||||||
| } | } | ||||||
|  |  | ||||||
| func (dbp *DebuggedProcess) stacktrace(pc, sp uint64, depth int) ([]stackLocation, error) { | func (dbp *DebuggedProcess) stacktrace(pc, sp uint64, depth int) ([]Location, error) { | ||||||
| 	var ( | 	var ( | ||||||
| 		ret       = pc | 		ret       = pc | ||||||
| 		data      = make([]byte, dbp.arch.PtrSize()) | 		data      = make([]byte, dbp.arch.PtrSize()) | ||||||
| 		btoffset  int64 | 		btoffset  int64 | ||||||
| 		locations []stackLocation | 		locations []Location | ||||||
| 		retaddr   uintptr | 		retaddr   uintptr | ||||||
| 	) | 	) | ||||||
| 	for i := int64(0); i < int64(depth); i++ { | 	for i := int64(0); i < int64(depth); i++ { | ||||||
| @ -55,8 +71,15 @@ func (dbp *DebuggedProcess) stacktrace(pc, sp uint64, depth int) ([]stackLocatio | |||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| 		ret = binary.LittleEndian.Uint64(data) | 		ret = binary.LittleEndian.Uint64(data) | ||||||
|  | 		if ret <= 0 { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
| 		f, l, fn := dbp.goSymTable.PCToLine(ret) | 		f, l, fn := dbp.goSymTable.PCToLine(ret) | ||||||
| 		locations = append(locations, stackLocation{addr: ret, file: f, line: l, fn: fn}) | 		locations = append(locations, Location{PC: ret, File: f, Line: l, Fn: fn}) | ||||||
|  | 		if fn != nil && fn.Name == "runtime.goexit" { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 	} | 	} | ||||||
| 	return locations, nil | 	return locations, nil | ||||||
| } | } | ||||||
|  | |||||||
| @ -308,5 +308,6 @@ func (thread *Thread) getG() (g *G, err error) { | |||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	g, err = parseG(thread, regs.CX(), false) | 	g, err = parseG(thread, regs.CX(), false) | ||||||
|  | 	g.thread = thread | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
|  | |||||||
| @ -53,6 +53,9 @@ type G struct { | |||||||
|  |  | ||||||
| 	// PC of entry to top-most deferred function. | 	// PC of entry to top-most deferred function. | ||||||
| 	DeferPC uint64 | 	DeferPC uint64 | ||||||
|  |  | ||||||
|  | 	// Thread that this goroutine is currently allocated to | ||||||
|  | 	thread *Thread | ||||||
| } | } | ||||||
|  |  | ||||||
| // Returns whether the goroutine is blocked on | // Returns whether the goroutine is blocked on | ||||||
| @ -68,7 +71,7 @@ func (g *G) chanRecvReturnAddr(dbp *DebuggedProcess) (uint64, error) { | |||||||
| 		return 0, err | 		return 0, err | ||||||
| 	} | 	} | ||||||
| 	topLoc := locs[len(locs)-1] | 	topLoc := locs[len(locs)-1] | ||||||
| 	return topLoc.addr, nil | 	return topLoc.PC, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // NoGError returned when a G could not be found | // NoGError returned when a G could not be found | ||||||
|  | |||||||
| @ -1,6 +1,9 @@ | |||||||
| package api | package api | ||||||
|  |  | ||||||
| import "github.com/derekparker/delve/proc" | import ( | ||||||
|  | 	"debug/gosym" | ||||||
|  | 	"github.com/derekparker/delve/proc" | ||||||
|  | ) | ||||||
|  |  | ||||||
| // convertBreakpoint converts an internal breakpoint to an API Breakpoint. | // convertBreakpoint converts an internal breakpoint to an API Breakpoint. | ||||||
| func ConvertBreakpoint(bp *proc.Breakpoint) *Breakpoint { | func ConvertBreakpoint(bp *proc.Breakpoint) *Breakpoint { | ||||||
| @ -27,14 +30,7 @@ func ConvertThread(th *proc.Thread) *Thread { | |||||||
| 		pc = loc.PC | 		pc = loc.PC | ||||||
| 		file = loc.File | 		file = loc.File | ||||||
| 		line = loc.Line | 		line = loc.Line | ||||||
| 		if loc.Fn != nil { | 		function = ConvertFunction(loc.Fn) | ||||||
| 			function = &Function{ |  | ||||||
| 				Name:   loc.Fn.Name, |  | ||||||
| 				Type:   loc.Fn.Type, |  | ||||||
| 				Value:  loc.Fn.Value, |  | ||||||
| 				GoType: loc.Fn.GoType, |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return &Thread{ | 	return &Thread{ | ||||||
| @ -55,23 +51,35 @@ func ConvertVar(v *proc.Variable) Variable { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| // convertGoroutine converts an internal Goroutine to an API Goroutine. | func ConvertFunction(fn *gosym.Func) *Function { | ||||||
| func ConvertGoroutine(g *proc.G) *Goroutine { | 	if fn == nil { | ||||||
| 	var function *Function | 		return nil | ||||||
| 	if g.Func != nil { |  | ||||||
| 		function = &Function{ |  | ||||||
| 			Name:   g.Func.Name, |  | ||||||
| 			Type:   g.Func.Type, |  | ||||||
| 			Value:  g.Func.Value, |  | ||||||
| 			GoType: g.Func.GoType, |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	return &Function{ | ||||||
|  | 		Name:   fn.Name, | ||||||
|  | 		Type:   fn.Type, | ||||||
|  | 		Value:  fn.Value, | ||||||
|  | 		GoType: fn.GoType, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // convertGoroutine converts an internal Goroutine to an API Goroutine. | ||||||
|  | func ConvertGoroutine(g *proc.G) *Goroutine { | ||||||
| 	return &Goroutine{ | 	return &Goroutine{ | ||||||
| 		ID:       g.Id, | 		ID:       g.Id, | ||||||
| 		PC:       g.PC, | 		PC:       g.PC, | ||||||
| 		File:     g.File, | 		File:     g.File, | ||||||
| 		Line:     g.Line, | 		Line:     g.Line, | ||||||
| 		Function: function, | 		Function: ConvertFunction(g.Func), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func ConvertLocation(loc proc.Location) Location { | ||||||
|  | 	return Location{ | ||||||
|  | 		PC:       loc.PC, | ||||||
|  | 		File:     loc.File, | ||||||
|  | 		Line:     loc.Line, | ||||||
|  | 		Function: ConvertFunction(loc.Fn), | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | |||||||
| @ -41,6 +41,13 @@ type Thread struct { | |||||||
| 	Function *Function `json:"function,omitempty"` | 	Function *Function `json:"function,omitempty"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type Location struct { | ||||||
|  | 	PC       uint64    `json:"pc"` | ||||||
|  | 	File     string    `json:"file"` | ||||||
|  | 	Line     int       `json:"line"` | ||||||
|  | 	Function *Function `json:"function,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
| // Function represents thread-scoped function information. | // Function represents thread-scoped function information. | ||||||
| type Function struct { | type Function struct { | ||||||
| 	// Name is the function name. | 	// Name is the function name. | ||||||
|  | |||||||
| @ -60,4 +60,7 @@ type Client interface { | |||||||
|  |  | ||||||
| 	// ListGoroutines lists all goroutines. | 	// ListGoroutines lists all goroutines. | ||||||
| 	ListGoroutines() ([]*api.Goroutine, error) | 	ListGoroutines() ([]*api.Goroutine, error) | ||||||
|  |  | ||||||
|  | 	// Returns stacktrace | ||||||
|  | 	Stacktrace(goroutineId, depth int) ([]*api.Location, error) | ||||||
| } | } | ||||||
|  | |||||||
| @ -309,3 +309,49 @@ func (d *Debugger) Goroutines() ([]*api.Goroutine, error) { | |||||||
| 	} | 	} | ||||||
| 	return goroutines, err | 	return goroutines, err | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (d *Debugger) Stacktrace(goroutineId, depth int) ([]api.Location, error) { | ||||||
|  | 	var rawlocs []proc.Location | ||||||
|  | 	var rawloc *proc.Location | ||||||
|  | 	var err error | ||||||
|  |  | ||||||
|  | 	if goroutineId < 0 { | ||||||
|  | 		rawlocs, err = d.process.CurrentThread.Stacktrace(depth) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		rawloc, err = d.process.CurrentThread.Location() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		gs, err := d.process.GoroutinesInfo() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		for _, g := range gs { | ||||||
|  | 			if g.Id == goroutineId { | ||||||
|  | 				rawlocs, err = d.process.GoroutineStacktrace(g, depth) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return nil, err | ||||||
|  | 				} | ||||||
|  | 				rawloc = d.process.GoroutineLocation(g) | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if rawlocs == nil { | ||||||
|  | 			return nil, fmt.Errorf("Unknown goroutine id %d\n", goroutineId) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	locations := make([]api.Location, 0, len(rawlocs)+1) | ||||||
|  |  | ||||||
|  | 	locations = append(locations, api.ConvertLocation(*rawloc)) | ||||||
|  | 	for i := range rawlocs { | ||||||
|  | 		rawlocs[i].Line-- | ||||||
|  | 		locations = append(locations, api.ConvertLocation(rawlocs[i])) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return locations, nil | ||||||
|  | } | ||||||
|  | |||||||
| @ -266,6 +266,15 @@ func (c *RESTClient) ListGoroutines() ([]*api.Goroutine, error) { | |||||||
| 	return goroutines, nil | 	return goroutines, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (c *RESTClient) Stacktrace(goroutineId, depth int) ([]*api.Location, error) { | ||||||
|  | 	var locations []*api.Location | ||||||
|  | 	err := c.doGET(fmt.Sprintf("/goroutines/%d/trace?depth=%d", goroutineId, depth), &locations) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return locations, nil | ||||||
|  | } | ||||||
|  |  | ||||||
| // TODO: how do we use http.Client with a UNIX socket URI? | // TODO: how do we use http.Client with a UNIX socket URI? | ||||||
| func (c *RESTClient) url(path string) string { | func (c *RESTClient) url(path string) string { | ||||||
| 	return fmt.Sprintf("http://%s%s", c.addr, path) | 	return fmt.Sprintf("http://%s%s", c.addr, path) | ||||||
|  | |||||||
| @ -82,6 +82,7 @@ func (s *RESTServer) Run() error { | |||||||
| 		Route(ws.GET("/threads/{thread-id}/vars").To(s.listThreadPackageVars)). | 		Route(ws.GET("/threads/{thread-id}/vars").To(s.listThreadPackageVars)). | ||||||
| 		Route(ws.GET("/threads/{thread-id}/eval/{symbol}").To(s.evalThreadSymbol)). | 		Route(ws.GET("/threads/{thread-id}/eval/{symbol}").To(s.evalThreadSymbol)). | ||||||
| 		Route(ws.GET("/goroutines").To(s.listGoroutines)). | 		Route(ws.GET("/goroutines").To(s.listGoroutines)). | ||||||
|  | 		Route(ws.GET("/goroutines/{goroutine-id}/trace").To(s.stacktraceGoroutine)). | ||||||
| 		Route(ws.POST("/command").To(s.doCommand)). | 		Route(ws.POST("/command").To(s.doCommand)). | ||||||
| 		Route(ws.GET("/sources").To(s.listSources)). | 		Route(ws.GET("/sources").To(s.listSources)). | ||||||
| 		Route(ws.GET("/functions").To(s.listFunctions)). | 		Route(ws.GET("/functions").To(s.listFunctions)). | ||||||
| @ -171,6 +172,29 @@ func (s *RESTServer) getBreakpoint(request *restful.Request, response *restful.R | |||||||
| 	response.WriteEntity(found) | 	response.WriteEntity(found) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (s *RESTServer) stacktraceGoroutine(request *restful.Request, response *restful.Response) { | ||||||
|  | 	goroutineId, err := strconv.Atoi(request.PathParameter("goroutine-id")) | ||||||
|  | 	if err != nil { | ||||||
|  | 		writeError(response, http.StatusBadRequest, "invalid goroutine id") | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	depth, err := strconv.Atoi(request.QueryParameter("depth")) | ||||||
|  | 	if err != nil { | ||||||
|  | 		writeError(response, http.StatusBadRequest, "invalid depth") | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	locations, err := s.debugger.Stacktrace(goroutineId, depth) | ||||||
|  | 	if err != nil { | ||||||
|  | 		writeError(response, http.StatusBadRequest, err.Error()) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	response.WriteHeader(http.StatusOK) | ||||||
|  | 	response.WriteEntity(locations) | ||||||
|  | } | ||||||
|  |  | ||||||
| func (s *RESTServer) listBreakpoints(request *restful.Request, response *restful.Response) { | func (s *RESTServer) listBreakpoints(request *restful.Request, response *restful.Response) { | ||||||
| 	response.WriteEntity(s.debugger.Breakpoints()) | 	response.WriteEntity(s.debugger.Breakpoints()) | ||||||
| } | } | ||||||
|  | |||||||
| @ -59,6 +59,7 @@ func DebugCommands(client service.Client) *Commands { | |||||||
| 		{aliases: []string{"print", "p"}, cmdFn: printVar, helpMsg: "Evaluate a variable."}, | 		{aliases: []string{"print", "p"}, cmdFn: printVar, helpMsg: "Evaluate a variable."}, | ||||||
| 		{aliases: []string{"info"}, cmdFn: info, helpMsg: "Subcommands: args, funcs, locals, sources, vars, or regs."}, | 		{aliases: []string{"info"}, cmdFn: info, helpMsg: "Subcommands: args, funcs, locals, sources, vars, or regs."}, | ||||||
| 		{aliases: []string{"exit"}, cmdFn: nullCommand, helpMsg: "Exit the debugger."}, | 		{aliases: []string{"exit"}, cmdFn: nullCommand, helpMsg: "Exit the debugger."}, | ||||||
|  | 		{aliases: []string{"stack"}, cmdFn: stackCommand, helpMsg: "stack [<depth> [<goroutine id>]]. Prints stack."}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return c | 	return c | ||||||
| @ -189,7 +190,7 @@ func goroutines(client service.Client, args ...string) error { | |||||||
| 		if g.Function != nil { | 		if g.Function != nil { | ||||||
| 			fname = g.Function.Name | 			fname = g.Function.Name | ||||||
| 		} | 		} | ||||||
| 		fmt.Printf("Goroutine %d - %s:%d %s\n", g.ID, g.File, g.Line, fname) | 		fmt.Printf("Goroutine %d - %s:%d %s (%#v)\n", g.ID, g.File, g.Line, fname, g.PC) | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| @ -399,7 +400,7 @@ func info(client service.Client, args ...string) error { | |||||||
| 		data = filterVariables(vars, filter) | 		data = filterVariables(vars, filter) | ||||||
|  |  | ||||||
| 	default: | 	default: | ||||||
| 		return fmt.Errorf("unsupported info type, must be args, funcs, locals, sources, or vars") | 		return fmt.Errorf("unsupported info type, must be args, funcs, locals, sources or vars") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// sort and output data | 	// sort and output data | ||||||
| @ -411,6 +412,45 @@ func info(client service.Client, args ...string) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func stackCommand(client service.Client, args ...string) error { | ||||||
|  | 	var err error | ||||||
|  |  | ||||||
|  | 	goroutineid := -1 | ||||||
|  | 	depth := 10 | ||||||
|  |  | ||||||
|  | 	switch len(args) { | ||||||
|  | 	case 0: | ||||||
|  | 		// nothing to do | ||||||
|  | 	case 2: | ||||||
|  | 		goroutineid, err = strconv.Atoi(args[1]) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("Wrong argument: expected integer") | ||||||
|  | 		} | ||||||
|  | 		fallthrough | ||||||
|  | 	case 1: | ||||||
|  | 		depth, err = strconv.Atoi(args[0]) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("Wrong argument: expected integer") | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	default: | ||||||
|  | 		return fmt.Errorf("Wrong number of arguments to stack") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	stack, err := client.Stacktrace(goroutineid, depth) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	for i := range stack { | ||||||
|  | 		name := "(nil)" | ||||||
|  | 		if stack[i].Function != nil { | ||||||
|  | 			name = stack[i].Function.Name | ||||||
|  | 		} | ||||||
|  | 		fmt.Printf("%d. %s\n\t%s:%d (%#v)\n", i, name, stack[i].File, stack[i].Line, stack[i].PC) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
| func printcontext(state *api.DebuggerState) error { | func printcontext(state *api.DebuggerState) error { | ||||||
| 	if state.CurrentThread == nil { | 	if state.CurrentThread == nil { | ||||||
| 		fmt.Println("No current thread available") | 		fmt.Println("No current thread available") | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 aarzilli
					aarzilli