diff --git a/proc/eval.go b/proc/eval.go index 58b91322..2b5a14b8 100644 --- a/proc/eval.go +++ b/proc/eval.go @@ -54,7 +54,9 @@ func (scope *EvalScope) evalAST(t ast.Expr) (*Variable, error) { case *ast.SelectorExpr: // . // try to interpret the selector as a package variable if maybePkg, ok := node.X.(*ast.Ident); ok { - if v, err := scope.packageVarAddr(maybePkg.Name + "." + node.Sel.Name); err == nil { + if maybePkg.Name == "runtime" && node.Sel.Name == "curg" { + return scope.Thread.getGVariable() + } else if v, err := scope.packageVarAddr(maybePkg.Name + "." + node.Sel.Name); err == nil { return v, nil } } diff --git a/proc/proc.go b/proc/proc.go index 69f34f41..5f991445 100644 --- a/proc/proc.go +++ b/proc/proc.go @@ -497,7 +497,11 @@ func (dbp *Process) GoroutinesInfo() ([]*G, error) { allgptr := binary.LittleEndian.Uint64(faddr) for i := uint64(0); i < allglen; i++ { - g, err := parseG(dbp.CurrentThread, allgptr+(i*uint64(dbp.arch.PtrSize())), true) + gvar, err := dbp.CurrentThread.newGVariable(uintptr(allgptr+(i*uint64(dbp.arch.PtrSize()))), true) + if err != nil { + return nil, err + } + g, err := gvar.parseG() if err != nil { return nil, err } diff --git a/proc/threads.go b/proc/threads.go index 3c16b012..17bffe73 100644 --- a/proc/threads.go +++ b/proc/threads.go @@ -1,6 +1,7 @@ package proc import ( + "debug/dwarf" "debug/gosym" "encoding/binary" "fmt" @@ -247,6 +248,48 @@ func (thread *Thread) SetPC(pc uint64) error { return regs.SetPC(thread, pc) } +func (thread *Thread) getGVariable() (*Variable, error) { + regs, err := thread.Registers() + if err != nil { + return nil, err + } + + if thread.dbp.arch.GStructOffset() == 0 { + // GetG was called through SwitchThread / updateThreadList during initialization + // thread.dbp.arch isn't setup yet (it needs a CurrentThread to read global variables from) + return nil, fmt.Errorf("g struct offset not initialized") + } + + gaddrbs, err := thread.readMemory(uintptr(regs.TLS()+thread.dbp.arch.GStructOffset()), thread.dbp.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) +} + +func (thread *Thread) newGVariable(gaddr uintptr, deref bool) (*Variable, error) { + typ, err := thread.dbp.findType("runtime.g") + if err != nil { + return nil, err + } + + name := "" + + if deref { + typ = &dwarf.PtrType{dwarf.CommonType{int64(thread.dbp.arch.PtrSize()), ""}, typ} + } else { + name = "runtime.curg" + } + + return thread.newVariable(name, gaddr, typ), nil +} + // GetG returns information on the G (goroutine) that is executing on this thread. // // The G structure for a thread is stored in thread local storage. Here we simply @@ -262,26 +305,12 @@ func (thread *Thread) SetPC(pc uint64) 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) { - regs, err := thread.Registers() + gaddr, err := thread.getGVariable() if err != nil { return nil, err } - if thread.dbp.arch.GStructOffset() == 0 { - // GetG was called through SwitchThread / updateThreadList during initialization - // thread.dbp.arch isn't setup yet (it needs a CurrentThread to read global variables from) - return nil, fmt.Errorf("g struct offset not initialized") - } - gaddrbs, err := thread.readMemory(uintptr(regs.TLS()+thread.dbp.arch.GStructOffset()), thread.dbp.arch.PtrSize()) - if err != nil { - return nil, err - } - gaddr := binary.LittleEndian.Uint64(gaddrbs) - - // On Windows, the value at TLS()+GStructOffset() is a - // pointer to the G struct. - needsDeref := runtime.GOOS == "windows" - - g, err = parseG(thread, gaddr, needsDeref) + + g, err = gaddr.parseG() if err == nil { g.thread = thread } diff --git a/proc/variables.go b/proc/variables.go index 1daf30df..d1f485bd 100644 --- a/proc/variables.go +++ b/proc/variables.go @@ -143,6 +143,10 @@ func (scope *EvalScope) newVariable(name string, addr uintptr, dwarfType dwarf.T return newVariable(name, addr, dwarfType, scope.Thread.dbp, scope.Thread) } +func (t *Thread) newVariable(name string, addr uintptr, dwarfType dwarf.Type) *Variable { + return newVariable(name, addr, dwarfType, t.dbp, t) +} + func (v *Variable) newVariable(name string, addr uintptr, dwarfType dwarf.Type) *Variable { return newVariable(name, addr, dwarfType, v.dbp, v.mem) } @@ -338,32 +342,40 @@ func (ng NoGError) Error() string { return fmt.Sprintf("no G executing on thread %d", ng.tid) } -func parseG(thread *Thread, gaddr uint64, deref bool) (*G, error) { - initialInstructions := make([]byte, thread.dbp.arch.PtrSize()+1) +func (gvar *Variable) parseG() (*G, error) { + mem := gvar.mem + dbp := gvar.dbp + gaddr := uint64(gvar.Addr) + _, deref := gvar.RealType.(*dwarf.PtrType) + + initialInstructions := make([]byte, dbp.arch.PtrSize()+1) initialInstructions[0] = op.DW_OP_addr binary.LittleEndian.PutUint64(initialInstructions[1:], gaddr) if deref { - gaddrbytes, err := thread.readMemory(uintptr(gaddr), thread.dbp.arch.PtrSize()) + gaddrbytes, err := mem.readMemory(uintptr(gaddr), dbp.arch.PtrSize()) if err != nil { return nil, fmt.Errorf("error derefing *G %s", err) } initialInstructions = append([]byte{op.DW_OP_addr}, gaddrbytes...) gaddr = binary.LittleEndian.Uint64(gaddrbytes) if gaddr == 0 { - return nil, NoGError{tid: thread.ID} + id := 0 + if thread, ok := mem.(*Thread); ok { + id = thread.ID + } + return nil, NoGError{tid: id} } } - rdr := thread.dbp.DwarfReader() + rdr := dbp.DwarfReader() rdr.Seek(0) entry, err := rdr.SeekToTypeNamed("runtime.g") if err != nil { return nil, err } - var mem memoryReadWriter = thread - if gtype, err := thread.dbp.dwarf.Type(entry.Offset); err == nil { - mem = cacheMemory(thread, uintptr(gaddr), int(gtype.Size())) + if gtype, err := dbp.dwarf.Type(entry.Offset); err == nil { + mem = cacheMemory(mem, uintptr(gaddr), int(gtype.Size())) } // Parse defer @@ -373,7 +385,7 @@ func parseG(thread *Thread, gaddr uint64, deref bool) (*G, error) { } var deferPC uint64 // Dereference *defer pointer - deferAddrBytes, err := mem.readMemory(uintptr(deferAddr), thread.dbp.arch.PtrSize()) + deferAddrBytes, err := mem.readMemory(uintptr(deferAddr), dbp.arch.PtrSize()) if err != nil { return nil, fmt.Errorf("error derefing defer %s", err) } @@ -412,7 +424,7 @@ func parseG(thread *Thread, gaddr uint64, deref bool) (*G, error) { if err != nil { return nil, err } - pc, err := readUintRaw(mem, uintptr(schedAddr+uint64(thread.dbp.arch.PtrSize())), 8) + pc, err := readUintRaw(mem, uintptr(schedAddr+uint64(dbp.arch.PtrSize())), 8) if err != nil { return nil, err } @@ -436,7 +448,7 @@ func parseG(thread *Thread, gaddr uint64, deref bool) (*G, error) { if err != nil { return nil, err } - waitreason, _, err := readString(mem, thread.dbp.arch, uintptr(waitReasonAddr)) + waitreason, _, err := readString(mem, dbp.arch, uintptr(waitReasonAddr)) if err != nil { return nil, err } @@ -450,7 +462,7 @@ func parseG(thread *Thread, gaddr uint64, deref bool) (*G, error) { return nil, err } - f, l, fn := thread.dbp.goSymTable.PCToLine(pc) + f, l, fn := dbp.goSymTable.PCToLine(pc) g := &G{ ID: int(goid), GoPC: gopc, @@ -460,7 +472,7 @@ func parseG(thread *Thread, gaddr uint64, deref bool) (*G, error) { WaitReason: waitreason, DeferPC: deferPC, Status: atomicStatus, - dbp: thread.dbp, + dbp: dbp, } return g, nil }