本系列是 x86 架构平台上 trampoline 的实现,从原理和实现上进行了详细的介绍。


perilogue 是个缝合单词,由 prologueepilogue 缝合组成。

prologueepilogue函数调用规约 中的术语。

  • prologue: 函数整体指令中最开始的几条指令。
  • epilogue: 函数整体指令中最末尾的几条指令。
  • perilogue: 函数整体指令中最开始、及最末尾的几条指令。

  • leave: 该指令的效果相当于 mov %ebp, %esp + pop %ebp
  • ret: 该指令的效果相当于 pop %eip

想要深入研究 perilogue 的小司机们,推荐阅读以下两篇博客:

perilogue of kernel function

使用 eBPF Talk: tailcall on x86【汇编慎入】【译】 中查看内核函数汇编的方法,直接看看内核函数的汇编吧。

以比较简单的内核函数 eth_type_trans() 为例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# cat /proc/kallsyms|grep eth_type_trans
ffffffff9eda5590 T __pfx_eth_type_trans
ffffffff9eda55a0 T eth_type_trans
ffffffff9fab5538 r __ksymtab_eth_type_trans
# gdb -q -c /proc/kcore -ex 'x/180i 0xffffffff9eda55a0' -ex 'quit'
[New process 1]
Core was generated by `BOOT_IMAGE=/vmlinuz-6.2.0-060200rc8-generic root=/dev/mapper/ubuntu--vg-ubuntu-'.
#0  0x0000000000000000 in ?? ()
   0xffffffff9eda55a0:  nopl   0x0(%rax,%rax,1)
   0xffffffff9eda55a5:  push   %rbp
   0xffffffff9eda55a6:  mov    %rsi,%rax
   0xffffffff9eda55a9:  mov    %rsp,%rbp
   0xffffffff9eda55ac:  sub    $0x10,%rsp
   # ...
   0xffffffff9eda5641:  jne    0xffffffff9eda56fd
   0xffffffff9eda5647:  leave
   0xffffffff9eda5648:  xor    %edx,%edx
   0xffffffff9eda564a:  xor    %ecx,%ecx
   0xffffffff9eda564c:  xor    %esi,%esi
   0xffffffff9eda564e:  xor    %edi,%edi
   0xffffffff9eda5650:  xor    %r8d,%r8d
   0xffffffff9eda5653:  ret

perilogue of bpf2bpf

eBPF Talk: bpf2bpf 特性揭秘 中的例子为例。

bpftool 工具提供了查看 JIT 后的 bpf prog 的程序汇编。在将 bpf prog 运行起来后:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# bpftool p
// ...
263: kprobe  name k_tcp_connect  tag d45d3e8749eeb769  gpl
    loaded_at 2023-02-20T14:07:53+0000  uid 0
    xlated 432B  jited 246B  memlock 4096B  map_ids 13
    btf_id 129

# bpftool p d j i 263 > bpftool.prog.dump.jit.log
# cat bpftool.prog.dump.jit.log
void handle_new_connection(void * ctx, struct sock * sk):
bpf_prog_93a5a8fde5afab51_handle_new_connection:
; handle_new_connection(void *ctx, struct sock *sk)
   0:   nopl    (%rax,%rax)
   5:   nop
   7:   pushq   %rbp
   8:   movq    %rsp, %rbp
   b:   subq    $24, %rsp
  12:   pushq   %rbx
  13:   pushq   %r13
  15:   pushq   %r14
  17:   movq    %rsi, %r14
  1a:   movq    %rdi, %rbx
  1d:   xorl    %edi, %edi
; ...
; }
  d5:   popq    %r14
  d7:   popq    %r13
  d9:   popq    %rbx
  da:   leave
  db:   retq
  dc:   int3

P.S. 完整的 log 文件请查看 bpftool.prog.dump.jit.log

总结

简要介绍了 x86 上的 perilogue

只需要记住:

  • prologue: 函数整体指令中最开始的几条指令。
  • epilogue: 函数整体指令中最末尾的几条指令。

因为将 eBPF Talk: poke on x86 作用在 prologue 的时候,威力无穷。