gdb的简介和功能:
gdb是GNU开发的一个在Unix,Linux上使用的C/C++和汇编语言程序的调试工具,它主要帮助用户在调试程序时完成以下工作:
- 启动程序,按照用户要求影响程序的运行
- 设置断点,在指定位置停止
- 当程序停止,检查它出现什么问题
- 动态改变程序的执行环境,可以先纠正一个错误,然后再纠正其他错误
使用gdb
为了发挥gdb的功能,需要在编译源程序时加上-g选项,这样才能够在目标代码加入调试信息。
启动GDB的方法有以下几种:
1、gdb <program>
program也就是你的执行文件,一般在当前目录下。
2、gdb <program> core
用gdb同时调试一个运行程序和core文件,core是程序非法执行后core dump后产生的文件。
3、gdb <program> <PID>
如果你的程序是一个服务程序,那么你可以指定这个服务程序运行时的进程ID。gdb会自动attach上去,并调试他。program应该在PATH环境变量中搜索得到。
//比如编译一个test.c
gcc -o test test.c -g
对于第二种,如下图:
比如代码如下:
在介绍如何调试多线程,多进程程序之前,我们先了解一些常用gdb命令
l :(字母l)从第一行开始列出源码
break n :在第n行处设置断点
break func:在函数func()的入口处设置断点
info break: 查看断点信息
r:运行程序
n:单步执行
c:继续运行
p 变量 :打印变量的值
bt:查看函数堆栈
finish:退出函数
shell 命令行:执行shell命令行
set args 参数:指定运行时的参数
show args:查看设置好的参数
show paths:查看程序运行路径;
set environment varname [=value] 设置环境变量。如:set env USER=hchen;
show environment [varname] 查看环境变量;clear 行号n:清除第n行的断点
delete 断点号n:删除第n个断点
disable 断点号n:暂停第n个断点
enable 断点号n:开启第n个断点
step:单步调试如果有函数调用,则进入函数;与命令n不同,n是不进入调用的函数的list :简记为 l ,其作用就是列出程序的源代码,默认每次显示10行。
list 行号:将显示当前文件以“行号”为中心的前后10行代码,如:list 12
list 函数名:将显示“函数名”所在函数的源代码,如:list main
list :不带参数,将接着上一次 list 命令的,输出下边的内容。run:简记为 r ,其作用是运行程序,当遇到断点后,程序会在断点处停止运行,等待用户输入下一步命令。
回车:重复上一条命令。
set args:设置运行程序时的命令行参数,如:set args 33 55
show args:显示命令行参数
continue:简讯为 c ,其作用是继续运行被断点中断的程序。
break:为程序设置断点。
break 行号:在当前文件的“行号”处设置断点,如:break 33
break 函数名:在用户定义的函数“函数名”处设置断点,如:break cb_button
info breakpoints:显示当前程序的断点设置情况print 表达式:简记为 p ,其中“表达式”可以是任何当前正在被测试程序的有效表达式,比如当前正在调试C语言的程序,那么“表达式”可以是任何C语言的有效表达式,包括数字,变量甚至是函数调用。
print a:将显示整数 a 的值
print name:将显示字符串 name 的值
print gdb_test(a):将以变量 a 作为参数调用 gdb_test() 函数
bt:显示当前程序的函数调用堆栈。
display 表达式:在单步运行时将非常有用,使用display命令设置一个表达式后,它将在每次单步进行指令后,紧接着输出被设置的表达式及值。如: display a
watch 表达式:设置一个监视点,一旦被监视的“表达式”的值改变,gdb将强行终止正在被调试的程序。如: watch a
kill:将强行终止当前正在调试的程序
help 命令:help 命令将显示“命令”的常用帮助信息
call 函数(参数):调用“函数”,并传递“参数”,如:call gdb_test(55)
layout:用于分割窗口,可以一边查看代码,一边测试:
quit:简记为 q ,退出gdb
我们通过调试多线程多进程程序来使用这些命令:
一些命令的学习:GDB中应该知道的几个调试方法
多进程调试
默认设置下,在调试多进程程序时GDB只会调试主进程。但是GDB(>V7.0)支持多进程的分别以及同时调试,换句话说,GDB可以同时调试多个程序。
只需要设置follow-fork-mode和detach-on-fork即可。
follow-fork-mode
选项 | 含义 |
---|---|
child | 只跟踪子进程 |
parent | 只跟踪父进程(默认设置) |
deatch-on-fork
选项 | 含义 |
---|---|
on | 只调试父进程或子进程的其中一个(根据follow-fork-mode来决定)(默认设置) |
off | 父子进程都在gdb的控制之下,其中一个进程正常调试(根据follow-fork-mode决定),另一个进程会被设置为暂停状态。 |
在Linux下,我们可以查看或者修改这两者,命令如下:
show follow-fork-mode
show detach-on-fork
set follow-fork-mode [parent|child]
set detach-on-fork [on|off]
下面我们以一个多进程的小程序来学习如何使用gdb调试。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
void father_process();
void child_process();
int main()
{
pid_t pid = fork();
if (pid < 0)
{
fprintf(stderr, "fork failure\n");
exit(-1);
}
else if (pid > 0)
{
father_process();
}
else
{
child_process();
}
return 0;
}
void father_process()
{
pid_t pid = getpid();
printf("father pid = %d\n", pid);
printf("hello world\n");
}
void child_process()
{
pid_t pid = getpid();
printf("child pid = %d\n", pid);
}
我们使用show命令查看默认的调试设置,与我们上面所说的一致。默认只调试父进程以及只调试单进程,我们可以通过上面的set命令修改。
我们设置调试子进程,且父进程暂停。下面开始调试:
1. 设置断点并查看
2. 运行程序,查询正在调试的进程
显示GDB调试的所有inferior,GDB会为他们分配ID。其中带有*的进程是正在调试的inferior。( GDB将每一个被调试程序的执行状态记录在一个名为inferior的结构中。一般情况下一个inferior对应一个进程,每个不同的inferior有不同的地址空间。inferior有时候会在进程没有启动的时候就存在。)
由于我之前设置设置调试子进程,且父进程暂停,且将断点打各自打在父子进程运行的函数中,因此当子进程一运行就会进入child_ process,执行该函数。如下图,屏幕打印:
child pid = 5294
3.切换调试的进程
使用 inferior <infer number>
4.其他
上面都是必须掌握的调试多进程的指令,必须要熟悉,下面还有一些指令最好也能学习以下:
1. add-inferior [-copies n] [-exec executable]
添加新的调试进程,可以用file executable来分配给inferior可执行文件。增加n个inferior并执行程序为executable。如果不指定n只增加一个inferior。如果不指定executable,则执行程序留空,增加后可使用file命令重新指定执行程序。这时候创建的inferior其关联的进程并没启动。
2. remove-inferiors infno
删除一个infno号的inferior。如果inferior正在运行,则不能删除,所以删除前需要先kill或者detach这个inferior。
3. clone-inferior [-copies n] [infno]
复制n个编号是infno的inferior。如果不指定n的话,就只复制一个inferior。如果不指定infno,则就复制正在调试的inferior。
4. detach inferior
detach掉编号是infno的inferior。注意这个inferior还存在,可以再次用run命令执行它。
5. kill inferior infno
kill掉infno号inferior。注意这个inferior仍然存在,可以再次用run等命令执行它。
多线程调试
在多线程编程时,当我们需要调试时,有时需要控制某些线程停在断点,有些线程继续执行。有时需要控制线程的运行顺序。有时需要中断某个线程,切换到其他线程。这些都可以通过gdb实现。
GDB默认支持调试多线程,跟踪主线程,子线程block在create thread。
多线程调试最常用可能就是下面几个命令:
info thread 查看当前进程的线程。
thread < ID > 切换调试的线程为指定ID的线程。
break file.c:100 thread all 在file.c文件第100行处为所有经过这里的线程设置断点。
set scheduler-locking off|on|step
在使用step或者continue命令调试当前被调试线程的时候,其他线程也是同时执行的,怎么只让被调试程序执行呢?通过这个命令就可以实现这个需求。
选项 | 含义 |
---|---|
off | 不锁定任何线程,也就是所有线程都执行,这是默认值。 |
on | 只有当前被调试程序会执行。 |
step | 在单步的时候,除了next过一个函数的情况(熟悉情况的人可能知道,这其实是一个设置断点然后continue的行为)以外,只有当前线程会执行。 |
我们以一个简单的多线程程序来学习gdb调试。
/*************************************************************************
> File Name: thread.c
> Author: xuyang
> Mail: xy913741894@gmail.com
> Created Time: 2017-06-15 17:32:16
************************************************************************/
#include<stdio.h>
#include<pthread.h>
void* thread1(void* arg)
{
printf("thread1, tid is %lu\n", pthread_self());
return NULL;
}
void* thread2(void* arg)
{
printf("thread2, tid is %lu\n", pthread_self());
return NULL;
}
int main()
{
pthread_t tid1, tid2;
pthread_create(&tid1, NULL, thread1, NULL);
pthread_create(&tid2, NULL, thread2, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
return 0;
}
打断点,然后运行到断点处。由于还没有创建新线程,用info threads查看此时只有一个主线程。