Programming with PTRACE, Part3 - 进程的终止与信号
在Part2中,我们粗略了解了如何使用ptrace
获得系统调用信息,即在一个大循环里不断获取程序信息,如果程序退出则停止循环。当然,那个判断异常简陋,几乎无法处理任何特殊情况。我将在本Part中详细解说各种异常情况的处理,同时讲解各种信号相关的问题。
一些重要的宏
在使用wait4
后,程序的信息被存储在sta
变量中,这些信息被存储在这个整数的不同二进制位上,这儿有一系列宏用于帮我们提取这些信息。以下信息是我对man 3 wait
中相关部分的翻译,同时参考了这个页面
WIFEXITED 如果进程正常退出,返回一个非0值(通常是进程调用了`exit()`或是`_exit()`)
WIFSIGNALED 如果进程由于一个未被捕获的信号而被终止,返回一个非0值
WIFSTOPPED 当进程被停止(非终止)时,返回一个非0值(通常发生在当进程处于`traced`状态时)
WEXITSTATUS 当`WIFEXITED`为非0值,获得进程`main()`函数的返回值
WTERMSIG 如果`WIFSIGNALED`为非0值,获得引起进程终止的信号代码
WSTOPSIG 如果`WIFSTOPPED`为非0值,获得引起进程停止的信号代码
除了这六个,还有WIFCONTINUED
和WCOREDUMP
两个宏,不过我们用不到,我也没仔细研究,就不说了。
当进程自行终止时,WIFEXITED
即为true
,配套使用WEXITSTATUS
获得返回值,不做过多解释。当子进程进行系统调用时,WIFSTOPPED
为true
,同时WSTOPSIG
等于SIGTRAP
(信号代码为7),我们可以用这种方法区分syscall-stop
和signal-delivery-stop
。当有一个外部信号要发送给子进程,这个信号会先到达父进程,使WIFSTOPPED
为true
,同时WSTOPSIG
等于该信号的信号代码。父进程可以选择将这个信号继续传递或是不传递,甚至传递另一个信号给子进程。一旦信号真正到达子进程,就进入子进程自己的处理流程或是系统默认动作,可能触发WIFSIGNALED
,比如SIGINT
。
在所有信号中,SIGKILL
是一个例外,它不会经过父进程引发WIFSTOPPED
,而是直接传递到子进程,引发WIFSIGNALED
。
信号的传递与修改
之前提到,父进程需要将信号传递给子进程,这是由ptrace(PTRACE_SYSCALL,pid,0,0)
的第四个参数决定的。如果为0,就不传递信号,否则传递对应代码的信号,比如ptrace(PTRACE_SYSCALL,pid,0,9)
就将信号9(SIGKILL)传递给了子进程。
修改信号简直信手拈来,传一个你想要传的信号即可。
strsignal()和代码
strsignal()
接受一个整数参数,返回const char*
,用于把信号代码变为对应的、人类可读的字符串描述,定义于string.h
。下面给出判断程序退出的代码:
1 | wait4(pid,&sta,0,&ru); |
来自ptrace的高级选项
你也许会纠结,如果外部传递了一个SIGTRAP
信号,那么如何分辨呢?答案是使用PTRACE_SETOPTIONS
设置PTRACE_O_TRACESYSGOOD
标记,即在while之前,第一个wait之后,第一个PTRACE_SYSCALL
之前,使用ptrace(PTRACE_SETOPTIONS,pid,0,PTRACE_O_TRACESYSGOOD)
。这会使得syscall-stop
导致的WSTOPSIG
从SIGTRAP
变为SIGTRAP|0x80
,而普通的来自外部的SIGTRAP
依然是SIGTRAP
。