diff --git a/pkg/proc/arch.go b/pkg/proc/arch.go index 84ae4660f832d25efd40ed01d43409ce02de9ece..5beb061f54afac556212943ab69f293361b8812a 100644 --- a/pkg/proc/arch.go +++ b/pkg/proc/arch.go @@ -8,6 +8,7 @@ type Arch interface { BreakpointInstruction() []byte BreakpointSize() int GStructOffset() uint64 + DerefTLS() bool } // AMD64 represents the AMD64 CPU architecture. @@ -77,3 +78,9 @@ func (a *AMD64) BreakpointSize() int { func (a *AMD64) GStructOffset() uint64 { return a.gStructOffset } + +// If DerefTLS returns true the value of regs.TLS()+GStructOffset() is a +// pointer to the G struct +func (a *AMD64) DerefTLS() bool { + return a.goos == "windows" +} diff --git a/pkg/proc/breakpoints.go b/pkg/proc/breakpoints.go index 30f811e74f916dd787064a4421d03b2bb02c83e1..12ff6c23978771aee42d48b159e3b2aa83dda4d7 100644 --- a/pkg/proc/breakpoints.go +++ b/pkg/proc/breakpoints.go @@ -112,7 +112,7 @@ func (bp *Breakpoint) checkCondition(thread *Thread) (bool, error) { return true, nil } if bp.Kind == NextDeferBreakpoint { - frames, err := thread.Stacktrace(2) + frames, err := ThreadStacktrace(thread, 2) if err == nil { ispanic := len(frames) >= 3 && frames[2].Current.Fn != nil && frames[2].Current.Fn.Name == "runtime.gopanic" isdeferreturn := false @@ -129,7 +129,7 @@ func (bp *Breakpoint) checkCondition(thread *Thread) (bool, error) { } } } - scope, err := thread.GoroutineScope() + scope, err := GoroutineScope(thread) if err != nil { return true, err } diff --git a/pkg/proc/disasm.go b/pkg/proc/disasm.go index 6abf0e462e4203680537e5785cc81480d2cd1a01..765e7b2de4d3f95827b1c13955916305bb5be54e 100644 --- a/pkg/proc/disasm.go +++ b/pkg/proc/disasm.go @@ -16,26 +16,35 @@ const ( IntelFlavour ) -func (dbp *Process) Disassemble(g *G, startPC, endPC uint64) ([]AsmInstruction, error) { +// DisassembleInfo is the subset of target.Interface used by Disassemble. +type DisassembleInfo interface { + CurrentThread() IThread + Breakpoints() map[uint64]*Breakpoint + BinInfo() *BinaryInfo +} + +// Disassemble disassembles target memory between startPC and endPC, marking +// the current instruction being executed in goroutine g. +// If currentGoroutine is set and thread is stopped at a CALL instruction Disassemble will evaluate the argument of the CALL instruction using the thread's registers +// Be aware that the Bytes field of each returned instruction is a slice of a larger array of size endPC - startPC +func Disassemble(dbp DisassembleInfo, g *G, startPC, endPC uint64) ([]AsmInstruction, error) { if g == nil { - regs, _ := dbp.currentThread.Registers(false) - return Disassemble(dbp.currentThread, regs, dbp.breakpoints, &dbp.bi, startPC, endPC) + ct := dbp.CurrentThread() + regs, _ := ct.Registers(false) + return disassemble(ct, regs, dbp.Breakpoints(), dbp.BinInfo(), startPC, endPC) } var regs Registers - thread := dbp.currentThread + var mem memoryReadWriter = dbp.CurrentThread() if g.thread != nil { - thread = g.thread + mem = g.thread regs, _ = g.thread.Registers(false) } - return Disassemble(thread, regs, dbp.breakpoints, &dbp.bi, startPC, endPC) + return disassemble(mem, regs, dbp.Breakpoints(), dbp.BinInfo(), startPC, endPC) } -// Disassemble disassembles target memory between startPC and endPC -// If currentGoroutine is set and thread is stopped at a CALL instruction Disassemble will evaluate the argument of the CALL instruction using the thread's registers -// Be aware that the Bytes field of each returned instruction is a slice of a larger array of size endPC - startPC -func Disassemble(memrw memoryReadWriter, regs Registers, breakpoints map[uint64]*Breakpoint, bi *BinaryInfo, startPC, endPC uint64) ([]AsmInstruction, error) { +func disassemble(memrw memoryReadWriter, regs Registers, breakpoints map[uint64]*Breakpoint, bi *BinaryInfo, startPC, endPC uint64) ([]AsmInstruction, error) { mem, err := memrw.readMemory(uintptr(startPC), int(endPC-startPC)) if err != nil { return nil, err diff --git a/pkg/proc/disasm_amd64.go b/pkg/proc/disasm_amd64.go index 82d66ec6ad54c1ff0c8d286d6f33b217a9763c70..2138f9da716b2d782c8d4f037c70c6e61b08747f 100644 --- a/pkg/proc/disasm_amd64.go +++ b/pkg/proc/disasm_amd64.go @@ -146,7 +146,7 @@ func (dbp *Process) FirstPCAfterPrologue(fn *gosym.Func, sameline bool) (uint64, // FirstPCAfterPrologue returns the address of the first instruction after the prologue for function fn // If sameline is set FirstPCAfterPrologue will always return an address associated with the same line as fn.Entry func FirstPCAfterPrologue(mem memoryReadWriter, breakpoints map[uint64]*Breakpoint, bi *BinaryInfo, fn *gosym.Func, sameline bool) (uint64, error) { - text, err := Disassemble(mem, nil, breakpoints, bi, fn.Entry, fn.End) + text, err := disassemble(mem, nil, breakpoints, bi, fn.Entry, fn.End) if err != nil { return fn.Entry, err } diff --git a/pkg/proc/proc.go b/pkg/proc/proc.go index 041dd1d33c77617b77bbfc7b395e351bf96281c5..219e66a27412658e2bf1b9ea7a084609d4d33d6d 100644 --- a/pkg/proc/proc.go +++ b/pkg/proc/proc.go @@ -149,11 +149,20 @@ func (dbp *Process) SelectedGoroutine() *G { return dbp.selectedGoroutine } -func (dbp *Process) Threads() map[int]*Thread { - return dbp.threads +func (dbp *Process) ThreadList() []IThread { + r := make([]IThread, 0, len(dbp.threads)) + for _, v := range dbp.threads { + r = append(r, v) + } + return r +} + +func (dbp *Process) FindThread(threadID int) (IThread, bool) { + th, ok := dbp.threads[threadID] + return th, ok } -func (dbp *Process) CurrentThread() *Thread { +func (dbp *Process) CurrentThread() IThread { return dbp.currentThread } @@ -396,7 +405,7 @@ func (dbp *Process) Continue() error { if err != nil { return err } - text, err := Disassemble(dbp.currentThread, regs, dbp.breakpoints, &dbp.bi, pc, pc+maxInstructionLength) + text, err := disassemble(dbp.currentThread, regs, dbp.breakpoints, dbp.BinInfo(), pc, pc+maxInstructionLength) if err != nil { return err } @@ -526,12 +535,12 @@ func (dbp *Process) StepInstruction() (err error) { if dbp.exited { return &ProcessExitedError{} } - dbp.selectedGoroutine.thread.clearBreakpointState() - err = dbp.selectedGoroutine.thread.StepInstruction() + dbp.selectedGoroutine.thread.(*Thread).clearBreakpointState() + err = dbp.selectedGoroutine.thread.(*Thread).StepInstruction() if err != nil { return err } - return dbp.selectedGoroutine.thread.SetCurrentBreakpoint() + return dbp.selectedGoroutine.thread.(*Thread).SetCurrentBreakpoint() } // StepOut will continue until the current goroutine exits the @@ -597,7 +606,7 @@ func (dbp *Process) SwitchThread(tid int) error { } if th, ok := dbp.threads[tid]; ok { dbp.currentThread = th - dbp.selectedGoroutine, _ = dbp.currentThread.GetG() + dbp.selectedGoroutine, _ = GetG(dbp.currentThread) return nil } return fmt.Errorf("thread %d does not exist", tid) @@ -609,7 +618,7 @@ func (dbp *Process) SwitchGoroutine(gid int) error { if dbp.exited { return &ProcessExitedError{} } - g, err := dbp.FindGoroutine(gid) + g, err := FindGoroutine(dbp, gid) if err != nil { return err } @@ -618,7 +627,7 @@ func (dbp *Process) SwitchGoroutine(gid int) error { return nil } if g.thread != nil { - return dbp.SwitchThread(g.thread.ID) + return dbp.SwitchThread(g.thread.ThreadID()) } dbp.selectedGoroutine = g return nil @@ -644,7 +653,7 @@ func (dbp *Process) GoroutinesInfo() ([]*G, error) { if dbp.threads[i].blocked() { continue } - g, _ := dbp.threads[i].GetG() + g, _ := GetG(dbp.threads[i]) if g != nil { threadg[g.ID] = dbp.threads[i] } @@ -673,7 +682,7 @@ func (dbp *Process) GoroutinesInfo() ([]*G, error) { allgptr := binary.LittleEndian.Uint64(faddr) for i := uint64(0); i < allglen; i++ { - gvar, err := dbp.currentThread.newGVariable(uintptr(allgptr+(i*uint64(dbp.bi.arch.PtrSize()))), true) + gvar, err := newGVariable(dbp.currentThread, uintptr(allgptr+(i*uint64(dbp.bi.arch.PtrSize()))), true) if err != nil { return nil, err } @@ -698,7 +707,7 @@ func (dbp *Process) GoroutinesInfo() ([]*G, error) { return allg, nil } -func (g *G) Thread() *Thread { +func (g *G) Thread() IThread { return g.thread } @@ -795,7 +804,7 @@ func initializeDebugProcess(dbp *Process, path string, attach bool) (*Process, e // because without calling SetGStructOffset we can not read the G struct of currentThread // but without calling updateThreadList we can not examine memory to determine // the offset of g struct inside TLS - dbp.selectedGoroutine, _ = dbp.currentThread.GetG() + dbp.selectedGoroutine, _ = GetG(dbp.currentThread) panicpc, err := dbp.FindFunctionLocation("runtime.startpanic", true, 0) if err == nil { @@ -875,11 +884,16 @@ func (scope *EvalScope) getGoInformation() (ver GoVersion, isextld bool, err err return } +type GoroutinesInfo interface { + SelectedGoroutine() *G + GoroutinesInfo() ([]*G, error) +} + // FindGoroutine returns a G struct representing the goroutine // specified by `gid`. -func (dbp *Process) FindGoroutine(gid int) (*G, error) { +func FindGoroutine(dbp GoroutinesInfo, gid int) (*G, error) { if gid == -1 { - return dbp.selectedGoroutine, nil + return dbp.SelectedGoroutine(), nil } gs, err := dbp.GoroutinesInfo() @@ -894,23 +908,29 @@ func (dbp *Process) FindGoroutine(gid int) (*G, error) { return nil, fmt.Errorf("Unknown goroutine %d", gid) } +// EvalScopeConvertible is a subset of target.Interface with the methods +// used by ConvertEvalScope/GoroutinesInfo/etc. +type EvalScopeConvertible interface { + GoroutinesInfo + CurrentThread() IThread + BinInfo() *BinaryInfo +} + // ConvertEvalScope returns a new EvalScope in the context of the // specified goroutine ID and stack frame. -func (dbp *Process) ConvertEvalScope(gid, frame int) (*EvalScope, error) { - if dbp.exited { - return nil, &ProcessExitedError{} - } - g, err := dbp.FindGoroutine(gid) +func ConvertEvalScope(dbp EvalScopeConvertible, gid, frame int) (*EvalScope, error) { + ct := dbp.CurrentThread() + g, err := FindGoroutine(dbp, gid) if err != nil { return nil, err } if g == nil { - return dbp.currentThread.ThreadScope() + return ThreadScope(ct) } - var thread *Thread + var thread memoryReadWriter if g.thread == nil { - thread = dbp.currentThread + thread = ct } else { thread = g.thread } @@ -929,6 +949,11 @@ func (dbp *Process) ConvertEvalScope(gid, frame int) (*EvalScope, error) { return &EvalScope{PC, CFA, thread, g.variable, dbp.BinInfo()}, nil } +// FrameToScope returns a new EvalScope for this frame +func FrameToScope(p EvalScopeConvertible, frame Stackframe) *EvalScope { + return &EvalScope{frame.Current.PC, frame.CFA, p.CurrentThread(), nil, p.BinInfo()} +} + func (dbp *Process) postExit() { dbp.exited = true close(dbp.ptraceChan) diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index 1948e63d9df8f43d024cfa42f1fd6a302bf587d4..c9c11a6e836cfd60da630e3106a37d83248ba81b 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -435,7 +435,7 @@ func TestNextConcurrent(t *testing.T) { _, err = p.ClearBreakpoint(bp.Addr) assertNoError(err, t, "ClearBreakpoint()") for _, tc := range testcases { - g, err := p.currentThread.GetG() + g, err := GetG(p.currentThread) assertNoError(err, t, "GetG()") if p.selectedGoroutine.ID != g.ID { t.Fatalf("SelectedGoroutine not CurrentThread's goroutine: %d %d", g.ID, p.selectedGoroutine.ID) @@ -474,7 +474,7 @@ func TestNextConcurrentVariant2(t *testing.T) { initVval, _ := constant.Int64Val(initV.Value) assertNoError(err, t, "EvalVariable") for _, tc := range testcases { - g, err := p.currentThread.GetG() + g, err := GetG(p.currentThread) assertNoError(err, t, "GetG()") if p.selectedGoroutine.ID != g.ID { t.Fatalf("SelectedGoroutine not CurrentThread's goroutine: %d %d", g.ID, p.selectedGoroutine.ID) @@ -586,6 +586,17 @@ func TestRuntimeBreakpoint(t *testing.T) { }) } +func returnAddress(thread IThread) (uint64, error) { + locations, err := ThreadStacktrace(thread, 2) + if err != nil { + return 0, err + } + if len(locations) < 2 { + return 0, NoReturnAddr{locations[0].Current.Fn.BaseName()} + } + return locations[1].Current.PC, nil +} + func TestFindReturnAddress(t *testing.T) { withTestProcess("testnextprog", t, func(p *Process, fixture protest.Fixture) { start, _, err := p.BinInfo().goSymTable.LineToPC(fixture.Source, 24) @@ -600,7 +611,7 @@ func TestFindReturnAddress(t *testing.T) { if err != nil { t.Fatal(err) } - addr, err := p.currentThread.ReturnAddress() + addr, err := returnAddress(p.currentThread) if err != nil { t.Fatal(err) } @@ -624,7 +635,7 @@ func TestFindReturnAddressTopOfStackFn(t *testing.T) { if err := p.Continue(); err != nil { t.Fatal(err) } - if _, err := p.currentThread.ReturnAddress(); err == nil { + if _, err := returnAddress(p.currentThread); err == nil { t.Fatal("expected error to be returned") } }) @@ -726,7 +737,7 @@ func TestStacktrace(t *testing.T) { for i := range stacks { assertNoError(p.Continue(), t, "Continue()") - locations, err := p.currentThread.Stacktrace(40) + locations, err := ThreadStacktrace(p.currentThread, 40) assertNoError(err, t, "Stacktrace()") if len(locations) != len(stacks[i])+2 { @@ -754,7 +765,7 @@ func TestStacktrace2(t *testing.T) { withTestProcess("retstack", t, func(p *Process, fixture protest.Fixture) { assertNoError(p.Continue(), t, "Continue()") - locations, err := p.currentThread.Stacktrace(40) + locations, err := ThreadStacktrace(p.currentThread, 40) assertNoError(err, t, "Stacktrace()") if !stackMatch([]loc{{-1, "main.f"}, {16, "main.main"}}, locations, false) { for i := range locations { @@ -764,7 +775,7 @@ func TestStacktrace2(t *testing.T) { } assertNoError(p.Continue(), t, "Continue()") - locations, err = p.currentThread.Stacktrace(40) + locations, err = ThreadStacktrace(p.currentThread, 40) assertNoError(err, t, "Stacktrace()") if !stackMatch([]loc{{-1, "main.g"}, {17, "main.main"}}, locations, false) { for i := range locations { @@ -883,7 +894,7 @@ func testGSupportFunc(name string, t *testing.T, p *Process, fixture protest.Fix assertNoError(p.Continue(), t, name+": Continue()") - g, err := p.currentThread.GetG() + g, err := GetG(p.currentThread) assertNoError(err, t, name+": GetG()") if g == nil { @@ -1011,7 +1022,7 @@ func TestIssue239(t *testing.T) { } func evalVariable(p *Process, symbol string) (*Variable, error) { - scope, err := p.currentThread.GoroutineScope() + scope, err := GoroutineScope(p.currentThread) if err != nil { return nil, err } @@ -1019,7 +1030,7 @@ func evalVariable(p *Process, symbol string) (*Variable, error) { } func setVariable(p *Process, symbol, value string) error { - scope, err := p.currentThread.GoroutineScope() + scope, err := GoroutineScope(p.currentThread) if err != nil { return err } @@ -1140,7 +1151,7 @@ func TestFrameEvaluation(t *testing.T) { continue } - scope, err := p.ConvertEvalScope(g.ID, frame) + scope, err := ConvertEvalScope(p, g.ID, frame) assertNoError(err, t, "ConvertEvalScope()") t.Logf("scope = %v", scope) v, err := scope.EvalVariable("i", normalLoadConfig) @@ -1161,11 +1172,11 @@ func TestFrameEvaluation(t *testing.T) { // Testing evaluation on frames assertNoError(p.Continue(), t, "Continue() 2") - g, err := p.currentThread.GetG() + g, err := GetG(p.currentThread) assertNoError(err, t, "GetG()") for i := 0; i <= 3; i++ { - scope, err := p.ConvertEvalScope(g.ID, i+1) + scope, err := ConvertEvalScope(p, g.ID, i+1) assertNoError(err, t, fmt.Sprintf("ConvertEvalScope() on frame %d", i+1)) v, err := scope.EvalVariable("n", normalLoadConfig) assertNoError(err, t, fmt.Sprintf("EvalVariable() on frame %d", i+1)) @@ -1194,7 +1205,7 @@ func TestPointerSetting(t *testing.T) { pval(1) // change p1 to point to i2 - scope, err := p.currentThread.GoroutineScope() + scope, err := GoroutineScope(p.currentThread) assertNoError(err, t, "Scope()") i2addr, err := scope.EvalExpression("i2", normalLoadConfig) assertNoError(err, t, "EvalExpression()") @@ -1333,7 +1344,7 @@ func TestBreakpointCountsWithDetection(t *testing.T) { if th.CurrentBreakpoint == nil { continue } - scope, err := th.GoroutineScope() + scope, err := GoroutineScope(th) assertNoError(err, t, "Scope()") v, err := scope.EvalVariable("i", normalLoadConfig) assertNoError(err, t, "evalVariable") @@ -1466,7 +1477,7 @@ func TestPointerLoops(t *testing.T) { func BenchmarkLocalVariables(b *testing.B) { withTestProcess("testvariables", b, func(p *Process, fixture protest.Fixture) { assertNoError(p.Continue(), b, "Continue() returned an error") - scope, err := p.currentThread.GoroutineScope() + scope, err := GoroutineScope(p.currentThread) assertNoError(err, b, "Scope()") for i := 0; i < b.N; i++ { _, err := scope.LocalVariables(normalLoadConfig) @@ -1600,7 +1611,7 @@ func TestIssue332_Part1(t *testing.T) { assertNoError(err, t, "SetBreakpoint()") assertNoError(p.Continue(), t, "Continue()") assertNoError(p.Next(), t, "first Next()") - locations, err := p.currentThread.Stacktrace(2) + locations, err := ThreadStacktrace(p.currentThread, 2) assertNoError(err, t, "Stacktrace()") if locations[0].Call.Fn == nil { t.Fatalf("Not on a function") @@ -1629,7 +1640,7 @@ func TestIssue332_Part2(t *testing.T) { // step until we enter changeMe for { assertNoError(p.Step(), t, "Step()") - locations, err := p.currentThread.Stacktrace(2) + locations, err := ThreadStacktrace(p.currentThread, 2) assertNoError(err, t, "Stacktrace()") if locations[0].Call.Fn == nil { t.Fatalf("Not on a function") @@ -1692,7 +1703,7 @@ func TestPackageVariables(t *testing.T) { withTestProcess("testvariables", t, func(p *Process, fixture protest.Fixture) { err := p.Continue() assertNoError(err, t, "Continue()") - scope, err := p.currentThread.GoroutineScope() + scope, err := GoroutineScope(p.currentThread) assertNoError(err, t, "Scope()") vars, err := scope.PackageVariables(normalLoadConfig) assertNoError(err, t, "PackageVariables()") @@ -1795,7 +1806,7 @@ func TestIssue462(t *testing.T) { }() assertNoError(p.Continue(), t, "Continue()") - _, err := p.currentThread.Stacktrace(40) + _, err := ThreadStacktrace(p.currentThread, 40) assertNoError(err, t, "Stacktrace()") }) } @@ -2141,7 +2152,7 @@ func TestStepConcurrentDirect(t *testing.T) { // loop exited break } - frames, err := p.currentThread.Stacktrace(20) + frames, err := ThreadStacktrace(p.currentThread, 20) if err != nil { t.Errorf("Could not get stacktrace of goroutine %d\n", p.selectedGoroutine.ID) } else { @@ -2316,7 +2327,7 @@ func TestStepOnCallPtrInstr(t *testing.T) { assertNoError(err, t, "PC()") regs, err := p.currentThread.Registers(false) assertNoError(err, t, "Registers()") - text, err := Disassemble(p.currentThread, regs, p.breakpoints, p.BinInfo(), pc, pc+maxInstructionLength) + text, err := disassemble(p.currentThread, regs, p.breakpoints, p.BinInfo(), pc, pc+maxInstructionLength) assertNoError(err, t, "Disassemble()") if text[0].IsCall() { found = true @@ -2452,7 +2463,7 @@ func BenchmarkTrace(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { assertNoError(p.Continue(), b, "Continue()") - s, err := p.currentThread.GoroutineScope() + s, err := GoroutineScope(p.currentThread) assertNoError(err, b, "Scope()") _, err = s.FunctionArguments(LoadConfig{false, 0, 64, 0, 3}) assertNoError(err, b, "FunctionArguments()") diff --git a/pkg/proc/stack.go b/pkg/proc/stack.go index fa31261fa7d274f2a4b0e6684c038138f0993c62..5f3af8a04dc0182f022320d7ecfe36b55a7cd7a8 100644 --- a/pkg/proc/stack.go +++ b/pkg/proc/stack.go @@ -38,39 +38,14 @@ type Stackframe struct { addrret uint64 } -// FrameToScope returns a new EvalScope for this frame -func (p *Process) FrameToScope(frame Stackframe) *EvalScope { - return &EvalScope{frame.Current.PC, frame.CFA, p.currentThread, nil, p.BinInfo()} -} - -// ReturnAddress returns the return address of the function -// this thread is executing. -func (t *Thread) ReturnAddress() (uint64, error) { - locations, err := t.Stacktrace(2) - if err != nil { - return 0, err - } - if len(locations) < 2 { - return 0, NoReturnAddr{locations[0].Current.Fn.BaseName()} - } - return locations[1].Current.PC, nil -} - -func (t *Thread) stackIterator(stkbar []savedLR, stkbarPos int) (*stackIterator, error) { - regs, err := t.Registers(false) - if err != nil { - return nil, err - } - return newStackIterator(&t.dbp.bi, t, regs.PC(), regs.SP(), regs.BP(), stkbar, stkbarPos), nil -} - // Stacktrace returns the stack trace for thread. // Note the locations in the array are return addresses not call addresses. -func (t *Thread) Stacktrace(depth int) ([]Stackframe, error) { - it, err := t.stackIterator(nil, -1) +func ThreadStacktrace(thread IThread, depth int) ([]Stackframe, error) { + regs, err := thread.Registers(false) if err != nil { return nil, err } + it := newStackIterator(thread.BinInfo(), thread, regs.PC(), regs.SP(), regs.BP(), nil, -1) return it.stacktrace(depth) } @@ -80,7 +55,11 @@ func (g *G) stackIterator() (*stackIterator, error) { return nil, err } if g.thread != nil { - return g.thread.stackIterator(stkbar, g.stkbarPos) + regs, err := g.thread.Registers(false) + if err != nil { + return nil, err + } + return newStackIterator(g.variable.bi, g.thread, regs.PC(), regs.SP(), regs.BP(), stkbar, g.stkbarPos), nil } return newStackIterator(g.variable.bi, g.variable.mem, g.PC, g.SP, 0, stkbar, g.stkbarPos), nil } @@ -95,13 +74,6 @@ func (g *G) Stacktrace(depth int) ([]Stackframe, error) { return it.stacktrace(depth) } -// GoroutineLocation returns the location of the given -// goroutine. -func (dbp *Process) GoroutineLocation(g *G) *Location { - f, l, fn := dbp.bi.PCToLine(g.PC) - return &Location{PC: g.PC, File: f, Line: l, Fn: fn} -} - // NullAddrError is an error for a null address. type NullAddrError struct{} diff --git a/pkg/proc/threads.go b/pkg/proc/threads.go index 1ddf84070b03b9dc7182e3ec8d78e99ce02c58be..7ca0fb90420c4cba47fcd776c86046a129847f0b 100644 --- a/pkg/proc/threads.go +++ b/pkg/proc/threads.go @@ -8,7 +8,6 @@ import ( "go/ast" "path/filepath" "reflect" - "runtime" "strings" "golang.org/x/debug/dwarf" @@ -32,6 +31,23 @@ type Thread struct { os *OSSpecificDetails } +// IThread represents a thread. +type IThread interface { + memoryReadWriter + Location() (*Location, error) + // Breakpoint will return the breakpoint that this thread is stopped at or + // nil if the thread is not stopped at any breakpoint. + // Active will be true if the thread is stopped at a breakpoint and the + // breakpoint's condition is met. + // If there was an error evaluating the breakpoint's condition it will be + // returned as condErr + Breakpoint() (breakpoint *Breakpoint, active bool, condErr error) + ThreadID() int + Registers(floatingPoint bool) (Registers, error) + Arch() Arch + BinInfo() *BinaryInfo +} + // Location represents the location of a thread. // Holds information on the current instruction // address, the source file:line, and the function. @@ -116,6 +132,14 @@ func (thread *Thread) Location() (*Location, error) { return &Location{PC: pc, File: f, Line: l, Fn: fn}, nil } +func (thread *Thread) Arch() Arch { + return thread.dbp.bi.arch +} + +func (thread *Thread) BinInfo() *BinaryInfo { + return &thread.dbp.bi +} + // ThreadBlockedError is returned when the thread // is blocked in the scheduler. type ThreadBlockedError struct{} @@ -133,7 +157,7 @@ func topframe(g *G, thread *Thread) (Stackframe, error) { if thread.blocked() { return Stackframe{}, ThreadBlockedError{} } - frames, err = thread.Stacktrace(0) + frames, err = ThreadStacktrace(thread, 0) } else { frames, err = g.Stacktrace(0) } @@ -167,17 +191,17 @@ func (dbp *Process) next(stepInto bool) error { }() csource := filepath.Ext(topframe.Current.File) != ".go" - thread := dbp.currentThread + var thread memoryReadWriter = dbp.currentThread var regs Registers if dbp.selectedGoroutine != nil && dbp.selectedGoroutine.thread != nil { thread = dbp.selectedGoroutine.thread - regs, err = thread.Registers(false) + regs, err = dbp.selectedGoroutine.thread.Registers(false) if err != nil { return err } } - text, err := Disassemble(thread, regs, dbp.breakpoints, &dbp.bi, topframe.FDE.Begin(), topframe.FDE.End()) + text, err := disassemble(thread, regs, dbp.breakpoints, dbp.BinInfo(), topframe.FDE.Begin(), topframe.FDE.End()) if err != nil && stepInto { return err } @@ -337,33 +361,30 @@ func (thread *Thread) SetPC(pc uint64) error { return regs.SetPC(thread, pc) } -func (thread *Thread) getGVariable() (*Variable, error) { +func getGVariable(thread IThread) (*Variable, error) { + arch := thread.Arch() regs, err := thread.Registers(false) if err != nil { return nil, err } - if thread.dbp.bi.arch.GStructOffset() == 0 { + if arch.GStructOffset() == 0 { // GetG was called through SwitchThread / updateThreadList during initialization // thread.dbp.arch isn't setup yet (it needs a current thread to read global variables from) return nil, fmt.Errorf("g struct offset not initialized") } - gaddrbs, err := thread.readMemory(uintptr(regs.TLS()+thread.dbp.bi.arch.GStructOffset()), thread.dbp.bi.arch.PtrSize()) + gaddrbs, err := thread.readMemory(uintptr(regs.TLS()+arch.GStructOffset()), arch.PtrSize()) if err != nil { return nil, err } gaddr := uintptr(binary.LittleEndian.Uint64(gaddrbs)) - // On Windows, the value at TLS()+GStructOffset() is a - // pointer to the G struct. - needsDeref := runtime.GOOS == "windows" - - return thread.newGVariable(gaddr, needsDeref) + return newGVariable(thread, gaddr, arch.DerefTLS()) } -func (thread *Thread) newGVariable(gaddr uintptr, deref bool) (*Variable, error) { - typ, err := thread.dbp.bi.findType("runtime.g") +func newGVariable(thread IThread, gaddr uintptr, deref bool) (*Variable, error) { + typ, err := thread.BinInfo().findType("runtime.g") if err != nil { return nil, err } @@ -371,12 +392,12 @@ func (thread *Thread) newGVariable(gaddr uintptr, deref bool) (*Variable, error) name := "" if deref { - typ = &dwarf.PtrType{dwarf.CommonType{int64(thread.dbp.bi.arch.PtrSize()), "", reflect.Ptr, 0}, typ} + typ = &dwarf.PtrType{dwarf.CommonType{int64(thread.Arch().PtrSize()), "", reflect.Ptr, 0}, typ} } else { name = "runtime.curg" } - return thread.newVariable(name, gaddr, typ), nil + return newVariableFromThread(thread, name, gaddr, typ), nil } // GetG returns information on the G (goroutine) that is executing on this thread. @@ -393,8 +414,8 @@ func (thread *Thread) newGVariable(gaddr uintptr, deref bool) (*Variable, error) // // In order to get around all this craziness, we read the address of the G structure for // the current thread from the thread local storage area. -func (thread *Thread) GetG() (g *G, err error) { - gaddr, err := thread.getGVariable() +func GetG(thread IThread) (g *G, err error) { + gaddr, err := getGVariable(thread) if err != nil { return nil, err } @@ -433,31 +454,31 @@ func (thread *Thread) Halt() (err error) { } // ThreadScope returns an EvalScope for this thread. -func (thread *Thread) ThreadScope() (*EvalScope, error) { - locations, err := thread.Stacktrace(0) +func ThreadScope(thread IThread) (*EvalScope, error) { + locations, err := ThreadStacktrace(thread, 0) if err != nil { return nil, err } if len(locations) < 1 { return nil, errors.New("could not decode first frame") } - return &EvalScope{locations[0].Current.PC, locations[0].CFA, thread, nil, thread.dbp.BinInfo()}, nil + return &EvalScope{locations[0].Current.PC, locations[0].CFA, thread, nil, thread.BinInfo()}, nil } // GoroutineScope returns an EvalScope for the goroutine running on this thread. -func (thread *Thread) GoroutineScope() (*EvalScope, error) { - locations, err := thread.Stacktrace(0) +func GoroutineScope(thread IThread) (*EvalScope, error) { + locations, err := ThreadStacktrace(thread, 0) if err != nil { return nil, err } if len(locations) < 1 { return nil, errors.New("could not decode first frame") } - gvar, err := thread.getGVariable() + gvar, err := getGVariable(thread) if err != nil { return nil, err } - return &EvalScope{locations[0].Current.PC, locations[0].CFA, thread, gvar, thread.dbp.BinInfo()}, nil + return &EvalScope{locations[0].Current.PC, locations[0].CFA, thread, gvar, thread.BinInfo()}, nil } // SetCurrentBreakpoint sets the current breakpoint that this @@ -475,7 +496,7 @@ func (thread *Thread) SetCurrentBreakpoint() error { } thread.BreakpointConditionMet, thread.BreakpointConditionError = bp.checkCondition(thread) if thread.onTriggeredBreakpoint() { - if g, err := thread.GetG(); err == nil { + if g, err := GetG(thread); err == nil { thread.CurrentBreakpoint.HitCount[g.ID]++ } thread.CurrentBreakpoint.TotalHitCount++ @@ -527,3 +548,11 @@ func (thread *Thread) onNextGoroutine() (bool, error) { } return bp.checkCondition(thread) } + +func (th *Thread) Breakpoint() (*Breakpoint, bool, error) { + return th.CurrentBreakpoint, (th.CurrentBreakpoint != nil && th.BreakpointConditionMet), th.BreakpointConditionError +} + +func (th *Thread) ThreadID() int { + return th.ID +} diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go index b9d208a00620d16c356ffb0d75720f40dbe867f1..9a3fac34b8f72fc03ead2a64a5fa43773f6cc59b 100644 --- a/pkg/proc/variables.go +++ b/pkg/proc/variables.go @@ -129,7 +129,7 @@ type G struct { CurrentLoc Location // Thread that this goroutine is currently allocated to - thread *Thread + thread IThread variable *Variable } @@ -157,8 +157,8 @@ func (scope *EvalScope) newVariable(name string, addr uintptr, dwarfType dwarf.T return newVariable(name, addr, dwarfType, scope.bi, scope.Mem) } -func (t *Thread) newVariable(name string, addr uintptr, dwarfType dwarf.Type) *Variable { - return newVariable(name, addr, dwarfType, t.dbp.BinInfo(), t) +func newVariableFromThread(t IThread, name string, addr uintptr, dwarfType dwarf.Type) *Variable { + return newVariable(name, addr, dwarfType, t.BinInfo(), t) } func (v *Variable) newVariable(name string, addr uintptr, dwarfType dwarf.Type) *Variable { diff --git a/pkg/target/target.go b/pkg/target/target.go index 157d1795dc5864d3fdb77323dcf6e7ea80839e13..d6b48cbb32a8cb4fa2b55032a803a98b7e03671d 100644 --- a/pkg/target/target.go +++ b/pkg/target/target.go @@ -13,7 +13,6 @@ type Interface interface { Info ProcessManipulation BreakpointManipulation - VariableEval } // Info is an interface that provides general information on the target. @@ -26,10 +25,6 @@ type Info interface { ThreadInfo GoroutineInfo - // Disassemble disassembles target memory between startPC and endPC, marking - // the current instruction being executed in goroutine g. - Disassemble(g *proc.G, startPC, endPC uint64) ([]proc.AsmInstruction, error) - // FindFileLocation returns the address of the first instruction belonging // to line lineNumber in file fileName. FindFileLocation(fileName string, lineNumber int) (uint64, error) @@ -58,15 +53,15 @@ type Info interface { // ThreadInfo is an interface for getting information on active threads // in the process. type ThreadInfo interface { - Threads() map[int]*proc.Thread - CurrentThread() *proc.Thread + FindThread(threadID int) (proc.IThread, bool) + ThreadList() []proc.IThread + CurrentThread() proc.IThread } // GoroutineInfo is an interface for getting information on running goroutines. type GoroutineInfo interface { GoroutinesInfo() ([]*proc.G, error) SelectedGoroutine() *proc.G - FindGoroutine(int) (*proc.G, error) } // ProcessManipulation is an interface for changing the execution state of a process. diff --git a/service/api/conversions.go b/service/api/conversions.go index e407d0e6617ee9fb4c452960c488823c4b6fce4f..ddada85e83c29bcc73e449f7953f83c6bf5c123a 100644 --- a/service/api/conversions.go +++ b/service/api/conversions.go @@ -47,7 +47,7 @@ func ConvertBreakpoint(bp *proc.Breakpoint) *Breakpoint { // ConvertThread converts a proc.Thread into an // api thread. -func ConvertThread(th *proc.Thread) *Thread { +func ConvertThread(th proc.IThread) *Thread { var ( function *Function file string @@ -66,16 +66,16 @@ func ConvertThread(th *proc.Thread) *Thread { var bp *Breakpoint - if th.CurrentBreakpoint != nil && th.BreakpointConditionMet { - bp = ConvertBreakpoint(th.CurrentBreakpoint) + if b, active, _ := th.Breakpoint(); active { + bp = ConvertBreakpoint(b) } - if g, _ := th.GetG(); g != nil { + if g, _ := proc.GetG(th); g != nil { gid = g.ID } return &Thread{ - ID: th.ID, + ID: th.ThreadID(), PC: pc, File: file, Line: line, @@ -204,7 +204,7 @@ func ConvertGoroutine(g *proc.G) *Goroutine { th := g.Thread() tid := 0 if th != nil { - tid = th.ID + tid = th.ThreadID() } return &Goroutine{ ID: g.ID, diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index 90d97b4cdf91565ea795615a83b2faa09c741e54..6c9eeaa8848f91daa8ba8ba6c294084899b4a791 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -179,10 +179,10 @@ func (d *Debugger) state() (*api.DebuggerState, error) { Exited: d.target.Exited(), } - for i := range d.target.Threads() { - th := api.ConvertThread(d.target.Threads()[i]) + for _, thread := range d.target.ThreadList() { + th := api.ConvertThread(thread) state.Threads = append(state.Threads, th) - if i == d.target.CurrentThread().ID { + if thread.ThreadID() == d.target.CurrentThread().ThreadID() { state.CurrentThread = th } } @@ -372,7 +372,7 @@ func (d *Debugger) Threads() ([]*api.Thread, error) { return nil, &proc.ProcessExitedError{} } threads := []*api.Thread{} - for _, th := range d.target.Threads() { + for _, th := range d.target.ThreadList() { threads = append(threads, api.ConvertThread(th)) } return threads, nil @@ -387,8 +387,8 @@ func (d *Debugger) FindThread(id int) (*api.Thread, error) { return nil, &proc.ProcessExitedError{} } - for _, th := range d.target.Threads() { - if th.ID == id { + for _, th := range d.target.ThreadList() { + if th.ThreadID() == id { return api.ConvertThread(th), nil } } @@ -472,7 +472,7 @@ func (d *Debugger) collectBreakpointInformation(state *api.DebuggerState) error state.Threads[i].BreakpointInfo = bpi if bp.Goroutine { - g, err := d.target.CurrentThread().GetG() + g, err := proc.GetG(d.target.CurrentThread()) if err != nil { return err } @@ -480,7 +480,7 @@ func (d *Debugger) collectBreakpointInformation(state *api.DebuggerState) error } if bp.Stacktrace > 0 { - rawlocs, err := d.target.CurrentThread().Stacktrace(bp.Stacktrace) + rawlocs, err := proc.ThreadStacktrace(d.target.CurrentThread(), bp.Stacktrace) if err != nil { return err } @@ -490,7 +490,11 @@ func (d *Debugger) collectBreakpointInformation(state *api.DebuggerState) error } } - s, err := d.target.Threads()[state.Threads[i].ID].GoroutineScope() + thread, found := d.target.FindThread(state.Threads[i].ID) + if !found { + return fmt.Errorf("could not find thread %d", state.Threads[i].ID) + } + s, err := proc.GoroutineScope(thread) if err != nil { return err } @@ -598,11 +602,11 @@ func (d *Debugger) PackageVariables(threadID int, filter string, cfg proc.LoadCo } vars := []api.Variable{} - thread, found := d.target.Threads()[threadID] + thread, found := d.target.FindThread(threadID) if !found { return nil, fmt.Errorf("couldn't find thread %d", threadID) } - scope, err := thread.ThreadScope() + scope, err := proc.ThreadScope(thread) if err != nil { return nil, err } @@ -623,7 +627,7 @@ func (d *Debugger) Registers(threadID int, floatingPoint bool) (api.Registers, e d.processMutex.Lock() defer d.processMutex.Unlock() - thread, found := d.target.Threads()[threadID] + thread, found := d.target.FindThread(threadID) if !found { return nil, fmt.Errorf("couldn't find thread %d", threadID) } @@ -647,7 +651,7 @@ func (d *Debugger) LocalVariables(scope api.EvalScope, cfg proc.LoadConfig) ([]a d.processMutex.Lock() defer d.processMutex.Unlock() - s, err := d.target.ConvertEvalScope(scope.GoroutineID, scope.Frame) + s, err := proc.ConvertEvalScope(d.target, scope.GoroutineID, scope.Frame) if err != nil { return nil, err } @@ -663,7 +667,7 @@ func (d *Debugger) FunctionArguments(scope api.EvalScope, cfg proc.LoadConfig) ( d.processMutex.Lock() defer d.processMutex.Unlock() - s, err := d.target.ConvertEvalScope(scope.GoroutineID, scope.Frame) + s, err := proc.ConvertEvalScope(d.target, scope.GoroutineID, scope.Frame) if err != nil { return nil, err } @@ -680,7 +684,7 @@ func (d *Debugger) EvalVariableInScope(scope api.EvalScope, symbol string, cfg p d.processMutex.Lock() defer d.processMutex.Unlock() - s, err := d.target.ConvertEvalScope(scope.GoroutineID, scope.Frame) + s, err := proc.ConvertEvalScope(d.target, scope.GoroutineID, scope.Frame) if err != nil { return nil, err } @@ -697,7 +701,7 @@ func (d *Debugger) SetVariableInScope(scope api.EvalScope, symbol, value string) d.processMutex.Lock() defer d.processMutex.Unlock() - s, err := d.target.ConvertEvalScope(scope.GoroutineID, scope.Frame) + s, err := proc.ConvertEvalScope(d.target, scope.GoroutineID, scope.Frame) if err != nil { return err } @@ -729,13 +733,13 @@ func (d *Debugger) Stacktrace(goroutineID, depth int, cfg *proc.LoadConfig) ([]a var rawlocs []proc.Stackframe - g, err := d.target.FindGoroutine(goroutineID) + g, err := proc.FindGoroutine(d.target, goroutineID) if err != nil { return nil, err } if g == nil { - rawlocs, err = d.target.CurrentThread().Stacktrace(depth) + rawlocs, err = proc.ThreadStacktrace(d.target.CurrentThread(), depth) } else { rawlocs, err = g.Stacktrace(depth) } @@ -752,7 +756,7 @@ func (d *Debugger) convertStacktrace(rawlocs []proc.Stackframe, cfg *proc.LoadCo frame := api.Stackframe{Location: api.ConvertLocation(rawlocs[i].Call)} if cfg != nil && rawlocs[i].Current.Fn != nil { var err error - scope := d.target.FrameToScope(rawlocs[i]) + scope := proc.FrameToScope(d.target, rawlocs[i]) locals, err := scope.LocalVariables(*cfg) if err != nil { return nil, err @@ -781,7 +785,7 @@ func (d *Debugger) FindLocation(scope api.EvalScope, locStr string) ([]api.Locat return nil, err } - s, _ := d.target.ConvertEvalScope(scope.GoroutineID, scope.Frame) + s, _ := proc.ConvertEvalScope(d.target, scope.GoroutineID, scope.Frame) locs, err := loc.Find(d, s, locStr) for i := range locs { @@ -808,12 +812,12 @@ func (d *Debugger) Disassemble(scope api.EvalScope, startPC, endPC uint64, flavo endPC = fn.End } - g, err := d.target.FindGoroutine(scope.GoroutineID) + g, err := proc.FindGoroutine(d.target, scope.GoroutineID) if err != nil { return nil, err } - insts, err := d.target.Disassemble(g, startPC, endPC) + insts, err := proc.Disassemble(d.target, g, startPC, endPC) if err != nil { return nil, err } diff --git a/service/test/variables_test.go b/service/test/variables_test.go index a704559b98fad93cd01ac554306fe21594d7b571..e6d0abf64ebfee590b471da23269184c6a23303a 100644 --- a/service/test/variables_test.go +++ b/service/test/variables_test.go @@ -54,7 +54,7 @@ func assertVariable(t *testing.T, variable *proc.Variable, expected varTest) { } func evalVariable(p *proc.Process, symbol string, cfg proc.LoadConfig) (*proc.Variable, error) { - scope, err := p.CurrentThread().GoroutineScope() + scope, err := proc.GoroutineScope(p.CurrentThread()) if err != nil { return nil, err } @@ -68,7 +68,7 @@ func (tc *varTest) alternateVarTest() varTest { } func setVariable(p *proc.Process, symbol, value string) error { - scope, err := p.CurrentThread().GoroutineScope() + scope, err := proc.GoroutineScope(p.CurrentThread()) if err != nil { return err } @@ -348,7 +348,7 @@ func TestLocalVariables(t *testing.T) { assertNoError(err, t, "Continue() returned an error") for _, tc := range testcases { - scope, err := p.CurrentThread().GoroutineScope() + scope, err := proc.GoroutineScope(p.CurrentThread()) assertNoError(err, t, "AsScope()") vars, err := tc.fn(scope, pnormalLoadConfig) assertNoError(err, t, "LocalVariables() returned an error")