gdb-2-技巧
打印美化
set print pretty on
使用layout图形化界面
set print object on打印派生对象
-函数
向上或向下切换函数堆栈帧
用gdb调试程序时,当程序暂停后,可以用“ up n ”或“ down n ”命令向上或向下选择函数堆栈帧,其
中 n 是层数。以上面程序为例:
可以看到程序断住后,先执行“ frame 2 ”命令,切换到 fun3 函数。接着执行“ up 1 ”命令,此时会切 换到 main 函数,也就是会往外层的堆栈帧移动一层。反之,当执行“ down 2 ”命令后,又会向内层堆 栈帧移动二层。如果不指定 n ,则 n 默认为 1 .
还有“ up-silently n ”和“ down-silently n ”这两个命令,与“ up n ”和“ down n ”命令区别在于,切换
堆栈帧后,不会打印信息,仍以上面程序为例:
-断点
在程序入口处打断点
如果不知道main在何处,那么可以在程序入口处打断点。先通过 readelf 获得入口地址,然后:
Entry point address:
(gdb) b *0x400440 (gdb) r
-打印
在gdb中,如果要打印C++ STL容器的内容,缺省的显示结果可读性很差:
gdb 7.0之后,可以使用gcc提供的python脚本,来改善显示结果:
1. 获得最新的python脚本
svn co svn://gcc.gnu.org/svn/gcc/trunk/libstdc++-v3/python
2. 将如下代码添加到.gdbinit文件中(假设python脚本位于 /home/maude/gdb_printers/ 下)
python
import sys
sys.path.insert(0, '/home/maude/gdb_printers/python') from libstdcxx.v6.printers import register_libstdcxx_printers register_libstdcxx_printers (None)
end
vscode 也有一个visualizerFile,是基于visual studio的,做一个xml把数据对应起来
打印大数组中的内容
在gdb中,如果要打印大数组的内容,缺省最多会显示200个元素:
(gdb) set print elements number-of-elements
也可以使用如下命令,设置为没有限制:
(gdb) set print elements 0
打印数组中任意连续元素值
在gdb中,如果要打印数组中任意连续元素的值,可以使用“ p array[index]@num ”命令 ( p 是 print 命令的缩写)。其中 index 是数组索引(从0开始计数), num 是连续多少个元素。以 上面代码为例:
如果要打印从数组开头连续元素的值,也可使用这个命令:“ p *array@num ”
打印数组的索引下标
(gdb) set print array-indexes on
打印进程内存信息
i proc mappings
打印静态变量的值
你可以显式地指定文件名(上下文):
(gdb) p 'static-1.c'::var $1 = 1
(gdb) p 'static-2.c'::var $2 = 2
打印变量的类型和所在文件
在gdb中,可以使用如下命令查看变量的类型:
(gdb) whatis he
type = struct child
如果想查看详细的类型信息:
(gdb) ptype he
type = struct child { char name[10]; enum {boy, girl} gender;
}
如果想查看定义该变量的文件:
(gdb) i variables he
打印内存的值
gdb中使用“ x ”命令来打印内存的值,格式为“ x/nfu addr ”。含义为以 f 格式打印从 addr 开始 的 n 个长度单元为 u 的内存值。参数具体含义如下:
a)n:输出单元的个数。
b)f:是输出格式。比如 x 是以16进制形式输出, o 是以8进制形式输出,等等。 c)u:标明一个单元的长度。 b 是一个 byte , h 是两个 byte (halfword), w 是四 个 byte (word), g 是八个 byte (giant word)。
以上面程序为例:
(1) 以16进制格式打印数组前 a 16个byte的值:
(gdb) x/16xb a
0x7fffffffe4a0: 0x00 0x7fffffffe4a8: 0x08
0x01 0x09
0x02 0x0a
0x03 0x0b
0x04 0x0c
(2) 以无符号10进制格式打印数组 a 前16个byte的值:
(gdb) x/16ub a
0x7fffffffe4a0: 0 0x7fffffffe4a8: 8
0x05 0x0d
0x06 0x0e
0x07 0x0f
(2) 以无符号10进制格式打印数组 a 前16个byte的值:
(gdb) x/16ub a
打印源代码行
如上所示,在gdb中可以使用 list (简写为l)命令来显示源代码以及行号。 list 命令可以指定行 号,函数:
(gdb) l 24
(gdb) l main
还可以指定向前或向后打印:
(gdb) l - (gdb) l +
还可以指定范围:
(gdb) l 1,10
每行打印一个结构体成员
可以执行“set print pretty on”命令,这样每行只会显示结构体的一名成员,而且还会根据成员的定义层 次进行缩进:
按照派生类型打印对象
在gdb中,当打印一个对象时,缺省是按照声明的类型进行打印:
set print object on
(gdb) whatis p type = Shape & (gdb) ptype p type = class Shape { public: virtual void draw(void);
} &
(gdb) set print object on (gdb) whatis p type = /* real type = Circle & */ Shape & (gdb) ptype p type = /* real type = Circle & */ class Shape { public: virtual void draw(void);
} &
指定程序的输入输出设备
在gdb中,缺省情况下程序的输入输出是和gdb使用同一个终端。你也可以为程序指定一个单独的输入 输出终端。
首先,打开一个新终端,使用如下命令获得设备文件名:
$ tty
/dev/pts/2
然后,通过命令行选项指定程序的输入输出设备:
$ gdb -tty /dev/pts/2 ./a.out (gdb) r
或者,在gdb中,使用命令进行设置:
(gdb) tty /dev/pts/2
使用“$_”和“$__”变量
" x "命令会把最后检查的内存地址值存在“ $_ ”这个“convenience variable”中,并且会把这个地址中的 内容放在“ $__ ”这个“convenience variable”,以上面程序为例:
(gdb) p $__ $2 = 15
打印程序动态分配内存的信息
用gdb调试程序时,可以用下面的自定义命令,打印程序动态分配内存的信息:
define mallocinfo
set $__f = fopen("/dev/tty", "w")
call malloc_info(0, $__f)
call fclose($__f)
end
(gdb) mallocinfo
打印调用栈帧中变量的值
也可以不进行切换,直接打印:
(gdb) p func2::b $1 = 2
同样,对于C++的函数名,需要使用单引号括起来,比如:
(gdb) p '(anonymous namespace)::SSAA::handleStore'::n->pi->inst->dump()
-多进程多线程
十、线程(Thread Stops)
如果你程序是多线程的话,你可以定义你的断点是否在所有的线程上,或是在某个特定的线程。GDB很容易帮你完成这一工作。
break <linespec> thread <threadno>
break <linespec> thread <threadno> if ...
linespec指定了断点设置在的源程序的行号。threadno指定了线程的ID,注意,这个ID是GDB分配的,你可以通过“info threads”命令来查看正在运行程序中的线程信息。如果你不指定thread <threadno>则表示你的断点设在所有线程上面。你还可以为某线程指定断点条件。如:
(gdb) break frik.c:13 thread 28 if bartab > lim
调试已经运行的进程
调试已经运行的进程有两种方法:一种是gdb启动时,指定进程的ID:gdb program processID(也可 以用-p或者--pid指定进程ID,例如:gdb program -p=10210)。以上面代码为例,用“ps”命令已经获 得进程ID为10210:
bash-3.2# gdb -q a 10210
另一种是先启动gdb,然后用“attach”命令“附着”在进程上:
bash-3.2# gdb -q a
Reading symbols from /data/nan/a...done. (gdb) attach 10210
调试子进程
在调试多进程程序时,gdb默认会追踪父进程。例如:
(gdb) set follow-fork-mode child (gdb) start
同时调试父进程和子进程
在调试多进程程序时,gdb默认只会追踪父进程的运行,而子进程会独立运行,gdb不会控制。以上面 程序为例:
可以看到当单步执行到第8行时,程序打印出“Child” ,证明子进程已经开始独立运行。
如果要同时调试父进程和子进程,可以使用“ set detach-on-fork off ”(默认 detach-on-
fork 是 on )命令,这样gdb就能同时调试父子进程,并且在调试一个进程时,另外一个进程处于挂起
在使用“ set detach-on-fork off ”命令后,用“ i inferiors ”( i 是 info 命令缩写)查看进程状
态,可以看到父子进程都在被gdb调试的状态,前面显示“*”是正在调试的进程。当父进程退出后,
用“ inferior infno ”切换到子进程去调试。
查看线程信息
(gdb) i threads
(gdb) i threads 1 2
只允许一个线程运行
用gdb调试多线程程序时,一旦程序断住,所有的线程都处于暂停状态。此时当你调试其中一个线程时 (比如执行“ step ”,“ next ”命令),所有的线程都会同时执行。以上面程序为例:
如果想在调试一个线程时,让其它线程暂停执行,可以使用“ set scheduler-locking on ”命令:
此外,“ set scheduler-locking ”命令除了支持 off 和 on 模式外(默认是 off ),还有一个 step 模 式。含义是:当用" step "命令调试线程时,其它线程不会执行,但是用其它命令(比如" next ")调试 线程时,其它线程也许会执行。
使用“$_thread”变量
gdb从7.2版本引入了 $_thread 这个“ convenience variable ”,用来保存当前正在调试的线程号。这
个变量在写断点命令或是命令脚本时会很有用。以上面程序为例:
一个gdb会话中同时调试多个程序
gdb支持在一个会话中同时调试多个程序。以上面程序为例,首先调试 a 程序:
接着使用“ add-inferior [ -copies n ] [ -exec executable ] ”命令加载可执行文件 b 。其中 n 默
认为1:
打印程序进程空间信息
使用gdb调试多个进程时,可以使用“ maint info program-spaces ”打印当前所有被调试的进程信息。
以上面程序为例:
(gdb) maint info program-spaces Id
/home/nan/b
Bound inferiors: ID 4 (process 0)
/home/nan/b
Bound inferiors: ID 3 (process 0)
/home/nan/b
Bound inferiors: ID 2 (process 15902)
/home/nan/a
Bound inferiors: ID 1 (process 15753)
使用“$_exitcode”变量
当被调试的程序正常退出时,gdb会使用 $_exitcode 这个“ convenience variable ”记录程序退出时 的“ exit code ”。
产生coredump文件
(gdb) gcore
Saved corefile core.13256
加载可执行程序和core dump文件
gdb coredump应用程序 coredump文件 调试coredump文件
gdb 调试coredump文件过程:
第一步:首先需要一个进程的coredump文件,怎么搞出coredump文件呢?
1、 ps -fax|grep 进程名称 找到进程的pid
2、gdb -p pid 调试进程
3、gcore coredump名称 则生成core文件
第二步:找出coredump文件的应用程序
1、gdb -c corefile 使用gdb调试core文件
2、info auxv 索引31对应的是core文件的应用程序
第三部:gdb使用应用程序调试coredump文件
gdb coredump应用程序 coredump文件 调试coredump文件
通过以上三步就可以调试coredump文件了
通过以下命令调试coredump文件
info threads 显示所有线程
bt 显示线程堆栈信息
thread thread_num 切换线程
frame num 切换栈
info r 显示当前帧的寄存器信息 (每一帧的寄存器信息都是不相同的)
readelf应用coredump
readelf -h 读取coredump文件头
readelf -wl 读取应用程序debug_line
readelf -wf 读取应用程序fde和cie信息
-改变程序的执行
跳转到指定位置执行
看起来是在15行,调用fun的时候出错了。常见的办法是在15行设置个断点,然后从头 run 一次。
如果你的环境支持反向执行,那么更好了。
如果不支持,你也可以直接 jump 到15行,再执行一次:
(gdb) b 15
Breakpoint 2 at 0x40056a: file jump.c, line 15. (gdb) j 15
Continuing at 0x40056a.
Breakpoint 2, main () at jump.c:15
需要注意的是:
1. jump 命令只改变pc的值,所以改变程序执行可能会出现不同的结果,比如变量i的值 2. 通过(临时)断点的配合,可以让你的程序跳到指定的位置,并停下来
修改被调试程序的二进制文件
gdb不仅可以用来调试程序,还可以修改程序的二进制代码。
缺省情况下,gdb是以只读方式加载程序的。可以通过命令行选项指定为可写:
$ gcc -write ./a.out
(gdb) show write
Writing into executable and core files is on.
修改二进制代码(注意大小端和指令长度):
(gdb) set variable *(short*)0x400651=0x0ceb (gdb) disassemble /mr drawing
修改pc的值
(gdb) n 6
a++;
(gdb) p $pc
$3 = (void (*)()) 0x8050944 <main+35> (gdb) set var $pc=0x08050949
- 信号
查看信号处理信息
用gdb调试程序时,可以用“ i signals ”命令(或者“ i handle ”命令, i 是 info 命令缩写)查看gdb
如何处理进程收到的信号:
第一项( Signal ):标示每个信号。
第二项( Stop ):表示被调试的程序有对应的信号发生时,gdb是否会暂停程序。 第三项( Print ):表示被调试的程序有对应的信号发生时,gdb是否会打印相关信息。 第四项( Pass to program ):gdb是否会把这个信号发给被调试的程序。 第五项( Description ):信号的描述信息。
信号发生时是否打印信号信息
用gdb调试程序时,可以用“ handle signal print/noprint ”命令设置当信号发生时,是否打印信号信
息,以上面程序为例:
接下来用“ handle SIGHUP noprint ”命令设置当 SIGHUP 信号发生时,gdb不打印信号信息,执行如
下:
信号发生时是否把信号丢给程序处理
用gdb调试程序时,可以用“ handle signal pass(noignore)/nopass(ignore) ”命令设置当信号发生
时,是否把信号丢给程序处理.其中 pass 和 noignore 含义相同, nopass 和 ignore 含义相同。以上
给程序发送信号
用gdb调试程序的过程中,当被调试程序停止后,可以用“ signal signal_name ”命令让程序继续运
行,但会立即给程序发送信号。以上面程序为例:
(gdb) signal SIGHUP
使用“$_siginfo”变量
在某些平台上(比如Linux)使用gdb调试程序,当有信号发生时,gdb在把信号丢给程序之前,可以通 过 $_siginfo 变量读取一些额外的有关当前信号的信息,这些信息是 kernel 传给信号处理函数的。 以上面程序为例:
(gdb) p $_siginfo._sifields._sigfault.si_addr
$1 = (void *) 0x850e
-共享库
使用" info sharedlibrary regex "命令可以显示程序加载的共享链接库信息,其中 regex 可以是正则 表达式,意为显示名字符合 regex 的共享链接库。如果没有 regex ,则列出所有的库。以上面程序为 例:
(gdb) i sharedlibrary hiredi*
-脚本
配置gdb init文件
当gdb启动时,会读取HOME目录和当前目录下的的配置文件,执行里面的命令。这个文件通常 为“.gdbinit”。
# 打印STL容器中的内容
python
import sys
sys.path.insert(0, "/home/xmj/project/gcc-trunk/libstdc++-v3/python") from libstdcxx.v6.printers import register_libstdcxx_printers register_libstdcxx_printers (None)
end
# 保存历史命令
set history filename ~/.gdb_history set history save on
# 退出时不显示提示信息 set confirm off
# 按照派生类型打印对象 set print object on
# 打印数组的索引下标
set print array-indexes on
# 每行打印一个结构体成员 set print pretty on
-源文件
设置源文件查找路径
有时gdb不能准确地定位到源文件的位置(比如文件被移走了,等等),此时可以用 directory 命令 设置查找源文件的路径。以上面程序为例:
(gdb) directory ../ki/
有时调试程序时,源代码文件可能已经移到其它的文件夹了。此时可以用 set substitute-path from
to 命令设置新的文件夹( to )目录替换旧的( from )。以上面程序为例:
进入和退出图形化调试界面
启动gdb时指定“ -tui ”参数(例如: gdb -tui program ),或者运行gdb过程中使用“ Crtl+X+A ”组
合键,都可以进入图形化调试界面。以调试上面程序为例:
显示汇编代码窗口
使用gdb图形化调试界面时,可以使用“ layout asm ”命令显示汇编代码窗口。以调试上面程序为例:
显示寄存器窗口
使用gdb图形化调试界面时,可以使用“ layout regs ”命令显示寄存器窗口。以调试上面程序为例:
“ tui reg system ”命令显示系统寄存器:
---其他
log 日志
用gdb调试程序时,可以使用“ set logging on ”命令把执行gdb的过程记录下来,方便以后自己参考或 是别人帮忙分析。默认的日志文件是“ gdb.txt ”,也可以用“ set logging file file ”改成别的名字。
以上面程序为例:
支持预处理器宏信息
使用 gcc -g 编译生成的程序,是不包含预处理器宏信息的:
(gdb) p NAME
No symbol "NAME" in current context.
如果想在gdb中查看宏信息,可以使用 gcc -g3 进行编译:
(gdb) p NAME $1 = "Joe"
设置被调试程序的参数
可以在gdb启动时,通过选项指定被调试程序的参数,例如:
$ gdb -args ./a.out a b c
也可以在gdb中,通过命令来设置,例如:
(gdb) set args a b c
(gdb) show args
Argument list to give program being debugged when it is started is "a b c".
设置被调试程序的环境变量
set env LD_PRELOAD=/lib/x86_64-linux-gnu/libpthread.so.0