如下是使用Ptrace跟踪进程收到的异常信号的正文内容,本文是网友投稿或本站会员创做,不表明本站观点: html
最初的想法,咱们在调试程序的时候,程序有时会出现进程收到SIGSEGV信号,异常退出。若是使用GDB,经过拦截该信号,并打印堆栈来实现,但在嵌入式的环境里,须要在嵌入式设备注入gdb server,来进行调试,比较麻烦。 linux
个人一个想法,可以直接写一个程序,跑在设备里,拦截到特殊信号后,就打印堆栈呢? 函数
有两个难点: 测试
一、 怎么样实现信号的拦截,发现信号后,开始打印堆栈。 google
二、 打印堆栈,最好可以连带调用函数时的参数值。 调试
信号的拦截 server
一开始我想到了使用ptrace,由于gdb是以它为基础的,gdb可以作到,它也可以作到。但经过查资料,ptrace主要是拦截系统调用,或者进行单步跟踪,没有谈及怎么样跟踪信号。那么在主程序,fork出子程序后,注册一个信号处理函数,在信号处理函数中打印出堆栈是否可行呢? htm
通过测试发现,在exec以后,起来的进程会冲掉原来该子进程的代码段,从而注册的信号处理事件失效。 进程
这条路是否到头了呢? 事件
继续想到,既然exec会冲掉原来的代码段,那么我是否能够采用把程序起来后,经过黑客的手段注入代码,并执行来实现呢?
理论上来说,应该可行,但难度比较大。
峰回路转……
在linux内核分析的时候,发现这么一段话:
在do_signal中,首先它检查current接收进程是否正受某一进程的监控:既然这样,do_signal就调用notify_parent()和schedule让监控进程知道进行的信号处理。
这说明signal的信号处理确定是可以被监控的。
经过google发现,strace是可以拦截signal的。我经过分析strace的源代码,发现了strace是怎么样监控信号。
Child=fork()
If(child==0)
{
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
Exec //执行待监控的进程
}
Else
{
pid = wait4(-1, &status, wait4_options, cflag ? &ru : NULL);
if (WIFSIGNALED(status)) //检查是否由于信号挂起
if (WIFEXITED(status))
if (!WIFSTOPPED(status))
if (WSTOPSIG(status) != SIGTRAP) //注意SIGSEGC信号就是经过这里处理的。
也就是说,对于信号的拦截,它是经过标记为trace以后,经过wai的status来判断的。而不是象通常针对系统调用的跟踪。
既然找到了地方,接下来的就很容易了,打印堆栈:
long ebp=0,eip=0,i=0;
struct user_regs_struct regs;
ptrace(PTRACE_GETREGS, pid,NULL,®s);
tprintf("EIP: %lx ----EBP:%lx/n",regs.eip,regs.ebp); //首先得到当前的ebp寄存器中的地址,该地址指向了堆栈中的帧。
ebp = regs.ebp;
eip = regs.eip;
for(;;)
{
i++;
tprintf("%d: eip:%lx ebp:%lx/n",i,eip,ebp);
eip = ptrace(PTRACE_PEEKDATA,pid,ebp+4,NULL); //根据帧的结构,eip的地址在ebp地址+4,把来把其解析成函数,具体的文本就全靠它了。
ebp = ptrace(PTRACE_PEEKDATA,pid,ebp,NULL); //注意先后两行的顺序,这个命令使ebp指向堆栈中的上一个帧
if( 0 == ebp)
break;
}
这样,就可以把堆栈中,各个函数所运行的地址拿到。
你可使用objdump –D 反编译你所跟踪的进程,来得到其对应的函数。
由于打印堆栈不是在所跟踪的进程内,因此不可使用backtrace_symbols来对其进行解析,怎么样把其解析成直观的函数调用,目前我考虑本身实现反汇编来编写,打印出函数的名称。
采用backtrace_symbols有个局限,在编译程序时必须加上-rdynamic才行,按理说经过objdump反编译能够看到,应该更可靠一些。