提交 35405583 编写于 作者: A Alessandro Arzilli 提交者: Derek Parker

proc: next, stepout should work on recursive goroutines (#831)

Before this commit our temp breakpoints only checked that we would stay
on the same goroutine.
However this isn't enough for recursive functions we must check that we
stay on the same goroutine AND on the same stack frame (or, in the case
of the StepOut breakpoint, the previous stack frame).

This commit:
1. adds a new synthetic variable runtime.frameoff that returns the
   offset of the current frame from the base of the call stack.
   This is similar to runtime.curg
2. Changes the condition used for breakpoints on the lines of the
   current function to check that runtime.frameoff hasn't changed.
3. Changes the condition used for breakpoints on the return address to
   check that runtime.frameoff corresponds to the previous frame in the
   stack.
4. All other temporary breakpoints (the step-into breakpoints and defer
   breakpoints) remain unchanged.

Fixes #828
上级 8d3e74a4
package main
import "fmt"
// Increment Natural number y
func Increment(y uint) uint {
if y == 0 {
return 1
}
if y%2 == 1 {
return (2 * Increment(y/2))
}
return y + 1
}
func main() {
fmt.Printf("%d\n", Increment(3))
}
......@@ -117,11 +117,15 @@ func (bp *Breakpoint) CheckCondition(thread Thread) (bool, error) {
}
}
}
return evalBreakpointCondition(thread, bp.Cond)
}
func evalBreakpointCondition(thread Thread, cond ast.Expr) (bool, error) {
scope, err := GoroutineScope(thread)
if err != nil {
return true, err
}
v, err := scope.evalAST(bp.Cond)
v, err := scope.evalAST(cond)
if err != nil {
return true, fmt.Errorf("error evaluating expression: %v", err)
}
......
......@@ -248,6 +248,10 @@ func (t *Thread) Blocked() bool {
return false
}
func (t *Thread) SetCurrentBreakpoint() error {
return nil
}
func (p *Process) Breakpoints() map[uint64]*proc.Breakpoint {
return p.breakpoints
}
......
......@@ -71,6 +71,8 @@ func (scope *EvalScope) evalAST(t ast.Expr) (*Variable, error) {
return nilVariable, nil
}
return scope.Gvar.clone(), nil
} else if maybePkg.Name == "runtime" && node.Sel.Name == "frameoff" {
return newConstant(constant.MakeInt64(scope.CFA-int64(scope.StackHi)), scope.Mem), nil
} else if v, err := scope.packageVarAddr(maybePkg.Name + "." + node.Sel.Name); err == nil {
return v, nil
}
......@@ -851,7 +853,7 @@ func (scope *EvalScope) evalBinary(node *ast.BinaryExpr) (*Variable, error) {
r := xv.newVariable("", 0, typ)
r.Value = rc
if r.Kind == reflect.String {
r.Len = xv.Len+yv.Len
r.Len = xv.Len + yv.Len
}
return r, nil
}
......
......@@ -13,7 +13,7 @@ type moduleData struct {
func loadModuleData(bi *BinaryInfo, mem MemoryReadWriter) (err error) {
bi.loadModuleDataOnce.Do(func() {
scope := &EvalScope{0, 0, mem, nil, bi}
scope := &EvalScope{0, 0, mem, nil, bi, 0}
var md *Variable
md, err = scope.packageVarAddr("runtime.firstmoduledata")
if err != nil {
......@@ -119,7 +119,7 @@ func resolveNameOff(bi *BinaryInfo, typeAddr uintptr, off uintptr, mem MemoryRea
}
func reflectOffsMapAccess(bi *BinaryInfo, off uintptr, mem MemoryReadWriter) (*Variable, error) {
scope := &EvalScope{0, 0, mem, nil, bi}
scope := &EvalScope{0, 0, mem, nil, bi, 0}
reflectOffs, err := scope.packageVarAddr("runtime.reflectOffs")
if err != nil {
return nil, err
......
......@@ -254,19 +254,41 @@ func SameGoroutineCondition(g *G) ast.Expr {
}
}
func frameoffCondition(frameoff int64) ast.Expr {
return &ast.BinaryExpr{
Op: token.EQL,
X: &ast.SelectorExpr{
X: &ast.Ident{Name: "runtime"},
Sel: &ast.Ident{Name: "frameoff"},
},
Y: &ast.BasicLit{Kind: token.INT, Value: strconv.FormatInt(frameoff, 10)},
}
}
func andFrameoffCondition(cond ast.Expr, frameoff int64) ast.Expr {
if cond == nil {
return nil
}
return &ast.BinaryExpr{
Op: token.LAND,
X: cond,
Y: frameoffCondition(frameoff),
}
}
// StepOut will continue until the current goroutine exits the
// function currently being executed or a deferred function is executed
func StepOut(dbp Process) error {
selg := dbp.SelectedGoroutine()
curthread := dbp.CurrentThread()
cond := SameGoroutineCondition(selg)
topframe, err := topframe(selg, curthread)
topframe, retframe, err := topframe(selg, curthread)
if err != nil {
return err
}
pcs := []uint64{}
sameGCond := SameGoroutineCondition(selg)
retFrameCond := andFrameoffCondition(sameGCond, retframe.CFA-int64(retframe.StackHi))
var deferpc uint64 = 0
if filepath.Ext(topframe.Current.File) == ".go" {
......@@ -278,7 +300,6 @@ func StepOut(dbp Process) error {
if err != nil {
return err
}
pcs = append(pcs, deferpc)
}
}
}
......@@ -288,7 +309,7 @@ func StepOut(dbp Process) error {
}
if deferpc != 0 && deferpc != topframe.Current.PC {
bp, err := dbp.SetBreakpoint(deferpc, NextDeferBreakpoint, cond)
bp, err := dbp.SetBreakpoint(deferpc, NextDeferBreakpoint, sameGCond)
if err != nil {
if _, ok := err.(BreakpointExistsError); !ok {
dbp.ClearInternalBreakpoints()
......@@ -304,11 +325,19 @@ func StepOut(dbp Process) error {
}
if topframe.Ret != 0 {
if err := setInternalBreakpoints(dbp, topframe.Current.PC, []uint64{topframe.Ret}, NextBreakpoint, cond); err != nil {
return err
_, err := dbp.SetBreakpoint(topframe.Ret, NextBreakpoint, retFrameCond)
if err != nil {
if _, isexists := err.(BreakpointExistsError); !isexists {
dbp.ClearInternalBreakpoints()
return err
}
}
}
if bp, _, _ := curthread.Breakpoint(); bp == nil {
curthread.SetCurrentBreakpoint()
}
return Continue(dbp)
}
......@@ -402,7 +431,7 @@ func GoroutinesInfo(dbp Process) ([]*G, error) {
}
func GetGoInformation(p Process) (ver GoVersion, isextld bool, err error) {
scope := &EvalScope{0, 0, p.CurrentThread(), nil, p.BinInfo()}
scope := &EvalScope{0, 0, p.CurrentThread(), nil, p.BinInfo(), 0}
vv, err := scope.packageVarAddr("runtime.buildVersion")
if err != nil {
return ver, false, fmt.Errorf("Could not determine version number: %v", err)
......@@ -482,10 +511,10 @@ func ConvertEvalScope(dbp Process, gid, frame int) (*EvalScope, error) {
PC, CFA := locs[frame].Current.PC, locs[frame].CFA
return &EvalScope{PC, CFA, thread, g.variable, dbp.BinInfo()}, nil
return &EvalScope{PC, CFA, thread, g.variable, dbp.BinInfo(), g.stackhi}, nil
}
// FrameToScope returns a new EvalScope for this frame
func FrameToScope(p Process, frame Stackframe) *EvalScope {
return &EvalScope{frame.Current.PC, frame.CFA, p.CurrentThread(), nil, p.BinInfo()}
return &EvalScope{frame.Current.PC, frame.CFA, p.CurrentThread(), nil, p.BinInfo(), frame.StackHi}
}
......@@ -533,6 +533,7 @@ func TestNextConcurrentVariant2(t *testing.T) {
initVval, _ := constant.Int64Val(initV.Value)
assertNoError(err, t, "EvalVariable")
for _, tc := range testcases {
t.Logf("test case %v", tc)
g, err := proc.GetG(p.CurrentThread())
assertNoError(err, t, "GetG()")
if p.SelectedGoroutine().ID != g.ID {
......@@ -2248,7 +2249,7 @@ func TestStepOut(t *testing.T) {
f, lno = currentLineNumber(p, t)
if lno != 35 {
t.Fatalf("wrong line number %s:%d, expected %d", f, lno, 34)
t.Fatalf("wrong line number %s:%d, expected %d", f, lno, 35)
}
})
}
......@@ -2880,3 +2881,56 @@ func TestEnvironment(t *testing.T) {
}
})
}
func getFrameOff(p proc.Process, t *testing.T) int64 {
frameoffvar, err := evalVariable(p, "runtime.frameoff")
assertNoError(err, t, "EvalVariable(runtime.frameoff)")
frameoff, _ := constant.Int64Val(frameoffvar.Value)
return frameoff
}
func TestRecursiveNext(t *testing.T) {
protest.AllowRecording(t)
testcases := []nextTest{
{6, 7},
{7, 10},
{10, 11},
{11, 17},
}
testseq("increment", contNext, testcases, "main.Increment", t)
withTestProcess("increment", t, func(p proc.Process, fixture protest.Fixture) {
bp, err := setFunctionBreakpoint(p, "main.Increment")
assertNoError(err, t, "setFunctionBreakpoint")
assertNoError(proc.Continue(p), t, "Continue")
_, err = p.ClearBreakpoint(bp.Addr)
assertNoError(err, t, "ClearBreakpoint")
assertNoError(proc.Next(p), t, "Next 1")
assertNoError(proc.Next(p), t, "Next 2")
assertNoError(proc.Next(p), t, "Next 3")
frameoff0 := getFrameOff(p, t)
assertNoError(proc.Step(p), t, "Step")
frameoff1 := getFrameOff(p, t)
if frameoff0 == frameoff1 {
t.Fatalf("did not step into function?")
}
_, ln := currentLineNumber(p, t)
if ln != 6 {
t.Fatalf("program did not continue to expected location %d", ln)
}
assertNoError(proc.Next(p), t, "Next 4")
_, ln = currentLineNumber(p, t)
if ln != 7 {
t.Fatalf("program did not continue to expected location %d", ln)
}
assertNoError(proc.StepOut(p), t, "StepOut")
_, ln = currentLineNumber(p, t)
if ln != 11 {
t.Fatalf("program did not continue to expected location %d", ln)
}
frameoff2 := getFrameOff(p, t)
if frameoff0 != frameoff2 {
t.Fatalf("frame offset mismatch %x != %x", frameoff0, frameoff2)
}
})
}
......@@ -30,6 +30,8 @@ type Stackframe struct {
Call Location
// Start address of the stack frame.
CFA int64
// High address of the stack.
StackHi uint64
// Description of the stack frame.
FDE *frame.FrameDescriptionEntry
// Return address for this stack frame (as read from the stack frame itself).
......@@ -45,7 +47,7 @@ func ThreadStacktrace(thread Thread, depth int) ([]Stackframe, error) {
if err != nil {
return nil, err
}
it := newStackIterator(thread.BinInfo(), thread, regs.PC(), regs.SP(), regs.BP(), nil, -1)
it := newStackIterator(thread.BinInfo(), thread, regs.PC(), regs.SP(), regs.BP(), 0, nil, -1)
return it.stacktrace(depth)
}
......@@ -59,9 +61,9 @@ func (g *G) stackIterator() (*stackIterator, error) {
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.Thread, regs.PC(), regs.SP(), regs.BP(), g.stackhi, stkbar, g.stkbarPos), nil
}
return newStackIterator(g.variable.bi, g.variable.mem, g.PC, g.SP, 0, stkbar, g.stkbarPos), nil
return newStackIterator(g.variable.bi, g.variable.mem, g.PC, g.SP, 0, g.stackhi, stkbar, g.stkbarPos), nil
}
// Stacktrace returns the stack trace for a goroutine.
......@@ -93,6 +95,7 @@ type stackIterator struct {
mem MemoryReadWriter
err error
stackhi uint64
stackBarrierPC uint64
stkbar []savedLR
}
......@@ -102,7 +105,7 @@ type savedLR struct {
val uint64
}
func newStackIterator(bi *BinaryInfo, mem MemoryReadWriter, pc, sp, bp uint64, stkbar []savedLR, stkbarPos int) *stackIterator {
func newStackIterator(bi *BinaryInfo, mem MemoryReadWriter, pc, sp, bp, stackhi uint64, stkbar []savedLR, stkbarPos int) *stackIterator {
stackBarrierFunc := bi.goSymTable.LookupFunc(runtimeStackBarrier) // stack barriers were removed in Go 1.9
var stackBarrierPC uint64
if stackBarrierFunc != nil && stkbar != nil {
......@@ -122,7 +125,7 @@ func newStackIterator(bi *BinaryInfo, mem MemoryReadWriter, pc, sp, bp uint64, s
}
stkbar = stkbar[stkbarPos:]
}
return &stackIterator{pc: pc, sp: sp, bp: bp, top: true, bi: bi, mem: mem, err: nil, atend: false, stackBarrierPC: stackBarrierPC, stkbar: stkbar}
return &stackIterator{pc: pc, sp: sp, bp: bp, top: true, bi: bi, mem: mem, err: nil, atend: false, stackhi: stackhi, stackBarrierPC: stackBarrierPC, stkbar: stkbar}
}
// Next points the iterator to the next stack frame.
......@@ -206,7 +209,7 @@ func (it *stackIterator) newStackframe(pc uint64, cfa int64, retaddr uintptr, fd
if err != nil {
return Stackframe{}, err
}
r := Stackframe{Current: Location{PC: pc, File: f, Line: l, Fn: fn}, CFA: cfa, FDE: fde, Ret: ret, addrret: uint64(retaddr)}
r := Stackframe{Current: Location{PC: pc, File: f, Line: l, Fn: fn}, CFA: cfa, FDE: fde, Ret: ret, addrret: uint64(retaddr), StackHi: it.stackhi}
if !top {
r.Call.File, r.Call.Line, r.Call.Fn = it.bi.PCToLine(pc - 1)
r.Call.PC = r.Current.PC
......
......@@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"go/ast"
"go/token"
"path/filepath"
"reflect"
"strings"
......@@ -31,6 +32,8 @@ type Thread interface {
StepInstruction() error
// Blocked returns true if the thread is blocked
Blocked() bool
// SetCurrentBreakpoint updates the current breakpoint of this thread
SetCurrentBreakpoint() error
}
// Location represents the location of a thread.
......@@ -51,26 +54,30 @@ func (tbe ThreadBlockedError) Error() string {
return ""
}
// returns topmost frame of g or thread if g is nil
func topframe(g *G, thread Thread) (Stackframe, error) {
// topframe returns the two topmost frames of g, or thread if g is nil.
func topframe(g *G, thread Thread) (Stackframe, Stackframe, error) {
var frames []Stackframe
var err error
if g == nil {
if thread.Blocked() {
return Stackframe{}, ThreadBlockedError{}
return Stackframe{}, Stackframe{}, ThreadBlockedError{}
}
frames, err = ThreadStacktrace(thread, 0)
frames, err = ThreadStacktrace(thread, 1)
} else {
frames, err = g.Stacktrace(0)
frames, err = g.Stacktrace(1)
}
if err != nil {
return Stackframe{}, err
return Stackframe{}, Stackframe{}, err
}
if len(frames) < 1 {
return Stackframe{}, errors.New("empty stack trace")
switch len(frames) {
case 0:
return Stackframe{}, Stackframe{}, errors.New("empty stack trace")
case 1:
return frames[0], Stackframe{}, nil
default:
return frames[0], frames[1], nil
}
return frames[0], nil
}
// Set breakpoints at every line, and the return address. Also look for
......@@ -80,10 +87,20 @@ func topframe(g *G, thread Thread) (Stackframe, error) {
// a breakpoint of kind StepBreakpoint is set on the CALL instruction,
// Continue will take care of setting a breakpoint to the destination
// once the CALL is reached.
//
// Regardless of stepInto the following breakpoints will be set:
// - a breakpoint on the first deferred function with NextDeferBreakpoint
// kind, the list of all the addresses to deferreturn calls in this function
// and condition checking that we remain on the same goroutine
// - a breakpoint on each line of the function, with a condition checking
// that we stay on the same stack frame and goroutine.
// - a breakpoint on the return address of the function, with a condition
// checking that we move to the previous stack frame and stay on the same
// goroutine.
func next(dbp Process, stepInto bool) error {
selg := dbp.SelectedGoroutine()
curthread := dbp.CurrentThread()
topframe, err := topframe(selg, curthread)
topframe, retframe, err := topframe(selg, curthread)
if err != nil {
return err
}
......@@ -117,7 +134,21 @@ func next(dbp Process, stepInto bool) error {
}
}
cond := SameGoroutineCondition(selg)
sameGCond := SameGoroutineCondition(selg)
retFrameCond := andFrameoffCondition(sameGCond, retframe.CFA-int64(retframe.StackHi))
sameFrameCond := andFrameoffCondition(sameGCond, topframe.CFA-int64(topframe.StackHi))
var sameOrRetFrameCond ast.Expr
if sameGCond != nil {
sameOrRetFrameCond = &ast.BinaryExpr{
Op: token.LAND,
X: sameGCond,
Y: &ast.BinaryExpr{
Op: token.LOR,
X: frameoffCondition(topframe.CFA - int64(topframe.StackHi)),
Y: frameoffCondition(retframe.CFA - int64(retframe.StackHi)),
},
}
}
if stepInto {
for _, instr := range text {
......@@ -126,12 +157,12 @@ func next(dbp Process, stepInto bool) error {
}
if instr.DestLoc != nil && instr.DestLoc.Fn != nil {
if err := setStepIntoBreakpoint(dbp, []AsmInstruction{instr}, cond); err != nil {
if err := setStepIntoBreakpoint(dbp, []AsmInstruction{instr}, sameGCond); err != nil {
return err
}
} else {
// Non-absolute call instruction, set a StepBreakpoint here
if _, err := dbp.SetBreakpoint(instr.Loc.PC, StepBreakpoint, cond); err != nil {
if _, err := dbp.SetBreakpoint(instr.Loc.PC, StepBreakpoint, sameGCond); err != nil {
if _, ok := err.(BreakpointExistsError); !ok {
return err
}
......@@ -165,7 +196,7 @@ func next(dbp Process, stepInto bool) error {
}
}
if deferpc != 0 && deferpc != topframe.Current.PC {
bp, err := dbp.SetBreakpoint(deferpc, NextDeferBreakpoint, cond)
bp, err := dbp.SetBreakpoint(deferpc, NextDeferBreakpoint, sameGCond)
if err != nil {
if _, ok := err.(BreakpointExistsError); !ok {
return err
......@@ -201,9 +232,33 @@ func next(dbp Process, stepInto bool) error {
}
// Add a breakpoint on the return address for the current frame
pcs = append(pcs, topframe.Ret)
for _, pc := range pcs {
if _, err := dbp.SetBreakpoint(pc, NextBreakpoint, sameFrameCond); err != nil {
if _, ok := err.(BreakpointExistsError); !ok {
dbp.ClearInternalBreakpoints()
return err
}
}
}
if bp, err := dbp.SetBreakpoint(topframe.Ret, NextBreakpoint, retFrameCond); err != nil {
if _, isexists := err.(BreakpointExistsError); isexists {
if bp.Kind == NextBreakpoint {
// If the return address shares the same address with one of the lines
// of the function (because we are stepping through a recursive
// function) then the corresponding breakpoint should be active both on
// this frame and on the return frame.
bp.Cond = sameOrRetFrameCond
}
} else {
return err
}
}
if bp, _, _ := curthread.Breakpoint(); bp == nil {
curthread.SetCurrentBreakpoint()
}
success = true
return setInternalBreakpoints(dbp, topframe.Current.PC, pcs, NextBreakpoint, cond)
return nil
}
func setStepIntoBreakpoint(dbp Process, text []AsmInstruction, cond ast.Expr) error {
......@@ -246,23 +301,6 @@ func setStepIntoBreakpoint(dbp Process, text []AsmInstruction, cond ast.Expr) er
return nil
}
// setInternalBreakpoints sets a breakpoint to all addresses specified in pcs
// skipping over curpc and curpc-1
func setInternalBreakpoints(dbp Process, curpc uint64, pcs []uint64, kind BreakpointKind, cond ast.Expr) error {
for i := range pcs {
if pcs[i] == curpc || pcs[i] == curpc-1 {
continue
}
if _, err := dbp.SetBreakpoint(pcs[i], kind, cond); err != nil {
if _, ok := err.(BreakpointExistsError); !ok {
dbp.ClearInternalBreakpoints()
return err
}
}
}
return nil
}
func getGVariable(thread Thread) (*Variable, error) {
arch := thread.Arch()
regs, err := thread.Registers(false)
......@@ -345,7 +383,7 @@ func ThreadScope(thread Thread) (*EvalScope, error) {
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.BinInfo()}, nil
return &EvalScope{locations[0].Current.PC, locations[0].CFA, thread, nil, thread.BinInfo(), 0}, nil
}
// GoroutineScope returns an EvalScope for the goroutine running on this thread.
......@@ -357,11 +395,11 @@ func GoroutineScope(thread Thread) (*EvalScope, error) {
if len(locations) < 1 {
return nil, errors.New("could not decode first frame")
}
gvar, err := getGVariable(thread)
g, err := GetG(thread)
if err != nil {
return nil, err
}
return &EvalScope{locations[0].Current.PC, locations[0].CFA, thread, gvar, thread.BinInfo()}, nil
return &EvalScope{locations[0].Current.PC, locations[0].CFA, thread, g.variable, thread.BinInfo(), g.stackhi}, nil
}
func onRuntimeBreakpoint(thread Thread) bool {
......@@ -384,12 +422,30 @@ func onNextGoroutine(thread Thread, breakpoints map[uint64]*Breakpoint) (bool, e
if bp == nil {
return false, nil
}
if bp.Kind == NextDeferBreakpoint {
// we just want to check the condition on the goroutine id here
bp.Kind = NextBreakpoint
defer func() {
bp.Kind = NextDeferBreakpoint
}()
// Internal breakpoint conditions can take multiple different forms:
// Step into breakpoints:
// runtime.curg.goid == X
// Next or StepOut breakpoints:
// runtime.curg.goid == X && runtime.frameoff == Y
// Breakpoints that can be hit either by stepping on a line in the same
// function or by returning from the function:
// runtime.curg.goid == X && (runtime.frameoff == Y || runtime.frameoff == Z)
// Here we are only interested in testing the runtime.curg.goid clause.
w := onNextGoroutineWalker{thread: thread}
ast.Walk(&w, bp.Cond)
return w.ret, w.err
}
type onNextGoroutineWalker struct {
thread Thread
ret bool
err error
}
func (w *onNextGoroutineWalker) Visit(n ast.Node) ast.Visitor {
if binx, isbin := n.(*ast.BinaryExpr); isbin && binx.Op == token.EQL && exprToString(binx.X) == "runtime.curg.goid" {
w.ret, w.err = evalBreakpointCondition(w.thread, n.(ast.Expr))
return nil
}
return bp.CheckCondition(thread)
return w
}
......@@ -124,6 +124,7 @@ type G struct {
Status uint64
stkbarVar *Variable // stkbar field of g struct
stkbarPos int // stkbarPos field of g struct
stackhi uint64 // value of stack.hi
// Information on goroutine location
CurrentLoc Location
......@@ -142,6 +143,7 @@ type EvalScope struct {
Mem MemoryReadWriter // Target's memory
Gvar *Variable
BinInfo *BinaryInfo
StackHi uint64
}
// IsNilErr is returned when a variable is nil.
......@@ -377,7 +379,7 @@ func (gvar *Variable) parseG() (*G, error) {
}
gvar = gvar.maybeDereference()
}
gvar.loadValue(LoadConfig{false, 1, 64, 0, -1})
gvar.loadValue(LoadConfig{false, 2, 64, 0, -1})
if gvar.Unreadable != nil {
return nil, gvar.Unreadable
}
......@@ -387,6 +389,12 @@ func (gvar *Variable) parseG() (*G, error) {
id, _ := constant.Int64Val(gvar.fieldVariable("goid").Value)
gopc, _ := constant.Int64Val(gvar.fieldVariable("gopc").Value)
waitReason := constant.StringVal(gvar.fieldVariable("waitreason").Value)
var stackhi uint64
if stackVar := gvar.fieldVariable("stack"); stackVar != nil {
if stackhiVar := stackVar.fieldVariable("hi"); stackhiVar != nil {
stackhi, _ = constant.Uint64Val(stackhiVar.Value)
}
}
stkbarVar, _ := gvar.structMember("stkbar")
stkbarVarPosFld := gvar.fieldVariable("stkbarPos")
......@@ -408,6 +416,7 @@ func (gvar *Variable) parseG() (*G, error) {
variable: gvar,
stkbarVar: stkbarVar,
stkbarPos: int(stkbarPos),
stackhi: stackhi,
}
return g, nil
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册