使用Ptrace跟踪进程收到的异常信号(信号SIGTRAP是通过traceme后wait得到的)

以下是使用Ptrace跟踪进程收到的异常信号的正文内容,本文是网友投稿或本站会员创作,不代表本站观点:

最初的想法,我们在调试程序的时候,程序有时会出现进程收到SIGSEGV信号,异常退出。如果使用GDB,通过拦截该信号,并打印堆栈来实现,但在嵌入式的环境里,需要在嵌入式设备注入gdb server,来进行调试,比较麻烦。

我的一个想法,能够直接写一个程序,跑在设备里,拦截到特殊信号后,就打印堆栈呢?

有两个难点:

1、 怎么样实现信号的拦截,发现信号后,开始打印堆栈。

2、 打印堆栈,最好能够连带调用函数时的参数值。

信号的拦截

一开始我想到了使用ptrace,因为gdb是以它为基础的,gdb能够做到,它也能够做到。但通过查资料,ptrace主要是拦截系统调用,或者进行单步跟踪,没有谈及怎么样跟踪信号。那么在主程序,fork出子程序后,注册一个信号处理函数,在信号处理函数中打印出堆栈是否可行呢?

经过测试发现,在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反编译可以看到,应该更可靠一些。

本文链接:http://www.linux-cn.com/html/test/20070412/2709.shtml

转载于:https://my.oschina.net/mavericsoung/blog/132992

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用ptrace劫持进程一般需要以下几步: 1. 获取目标进程的PID(进程标识符)。 2. 使用ptrace系统调用附加到目标进程。 3. 使用ptrace系统调用在目标进程中设置断点,或者修改目标进程的寄存器状态、内存空间等。 4. 使用ptrace系统调用恢复目标进程的执行,等待目标进程触发断点,或者在目标进程中运行自己的代码。 5. 使用ptrace系统调用脱离目标进程。 下面是一个简单的示例代码,演示如何使用ptrace在目标进程中设置断点: ``` #include <stdio.h> #include <stdlib.h> #include <sys/ptrace.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> int main() { pid_t child_pid; long orig_eax; int status; int data; child_pid = fork(); if (child_pid == 0) { // 子进程 ptrace(PTRACE_TRACEME, 0, NULL, NULL); execl("/bin/ls", "ls", NULL); } else { // 父进程 wait(&status); while (WIFSTOPPED(status)) { // 目标进程被暂停 orig_eax = ptrace(PTRACE_PEEKUSER, child_pid, 4 * ORIG_EAX, NULL); if (orig_eax == SYS_write) { // 目标进程执行了write系统调用,设置断点 data = ptrace(PTRACE_PEEKUSER, child_pid, 4 * EBX, NULL); printf("Write called with " "parameter %d\n", data); // 修改目标进程的内存空间 ptrace(PTRACE_POKEDATA, child_pid, data, 0); } // 恢复目标进程执行 ptrace(PTRACE_SYSCALL, child_pid, NULL, NULL); wait(&status); } } return 0; } ``` 这段代码创建了一个子进程,在子进程中执行了/bin/ls命令,父进程使用ptrace附加到子进程,并在子进程中设置了一个断点,当子进程执行了write系统调用时,会暂停执行并触发断点。在断点处理函数中,修改了子进程的内存空间,并恢复了子进程的执行。这是一个简单的示例,实际的ptrace劫持进程需要更加复杂和细致的处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值