本系列收录于基础理论专栏。
启动和退出GDB
GDB(GNU Project Debugger)几乎适用于所有类Unix系统,小巧方便且不失功能强大,Linux/Unix程序员经常用它来调试程序。
总的来说有几下几种方法启动GDB
gdb(不加任何参数),如果不希望打印授权信息加上-silent参数
gdb 可执行文件
gdb 可执行文件的core文件
gdb 正在运行的进程之pid
要退出GDB则有两种方法
quit
ctrl+d
命令表(记忆)
- 断点相关命令
GDB命令 | 参数 | 意义 | 常用示例 |
---|---|---|---|
break | 地址 | 下断点,可简写为b。地址类型包括:函数名、源文件行号、*内存地址 | break main、break 12、break *0x08048373 |
watch | 表达式 | 表达式的值被改变程序将立即停止运行 | watch *((int*)0x80d1ba8) |
clear | 地址 | 和break相反,清除指定地址上的断点 | clear main、clear 12、clear *0x08048373 |
info | break | 显示断点信息,包括所有断点的编号、种类、使能状态、地址以及位置 | info break |
disable | 断点编号 | 禁用一个断点 | disable 1 |
enable | 断点编号 | 启用一个被禁用的断点 | enable 1 |
delete | 断点编号 | 删除一个断点,可简写为d | delete 1 |
- 执行相关命令
GDB命令 | 参数 | 意义 | 常用示例 |
---|---|---|---|
run | 命令行参数 | 运行程序,可简写为r | run vuln |
attach | 进程号 | 调试已运行的进程 | attach 1022 |
continue | 次数(可选) | 继续执行,可简写为c | c 4 |
next | 次数(可选) | 单步(不进入函数调用),可简写为n | n 4 |
step | 次数(可选) | 单步(进入函数调用),可简写为s | s 4 |
unitl | 源文件行号 | 执行到执行地址后中断,可简写为u | u 18 |
finish | - | 运行当前函数直到函数退出 | - |
return | - | 立即退出当前函数 | - |
- 信息查看相关命令
GDB命令 | 参数 | 意义 | 常用示例 |
---|---|---|---|
info | reg、break、file、args、frame、functions | 显示各种信息,info可简写为i | info reg |
backtrace | 帧的数目 | 显示当前函数调用栈信息,可简写为bt | bt |
/f exp,其中f表示修饰,exp为表达式,print可简写为p | 显示表达式的值,格式有:x(十六进制)、c(字符)等,print可简写为p | p/c 0x41、p/x 1024、p str、p/x $eax | |
x | /nfu addr,其中n表示个数、f表示格式、u表示单元大小,如果没有制定地址则接着上一次x命令显示之后的地址 | 显示指定地址内容,格式有:x(十六进制)、s(字符串)、i(指令)等,单元大小有b、h、w、g、b为一个字节,依次比前一个大一倍 | x/4i $pc、x/16xb $sp、x/s *(argv+1)、x/s 0xbffffc52 |
list | 行号、函数或地址 | 如果调试的是带符号编译的程序,那么list命令可以列出程序源码,list可简写为l | l file.c:19 |
disass | 函数名 | 反汇编指定函数,默认为当前函数 | disass main |
- 其它常用命令
GDB命令 | 参数 | 意义 | 常用示例 |
---|---|---|---|
set | set的参数非常多,具体参见help set | 设置值 | set var=4、set {int}0xbffffc52=50、set {int}($esp+4)=$eip |
shell | 外部shell命令 | 执行外部shell命令 | shell ps -ef |
比较常用的命令如p
、x
、disass
、break
、si
、ni
、c
、finish
、set
,美中不足的是GDB没有内置搜索内存的功能,我们可以自定义一个宏脚本并保存在用户目录的.gdbinit
文件里即可。
[已解决] 为什么我的Mac无法正常使用GDB
这是因为 Darwin 内核在你没有特殊权限的情况下,不允许调试其它进程。调试某个进程,意味着你对这个进程有完全的控制权限,所以为了防止被恶意利用,它是默认禁止的。允许 gdb 控制其它进程最好的方法就是用系统信任的证书对它进行签名。
首先打开钥匙串app
open /Applications/Utilities/Keychain\ Access.app
接着按照下图创建自签名根证书
接下来录一路点击确定,直到出现下面的画面
要求你输入用户的账号密码
回到起初打开的钥匙串访问程序,找到新建的证书选择永远信任
最后输入命令并重启
codesign -s gdb_codesign gdb && reboot
从最简单的程序开始吧
写一个简单的C语言源程序
使用gcc编译器将源文件编译成可执行文件。加-g选项是为了便于调试,否则之后在导入gdb时会找不到调试标记(debugging symbol)。
概选项的作用主要有二:
创建符号表,符号表包含了程序中使用的变量名称的列表
关闭所有的优化机制,以便程序执行过程中严格按照原来的C代码进行
gcc -g cc.c -o cc
启动GDB
gdb
导入相应的可执行文件,gdb进行读取,出现reading…done则表明成功读取。
file cc # 既可以跟相对路径,也可以跟绝对路径
不妨查看下源文件(编译前)的行号信息。
shell nl cc.c # 这样其实麻烦了,可以直接敲list或者简写l
我们在第一个打印后设置断点。注意断点是程序暂停的那一行,程序中止时是没有运行到断点所在行的(运行到断点所在行的上一行)。
break 7 # 7是源文件的行号
然后运行该文件,直到第一个断点处程序暂停。
run
如果要继续运行,使用continue命令
continue
根据提示信息,程序已经正常退出。而本次程序运行设置的断点也被自行销毁,下次必须重新设置断点。
还是上面的那个程序,下面让我们放慢节奏看看它的内部细节。首先在源程序的第七行设置断点,然后运行它。
你的第二个调试程序
我加上了循环、函数调用的概念。首先还是用vim写一个C源文件。很容易的,为了和后面的调试形成参照,这里我想不加修饰的将该文件运行看看。
接着启动gdb,这次你懂得多了可以直接敲
gdb cc
接着查看带标号的程序源码。如果你使用不加参数的list命令,它将在第一个遇到的函数调用处中止。(这省却了对源文件的维护)
运行一遍该程序,没有问题。
但是我们的目的不在此,我们是要用它来学习的,所以即使程序运行没问题,还是下个断点,就定在源文件的第11、12、16行好了。
看一下断点信息。
然后运行并调试程序,当遇到第一个断点时程序会自动停下。这里也就是在第10行运行结束的位置停下了,敲p a
来查看变量a的当前值,显示为1。
因为我之前在12行设置了断点,所以这时候直接c
继续运行程序即可。如果没有设置断点,也可以敲n
来单点步入(不进入函数)。
由于停在11行,而实际上第11行尚未执行,所以打印变量a的值仍然是1。注意到循环变量i的值这时候已经被初始化为0了。
这里我们使用命令s
进行单点步入(进入函数)。随着循环变量的递增,可以清晰的看到随着i的变化a也发生着变化。这里的代码结构是循环套函数,如果你使用两个连续的s
命令,将进入调用的函数内部,而这里只是进入了循环的内部。
我们选择继续运行,直到遇到下一个断点(位于第16行)。
注意到此时之前的循环变量的值还没有销毁,等于3。
好吧,继续运行直到程序正常退出。结束。