// 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 != abi.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 if f.args != abi.ArgsSizeUnknown { argMap.n = f.args / goarch.PtrSize return } // 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((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(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, abi.PCDATA_StackMapIndex, targetpc) } 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, abi.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, abi.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 == "loong64" || 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, abi.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_&abi.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.PtrBytes), gcdataoff: uint32(uintptr(unsafe.Pointer(abiRegArgsType.GCData)) - mod.rodata), } }