天天看点

通俗易懂说GDB调试(三)总结1. 启动调试2. 断点设置-一些高级命令3. 变量查看4. 源码查看5. gdb多进程,多线程调试参考

通俗易懂说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启动调试方式,分别是调试未运行的程序和已经运行的程序。

  1. 哪类程序可被调试

    对于C程序来说,需要在编译时加上-g参数,保留调试信息,否则不能使用GDB进行调试。

  2. 如何判断一个文件是否带有调试信息呢?

    但如果不是自己编译的程序,并不知道是否带有-g参数,如何判断一个文件是否带有调试信息呢?

    $ gdb helloworld

    Reading symbols from helloWorld…(no debugging symbols found)…done.

    如果没有调试信息,会提示no debugging symbols found。

1.1 调试未运行的程序

程序还未启动时,可有多种方式启动调试。

  1. 调试启动无参程序 : gdb a.out

    例如:

    $ gdb helloWorld

    (gdb) run

    输入run命令,即可运行程序

    $ gdb hello

    (gdb)run abc

1.2 调试已运行程序

调试已运行程序 : gdb attach processId

  1. 首先使用ps命令找到进程id:

    $ ps -ef | grep 进程名 //查看进程id

    4.1 attach方式

  2. 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文件

  1. 设置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

  2. 控制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

  1. 根据条件设置断点 : b n if xxx

    b 7 if num =1 //在7行设置当b=1 的时候断住

  2. 根据规则设置断点 :rbreak

    rbreak printNum* //以printNum开头的函数都设置断点

  3. 设置临时断点 :tbreak

    tbreak 7 //在第10行设置临时断点,断一次就结束

  4. 跳过多次设置断点 :ignore

    ignore 1 10 //断点1在触发断点10次后(首先要设置断点)

  5. 根据表达式值变化产生断点:watch num

    watch num //观察num的值,当有变化时,停止

    (先在第7行设置断点,run执行起来,再设置watch num)

  6. 禁用或启动断点:disable enable

    disable #禁用所有断点

    disable bnum #禁用标号为bnum的断点

    enable #启用所有断点

    enable bnum #启用标号为bnum的断点

  7. 断点清除: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调试最大的目的之一就是走查代码,查看运行结果是否符合预期。 既然如此,我们就不得不了解一些查看各种类型变量的方法,以帮助我们进一步定位问题。

  1. 普通变量查看,用print(可简写为p)打印变量内容

    1.1 打印基本类型变量,数组,字符数组

    p iTen

  2. 打印指针

    p pInt //打印指针地址

    p *pInt //打印指针指向的内容

    在第pInt malloc后打印为空 {, , , , , , , , , }

  3. 打印指针多值 p pInt @3

    //使用*只能打印第一个值,如果要打印多个值,后面跟上@并加上要打印的长度,这里就是打印前三个值

  4. $ 可表示上一个变量

    假设此时有一个链表linkNode,它有next成员代表下一个节点,则可使用下面方式不断打印链表内容:

    (gdb) p *linkNode

    (这里显示linkNode节点内容)

    (gdb) p *$.next

    (这里显示linkNode节点下一个节点的内容)

  5. set 可设置一个临时变量,类似c语言一样

    (gdb) set $index=0

    (gdb)p iArr[index++]

    这样就不需要每次修改下标去打印啦。

  6. 按照特定格式打印变量

    常见格式控制字符如下:

    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 //查看浮点数的二进制格式

  7. 查看内存内容,examine(简写为x)

    x/[n][f][u] addr

    其中:

    n 表示要显示的内存单元数,默认值为1

    f 表示要打印的格式,前面已经提到了格式控制字符

    u 要打印的单元长度

    addr 内存地址

    单元类型常见有如下:

    b 字节

    h 半字,即双字节

    w 字,即四字节

    g 八字节

    (gdb) x/4tb &fEight //变量e的四个字节都以二进制的方式

    //float变量fEight 按照二进制方式打印,并且打印单位是一字节

  8. 自动显示变量内容 display

    假设我们希望程序断住时,就显示某个变量的值,可以使用display命令。

    那么每次程序断住时,就会打印e的值。

  9. 查看寄存器内容 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. 源码查看

  1. 列出源码 list/l
  2. l 88 //表明要列出第88行附近的源码
  3. 列出指定函数附近的源码

    (gdb) l printNum //列出 printNum函数附近的源码

  4. 列出指定行之间的源码

    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

继续阅读