From 35026f373233de30dbdb6752822d3eabf2220c11 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Wed, 17 Aug 2022 09:06:14 -0400 Subject: [PATCH] runtime: consolidate stkframe and its methods into stkframe.go The stkframe struct and its methods are strewn across different source files. Since they actually have a pretty coherent theme at this point, migrate it all into a new file, stkframe.go. There are no code changes in this CL. For #54466, albeit rather indirectly. Change-Id: Ibe53fc4b1106d131005e1c9d491be838a8f14211 Reviewed-on: https://go-review.googlesource.com/c/go/+/424516 Reviewed-by: Michael Pratt Reviewed-by: Cherry Mui TryBot-Result: Gopher Robot Run-TryBot: Austin Clements Auto-Submit: Austin Clements --- src/runtime/runtime2.go | 49 ------- src/runtime/stack.go | 136 ------------------ src/runtime/stkframe.go | 288 +++++++++++++++++++++++++++++++++++++++ src/runtime/traceback.go | 91 ------------- 4 files changed, 288 insertions(+), 276 deletions(-) create mode 100644 src/runtime/stkframe.go diff --git a/src/runtime/runtime2.go b/src/runtime/runtime2.go index fe9b770b44..44dcfcca82 100644 --- a/src/runtime/runtime2.go +++ b/src/runtime/runtime2.go @@ -985,55 +985,6 @@ type _panic struct { goexit bool } -// A stkframe holds information about a single physical stack frame. -type stkframe struct { - // fn is the function being run in this frame. If there is - // inlining, this is the outermost function. - fn funcInfo - - // pc is the program counter within fn. - // - // The meaning of this is subtle: - // - // - Typically, this frame performed a regular function call - // and this is the return PC (just after the CALL - // instruction). In this case, pc-1 reflects the CALL - // instruction itself and is the correct source of symbolic - // information. - // - // - If this frame "called" sigpanic, then pc is the - // instruction that panicked, and pc is the correct address - // to use for symbolic information. - // - // - If this is the innermost frame, then PC is where - // execution will continue, but it may not be the - // instruction following a CALL. This may be from - // cooperative preemption, in which case this is the - // instruction after the call to morestack. Or this may be - // from a signal or an un-started goroutine, in which case - // PC could be any instruction, including the first - // instruction in a function. Conventionally, we use pc-1 - // for symbolic information, unless pc == fn.entry(), in - // which case we use pc. - pc uintptr - - // continpc is the PC where execution will continue in fn, or - // 0 if execution will not continue in this frame. - // - // This is usually the same as pc, unless this frame "called" - // sigpanic, in which case it's either the address of - // deferreturn or 0 if this frame will never execute again. - // - // This is the PC to use to look up GC liveness for this frame. - continpc uintptr - - lr uintptr // program counter at caller aka link register - sp uintptr // stack pointer at pc - fp uintptr // stack pointer at caller aka frame pointer - varp uintptr // top of local variables - argp uintptr // pointer to function arguments -} - // ancestorInfo records details of where a goroutine was started. type ancestorInfo struct { pcs []uintptr // pcs from the stack of this goroutine diff --git a/src/runtime/stack.go b/src/runtime/stack.go index 1b782ede88..10c45045d9 100644 --- a/src/runtime/stack.go +++ b/src/runtime/stack.go @@ -1247,142 +1247,6 @@ func freeStackSpans() { unlock(&stackLarge.lock) } -// getStackMap returns the locals and arguments live pointer maps, and -// stack object list for frame. -func (frame *stkframe) getStackMap(cache *pcvalueCache, debug bool) (locals, args bitvector, objs []stackObjectRecord) { - targetpc := frame.continpc - if targetpc == 0 { - // Frame is dead. Return empty bitvectors. - return - } - - f := frame.fn - pcdata := int32(-1) - if targetpc != f.entry() { - // Back up to the CALL. If we're at the function entry - // point, we want to use the entry map (-1), even if - // the first instruction of the function changes the - // stack map. - targetpc-- - pcdata = pcdatavalue(f, _PCDATA_StackMapIndex, targetpc, cache) - } - if pcdata == -1 { - // We do not have a valid pcdata value but there might be a - // stackmap for this function. It is likely that we are looking - // at the function prologue, assume so and hope for the best. - pcdata = 0 - } - - // Local variables. - size := frame.varp - frame.sp - var minsize uintptr - switch goarch.ArchFamily { - case goarch.ARM64: - minsize = sys.StackAlign - default: - minsize = sys.MinFrameSize - } - if size > minsize { - stackid := pcdata - stkmap := (*stackmap)(funcdata(f, _FUNCDATA_LocalsPointerMaps)) - if stkmap == nil || stkmap.n <= 0 { - print("runtime: frame ", funcname(f), " untyped locals ", hex(frame.varp-size), "+", hex(size), "\n") - throw("missing stackmap") - } - // If nbit == 0, there's no work to do. - if stkmap.nbit > 0 { - if stackid < 0 || stackid >= stkmap.n { - // don't know where we are - print("runtime: pcdata is ", stackid, " and ", stkmap.n, " locals stack map entries for ", funcname(f), " (targetpc=", hex(targetpc), ")\n") - throw("bad symbol table") - } - locals = stackmapdata(stkmap, stackid) - if stackDebug >= 3 && debug { - print(" locals ", stackid, "/", stkmap.n, " ", locals.n, " words ", locals.bytedata, "\n") - } - } else if stackDebug >= 3 && debug { - print(" no locals to adjust\n") - } - } - - // Arguments. First fetch frame size and special-case argument maps. - var isReflect bool - args, isReflect = frame.argMapInternal() - if args.n > 0 && args.bytedata == nil { - // Non-empty argument frame, but not a special map. - // Fetch the argument map at pcdata. - stackmap := (*stackmap)(funcdata(f, _FUNCDATA_ArgsPointerMaps)) - if stackmap == nil || stackmap.n <= 0 { - print("runtime: frame ", funcname(f), " untyped args ", hex(frame.argp), "+", hex(args.n*goarch.PtrSize), "\n") - throw("missing stackmap") - } - if pcdata < 0 || pcdata >= stackmap.n { - // don't know where we are - print("runtime: pcdata is ", pcdata, " and ", stackmap.n, " args stack map entries for ", funcname(f), " (targetpc=", hex(targetpc), ")\n") - throw("bad symbol table") - } - if stackmap.nbit == 0 { - args.n = 0 - } else { - args = stackmapdata(stackmap, pcdata) - } - } - - // stack objects. - if (GOARCH == "amd64" || GOARCH == "arm64" || GOARCH == "ppc64" || GOARCH == "ppc64le" || GOARCH == "riscv64") && - unsafe.Sizeof(abi.RegArgs{}) > 0 && isReflect { - // For reflect.makeFuncStub and reflect.methodValueCall, - // we need to fake the stack object record. - // These frames contain an internal/abi.RegArgs at a hard-coded offset. - // This offset matches the assembly code on amd64 and arm64. - objs = methodValueCallFrameObjs[:] - } else { - p := funcdata(f, _FUNCDATA_StackObjects) - if p != nil { - n := *(*uintptr)(p) - p = add(p, goarch.PtrSize) - r0 := (*stackObjectRecord)(noescape(p)) - objs = unsafe.Slice(r0, int(n)) - // Note: the noescape above is needed to keep - // getStackMap from "leaking param content: - // frame". That leak propagates up to getgcmask, then - // GCMask, then verifyGCInfo, which converts the stack - // gcinfo tests into heap gcinfo tests :( - } - } - - return -} - -var methodValueCallFrameObjs [1]stackObjectRecord // initialized in stackobjectinit - -func stkobjinit() { - var abiRegArgsEface any = abi.RegArgs{} - abiRegArgsType := efaceOf(&abiRegArgsEface)._type - if abiRegArgsType.kind&kindGCProg != 0 { - throw("abiRegArgsType needs GC Prog, update methodValueCallFrameObjs") - } - // Set methodValueCallFrameObjs[0].gcdataoff so that - // stackObjectRecord.gcdata() will work correctly with it. - ptr := uintptr(unsafe.Pointer(&methodValueCallFrameObjs[0])) - var mod *moduledata - for datap := &firstmoduledata; datap != nil; datap = datap.next { - if datap.gofunc <= ptr && ptr < datap.end { - mod = datap - break - } - } - if mod == nil { - throw("methodValueCallFrameObjs is not in a module") - } - methodValueCallFrameObjs[0] = stackObjectRecord{ - off: -int32(alignUp(abiRegArgsType.size, 8)), // It's always the highest address local. - size: int32(abiRegArgsType.size), - _ptrdata: int32(abiRegArgsType.ptrdata), - gcdataoff: uint32(uintptr(unsafe.Pointer(abiRegArgsType.gcdata)) - mod.rodata), - } -} - // A stackObjectRecord is generated by the compiler for each stack object in a stack frame. // This record must match the generator code in cmd/compile/internal/liveness/plive.go:emitStackObjects. type stackObjectRecord struct { diff --git a/src/runtime/stkframe.go b/src/runtime/stkframe.go new file mode 100644 index 0000000000..97807a038e --- /dev/null +++ b/src/runtime/stkframe.go @@ -0,0 +1,288 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package runtime + +import ( + "internal/abi" + "internal/goarch" + "runtime/internal/sys" + "unsafe" +) + +// A stkframe holds information about a single physical stack frame. +type stkframe struct { + // fn is the function being run in this frame. If there is + // inlining, this is the outermost function. + fn funcInfo + + // pc is the program counter within fn. + // + // The meaning of this is subtle: + // + // - Typically, this frame performed a regular function call + // and this is the return PC (just after the CALL + // instruction). In this case, pc-1 reflects the CALL + // instruction itself and is the correct source of symbolic + // information. + // + // - If this frame "called" sigpanic, then pc is the + // instruction that panicked, and pc is the correct address + // to use for symbolic information. + // + // - If this is the innermost frame, then PC is where + // execution will continue, but it may not be the + // instruction following a CALL. This may be from + // cooperative preemption, in which case this is the + // instruction after the call to morestack. Or this may be + // from a signal or an un-started goroutine, in which case + // PC could be any instruction, including the first + // instruction in a function. Conventionally, we use pc-1 + // for symbolic information, unless pc == fn.entry(), in + // which case we use pc. + pc uintptr + + // continpc is the PC where execution will continue in fn, or + // 0 if execution will not continue in this frame. + // + // This is usually the same as pc, unless this frame "called" + // sigpanic, in which case it's either the address of + // deferreturn or 0 if this frame will never execute again. + // + // This is the PC to use to look up GC liveness for this frame. + continpc uintptr + + lr uintptr // program counter at caller aka link register + sp uintptr // stack pointer at pc + fp uintptr // stack pointer at caller aka frame pointer + varp uintptr // top of local variables + argp uintptr // pointer to function arguments +} + +// reflectMethodValue is a partial duplicate of reflect.makeFuncImpl +// and reflect.methodValue. +type reflectMethodValue struct { + fn uintptr + stack *bitvector // ptrmap for both args and results + argLen uintptr // just args +} + +// argBytes returns the argument frame size for a call to frame.fn. +func (frame *stkframe) argBytes() uintptr { + if frame.fn.args != _ArgsSizeUnknown { + return uintptr(frame.fn.args) + } + // This is an uncommon and complicated case. Fall back to fully + // fetching the argument map to compute its size. + argMap, _ := frame.argMapInternal() + return uintptr(argMap.n) * goarch.PtrSize +} + +// argMapInternal is used internally by stkframe to fetch special +// argument maps. +// +// argMap.n is always populated with the size of the argument map. +// +// argMap.bytedata is only populated for dynamic argument maps (used +// by reflect). If the caller requires the argument map, it should use +// this if non-nil, and otherwise fetch the argument map using the +// current PC. +// +// hasReflectStackObj indicates that this frame also has a reflect +// function stack object, which the caller must synthesize. +func (frame *stkframe) argMapInternal() (argMap bitvector, hasReflectStackObj bool) { + f := frame.fn + argMap.n = f.args / goarch.PtrSize + if f.args == _ArgsSizeUnknown { + // Extract argument bitmaps for reflect stubs from the calls they made to reflect. + switch funcname(f) { + case "reflect.makeFuncStub", "reflect.methodValueCall": + // These take a *reflect.methodValue as their + // context register and immediately save it to 0(SP). + // Get the methodValue from 0(SP). + arg0 := frame.sp + sys.MinFrameSize + + minSP := frame.fp + if !usesLR { + // The CALL itself pushes a word. + // Undo that adjustment. + minSP -= goarch.PtrSize + } + if arg0 >= minSP { + // The function hasn't started yet. + // This only happens if f was the + // start function of a new goroutine + // that hasn't run yet *and* f takes + // no arguments and has no results + // (otherwise it will get wrapped in a + // closure). In this case, we can't + // reach into its locals because it + // doesn't have locals yet, but we + // also know its argument map is + // empty. + if frame.pc != f.entry() { + print("runtime: confused by ", funcname(f), ": no frame (sp=", hex(frame.sp), " fp=", hex(frame.fp), ") at entry+", hex(frame.pc-f.entry()), "\n") + throw("reflect mismatch") + } + return bitvector{}, false // No locals, so also no stack objects + } + hasReflectStackObj = true + mv := *(**reflectMethodValue)(unsafe.Pointer(arg0)) + // Figure out whether the return values are valid. + // Reflect will update this value after it copies + // in the return values. + retValid := *(*bool)(unsafe.Pointer(arg0 + 4*goarch.PtrSize)) + if mv.fn != f.entry() { + print("runtime: confused by ", funcname(f), "\n") + throw("reflect mismatch") + } + argMap = *mv.stack + if !retValid { + // argMap.n includes the results, but + // those aren't valid, so drop them. + n := int32((uintptr(mv.argLen) &^ (goarch.PtrSize - 1)) / goarch.PtrSize) + if n < argMap.n { + argMap.n = n + } + } + } + } + return +} + +// getStackMap returns the locals and arguments live pointer maps, and +// stack object list for frame. +func (frame *stkframe) getStackMap(cache *pcvalueCache, debug bool) (locals, args bitvector, objs []stackObjectRecord) { + targetpc := frame.continpc + if targetpc == 0 { + // Frame is dead. Return empty bitvectors. + return + } + + f := frame.fn + pcdata := int32(-1) + if targetpc != f.entry() { + // Back up to the CALL. If we're at the function entry + // point, we want to use the entry map (-1), even if + // the first instruction of the function changes the + // stack map. + targetpc-- + pcdata = pcdatavalue(f, _PCDATA_StackMapIndex, targetpc, cache) + } + if pcdata == -1 { + // We do not have a valid pcdata value but there might be a + // stackmap for this function. It is likely that we are looking + // at the function prologue, assume so and hope for the best. + pcdata = 0 + } + + // Local variables. + size := frame.varp - frame.sp + var minsize uintptr + switch goarch.ArchFamily { + case goarch.ARM64: + minsize = sys.StackAlign + default: + minsize = sys.MinFrameSize + } + if size > minsize { + stackid := pcdata + stkmap := (*stackmap)(funcdata(f, _FUNCDATA_LocalsPointerMaps)) + if stkmap == nil || stkmap.n <= 0 { + print("runtime: frame ", funcname(f), " untyped locals ", hex(frame.varp-size), "+", hex(size), "\n") + throw("missing stackmap") + } + // If nbit == 0, there's no work to do. + if stkmap.nbit > 0 { + if stackid < 0 || stackid >= stkmap.n { + // don't know where we are + print("runtime: pcdata is ", stackid, " and ", stkmap.n, " locals stack map entries for ", funcname(f), " (targetpc=", hex(targetpc), ")\n") + throw("bad symbol table") + } + locals = stackmapdata(stkmap, stackid) + if stackDebug >= 3 && debug { + print(" locals ", stackid, "/", stkmap.n, " ", locals.n, " words ", locals.bytedata, "\n") + } + } else if stackDebug >= 3 && debug { + print(" no locals to adjust\n") + } + } + + // Arguments. First fetch frame size and special-case argument maps. + var isReflect bool + args, isReflect = frame.argMapInternal() + if args.n > 0 && args.bytedata == nil { + // Non-empty argument frame, but not a special map. + // Fetch the argument map at pcdata. + stackmap := (*stackmap)(funcdata(f, _FUNCDATA_ArgsPointerMaps)) + if stackmap == nil || stackmap.n <= 0 { + print("runtime: frame ", funcname(f), " untyped args ", hex(frame.argp), "+", hex(args.n*goarch.PtrSize), "\n") + throw("missing stackmap") + } + if pcdata < 0 || pcdata >= stackmap.n { + // don't know where we are + print("runtime: pcdata is ", pcdata, " and ", stackmap.n, " args stack map entries for ", funcname(f), " (targetpc=", hex(targetpc), ")\n") + throw("bad symbol table") + } + if stackmap.nbit == 0 { + args.n = 0 + } else { + args = stackmapdata(stackmap, pcdata) + } + } + + // stack objects. + if (GOARCH == "amd64" || GOARCH == "arm64" || GOARCH == "ppc64" || GOARCH == "ppc64le" || GOARCH == "riscv64") && + unsafe.Sizeof(abi.RegArgs{}) > 0 && isReflect { + // For reflect.makeFuncStub and reflect.methodValueCall, + // we need to fake the stack object record. + // These frames contain an internal/abi.RegArgs at a hard-coded offset. + // This offset matches the assembly code on amd64 and arm64. + objs = methodValueCallFrameObjs[:] + } else { + p := funcdata(f, _FUNCDATA_StackObjects) + if p != nil { + n := *(*uintptr)(p) + p = add(p, goarch.PtrSize) + r0 := (*stackObjectRecord)(noescape(p)) + objs = unsafe.Slice(r0, int(n)) + // Note: the noescape above is needed to keep + // getStackMap from "leaking param content: + // frame". That leak propagates up to getgcmask, then + // GCMask, then verifyGCInfo, which converts the stack + // gcinfo tests into heap gcinfo tests :( + } + } + + return +} + +var methodValueCallFrameObjs [1]stackObjectRecord // initialized in stackobjectinit + +func stkobjinit() { + var abiRegArgsEface any = abi.RegArgs{} + abiRegArgsType := efaceOf(&abiRegArgsEface)._type + if abiRegArgsType.kind&kindGCProg != 0 { + throw("abiRegArgsType needs GC Prog, update methodValueCallFrameObjs") + } + // Set methodValueCallFrameObjs[0].gcdataoff so that + // stackObjectRecord.gcdata() will work correctly with it. + ptr := uintptr(unsafe.Pointer(&methodValueCallFrameObjs[0])) + var mod *moduledata + for datap := &firstmoduledata; datap != nil; datap = datap.next { + if datap.gofunc <= ptr && ptr < datap.end { + mod = datap + break + } + } + if mod == nil { + throw("methodValueCallFrameObjs is not in a module") + } + methodValueCallFrameObjs[0] = stackObjectRecord{ + off: -int32(alignUp(abiRegArgsType.size, 8)), // It's always the highest address local. + size: int32(abiRegArgsType.size), + _ptrdata: int32(abiRegArgsType.ptrdata), + gcdataoff: uint32(uintptr(unsafe.Pointer(abiRegArgsType.gcdata)) - mod.rodata), + } +} diff --git a/src/runtime/traceback.go b/src/runtime/traceback.go index 0c8e5bace3..a9bec426d1 100644 --- a/src/runtime/traceback.go +++ b/src/runtime/traceback.go @@ -648,97 +648,6 @@ printloop: } } -// reflectMethodValue is a partial duplicate of reflect.makeFuncImpl -// and reflect.methodValue. -type reflectMethodValue struct { - fn uintptr - stack *bitvector // ptrmap for both args and results - argLen uintptr // just args -} - -// argBytes returns the argument frame size for a call to frame.fn. -func (frame *stkframe) argBytes() uintptr { - if frame.fn.args != _ArgsSizeUnknown { - return uintptr(frame.fn.args) - } - // This is an uncommon and complicated case. Fall back to fully - // fetching the argument map to compute its size. - argMap, _ := frame.argMapInternal() - return uintptr(argMap.n) * goarch.PtrSize -} - -// argMapInternal is used internally by stkframe to fetch special -// argument maps. -// -// argMap.n is always populated with the size of the argument map. -// -// argMap.bytedata is only populated for dynamic argument maps (used -// by reflect). If the caller requires the argument map, it should use -// this if non-nil, and otherwise fetch the argument map using the -// current PC. -// -// hasReflectStackObj indicates that this frame also has a reflect -// function stack object, which the caller must synthesize. -func (frame *stkframe) argMapInternal() (argMap bitvector, hasReflectStackObj bool) { - f := frame.fn - argMap.n = f.args / goarch.PtrSize - if f.args == _ArgsSizeUnknown { - // Extract argument bitmaps for reflect stubs from the calls they made to reflect. - switch funcname(f) { - case "reflect.makeFuncStub", "reflect.methodValueCall": - // These take a *reflect.methodValue as their - // context register and immediately save it to 0(SP). - // Get the methodValue from 0(SP). - arg0 := frame.sp + sys.MinFrameSize - - minSP := frame.fp - if !usesLR { - // The CALL itself pushes a word. - // Undo that adjustment. - minSP -= goarch.PtrSize - } - if arg0 >= minSP { - // The function hasn't started yet. - // This only happens if f was the - // start function of a new goroutine - // that hasn't run yet *and* f takes - // no arguments and has no results - // (otherwise it will get wrapped in a - // closure). In this case, we can't - // reach into its locals because it - // doesn't have locals yet, but we - // also know its argument map is - // empty. - if frame.pc != f.entry() { - print("runtime: confused by ", funcname(f), ": no frame (sp=", hex(frame.sp), " fp=", hex(frame.fp), ") at entry+", hex(frame.pc-f.entry()), "\n") - throw("reflect mismatch") - } - return bitvector{}, false // No locals, so also no stack objects - } - hasReflectStackObj = true - mv := *(**reflectMethodValue)(unsafe.Pointer(arg0)) - // Figure out whether the return values are valid. - // Reflect will update this value after it copies - // in the return values. - retValid := *(*bool)(unsafe.Pointer(arg0 + 4*goarch.PtrSize)) - if mv.fn != f.entry() { - print("runtime: confused by ", funcname(f), "\n") - throw("reflect mismatch") - } - argMap = *mv.stack - if !retValid { - // argMap.n includes the results, but - // those aren't valid, so drop them. - n := int32((uintptr(mv.argLen) &^ (goarch.PtrSize - 1)) / goarch.PtrSize) - if n < argMap.n { - argMap.n = n - } - } - } - } - return -} - // tracebackCgoContext handles tracing back a cgo context value, from // the context argument to setCgoTraceback, for the gentraceback // function. It returns the new value of n.