| // 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((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, abi.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, 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 == "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_&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), |
| } |
| } |