Skip to content

Commit a41a29a

Browse files
committed
runtime: adjust frame pointer on stack copy on ARM64
Frame pointer is enabled on ARM64. When copying stacks, the saved frame pointers need to be adjusted. Updates #39524, #40044. Fixes #58432. Change-Id: I73651fdfd1a6cccae26a5ce02e7e86f6c2fb9bf7 Reviewed-on: https://go-review.googlesource.com/c/go/+/241158 Reviewed-by: Felix Geisendörfer <[email protected]> Run-TryBot: Cherry Mui <[email protected]> Reviewed-by: Michael Knyszek <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent 1064335 commit a41a29a

File tree

5 files changed

+96
-4
lines changed

5 files changed

+96
-4
lines changed

src/runtime/stack.go

+22-4
Original file line numberDiff line numberDiff line change
@@ -537,7 +537,7 @@ var ptrnames = []string{
537537
// +------------------+ <- frame->argp
538538
// | return address |
539539
// +------------------+
540-
// | caller's BP (*) | (*) if framepointer_enabled && varp < sp
540+
// | caller's BP (*) | (*) if framepointer_enabled && varp > sp
541541
// +------------------+ <- frame->varp
542542
// | locals |
543543
// +------------------+
@@ -549,13 +549,18 @@ var ptrnames = []string{
549549
// | args from caller |
550550
// +------------------+ <- frame->argp
551551
// | caller's retaddr |
552+
// +------------------+
553+
// | caller's FP (*) | (*) on ARM64, if framepointer_enabled && varp > sp
552554
// +------------------+ <- frame->varp
553555
// | locals |
554556
// +------------------+
555557
// | args to callee |
556558
// +------------------+
557559
// | return address |
558560
// +------------------+ <- frame->sp
561+
//
562+
// varp > sp means that the function has a frame;
563+
// varp == sp means frameless function.
559564

560565
type adjustinfo struct {
561566
old stack
@@ -673,9 +678,8 @@ func adjustframe(frame *stkframe, adjinfo *adjustinfo) {
673678
adjustpointers(unsafe.Pointer(frame.varp-size), &locals, adjinfo, f)
674679
}
675680

676-
// Adjust saved base pointer if there is one.
677-
// TODO what about arm64 frame pointer adjustment?
678-
if goarch.ArchFamily == goarch.AMD64 && frame.argp-frame.varp == 2*goarch.PtrSize {
681+
// Adjust saved frame pointer if there is one.
682+
if (goarch.ArchFamily == goarch.AMD64 || goarch.ArchFamily == goarch.ARM64) && frame.argp-frame.varp == 2*goarch.PtrSize {
679683
if stackDebug >= 3 {
680684
print(" saved bp\n")
681685
}
@@ -689,6 +693,10 @@ func adjustframe(frame *stkframe, adjinfo *adjustinfo) {
689693
throw("bad frame pointer")
690694
}
691695
}
696+
// On AMD64, this is the caller's frame pointer saved in the current
697+
// frame.
698+
// On ARM64, this is the frame pointer of the caller's caller saved
699+
// by the caller in its frame (one word below its SP).
692700
adjustpointer(adjinfo, unsafe.Pointer(frame.varp))
693701
}
694702

@@ -750,7 +758,17 @@ func adjustctxt(gp *g, adjinfo *adjustinfo) {
750758
throw("bad top frame pointer")
751759
}
752760
}
761+
oldfp := gp.sched.bp
753762
adjustpointer(adjinfo, unsafe.Pointer(&gp.sched.bp))
763+
if GOARCH == "arm64" {
764+
// On ARM64, the frame pointer is saved one word *below* the SP,
765+
// which is not copied or adjusted in any frame. Do it explicitly
766+
// here.
767+
if oldfp == gp.sched.sp-goarch.PtrSize {
768+
memmove(unsafe.Pointer(gp.sched.bp), unsafe.Pointer(oldfp), goarch.PtrSize)
769+
adjustpointer(adjinfo, unsafe.Pointer(gp.sched.bp))
770+
}
771+
}
754772
}
755773

756774
func adjustdefers(gp *g, adjinfo *adjustinfo) {

src/runtime/stack_test.go

+12
Original file line numberDiff line numberDiff line change
@@ -927,3 +927,15 @@ func deferHeapAndStack(n int) (r int) {
927927

928928
// Pass a value to escapeMe to force it to escape.
929929
var escapeMe = func(x any) {}
930+
931+
func TestFramePointerAdjust(t *testing.T) {
932+
switch GOARCH {
933+
case "amd64", "arm64":
934+
default:
935+
t.Skipf("frame pointer is not supported on %s", GOARCH)
936+
}
937+
output := runTestProg(t, "testprog", "FramePointerAdjust")
938+
if output != "" {
939+
t.Errorf("output:\n%s\n\nwant no output", output)
940+
}
941+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build amd64 || arm64
6+
7+
package main
8+
9+
import "unsafe"
10+
11+
func init() {
12+
register("FramePointerAdjust", FramePointerAdjust)
13+
}
14+
15+
func FramePointerAdjust() { framePointerAdjust1(0) }
16+
17+
//go:noinline
18+
func framePointerAdjust1(x int) {
19+
argp := uintptr(unsafe.Pointer(&x))
20+
fp := *getFP()
21+
if !(argp-0x100 <= fp && fp <= argp+0x100) {
22+
print("saved FP=", fp, " &x=", argp, "\n")
23+
panic("FAIL")
24+
}
25+
26+
// grow the stack
27+
grow(10000)
28+
29+
// check again
30+
argp = uintptr(unsafe.Pointer(&x))
31+
fp = *getFP()
32+
if !(argp-0x100 <= fp && fp <= argp+0x100) {
33+
print("saved FP=", fp, " &x=", argp, "\n")
34+
panic("FAIL")
35+
}
36+
}
37+
38+
func grow(n int) {
39+
if n > 0 {
40+
grow(n - 1)
41+
}
42+
}
43+
44+
func getFP() *uintptr
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
#include "textflag.h"
6+
7+
TEXT ·getFP(SB), NOSPLIT|NOFRAME, $0-8
8+
MOVQ BP, ret+0(FP)
9+
RET
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
#include "textflag.h"
6+
7+
TEXT ·getFP(SB), NOSPLIT|NOFRAME, $0-8
8+
MOVD R29, ret+0(FP)
9+
RET

0 commit comments

Comments
 (0)