From 9c1d19a183e89be6cf18168addccc0d24551bb71 Mon Sep 17 00:00:00 2001 From: Michael Pratt Date: Fri, 2 May 2025 14:18:03 -0400 Subject: [PATCH] runtime: clear frame pointer in mcall On amd64, mcall leaves BP untouched, so the callback will push BP, connecting the g0 stack to the calling g stack. This seems OK (frame pointer unwinders like Linux perf can see what user code called into the scheduler), but the "scheduler" part is problematic. mcall is used when calling into the scheduler to deschedule the current goroutine (e.g., in goyield). Once the goroutine is descheduled, it may be picked up by another M and continue execution. The other thread is mutating the goroutine stack, but our M still has a frame pointer pointing to the goroutine stack. A frame pointer unwinder like Linux perf could get bogus values off of the mutating stack. Note that though the execution tracer uses framepointer unwinding, it never unwinds a g0, so it isn't affected. Clear the frame pointer in mcall so that unwinding always stops at mcall. On arm64, mcall stores the frame pointer from g0.sched.bp. This doesn't really make any sense. mcall wasn't called by whatever used g0 last, so at best unwinding will get misleading results (e.g., it might look like cgocallback calls mcall?). Also clear the frame pointer on arm64. Other architectures don't use frame pointers. For #63630. Cq-Include-Trybots: luci.golang.try:gotip-linux-amd64-longtest,gotip-linux-arm64-longtest Change-Id: I6a6a636cb6404f3c95ecabdb969c9b8184615cee Reviewed-on: https://go-review.googlesource.com/c/go/+/669615 Reviewed-by: Michael Knyszek LUCI-TryBot-Result: Go LUCI Reviewed-by: Nick Ripley Reviewed-by: Cherry Mui Auto-Submit: Michael Pratt --- src/runtime/asm_amd64.s | 1 + src/runtime/asm_arm64.s | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/runtime/asm_amd64.s b/src/runtime/asm_amd64.s index 9c262a7ce8..14e2ea6686 100644 --- a/src/runtime/asm_amd64.s +++ b/src/runtime/asm_amd64.s @@ -452,6 +452,7 @@ goodm: get_tls(CX) // Set G in TLS MOVQ R14, g(CX) MOVQ (g_sched+gobuf_sp)(R14), SP // sp = g0.sched.sp + MOVQ $0, BP // clear frame pointer, as caller may execute on another M PUSHQ AX // open up space for fn's arg spill slot MOVQ 0(DX), R12 CALL R12 // fn(g) diff --git a/src/runtime/asm_arm64.s b/src/runtime/asm_arm64.s index 6c447ac23c..18d2dc5d57 100644 --- a/src/runtime/asm_arm64.s +++ b/src/runtime/asm_arm64.s @@ -233,7 +233,7 @@ TEXT runtime·mcall(SB), NOSPLIT|NOFRAME, $0-8 MOVD (g_sched+gobuf_sp)(g), R0 MOVD R0, RSP // sp = m->g0->sched.sp - MOVD (g_sched+gobuf_bp)(g), R29 + MOVD $0, R29 // clear frame pointer, as caller may execute on another M MOVD R3, R0 // arg = g MOVD $0, -16(RSP) // dummy LR SUB $16, RSP -- 2.51.0