1、C语言-gdb调试,上海*通信有限责任公司Mr Jim()2013-02,培训大纲,一、概述 二、gdb实现原理 三、gdb基本命令 四、gdb高级命令 五、Coredump分析 六、gdb使用技巧 七、常见问题,背景概述 GNU Project Debugger、GNU Debugger GNU开源组织开发出来的程序调试工具 版本(最新版本7.2) 系统(基于UNIX/Linux ) 多语言(C/C+等),GDB概述,原理概述GDB作为父进程启动后,创建被调试程序为子进程,通过ptrace系统调用和一系列的信号交互来观察和控制被调试进程的运行,检查和修改其内存环境。 调试器(GDB)能让你观
2、察另一个程序在执行时的内部活动,或程序出错时发生了什么。GDB主要能为你做四件事(包括为了完成这些事而附加的功能),帮助你找出程序中的错误。 * 运行你的程序,设置所有的能影响程序运行的东西。 * 保证你的程序在指定的条件下停止。 * 当你程序停止时,让你检查发生了什么。 * 改变你的程序。那样你可以试着修正某个bug引起的问题,然后继续查找另一个bug.,GDB概述,功能概述编译调试程序$gcc g hello.c o helloGDB启动gdbgdb program_namegdb pidgdb -c core_file_name,GDB概述,功能概述停住调试程序Breakpoint :
3、代码段某个地址WatchPoint: 数据段的变量Catchpoint: 某个事件Signal : 信号Break thread :单个线程,GDB概述,功能概述检查调试程序环境查看源代码信息查看内存信息查看环境变量,GDB概述,功能概述动态控制调试程序的执行修改变量值跳转执行触发信号强制函数调用强制函数返回,GDB概述,功能概述分析CoreCore文件内容Core文件的产生产生Core文件的相关设置,GDB概述,GDB简介,gdb - GNU debugger。gdb的主要功能 救死扶伤。gdb的主要用途 修复bug;分析程序结构。gdb官方网址 - http:/www.gnu.org/so
4、ftware/gdb/gdb.htmlgdb下载地址 - http:/ftp.gnu.org/gnu/gdb/,培训大纲,一、概述 二、gdb实现原理 三、gdb基本命令 四、gdb高级命令 五、Coredump分析 六、gdb使用技巧 七、常见问题,gdb调试的工具 ptrace系统调用,ptrace系统调用的原型long ptrace(enum _ptrace_request request, pid_t pid, void *addr, void *data);ptrace系统调用的简要说明ptrace系统调用提供了一种方法来让父进程可以观察和控制其它进程的执行,检查和改变其核心映像以及
5、寄存器。,gdb调试的工具 ptrace系统调用,ptrace系统调用的主要选项PTRACE_TRACEME表示本进程将被其父进程跟踪,交付给这个进程的所有信号(除SIGKILL之外),都将使其停止,父进程将通过wait()获知这一情况。 PTRACE_ATTACHattach到一个指定的进程,使其成为当前进程跟踪的子进程,子进程的行为等同于它进行了一次PTRACE_TRACEME操作。 PTRACE_CONT继续运行之前停止的子进程。可同时向子进程交付指定的信号。,gdb的二种调试方式 (1),attach并调试一个已经运行的进程调试关系的建立过程: 用户确定需要进行调试的进程id; 运行g
6、db,输入attach pid,gdb对指定进程执行下述操作:ptrace(PTRACE_ATTACH, pid, 0, 0);,gdb的二种调试方式 (2),attach并调试一个已经运行的进程,gdb的二种调试方式 (3),运行并调试一个新的进程调试关系的建立过程: 运行gdb,通过命令行参数或file命令指定目标程序。 输入run命令,gdb执行下述操作: 通过fork()系统调用创建一个新进程; 在新创建的子进程中执行下述操作:ptrace(PTRACE_TRACEME, 0, 0, 0); 在子进程中通过execv()系统调用加载用户指定的可执行文件。,gdb的二种调试方式 (4),
7、运行并调试一个新的进程,gdb调试的基础 信号 (1),在使用参数为PTRACE_TRACEME或PTRACE_ATTACH的ptrace系统调用建立调试关系之后,交付给目标程序的任何信号(除SIGKILL之外)都将被gdb先行截获。gdb因此有机会对信号进行相应处理,并根据信号的属性决定在继续目标程序运行时是否将之前截获的信号实际交付给目标程序。,gdb调试的基础 信号 (2),信号是实现断点功能的基础。以x86为例,向某个地址打入断点,实际上就是往该地址写入断点指令INT 3,即0xCC。目标程序运行到这条指令之后就会触发SIGTRAP信号,gdb捕获到这个信号,根据目标程序当前停止位置查
8、询gdb维护的断点链表,若发现在该地址确实存在断点,则可判定为断点命中。gdb暂停目标程序运行的方法是向其发送SIGSTOP信号。kill_lwp(process-head.id, SIGSTOP);,gdb指令级单步的实现 (1),所谓指令级单步就是指gdb控制目标程序只运行一条指令之后即停止。指令级单步是next、step、nexti、stepi等运行类调试命令的基础。指令级单步有硬件单步和软件单步之分。所谓硬件单步是指cpu架构本身就支持指令级单步,目标程序可以在运行一条指令之后自动停止。所谓软件单步是指cpu架构不支持指令级单步,需要gdb用软件方法来实现指令级单步。支持硬件单步的架构
9、如x86和ppc。对于x86,可通过设置EFLAGS寄存器中的TF标志来将cpu置于单步模式。对于ppc,则可通过设置MSR寄存器中的SE标志来将cpu置于单步模式。在单步模式中,cpu每执行一条指令,就会产生一个单步异常,通知gdb进行处理。,gdb指令级单步的实现 (2),不支持硬件单步的架构如arm和mips。对于此类架构,gdb采用的是用临时的软件断点来模拟单步的方法。即在需执行指令的下一条指令处临时插入一个断点,然后让目标程序继续运行,它会在执行完当前指令之后遇到下一条指令处的临时断点,于是目标程序停止,通知gdb命中断点,gdb再将此断点删除,由此来完成指令级单步的过程。(插入临时
10、断点需要gdb实现代码分支预测的功能),gdb next命令的实现 (1),next命令实现c代码级的单步。分析其实现机制首先需要理解step range以及step_range_start和step_range_end的概念。 执行next命令时,gdb会计算出当前停止位置的c语句的第一条指令的地址作为step_range_start,然后计算出当前停止位置下一行的c语句的第一条指令的地址作为step_range_end,随后控制目标程序从当前停止位置开始走指令级单步,直至pc超出step range为止。,gdb next命令的实现 (2),next命令的结束条件:pc = step_ra
11、nge_end。之所以不能简单地判断pc是否到达step_range_end,是因为step_range_end仅仅是c源代码意义上的下一行的第一条指令的地址,目标程序实际运行时未必就会到达那里。因此,next命令的结束条件可以理解为只要pc离开当前源代码行即可。next过程中遇到函数调用怎么办?我们知道,next命令是会跨过函数调用的,这个过程是如何实现的呢?,gdb next命令的实现 (3),next命令跨越函数调用的过程:1、从当前停止位置开始走指令级单步;2、走到子函数第一条指令时发现是函数调用,就在函数返回地址插入一个临时断点;3、让目标程序继续运行,通过子函数体,直至遇到之前插入
12、的临时断点;4、继续走指令级单步,直至满足next命令的结束条件为止。,gdb step、nexti、stepi命令的实现,step命令和next命令一样,也是实现c源代码级的单步,对于简单语句,step完全等同于next。唯一不同的是,若单步过程中遇到函数调用,step命令将停止在子函数的起始处,而不是将其跨越(无调试信息的子函数除外)。nexti命令实现指令级单步,和next命令类似,nexti命令单步过程中不会进入子函数调用。stepi命令实现指令级单步,而且是严格的指令级单步,每次直接走一条指令后即停止,不再区分是否存在函数调用。,gdb finish命令的实现,finish命令会让目
13、标程序继续执行完当前函数的剩余语句,并停止在返回到上一级函数之后的第一条指令处(也就是调用当前函数时的返回地址)。因此,实现finish命令时,只需找到当前函数的返回地址,并在该处插入一个临时断点,然后让目标程序继续运行,直至遇到该断点而停止。,gdb until命令的实现,不带参数的until命令让目标程序运行至当前函数中当前行后的任意一行。和next命令类似,这种until命令也是用指令级单步来实现的,但不同的是它的step_range_start设定为当前函数的起始位置,也就是说,若指令级单步过程中pc向函数前部移动,程序是不会停止的,仅当程序单步至当前行后的某一行时程序才会停止,这就提
14、供了一种跳出循环体的快捷方式。 带参数的until命令让目标程序继续运行,直至达到指定位置为止。因为只要在当前函数体内,until命令的目的地址可以任意指定,因此不能再用指令级单步来实现它,而是采用在指定地址插入临时断点,然后让目标程序继续运行直至遇到断点停止的方法。 关于until命令需要注意的是,不管带参数还是不带参数,until都是针对当前函数内部而言的,也就是说,只要pc离开当前函数体程序就会停止。,gdb对断点的处理 (1),断点功能的实现就是在指定位置插入断点指令,使目标程序运行至该处时产生SIGTRAP信号,该信号被gdb捕获,通过断点地址的匹配确定是否命中断点。断点的属性: 是
15、否有条件(由condition命令修改); 是否有忽略次数 (由ignore命令修改); 是否只针对某个线程有效(由break命令的thread参数指定); 是否是临时断点(由tbreak命令插入)。,gdb对断点的处理 (2),断点命中的判定:目标程序遇到断点,并不一定就需要停下来,该停就停,不该停的还是要继续跑。只有真正需要停止运行的情况才认为是断点命中。是否命中断点的判定因素主要有以下这些: 导致目标程序本次停止运行的信号是不是SIGTRAP; 在gdb维护的断点链表中是否存在一个断点的地址与目标程序本次停止位置匹配; 若断点存在条件,此时条件是否满足; 断点的忽略次数此时是否为0; 若
16、断点只针对某个线程有效,那么遇到该断点的线程是否就是断点所设定的线程; 若前两个条件之一不满足,则认为目标程序本次是因随机信号而停止。若后三个条件之一不满足,则认为目标程序本次没有命中断点,gdb会让其继续运行。,gdb对断点的处理 (3),临时断点 断点命中之后的处理。当判定为断点命中之后,若该断点为临时断点,gdb就会将这个断点删除。也就是说,临时断点只命中一次。可能用到临时断点的场合: 用户通过tbreak命令显式插入; next、nexti、step命令需要跨越函数调用的时候,由gdb自动在函数返回地址处插入临时断点; finish命令需要在当前函数返回地址处插入临时断点; 带参数的u
17、ntil命令需要在当前函数返回地址以及参数指定地址插入临时断点; 在不支持硬件单步的架构上,gdb需要逐指令插入临时断点来实现软件单步;,gdb对断点的处理 (4),gdb将断点实际插入目标程序的时机:当用户通过break命令设置一个断点时,这个断点并不会立即生效,因为gdb此时只是在内部的断点链表中为这个断点新创建了一个节点而已。gdb会在用户下次发出继续目标程序运行的命令时,将所有断点插入目标程序,新设置的断点到这个时候才会实际存在于目标程序中。与此相呼应,当目标程序停止时,gdb会将所有断点暂时从目标程序中清除。 断点命中失败的情况下,跨越断点继续运行的过程: 清除断点 单步到断点的下一
18、条指令 恢复断点 继续目标程序运行,gdb对随机信号的处理,对于gdb而言,导致目标程序本次停止的信号有随机和非随机之分。非随机信号是指gdb已经预知其会发生或者本身就是gdb导致的信号,也就是说,这些信号是具有明确的调试含义的,比如遇到断点指令时的SIGTRAP。而随机信号则是gdb没有预知的、不了解其实际含义的信号,比如因程序异常而导致的SIGSEGV,因定时机制而产生的SIGALRM,或者是用户程序自己内部使用的信号。 对于随机信号,gdb提供了两个属性来决定对它的处理方式。一个是当此信号发生时是否停止目标程序的运行,一个是在目标程序因此信号而停止之后,用户发出继续目标程序运行的命令时,
19、是否将此信号交付给目标程序。 可通过info signals命令查看信号的配置属性,并通过handle signal命令来修改信号的属性。,培训大纲,一、概述 二、gdb实现原理 三、gdb基本命令 四、gdb高级命令 五、Coredump分析 六、gdb使用技巧 七、常见问题,GDB基本命令,帮助命令 help(gdb) help List of classes of commands:aliases - Aliases of other commands breakpoints - Making program stop at certain points data - Examining
20、 data files - Specifying and examining files internals - Maintenance commands obscure - Obscure features running - Running the program stack - Examining the stack status - Status inquiries support - Support facilities tracepoints - Tracing of program execution without stopping the program user-defin
21、ed - User-defined commandsType “help“ followed by a class name for a list of commands in that class. Type “help“ followed by command name for full documentation. Command name abbreviations are allowed if unambiguous.,GDB基本命令,基本命令,GDB基本命令,基本命令,GDB基本命令,查看命令 源代码,GDB基本命令,查看命令 设置,符号,GDB基本命令,调试命令 查看,GDB基本
22、命令,调试命令 查看,GDB基本命令,调试命令 设置,GDB基本命令,调试命令 设置,GDB基本命令,调试命令 执行,GDB基本命令,调试命令 执行,GDB基本命令,调试命令 break,培训大纲,一、概述 二、gdb实现原理 三、gdb基本命令 四、gdb高级命令 五、Coredump分析 六、gdb使用技巧 七、常见问题,GDB高级命令,捕获事件 ,GDB高级命令,捕获信号 设置,GDB高级命令,强制执行 执行,GDB高级命令,单线程停止 设置,GDB高级命令,自定义命令 设置,GDB高级命令,跟踪点 设置,GDB高级命令,源文件搜索 search,培训大纲,一、概述 二、gdb实现原理
23、三、gdb基本命令 四、gdb高级命令 五、Coredump分析 六、gdb使用技巧 七、常见问题,Coredump分析,认识Core文件Core文件是程序异常时系统将该程序最后的内存栈信息的一个镜像。通过gdb命令可以查询每块内存的内容以定位程序异常的原因。core文件为程序运行时的线程生产栈信息,每个栈根据函数调用关系分为从低向上的分层帧,每个帧反映一个函数调用。,Coredump分析,分析Core文件info threads #显示所有的线程 thread 1 #切换到1号线程 Backtrace/bt n #打印栈顶n层栈 Backtrace/bt n #打印栈底n层栈,Coredum
24、p分析,分析Core文件 up #向栈上面移动n层,上移栈桢,使另一个函数成为当前函数。 down #向栈下面移动n层,下移栈桢,使得另一个函数成为当前函数。 where #显示当前线程所有栈信息 Frame/f 4 #显示当前栈第4帧的信息 Frame #显示下一条continue命令的桢 info frame 4 #显示更为详细的栈信息,显示当前栈的第4层的内存信息,培训大纲,一、概述 二、gdb实现原理 三、gdb基本命令 四、gdb高级命令 五、Coredump分析 六、gdb使用技巧 七、常见问题,命令的简化、别名和自动补齐tab键的使用、重复历史命令Shell命令的使用.gdbin
25、it,GDB使用技巧,原理:利用LINUX提供的CORE DUMP机制,当程序中出现内存错误时,会发生崩溃并产生核心文件(core文件)。使用gdb可以对产生的核心文件进行分析,找出程序是在什么时候崩溃的和在崩溃之前程序都做了什么。 操作步骤: 1)编译程序的时候带上-g调试选项,去出-O优化选项 2)生成core文件。一般来说,在默认情况下,在程序崩溃时,coer文件是不生成的(很多Linux发行版在默认时禁止生成核心文件)。所以,你必须修改这个默认选项,在命令行执行:ulimit c unlimited 表示不限制生成core文件的大小。,调试SEGMENTATION FAULT,3)运行
26、你的程序,不管用什么方法,使之重现Segmentation Fault错误。 4)这时,你会发现在你程序同一目录下,生成了一个文件名为core.*的文件,即核心文件。例如,”core.98345”这样的文件。 5)用gdb调试它。假设你的可执行程序名为hello,则在命令行执行:gdb hello core.98345 然后可能会出现一队信息: oracleyf1 mygrep0620$ gdb mygrep_server core.14040 GNU gdb (GDB) Red Hat Enterprise Linux (7.0.1-42.el5) *省略代码行* Reading symbo
27、ls from /lib64/ld-linux-x86-64.so.2.(no debugging symbols found).done. Loaded symbols for /lib64/ld-linux-x86-64.so.2warning: no loadable sections found in added symbol-file system-supplied DSO at 0x7fff457f7000 Core was generated by ./mygrep_server. Program terminated with signal 11, Segmentation f
28、ault. #0 0x0000003a56660d87 in ? () (gdb),调试SEGMENTATION FAULT,4)然后我输入命令bt并执行(gdb)bt 就会得到类似下面的信息: #0 0x0804c760 in thread _handler () at hello.c:707 #1 0x006b149b in start_thread () from /lib/libpthread.so.0 #2 0x0060842e in clone () from /lib/libc.so.6于是,我们就能够一眼可以看出来在hello.c源代码文件的707行存在错误,一般都跟指针有关。
29、,调试SEGMENTATION FAULT,子进程调试,Proc2 是 Proc1 的子进程,Proc3 又是 Proc2 的子进程。如何使用 GDB 调试 proc2 或者 proc3 呢?实际上,GDB 没有对多进程程序调试提供直接支持。例如,使用GDB调试某个进程,如果该进程fork了子进程,GDB会继续调试该进程,子进程会不受干扰地运行下去。如果你事先在子进程代码里设定了断点,子进程会收到SIGTRAP信号并终止。那么该如何调试子进程呢?其实我们可以利用GDB的特点或者其他一些辅助手段来达到目的。此外,GDB 也在较新内核上加入一些多进程调试支持。,子进程调试(FOLLOW-FORK-
30、MODE),follow-fork-mode 在2.5.60版Linux内核及以后,GDB对使用fork/vfork创建子进程的程序提供了follow-fork-mode选项来支持多进程调试。follow-fork-mode的用法为:set follow-fork-mode parent|child parent: fork之后继续调试父进程,子进程不受影响。 child: fork之后调试子进程,父进程不受影响。 因此如果需要调试子进程,在启动gdb后:,子进程调试(FOLLOW-FORK-MODE),(gdb) set follow-fork-mode child 并在子进程代码设置断点。
31、 此外还有detach-on-fork参数,指示GDB在fork之后是否断开(detach)某个进程的调试,或者都交由GDB控制:set detach-on-fork on|off on: 断开调试follow-fork-mode指定的进程。 off: gdb将控制父进程和子进程。follow-fork-mode指定的进程将被调试,另一个进程置于暂停(suspended)状态。 注意,最好使用GDB 6.6或以上版本,如果你使用的是GDB6.4,就只有follow-fork-mode模式。follow-fork-mode/detach-on-fork的使用还是比较简单的,但由于其系统内核/gd
32、b版本限制,我们只能在符合要求的系统上才能使用。而且,由于follow-fork-mode的调试必然是从父进程开始的,对于fork多次,以至于出现孙进程或曾孙进程的系统,例如上图3进程系统,调试起来并不方便。,子进程调试(ATTACH),众所周知,GDB有附着(attach)到正在运行的进程的功能,即attach 命令。因此我们可以利用该命令attach到子进程然后进行调试。例如我们要调试某个进程RIM_Oracle_Agent.9i,首先得到该进程的pidroottivf09 tianq# ps -ef|grep RIM_Oracle_Agent.9i nobody 6722 6721 0
33、05:57 ? 00:00:00 RIM_Oracle_Agent.9i root 7541 27816 0 06:10 pts/3 00:00:00 grep -i rim_oracle_agent.9i 通过pstree可以看到,这是一个三进程系统,oserv是RIM_Oracle_prog的父进程,RIM_Oracle_prog又是RIM_Oracle_Agent.9i的父进程。roottivf09 root# pstree -H 6722,子进程调试(ATTACH),(gdb)attach 8003 现在就可以调试了。一个新的问题是,子进程一直在运行,attach上去后都不知道运行到哪
34、里了。有没有办法解决呢?一个办法是,在要调试的子进程初始代码中,比如main函数开始处,加入一段特殊代码,使子进程在某个条件成立时便循环睡眠等待,attach到进程后在该代码段后设上断点,再把成立的条件取消,使代码可以继续执行下去。至于这段代码所采用的条件,看你的偏好了。比如我们可以检查一个指定的环境变量的值,或者检查一个特定的文件存不存在。,多线程调试,基本命令: info threads 显示当前可调试的所有线程,每个线程会有一个GDB为其分配的ID,后面操作线程的时候会用到这个ID。 前面有*的是当前调试的线程。 thread ID 切换当前调试的线程为指定ID的线程。 break th
35、read_test.c:123 thread all 在所有线程中相应的行上设置断点thread apply ID1 ID2 command 让一个或者多个线程执行GDB命令command。 thread apply all command 让所有被调试线程执行GDB命令command。 set scheduler-locking off|on|step 估计是实际使用过多线程调试的人都可以发现,在使用step或者continue命令调试当前被调试线程的时候,其他线程也是同时执行的,怎么只让被调试程序执行呢?通过这个命令就可以实现这个需求。off 不锁定任何线程,也就是所有线程都执行,这是默认
36、值。 on 只有当前被调试程序会执行。 step 在单步的时候,除了next过一个函数的情况(熟悉情况的人可能知道,这其实是一个设置断点然后continue的行为)以外,只有当前线程会执行。,多线程调试,gdb对于多线程程序的调试有如下的支持: 线程产生通知:在产生新的线程时, gdb会给出提示信息 (gdb) r Starting program: /root/thread New Thread 1073951360 (LWP 12900) New Thread 1082342592 (LWP 12907)-以下三个为新产生的线程 New Thread 1090731072 (LWP 129
37、08) New Thread 1099119552 (LWP 12909)查看线程:使用info threads可以查看运行的线程。 (gdb) info threads4 Thread 1099119552 (LWP 12940) 0xffffe002 in ? ()3 Thread 1090731072 (LWP 12939) 0xffffe002 in ? ()2 Thread 1082342592 (LWP 12938) 0xffffe002 in ? () * 1 Thread 1073951360 (LWP 12931) main (argc=1, argv=0xbfffda04)
38、 at thread.c:21 (gdb),多线程调试,注意,行首的蓝色文字为gdb分配的线程号,对线程进行切换时,使用该该号码,而不是上文标出的绿色数字。另外,行首的红色星号标识了当前活动的线程 切换线程:使用 thread THREADNUMBER 进行切换,THREADNUMBER 为上文提到的线程号。下例显示将活动线程从 1 切换至 4。 (gdb) info threads4 Thread 1099119552 (LWP 12940) 0xffffe002 in ? ()3 Thread 1090731072 (LWP 12939) 0xffffe002 in ? ()2 Threa
39、d 1082342592 (LWP 12938) 0xffffe002 in ? () * 1 Thread 1073951360 (LWP 12931) main (argc=1, argv=0xbfffda04) at thread.c:21 (gdb) thread 4 Switching to thread 4 (Thread 1099119552 (LWP 12940)#0 0xffffe002 in ? () (gdb) info threads * 4 Thread 1099119552 (LWP 12940) 0xffffe002 in ? ()3 Thread 1090731
40、072 (LWP 12939) 0xffffe002 in ? ()2 Thread 1082342592 (LWP 12938) 0xffffe002 in ? ()1 Thread 1073951360 (LWP 12931) main (argc=1, argv=0xbfffda04) at thread.c:21 (gdb)后面就是直接在你的线程函数里面设置断点,然后continue到那个断点,一般情况下多线程的时候,由于是同时运行的,最好设置 set scheduler-locking on 这样的话,只调试当前线程,培训大纲,一、概述 二、gdb实现原理 三、gdb基本命令 四、gdb高级命令 五、Coredump分析 六、gdb使用技巧 七、常见问题,FQA找不到core文件 默认是程序运行的当前路径,如果程序中有chdir之类的操作改变当前路径导致core文件产生在改变后的当前路径下。可通过命令设置core文件的路径 ulimit c 为0栈信息都是问号no debugging symbols found 编译参数带 -g 去 -O栈信息与源代码对不上 产生core文件的程序的版本和源代码版本不匹配no stack 一般发生在程序退出时,释放资源时,某些线程还没完全退出;,常见问题,Thanks,