GDB 全名为 Global Project DeBug,它可以用来检视系统执行档所执行的语法,以及记忆体地址所存的资料,可以用来 Debug ,反组译,Hack 执行档等等,Linux 与 Windows 系统皆可以使用这套软体。
GDB 有以下数种功用
- Debug
- 反组译
- Hack 执行档
对於一个已经编译好的执行档,因为我们没有他的原始程式码,无法直接参透程式的内容,而 GDB 能够将执行档的程式转成组语 (assemble) ,只要你看得懂组语,就能够猜出原始的程式码大约会是什么。
如何开始使用 GDB
首先,我们先写一段简单的 c 语言,并正确的编译 g++ -g main.cc
,请注意一定要加 -g 这个参数, -g 代表 debug 模式,使用 GDB 时会更加好用。
- #include "stdio.h"
- int main () {
- char const *message = "How are you?";
- printf("%s", message);
- return 0;
- }
编译完成后,会产生一个叫 a.out 的档案,接著我们用 gdb a.out 就可以开始使用 GDB 罗,下面有执行的范例。
- # gdb a.out
- GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-64.el7
- Copyright (C) 2013 Free Software Foundation, Inc.
- License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
- This is free software: you are free to change and redistribute it.
- There is NO WARRANTY, to the extent permitted by law. Type "show copying"
- and "show warranty" for details.
- This GDB was configured as "x86_64-redhat-linux-gnu".
- For bug reporting instructions, please see:
- <http://www.gnu.org/software/gdb/bugs/>...
- Reading symbols from a.out...(no debugging symbols found)...done.
- (gdb)
GDB 基本指令
[disass] display assemble 列印出组语程式码
- disass main
- disass /r main
- disass 0x400530,0x400550 (disass start,end)
disass 后面可以接 function 的名称,当然你要知道有什么程式里面有什么 function 名称,一般来说 C 语言里面都会有个 main function ,所以我们可以输入 disass main 来检视 main 底下的组语内容。
disass -r
,加上 -r 这个参数,可以把组语的内容用 hex 的方式印出来。
disass -m
,加上 -m 这个参数,可以印出程式原始码内容,如果你想要这个功能,就得在编译时先加上 debug 的参数。
disass 0x400530,0x400550
,如果你看某一个区间的组语内容,可以用 disass start address,end address , GDB 就会自动印出两段 memory address 之间的所有组语内容。
来看一下 disass 执行的结果:
- (gdb) disass main
- Dump of assembler code for function main():
- 0x00000000004005f0 <+0>: push %rbp
- 0x00000000004005f1 <+1>: mov %rsp,%rbp
- 0x00000000004005f4 <+4>: sub $0x10,%rsp
- 0x00000000004005f8 <+8>: movq $0x4006b0,-0x8(%rbp)
- 0x0000000000400600 <+16>: mov -0x8(%rbp),%rax
- 0x0000000000400604 <+20>: mov %rax,%rsi
- 0x0000000000400607 <+23>: mov $0x4006bd,%edi
- 0x000000000040060c <+28>: mov $0x0,%eax
- 0x0000000000400611 <+33>: callq 0x4004d0 <printf@plt>
- 0x0000000000400616 <+38>: mov $0x0,%eax
- 0x000000000040061b <+43>: leaveq
- 0x000000000040061c <+44>: retq
- End of assembler dump.
[b] breakpoint 设定程式暂停点
breakpoint 可以任何设定暂停点,通常我们会在文字比较 (strncmp) 的时候,暂停程式,检查两个文字的内容,分别是什么,breakpoint 有两种缩写可以使用, [break] & [b]。
b main
, 当程式执行到 main 这个 function 时,程式会暂停
b *0x00000000004005f0
, 当程式执行到这个记忆体 address 时,程式会暂停
info b
, 列出所有设定过的 breakpoint
delete 1
, 移除第一个 breakpoint ,你必需先用 info b 来看看目前有哪些 breakpoint ,然后再用 delete 1 2 3 来移除 breakpoint 。
来看看 breakpoint 的执行过程:
- (gdb) b main
- Breakpoint 5 at 0x4005f8: file main.cc, line 5.
- (gdb) b *0x00000000004005f0
- Breakpoint 6 at 0x4005f0: file main.cc, line 4.
- (gdb) info b
- Num Type Disp Enb Address What
- 5 breakpoint keep y 0x00000000004005f8 in main() at main.cc:5
- 6 breakpoint keep y 0x00000000004005f0 in main() at main.cc:4
- (gdb) delete 5 6
[si] stepi 执行一行组语
stepi 的缩写是 si , stepi 代表只执行一行组语指令,每执行一行就会自动暂停。
- (gdb) b main
- (gdb) run
- .
- .
- (gdb) si
- 0x000000000040060c in main ()
- (gdb) si
- 0x0000000000400611 in main ()
- (gdb) si
- 0x00000000004004d0 in printf@plt ()
[ni] nexti 执行一行组语
nexti 的缩写是 [ni] , next 跟 stepi 很像,也是执行一行组语,但不同的是 ,如果 nexti 遇到要 call 另外一个 function ,那么它会直接执行到该 function 结束,也就是说 nexti 不会暂停在其它的 function 。
[n] next 执行到下一行 source code
next 的缩写为 n ,程式码中的每一行都可以对应到组语中的其中一段程式,有可能一行程式码对到十行组语,如果你使用 [si] 或 [ni] 都只能一行执行一行组语,而若是我们想直接执行一个程式码,就可以使用 next ,有一点要特别注意的是,当你 compile 程式码的时候要使用 g++ -g
,开始 debug mode 才能用这个功能 。
- (gdb) b main
- (gdb) run
- .
- .
- (gdb) n
- 5 char const *message = "How are you?";
- (gdb) n
- 6 printf("%s", message);
[c] continue 执行到下一行 breakpoint
continue 的缩写是 c ,功能很容易懂,就会直接执行直到下一个 breakpoint ,当然你要记得先设定 breakpoint ,例如 b *0x00000450400
watch 观察特定变数
watch 可以用来侦测那个变数的值有被修改,当指定的变数被更改时,程式会暂停,并印出更改前后的数值,这个功能一样要打开 debug 模式才能使用 (g++ -g)。
watch str
, 观察变数 str
watch (t > 10)
, 观察变数 t 是否大於 10
来看看 watch 的执行过程:
- (gdb) b main
- (gdb) run
- .
- .
- (gdb) watch message
- Hardware watchpoint 2: message
- (gdb) c
- Continuing.
- Hardware watchpoint 2: message
- Old value = 0x0
- New value = 0x4006f4 "How are you?"
- main () at main2.cc:13
- 13 printf("%s", message);
frame
所有的程式,每一个 function 都会被分配到一个 frame ,每个 frame 都是一个 组语 stack ,存放所有组语指令,然后再一行一行的执行,例如当程式执行到 printf 这个 function 的时候,就会进入该 printf frame 。
frame 1
, 进入 frame 1
up
, 进入上一个 frame
down
, 进入下一个 frame
bt
, backtrace , 列出目前所有的 frame
来看看 frame 的执行过程:
- (gdb) bt
- #0 0x00000000004004d0 in printf@plt ()
- #1 0x0000000000400616 in showPassword () at main2.cc:5
- #2 0x0000000000400625 in main () at main2.cc:11
- (gdb) frame 1
- #1 0x0000000000400616 in showPassword () at main2.cc:5
- (gdb) down
- #0 0x00000000004004d0 in printf@plt ()
- (gdb) up
- #1 0x0000000000400616 in showPassword () at main2.cc:5
print : 印出某个变数或 memory address 的数值
- (gdb) print x
- $1 = 0
printf : 一次印出两个以上的变数
- (gdb) print "%d,%d\n",x,y
- 5,2
list 显示目前程式执行到那一行
要使用这个功能的前提是你必须在编译的时候,带 -g 这个参数,
- (gdb) list
- 1 #include "stdio.h"
- 2 int main () {
- 3 char const *message = "How are you?";
- 4 printf("%s", message);
- 5
- 6 return 0;
- 7 }
display : 当碰到 breakpoint 就印出某些个变数或 memory address 的数值
until : 执行完当前的回圈
finish : 执行完当前的 function
GDB test specific process
- sudo gdb -p 21611(process id)
- b file.cc:277 (break point)
- c (continue)
- bt
- ///usr/bin/strace -Ttt -s 1000 -p 6
dump void *
x/20c (*r->useragent_addr)->ipaddr_ptr
使用 nm 查询 function 在哪一个 shared object 里:
nm -C -A *.so | grep xxx_function
其它
- x/i $pc 用 hex 印出目前位置
- x/20c $pc
- set $pc=0x0804852b : 修改当前的 memory address ,可以在 call xxx 时使用。
- info frame
- info stack
- info functions 列出所有 function
- set {int}0x8048667=32 : 修改 memory value
相关文章
- GDB online documentation : https://sourceware.org/gdb/onlinedocs/gdb/
- disass : https://sourceware.org/gdb/onlinedocs/gdb/Machine-Code.html
- GDB Project : https://www.gnu.org/software/gdb/
- Reverse Debugging: https://www.gnu.org/software/gdb/news/reversible.html
回應 (Leave a comment)