GDB 调试

GDB 调试篇

运行命令

  • run:简记为 r ,其作用是运行程序,当遇到断点后,程序会在断点处停止运行,等待用户输入下一步的命令。
  • continue (简写c ):继续执行,到下一个断点处(或运行结束)
  • next:(简写 n),单步跟踪程序,当遇到函数调用时,也不进入此函数体;此命令同 step 的主要区别是,step 遇到用户自定义的函数,将步进到函数中去运行,而 next 则直接调用函数,不会进入到函数体内。
  • step (简写s):单步调试如果有函数调用,则进入函数;与命令n不同,n是不进入调用的函数的
  • until:当你厌倦了在一个循环体内单步跟踪时,这个命令可以运行程序直到退出循环体。
  • until+行号: 运行至某行,不仅仅用来跳出循环
  • finish: 运行程序,直到当前函数完成返回,并打印函数返回时的堆栈地址和返回值及参数值等信息。
  • call 函数(参数):调用程序中可见的函数,并传递“参数”,如:call gdb_test(55)
  • quit:简记为 q ,退出gdb



设置断点

  • break n (简写b n):在第n行处设置断点

    (可以带上代码路径和代码名称: b OAGUPDATE.cpp:578)

  • b fn1 if a>b:条件断点设置

  • break func(break缩写为b):在函数func()的入口处设置断点,如:break cb_button

  • delete 断点号n:删除第n个断点

  • disable 断点号n:暂停第n个断点

  • enable 断点号n:开启第n个断点

  • clear 行号n:清除第n行的断点

  • info b (info breakpoints) :显示当前程序的断点设置情况

  • delete breakpoints:清除所有断点:

查看源代码

  • list :简记为 l ,其作用就是列出程序的源代码,默认每次显示10行。
  • list 行号:将显示当前文件以“行号”为中心的前后10行代码,如:list 12
  • list 函数名:将显示“函数名”所在函数的源代码,如:list main
  • list :不带参数,将接着上一次 list 命令的,输出下边的内容。

打印表达式

  • print 表达式:简记为 p ,其中“表达式”可以是任何当前正在被测试程序的有效表达式,比如当前正在调试C语言的程序,那么“表达式”可以是任何C语言的有效表达式,包括数字,变量甚至是函数调用。
  • print a:将显示整数 a 的值
  • print ++a:将把 a 中的值加1,并显示出来
  • print name:将显示字符串 name 的值
  • print gdb_test(22):将以整数22作为参数调用 gdb_test() 函数
  • print gdb_test(a):将以变量 a 作为参数调用 gdb_test() 函数
  • display 表达式:在单步运行时将非常有用,使用display命令设置一个表达式后,它将在每次单步进行指令后,紧接着输出被设置的表达式及值。如: display a
  • watch 表达式:设置一个监视点,一旦被监视的“表达式”的值改变,gdb将强行终止正在被调试的程序。如: watch a
  • whatis :查询变量或函数
  • info function: 查询函数
  • 扩展info locals: 显示当前堆栈页的所有变量

查询运行信息

  • where/bt :当前运行的堆栈列表;
  • bt backtrace 显示当前调用堆栈
  • up/down 改变堆栈显示的深度
  • set args 参数:指定运行时的参数
  • show args:查看设置好的参数
  • info program: 来查看程序的是否在运行,进程号,被暂停的原因。

调试

调试问题

  1. 字符串显示不全

(gdb) p recvbuf
​ $1 = 0x7ffff2692eb0 “{\n \”errcode\”: 0,\n \”errlevel\”: 68,\n \”errmsg\”: \”i`m ip:192.168.2.253\”,\n \”filename\”: \”main.cpp\”,\n \”happenddata\”: \”(2017-08-18 15:36:49 周五)\”,\n \”isbehave\”: 0,\n \”ishardware\”: 0,\n “…

GDB 默认显示 200 的字符长度;

(gdb) show print elements  
Limit on string chars or array elements to print is 200.

使用set print elements [] 调整参数

set print elements 0  调整
set print elements 300
  1. GDB 调试程序,找不到源码,list无效

    1
    2
    show directories    ## 查看当前GDB寻找源码路径
    dir dirname ## 添加一个新的源代码查找路径; 多个用 : 分开

  2. 查看源代码信息:

    1. 文件名, 路径, Source language等

      1
      info source
    2. 查看当前栈 bt (backtrace)/ f (frame)

      1. f: 打印栈的层编号, 当前函数名, 函数参数值, 所在文件, 行号, 执行语句等
      2. up: 表示向栈的上面移动n层, 默认 1
      3. down: 标识向栈的下面移动n层, 默认 1
    3. info args 打印当前函数的参数名以及值

    4. info locals 打印当前函数中所有局部变量以及值

    5. info catch 打印当前的函数中异常处理信息

    6. list :

      list + 显示往后代码
      list - 显示之前代码
      set listsize 设置一次显示源代码行数
      show listsize 显示当前listsize 设置
      
    7. 搜索源代码 : 可以使用正则表达式

      forward-search / search 向前搜索
      reverse-search  全部搜索
      
    8. 输出格式:

      x 按十六进制格式显示变量
      
      d 按十进制格式显示变量
      
      u 按十六进制格式显示无符号整型
      
      o 按八进制格式显示变量
      
      t 按二进制格式显示变量
      
      a 按十六进制格式显示变量
      
      c 按字符格式显示变量
      
      f 按浮点数格式显示变量
      
      1
      2
      3
      4
      (gdb) p  i
      $21 = 101
      (gdb) p/a i
      $22 = 0x65

查看结构体类型 ptype

1
2
3
4
5
6
(gdb) ptype List
type = struct List {
NodeTag type;
int length;
ListCell *head;
ListCell *tail;

改变程序执行

print x=4
whatis width  // 查看数据类型 
set width=47  // 修改值;

jump 跳转执行

jump 提供乱序执行的功能。 GDB可以修改程序的执行顺序,让程序执行随意的跳转;

jump 指定下一条语句的运行点, 可以是文件的行号, 可以使file:line 格式, 可以使+num 偏移量格式,表示着下一条运行语句从哪开始

jump 代码行的运行地址:

​ jump 命令不会改变当前程序栈的内容,所以,当你从一个函数跳到另一个函数时,函数运行完返回时进行弹栈操作时必然会发生错误,可能结果非常奇怪, 甚至产生Core dump文件, 所以最好在同一个函数中跳转;

jump 修改寄存器地址, set $pc

1
set $pc = 0x485

产生信号量

使用signal命令,可以产生一个信号量给被调试的程序。如: 中断信号Ctrl + C , 这非常方便程序的调试, 可以在程序运行的任意位置设置断点,并在该断点用GDB产生一个信号量,精确在某处产生信号非常有利于调试。
    语法:    signal, Unix 的系统信号量通常从1到15,所以取值也在这个范围。
    signal 命令和shell的kill不同,系统的kill命令发送信号给被调试程序时,是有GDB截获,而signal命令所发出一信号则是直接发给被调试程序的。

强制函数返回

如果你的调试断点在某个函数中,并还有语句没有执行完, 你可以使用return命令强制函数忽略还没有执行的语句并返回。

return
    使用return命令取消当前函数的执行,并立即返回,如果指定了,那么该表达式的值会被认作函数的返回值

设置临时变量 set var

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

int func(void)
{
int i = 2;
return i;
}

int main(void)
{
int a = 0;
a = func();
printf("%d\n", a);
return 0;
}

技巧

  1. 在gdb中,可以用“set var variable=expr”命令设置变量的值,以上面代码为例:

    1
    2
    3
    4
    5
    6
    7
    Breakpoint 2, func () at a.c:5
    5 int i = 2;
    (gdb) n
    7 return i;
    (gdb) set var i = 8
    (gdb) p i
    $4 = 8

    可以看到在func函数里用set命令把i的值修改成为8。

  2. 也可以用“set {type}address=expr”的方式,含义是给存储地址在address,变量类型为type的变量赋值,仍以上面代码为例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    Breakpoint 2, func () at a.c:5
    5 int i = 2;
    (gdb) n
    7 return i;
    (gdb) p &i
    $5 = (int *) 0x8047a54
    (gdb) set {int}0x8047a54 = 8
    (gdb) p i
    $6 = 8

    可以看到i的值被修改成为8。

  3. 另外寄存器也可以作为变量,因此同样可以修改寄存器的值:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    Breakpoint 2, func () at a.c:5
    5 int i = 2;
    (gdb)
    (gdb) n
    7 return i;
    (gdb)
    8 }
    (gdb) set var $eax = 8 ;; 修改寄存器地址;
    (gdb) n
    main () at a.c:15
    15 printf("%d\n", a);
    (gdb)
    8
    16 return 0;

    可以看到因为eax寄存器存储着函数的返回值,所以当把eax寄存器的值改为8后,函数的返回值也变成了8。

强制调用函数

call
    表达式中可以是函数, 以此达到强制调用函数的目的, 并显示函数的返回值,如果函数返回值是void,那么就不显示。

另一个相似的命令也可以完成这一功能 -- print, print 后边可以跟表达式,所以也可以用它来调用函数, 
    print 和 call 的不同是, 如果函数返回void, call 则不显示, print则显示函数的返回值,并把该值存入历史数据中.

GDB 调试子进程 follow-fork-mode

  • follow-fork-mode 用法:

    1
    set follow-fork-mode [parent|child]
    • parent: fork之后继续调试父进程,子进程不受影响。
    • child: fork之后调试子进程,父进程不受影响。

    因此如果需要调试子进程,在启动gdb后:
    (gdb) set follow-fork-mode child

示例

子进程代码

GDB 调试

GDB 同时调试父子进程

  • detach-on-fork参数,指示GDB在fork之后是否断开(detach)某个进程的调试,或者都交由GDB控制:

set detach-on-fork [on|off]

  • on: 断开调试follow-fork-mode指定的进程。GDB 默认参数
  • off: gdb将控制父进程和子进程。follow-fork-mode指定的进程将被调试,另一个进程置于暂停(suspended)状态。

注意,最好使用GDB 6.6或以上版本,如果你使用的是GDB6.4,就只有follow-fork-mode模式。


命令与参数

查看结构体类型 ptype

1
2
3
4
5
6
(gdb) ptype List
type = struct List {
NodeTag type;
int length;
ListCell *head;
ListCell *tail;

调试宏定义 -g[level]

1
2
3
4
5
6
7
8
Request debugging information and also use level to specify how much information.  The default level is 2.
Level 0 produces no debug information at all. Thus, -g0 negates -g.
Level 1 produces minimal information, enough for making backtraces in parts of the program that you don't plan to debug. This includes descriptions of
functions and external variables, but no information about local variables and no line numbers.
Level 3 includes extra information, such as all the macro definitions present in the program. Some debuggers support macro expansion when you use -g3.
-gdwarf-2 does not accept a concatenated debug level, because GCC used to support an option -gdwarf that meant to generate debug information in version
1 of the DWARF format (which is very different from version 2), and it would have been too confusing. That debug format is long obsolete, but the
option cannot be changed now. Instead use an additional -glevel option to change the debug level for DWARF.

配置文件 ~/.gdbinit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
set startup-with-shell off

set listsize 30

#缩进 显示结构体成员
set print pretty on

#保存历史命令
set history filename ~/.gdb_history
set history save on


#gdb 过程记录
#set logging file log.txt
set logging on

#是否让输出覆盖之前的日志文件
#set logging overwrite on
欣赏此文? 求鼓励,求支持!