目录
1. 前言
本文主要是根据阅码场 《Linux内核tracers的实现原理与应用》视频课程在aarch64上的实践。通过观察钩子函数的创建过程以及替换过程,理解trace的原理。本文同样以blk_update_request函数为例进行说明。
kernel版本:5.10
平台:arm64
2. ARM64栈帧结构
在开始介绍arm64架构下的ftrace之前,先来简要说明一下arm64栈帧的相关知识。arm64有31个通用寄存器r0-r30,其中r0-r7用于Parameter/result 寄存器; r29为Frame Pointer寄存器,r30为Link寄存器,指向上级函数的返回地址;SP为栈指针。
将以如下代码为例,说明它的栈帧结构:
/*
* ARCH: armv8
* GCC版本:aarch64-linux-gnu-gcc (Linaro GCC 5.4-2017.01) 5.4.1 20161213
*/
int fun2(int c,int d)
{
return 0;
}
int fun1(int a,int b)
{
int c = 1;
int d = 2;
fun2(c, d);
return 0;
}
int main(int argc,char **argv)
{
int a = 0;
int b = 1;
fun1(a,b);
}
aarch64-linux-gnu-objdump -d a.out 反汇编后的结果为:
0000000000400530 <fun2>:
/* 更新sp到fun2的栈顶 */
400530: d10043ff sub sp, sp, #0x10
400534: b9000fe0 str w0, [sp,#12]
400538: b9000be1 str w1, [sp,#8]
40053c: 52800000 mov w0, #0x0 // #0
400540: 910043ff add sp, sp, #0x10
400544: d65f03c0 ret
0000000000400548 <fun1>:
/* 分配48字节栈空间,先更新sp=sp-48, 再入栈x29, x30, 此时sp指向栈顶 */
400548: a9bd7bfd stp x29, x30, [sp,#-48]!
/* x29、sp指向栈顶*/
40054c: 910003fd mov x29, sp
/* 入栈fun1参数0 */
400550: b9001fa0 str w0, [x29,#28]
/* 入栈fun1参数1 */
400554: b9001ba1 str w1, [x29,#24]
/* 入栈fun1局部变量c */
400558: 52800020 mov w0, #0x1 // #1
40055c: b9002fa0 str w0, [x29,#44]
/* 入栈fun1局部变量d */
400560: 52800040 mov w0, #0x2 // #2
400564: b9002ba0 str w0, [x29,#40]
400568: b9402ba1 ldr w1, [x29,#40]
40056c: b9402fa0 ldr w0, [x29,#44]
/* 跳转到fun2 */
400570: 97fffff0 bl 400530 <fun2>
400574: 52800000 mov w0, #0x0 // #0
400578: a8c37bfd ldp x29, x30, [sp],#48
40057c: d65f03c0 ret
0000000000400580 <main>:
/* 分配48字节栈空间,先更新sp=sp-48, 再入栈x29, x30, 此时sp指向栈顶*/
400580: a9bd7bfd stp x29, x30, [sp,#-48]!
/* x29、sp指向栈顶*/
400584: 910003fd mov x29, sp
/* 入栈main参数0 */
400588: b9001fa0 str w0, [x29,#28]
/* 入栈main参数1 */
40058c: f9000ba1 str x1, [x29,#16]
/* 入栈变量a */
400590: b9002fbf str wzr, [x29,#44]
400594: 52800020 mov w0, #0x1 // #1
/* 入栈变量b */
400598: b9002ba0 str w0, [x29,#40]
40059c: b9402ba1 ldr w1, [x29,#40]
4005a0: b9402fa0 ldr w0, [x29,#44]
/* 跳转到fun1 */
4005a4: 97ffffe9 bl 400548 <fun1>
4005a8: 52800000 mov w0, #0x0 // #0
4005ac: a8c37bfd ldp x29, x30, [sp],#48
4005b0: d65f03c0 ret
4005b4: 00000000 .inst 0x00000000 ; undefined
对应栈帧结构为:

总结一下:
通过对aarch64代码反汇编的分析,可以得出:
- 每个函数在入口处首先会分配栈帧空间,且一次分配,确定栈帧顶,之后sp将不再变化;
- 每个函数的栈帧顶部存放的是caller的栈帧顶指针,即fun1的栈帧顶存放的是main栈帧顶指针;
- 对于最后一级callee函数,由于x29保存了上一级caller的栈顶sp指针,因此不在需要入栈保存,如示例中fun2执行时,此时x29指向fun1的栈帧顶
下面我们将根据是否开启ftrace配置,并区分编译阶段、链接阶段和运行阶段,分别查看钩子函数的替换及构建情况。
3. 编译阶段
3.1 未开启ftrace时的blk_update_request
00000000000012ac <blk_update_request>:
12ac: d10183ff sub sp, sp, #0x60
12b0: a9017bfd stp x29, x30, [sp,#16]
12b4: 910043fd add x29, sp, #0x10
12b8: a90253f3 stp x19, x20, [sp,#32]
12bc: a9035bf5 stp x21, x22, [sp,#48]
12c0

本文详细解析了Linux内核Ftrace在ARM64架构下的实现原理,从编译到运行各阶段,展示了如何通过Ftrace跟踪函数调用。
最低0.47元/天 解锁文章
2896

被折叠的 条评论
为什么被折叠?



