从零开始学GDB
启动gdb后,可以进入到交互模式,通过以下命令对程序完成调试工作!
g++ -g -std=c++11 main.cpp gdb a.out
运行指令
指令 | 说明 |
---|---|
run(简写r) | 运行程序,当遇到断点后,程序会在断点处停止运行,等待用户输入下一步命令 |
continue(简写c) | 继续执行,到下一个断点停止(或运行结束) |
next(简写n) | 单步跟踪程序,当遇到函数调用时,也不进入此函数体;此命令同 step 的主要区别是,step 遇到用户自定义的函数,将步进到函数中去运行,而 next 则直接调用函数,不会进入到函数体内。 |
step(简写s) | 单步调试如果有函数调用,则进入函数;与命令n不同,n是不进入调用的函数的 |
until(简写u) | 当你厌倦了在一个循环体内单步跟踪时,这个命令可以运行程序直到退出循环体。 |
until+行号 | 运行至某行,不仅仅用来跳出循环 |
finish | 运行程序,直到当前函数完成返回,并打印函数返回时的堆栈地址和返回值及参数值等信息。 |
call函数(参数) | 调用程序中可见的函数,并传递“参数”,如:call gdb_test(55) |
quit(简写q) | 退出gdb |
带参运行
进入调试之前可以 gdb --args ./grogram arg1 arg2
进入调试之后可以两种方式添加参数
1)run arg1 arg2
2)set args arg1 arg2
设置断点
指令 | 说明 |
---|---|
break n (简写b n) | 在第n行处设置断点(可以带上代码路径和代码名称: b OAGUPDATE.cpp:578) |
break func(break缩写为b) | 在函数func()的入口处设置断点,如:break cb_button |
b fn1 if a>b | 条件断点设置 |
b *0x12345678 |
内存地址下断(代码段地址) |
delete 断点号n | 删除第n个断点 |
disable 断点号n | 暂停第n个断点 |
enable 断点号n | 开启第n个断点 |
clear 行号n | 清除第n行的断点 |
info b (info breakpoints) | 示当前程序的断点设置情况 |
delete breakpoints | 清除所有断点: |
watch *(int*) 0x22cbc0 |
监视内存地址写操作 |
查看内存
x/20xb 0x7ffff7ff6000 |
查看栈 x/20 $sp
查看格式 | 说明 |
---|---|
x | 按十六进制格式显示变量。 |
d | 按十进制格式显示变量。 |
u | 按十六进制格式显示无符号整型。 |
o | 按八进制格式显示变量。 |
t | 按二进制格式显示变量。 |
a | 按十六进制格式显示变量。 |
c | 按字符格式显示变量。 |
f | 按浮点数格式显示变量 |
数据长度 | 说明 |
---|---|
b | 表示单字节 |
h | 表示双字节 |
w | 表示四字节 |
g | 表示八字节 |
查看汇编
指令 | 说明 |
---|---|
disas/disass | 查看汇编代码 |
display /i $pc | 单步调试显示汇编指令 |
set disassemble-flavor intel | 切换intel格式 |
set disassemble-flavor att | 切换att格式 |
layout asm | 可视化汇编调试 |
continue(c) | 放行 |
s | 代码级步入 |
n | 代码级步过 |
si | 汇编级步入 |
ni | 汇编级步过 |
i reg | 查看所有寄存器 |
i reg rax | 查看rax寄存器 |
display \(rax|监视rax| |p (char*)\)eax | 查看字符串 |
正确的命令是:
sudo echo "set disassembly-flavor intel"> ~/.gdbinit
GDB 在启动的时候会按一定的路径顺序(通常是先当前目录而后用户目录)寻找 .gdbinit 文件,一旦找到,就会自动执行里面的命令。这个功能允许用户把常用的一些命令放在这个文件里,这样就不用每次进入 gdb 后再去手动执行这些命令。事实上,.gdbinit 就是一个脚本,甚至可在里面把常用的若干 gdb命令序列定义成一个新命令,这样只要在 gdb 里面输入这个新命令就等于自动执行了被定义的那个命令序列。
查看源代码
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 | 显示当前堆栈页的所有变量 |
實時顯示表達式
和 print 命令一样,display 命令也用于调试阶段查看某个变量或表达式的值,它们的区别是,使用 display 命令查看变量或表达式的值,每当程序暂停执行(例如单步执行)时,GDB 调试器都会自动帮我们打印出来,而 print 命令则不会。
也就是说,使用 1 次 print 命令只能查看 1 次某个变量或表达式的值,而同样使用 1 次 display 命令,每次程序暂停执行时都会自动打印出目标变量或表达式的值。因此,当我们想频繁查看某个变量或表达式的值从而观察它的变化情况时,使用 display 命令可以一劳永逸。
(gdb) display expr |
/fmt | 功 能 |
---|---|
/x | 以十六进制的形式打印出整数。 |
/d | 以有符号、十进制的形式打印出整数。 |
/u | 以无符号、十进制的形式打印出整数。 |
/o | 以八进制的形式打印出整数。 |
/t | 以二进制的形式打印出整数。 |
/f | 以浮点数的形式打印变量或表达式的值。 |
/c | 以字符形式打印变量或表达式的值。 |
查询运行信息
指令 | 说明 |
---|---|
where/bt | 当前运行的堆栈列表; |
bt backtrace | 显示当前调用堆栈 |
up/down | 改变堆栈显示的深度 |
set args 参数 | 指定运行时的参数 |
show args | 查看设置好的参数 |
info program | 来查看程序的是否在运行,进程号,被暂停的原因。 |