-o
参数指定了编译生成的可执行文件名为 gdblianxi,使用参数-g
表示将源代码信息编译到可执行文件中。若是不使用参数-g
,会给后面的GDB调试形成不便。上面的信息表示已经执行完“n = 1;”,并显示下一条要执行的代码为第15行的“n++;”。前端
下面咱们分别在第21行打印处、tempFunction 函数开头各设置一个断点(分别使用命令“b 21”“b tempFunction”):linux
命令 | 解释 | 示例 |
---|---|---|
file <文件名> | 加载被调试的可执行程序文件。 由于通常都在被调试程序所在目录下执行GDB,于是文本名不须要带路径 |
(gdb) file gdblianxi |
r | Run的简写,运行被调试的程序。 若是此前没有下过断点,则执行完整个程序;若是有断点,则程序暂停在第一个可用断点处。 |
(gdb) r |
c | Continue的简写,继续执行被调试程序,直至下一个断点或程序结束。 | (gdb) c |
b <行号> b <函数名称> b * <函数名称> b * <代码地址> d [编号] |
b: Breakpoint的简写,设置断点。两可使用“行号”“函数名称”“执行地址”等方式指定断点位置。 其中在函数名称前面加“*”符号表示将断点设置在“由编译器生成的prolog代码处”。若是不了解汇编,能够不予理会此用法。 d: Delete breakpoint的简写,删除指定编号的某个断点,或删除全部断点。断点编号从1开始递增。 |
(gdb) b 8 (gdb) b main (gdb) b * main (gdb) b * 0x804835c (gdb) d |
s, n | s: 执行一行源程序代码,若是此行代码中有函数调用,则进入该函数; n: 执行一行源程序代码,此行代码中的函数调用也一并执行。 s 至关于其它调试器中的“Step Into (单步跟踪进入)” n 至关于其它调试器中的“Step Over (单步跟踪)” 这两个命令必须在有源代码调试信息的状况下才可使用(GCC编译时使用“-g”参数)。 |
(gdb) s (gdb) n |
si, ni | si命令相似于s命令,ni命令相似于n命令。 所不一样的是,这两个命令(si/ni)所针对的是汇编指令,而s/n针对的是源代码。 |
(gdb) si (gdb) ni |
p <变量名称> | Print的简写,显示指定变量(临时变量或全局变量)的值。 | (gdb) p i (gdb) p nGlobalVar |
display ... undisplay <编号> |
display,设置程序中断后欲显示的数据及其格式。 例如,若是但愿每次程序中断后能够看到即将被执行的下一条汇编指令,可使用命令“display /i $pc” 其中 $pc 表明当前汇编指令,/i 表示以十六进行显示。当须要关心汇编代码时,此命令至关有用。 undispaly,取消先前的display设置,编号从1开始递增。 |
(gdb) display /i $pc (gdb) undisplay 1 |
i | Info的简写,用于显示各种信息,详情请查阅“help i”。 | (gdb) i r |
q | Quit的简写,退出GDB调试环境。 | (gdb) q |
help [命令名称] | GDB帮助命令,提供对GDB名种命令的解释说明。 若是指定了“命令名称”参数,则显示该命令的详细说明;若是没有指定参数,则分类显示全部GDB命令,供用户进一步浏览和查询。 |
(gdb) help display |
(1)相比GDB,增长了语法加亮的代码窗口,显示在GDB窗口的上部,随GDB的调试位置代码同步显示。程序员
(2)断点设置可视化 。正则表达式
(3)在代码窗口中可以使用GDB经常使用命令 。编程
(4)在代码窗口可进行代码查找,支持正则表达式 。小程序
(1)代码窗口windows
调试时同步显示被调试程序源代码,自动标记出程序运行到的位置。当焦点在代码窗口时,能够浏览代码、查找代码以及执行命令 ,操做方式同vi 。经常使用命令以下:数组
i : 焦点切换到GDB窗口 。 o :打开文件选择框,可选择要显示的代码文件 。 空格 :设置/取消断点 。 k:向上移动 j:向下移动 /:查找
(2)状态条窗口数据结构
(3)GDB窗口多线程
CGDB的操做界面,同GDB ,按ESC键则焦点切换到代码窗口 。
启动&退出——启动:cgdb;退出:在代码窗口或GDB窗口,执行quit命令 。
代码实现:
“(gdb)”表示GDB已经启动,等待咱们输入命令。此时程序并未开始运行,输入“run”开始运行程序。这种方式在GDB内部运行程序:
List n,m表示显示n到m行的代码
设置断点,break n,用step单步执行(这里break 21):
汇编级的调试或跟踪,须要用到display命令“display /i $pc”,如上表所示,
“display /i $pc” 其中 $pc 表明当前汇编指令,/i 表示以十六进行显示。当须要关心汇编代码时,此命令至关有用。 undispaly,取消先前的display设置,编号从1开始递增。
而且之后程序每次中断都将显示下一条汇编指定(“si”命令用于执行一条汇编代码——区别于“s”执行一行C代码)
接下来咱们试一下命令“b * <函数名称> ”。 为了更简明,有必要先删除目前全部断点(使用“d”命令——Delete breakpoint)
当被询问是否删除全部断点时,输入“y”并按回车键便可。
下面使用命令“b *main”在 main 函数的 prolog 代码处设置断点(prolog、epilog,分别表示编译器在每一个函数的开头和结尾自行插入的代码):
此时可使用“i r”命令显示寄存器中的当前值———“i r”即“Infomation Register”,
也能够输入“i r 寄存器名”显示任意一个指定的寄存器值:
最后输入命令“q”,退出(Quit)GDB调试环境
打开终端命令行窗口,输入命令vi testddd.c,创建testddd.c文件做为以后调试的文件:
在testddd.c文件中输入一些C语言的程序数据,DDD工具能够调试不少种程序设置基于的代码,本次调试以C语言做为说明对象。
把testddd.c文件编译成能够执行的文件testddd,命令:gcc -g -o testddd testddd.c
,注意必定要带-g参数,不然生成的可执行文件中没有必要的调试信息,最终使用DDD工具不能调试。
运行DDD调试工具,直接输入命令ddd就能够打开DDD工具。
DDD工具打开后以下图所示,上面较大空白部分为代码区,和工具区,分割线下面是调试生成信息区。
点击菜单栏上的“文件”----->“打开程序”,准备打开咱们上面准备的testddd.c文件
在打开程序框中,定位到咱们要调试的程序的目录下,在Files列表下选择咱们要调试 信息,以后点击左下方的打开按钮。
调试程序打开后,在代码区能够看到咱们的代码,右边的一些按钮是咱们调试要用的工具。
在代码区点鼠标右键,会弹出如图所示的菜单:
咱们能够给程序设置断点等,点击工具区里面的Run按钮,能够执行程序,在下面的调试信息区能够看到程序的执行结果。
如上图所示:在鼠标右键点击的地方设置了断点,在下方调试信息生成区显示了程序运行的输入信息。
PS:也能够在Terminal中输入ddd 文件名来直接打开ddd调试该文件的界面:
在怀疑程序哪一个变量为可疑变量时,能够在控制台输入以下命令
或者在主窗口原程序中点击某个变量如sum选中该变量,右击后选择display sum 选项就会看到该变量的值在主窗口的上方。 接着往下单步运行,屡次点击工具栏中的“Step”按钮,观察变量sum的结果。
若是问题出在count上。这时点击命令工具栏上的“Kill”按钮将程序断掉,把初始化sum的那一句改正确。从新运行以后,发现结果正确,调试过程完毕。
run 执行程序 step 单步调试 kill 杀死正在运行的程序 interrupt 退出这次调试回到原始状态
段错误产生的缘由:
(1) 访问不存在的内存地址
(2) 访问系统保护的内存地址
(3) 访问只读的内存地址
(4) 栈溢出
下面以缘由一访问不存在的内存地址为例,进行实践。
从输出中能够看出,程序收到SIGSEGV信号,触发段错误,并提示0x00000000004004e六、调用main报的错,在Derro.c中23行。而且在代码窗口第23行被标记出来。
适用场景
仅当能肯定程序必定会发生段错误的状况下使用。 当程序的源码能够得到的状况下,使用-g参数编译程序。 通常用于测试阶段,生产环境下gdb会有反作用:使程序运行减慢,运行不够稳定,等等。 即便在测试阶段,若是程序过于复杂,gdb也不能处理。
#include <sys/types.h> #include <unistd.h> pid_t fork(void);
进程的终止存在两个可能:(1)父进程先于子进程终止(init进程领养) (2)子进程先于主进程终止。对于后者,系统内核为子进程保留必定的状态信息:进程ID、终止状态、CPU时间等;当父进程调用wait或waitpid函数时,获取这些信息; 当子进程正常或异常终止时,系统内核向其父进程发送SIGCHLD信号;缺省状况下,父进程忽略该信号,或者提供一个该信号发生时即被调用的函数。
#include <stdlib.h> void exit(int status);
#include <sys/types.h> #include <sys/wait.h> pid_t wait(int *stat_loc);
返回:终止子进程的ID-成功;-1-出错;statloc存储子进程的终止状态(一个整数);
若是没有终止的子进程,可是有一个或多个正在执行的子进程,则该函数将堵塞,直到有一个子进程终止或者wait被信号中断时,wait返回。 当调用该系统调用时,若是有一个子进程已经终止,则该系统调用当即返回,并释放子进程全部资源。
pidt waitpid(pidt pid, int *statloc, int options);
-1:要求知道任何一个子进程的返回状态(等待第一个终止的子进程); >0:要求知道进程号为pid的子进程的状态; <-1: wait for any child process whose process group ID is equal to the absolute value of pid.
Options最经常使用的选项是WNOHANG,它通知内核在没有已终止进程时不要堵塞。
调用wait或waitpid函数时,正常状况下,可能会有如下几种状况:
阻塞(若是其全部子进程都还在运行); 得到子进程的终止状态并当即返回(若是一个子进程已终止,正等待父进程存取其终止状态); 出错当即返回(若是它没有任何子进程)
set follow-fork-mode mode
来设置fork跟随模式。parent gdb只跟踪父进程,不跟踪子进程,这是默认的模式。 child gdb在子进程产生之后只跟踪子进程,放弃对父进程的跟踪。
show follow-fork-mode
来查看目前的跟踪模式。能够看到目前使用的模式是parent。
有时,咱们想同时调试父进程和子进程,以上的方法就不能知足了。Linux提供了set detach-on-fork mode
命令来供咱们使用。其使用的mode能够是如下的一种:
on 只调试父进程或子进程的其中一个(根据follow-fork-mode来决定),这是默认的模式。 off 父子进程都在gdb的控制之下,其中一个进程正常调试(根据follow-fork-mode来决定)
show detach-on-fork
显示了目前是的detach-on-fork模式,如图所示。new 当发生exec的时候,若是这个选项是new,则新建一个inferior给执行起来的子进程,而父进程的inferior仍然保留,当前保留的inferior的程序状态是没有执行。 same 当发生exec的时候,若是这个选项是same(默认值),由于父进程已经退出,因此自动在执行exec的inferior上控制子进程。
线程:运行在单一进程上下文中的逻辑流,由内核进行调度,共享同一进程的虚拟地址空间。
简而言之,线程就是把一个进程分为不少片,每一片均可以是一个独立的流程。这已经明显不一样于多进程了,进程是一个拷贝的流程,而线程只是把一条河流截成不少条小溪。它没有拷贝这些额外的开销,可是仅仅是现存的一条河流,就被多线程技术几乎无开销地转成不少条小流程,它的伟大就在于它少之又少的系统开销。
(1)函数pthread_create用来建立一个线程,它的原型为:
extern int pthread_create __P ((pthread_t *__thread, __const pthread_attr_t *__attr,void *(*__start_routine) (void *), void *__arg));
第一个参数为指向线程标识符的指针,第二个参数用来设置线程属性,第三个参数是线程运行函数的起始地址,最后一个参数是运行函数的参数。
(2)函数pthread_join用来等待一个线程的结束。函数原型为:
2extern int pthread_join __P ((pthread_t __th, void **__thread_return));
第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它能够用来存储被等待线程的返回值。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。
(3)一个线程的结束有两种途径,一种是象咱们上面的例子同样,函数结束了,调用它的线程也就结束了;另外一种方式是经过函数pthread_exit来实现。它的函数原型为:
extern void pthread_exit __P ((void *__retval)) __attribute__ ((__noreturn__));
惟一的参数是函数的返回代码,只要pthread_ join中的第二个参数thread_ return不是NULL,这个值将被传递给 thread_return。
最后要说明的是,一个线程不能被多个线程等待,不然第一个接收到信号的线程成功返回,其他调用pthread_join的线程则返回错误代码ESRCH。
Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,须要使用头文件pthread.h,链接时须要使用库libpthread.a。Linux下pthread的实现是经过系统调用clone()来实现的。clone()是Linux所特有的系统调用,它的使用方式相似fork。
下面代码示例:
代码分析:主线程作本身的事情,生成2个子线程,task1为分离,任其自生自灭,而task2仍是继续送外卖,须要等待返回。
编译运行:
这篇GDB的深刻研究是我作的第一个加分项目,到此算是告一段落了。在作以前,一直感受GDB调试是很困难的一件事,可是本身真正去实践才发现它并无我想象中的那么难。此次我完成了GDB代码调试、CGDB代码调试、汇编代码调试、DDD代码调试以及多进程与多线程的学习,中途也遇到过不少问题,可是经过查阅资料,参考以前的学长学姐的经验最终都解决了。不知不觉,写博客已经有一年的时间了,这门课程也快结束了。从一开始的烦躁到后面的适应再到习惯,本身自主学习的能力提高了太多。之后继续加油!