分类: LINUX
2008-12-12 19:01:38
|
将上面的代码保存为dummy2.c。按下面的方法编译运行:
gcc -o dummy2 dummy2.c
./dummy2 &
现在我们可以用下面的代码来附着到dummy2上。
|
上面的程序仅仅是附着在子进程上,等待它结束,并测量它的eip( 指令指针)然后释放子进程。
设置断点
调试器是怎么设置断点的呢?通常是将当前将要执行的指令替换成trap指令,于是被调试的程序就会在这里停滞,这时调试器就可以察看被调试程序的信息了。被调试程序恢复运行以后调试器会把原指令再放回来。这里是一个例子:
|
上面的程序将把三个byte的内容进行替换以执行trap指令,等被调试进程停滞以后,我们把原指令再替换回来并把eip修改为原来的值。下面的图中演示了指令的执行过程
1. 进程停滞后 | 2. 替换入trap指令 |
3.断点成功,控制权交给了调试器 | 4. 继续运行,将原指令替换回来并将eip复原 |
在了解了断点的机制以后,往运行中的程序里面添加指令也不再是难事了,下面的代码会使原程序多出一个”hello world”的输出
这时一个简单的”hello world”程序,当然为了我们的特殊需要作了点修改:
|
使用 gcc –o hello hello.c来编译它。
在backward和forward之间的跳转是为了使程序能够找到”hello world” 字符串的地址。
使用GDB我们可以得到上面那段程序的机器码。启动GDB,然后对程序进行反汇编:
|
我们需要使用从man+3到backward+20之间的字节码,总共41字节。使用GDB中的x命令来察看机器码。
|
已经有了我们想要执行的指令,还等什么呢?只管把它们根前面那个例子一样插入到被调试程序中去!
代码:
|
将代码插入到自由空间
在前面的例子中我们将代码直接插入到了正在执行的指令流中,然而,调试器可能会被这种行为弄糊涂,所以我们决定把指令插入到进程中的自由空间中去。通过察看/proc/pid/maps可以知道这个进程中自由空间的分布。接下来这个函数可以找到这个内存映射的起始点:
|
ptrace的幕后工作
那么,在使用ptrace的时候,内核里发生了声么呢?这里有一段简要的说明:当一个进程调用了 ptrace( PTRACE_TRACEME, …)之后,内核为该进程设置了一个标记,注明该进程将被跟踪。内核中的相关原代码如下:
|
一次系统调用完成之后,内核察看那个标记,然后执行trace系统调用(如果这个进程正处于被跟踪状态的话)。其汇编的细节可以在 arh/i386/kernel/entry.S中找到。
现在让我们来看看这个sys_trace()函数(位于 arch/i386/kernel/ptrace.c )。它停止子进程,然后发送一个信号给父进程,告诉它子进程已经停滞,这个信号会激活正处于等待状态的父进程,让父进程进行相关处理。父进程在完成相关操 作以后就调用ptrace( PTRACE_CONT, …)或者 ptrace( PTRACE_SYSCALL, …), 这将唤醒子进程,内核此时所作的是调用一个叫wake_up_process() 的进程调度函数。其他的一些系统架构可能会通过发送SIGCHLD给子进程来达到这个目的。
小结:
ptrace函数可能会让人们觉得很奇特,因为它居然可以检测和修改一个运行中的程序。这种技术主要是在调试器和系统调用跟踪程序中使用。它使程序
员可以在用户级别做更多有意思的事情。已经有过很多在用户级别下扩展操作系统得尝试,比如UFO,一个用户级别的文件系统扩展,它使用ptrace来实现
一些安全机制。