ptrace是linux系统中为了调试专门设立的一种系统调用。要想调试调试一个进程,有两种方式:
PTRACE_TRACEME和PTRACE_ATTACH。这两种方式的主要区别可以概括为:
PTRACE_TRACEME是子进程主动申请被TRACE。而PTRACE_ATTACH是父进程自己要attach到子进程,相当于子进程是被动的trace。
PTRACE_TRACEME程序设置的框架大概为:
if(pid==0)//child
{
ptrace(PTACE_TRACEME,0,NULL,NULL);
exec(...);
}
else//parent
{
wait(&status);
if(WIFSTOPPED(status))
fprintf(stderr,"%s\n",WSTOPSIG(status);//打印出子进程当前的状态。
}
看似简单的代码,其实内核做了大量的工作,我们在此进行详细分析。
当程序执行ptrace系统调用,传入PTRACE_TRACEME函数时,内核执行下面的代码段:
static int ptrace_traceme(void)
{
int ret = -EPERM;
write_lock_irq(&tasklist_lock);
/* Are we already being traced? */
if (!current->ptrace) {
ret = security_ptrace_traceme(current->parent);
/*
* Check PF_EXITING to ensure ->real_parent has not passed
* exit_ptrace(). Otherwise we don't report the error but
* pretend ->real_parent untraces us right after return.
*/
if (!ret && !(current->real_parent->flags & PF_EXITING)) {
current->ptrace = PT_PTRACED;
__ptrace_link(current, current->real_parent);
}
}
write_unlock_irq(&tasklist_lock);
return ret;
}
从上面的源码我们可以看出:PTRACE_TRACEME并没有使子进程停止,而是将进行一系列判断之后(父进程是否能对子进程进行跟踪的合法性检查),将子进程链接到父进程的ptrace链表中。
真正导致子进程停止的是exec系统调用,该系统调用成功之后,内核会判断该进程是否被ptrace跟踪,如果被跟踪的话,内核将向该进程发送SIGTRAP信号。该信号将导致当前进程停止。具体的代码如下:
static inline void ptrace_event(int event, unsigned long message)
{
if (unlikely(ptrace_event_enabled(current, event))) {
current->ptrace_message = message;
ptrace_notify((event << 8) | SIGTRAP);
} else if (event == PTRACE_EVENT_EXEC) {
/* legacy EXEC report via SIGTRAP */
if ((current->ptrace & (PT_PTRACED|PT_SEIZED)) == PT_PTRACED)
send_sig(SIGTRAP, current, 0);
}
}
我们都知道:SIGTRAP信号是专门为debug设计的,当内核踩中断点的时候(断点就是int 3,相应的回调函数就是do_trap),就会发送这个信号:
asmlinkage void do_trap(struct pt_regs *regs, unsigned long address)
{
siginfo_t info;
memset(&info, 0, sizeof(info));
info.si_signo = SIGTRAP;
info.si_code = TRAP_TRACE;
info.si_addr = (void *)address;
force_sig_info(SIGTRAP, &info, current);
regs->pc += 4;
}
从这里我们可以看出,进程被trace之后,对于子进程的许多操作(尤其是父进程感兴趣的操作)都改变了,目的是为了让父进程感知到这些事件,exec就是一个例子。
此时,父进程的wait操作将被唤醒。我们知道wait操作就是父进程用来检测子进程的退出情况,wait操作返回有三种情况:
1、子进程正常退出
int status;
wait(&status);//status保存了子进程当前的状态
WIFEXITED(status)://如果子进程确实正常退出,则为真
WEXITSTATUS(status)://打印出进程的退出码
2、子进程因为收到信号而退出
int status;
wait(&status);
WIFSIGNALED(status)://如果子进程确实正常退出,则为真
WTERMSIG(status)://打印出进程的退出码
3、子进程因为收到信号而暂停
int status;
wait(&status);
WIFSTOPPED(status)://如果子进程确实正常退出,则为真
WSTOPSIG(status)://打印出进程的退出码
显然,PTRACE_TRACEME对应的就是第三种情况。如果想要输出信号对应的描述性字符串,可以这样操作:sprintf(stderr,"%s\n",WSTOPSIG(status));