// +------------------+ <- frame->argp
// | return address |
// +------------------+
-// | caller's BP (*) | (*) if framepointer_enabled && varp < sp
+// | caller's BP (*) | (*) if framepointer_enabled && varp > sp
// +------------------+ <- frame->varp
// | locals |
// +------------------+
// | args from caller |
// +------------------+ <- frame->argp
// | caller's retaddr |
+// +------------------+
+// | caller's FP (*) | (*) on ARM64, if framepointer_enabled && varp > sp
// +------------------+ <- frame->varp
// | locals |
// +------------------+
// +------------------+
// | return address |
// +------------------+ <- frame->sp
+//
+// varp > sp means that the function has a frame;
+// varp == sp means frameless function.
type adjustinfo struct {
old stack
adjustpointers(unsafe.Pointer(frame.varp-size), &locals, adjinfo, f)
}
- // Adjust saved base pointer if there is one.
- // TODO what about arm64 frame pointer adjustment?
- if goarch.ArchFamily == goarch.AMD64 && frame.argp-frame.varp == 2*goarch.PtrSize {
+ // Adjust saved frame pointer if there is one.
+ if (goarch.ArchFamily == goarch.AMD64 || goarch.ArchFamily == goarch.ARM64) && frame.argp-frame.varp == 2*goarch.PtrSize {
if stackDebug >= 3 {
print(" saved bp\n")
}
throw("bad frame pointer")
}
}
+ // On AMD64, this is the caller's frame pointer saved in the current
+ // frame.
+ // On ARM64, this is the frame pointer of the caller's caller saved
+ // by the caller in its frame (one word below its SP).
adjustpointer(adjinfo, unsafe.Pointer(frame.varp))
}
throw("bad top frame pointer")
}
}
+ oldfp := gp.sched.bp
adjustpointer(adjinfo, unsafe.Pointer(&gp.sched.bp))
+ if GOARCH == "arm64" {
+ // On ARM64, the frame pointer is saved one word *below* the SP,
+ // which is not copied or adjusted in any frame. Do it explicitly
+ // here.
+ if oldfp == gp.sched.sp-goarch.PtrSize {
+ memmove(unsafe.Pointer(gp.sched.bp), unsafe.Pointer(oldfp), goarch.PtrSize)
+ adjustpointer(adjinfo, unsafe.Pointer(gp.sched.bp))
+ }
+ }
}
func adjustdefers(gp *g, adjinfo *adjustinfo) {
// Pass a value to escapeMe to force it to escape.
var escapeMe = func(x any) {}
+
+func TestFramePointerAdjust(t *testing.T) {
+ switch GOARCH {
+ case "amd64", "arm64":
+ default:
+ t.Skipf("frame pointer is not supported on %s", GOARCH)
+ }
+ output := runTestProg(t, "testprog", "FramePointerAdjust")
+ if output != "" {
+ t.Errorf("output:\n%s\n\nwant no output", output)
+ }
+}
--- /dev/null
+// Copyright 2023 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.
+
+//go:build amd64 || arm64
+
+package main
+
+import "unsafe"
+
+func init() {
+ register("FramePointerAdjust", FramePointerAdjust)
+}
+
+func FramePointerAdjust() { framePointerAdjust1(0) }
+
+//go:noinline
+func framePointerAdjust1(x int) {
+ argp := uintptr(unsafe.Pointer(&x))
+ fp := *getFP()
+ if !(argp-0x100 <= fp && fp <= argp+0x100) {
+ print("saved FP=", fp, " &x=", argp, "\n")
+ panic("FAIL")
+ }
+
+ // grow the stack
+ grow(10000)
+
+ // check again
+ argp = uintptr(unsafe.Pointer(&x))
+ fp = *getFP()
+ if !(argp-0x100 <= fp && fp <= argp+0x100) {
+ print("saved FP=", fp, " &x=", argp, "\n")
+ panic("FAIL")
+ }
+}
+
+func grow(n int) {
+ if n > 0 {
+ grow(n - 1)
+ }
+}
+
+func getFP() *uintptr