异常、调试、ptrace的过程和原理

前言

从今年上半年开始,一直在利用ptrace做plt hook的一些工作。在此之前,对ptrace并没有什么了解。在工作开始后,为了能尽快出成果,也只是快速的翻了一下ptrace的相关接口函数,然后根据查阅到的资料,在较短时间内达成了项目目标。对其中的原理,并未做深入研究。今天看了一些资料,做个简单总结,以此为记。

异常

什么是异常

异常是异常控制流的一种形式,一部分由硬件实现,一部分由操作系统实现;它是控制流中的突变,用来相应处理其状态中的某些变化;
——《深入理解计算机系统》
更详细的异常说明,可以参考:
Linux 对异常和中断处理
异常、调试、ptrace的过程和原理_第1张图片
可以用下图来简单表示:
异常、调试、ptrace的过程和原理_第2张图片


异常、调试、ptrace的过程和原理_第3张图片

异常的种类

异常可以分为:
中断(interrupt)、陷阱(trap)、故障(fault)、终止(abort)
异常、调试、ptrace的过程和原理_第4张图片
各自对应的典型例子:
中断:IO输入
陷阱:syscall
故障:缺页
终止:kill
各自对应的处理流程可以参考:《深入理解计算机系统》
或者: CSAPP笔记之异常控制流与进程

异常处理

在内核中,有一个保存异常处理程序的数组称为异常表。异常号实际上就是这个数组的下标。
一部分异常号是由处理器提供的(除0,缺页,断点,内存访问违规等)另一部分是由操作系统提供的(系统调用,外部 I/O 设备的信号)。
异常、调试、ptrace的过程和原理_第5张图片
当程序运行时检测到哪种异常号,就去找到相应的异常处理程序。然后执行。
与普通的函数调用有以下区别:

1、异常处理后执行的指令有可能还是当前指令(例如页错误),有可能是下一条指令。而函数调用肯定是下一条指令。
2、异常处理程序运行在内核模式。

对linux而言,其异常处理流程一般如下:
1、准备阶段
在内核栈保存通用寄存器内容(称为现场信息),这部分大多用汇编语言程序实现
2、处理阶段
采用 C 函数进行具体处理。函数名由 do_ 前缀和处理程序名组成,如 do_overflow 为溢出处理函数。大部分函数的处理方式:保存硬件出错码(如果有的话)和异常类型号,然后,向当前进程发送一个信号
当前进程接受到信号后,若有对应信号处理程序,则转信号处理程序执行;若没有,则调用内核 abort 例 程,终止当前进程
3、恢复阶段
恢复保存在内核栈中的各个寄存器的内容,切换到用户态并返回到当前进程的断点处继续执行
——上述内容参考: Linux 对异常和中断处理

小结
异常–》产生信号
关于信号的一些知识,可以参考:
Linux下的信号简单理解

ptrace

什么是ptrace

ptrace() 是一个由 Linux 内核提供的系统调用,允许一个用户态进程检查、修改另一个进程的内存和寄存器,通常用在类似 gdb、strace 的调试器中,用来实现断点调试、系统调用的跟踪。
可以man一下ptrace:
异常、调试、ptrace的过程和原理_第6张图片
异常、调试、ptrace的过程和原理_第7张图片

注意:
observe and control the execution of another process
如何实现的呢?
任何发给被跟踪进程(tracee)的信号(除了 SIGKILL)将导致该进程停止运行,而跟踪进程(tracer)会通过 wait() 获得通知。 另外,该进程之后所有对 exec() 的调用都将使操作系统产生一个 SIGTRAP 信号发送给它,这让父进程有机会在新程序开始执行之前获得对子进程的控制权;

举例来说,如何实现gdb当中的断点功能:
1.根据输入的行数或者函数名或者其他有效信息,得到要断点的代码在进程中的具体位置——也就是代码所对应的虚拟地址
2.通过ptrace的接口,修改代码对应位置的执行码为 0xcc (debug专用)
3.当程序执行到该位置时,就会触发异常
4.在异常处理过程中,系统会向当前进程(被跟踪进程)发送一个SIGTRAP信号
5.被跟中进程被停止,兵器,跟踪进程截获了发送给被跟踪进程的SIGTRAP信号
6.这时候,可以通过相应的gdb命令,来查看调用栈(bt)或者某个变量的值
7.如果继续向下执行,gdb会通过ptrace的接口来把断点位置的机器码恢复,并控制被跟踪进程继续向下执行
。。。。。。

总结

简单概括,就是:
产生异常–>异常处理–>产生信号–>ptrace原理(跟踪进程截获被跟踪进程的信号并停止被跟踪进程)
关于linux内核调试的实现,可以参考:
关于linux内核调试的实现

你可能感兴趣的:(计算机基础,Linux)