GDB调试教程基础版(不包含并发程序)

编程环境

系统:CentOs 7.4版本

编译器:gcc 4.8.5版本

debug:gdb 7.6.1版本

文本编辑器:VScode or VIM

调试准备工作

gdb常用命令:

  1. gcc/g++ -g test.c/test.cpp          调试前先对源文件进行编译,注意一定要加 -g。
  2. gdb a.out                                   a.out为可已经编译好的可执行文件(当然如果存在语法错误是肯定不能进入gdb调试的)。
  3. gdb attach 进程号                     调试已经在运行的进程
  4. gdb --pid 进程号                        调试已经在运行的进程
  5. list/l 行号                                   显示源代码,从指定行号处开始显示,每次显示10行。
  6. list/l 函数名                                列出某个函数的源代码。
  7. run/r                                          运行程序。
  8. next/n                                        单条执行,类似于Vs中的F10。
  9. step/s                                        进入函数调用,类似于Vs中的F11。
  10. break/b 行号                             在某一行设置断点。
  11. break/b 函数                             在某个函数处设置断点。
  12. info break/b                               查看断点信息。
  13. finish                                         执行到当前函数返回,然后停下来等待命令。
  14. print/p                                       打印表达式的值,通过表达式的值可以修改变量的值或者调用函数。
  15. p 变量                                       打印变量的值。
  16. continue/c                                 从当前位置开始连续而非单步执行程序。
  17. delete breakpoints                    删除所有断点。
  18. delete breakpoints n                 删除序号为n的断点。
  19. disable breakpoints                   禁用断点 没有参数表示禁用全部断点。
  20. enable breakpoints                    启用断点 没有参数表示启用全部断点。
  21. info/i breakpoints                       查看当前设置了哪些断点。
  22. display 变量名                           跟踪查看一个变量,每次停下来都显示它的值。
  23. undisplay                                   取消对先前设置的那些变量的跟踪。
  24. until X行号                                 执行至X行。
  25. breaktrace/bt                             查看各级函数调用及参数。
  26. info/i locals                                查看当前栈帧局部变量的值。
  27. quit/q                                         退出gdb。
  28. frame/f 帧号                              查看某一帧信息,段错误时常用。如果没有帧号显示当前所在代码行数 
  29. watch  变量                               当变量改变时显示出来。
  30. jump 行数或函数                       跳过中间代码,直接执行指定语句,容易出错不建议使用。
  31. whatis   变量                             查看变量的类型                                                  

给出测试代码(名字为test.c,可执行程序名字为test.exe):

#include<stdio.h>
int Get(int a)
{
    if(a == 0)
        return 1;
    else
        return 0;
    return 0;
}
int main(int argc , char *argv[])  
{
    int a;
    scanf("%d",&a);
    int b;
    b = Get(a);
    printf("%d\n",b);
    return 0;
}

第一步:如果gcc编译的时候没有加上-g参数,那么就不会保留调试参数,就不能用GDB调试。

例如:

gcc test.c -o test.exe
gdb test.exe
提示:
Reading symbols from /home/wanghe/test.exe...(no debugging symbols found)...done.

这样是不能用GDB调试的。

gcc -g -o test.exe test.c
gdb test.exe
提示:
Reading symbols from /home/wanghe/test.exe...done.

这样是可以的。

使用readelf命令查看段信息。

  readelf -S test.exe|grep debug
  [28] .debug_aranges    PROGBITS         0000000000000000  00001061
  [29] .debug_info       PROGBITS         0000000000000000  00001091
  [30] .debug_abbrev     PROGBITS         0000000000000000  00001195
  [31] .debug_line       PROGBITS         0000000000000000  0000122d
  [32] .debug_str        PROGBITS         0000000000000000  00001271

如果没有debug信息表示不能被调试。

file查看strip状况

file test.exe 
test.exe: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=198f87ce837ca2afcee77b9c8b198d23922f08dd, not stripped

最后是not strip表示可以调试,否则不行。

第二步:在linux下当应用程序发生异常中止退出或者发生崩溃的时候,linux内核会将应用程序在这段运行期间的内存状态等相关信息转存到磁盘,以供系统故障排查或者调试。这个转存的文件叫core dump文件。core dump文件中会记录程序当时的内存调用、堆栈引用、进程和线程调用等信息,可以帮助开发人员和维护人员了解异常发生当时的环境参数和信息,所以core dump对故障排查和bug调试具有重大的意义。

首先判断系统有没有限制core文件的产生。

ulimit -c
输出 0

输出结果是0,表示不会保存core文件,需要进行设置。

ulimit -c unlimied  #表示不限制core文件大小
ulimit -c 10        #设置最大大小,单位为块,一块默认为512字节

当core dump 之后,使用命令 gdb program core 来查看 core 文件,其中 program 为可执行程序名,core 为生成的 core 文件名,接着输入bt命令即可查看问题。

设置完以后就可以通过gdb + 可执行文件名来进行调试,程序分为启动时是否带有参数,所以调试也需要根据是否有参数来进行。

例如上述程序不需要参数,所以直接可以进行调试。

$ gdb test.exe 
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-114.el7
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/wanghe/test.exe...done.
(gdb) 

如果对程序进行修改:

#include<stdio.h>

int Get(int a)
{
    if(a == 0)
        return 1;
    else
        return 0;
    return 0;
}
int main( int argc , char *argv[] )  
{
    int b;
    if(argc > 1)
        b  = 0;
    else
        b = 1;
    printf("%d\n",b);
    return 0;
}
gdb test.exe 
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-114.el7
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/wanghe/test.exe...done.
(gdb) run a b
Starting program: /home/wanghe/test.exe a b
0
[Inferior 1 (process 16926) exited normally]
Missing separate debuginfos, use: debuginfo-install glibc-2.17-260.el7_6.5.x86_64
(gdb) 

通过run后面跟参数即可。

设置断点

为了更好的找出程序哪里出现问题,我们通常对程序设置断点,即让程序执行到断点处暂定执行,这样就可以更直观的了解程序的运行过程。

通过gdb进入调试程序

首先给出查看断点的命令。

(gdb) info breakpoints
No breakpoints or watchpoints.

方式一:根据行号设置断点

b 10
b test.c:10 #都表示断点设置在程序第10行
 
(gdb) b 10
Breakpoint 1 at 0x400547: file test.c, line 10.
(gdb) b test.c:11
Note: breakpoint 1 also set at pc 0x400547.
Breakpoint 2 at 0x400547: file test.c, line 11.
(gdb) info breakpoints
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000400547 in main at test.c:10
2       breakpoint     keep y   0x0000000000400547 in main at test.c:11

方式二:根据函数名设置断点

(gdb) b Get
Breakpoint 1 at 0x400524: file test.c, line 4.

方式三:根据条件设置断点

假设程序某处发生崩溃,而崩溃的原因怀疑是某个地方出现了非期望的值,那么你就可以在这里断点观察,当出现该非法值时,程序暂停。

(gdb) break test.c:17 if b == 0
Breakpoint 1 at 0x40055d: file test.c, line 17.
(gdb) r 2 3
Starting program: /home/wanghe/test.exe 2 3
Breakpoint 1, main (argc=3, argv=0x7fffffffdfd8) at test.c:17
warning: Source file is more recent than executable.
17          printf("%d\n",b);

由于2 3是两个参数。b被赋值为0,所以执行到断点处暂停。

通过condition可以修改条件。

(gdb) condition 1 b == 1
(gdb) info breakpoints 
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x000000000040055d in main at test.c:17
        stop only if b == 1
        breakpoint already hit 1 time
(gdb) 

方式四:根据规则设置断点

rbreak + 表达式可以设置断点,例如rbreak test.c:.可以对所有函数设置断点:

(gdb) rbreak test.c:.
Breakpoint 1 at 0x400524: file test.c, line 4.
int Get(int);
Breakpoint 2 at 0x400547: file test.c, line 13.
int main(int, char **);

方式五:设置临时断点

临时断点只会生效一次。

(gdb) tbreak test.c:10
Temporary breakpoint 1 at 0x400547: file test.c, line 10.

方式六:跳过断点固定次数

有时循环要跑某条语句很多次,但是假设前30次都没有问题,后面才会出现问题,总不能慢慢的执行,所以希望程序先执行30次。

ignore 1 30 #1是断点号 30表示次数

方式七:通过watch观察值得变化

程序必须运行起来,因为刚开始临时变量不会存在,如果watch会出现以下错误。

(gdb) watch b
No symbol "b" in current context.
(gdb) b 12
Breakpoint 1 at 0x400547: file test.c, line 12.
(gdb) run
Starting program: /home/wanghe/test.exe 
Breakpoint 1, main (argc=1, argv=0x7fffffffdff8) at test.c:12
12          int b = -1;
Missing separate debuginfos, use: debuginfo-install glibc-2.17-260.el7_6.5.x86_64
(gdb) watch b
Hardware watchpoint 2: b
(gdb) continue
Continuing.
Hardware watchpoint 2: b

Old value = 0
New value = 1
main (argc=1, argv=0x7fffffffdff8) at test.c:17
17          printf("%d\n",b);

方式八:禁用或启动断点

disable  #禁用所有断点
disable bnum #禁用标号为bnum的断点
enable  #启用所有断点
enable bnum #启用标号为bnum的断点
enable delete bnum  #启动标号为bnum的断点,并且在此之后删除该断点

例如禁用所有断点

(gdb) disable
(gdb) info breakpoints 
Num     Type           Disp Enb Address            What
1       breakpoint     keep n   0x0000000000400524 in Get at test.c:4
2       breakpoint     keep n   0x0000000000400547 in main at test.c:13 #Enb是n,否则是y

最后需要清除断点。

clear   #删除当前行所有breakpoints
clear function  #删除函数名为function处的断点
clear filename:function #删除文件filename中函数function处的断点
clear lineNum #删除行号为lineNum处的断点
clear f:lename:lineNum #删除文件filename中行号为lineNum处的断点
delete  #删除所有breakpoints,watchpoints和catchpoints
delete bnum #删除断点号为bnum的断点

查看变量

通过p指令来打印变量的值

给出测试代码:

#include<stdio.h>
#include<stdlib.h>

int main( int argc , char *argv[] )  
{
    int a = 1;
    int i = 0;
    int b[3] = {0,1,2};
    for(i = 0; i < 3;i++)
        b[i] = b[i] + 1;
    printf("%d\n",a);
    int *p;
    p = b;
    printf("%d\n",p[0]);
    return 0;
}
(gdb) p a #执行到printf语句的时候
$1 = 3
(gdb) p b
$2 = {1, 2, 3} 

打印的是变量a和数组b的值。

如果多个函数或文件有相同的变量名,则需要在前面加上文件或函数名。

(gdb) p 'main'::a
$3 = 3

打印指针

(gdb) p p
$4 = (int *) 0x7fffffffdff0

打印指针指向的内容,@后面跟的是打印的长度。

(gdb)  p *p@3
$5 = {1, 2, 3}

也可以设置变量打印。

(gdb) set $index = 0
(gdb) p p[$index++]
$6 = 1
(gdb) p p[$index++]
$7 = 2
(gdb) p p[$index++]
$8 = 3

还可以设置打印格式:

  1. x 按十六进制格式显示变量。
  2. d 按十进制格式显示变量。
  3. u 按十六进制格式显示无符号整型。
  4. o 按八进制格式显示变量。
  5. t 按二进制格式显示变量。
  6. a 按十六进制格式显示变量。
  7. c 按字符格式显示变量。
  8. f 按浮点数格式显示变量。
(gdb) p/x a
$10 = 0x3

查看内存内容,语法是

x/[n][f][u] addr
  1. n 表示要显示的内存单元数,默认值为1
  2. f 表示要打印的格式,前面已经提到了格式控制字符
  3. u 要打印的单元长度
  4. addr 内存地址

单元类型常见有如下:

  1. b 字节

  2. h 半字,即双字节

  3. w 字,即四字节

  4. g 八字节

(gdb) x/4tb &a
0x7fffffffdf0c: 00000011        00000000        00000000        00000000

a的值是三,所以高三个字节都是0。

如果希望遇到断点的时候显示某个值,可以用display。

(gdb) display p
1: p = (int *) 0x7fffffffdef0

可以通过info display命令来查看哪些变量被设置了display。

(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
1:   y  p

清除display变量:

delete display num #删除 num为前面变量前的编号,不带num时清除所有
disable display num  #去使能 num为前面变量前的编号,不带num时去使能所有

查看寄存器的值:

(gdb) info registers
rax            0x7fffffffdef0   140737488346864
rbx            0x0      0
rcx            0x1      1
rdx            0x7ffff7dd6a00   140737351870976
rsi            0x7ffffffe       2147483646
rdi            0x0      0
rbp            0x7fffffffdf10   0x7fffffffdf10
rsp            0x7fffffffdee0   0x7fffffffdee0
r8             0xffffffffffffffff       -1
r9             0x7ffff7a5b14d   140737348219213

 

单步调试

l命令可以显示代码。

(gdb) l
warning: Source file is more recent than executable.
1       #include<stdio.h>
2       #include<stdlib.h>
3
4       int main( int argc , char *argv[] )  
5       {
6           int a = 1;
7           int i = 0;
8           int b[3] = {0,1,2};
9           for(i = 0; i < 3;i++)
10              b[i] = a + 1;
(gdb) 

next命令(可简写为n)用于运行到程序断点处后,继续执行下一条语句,假设已经启动调试,并在第12行停住,如果要继续执行,则使用n执行下一条语句,如果后面跟上数字num,则表示执行该命令num次,就达到继续执行n行的效果了。

(gdb) b 12
Breakpoint 1 at 0x40057e: file test.c, line 12.
(gdb) run
Starting program: /home/wanghe/test.exe 
3

Breakpoint 1, main (argc=1, argv=0x7fffffffdff8) at test.c:12
12          int *p;
Missing separate debuginfos, use: debuginfo-install glibc-2.17-260.el7_6.5.x86_64
(gdb) n
13          p = b;

对于上面的情况,如果我们想跟踪函数内部的情况,可以使用step命令(可简写为s),它可以单步跟踪到函数内部,但前提是该函数有调试信息并且有源码信息。如果没有函数调用,s的作用与n的作用并无差别,仅仅是继续执行下一行。它后面也可以跟数字,表明要执行的次数。

(gdb) s
12          int c = add(a , b);
(gdb) s
add (a=1, b=2) at test.c:6
6           return a + b;

如果需要跳过该函数执行,可使用finish命令,继续后面的执行。

(gdb) finish
Run till exit from #0  add (a=1, b=2) at test.c:6
0x000000000040055d in main (argc=1, argv=0x7fffffffdff8) at test.c:12
12          int c = add(a , b);
Value returned is $1 = 3

until是继续运行到指定位置,假如我们在25行停住了,现在想要运行到29行停住,就可以使用until命令(可简写为u)。

(gdb) u 15
1
main (argc=1, argv=0x7fffffffdff8) at test.c:15
15          return 0;

skip可以在step时跳过一些不想关注的函数或者某个文件的代码。

(gdb) b 13
Breakpoint 1 at 0x400560: file test.c, line 13.
(gdb) skip function add
Function add will be skipped when stepping.
(gdb) run
Starting program: /home/wanghe/test.exe 

Breakpoint 1, main (argc=1, argv=0x7fffffffdff8) at test.c:13
13          printf("%d\n",c);
Missing separate debuginfos, use: debuginfo-install glibc-2.17-260.el7_6.5.x86_64

continue是直接继续执行到下一个断点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值