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