Skip to content

Commit dfb9247

Browse files
committed
feat: add mcall support and fix systemstack arm64 register order
1 parent 9976c87 commit dfb9247

12 files changed

Lines changed: 407 additions & 129 deletions

File tree

interpreter/golabels/runtime_data.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,21 +27,27 @@ func getOffsets(vers string) support.GoLabelsOffsets {
2727
Hmap_log2_bucket_count: 0,
2828
// https://github.com/golang/go/blob/6885bad7dd86880be6929c0/src/runtime/map.go#L118
2929
Hmap_buckets: 0,
30-
// Absolute offsets from g struct start for g.sched (gobuf) fields.
31-
// g.sched starts at offset 56 (immediately after g.m at offset 48, pointer 8 bytes).
32-
// gobuf.sp is at offset 0 within gobuf - stable across all supported Go versions.
33-
// https://github.com/golang/go/blob/80e2e474b8d9124d03b744f/src/runtime/runtime2.go#L325
30+
// g.sched is a gobuf struct immediately following g.m (offset 48 + 8 = 56).
31+
// gobuf.sp is the first field (offset 0 in gobuf), gobuf.pc is the second (offset 8).
32+
// https://github.com/golang/go/blob/80e2e474b8d9124d03b744f4e2da099a4eec5957/src/runtime/runtime2.go#L311
3433
Sched_sp: 56,
34+
Sched_pc: 64,
35+
// gobuf.bp is at offset 48 within gobuf in go1.24 and earlier. In go1.25 and later,
36+
// it is at offset 40 because of ret field removal. (offset 56 + 48 = 104)
37+
// go1.25: https://github.com/golang/go/blob/6e676ab2b809d46623acb5988248d95d1eb7939c/src/runtime/runtime2.go#L315
38+
Sched_bp: 104,
3539
}
3640

3741
// Version enforcement takes place in the Loader function.
3842
if version.Compare(vers, "go1.26") >= 0 {
3943
offsets.Curg = 184
4044
offsets.Labels = 352
45+
offsets.Sched_bp = 96
4146
return offsets
4247
} else if version.Compare(vers, "go1.25") >= 0 {
4348
offsets.Curg = 184
4449
offsets.Labels = 344
50+
offsets.Sched_bp = 96
4551
return offsets
4652
} else if version.Compare(vers, "go1.24") >= 0 {
4753
offsets.Labels = 352

nativeunwind/elfunwindinfo/elfgopclntab.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@ var goFunctionsStopDelta = map[string]*sdtypes.UnwindInfo{
3232
"runtime.goexit": &sdtypes.UnwindInfoStop, // return address in all goroutine stacks
3333

3434
// Stack switch functions: when encountered during unwinding on the g0 (system) stack,
35-
// the unwinder crosses back to the goroutine stack by reading the goroutine's saved
36-
// context from gobuf.
35+
// the unwinder crosses back to the goroutine stack using the goroutine's saved
36+
// context from g.sched (gobuf).
3737
"runtime.systemstack": &sdtypes.UnwindInfoGoSystemstack,
38-
"runtime.mcall": &sdtypes.UnwindInfoStop,
38+
"runtime.mcall": &sdtypes.UnwindInfoGoMcall,
3939

4040
// signal return frame
4141
"runtime.sigreturn": &sdtypes.UnwindInfoSignal,

nativeunwind/stackdeltatypes/stackdeltatypes.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,18 @@ var UnwindInfoFramePointer = UnwindInfo{Flags: support.UnwindFlagCommand,
4949
// UnwindInfoGoSystemstack is the stack delta info for runtime.systemstack.
5050
// When encountered during unwinding, the unwinder crosses from the g0 system
5151
// stack to the goroutine stack by reading the saved FP and RA from the frame
52-
// pointer prologue at gobuf.sp (which equals gobuf.bp on both amd64 and arm64).
52+
// pointer prologue at gobuf.sp.
5353
var UnwindInfoGoSystemstack = UnwindInfo{Flags: support.UnwindFlagCommand,
5454
Param: support.UnwindCommandGoSystemstack}
5555

56+
// UnwindInfoGoMcall is the stack delta info for runtime.mcall.
57+
// When encountered during unwinding, the unwinder crosses from the g0 system
58+
// stack to the goroutine stack by reading the caller's saved registers directly
59+
// from gobuf.{pc, sp, bp}. If m.curg is nil (after dropg), the goroutine pointer
60+
// is recovered from the g0 stack at *(g0.sched.sp - 8).
61+
var UnwindInfoGoMcall = UnwindInfo{Flags: support.UnwindFlagCommand,
62+
Param: support.UnwindCommandGoMcall}
63+
5664
// UnwindInfoLR contains the description to unwind ARM64 function without a frame (LR only)
5765
var UnwindInfoLR = UnwindInfo{
5866
BaseReg: support.UnwindRegSp,

processmanager/execinfomanager/manager.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,9 @@ func NewExecutableInfoManager(
127127

128128
interpreterLoaders = append(interpreterLoaders, apmint.Loader)
129129

130-
// Always register golabels loader: the native unwinder needs Go runtime
130+
// Register golabels loader. The native unwinder needs Go runtime
131131
// offsets (m, curg, g.sched) to cross the systemstack boundary,
132-
// regardless of whether custom labels extraction is enabled.
132+
// to perform Go stack unwinding.
133133
interpreterLoaders = append(interpreterLoaders, golabels.Loader)
134134

135135
deferredFileIDs, err := lru.NewSynced[host.FileID, libpf.Void](deferredFileIDSize,

support/ebpf/go_runtime.h

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,23 @@
77
#include "tsd.h"
88
#include "types.h"
99

10-
// get_m_ptr reads the M (machine/OS thread) pointer for the current goroutine.
11-
// It does so by reading the G (goroutine) pointer from thread-local storage,
12-
// then following the g.m pointer.
10+
// get_g_ptr reads the current G (goroutine) pointer from thread-local storage.
11+
// TLS always contains the G that is currently executing on the thread. During
12+
// systemstack/mcall, this is g0 (the system goroutine) since we are on the
13+
// system stack.
1314
//
1415
// On aarch64, when tls_offset is 0 (non-CGO binaries), the G pointer is taken
1516
// from the r28 register saved in the unwind state instead of TLS.
16-
static EBPF_INLINE void *get_m_ptr(struct GoLabelsOffsets *offs, UnwindState *state)
17+
static EBPF_INLINE u64 get_g_ptr(struct GoLabelsOffsets *offs, UnwindState *state)
1718
{
1819
#if defined(__x86_64__)
1920
(void)state;
2021
#endif
2122
u64 g_addr = 0;
2223
void *tls_base = NULL;
2324
if (tsd_get_base(&tls_base) < 0) {
24-
DEBUG_PRINT("cl: failed to get tsd base; can't read m_ptr");
25-
return NULL;
25+
DEBUG_PRINT("cl: failed to get tsd base; can't read g_addr");
26+
return 0;
2627
}
2728
DEBUG_PRINT(
2829
"cl: read tsd_base at 0x%lx, g offset: %d", (unsigned long)tls_base, offs->tls_offset);
@@ -33,17 +34,31 @@ static EBPF_INLINE void *get_m_ptr(struct GoLabelsOffsets *offs, UnwindState *st
3334
g_addr = state->r28;
3435
#elif defined(__x86_64__)
3536
DEBUG_PRINT("cl: TLS offset for g pointer missing for amd64");
36-
return NULL;
37+
return 0;
3738
#endif
3839
}
3940

4041
if (g_addr == 0) {
4142
if (bpf_probe_read_user(&g_addr, sizeof(void *), (void *)((s64)tls_base + offs->tls_offset))) {
4243
DEBUG_PRINT("cl: failed to read g_addr, tls_base(%lx)", (unsigned long)tls_base);
43-
return NULL;
44+
return 0;
4445
}
4546
}
4647

48+
DEBUG_PRINT("cl: g_addr 0x%lx", (unsigned long)g_addr);
49+
return g_addr;
50+
}
51+
52+
// get_m_ptr reads the M (machine/OS thread) pointer for the current goroutine.
53+
// It does so by reading the G (goroutine) pointer from thread-local storage,
54+
// then following the g.m pointer.
55+
static EBPF_INLINE void *get_m_ptr(struct GoLabelsOffsets *offs, UnwindState *state)
56+
{
57+
u64 g_addr = get_g_ptr(offs, state);
58+
if (!g_addr) {
59+
return NULL;
60+
}
61+
4762
DEBUG_PRINT("cl: reading m_ptr_addr at 0x%lx + 0x%x", (unsigned long)g_addr, offs->m_offset);
4863
void *m_ptr_addr;
4964
if (bpf_probe_read_user(&m_ptr_addr, sizeof(void *), (void *)(g_addr + offs->m_offset))) {
@@ -54,4 +69,4 @@ static EBPF_INLINE void *get_m_ptr(struct GoLabelsOffsets *offs, UnwindState *st
5469
return m_ptr_addr;
5570
}
5671

57-
#endif // OPTI_GO_RUNTIME_H
72+
#endif // OPTI_GO_RUNTIME_H

0 commit comments

Comments
 (0)