gdb 指令的主要目的是让你能知道一个程序执行时内部做了什么。
gdb 可以做4类事情来帮助你找到 bug。
1.运行程序,指定一些东西来影响程序的行为。
2.通过指定条件让程序暂停。
3.检测在程序停止时发生了什么。
4.改变一些东西,可以让因为 bug 而出问题的程序正常的执行下去。
一般如果我们要调试 c 程序,我们最好关闭各种优化。
注意:调试的时候出现莫名其妙的状况一般都是没关闭优化导致的,请仔细检查有没带上 -O0 编译选项。
多进程调试技巧:
1.让每个进程都调用一个函数,函数里调用 sleep(xxxx),然后让每个gdb都绑定到特定的 pid 上,然后再强制返回跳过 sleep。
-O0
-O1
-O2
-O3
编译器的优化选项的4个级别,-O0表示没有优化,-O1为缺省值,-O3优化级别最高
-g
只是编译器,在编译的时候,产生调试信息。
-g3 包含了额外信息,比如宏命令支持。
-static
此选项将禁止使用动态库,所以,编译出来的东西,一般都很大,也无需什么
动态连接库,就能够运行.
-share
此选项将尽量使用动态库,所以生成文档比较小,但是需要系统由动态库.
一般我们需要生成调试信息,最好类似如下编译
gcc -O0 -g3 main.c
gdb -h
--args 指定程序的后面可以携带参数
--cd=DIR 改变当前工作目录
--core=COREFILE 指定 core dump 文件
--pid=PID 调试特定进程
-h/--help 帮助
--quiet Do not print version number on startup.
--se=FILE Use FILE as symbol file and executable file.
--symbols=SYMFILE Read symbols from SYMFILE.
-tui 界面模式调试
--version Print version information and then exit.
#普通调试
gdb program
#同时指定 core dump 文件
gdb program core
#通过进程id调试特定的进程,gdb会先查找 core 文件
gdb program pid
#直接调试进程
gdb --pid 1234
gdb attach 1234
#指定调试程序的参数
gdb --args a.out -l 3 -b 2
#当前文件指定行号
b 12
#指定函数
b func
#查看断点
info b
指定特定文件
b file:func
b file:12
#指定条件
b if i=100
#删除所有断点
d
#通过断点号删除特定断点
d 1
假设监控的是一个指针,那么指的的指针本身而不是指针指向的内容,如果想监控指针指向的内容,那么可以用 * 符号来反解析。
#当变量 i 改变的时候暂停
watch i
#当变量 i>0 是暂停
watch i>0
#当变量 i 被读的时候暂停
rwatch i
#当变量 i 被读或者写的时候暂停
awatch i
#Watchpoint 4 deleted because the program has left the block in xxxx
#防止监控点自动删除
watch -location i
info watchpoints
通过观测点序号来删除禁用启用观测点
delete 2
disable 2
enable 2
一般来说,list 后面可以跟一下这些参数
<linenum> 行号。
<+offset> 当前行号的正偏移量。
<-offset> 当前行号的负偏移量。
<filename:linenum> 哪个文件的哪一行。
<function> 函数名。
<filename:function> 哪个文件中的哪个函数。
<*address> 程序运行时的语句在内存中的地址。
#查看当前行后的代码
l
#显示行号为3周围代码
l 3
#显示特定范围的代码
l 3,10
#显示函数 func 代码
l func
#查看当前 listsize 的设置
show listsize
#设置显示的行数
set listsize 10
layout src #类似于gdb -tui
layout asm
layout split
layout reg
ctrl+L
#修改断点号为 bnum 的停止条件为 expression
condition <bnum> <expression>
#删除断点号 bnum 的停止条件
condition <bnum>
#忽略断点号为 bnum 的停止条件 count 次
ignore <bnum> <count>
#在函数 foo 内部,当 i > 0是打印i,并继续运行
break i if i>0
commands
printf "%d\n",i
continue
end
#continue 继续运行直到程序结束或者遇到断点
c
#step 单步进入,会进入到函数内部
s
#next 单步
n
#finish 直到函数返回
f
# until 直到循环返回
u
#强制函数返回 0
return 0
handle <signal> <keywords...>
比如 SIGKILL,all 代表所有信号。keywors 可以是以下几种关键字的一个或多个。
nostop:gdb 不停住,但是会打印消息告诉你收到的这种信号。
stop:停住。
print:显示一条信息。
noprint:不显示信息。
#检查哪些信号当前被 gdb 检测中
info signals
info handle
查看线程信息
#查看线程
info thread
#切换线程
thread thread_no
#特定线程断点
b <line> thread <threadno>
follow-fork-mode detach-on-fork 说明
parent on 只调试主进程(GDB默认)
child on 只调试子进程
parent off 同时调试两个进程,gdb跟主进程,子进程block在fork位置
child off 同时调试两个进程,gdb跟子进程,主进程block在fork位置
设置方法:set follow-fork-mode [parent|child] set detach-on-fork [on|off]
查询正在调试的进程
info inferiors
切换调试进程
inferior <infer number>
当程序出现问题停止了,我们可以查看栈来确定是在哪个函数里出问题的。
#backtrace 显示栈顶 n 层。
bt <n>
#显示栈低 n 层。
bt <-n>
#frame选中栈序号为 3 的栈
f 3
#往上移动 n 层
up <n>
#往下移动 n 层
down <n>
#打印出更为详细的栈层信息
info f
#打印出当前函数参数名和值
info args
#打印当前函数所有局部变量及值
info locals
#打印当前函数中所有局部变量及值
info catch
@ 是一个和数组相关的操作符
int main(int argc, char *argv[])
{
char *arr = malloc(10 * sizeof(int));
int *tmp;
*(int *)arr = 1;
tmp = (int *)(arr+4);
*tmp = 2;
tmp = (int *)(arr+8);
*tmp = 3;
return 0;
}
(gdb) p *(int *)arr@6
$1 = {1, 2, 3, 0, 0, 0}
:: 指定文件或者函数中的变量
file::variable
function:variable
p 'a.c'::x
x 按十六进制格式显示变量。
a 按十六进制格式显示变量。
u 按十六进制格式显示无符号整型。
t 按二进制格式显示变量。
d 按十进制格式显示变量。
o 按八进制格式显示变量。
c 按字符格式显示变量。
f 按浮点数格式显示变量。
set $fo = *ptr
#查看所有设置的环境变量
show convenience
环境变量很强大,比如可以这样使用
set $x = 1
print arr[$x++]->name
这样你就只要重复执行命令可以执行类似于下面的指令,而不用一条一条敲。
print arr[1]->name
print arr[2]->name
print arr[3]->name
gdb 会以 $1,$2,$3...这样的方式对每一个 print 命令编号。
p $1
p $2
display $a
display /i $a
display /i addr
int i = 0;
i++;
i++;
i++;
set $addr = &i
display *$addr
这样每次程序暂停,都会自动显示变量 i 的值。
#通过编号,删除自动显示,多个用空格隔开,也可以用 - 指定范围
undisplay <disnum>
delete display <disnum>
#不删除,只是临时禁用或者恢复
disable display <dnum>
enable display <dnum>
#查看 display 信息
info display
#默认为off,遇到 \0 不会停止显示
set print null-stop off
set print null-stop on
#默认为 off,显示漂亮的结构体
set print pretty off
set print pretty on
一旦 gdb 挂上被调试的程序,你就可以动态的改变程序的执行思路。
带上 var 表示 x 为程序的变量而不是 gdb 的参数。
#改变变量x的值
p x=4
set var x=4
jump 命令不会改变当前程序栈的内容,所以尽量在一个函数内跳转,否则可能会出现意想不到的错误。
jump line
jump file:line
jump +4
熟悉汇编的都知道,有一个寄存器是用来保留当前执行指令的内存地址,所以 jump 命令就是改变该值。于是,你也可以这样做来跳转。
set $pc = 0x444
有时候需要模拟接收到特定信号,则可以使用如下来发送信号,信号范围为 1-15,也可以通过指定信号名如 SIGTERM。
signal 15
finish
如果是表达式,则返回表达式计算后的值
return 10
return <expr>
显示返回值,返回 void 则不显示
call func
u
#禁用路径安全监测
echo 'set auto-load safe-path /' > ~/.gdbinit
然后在当前调试目录建立 .gdbinit,这样就可以在里面设置一些配置指令比如:
file a.out
b main
set print null-stop on
set print pretty on
假设代码由汇编代码生成,或者各种调试信息都没了,此时需要用到指令调试
#intel语法
#展示汇编
#展示寄存器
set disassembly-flavor intel
layout asm
layout regs
#打断点
b *0x3fdb24
#单步调试,类似于n
ni
#单步调试,类似于s,需要知道每一步建议使用 si
si
[root@dldl lib_test]# gdb -q ls
(gdb) b _start
Breakpoint 1 at 0x5e00
(gdb) r
(gdb) info program
Using the running image of child Thread 0x7ffff7fde640 (LWP 11219).
Program stopped at 0x555555559e00.
It stopped at breakpoint 1.
Type "info stack" or "info registers" for more information.
(gdb) shell cat /proc/11219/maps | grep vdso
7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso]
(gdb) dump memory /tmp/vdso.so 0x7ffff7ffa000 0x7ffff7ffc000