eBPF Talk: trampoline stack on x86【汇编慎入】
文章目录
本系列是 x86 架构平台上 trampoline 的实现,从原理和实现上进行了详细的介绍。
- eBPF Talk: poke on x86【汇编慎入】
- eBPF Talk: perilogue on x86【汇编慎入】
- eBPF Talk: freplace on x86【汇编慎入】
- eBPF Talk: trampoline on x86【汇编慎入】
- eBPF Talk: trampoline on x86【续】【汇编慎入】
- eBPF Talk: trampoline stack on x86【汇编慎入】
前面学习了 trampoline 的工作原理:
继续深入学习 trampoline 底层实现,回答以下 2 个问题:
fentrybpf prog 是如何获取 trace 目标函数的参数的?fexitbpf prog 为什么能够同时获取 trace 目标函数的参数和返回值?
TL;DR 答案是,trampoline 将 trace 目标函数的参数和返回值都保存到当前 trampoline bpf prog 的栈上了。
P.S. 相对比,kretprobe 能获取到返回值,但不一定能获取到参数。
trampoline stack on x86
答案尽在源代码中。
|
|

fexit
由 trampoline 生成的栈可知,fentry 和 fexit 获取 trace 目标函数参数的方式是一样的,所以分析 fexit 获取函数参数的实现,便可知 fentry 获取函数参数的实现。
使用 fexit 时,trampoline bpf prog 对应的汇编代码如下:

注意其中 “蓝色” 标出的三块汇编:
- 将参数压入栈。
- 将返回值压入栈。
- 将栈中的函数返回值
mov到%rax(返回值寄存器)。
inet_csk_complete_hashdance() 函数声明如下:
|
|
inet_csk_complete_hashdance() 函数有 4 个参数,依次将参数压入栈中。
|
|
| x86-64 | 说明 |
|---|---|
| rax | 返回值 |
| rdi | 第一个参数 |
| rsi | 第二个参数 |
| rdx | 第三个参数 |
| rcx | 第四个参数 |
| r8 | 第五个参数 |
| rbx | 第六个参数 |
P.S. %cl 指一个字节大小的第四个参数。

fentry tcp_connect()
eBPF 源代码:
|
|
x86 上的汇编:
|
|
关键在于 mov 0x0(%rdi),%rsi 这一条指令。
%rdi是第一个参数,即 bpf prog 里的第一个参数ctx;在fentry/fexitbpf prog 里,ctx是指向第一个参数所在栈的地址。0x0(%rdi)指将%rdi地址所在的内存的 8 个字节加载到%rsi寄存器(第二个参数)。
一条指令便准备好了 handle_new_connection() 函数的参数,因为复用了第一个参数。
fexit inet_csk_complete_hashdance()
eBPF 源代码:
|
|
x86 上的汇编:
|
|
关键在于 mov 0x20(%rdi),%rsi 这一条指令。
%rdi是第一个参数,即 bpf prog 里的第一个参数ctx;在fentry/fexitbpf prog 里,ctx是指向第一个参数所在栈的地址。0x20(%rdi)指将%rdi地址向上偏移0x20个字节所在的内存的 8 个字节(正是return value)加载到%rsi寄存器(第二个参数)。
一条指令便准备好了 handle_new_connection() 函数的参数,因为复用了第一个参数。
总结
一顿汇编和栈的分析,终于搞清楚以下 2 个问题:
fentrybpf prog 是如何获取 trace 目标函数的参数的?fexitbpf prog 为什么能够同时获取 trace 目标函数的参数和返回值?
这是因为 trampoline 将 trace 目标函数的参数和返回值都保存到当前 trampoline bpf prog 的栈上,而后 fentry/fexit/fmod_ret bpf prog 按需从栈上获取参数和返回值。
文章作者 Leon Hwang
上次更新 2023-05-23