使用gdb调试程序

目录

1 说明

2 查看某些变量值或某段内存值

2.1 查看变量值

2.2 查看内存值

2.3 使用display自动显示变量值

2.4 显示源文件中的代码

3 改变已有变量的值

4 设置某些临时变量

5 直接调用指定的函数并传递参数

6 设置、启用、禁用、删除断点并查看断点

6.1 在函数入口处设置断点

6.2 在文件指定行设置断点(该行代码并不执行)

6.3 查看当前断点

6.4 禁用断点

6.5 启用断点

6.6 删除断点

6.7 按照指定条件设置断点

6.8 忽略断点CNT次

6.9 随时停止程序执行

7 使用循环遍历打印链表或数组的值

8 多个命令合并成一个函数

9 单步执行代码行、单步执行函数、跳跃执行

9.1 单步执行代码行、单步执行函数

9.2 跳跃执行

10 控制多线程的执行与切换

10.1 查看当前有多少个线程

11 监测变量的改变

11.1 列出当前所有的watchpoint

11.2 删除指定的watchpoint

12 执行到指定的代码行

13 更改默认打印长度

14 执行shell命令

15 给main函数传入参数

16 当前执行代码行甄别

17 gdb或者valgrind调试时显示的行号与编辑器里面文件的行号不匹配

18 其他


1 说明

gdb一般用于调试程序的段错误。可以借助gdb改变一些变量,改变某些函数的调用顺序等帮助快速达到功能测试的目的。

编译源文件时要加-g选项,执行语法类似于:gdb ./a.out,可以在程序运行过程中Ctrl+C,之后再执行下列讲到的一系列操作。本文内容来源于调试hi3531A的过程中总结的经验。

2 查看某些变量值或某段内存值

2.1 查看变量值

p VAR,VAR为变量的名称,p为print的缩写,此时VAR要在当前运行上下文中,否则会报错:No symbol "VAR" in current context.

也可用printf格式化打印变量,比如打印整型A并换行:printf “%d\n”,A。

使用P VAR方式打印整数时默认为10进制打印,如果变量VAR为16进制数据并且我们希望按照16进制打印则可以使用p/x VAR方式,如果变量VAR为单个可见字符则可以使用p/c VAR方式,如果变量VAR为整数想使用二进制形式打印则可以使用p/t VAR。

GDB打印数据显示格式:

x (hexadecimal)按十六进制格式显示变量。

d (signed decimal)按十进制格式显示变量。

u (unsigned decimal)按十进制格式显示无符号整型。

o (octal)按八进制格式显示变量。

t (binary)按二进制格式显示变量。

a (address)按十六进制格式显示地址,并显示距离前继符号的偏移量(offset)。常用于定位未知地址(变量)。

c (character)按字符格式显示变量。

f (floating)按浮点数格式显示变量。

2.2 查看内存值

x/nfu address

n、f、u是可选的参数,n表示取几个内存单元,f表示内存单元的显示形式,u表示一个内存单元几个字节。

f的值一般为:x(补零形式十六进制格式),d(有符号十进制格式),u(无符号十进制格式),o(八进制格式),t(二进制格式),

a(不补零形式十六进制格式),c(字符格式),f(浮点数格式),使用时要更变量实际的类型匹配。

u的值一般为:b(单字节),h(双字节),w(四字节),g(八字节)

具体使用可查看帮助:

(gdb) help x

Examine memory: x/FMT ADDRESS.

ADDRESS is an expression for the memory address to examine.

FMT is a repeat count followed by a format letter and a size letter.

Format letters are o(octal), x(hex), d(decimal), u(unsigned decimal),

  t(binary), f(float), a(address), i(instruction), c(char) and s(string).

Size letters are b(byte), h(halfword), w(word), g(giant, 8 bytes).

The specified number of objects of the specified size are printed

according to the format.

Defaults for format and size letters are those previously used.

Default count is 1.  Default address is following last thing printed

with this command or "print".

n/f/u三个参数可以一起使用。例如:

命令:x/3uh 0x7ff320

表示,从内存地址0x7ff320读取内容,3表示三个单元,h表示以双字节为一个单元,u表示按无符号十进制格式显示。

2.3 使用display自动显示变量值

(gdb) display var //在下一断点处自动显示变量var(var要在当前运行上下文中)的值

以下来自gdb帮助文档(help display,help info,apropos info)

delete display -- Cancel some expressions to be displayed when program stops

disable display -- Disable some expressions to be displayed when program stops

enable display -- Enable some expressions to be displayed when program stops

undisplay -- Cancel some expressions to be displayed when program stops

info display -- Expressions to display when program stops

(gdb)  help display

Print value of expression EXP each time the program stops.

/FMT may be used before EXP as in the "print" command.

/FMT "i" or "s" or including a size-letter is allowed,

as in the "x" command, and then EXP is used to get the address to examine

and examining is done as in the "x" command.

With no argument, display all currently requested auto-display expressions.

Use "undisplay" to cancel display requests previously made.

2.4 显示源文件中的代码

list,显示当前行后面的源代码

set listsize N,设置一次显示的行数为N(默认一次显示10行)

show listsize,显示当前的listsize大小

list FN,LN,显示从FN到LN之间的源代码

3 改变已有变量的值

一般用于改变当前上下文中的指定变量用以覆盖测试用例,

执行set var 变量名称,即可:

(gdb) p A

$17 = 0

(gdb) set var A=3 //’var’为’variable’的缩写

(gdb) p A        

$18 = 3

4 设置某些临时变量

设置变量A为结构体A_ST的变量:set var $A=(A_ST *)malloc(sizeof(A_ST)), ‘$’符合为必需,A_ST要在当前运行上下文中有意义,此时可以用whatis命令查看A的数据类型:

(gdb) whatis $A

type = A *

打印A的地址:

(gdb) p $A

$4 = (A_ST *) 0xe3de38

给A的成员变量赋值:

(gdb) set $A->u8a=1

(gdb) set $A->u16b=2

(gdb) set $A->u8c=3    

(gdb) set $A->u8d=4

(gdb) set $A->u8e=5

(gdb) set $A->u8f=1     

(gdb) set $A->as8g="abcd" //字符数组

打印A的内容:

(gdb) p *$A

$6 = {u8a = 1 '\001', u16b = 2, u8c = 3 '\003',

  u8d = 4 '\004', u8e = 5 '\005',

  u8f = 1 '\001', as8g = "abcd", '\000' <repeats 25 times>} //'\000' <repeats 25 times>:as8g为长度30的字符串,有效长度为4,所有’\0’之后重复了25个’\0’。

或者直接给结构体赋值:

(gdb) p busRectTemp //busRectTemp为全局变量

$1 = {x = -72, y = 203, w = 48, h = 24}

(gdb) set var busRectTemp={126,190,48,24}

(gdb) p busRectTemp                      

$2 = {x = 126, y = 190, w = 48, h = 24}

(gdb) set var $pstA=(A_ST *)malloc(sizeof(A_ST))

(gdb) set var $pstA=(A_ST *)(&g_astA[0].pu8Data[7*(sizeof(A_ST))]) //g_astA为一个全局变量

(gdb) p *$pstA                                                                                     

$6 = {u64At = 1, u64B = 1, u32C = 6312}

5 直接调用指定的函数并传递参数

比如调用函数test_func(void):

call test_func() //该函数不带参数,所以不传参数

如果以call 函数名称,这种方式调用则无实际效果之后显示函数原型,这跟p 函数名称 的效果一样:

(gdb) call A_func  

$11 = {int (void)} 0xaafd0 <A_func>

(gdb) p A_func   

$12 = {int (void)} 0xaafd0 <A_func>

比如调用带参数函数int A_func(A_ST *pstSend),参考‘设置某些临时变量’章节:

(gdb) call A_func(*$A)

比如调用带基本数据类型的函数int A_func(int a, char b),可以直接传递数值即可:

(gdb) call A_func(1,2)

也可参考‘设置某些临时变量’章节,先设置临时变量,再传递临时变量即可:

(gdb) set $a=1

(gdb) call A_func($a,2)

6 设置、启用、禁用、删除断点并查看断点

6.1 在函数入口处设置断点

(gdb) b A_func // ‘b’ 为’break’的缩写

Breakpoint 1 at 0xaafdc: file A.c, line 1019.

6.2 在文件指定行设置断点(该行代码并不执行)

(gdb) b B.c:1021 //1021即为指定的行

Breakpoint 2 at 0xab008: file B.c, line 1021.

6.3 查看当前断点

info b/info break/info breakpoints/info breakpoint,这四条命令都可以

6.4 禁用断点

(gdb) disable 1 //数值1为断点编号,查看当前断点时可以看到所有断点的编号

6.5 启用断点

(gdb) enable 1 //数值1为断点编号,查看当前断点时可以看到所有断点的编号

6.6 删除断点

(gdb) delete 1 //数值1为断点编号,查看当前断点时可以看到所有断点的编号

(gdb) delete 1-3 //删除断点1至3这3个断点,如果没有2这个断点则会提示且1、3断点被删除

6.7 按照指定条件设置断点

break 文件名:行号 if 条件

(gdb) break test.c:100 if g_s32A==1 //如果g_s32A(当前作用域有效的变量)等于1则在test.c的100处设置断点

Breakpoint 2 at 0x1d075c: file test.c, line 100.

在函数入口处按条件设置断点,比如函数原型为int A_func(int a),

(gdb) b A_func if a==1

(gdb) call A_func(1) //此时进入函数会阻塞,因为参数等于1

(gdb) call A_func(0) //此时进入函数会不会阻塞,因为参数不等于1

6.8 忽略断点CNT次

ignore 断点编号N CNT //接下来的CNT次在断点处不会停止执行,在CNT+1次是停在断点处

(gdb) ignore 1 3

Will ignore next 3 crossings of breakpoint 1.

6.9 随时停止程序执行

如果没有设置断点或者想立即停止程序的执行,可以使用Ctrl+Z(SIGTSTP信号),程序停止之后可以执行各种操作,操作完成之后通过使用”signal 0”命令使程序恢复执行(可能需要多次执行该命令,直到没有出现”Program received signal SIGTSTP, Stopped (user).”为止)。

7 使用循环遍历打印链表或数组的值

基本用法可参看“4设置某些临时变量”。用于调试时查看链表或数组的内容,比如进程环境中有链表A,g_pstHead为头节点,节点的类型为

typedef struct A

{

int s32Val;

*pNext;

}A_ST;

以下为遍历打印链表内容:

(gdb) set var $PSTTMP=g_pstHead

(gdb) while($PSTTMP->pNext != 0) //指针为NULL此处必需用0代替,否则出现No symbol "NULL" in current context.

(gdb) p *$PSTTMP

(gdb) set var $PSTTMP=$PSTTMP->pNext

(gdb) end

也可在循环中嵌套循环打印链表内容(一般用于链表节点的指针指向另一个链表):

(gdb) set var $PSTYMD=(OPEN_YMD_LIST_ST *)g_astFileListCtrl[0].pstList->pNext

(gdb) while($PSTYMD != 0)

 >p *$PSTYMD

 >set var $PSTHMS=(OPEN_HMS_LIST_ST *)$PSTYMD->pstHMSlist->pNext

 >while($PSTHMS != 0)

  >p *$PSTHMS

  >set var $PSTHMS=$PSTHMS->pNext

  >end

 >set var $PSTYMD=$PSTYMD->pNext

 >end

$5 = {u8Year = 119 'w', u8Month = 1 '\001', u8Day = 15 '\017', pNext = 0x83108a0, pstHMSlist = 0x8310850, pstLast = 0x8310890}

$6 = {u8Hour = 9 '\t', u8Minute = 57 '9', u8Second = 22 '\026', pNext = 0x8310870}

$7 = {u8Hour = 9 '\t', u8Minute = 57 '9', u8Second = 10 '\n', pNext = 0x8310880}

$8 = {u8Hour = 9 '\t', u8Minute = 56 '8', u8Second = 6 '\006', pNext = 0x8310890}

$9 = {u8Hour = 9 '\t', u8Minute = 55 '7', u8Second = 58 ':', pNext = 0x0}

$10 = {u8Year = 119 'w', u8Month = 1 '\001', u8Day = 14 '\016', pNext = 0x0, pstHMSlist = 0x83108b8, pstLast = 0x8310908}

$11 = {u8Hour = 17 '\021', u8Minute = 12 '\f', u8Second = 19 '\023', pNext = 0x83108d8}

$12 = {u8Hour = 17 '\021', u8Minute = 12 '\f', u8Second = 7 '\a', pNext = 0x83108e8}

$13 = {u8Hour = 17 '\021', u8Minute = 10 '\n', u8Second = 55 '7', pNext = 0x83108f8}

$14 = {u8Hour = 17 '\021', u8Minute = 10 '\n', u8Second = 43 '+', pNext = 0x8310908}

$15 = {u8Hour = 17 '\021', u8Minute = 9 '\t', u8Second = 35 '#', pNext = 0x0}

打印数组的内容则简单很多,执行命令的形式为:p *array@len,array为数组名,len为打印数组元素的个数(当然不能越界,可以借助sizeof(A)/sizeof(A[0])),

(gdb) set var A={1,2,3,4,5}                                                      

No symbol "A" in current context.

(gdb) set var $A={1,2,3,4,5}

(gdb) p *$A

$43 = 1

(gdb) p *$A@sizeof($A)/sizeof($A[0])

$44 = {1, 2, 3, 4, 5}

(gdb) p sizeof($A)/sizeof($A[0])

$45 = 5

(gdb)

8 多个命令合并成一个函数

一次要执行多条命令以达到调试的目的,而且会多次重复类似操作,则可以将多个命令合并成一个函数,提高效率。

定义函数:

(gdb) def funcName //funcName即为要定义函数的名字

Type commands for definition of "showList".

End with a line saying just "end".

(gdb) cmd1 //第一条命令

(gdb) cmd2 //第二条命令

...

(gdb) end //函数定义以end结尾

例如“7 使用循环遍历打印链表或数组的值”中使用循环打印链表内容可以定义一个函数:

(gdb) def showList

Type commands for definition of "showList".

End with a line saying just "end".

>set var $PSTYMD=(OPEN_YMD_LIST_ST *)g_astFileListCtrl[0].pstList->pNext

>while($PSTYMD != 0)

 >p *$PSTYMD

 >set var $PSTHMS=$PSTYMD->pstHMSlist->pNext

 >while($PSTHMS != 0)

  >p *$PSTHMS

  >set var $PSTHMS=$PSTHMS->pNext

  >end

 >set var $PSTYMD=$PSTYMD->pNext

 >end

>end

(gdb) showList

$26 = {u8Year = 119 'w', u8Month = 1 '\001', u8Day = 15 '\017', pNext = 0x0, pstHMSlist = 0x830e850, pstLast = 0x830e8c0}

$27 = {u8Hour = 14 '\016', u8Minute = 4 '\004', u8Second = 3 '\003', pNext = 0xb5900468}

$28 = {u8Hour = 14 '\016', u8Minute = 2 '\002', u8Second = 17 '\021', pNext = 0x830e860}

$29 = {u8Hour = 13 '\r', u8Minute = 48 '0', u8Second = 8 '\b', pNext = 0x830e870}

$30 = {u8Hour = 13 '\r', u8Minute = 48 '0', u8Second = 1 '\001', pNext = 0x830e880}

$31 = {u8Hour = 13 '\r', u8Minute = 46 '.', u8Second = 35 '#', pNext = 0x830e890}

$32 = {u8Hour = 13 '\r', u8Minute = 46 '.', u8Second = 23 '\027', pNext = 0x830e8a0}

$33 = {u8Hour = 13 '\r', u8Minute = 28 '\034', u8Second = 54 '6', pNext = 0x830e8b0}

$34 = {u8Hour = 13 '\r', u8Minute = 28 '\034', u8Second = 42 '*', pNext = 0x830e8c0}

$35 = {u8Hour = 13 '\r', u8Minute = 24 '\030', u8Second = 48 '0', pNext = 0x0}

9 单步执行代码行、单步执行函数、跳跃执行

9.1 单步执行代码行、单步执行函数

在函数入口处设置断点,形如:

(gdb) b func_name //func_name为函数名称

Breakpoint 1 at 0x142eb0: filea.c, line 222.

此时程序运行至断点处自动停止,此时可单步运行,

(gdb) step //执行函数内部的一行源代码

(gdb) next //执行函数的一行源代码,如果该行为函数调用则直接执行完整个函数

(gdb) c //继续执行程序,直到下一个断点或者程序执行完毕

如果进入某个函数执行了若干行代码之后想一次执行完接下的代码并退到上一级调用点则可以执行(gdb) finish,如果进入某个函数执行了若干行代码之后不想执行之后的想直接退到上一级调用点则可以执行(gdb) finish,不过在执行过程中如果遇到有断点则会停在断点处。

9.2 跳跃执行

使用”jump 行号”形式可以实现跨过部分代码行的执行,一般配合断点一起使用,类型goto语句,形如:

(gdb) jump 8996 //直接跳到当前文件的8996行处并继续执行,直到下一个断点处或程序结束,此时在8996行处设置一个断点则执行完8996行代码之后停止8997行代码处。

10 控制多线程的执行与切换

情况1:两个线程,正常情况下线程A会获取变量C,变量C由线程B动态改变,默认情况下,在线程A处设置断点(默认情况在断点处所有线程会被阻塞)并改变变量C的值时,可能不会获得预期值,因为A、B两个线程同时唤醒并执行。此时使用:(gdb) set scheduler-locking on,关掉线程的调度,则继续执行时除了A线程之外的线程不会被唤醒,此时即可成功设置变量C为指定的值。

set scheduler-locking off|on|step ,off不锁定任何线程,也就是所有线程都执行,这是默认值。 on只有当前被调试程序会执行。 step在单步的时候,除了next过一个函数的情况(熟悉情况的人可能知道,这其实是一个设置断点然后continue的行为)以外,只有当前线程会执行。

10.1 查看当前有多少个线程

info threads

11 监测变量的改变

如果想在变量的值有改变(写)时控制程序的运行,则可以通过该方法,

(gdb) watch g_A //g_A一般为全局变量或者当前环境的有效变量

该方式可以理解为设置断点并且当条件成立时则断点被使能,如果想在变量的值被读取时控制程序的运行,则可以通过该方法,

(gdb) rwatch g_A //g_A一般为全局变量或者当前环境的有效变量

如果想变量的值被读或者写时控制程序的运行,则可以通过该方法,

(gdb) awatch g_A //g_A一般为全局变量或者当前环境的有效变量

11.1 列出当前所有的watchpoint

可以执行info watch/watchpoints查看当前所有的监控点,也可以执行info b将当前所有的断点信息显示出来(watchpoint可以看做为特殊的断点)。

11.2 删除指定的watchpoint

和删除指定的断点一样(watchpoint可以看做为特殊的断点),执行delete N,N为指定的断点编号。

12 执行到指定的代码行

一般用于单步调试时,函数中被调试的代码前面有循环语句,此时可以使用until line命令让程序执行到指定的代码行(在该行处停止,该行代码并不执行),如下:

(gdb) b main

Breakpoint 1 at 0x13470: file temp.c, line 3410.

(gdb) r

Starting program: /tmp/temp

[Thread debugging using libthread_db enabled]

Using host libthread_db library "/lib/libthread_db.so.1".

Breakpoint 1, main () at temp.c:3410

3410            open_initTempVal_func(&g_astDiskCtrl[0]);

(gdb) until 3414 // 3414即为指定的代码行

main () at temp.c:3414

3414            DPRINTF_BLUE("going to open_initAVBuf_func!");

(gdb)

13 更改默认打印长度

通过以下命令可以查看:

(gdb) show print elements

Limit on string chars or array elements to print is 200.

可以看到默认只会打印200个字符。

可以通过命令:

(gdb) set print elements 0

(gdb) show print elements

Limit on string chars or array elements to print is unlimited.

使打印的字符串长度不受限制。当然这里你也可以设置一个你需要的合理值。

例如:

(gdb) set print elements 300

(gdb) show print elements

Limit on string chars or array elements to print is 300.

14 执行shell命令

当gdb暂停执行时,可以执行shell,使用方式如下:

(gdb) shell ps aux //查看当前所有执行的程序

(gdb) ! ps aux //这里的’!’等价于上一条语句中的’shell’

15 给main函数传入参数

通过show args命令可以查看当前传入的参数。

方法1:使用--args参数,gdb --args ./a 123 444 abc,./a程序的带3个参数”123”、”444”、”abc”。

方法2:在run后面直接带上要传递的参数,run 123 444 abc,./a程序的带3个参数”123”、”444”、”abc”。

方法3:使用set args命令传递参数,以后执行run时会使用这些参数,set args 123 444 abc,./a程序的带3个参数”123”、”444”、”abc”。

16 当前执行代码行甄别

在调试过程中,gdb会显示即将被执行的代码行,形如:

(gdb) b temp.c:3414

Breakpoint 1 at 0x1347c: file temp.c, line 3414.

(gdb) r

Starting program: /tmp/temp

[Thread debugging using libthread_db enabled]

Using host libthread_db library "/lib/libthread_db.so.1".

Breakpoint 1, main () at temp.c:3414

3414            DPRINTF_BLUE("going to open_initAVBuf_func!");

(gdb) next

(temp.c,main,3414): going to open_initAVBuf_func!

3415         open_initAVBuf_func(&g_astDiskCtrl[0]);

(gdb)

17 gdb或者valgrind调试时显示的行号与编辑器里面文件的行号不匹配

目前发现该问题是由于Windows与Linux处理换行不一致引起,一般出现在函数末尾的换行或者调用打印字符串函数的时候。

处理方式:通过vi或者vim查看.c及.h文件,如果出现^M则将其删除。由于^M为不可见字符,所以在编辑器中看不到,在vi或vim下也搜索不到,但是可以通过在vim时在串口终端中搜索^M找到。能对应上的行号说明在此之前没有^M,由此可以缩小查找范围。

18 其他

gdb其他还有很多功能,比如监视变量的变化(watch),多线程调试等等。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ta是一个搬运工

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值