目录
[一、gdb/strace 跟踪程序系统调用](#一、gdb/strace 跟踪程序系统调用)
(代码:linux 6.3.1,架构:arm64)
One look is worth a thousand words. ------ Tess Flanders
相关链接:
linux ptrace 图文详解(二) PTRACE_TRACEME 跟踪程序
linux ptrace 图文详解(三) PTRACE_ATTACH 跟踪程序
linux ptrace 图文详解(五) gdb设置硬断点、观察点
一、gdb/strace 跟踪程序系统调用
gdb、strace都能够跟踪被调试程序在执行过程中的所有系统调用,其底层依赖的就是ptrace(PTRACE_SYSCALL) 的能力。当使用 PTRACE_SYSCALL 时,被跟踪的进程会在每次系统调用的开始或结束时被暂停挂起,并通知父进程(gdb、strace)。这使得开发者能够详细地监控和分析程序的系统调用行为,对于调试和性能分析非常有用。
下图是strace跟踪ls命令执行期间,用到的所有系统调用信息:

二、实现原理

上图是gdb跟踪被调试程序执行过程中所有系统调用的原理:
1)gdb通过ptrace(PTRACE_SYSCALL),为被调试程序的task置位TIF_SYSCALL_TRACE,然后返回;
2)被调试程序执行系统调用陷入内核;
3)在系统调用的入口,调用tracehook_report_syscall,判断当前task是否置位TIF_SYSCALL_TRACE;
4)若置位,则将PTRACE_EVENTMSG_SYSCALL_ENTRY记录到被调试程序task的ptrace_message中;
5)随后给父进程gdb发送SIGCHLD信号,并唤醒gdb的wait操作,同时设置父进程gdb wait操作的status值 ( (SIGTRAP | 0x80) << 8) | 0X70;其中,0x80代表被调试程序触发了syscall!
6)被调试程序将自己挂起;
7)gdb被唤醒后,检查wait的status返回值内容,发现置位了0x80,说明被调试程序执行了syscall;
8)gdb通过ptrace(PTRACE_GETEVENTMSG) 获取被调试任务内核中的task->ptrace_message内容,来判断当前被调试程序是刚进入syscall、还是已经执行完毕syscall;
9)gdb唤醒被调试程序继续运行,被调试程序被调度运行后,调用invoke_syscall执行真正的系统调用任务;
10)当invoke_syscall执行完毕后,会再次调用tracehook_report_syscall,将自身挂起并通知gdb(这个流程与上述3~6步一致),唯一的区别是:此时设置到被调试任务task->ptrace_message中的字段是PTRACE_EVENTMSG_SYSCALL_EXIT;
11)gdb被唤醒后,判断出被调试程序是因为syscall挂起的,通过ptrace(PTRACE_GETEVENTMSG)可以获取到被调试程序执行完毕系统调用的信息;
是
三、代码实现
1、gdb、strace 通过 ptrace(PTRACE_SYSCALL) 为被调试程序置位标志
cpp
ptrace_request
case PTRACE_SYSCALL:
return ptrace_resume(child, request, data) {
if (request == PTRACE_SYSCALL)
set_task_syscall_work(child, SYSCALL_TRACE) {
set_ti_thread_flag(task_thread_info(t), TIF_SYSCALL_TRACE)
}
}
2、被调试程序进入系统调用前夕,将自己暂停下来并通知gdb
cpp
el0t_64_sync_handler {
el0_svc
do_el0_svc
el0_svc_common {
unsigned long flags = current_thread_info()->flags
if (has_syscall_work(flags)) {
syscall_trace_enter {
if (flags & (_TIF_SYSCALL_EMU | _TIF_SYSCALL_TRACE))
tracehook_report_syscall(struct pt_regs *regs = regs,
enum ptrace_syscall_dir dir = PTRACE_SYSCALL_ENTER) {
regno = (is_compat_task() ? 12 : 7)
saved_reg = regs->regs[regno]
regs->regs[regno] = dir
if (dir == PTRACE_SYSCALL_ENTER) {
tracehook_report_syscall_entry(regs){
ptrace_report_syscall(regs, unsigned long message = PTRACE_EVENTMSG_SYSCALL_ENTRY) {
/* 保存syscall entry/exit event */ current->ptrace_message = message // 保存 PTRACE_EVENTMSG_SYSCALL_ENTRY 到ptrace_message, 之后gdb会调用ptrace来获取该信息
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ptrace_notify(int exit_code = SIGTRAP | 0x80) {
ptrace_do_notify(int signr = SIGTRAP, int exit_code = exit_code, int why = CLD_TRAPPED) {
ptrace_stop(exit_code, why, int clear_code = 1, &info) { // 通知tracer, 并将自己挂起
current->last_siginfo = info
current->exit_code = exit_code
do_notify_parent_cldstop(current, true, why = CLD_TRAPPED)
info.si_signo = SIGCHLD
info.si_code = why // A.K.A: CLD_TRAPPED
info.si_status = tsk->exit_code & 0x7f
__group_send_sig_info(SIGCHLD, &info, parent)
send_signal(sig, info, p, PIDTYPE_TGID)
__send_signal(sig, info, t, type, force)
__wake_up_parent
freezable_schedule
current->last_siginfo = NULL // after wake up by gdb, clear last_siginfo
}
}
}
if (current->exit_code) {
send_sig(current->exit_code, current, 1)
current->exit_code = 0
}
current->ptrace_message = 0
return fatal_signal_pending(current)
}//ptrace_report_syscall
}//tracehook_report_syscall_entry
}
regs->regs[regno] = saved_reg
}
return regs->syscallno
}//syscall_trace_enter
}
invoke_syscall
...
}
}
3、被调试程序系统调用执行完毕后,将自己暂停下来并通知gdb
cpp
el0t_64_sync_handler {
el0_svc
do_el0_svc
el0_svc_common {
unsigned long flags = current_thread_info()->flags
if (has_syscall_work(flags)) {
syscall_trace_enter
}
invoke_syscall
syscall_trace_exit {
unsigned long flags = READ_ONCE(current_thread_info()->flags)
if (flags & (_TIF_SYSCALL_TRACE | _TIF_SINGLESTEP))
tracehook_report_syscall(regs, PTRACE_SYSCALL_EXIT) {
tracehook_report_syscall_exit(regs, step = 0) {
ptrace_report_syscall(regs, unsigned long message = PTRACE_EVENTMSG_SYSCALL_EXIT) {
current->ptrace_message = message
ptrace_notify(exit_code = SIGTRAP | 0x80)
ptrace_do_notify(SIGTRAP, exit_code, why = CLD_TRAPPED) {
kernel_siginfo_t info
info.si_signo = signr
info.si_code = exit_code // A.K.A: SIGTRAP | 0x80
ptrace_stop(exit_code, why, 1, &info) {
current->last_siginfo = info
current->exit_code = exit_code
do_notify_parent_cldstop(current, true, why) {
info.si_signo = SIGCHLD
info.si_code = why // A.K.A: CLD_TRAPPED
info.si_status = tsk->exit_code & 0x7f
__group_send_sig_info(SIGCHLD, &info, parent)
send_signal(sig, info, p, PIDTYPE_TGID)
__send_signal(sig, info, t, type, force)
}
current->last_siginfo = NULL // after wake up by gdb, clear last_siginfo
}
}//ptrace_do_notify
}//ptrace_report_syscall
}//tracehook_report_syscall_exit
}//tracehook_report_syscall
}//syscall_trace_exit
}//el0_svc_common
}
四、总结
gdb、strace监控被调试程序的系统调用,主要是依赖系统调用的路径上,根据被调试程序是否置位TIF_SYSCALL_TRACE,通过tracehook_report_syscall将自身暂停,并记录相应的信息(PTRACE_EVENTMSG_SYSCALL_ENTRY、PTRACE_EVENTMSG_SYSCALL_EXIT)到current->ptrace_message中供后续gdb、strace通过ptrace(PTRACE_GETEVENTMSG)获取,最后通知gdb。