linux调试命令 gdb

2018-08-26 12:55:52

gdb 指令的主要目的是让你能知道一个程序执行时内部做了什么。

gdb 可以做4类事情来帮助你找到 bug。

1.运行程序,指定一些东西来影响程序的行为。
2.通过指定条件让程序暂停。
3.检测在程序停止时发生了什么。
4.改变一些东西,可以让因为 bug 而出问题的程序正常的执行下去。

一般如果我们要调试 c 程序,我们最好关闭各种优化。

注意:调试的时候出现莫名其妙的状况一般都是没关闭优化导致的,请仔细检查有没带上 -O0 编译选项。

-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

#普通调试
gdb program

#同时指定 core dump 文件
gdb program core

#通过进程id调试特定的进程,gdb会先查找 core 文件
gdb program pid

#直接调试进程
gdb --pid 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
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

条件维护

#修改断点号为 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

处理信号

handle <signal> <keywords...>

比如 SIGKILL,all 代表所有信号。keywors 可以是以下几种关键字的一个或多个。

nostop:gdb 不停住,但是会打印消息告诉你收到的这种信号。
stop:停住。
print:显示一条信息。
noprint:不显示信息。

#检查哪些信号当前被 gdb 检测中
info signals
info handle

调试线程

b <line> thread <threadno>
b <line> thread <threadno> if ...

threadno 为gdb的线程号,可以通过

info thread

调试多进程

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

强制函数返回

如果是表达式,则返回表达式计算后的值

return 10
return <expr>

强制调用函数

显示返回值,返回 void 则不显示

call func

设置 .gdbinit 文件

#禁用路径安全监测
echo 'set auto-load safe-path /' > ~/.gdbinit

然后在当前调试目录建立 .gdbinit,这样就可以在里面设置一些配置指令比如:

file a.out
b main
set print null-stop on
set print pretty on


备注:
1.本系列命令都在centos7里测试,其他发行版如ubuntu、debian、fedora、opensuse等可能略微不同
2.本文只讲解常用用法,详细用法请自行利用 man 命令查看
3.原文地址http://www.freecls.com/a/2712/ff

 

©著作权归作者所有
收藏
推荐阅读
简介
天降大任于斯人也,必先苦其心志。