通俗易懂说GDB调试(三)总结
- 1. 启动调试
-
- 1.1 调试未运行的程序
- 1.2 调试已运行程序
- 1.3 调试core文件: gdb a.out xxx.core或者 gdb /sbin/xxd abc.core
-
- 1.3.1 core文件的限制
- 1.3.2 core 路径 core dump 限制
- 1.3.3 core 文件产生不了
- 1.3.4 设置core文件的名称和文件路径
- 2. 断点设置-一些高级命令
- 3. 变量查看
-
- 3.1 打印变量的16进制,2进制
- 3.2 gdb查看设备大小端
- 4. 源码查看
- 5. gdb多进程,多线程调试
-
- 5.1. gdb多进程
-
- 5.1.1 set follow-fork-mode [parent|child] //多进程中调试主进程还是子进程
- 5.1.2. set detach-on-fork [on|off]
- 5.1.3. 查看系统默认的follow-fork-mode 和 detach-on-fork:
- 5.2 gdb多线程调试
- 参考
通俗易懂说GDB调试(一)
https://blog.csdn.net/lqy971966/article/details/88963635
通俗易懂说GDB调试(二)
https://blog.csdn.net/lqy971966/article/details/102812016
通俗易懂说GDB调试(三)总结
https://blog.csdn.net/lqy971966/article/details/103118094
1. 启动调试
启动调试有:两种类型的GDB启动调试方式,分别是调试未运行的程序和已经运行的程序。
-
哪类程序可被调试
对于C程序来说,需要在编译时加上-g参数,保留调试信息,否则不能使用GDB进行调试。
-
如何判断一个文件是否带有调试信息呢?
但如果不是自己编译的程序,并不知道是否带有-g参数,如何判断一个文件是否带有调试信息呢?
$ gdb helloworld
Reading symbols from helloWorld…(no debugging symbols found)…done.
如果没有调试信息,会提示no debugging symbols found。
1.1 调试未运行的程序
程序还未启动时,可有多种方式启动调试。
-
调试启动无参程序 : gdb a.out
例如:
$ gdb helloWorld
(gdb) run
输入run命令,即可运行程序
$ gdb hello
(gdb)run abc
1.2 调试已运行程序
调试已运行程序 : gdb attach processId
-
首先使用ps命令找到进程id:
$ ps -ef | grep 进程名 //查看进程id
4.1 attach方式
-
gdb attach id
4.2 直接调试相关id进程
gdb hello
不行就 gdb -p xxxd
1.3 调试core文件: gdb a.out xxx.core或者 gdb /sbin/xxd abc.core
1.3.1 core文件的限制
当程序core dump时,可能会产生core文件,它能够很大程序帮助我们定位问题。但前提是系统没有限制core文件的产生。可以使用命令limit -c查看:
$ ulimit -c(用-a 更加全面)
如果结果是0,那么恭喜你,即便程序core dump了也不会有core文件留下。我们需要让core文件能够产生:
$ ulimit -c unlimied #表示不限制core文件大小
调试xxx进程的core文件:
gdb /sbin/xxx xxx.core
1.3.2 core 路径 core dump 限制
在做个项目的时候,发现没有core文件信息,但是unlimit 也打开了。
后来发现core文件的路径出了问题。
[[email protected] src]# vi /proc/sys/kernel/core_pattern
/tmp/core_%P_%p_%e_%s_%t
https://blog.csdn.net/u011417820/article/details/71435031
1.3.3 core 文件产生不了
原因:
switch(iSig)
{
/*
case SIGILL:
case SIGBUS:
case SIGSEGV:
case SIGFPE:
case SIGABRT:
{
if((pstT != NULL) && (pstT->si_code != SI_KERNEL))
{
(VOID)raise(iSig);
break;
}
else
{
break;
}
}
*/
……
1.3.4 设置core文件的名称和文件路径
默认生成路径:输入可执行文件运行命令的同一路径下
默认生成名字:默认命名为core。新的core文件会覆盖旧的core文件
-
设置pid作为文件扩展名
1:添加pid作为扩展名,生成的core文件名称为core.pid
0:不添加pid作为扩展名,生成的core文件名称为core
修改 /proc/sys/kernel/core_uses_pid 文件内容为: 1
修改文件命令: echo “1” > /proc/sys/kernel/core_uses_pid
或者
sysctl -w kernel.core_uses_pid=1 kernel.core_uses_pid = 1
-
控制core文件保存位置和文件名格式
修改文件命令: echo “/tmp/core_%P_%p_%e_%s_%t” > /proc/sys/kernel/core_pattern
[[email protected] home]# cat /proc/sys/kernel/core_pattern
/tmp/core_%P_%p_%e_%s_%t
[[email protected] home]#
https://blog.csdn.net/u011417820/article/details/71435031
https://blog.csdn.net/shenhuan1104/article/details/76172698
2. 断点设置-一些高级命令
1 #include<stdio.h>
2 void printNum(int num)
3 {
4 printf("printNum start \n");
5 while(num > 0)
6 {
7 printf("num= %d\n",num);
8 num--;
9 }
10
11 int main(int argc,char *argv[])
12 {
13 printNum(5);
14 return 0;
15 }
断点设置除了常用的前两节讲的基本用法之外,还有一些高级命令。
基本用法参见:
通俗易懂说GDB调试(一)
https://blog.csdn.net/lqy971966/article/details/88963635
通俗易懂说GDB调试(二)
https://blog.csdn.net/lqy971966/article/details/102812016
-
根据条件设置断点 : b n if xxx
b 7 if num =1 //在7行设置当b=1 的时候断住
-
根据规则设置断点 :rbreak
rbreak printNum* //以printNum开头的函数都设置断点
-
设置临时断点 :tbreak
tbreak 7 //在第10行设置临时断点,断一次就结束
-
跳过多次设置断点 :ignore
ignore 1 10 //断点1在触发断点10次后(首先要设置断点)
-
根据表达式值变化产生断点:watch num
watch num //观察num的值,当有变化时,停止
(先在第7行设置断点,run执行起来,再设置watch num)
-
禁用或启动断点:disable enable
disable #禁用所有断点
disable bnum #禁用标号为bnum的断点
enable #启用所有断点
enable bnum #启用标号为bnum的断点
-
断点清除:clear delete
clear #删除当前行所有breakpoints
clear function #删除函数名为function处的断点
delete #删除所有breakpoints,watchpoints和catchpoints
delete bnum #删除断点号为bnum的断点
3. 变量查看
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
int iTen = 10; //整型
int iArr[] = {1,2,3,5}; //数组
float fEight = 8.5f; //单精度值
char szStr[] = "hello,world";//字符数组
/*申请内存,失败时退出*/
int *pInt= (int*)malloc(iTen * sizeof(int));
if(NULL == pInt)
{
printf("pInt malloc error\n");
return -1;
}
/*赋值*/
for(int iLoop=0; iLoop < 10; iLoop++)
{
pInt[iLoop] = iLoop;
}
free(pInt);
pInt = NULL;
return 0;
}
在启动调试以及设置断点之后,就到了我们非常关键的一步-查看变量。GDB调试最大的目的之一就是走查代码,查看运行结果是否符合预期。 既然如此,我们就不得不了解一些查看各种类型变量的方法,以帮助我们进一步定位问题。
-
普通变量查看,用print(可简写为p)打印变量内容
1.1 打印基本类型变量,数组,字符数组
p iTen
-
打印指针
p pInt //打印指针地址
p *pInt //打印指针指向的内容
在第pInt malloc后打印为空 {, , , , , , , , , }
-
打印指针多值 p pInt @3
//使用*只能打印第一个值,如果要打印多个值,后面跟上@并加上要打印的长度,这里就是打印前三个值
-
$ 可表示上一个变量
假设此时有一个链表linkNode,它有next成员代表下一个节点,则可使用下面方式不断打印链表内容:
(gdb) p *linkNode
(这里显示linkNode节点内容)
(gdb) p *$.next
(这里显示linkNode节点下一个节点的内容)
-
set 可设置一个临时变量,类似c语言一样
(gdb) set $index=0
(gdb)p iArr[index++]
这样就不需要每次修改下标去打印啦。
-
按照特定格式打印变量
常见格式控制字符如下:
x 按十六进制格式显示变量。
d 按十进制格式显示变量。
u 按十六进制格式显示无符号整型。
o 按八进制格式显示变量。
t 按二进制格式显示变量。
a 按十六进制格式显示变量。
c 按字符格式显示变量。
f 按浮点数格式显示变量。
(gdb) p szStr //–hello,world
(gdb) p/x szStr //十六进制格式
(gdb) p fEight
$1 = 8.5
(gdb) p/t fEight
$2 = 1000 //查看浮点数的二进制格式
-
查看内存内容,examine(简写为x)
x/[n][f][u] addr
其中:
n 表示要显示的内存单元数,默认值为1
f 表示要打印的格式,前面已经提到了格式控制字符
u 要打印的单元长度
addr 内存地址
单元类型常见有如下:
b 字节
h 半字,即双字节
w 字,即四字节
g 八字节
(gdb) x/4tb &fEight //变量e的四个字节都以二进制的方式
//float变量fEight 按照二进制方式打印,并且打印单位是一字节
-
自动显示变量内容 display
假设我们希望程序断住时,就显示某个变量的值,可以使用display命令。
那么每次程序断住时,就会打印e的值。
-
查看寄存器内容 info registers
(gdb)info registers
rax 0x0
rbx 0x0
rcx 0x7ffff7dd1b00
rdx 0x0
rsi 0x7ffff7dd1b30
rdi 0xffffffff
rbp 0x7fffffffdc10 0x7fffffffdc10
(内容过多未显示完全)
3.1 打印变量的16进制,2进制
(gdb) p uiMsgType
$1 = 196622
(gdb) p/x uiMsgType //x 按十六进制格式显示变量。
$2 = 0x3000e
(gdb) p/t uiMsgType //t 按二进制格式显示变量。
$3 = 110000000000001110
(gdb)
3.2 gdb查看设备大小端
(gdb) p/t uiMsgType //t 按二进制格式显示变量。
$3 = 110000000000001110
(gdb) x/4tb &uiMsgType //4tb: 4字节,二进制,字节
0xbfd29d88: 00001110 00000000 00000011 00000000
(gdb)
从上面显示看,此设备是小端设备
大小端:
数据: 0x1234 大小端传输:
在十进制中我们都说靠左边的是高位,靠右边的是低位,在其他进制也是如此。就拿 0x1234来说,从高位到低位的字节依次是0x12、0x34。
其中,地址 0x00 是低地址,0x01是高地址。
地址: 0x00 0x01
大端: 12 34
小端: 34 12
4. 源码查看
- 列出源码 list/l
- l 88 //表明要列出第88行附近的源码
-
列出指定函数附近的源码
(gdb) l printNum //列出 printNum函数附近的源码
-
列出指定行之间的源码
list 3,10 //列出3到10行之间的源码
**5. 列出指定文件的源码 location **
location 可以是文件名加行号或函数名
(gdb) l test.c:printNum1
5. gdb多进程,多线程调试
5.1. gdb多进程
5.1.1 set follow-fork-mode [parent|child] //多进程中调试主进程还是子进程
默认设置下,在调试多进程程序时GDB只会调试主进程
只需要设置follow-fork-mode(默认值:parent)
使用follow-fork-mode,只能调试一个进程,不能同时调试父子进程
5.1.2. set detach-on-fork [on|off]
parent on 只调试主进程(GDB默认)
child on 只调试子进程
parent off 同时调试两个进程,gdb跟主进程,子进程block在fork位置
child off 同时调试两个进程,gdb跟子进程,主进程block在fork位置
5.1.3. 查看系统默认的follow-fork-mode 和 detach-on-fork:
show follow-fork-mode
show detach-on-fork
5.2 gdb多线程调试
1. # pstack 1311 //进程ID,查看所有线程堆栈
2. (gdb) info threads //查看线程
3. (gdb) thread n //info threads后(n代表第几个线程)
4. bt,info b ,b xx 等同普通调试相同
参考:
https://blog.csdn.net/zhangye3017/article/details/80382496
https://blog.csdn.net/snow_5288/article/details/72982594
参考
https://cloud.tencent.com/developer/article/1469554