@@ -116,8 +116,11 @@ static inline void
116
116
emit_local_call (struct ubpf_vm * vm , struct jit_state * state , uint32_t target_pc )
117
117
{
118
118
UNUSED_PARAMETER (vm );
119
- // Because the top of the stack holds the stack usage of the calling function,
120
- // we adjust the base pointer down by that value!
119
+ // Invariant: The top of the host stack always holds the amount of space needed
120
+ // by the currently-executing eBPF function.
121
+
122
+ // Because the top of the host stack holds the stack usage of the currently-executing
123
+ // function, we adjust the eBPF base pointer down by that value!
121
124
// sub r15, [rsp]
122
125
emit1 (state , 0x4c );
123
126
emit1 (state , 0x2B );
@@ -146,8 +149,8 @@ emit_local_call(struct ubpf_vm* vm, struct jit_state* state, uint32_t target_pc)
146
149
emit_pop (state , map_register (BPF_REG_7 ));
147
150
emit_pop (state , map_register (BPF_REG_6 ));
148
151
149
- // Because the top of the stack holds the stack usage of the calling function,
150
- // we adjust the base pointer back up by that value!
152
+ // Because the top of the host tack holds the stack usage of the currently-executing
153
+ // function, we adjust the eBPF base pointer back up by that value!
151
154
// add r15, [rsp]
152
155
emit1 (state , 0x4c );
153
156
emit1 (state , 0x03 );
@@ -236,6 +239,9 @@ ubpf_set_register_offset(int x)
236
239
}
237
240
238
241
/*
242
+ * JIT'd Code Layout & Invariants:
243
+ *
244
+ * 1. Layout of external dispatcher/helpers pointers
239
245
* In order to make it so that the generated code is completely standalone, all the necessary
240
246
* function pointers for external helpers are embedded in the jitted code. The layout looks like:
241
247
*
@@ -251,7 +257,13 @@ ubpf_set_register_offset(int x)
251
257
* External Helper Function Pointer Idx MAX_EXT_FUNCS-1 (8 bytes, maybe NULL)
252
258
* state->buffer + state->offset:
253
259
*
254
- * The layout and operation of this mechanism is identical for code JIT compiled for Arm.
260
+ * 2. Invariants
261
+ * a. The top of the host stack always contains an 8-byte value which is the size
262
+ * of the eBPF stack usage of currently-executing eBPF function. The invariant
263
+ * is maintained in the code generated for the EXIT, and CALL opcodes and in the
264
+ * code generated for the first instruction in an eBPF function.
265
+ *
266
+ * The layout and invariants are identical for code JIT compiled for Arm.
255
267
*/
256
268
257
269
static int
@@ -341,9 +353,13 @@ translate(struct ubpf_vm* vm, struct jit_state* state, char** errmsg)
341
353
int src = map_register (inst .src );
342
354
uint32_t target_pc = i + inst .offset + 1 ;
343
355
344
- // If a) the previous instruction could fallthrough to this instruction and
345
- // b) this instruction starts a local function, then
346
- // we have to "jump around" the code that manipulates the stack!
356
+ // If
357
+ // a) the previous instruction in the eBPF program could fallthrough
358
+ // to this instruction and
359
+ // b) the current instruction starts a local function,
360
+ // then there has to be a means to "jump around" the code that
361
+ // manipulates the stack when the program executes in the fallthrough
362
+ // path.
347
363
uint32_t fallthrough_jump_source = 0 ;
348
364
bool fallthrough_jump_present = false;
349
365
if (i != 0 && vm -> int_funcs [i ]) {
@@ -354,12 +370,23 @@ translate(struct ubpf_vm* vm, struct jit_state* state, char** errmsg)
354
370
}
355
371
}
356
372
373
+ /*
374
+ * There is an invariant that the top of the host stack always contains
375
+ * the amount of local space used by the currently-executing eBPF program.
376
+ * So, if we are at the start of an eBPF function, we will need to put the
377
+ * amount of its local space usage on the top of the host stack. It is safe
378
+ * to adjust the stack by only 8 bytes here because the `call` pushed the
379
+ * return address (8 bytes). In combination, there is a 16-byte change
380
+ * to the stack pointer which maintains the 16-byte stack alignment.
381
+ */
357
382
if (i == 0 || vm -> int_funcs [i ]) {
358
383
size_t prolog_start = state -> offset ;
359
384
uint16_t stack_usage = ubpf_stack_usage_for_local_func (vm , i );
385
+ // Move the stack pointer to make space for a 64-bit integer ...
360
386
emit_alu64_imm32 (state , 0x81 , 5 , RSP , 8 );
387
+ // ... that is filled with the amount of space needed for the local function.
361
388
emit1 (state , 0x48 );
362
- emit1 (state , 0xC7 );
389
+ emit1 (state , 0xC7 ); // mov immediate to [rsp]
363
390
emit1 (state , 0x04 ); // Mod: 00b Reg: 000b RM: 100b
364
391
emit1 (state , 0x24 ); // Scale: 00b Index: 100b Base: 100b
365
392
emit4 (state , stack_usage );
@@ -370,9 +397,10 @@ translate(struct ubpf_vm* vm, struct jit_state* state, char** errmsg)
370
397
} else {
371
398
assert (state -> bpf_function_prolog_size == state -> offset - prolog_start );
372
399
}
373
-
374
400
}
375
401
402
+ // If there was a jump inserted to bypass the host stack manipulation code,
403
+ // we need to update its target.
376
404
if (fallthrough_jump_present ) {
377
405
fixup_jump_target (state -> jumps , state -> num_jumps , fallthrough_jump_source , state -> offset );
378
406
}
@@ -737,8 +765,11 @@ translate(struct ubpf_vm* vm, struct jit_state* state, char** errmsg)
737
765
}
738
766
break ;
739
767
case EBPF_OP_EXIT :
740
- /* On entry to every local function we add an additional 8 bytes.
741
- * Undo that here!
768
+ /* There is an invariant that the top of the host stack contains
769
+ * the amout of space used by the currently-executing eBPF function.
770
+ * 8 bytes are required for the storage. So, anytime that we leave an
771
+ * eBPF function, we must pop its local usage from the top of the
772
+ * host stack.
742
773
*/
743
774
emit_alu64_imm32 (state , 0x81 , 0 , RSP , 8 );
744
775
emit_ret (state );
0 commit comments