(一)gdb调试原理html
此部分转自:https://blog.csdn.net/u012658346/article/details/51159971 https://www.cnblogs.com/xsln/p/ptrace.html网络
gdb调试的原理是基于ptrace系统调用,ptrace()系统调用提供了一个方法,该方法使一个程序(追踪者)能够观察和控制另一个程序(被追踪者)的执行,并检查和改变被追踪者的内存及寄存器。它主要用于实现断点调试和追踪系统调用。函数
当被追踪时,被追踪线程在接收信号时会被中止,即便那个信号是被忽略的也是如此(SIGKILL除外)。追踪程序会在一个调用waitpid(或者其余类wait系统调用)时收到通知,该调用会返回一个包含被追踪线程中止的缘由的状态值。当被追踪线程中止时,追踪程序可使用多种ptrace请求来检查和编辑被追踪线程。追踪程序可让被追踪线程继续运行,有选择地忽略发过来的信号(甚至能够发送一个彻底不一样的信号给被追踪线程)性能
利用ptrace系统调用,可在被调试程序和gdb之间创建追踪关系。而后全部发送给被调试程序(被追踪线程)的信号(除SIGKILL)都会被gdb截获,gdb根据截获的信号,查看被调试程序相应的内存地址,并控制被调试的程序继续运行。测试
ptrace系统调用原型:编码
long ptrace(enum __ptrace_request request, pid_t pid,void *addr,void *data); spa
request参数的主要选项:
PTRACE_TRACEME:由子进程调用,表示本进程将被其父进程跟踪,交付给这个进程的全部信号,即便信号是忽略处理的(除SIGKILL以外),都将使其中止,父进程将经过wait()获知这一状况。.net
PTRACE_ATTACH: attach到一个指定的进程,使其成为当前进程跟踪的子进程,而子进程的行为等同于它进行了一次PTRACE_TRACEME操做。可是,须要注意的是,虽然当前进程成为被跟踪进程的父进程,可是子进程使用getppid()的到的仍将是其原始父进程的pid。当你在gdb中使用attach命令来跟踪一个指定进程/线程的时候,gdb就自动成为改进程的父进程,而被跟踪的进程则使用了一次PTRACE_TRACEME,gdb也就瓜熟蒂落的接管了这个进程。 命令行
PTRACE_CONT:继续运行以前中止的子进程。可同时向子进程交付指定的信号。线程
gdb三种调试方式:
1)attach并调试一个已经运行的进程:
肯定须要进行调试的进程id,运行gdb,输入attch pid,如:gdb 12345。gdb将对指定进行执行以下操做:ptrace(PTRACE_ATTACH,pid,0,0), 创建本身与进程号为pid的进程间的跟踪关系。即利用PTRACE_ATTACH,使本身变成被调试程序的父进程。用attach创建起来的跟踪关系,能够调用ptrace(PTRACE_DETACH,pid,...)来解除。注意attach进程时的权限问题,如一个非root权限的进程是不能attach到一个root进程上的 。
2)运行并调试一个新的进程,利用fork+execve执行被测试的程序,子进程在执行execve以前调用ptrace(PTRACE_TRACEME),创建了与父进程(debugger)的跟踪关系:
运行gdb,经过命令行参数或file指定目标调试程序,如gdb ./test
输入run命令,gdb执行下述操做:
经过fork()系统调用建立一个新进程
在新建立的子进程中调用ptrace(PTRACE_TRACEME,0,0,0)
在子进程中经过execv()系统调用加载用户指定的可执行文件
3)远程调试目标主机上新建立的进程
gdb运行在调试机,gdbserver运行在目标机,经过两者之间定义的数据格式进行通讯
gdb调试基础--信号
gdb调试的实现都是创建在信号的基础上的,在使用参数为PTRACE_TRACEME或PTRACE_ATTACH的ptrace系统调用创建调试关系后,交付给目标程序的任何信号首先都会被gdb截获。 所以gdb能够先行对信号进行相应处理,并根据信号的属性决定是否要将信号交付给目标程序。
.断点原理:
1) 断点的实现原理,就是在指定的位置插入断点指令,当被调试的程序运行到断点的时候,产生SIGTRAP信号。该信号被gdb捕获并进行断点命中断定,当gdb判断出此次SIGTRAP是断点命中以后就会转入等待用户输入进行下一步处理,不然继续。
2) 断点的设置原理: 在程序中设置断点,就是先将该位置的原来的指令保存,而后向该位置写入int 3。当执行到int 3的时候,发生软中断,内核会给子进程发出SIGTRAP信号,固然这个信号会被转发给父进程。而后用保存的指令替换int3,等待恢复运行。
3) 断点命中断定:gdb把全部的断点位置都存放在一个链表中,命中断定即把被调试程序当前中止的位置和链表中的断点位置进行比较,看是断点产生的信号,仍是无关信号。
4) 条件断点的断定:原理同3),只是恢复断点处的指令后,再多加一步条件判断。若表达式为真,则触发断点。因为须要判断一次,所以加入条件断点后,无论有没有触发到条件断点,都会影响性能。在x86平台,某些硬件支持硬件断点,在条件断点处不插入int 3,而是插入一个其余指令,当程序走到这个地址的时候,不发出int 3信号,而是先去比较一下特定寄存器和某个地址的内容,再决定是否发送int 3。所以,当你的断点的位置会被程序频繁地“路过”时,尽可能使用硬件断点,会对提升性能有帮助。
单步跟踪:
next指令能够实现单步调试,即每次只执行一行语句。一行语句可能对应多条及其指令,当执行next指令时,gdb会计算下一条语句对应的第一条指令的地址,而后控制目标程序走到该位置中止。
(二)qemu中的gdbserver
正常状况下进行远程调试须要被调试端安装有gdbserver程序,而qemu中内置了gdbserver模块,基于此可以使用gdb实现对qemu虚拟机的远程调试,GDB/GDBSERVER调试模型的原理以下:
在GDB/GDBSERVER调试模型中,GDBSERVER是一个轻量级的GDB调试器,在调试过程当中担任着调试代理的角色。在调试过程当中,主机和目标机之间使用串口或者网络做为通讯的通道。在主机上GDB经过这条通道使用一种基于ASCII的简单通信协议RSP与在目标机上运行的GDBSERVER进行通信。GDB发送指令,如内存、寄存器读写,GDBSERVER则首先与运行被调试程序映像的进程进行绑定,而后等待GDB发来的数据,对包含命令的数据包进行解析以后便进行相关处理,而后将结果返回给主机上的GDB。
RSP协议将GDB/GDBSERVER间通信的内容更看作是数据包,数据包的内容都使用ASCII字符。每个数据包都遵循这样的格式:$ <调试信息>#<校验码>.
如上图所示,包的内容会以16进制的形式来编码(enhex),#后面的两位数字是校验码,具体的计算方式是数据包中全部字符求和再用256求余数。而数据包的内容,也就是RSP协议的载体,将会是gdb接收的命令。接受方在收到数据包以后,对数据包进行校验,若正确回应“+”,反之回应“-”。
RSP 协议中定义的主要命令能够分为 3 类:
(1)寄存器/内存读写命令
命令 g: 读全部寄存器的值
命令 G:写全部寄存器的值
命令 P: 写某个寄存器
命令 m: 读某个内存单元
命令 M:写某个内存单元
(2)程序控制命令
命令?: 报告上一次的信号
命令 s: 单步执行
命令 c: 继续执行
命令 k: 终止程序
(3)其它命令
命令 O:控制台输出(Console Output )
命令 E:出错回应(Error response)
当主机使用gdb调试时,gdb和qemu中内置的gdbserver就使用上述模型进行交互,从而对qemu虚拟机进行调试。如gdb调试端发送x/ <n/f/u> <addr>表示读取addr处的内容,命令经RSP协议封装成数据包发送至qemu的gdbserver端,gdbserver收到数据包后对其进行校验,校验成功后进行解析处理并返回至gdb客户端。
开启gdbserver以后,会等待来自gdb的链接请求,默认端口为1234,gdb使用ip和端口与gdbserver链接:
链接创建后会调用gdb_handlesig()等待stdin传来的gdb指令,调用gdb_read_byte()解析用户的输入,并对数据包进行校验,若校验正确调用gdb_handle_packet()进行gdb命令的处理。
以下解析字符为m时,表示读取某个内存单元,则调用函数target_memory_rw_debug()进行内存单元的读取,该函数最后调用cpu_memory_rw_debug()读取内存内容
当解析字符为g时,使用gdb_read_register读取寄存器信息,该函数会调用特定CPU类型的回调函数:
x86下调用以下函数,经过qemu为虚拟机维护的CPUX86State结构体获得虚拟机的寄存器信息:
相似地,插入一个断点时:
在kvm_enabled的状况下,调用kvm_insert_breakpoint:
该函数进行断点的插入并最终使用kvm_update_guest_debug向kvm更新客户机的debug状态,该函数调用kvm_invoke_set_guest_debug,进一步调用kvm_vcpu_ioctl(cpu, KVM_SET_GUEST_DEBUG,&dbg_data->dbg)执行ioctl至kvm中设置相关异常向量,BP(breakpoint,int3),DB(int 1)(插一句,可经过设置异常位图中的这两个位对上述指令进行拦截)