gdb学习笔记

gdb是用于调试C程序的一种工具,一般包含在gcc工具链中,

常用于调试用gcc -g编译的可执行文件。

 

1. 断点命令列表

gdb支持断点命令列表,即在断点发生时执行一连串命令。

如果断点命令列表中包含cont(continue的缩写),

则其行为很像OllyDbg的日志断点。

不过,gdb断点命令列表是针对带源码的程序调试,

而OllyDbg则是汇编级的。

 

例如,如果我用-g开关编译lua(Windows下用MinGW,Linux下用gcc)

 

 

修改Makefile,
把CC=gcc
改为CC= gcc -g
并且注释掉#$(RANLIB) $@

 

然后用gdb尝试对llex.c文件的llex静态函数进行断点并输出其参数ls的地址。

可以先写一个gdb脚本(保存为gdbinit.txt):

 

 

#
# 编译lua时修改Makefile使用-g开关
# CC= gcc -g
# 并且注释掉#$(RANLIB) $@
#
# 测试平台:
# 1. GNU gdb 6.3 for MinGW 
# 2. GNU gdb (GDB) 7.1-ubuntu
#
# 启动命令行:
# gdb --command=gdbinit.txt --quiet ./lua
#

# see
# http://ftp.gnu.org/old-gnu/Manuals/gdb-5.1.1/html_node/gdb_34.html
# Breakpoint command lists
b llex.c:llex
commands
silent
printf "ls is %p\n", ls
cont
end

# see 
# gdb-6.3\gdb\windows-nat.c
# Linux下需要注释掉
set debugevents off
set debugexceptions off

# set args -e "print('hello, world')"
run -e "print('hello, world')"
quit

 

这里指定了一个命令列表,然后用

-e "print('hello, world')"

参数执行lua脚本。

 

其中,如果用Ubuntu的gdb,则删除以下两句

 

 

set debugevents off
set debugexceptions off

 

 

 然后执行

 

 

$ gdb --command=gdbinit.txt --quiet ./lua

 

 

--command用于指定前面提到的那个gdbinit脚本

则输出如下信息:

 

 

$ gdb --command=gdbinit.txt --quiet ./lua
Breakpoint 1 at 0x40a794: file llex.c, line 333.
ls is 0022FB50
ls is 0022FB50
ls is 0022FB50
ls is 0022FB50
ls is 0022FB50
hello, world

Program exited normally.

 

 

可以看到lua在解析执行

 

 

"print('hello, world')"

 时,执行了5次llex,每次的ls指针都是相同的。

这里还可以使用更复杂的表达式,然后用gdb自带的printf输出到控制台上。

 

 

20120411补注:

网上有许多写得很复杂的gdb脚本,例如这个:

https://github.com/mkottman/lua-gdb-helper

可以参考,不过我自己没有测试过。

 

----------------------

 

 

 

 

2. 基于二进制地址的断点设置(仅适用于mingw带源码编译的调试版exe)

如果用mingw编译的exe,可以用nm命令查找符号所在地址。

用这种方法,不需要指定函数所在的文件名。

$ nm ./lua.exe | grep llex

0040a780 t _llex

然后在gdb中设置这个地址

(gdb) b *0x40a780

Breakpoint 1 at 0x40a780: file llex.c, line 332.

 

3. 用gcc工具链查找函数(包括系统API)引用(仅适用于mingw带源码编译的调试版exe)

如果要查找所有调用llex的地址,可以使用objdump进行反编译

$ objdump -d ./lua.exe | grep call | grep llex

  40b14b:       e8 30 f6 ff ff          call   40a780 <_llex>

  40b179:       e8 02 f6 ff ff          call   40a780 <_llex>

可以看到调用llex函数的地方有两个:0x40b14b和0x40b179。

还可以用addr2line转换为可读的函数名。

$ objdump -d ./lua.exe | grep call | grep llex | addr2line -f -e ./lua.exe

luaX_next

D:/java/home/Administrator/testgprof/lua-5.1.4/src/llex.c:453

luaX_lookahead

D:/java/home/Administrator/testgprof/lua-5.1.4/src/llex.c:459

可以看到引用llex的函数是luaX_next和luaX_lookahead。

利用上面查找到的地址,

用b *0x40b14b和b *0x40b179在gdb中添加断点。

用这种方法可以实现类似API断点的效果

(例如把断点设置在所有使用fprintf的地方)

$ objdump -d ./lua.exe | grep call | grep fprintf

  4013de:       e8 6d 1a 02 00          call   422e50 <_fprintf>

  4013fb:       e8 50 1a 02 00          call   422e50 <_fprintf>

  40208d:       e8 be 0d 02 00          call   422e50 <_fprintf>

  405bad:       e8 9e d2 01 00          call   422e50 <_fprintf>

  414ed2:       e8 79 df 00 00          call   422e50 <_fprintf>

  41e6d6:       e8 cd 49 00 00          call   4230a8 <_vfprintf>

 

4. 用OllyDbg确定API引用的地址

确定API引用的地址,有助于用断点使程序尽可能停在执行该API之前。

以OllyDbg 1.10为例,要调试的exe为testbp.exe

1) 如果OllyDbg的标题栏显示此时CPU窗口显示的是exe所在的模块

标题应该是:

OllyDbg - testbp.exe -[CPU - module testbp]

如果标题是:

OllyDbg - testbp.exe -[CPU - module kernel32]

需要切换回exe所在模块:

CPU->右键->View->View->Module testbp

2) 查找导入API函数的引用位置

CPU->右键->Search for->Name (Label) in current module (Ctrl+N)

弹出Names in testbp列表一般很长,可以粘贴出来查找。

如果对

KERNEL32.OutputDebugStringA

感兴趣,那么

选中->右键->Find references to import

则弹出一个引用列表,显示该API在反编译后被引用的所有地址

(如果API的地址不是动态获取的)。

3) 查看某个地址附近的指令

有时用工具知道大概要关心的地址,可以在

CPU->右键->Go to->Expression

输入地址(可忽略0x前缀)跳转到相应位置。

CPU->右键->Go to->Previous procedure

跳转到当前地址之前最近的过程入口点(相当于函数入口,一般标有$)。

4) 列出交叉引用表(call <...>)

CPU->右键->Search for->All intermodular calls 

和1, 2的做法差不多,也可用于下API断点或日志断点。

 

5. 用IDA Pro确定API引用的地址

View->Open subviews->Imports

(或View->Open subviews->Names,列出所有模块的符号,

或View->Open subviews->Functions,列出所有方法)

选中感兴趣的API如fprintf(或Search->Search...查找字符串)

双击跳到IDA View A视图

然后用鼠标把光标移到

.idata:0042A390 ; int fprintf(FILE *File, const char *Format, ...)

的fprintf

右键->Jump to xref to operand... X

打开交叉引用列表,列出所有引用fprintf函数的相对地址。

如果要查看绝对地址,需要双击跳转。

 

6. 无源码的内存查看(类似OllyDbg的log breakpoint)

用gcc编译的可执行文件(包括exe)可以用命令列表实现日志断点(立刻恢复的断点)。

但是,即便无源码,也可以通过直接读取内存,或通过寄存器的指针值读取内存。

例如,

用OllyDbg依附mysqld后步进调试,(不能打开Handle窗口,OllyDbg会崩溃)

发现mysqld在执行到0x0045da00地址时ebx指向一个字符串,

是网络接收到的命令(不带末尾的分号),可以用如下gdb命令查看此时的字符串值

 

(gdb) attach 7820

(gdb) b *0x0045da00

(gdb) c

(gdb) i r

(gdb) p $ebx

(gdb) x/s $ebx

(gdb) printf "%s\n", $ebx

每当mysqld接收到字符串时,执行到0x0045da00处会被gdb中断,

然后可以通过x/s $ebx或printf "%s\n", $ebx输出接收到的字符串。

x表示读内存,ebx作为寄存器,必须带$前缀,s表示数据类型为字符串。

用这种方法结合命令列表可实时输出mysqld通过recv调用获取的字符串内容。

 

7. 产生core核心转储文件,分析堆栈回溯和其它运行时信息(主要用于linux)(对于win32的mingw,见第8项)

(1) 用ulimit和kill生成core文件

这种方法会中止进程,但可以查看较详细的堆栈回溯信息。

$ ulimit -a

core file size          (blocks, -c) 0

data seg size           (kbytes, -d) unlimited

scheduling priority             (-e) 20

file size               (blocks, -f) unlimited

pending signals                 (-i) 16382

max locked memory       (kbytes, -l) 64

max memory size         (kbytes, -m) unlimited

open files                      (-n) 1024

pipe size            (512 bytes, -p) 8

POSIX message queues     (bytes, -q) 819200

real-time priority              (-r) 0

stack size              (kbytes, -s) 8192

cpu time               (seconds, -t) unlimited

max user processes              (-u) unlimited

virtual memory          (kbytes, -v) unlimited

file locks                      (-x) unlimited

$ ulimit -c unlimited

$ ./lua

(注意,ulimit只对当前会话有效)

$ ps -a | grep lua

19950 pts/0    00:00:00 lua

$ kill -6 19950

(kill -6是Aborted,kill -11是Segmentation fault)

$ ls | grep core

core

$ gdb ./lua ./core

(gdb) bt

(2) 强制生成core文件

这种方法不中止进程,但堆栈回溯信息不详。

$ ps -a | grep lua

19678 pts/0    00:00:00 lua

$ gcore 19678

0x00ef9422 in __kernel_vsyscall ()

Saved corefile core.19678

$ gdb ./lua ./core.19678

 

 

(20140126补充:)

8.win32 mingw gdb的attach调试(相当于linux的core转储,用于判断程序卡在哪个位置),

(1)用-g3 -O0参数编译程序。

(2)如果程序会崩溃的话,直接用gdb启动程序(run命令)即可,不需要用attach命令,gdb会自动暂停,用bt命令可看到后退回溯(backtrace)。

(3)否则,可以尝试用attach(相当于上面的core转储)

首先,不用gdb直接启动程序,然后通过任务管理器查询到进程号(通常是4位整数),然后在合适的时候手动执行attach:

(gdb) attach <进程号>

然后列举线程

(gdb) info threads

切换线程(t=thread命令)

(gdb) thread <线程编号>

打印后退回溯(bt=backtrace命令)

(gdb) bt

通常主线程在thread 1上,但有可能会看不到bt,如果是这样需要暂停多次(退出后在合适的时候重新执行attach命令)。

如果仍无法看到bt,可以考虑用breakpoint方法(这个更需要技巧确定断在什么位置)

(gdb) b <文件名>:<行号>

删除breakpoint。

(gdb) info break

(gdb) remove <断点序号>

 

 

 

 

(TODO)

------------------------

 

参考资料:

1. Debugging Mozilla with gdb

https://developer.mozilla.org/en/Debugging_Mozilla_on_Linux_FAQ

 

2. mingw 下的 stack backtrace
 
3. 使用 Strace 和 GDB 调试工具的乐趣

 

4. eglog

http://gorry.haun.org/eglog/

 

5. Digital Travesia

http://hp.vector.co.jp/authors/VA028184/

 

6. GDB 多线程调试

 

http://www.yuanma.org/data/2009/0407/article_3605.htm

http://www.linuxforum.net/forum/gshowflat.php?Cat=&Board=program&Number=692404&page=0&view=collapsed

http://www.ibm.com/developerworks/cn/java/j-memoryanalyzer/

 

7. DLL劫持技术(内存补丁技术) (转)

http://hi.baidu.com/woaiwlaopo/blog/item/699f2a3fb0485ee954e7238d.html

 

8. gdb的一些命令

http://blog.sinzy.net/ifyr/entry/22198

 

9. Windows API Hooking Tutorial

http://ruffnex.oc.to/kenji/text/api_hook/

 

10. Visual Novel tools

http://vn.i-forge.net/tools/

 

(其它,游戏提取,与上述内容无关。。。)

 

11. Welcome to m-akita's Home Page

http://m-akita.sakura.ne.jp/index.php

 

12. asmodean's reverse engineering page - news and updates

http://asmodean.reverse.net/

 

13. VN Translation

http://tlwiki.tsukuru.info/index.php?title=Tools

 

14. notazsite

http://anime.geocities.jp/notazsite1/soft/index.html

Irregular child .. ?

http://hp.vector.co.jp/authors/VA018359/index2.html

 

(20130715)

15. 混沌の部屋

http://www.geocities.jp/hoku_hoshi/index2.html

 

 (20150528)

16. 【教材】用OllyDbg找出Agth提取GAL文本的特殊码(详细新人版)

http://867258173.diandian.com/post/2014-02-12/40060970396

 

 

你可能感兴趣的:(学习笔记)