目录
(代码: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设置软断点
linux ptrace 图文详解(五) gdb设置硬断点、观察点
linux ptrace 图文详解(七) gdb、strace跟踪系统调用
linux ptrace 图文详解(八) gdb跟踪被调试程序的子线程、子进程
一、背景介绍
前序文章中,讨论了绝大多数 ptrace系统调用 在 gdb 中的使用场景,这些场景中有一个共同点:被调试程序(即:tracee)最终都会给父进程GDB发送SIGCHLD信号,并唤醒GDB的wait系统调用。这其中有一个很重要的点,前序文章中并没有展开详细讲解:GDB被唤醒后,是如何判断自己是被什么事件唤醒的、以及是被哪个调试任务唤醒的,被调试程序发生了什么事情?
本文将详细介绍上述问题的实现原理,即:gdb通过何种机制,来判断出被调试程序具体发生了什么事件(触发软断点、触发硬断点、执行了fork/pthread_create、执行了系统调用等等)。
二、gdb判断被唤醒原因的实现原理
当被调试程序正常运行时,gdb是处于一个等待状态的,通过wait系统调用,在内核中处于挂起状态。当被调试程序触发了某种事件后,会唤醒GDB的wait系统调用,同时,会将wait系统调用的参数status设置为指定值。gdb被唤醒后,正是通过 “wait 系统调用的返回值”、以及 “wait系统调用的参数status”,来判断出具体是被哪个调试任务唤醒、以及被唤醒的原因。
1) tacee 创建task(进程/线程)场景
tracee会将自身的 task struct->trace与 SIGTRAP 做位运算然后保存到 task->exit_code 中,随后在 gdb 调用wait(&status)时,内核会将 tacee 的 task->exit_code_记录到 status 的高8位。这样当 gdb 调用 wait 返回后得到的status 就记录了为何被唤醒,status 的高 16 位就记录着 tacee 的 task->trace 内容。
注:对于创建 task 这种 case,tracee 的 task->trace 值可能是 PTRACE EVENT FORK、PTRACE EVENT CLONE在 task create 的时候赋值。gdb 会调用 ptrace(PTRACE SETOPTIONS)设置 tracee 的 task->ptrace。
2)对于信号处理场景
tracee 的 task->exit_code会被设置为 signum,该值会被记录到 gdb 调用 wait 的 status 的高8位。
3)对于系统调用场景
tracee 的 task->exit_code 会被设置为SIGTRAP((ptace&PT TRACESYSGOOD)?0x80:0),这里的 ptrace 有PT TRACESYSGOOD 位。所以 gdb wait 的 status的高位 为:(SIGTRAP|0x80),这个0x80用于distinguish between a syscall stop and SIGTRAP delivery。
4)对于触发 硬断点、观察点 场景
gdb 调用 wait 的 status 的高位 为(SIGTRAP)
对于不同场景唤醒gdb时,其 wait 的入参 status 的字段内容、含义如下:
三、总结
在内核中,被调试程序因为各种原因需要将自己挂起并通知养父gdb时,会将被挂起的原因,依照一定规则,写入父进程wait的参数status中。这样一来,gdb被唤醒后根据wait的参数status中的字段内容,就可以判断出被调试程序是因为什么事件停下来了。