gdb调试原理

gdb调试原理转载:http://www.spongeliu.com/240.html     https://blog.csdn.net/u012658346/article/details/51159971gdb凭什么可以调试一个程序?凭什么能够接管一个程序的运行?我以前也想过这样的问题,但是后来居然忘记去查看了。我想到了我们的二进制翻译器,想到了intel的pin,Dynamo。这些都是将翻译后的…

转载:http://www.spongeliu.com/240.html

         https://blog.csdn.net/u012658346/article/details/51159971

gdb凭什么可以调试一个程序?凭什么能够接管一个程序的运行?我以前也想过这样的问题,但是后来居然忘记去查看了。我想到了我们的二进制翻译器,想到了intel的pin,Dynamo。这些都是将翻译后的代码放到codecache中去运行,然后接管整个程序的执行。gdb是不是也一样呢?

如果真是这样,为什么我记得用gdb跑一个程序,这个程序会有一个单独的进程?gdb的attach功能又是怎么实现的?

想了想,我还是没有答上来。面试就是由这么一个又一个细节的小杯具最后汇集成一个大杯具。

那么,gdb到底是凭什么接管的一个进程的执行呢?其实,很简单,通过一个系统调用:ptrace。ptrace系统调用的原型如下:

#include <sys/ptrace.h>
 
long ptrace(enum __ptrace_request request, pid_t pid,
                   void *addr, void *data);

说明:ptrace系统调用提供了一种方法来让父进程可以观察和控制其它进程的执行,检查和改变其核心映像以及寄存器。 主要用来实现断点调试和系统调用跟踪。(man手册)

其实,说到这里,一切原理层面应该都比较明朗了(且先不去管内核中是怎么实现ptrace的)。gdb就是调用这个系统调用,然后通过一些参数来控制其他进程的执行的。

下面我们来看ptrace函数中request参数的一些主要选项:

PTRACE_TRACEME: 表示本进程将被其父进程跟踪,交付给这个进程的所有信号,即使信号是忽略处理的(除SIGKILL之外),都将使其停止,父进程将通过wait()获知这一情况。

这是什么意思呢?我们可以结合到gdb上来看。如果在gdb中run一个程序,首先gdb会fork一个子进程,然后该子进程调用ptrace系统调用,参数就是PTRACE_TRACEME,然后调用一个exec执行程序。基本过程是这样,细节上可能会有出入。需要注意的是,这个选项PTRACE_TRACEME是由子进程调用的而不是父进程!

以下选项都是由父进程调用:

PTRACE_ATTACH:attach到一个指定的进程,使其成为当前进程跟踪的子进程,而子进程的行为等同于它进行了一次PTRACE_TRACEME操作。但是,需要注意的是,虽然当前进程成为被跟踪进程的父进程,但是子进程使用getppid()的到的仍将是其原始父进程的pid。

这下子gdb的attach功能也就明朗了。当你在gdb中使用attach命令来跟踪一个指定进程/线程的时候,gdb就自动成为改进程的父进程,而被跟踪的进程则使用了一次PTRACE_TRACEME,gdb也就顺理成章的接管了这个进程。

PTRACE_CONT:继续运行之前停止的子进程。可同时向子进程交付指定的信号。

这个选项呢,其实就相当于gdb中的continue命令。当你使用continue命令之后,一个被gdb停止的进程就能继续执行下去,如果有信号,信号也会被交付给子进程。

除了以上这几个选项,ptrace还有很多其他选项,可以在linux下阅读man手册:man ptrace

需要注意的另一点是,使用gdb调试过多线程/进程的人应该都知道,当子进程遇到一个信号的时候,gdb就会截获这个信号,并将子进程暂停下来。这是为什么呢?

实际上,在使用参数为PTRACE_TRACEME或PTRACE_ATTACH的ptrace系统调用建立调试关系之后,交付给目标程序的任何信号(除SIGKILL之外)都将被gdb先行截获,或在远程调试中被gdbserver截获并通知gdb。gdb因此有机会对信号进行相应处理,并根据信号的属性决定在继续目标程序运行时是否将之前截获的信号实际交付给目标程序。

一、gdb简介

  gdb:GNU debugger 
  UNIX及UNIX-like下一个强大的命令行的调试工具 
  gdb调试的整体架构如下图所示: 
  这里写图片描述 
  可以发现gdb调试不管是本地调试还是远程调试,都是基于ptrace系统调用来实现的   

二、ptrace

  ptrace系统调用的原型:

long ptrace(enum __ptrace_request request, \
    pid_t pid,void *addr,void *data); 
  • 1
  • 2

  ptrace系统调用提供了一种方法,让父进程可以观察和控制其它进程的执行,检查和改变其核心映像及寄存器。主要用来实现断点调试和系统调用跟踪 
  可通过man手册查看具体使用:man ptrace 
   
  request参数的主要选项: 
PTRACE_TRACEME:由子进程调用,表示本进程将被其父进程跟踪,交付给这个进程的所有信号,即使信号是忽略处理的(除SIGKILL之外),都将使其停止,父进程将通过wait()获知这一情况。

PTRACE_ATTACH: attach到一个指定的进程,使其成为当前进程跟踪的子进程,而子进程的行为等同于它进行了一次PTRACE_TRACEME操作。但是,需要注意的是,虽然当前进程成为被跟踪进程的父进程,但是子进程使用getppid()的到的仍将是其原始父进程的pid。 
这下子gdb的attach功能也就明朗了。当你在gdb中使用attach命令来跟踪一个指定进程/线程的时候,gdb就自动成为改进程的父进程,而被跟踪的进程则使用了一次PTRACE_TRACEME,gdb也就顺理成章的接管了这个进程。

PTRACE_CONT:继续运行之前停止的子进程。可同时向子进程交付指定的信号。

三、gdb三种调试方式

1)attach并调试一个已经运行的进程: 
确定需要进行调试的进程id 
运行gdb,输入attch pid,如:gdb 12345。gdb将对指定进行执行如下操作:ptrace(PTRACE_ATTACH,pid,0,0) 
2)运行并调试一个新的进程 
运行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、设置断点:    
  信号是实现断点的基础,当用breakpoint 设置一个断点后,gdb会在=找到该位置对应的具体地址,然后向该地址写入断点指令INT3,即0xCC。 
  目标程序运行到这条指令时,就会触发SIGTRAP信号,gdb会首先捕获到这个信号。然后根据目标程序当前停止的位置在gdb维护的断点链表中查询,若存在,则可判定为命中断点。 
  gdb暂停目标程序运行的方法是想起发送SIGSTOP信号。 
   
  2、next单步调试: 
  next指令可以实现单步调试,即每次只执行一行语句。一行语句可能对应多条及其指令,当执行next指令时,gdb会计算下一条语句对应的第一条指令的地址,然后控制目标程序走到该位置停止。 
  这里写图片描述

参考: 
http://www.spongeliu.com/240.html 

http://www.docin.com/p-18618736.html

1、GDB对于基于GNU系统开发的程序员来说是最基本的东西,必须的。所以这篇学习总结中,不打算包括GDB的一般使用方法。因为这些东西必须是随手拈来的。所以也就不花时间来整理,我只把一些比较高级的应用在这里作一个整理。
 
2、在编译链接程序时需要使用”-ggdb”选项来生成可供GDB调试用的信息,否则GDB将失去作用,因此GDB和GCC联系的非常紧密。并且当-g和-O开关同时打开时,调试和优化可能会产生冲突,经常会发现所见和事实不合的情况,所以要选择性地开启优化开关。
 
3、GDB的一些使用技巧:
 1)设置断点的方法包括:函数,行号,if条件断点express,这些前面都可以跟上文件名。另外还可以设置地址断点:b *0x8048424.
 2)GDB用来分析core文件,启动格式:gdb debugme core.xyz
 3)开启core文件生成的方法是: ulimit -c unlimited
 4)在不同函数的调用栈上切换及查看当前信息:bt/frame XX/up/down/info frame/args/locals
 5)调试一正运行的进程:gdb debugme pid或者gdb debugme + attach pid + detach,类似的应用还有:strace/ltrace/truss
 6)如果某个线程/进程处于死锁状态,还可以通过gcore pid来手动生成core文件来分析当前线程/进程的状态,然后利用GDB来分析, gcore使用方法:gcore pid,注意被调试的进程会临时性停止去生成core文件
 7)查看函数的反汇编指令:disassemble fun_name
 8)汇编指令级别的单步执行:ni/si,显示当前执行的汇编指令: x/i $pc
 9)查看寄存器的内容:info registers/all-registers
 10)查看某地址开始的内容:x/num 0xYYYYYYY 查看从0xYYYYYYY开始的num个单元内容;p 输出数组内容
 11)在函数调试中途强制返回:return  <expression>;
 12)向被调试程序发送指定信号:在任意一点ctrl+C进入gdb调试命令行,然后:signal 1-15
 
4、用GDB来调试多线程程序:
 1)显示当前可调试的所有线程:info threads,GDB按照线程启动顺序重新安排了一个线程ID,这个ID是供GDB使用的
 2)在调试多线程的程序时,默认调试的是主线程,其他线程也同时处于暂停状态,如果想切换调试其他的线程,则只需要:thread id
 3)在对某一线程进行next/step执行的时候,其他线程也同时在执行,如果要限制其他线程执行,则可以使用:set scheduler-locking on
 4)对指定线程或者所有线程执行同样的操作,比如查看调用栈信息:thread apply ID1 ID2/all bt
 5)另外你也可以利用strace -p pid来显示某个线程当前的系统调用情况。或者利用gdb debugme pid来调试某个线程,但注意该方法会暂停整个进程的执行。对于多线程的程序gdb ./debugme相当于默认调试主线程,而gdb ./debugme pid则相当于默认调试pid线程。
 
5、用GDB来调试多进程程序:
 1)当fork子进程后,继续调试父进程或者调试刚产生的子进程:set follow-fork-mode parent/child,注意调试的时候其他的进程仍然在运行。
 2)如果父进程fork了多个子进程,上面的这种方法也只能跟踪调试到第一个子进程,并且不影响其他子进程的运行。
 3)如果想在调试一个进程的时候,其他进程处于暂停状态,则可以利用:set detach-on-fork off来做到
 4)利用attach来调试子进程。因为父进程fork子进程后,子进程会马上得到执行,如果恰好执行过了你要调试的地方,则来不及查询pid并且attach,所以为了支持直接attach调试,一般会在子进程的代码开始处加上一个sleep,以使得你有时间来查询pid,然后attach进入来调试。
   attach pid + stop + break XXX + continue + n + n …+ s + s + ….
 5)利用gdb ./debugme pid都可以用来调试进程和线程,但不同的是GDB控制的范围不一样,前者不影响其他的并行单元(进程),而后则会使真个进程暂停。 
 
6、调试动态链接库函数:
 我们可能要调试动态库的函数,或者通过调试来学习动态库函数的实现。这个时候,则需要GDB包括该动态库的debug版本,否则在GDB下面只会打印:0xXXXXXX: ??
 比如包括:glibc debug version,如下是一些glibc的debug版本的下载地址:
 http://linux.maruhn.com/sec/glibc-debug.html
 
注:GDB的远端调试功能,暂时还没有接触过,现不做学习和总结.
 GDB对于多线程,多进程的调试支持并不强大,但可以利用其他专用调试器,比如TotalView:
 参考地址:http://www.totalviewtech.com/
         http://www.total-view.com.cn/
    
   
7、一些辅助的诊断及调试工具:
 1)strace:跟踪系统调用情况
 2)ltrace:跟踪动态库的调用情况
 3)mtrace,pmalloc:跟踪内存使用情况,需要嵌入代码,打印内存使用记录。
 4)Binuitls:Toolchain的工具,参考我的上一篇总结。
 5)Valgrind:非常好的内存泄露检测工具,限于i386
 6)oprofile, NPTL Trace Tool等
 7)ald:汇编语言调试器
 8)Dude:另一个运行linux上的调试器,未使用ptrace实现
 9)Linice(http://www.linice.com/)是SoftIce在Linux中的模拟软件,用于调试没有源代码的二进制文件的内核级调试器。
 10)其他
关于调试及诊断工具包括许多,估计可以写一系列的文章来说明。

其他参考资料:
0)GDB官方网站:http://www.gnu.org/software/gdb/gdb.html
1)快速参考GDB支持的所有调试命令:《GDB QUICK REFERENCE》
2)GDB的使用手册:《Debugging with gdb–The gnu Source-Level Debugger》
3)《Embedded linux prime》的第13/14/15章可以作为参考。
http://book.opensourceproject.org.cn/embedded/embeddedprime/index.html?page=opensource/0136130550/ch13lev1sec1.html
文章出处:飞诺网(www.firnow.com):http://dev.firnow.com/course/6_system/linux/Linuxjs/20091209/184488.html

 

***********************************************************************************************

1、GDB基本组成:
   GDB由三个部分组成:
 (1)用户接口user interface,除支持传统的CLI接口还支持mi接口(ddd等工具使用)
 (2)符号处理层symbol handling,当gdb ./debugme后GDB会读取文件的符号信息,之后的原代码,变量/函数/类型的显示都由该部分进行(everything you can do without live process)。
 (3)目标系统处理层target system handling。包括执行控制,断点设置,单步执行,堆栈分析等操作都有该部分来进行。
 
2、GDB各部分的实现:
 (1)用户接口层(CLI)的实现很显然要用到readline/history库,而图形界面mi则需要用到:GNU ncurses库。
   参考资料: http://www.gnu.org/software/ncurses/
          http://tiswww.case.edu/php/chet/readline/rltop.html
         
 (2)符号处理层则需要使用到:BFD/Opcodes库,分别用来读取分析ELF/Core文件,反汇编.
  参考资料: http://www.xfocus.net/articles/200109/265.html
     http://sourceware.org/binutils/docs/bfd/index.html#Top
     http://www.linuxselfhelp.com/gnu/bfd/html_chapter/bfd_toc.html
    
 (3)目标系统控制层:用ptrace系统调用来实现对其他进程的执行控制,检查和改变其核心映像以及寄存器等操作。
 
  
3、后端(目标系统控制层)实现:
 (1)内核在执行用户请求的系统调用之前回检查当前进程是否处于被“跟踪”状态,如果是的话内核暂停当前进程并将控制权交给调试进程,使跟踪调试进程可以查看甚至修改被调试进程的内存,寄存器等数据。而ptrace函数的作用就是告诉内核在执行子进程的系统调用之前做的动作。所有的动作都可以通过request进行传入。
 (2)设置断点原理:通过查找输入的断点和具体代码位置对应起来,并在该位置替换为一条断点指令,并且保存以前的指令,到目标程序运行到该断点处时,产生SIGTRAP信号,该信号被GDB捕获,GDB查找断点列表来确定是否命中断点。继续执行的时候则会把保存的指令重新放回并执行。n/s/ni/si/finish/uitil也会自动设置断点。
 (3)内核传递给被调试进程所有的信号,都会先传递给GDB再由gdb采取定义的动作来和被调试进程之间进行相互协调操作。gdb暂停目标程序运行的方法是向其发送SIGSTOP信号,GDB对于随机信号(非GDB产生的)的处理包括,可以通过handle signals命令来预定义
 
4、 ptrace函数简单介绍:long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);其中第一个参数代表告诉给kernel要做的动作。
 PTRACE_ME:设置自己的被跟踪标志,在被调试进程中使用。
 PTRACE_PEEKUSER:可以得到系统调用号及参数信息
 PTRACE_CONT:使被跟踪进程继续执行
 PRACE_GETREGS:一次性得到所有寄存器相关的值,提供输出参数
 PTRACE_POKEDATA:可用来改变子进程中变量的值
 PTRACE_SINGLESTEP:会使内核在子进程的每一条指令执行前先将其阻塞,然后将控制权交给父进程
 PTRACE_ATTACH:向运行着的子进程置上跟踪标志为。
 PTRACE_DETACH:和上面的行为相反。
很多工具strace/ltrace/stuss等工具都用到了ptrace,学习ptrace的最好的资料是这些工具的原代码和kernel相关代码。
 
其他参考资料:
1)Ptrace相关资料: http://linuxgazette.net/issue81/sandeep.html
       http://blog.chinaunix.net/u/19651/showart_362901.html //玩转ptrace系列
       http://blog.chinaunix.net/u/19651/showart_362921.html
      
2)GDB的实现说明: 《gdb Internals》 

今天的文章gdb调试原理分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/27147.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注