1、函数插桩
1.1、”events/raw_syscalls/sys_enter”、”events/raw_syscalls/sys_exit” trace event的插桩
在系统调用的出口、入口路径上,系统布置了两个tracepoint格式的trace event。
el0_svc为用户态系统调用的入口,arch\arm64\kernel\entry.S:
1 | /* |
继续分析syscall_trace_enter()/syscall_trace_exit() hook函数,arch/arm64/kernel/ptrace.c:
1 | asmlinkage int syscall_trace_enter(struct pt_regs *regs) |
trace_sys_enter()、trace_sys_exit()这两个tracepoint桩函数,是通过trace event定义的。在include/trace/events/syscalls.h头文件中,定义了两个标准trace event:
1 | #undef TRACE_SYSTEM |
在arch/arm64/kernel/ptrace.c文件中,需要创建对应的tracepoint:
1 | #define CREATE_TRACE_POINTS |
创建完成后,可以在events目录下看到新的event,”events/raw_syscalls/sys_enter”、”events/raw_syscalls/sys_exit”:
1 | /d/tracing # ls events/raw_syscalls/ |
完成上述的步骤后,在系统调用的入口/出口分别插入了两个tracepoint:trace_sys_enter()、trace_sys_exit()。这就是1级插桩点:
- 可以通过enable “events/raw_syscalls/sys_enter”、”events/raw_syscalls/sys_exit”的方式把本trace event的trace函数加入到tracepoint的调用链表。这些会记录TP_fast_assign()中的数据到trace buffer,这个信息也能记录系统调用的进入/退出,但是这个信息比较笼统,可以使用每个系统调用本身的event来记录更详细的信息;
- 还可以手工调用register_trace_sys_enter()、register_trace_sys_exit()函数来加入更多的函数到tracepoint的调用链表中。后续就利用这个特性把具体系统调用event的函数挂载了进来;
1.2、syscall event的插桩
除了上面描述的1级插桩点,对每一个具体的syscall还创建了两个trace event:”sys_enter”#sname、”sys_exit”#sname。include/linux/syscalls.h:
1 | #ifdef CONFIG_FTRACE_SYSCALLS |
在使用宏SYSCALL_DEFINE0()…SYSCALL_DEFINE6()来定义系统调用时,对应的两个trace_event_call被创建: event_enter_##sname、event_exit_##sname。对应的还有一个结构被创建:__syscall_meta_##sname。这个meta数据中主要存放的是syscall的参数数据。
在syscall event初始化时,会把meta数据存放到syscalls_metadata[]数组。kernel/trace/trace_syscalls.c:
1 | start_kernel() -> trace_init() -> trace_event_init() -> init_ftrace_syscalls() |
上述的trace_event_call的指针同时被存储在section(“_ftrace_events”)当中,所以系统初始化时会在events文件夹下面自动创建对应文件夹:
1 | /d/tracing # ls events/syscalls/ |
1.2、syscall event的enable
经过上面两步:
- 1、使用TRACE_EVENT_FN()标准的方法创建了两个trace event:sys_enter、sys_exit。并且其tracepoint桩函数trace_sys_enter()、trace_sys_exit()在系统调用的入口/出口被显式调用;
- 2、使用自定义的方式为每个系统调用定义了两个syscall event:”sys_enter”#sname、”sys_exit”#sname。但是这两个event并没有定义桩函数,还没有调用入口;
接下来syscall event的enable操作中,会把syscall event的trace函数加入到数组中,同时把自己的桩函数通过register_trace_sys_enter()、register_trace_sys_exit()注册到1级插桩点的tracepoint。
event enable的执行路径为:ftrace_enable_fops -> event_enable_write() -> ftrace_event_enable_disable() -> __ftrace_event_enable_disable() -> call->class->reg(call, TRACE_REG_UNREGISTER/TRACE_REG_REGISTER, file);
syscall event的call->class->reg()为syscall_enter_register()/syscall_exit_register():
1 | struct trace_event_class __refdata event_class_syscall_enter = { |
2、数据存入
syscall enter event的数据存入路径为:ftrace_syscall_enter():
1 | static void ftrace_syscall_enter(void *data, struct pt_regs *regs, long id) |
syscall exit event的数据存入路径为:ftrace_syscall_exit():
1 | static void ftrace_syscall_exit(void *data, struct pt_regs *regs, long ret) |
2.1、数据格式
syscall enter event的数据格式:
syscall exit event的数据格式:
2.2、filter
trigger、filter和普通的trace event操作一致,请参考:trace event
3、数据取出
1、从trace文件读出的“raw_syscalls/sys_enter、sys_exit” event数据格式为:
1 | /d/tracing # echo 1 > events/raw_syscalls/sys_enter/enable |
使用标准的trace event定义,不管实际的参数情况怎么样?只能固定的保存和打印6个参数。
2、从trace文件读出的syscall event数据格式为:
1 | /d/tracing # echo 1 > events/syscalls/sys_enter_ioctl/enable |
在syscall event定义时给.event.funcs赋值,syscall enter event使用enter_syscall_print_funcs,syscall exit event使用exit_syscall_print_funcs:
1 | #define SYSCALL_TRACE_ENTER_EVENT(sname) \ |
在数据读出时,会调用到event对应的event->funcs->trace()函数,seq_read() -> s_show() -> print_trace_line() -> print_trace_fmt() -> event->funcs->trace():
syscall enter event对应print_syscall_enter:
1 | static enum print_line_t |
syscall exit event对应print_syscall_exit:
1 | static enum print_line_t |