55// The number of frames to unwind per frame-unwinding eBPF program.
66#define FRAMES_PER_PROGRAM 16
77
8+ // The max number of loops to unroll when searching for the correct CodeHeader.
9+ // Should be log base 2 of a reasonable number of modules to binary-search through.
10+ #define CODE_HEADER_SEARCH_ITERATIONS 16
11+
812bpf_map_def SEC ("maps" ) beam_procs = {
913 .type = BPF_MAP_TYPE_HASH ,
1014 .key_size = sizeof (pid_t ),
1115 .value_size = sizeof (BEAMProcInfo ),
1216 .max_entries = 256 ,
1317};
1418
15- static EBPF_INLINE ErrorCode unwind_one_beam_frame (PerCPURecord * record , u64 active_ranges ) {
19+ typedef struct BEAMRangeEntry {
20+ u64 start ;
21+ u64 end ;
22+ } BEAMRangeEntry ;
23+
24+ typedef struct BEAMRangesInfo {
25+ u64 modules ;
26+ u64 n ;
27+ BEAMRangeEntry first , mid , last ;
28+ } BEAMRangesInfo ;
29+
30+ static EBPF_INLINE ErrorCode unwind_one_beam_frame (PerCPURecord * record , BEAMRangesInfo * ranges ) {
1631 UnwindState * state = & record -> state ;
1732 Trace * trace = & record -> trace ;
18- u64 sp = state -> sp , fp = state -> fp , pc = state -> pc ;
33+ u64 pc = state -> pc ;
1934
20- DEBUG_PRINT ("beam: pc: %llx, sp: %llx, fp: %llx" , pc , sp , fp );
35+ if (pc < ranges -> first .start || pc > ranges -> last .end ) {
36+ return ERR_BEAM_PC_INVALID ;
37+ }
2138
22- bpf_probe_read_user ( & state -> fp , sizeof ( u64 ), ( void * ) fp ) ;
23- bpf_probe_read_user ( & state -> pc , sizeof ( u64 ), ( void * )( fp + 8 )) ;
24- bpf_probe_read_user ( & state -> sp , sizeof ( u64 ), ( void * )( fp + 16 )) ;
39+ u64 low = 0 ;
40+ u64 high = ranges -> n ;
41+ u64 current = low + ( high - low ) / 2 ;
2542
26- unwinder_mark_nonleaf_frame (state );
43+ BEAMRangeEntry current_range ;
44+ current_range .start = ranges -> mid .start ;
45+ current_range .end = ranges -> mid .end ;
2746
28- _push_with_return_address (trace , active_ranges , pc , FRAME_MARKER_BEAM , state -> return_address );
47+ #pragma unroll
48+ for (int i = 0 ; i < CODE_HEADER_SEARCH_ITERATIONS ; i ++ ) {
49+ if (pc < current_range .start ) {
50+ high = current ;
51+ } else if (pc >= current_range .end ) {
52+ low = current + 1 ;
53+ } else {
54+ // `pc` is in the `current_range` CodeHeader
55+ _push_with_return_address (trace , current_range .start , pc , FRAME_MARKER_BEAM , state -> return_address );
56+ break ;
57+ }
58+
59+ current = low + (high - low ) / 2 ;
60+ u64 addr = ranges -> modules + current * sizeof (BEAMRangeEntry );
61+ if (bpf_probe_read_user ((void * )& current_range , sizeof (BEAMRangeEntry ), (void * )(addr ))) {
62+ DEBUG_PRINT ("beam: Failed to read ranges[%llu]" , current );
63+ return -1 ;
64+ }
65+ }
66+
67+ u64 fp = state -> fp ;
68+ bpf_probe_read_user (& state -> fp , sizeof (u64 ), (void * )fp );
69+ bpf_probe_read_user (& state -> pc , sizeof (u64 ), (void * )(fp + 8 ));
2970
3071 return ERR_OK ;
3172}
@@ -36,41 +77,76 @@ static EBPF_INLINE ErrorCode unwind_one_beam_frame(PerCPURecord *record, u64 act
3677static EBPF_INLINE int unwind_beam (struct pt_regs * ctx ) {
3778 PerCPURecord * record = get_per_cpu_record ();
3879 if (!record ) {
80+ DEBUG_PRINT ("beam: no PerCPURecord found" );
3981 return -1 ;
4082 }
4183
4284 Trace * trace = & record -> trace ;
4385 u32 pid = trace -> pid ;
4486
45- DEBUG_PRINT ("==== unwind_beam %d ====" , trace -> stack_len );
46-
47- int unwinder = PROG_UNWIND_STOP ;
48- ErrorCode error = ERR_OK ;
4987 BEAMProcInfo * info = bpf_map_lookup_elem (& beam_procs , & pid );
5088
5189 if (!info ) {
5290 DEBUG_PRINT ("beam: no BEAMProcInfo for this pid" );
53- goto exit ;
91+ return -1 ;
5492 }
5593
94+ DEBUG_PRINT ("==== unwind_beam %d ====" , trace -> stack_len );
95+
5696 // "the_active_code_index" symbol is from:
5797 // https://github.com/erlang/otp/blob/OTP-27.2.4/erts/emulator/beam/code_ix.c#L46
5898 u32 the_active_code_index ;
59- bpf_probe_read_user (& the_active_code_index , sizeof (u32 ), (void * )info -> the_active_code_index );
99+ if (bpf_probe_read_user (& the_active_code_index , sizeof (u32 ), (void * )info -> the_active_code_index )) {
100+ DEBUG_PRINT ("beam: Failed to read the_active_code_index" );
101+ return -1 ;
102+ }
60103
61104 // Index into the active static `r` variable using the currently-active code index
62105 // https://github.com/erlang/otp/blob/OTP-27.2.4/erts/emulator/beam/beam_ranges.c#L62
63106 u64 active_ranges = info -> r + (the_active_code_index * info -> ranges_sizeof );
64- DEBUG_PRINT ("==== unwind_beam active_ranges: %llx, the_active_code_index: %d ====" , active_ranges , the_active_code_index );
65107
108+ DEBUG_PRINT ("beam: r: %llx, the_active_code_index: %d, active_ranges: %llx" , info -> r , the_active_code_index , active_ranges );
109+
110+ BEAMRangesInfo ranges ;
111+
112+ if (bpf_probe_read_user (& ranges .modules , sizeof (u64 ), (void * )(active_ranges + info -> ranges_modules ))) {
113+ DEBUG_PRINT ("beam: Failed to read ranges.modules" );
114+ return -1 ;
115+ }
116+
117+ if (bpf_probe_read_user (& ranges .n , sizeof (u64 ), (void * )(active_ranges + info -> ranges_n ))) {
118+ DEBUG_PRINT ("beam: Failed to read ranges.n" );
119+ return -1 ;
120+ }
121+
122+ DEBUG_PRINT ("beam: modules: %llx, n: %llu" , ranges .modules , ranges .n );
123+
124+ if (bpf_probe_read_user (& ranges .first , sizeof (BEAMRangeEntry ), (void * )(ranges .modules ))) {
125+ DEBUG_PRINT ("beam: Failed to read ranges.first" );
126+ return -1 ;
127+ }
128+ if (bpf_probe_read_user (& ranges .mid , sizeof (BEAMRangeEntry ), (void * )(ranges .modules + (ranges .n / 2 ) * sizeof (BEAMRangeEntry )))) {
129+ DEBUG_PRINT ("beam: Failed to read ranges.mid" );
130+ return -1 ;
131+ }
132+ if (bpf_probe_read_user (& ranges .last , sizeof (BEAMRangeEntry ), (void * )(ranges .modules + (ranges .n - 1 ) * sizeof (BEAMRangeEntry )))) {
133+ DEBUG_PRINT ("beam: Failed to read ranges.last" );
134+ return -1 ;
135+ }
136+
137+ DEBUG_PRINT ("beam: ranges.first.start: %llx, ranges.last.end: %llx" , ranges .first .start , ranges .last .end );
138+
139+ int unwinder = PROG_UNWIND_STOP ;
140+ ErrorCode error = ERR_OK ;
66141#pragma unroll
67142 for (int i = 0 ; i < FRAMES_PER_PROGRAM ; i ++ ) {
68143 if (record -> state .fp & 0x3 ) {
69144 unwinder = PROG_UNWIND_NATIVE ;
70145 break ;
71146 }
72147
73- error = unwind_one_beam_frame (record , active_ranges );
148+ unwinder_mark_nonleaf_frame (& record -> state );
149+ error = unwind_one_beam_frame (record , & ranges );
74150 if (error ) {
75151 break ;
76152 }
0 commit comments