简述
默认本篇文章的读者已经了解arm、编译和简单汇编。 arm64采用fpatchable-function-entry而不是pg,这里暂不讨论,扩展了解https://zhuanlan.zhihu.com/p/104683907。
简单叙述ftrace的原理和流程,ftrace的实现依赖3个过程,分别为编译、链接重定位、系统初始化、开启追踪。
- 编译时:内核开启
CONFIG_FUNCTION_TRACER 后,编译选项会增加-pg ,在每个函数中打上标记; - 链接重定位:将编译时标记链接到处理函数;
- 系统初始化:将函数标记替换为nop指令;
- 开启追踪:将函数标记替换为ftrace_caller,记录信息。
编译时处理
- 当内核开启
CONFIG_FUNCTION_TRACER 时,kernel Makefile会在编译参数增加-pg。
# The arch Makefiles can override CC_FLAGS_FTRACE. We may also append it later.
ifdef CONFIG_FUNCTION_TRACER
CC_FLAGS_FTRACE := -pg
endif
- 示例驱动代码
int func2(int p1, int p2)
{
return p1+p2;
}
int func1(int p1, int p2)
{
return func2(p1, p2);
}
static int __init hello_init(void)
{
pr_info("hello driver init!\n");
func1(1, 2);
return 0;
}
static void __exit hello_exit(void)
{
pr_info("hello driver exit\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
- 未开启
CONFIG_FUNCTION_TRACER 时,objdump -d如下:
00000000 <func2>:
0: e1a0c00d mov ip, sp
4: e92dd800 push {fp, ip, lr, pc}
8: e24cb004 sub fp, ip, #4
c: e0800001 add r0, r0, r1
10: e89da800 ldm sp, {fp, sp, pc}
00000014 <func1>:
14: e1a0c00d mov ip, sp
18: e92dd800 push {fp, ip, lr, pc}
1c: e24cb004 sub fp, ip, #4
20: e0800001 add r0, r0, r1
24: e89da800 ldm sp, {fp, sp, pc}
Disassembly of section .init.text:
00000000 <init_module>:
0: e1a0c00d mov ip, sp
4: e92dd800 push {fp, ip, lr, pc}
8: e24cb004 sub fp, ip, #4
c: e59f0008 ldr r0, [pc, #8] ; 1c <init_module+0x1c>
10: ebfffffe bl 0 <_printk>
14: e3a00000 mov r0, #0
18: e89da800 ldm sp, {fp, sp, pc}
1c: 00000000 .word 0x00000000
- 开启
CONFIG_FUNCTION_TRACER 时,objdump -d如下:
00000000 <func2>:
0: e1a0c00d mov ip, sp
4: e92dd800 push {fp, ip, lr, pc}
8: e24cb004 sub fp, ip, #4
c: e52de004 push {lr} ; (str lr, [sp, #-4]!)
10: ebfffffe bl 0 <__gnu_mcount_nc>
14: e0800001 add r0, r0, r1
18: e89da800 ldm sp, {fp, sp, pc}
0000001c <func1>:
1c: e1a0c00d mov ip, sp
20: e92dd800 push {fp, ip, lr, pc}
24: e24cb004 sub fp, ip, #4
28: e52de004 push {lr} ; (str lr, [sp, #-4]!)
2c: ebfffffe bl 0 <__gnu_mcount_nc>
30: e0800001 add r0, r0, r1
34: e89da800 ldm sp, {fp, sp, pc}
Disassembly of section .init.text:
00000000 <init_module>:
0: e1a0c00d mov ip, sp
4: e92dd800 push {fp, ip, lr, pc}
8: e24cb004 sub fp, ip, #4
c: e52de004 push {lr} ; (str lr, [sp, #-4]!)
10: ebfffffe bl 0 <__gnu_mcount_nc>
14: e59f0008 ldr r0, [pc, #8] ; 24 <init_module+0x24>
18: ebfffffe bl 0 <_printk>
1c: e3a00000 mov r0, #0
20: e89da800 ldm sp, {fp, sp, pc}
24: 00000000 .word 0x00000000
- 对比可以发现,开启
CONFIG_FUNCTION_TRACER 后,编译代码会增加bl 0 <__gnu_mcount_nc> 。
链接时处理
如果编译为ko,则链接时是在insmod时进行;如果是编译进内核,vmlinux就是链接后的文件,可以通过反汇编查看。 为方便展示,将上述驱动编译进内核,objdum -d vmlinux如下。
8069a694 <func1>:
8069a694: e1a0c00d mov ip, sp
8069a698: e92dd830 push {r4, r5, fp, ip, lr, pc}
8069a69c: e24cb004 sub fp, ip, #4
8069a6a0: e52de004 push {lr} ; (str lr, [sp, #-4]!)
8069a6a4: ebe9d726 bl 80110344 <__gnu_mcount_nc>
8069a6a8: e0050091 mul r5, r1, r0
8069a6ac: e1a00005 mov r0, r5
8069a6b0: ebffffe8 bl 8069a658 <func2>
8069a6b4: e59f1014 ldr r1, [pc, #20] ; 8069a6d0 <func1+0x3c>
8069a6b8: e1a04000 mov r4, r0
8069a6bc: e1a02000 mov r2, r0
8069a6c0: e59f000c ldr r0, [pc, #12] ; 8069a6d4 <func1+0x40>
8069a6c4: ebebe82d bl 80194780 <printk>
8069a6c8: e0850004 add r0, r5, r4
8069a6cc: e89da830 ldm sp, {r4, r5, fp, sp, pc}
可以看到bl 0 <__gnu_mcount_nc> 已经被替换为bl 80110344 <__gnu_mcount_nc> 。查看__gnu_mcount_nc实现如下。
80110344 <__gnu_mcount_nc>:
80110344: e1a0c00e mov ip, lr
80110348: e8bd4000 ldmfd sp!, {lr}
8011034c: e1a0f00c mov pc, ip
相当于执行了一个return; 。
系统初始化
todo
|