- 什么是GDB
- Linux下core文件gdb调试方法
- GDB 常用操作
- 1. 显示版本信息
- 2. 显示gdb版权相关信息
- 3. 启动时不显示提示信息
- 4. gdb退出时不显示提示信息
- 5. 输出信息多时不会暂停输出
- 6. 列出函数的名字
- 7. 是否进入带调试信息的函数
- 8. 进入不带调试信息的函数
- 9. 退出正在调试的函数
- 10. 直接执行函数
- 11. 打印函数堆栈帧信息
- 12. 打印尾调用堆栈帧信息
- 13. 选择函数堆栈帧
- 14. 向上或向下切换函数堆栈帧
- 15. 在匿名空间设置断点
- 16. 在程序地址上打断点
- 17. 在程序入口处打断点
- 18. 在文件行号上打断点
- 19. 保存已经设置的断点
- 20. 设置临时断点
- 21. 设置条件断点
- 22. 忽略断点
- 23. 设置观察点
- 从同事那里学来的gdb调试小技巧
- gdb调试进阶
- gdb图形化界面
- gdb配置文件调试方法
- gstack (卡住问题定位)
- pstack
- 参考链接:
什么是GDB
Gdb是GNU开源组织发布的一个强大的UNIX下的程序调试工具
Gdb可以通过ptrace来控制指定的进程
strip -d
strip –s
如何提取符号表使用,编译时增加-g选项
如何使用gdb调试一个程序
使用gdb有三种常用方式:
\1. gdb [program] 使用gdb启动一个程序并调试
\2. gdb [program]+[core] 使用gdb调试core文件
\3. gdb [program]+[pid] 使用gdb调试已在运行的程序(通过pid)
linux上进程的几种状态:
R(TASK_RUNNING),可执行状态&运行状态(在run_queue队列里的状态)
S(TASK_INTERRUPTIBLE),可中断的睡眠状态,可处理signal
D(TASK_UNINTERRUPTIBLE),不可中断的睡眠状态,可处理signal,有延迟
Z(TASK_DEAD-EXIT_ZOMBIE)退出状态,进程称为僵尸进程,不可被kill,即不相应任务信号,无法用SIGKILL杀死
T(TASK_STOPPED or TASK_TRACED),暂停状态或跟踪状态,不可处理signal,因为根本没有时间片运行代码
X(TASK_DEAD-EXIT_DEAD),退出状态,进程即将被销毁
数据断点
数据断点在gdb中分为硬件断点和软件断点
Gdb中调用数据断点的指令分为watch,rwatch,awatch
watch:写型数据断点,当指定内存被修改时触发
rwatch:读型数据断点,当指定内存被访问时触发
awatch:综合上述两种
指令介绍
\1. help
help是一个查询指令,可以使用help来查看其他指令的用法,例如help print,就可以看到print的介绍以及用法
\2. shell
启动标准shell执行command string
\3. print
用于打印的一个指令
p /x 打印16进制
p/d 打印有符号整型数
u 打印无符号整型数
p/o 打印8进制
p/t 打印二进制
p/a 作为一个地址(系统会查找处于哪个函数)
p/c 作为要给ASCII字符
p/f 浮点数
p/s 字符串
\4. set
用于设置gdb内部的一些环境与运行时的参数
\5. show
show描述gdb本身的状态
show version
\6. info
描述程序的状态
info args显示传递给函数的参数
info registers 列出寄存器数据
info breakpoints 列出设置的断点
info thread 查看线程状态
\7. -cd/directory
指定源码路径(当当前执行gdb的位置不在源码目录下或二进制不在源码目录下时,否则会自动查找)
\8. file
用于加载调试用的二进制文件,不过一般都在启动gdb时直接加载
\9. backtrace
可以查看调用栈,在调试core时一般首先就是使用bt查看调用栈信息
\10. list
用于列出源代码
list+:只打印最近打印行后的代码
\11. thread
切换线程,适用于多线程调试时使用
\12. x
以多种格式直接查看内存,与程序数据类型无关
x/nfu
n:10进制数,指定显示多长的内存,默认为1
f:多了i格式(机器指令格式),其他格式同print, 模式时x型,即16进制
u:单元大小,b 表示1字节,h表示2字节,w表示4字节(默认),g表示8字节
\13. break
设置断点
\14. commands
用于在break后增加一些指令,增加break的可操作性
\15. define
将一些gdb的指令组合起来,方便连续的重复使用
\16. (gdb)info shared library
\17. (gdb) add-symbol-file
\18. (gdb)show follow-forkk-mode
\19. (gdb) info inferiors
Linux下core文件gdb调试方法
在程序不寻常退出时,内核会在当前工作目录下生成一个core文件(是一个内存映像,同时加上调试信息)。
使用gdb来查看core文件,可以指示出导致程序出错的代码所在文件和行数。
exit
exit会附带清理工作,比如全局对象、静态对象的析构,别的线程还在跑,很容易发生一些难以预测的结果。(代码中尽量不要用)
exit的清理工作还没做完,进程不会退出,别的线程还在跑,一访问被析构掉的静态对象就core了。
1.core文件的生成开关和大小限制
1)使用ulimit -c命令可查看core文件的生成开关。若结果为0,则表示关闭了此功能,不会生成core文件。 2)使用ulimit -c filesize命令,可以限制core文件的大小(filesize的单位为kbyte)。若ulimit -c unlimited,则表示core文件的大小不受限制。如果生成的信息超过此大小,将会被裁剪,最终生成一个不完整的core文件。在调试此core文件的时候,gdb会提示错误。 3)使用ulimit -a命令可以查看显示所有的用户定制,其中选项 -a 代表“ all ”
2.core文件的名称和生成路径
core文件生成路径: 输入可执行文件运行命令的同一路径下。 若系统生成的core文件不带其它任何扩展名称,则全部命名为core。新的core文件生成将覆盖原来的core文件。 1)/proc/sys/kernel/core_uses_pid可以控制core文件的文件名中是否添加pid作为扩展。文件内容为1,表示添加pid作为扩展名,生成的core文件格式为core.xxxx;为0则表示生成的core文件同一命名为core。
查看文件对应的值:
cat /proc/sys/kernel/core_uses_pid 可通过以下命令修改此文件: echo “1” > /proc/sys/kernel/core_uses_pid 2)/proc/sys/kernel/core_pattern可以控制core文件保存位置和文件名格式。
查看文件对应的保存位置
cat /proc/sys/kernel/core_pattern 可通过以下命令修改此文件: echo “/corefile/core-%e-%p-%t” > core_pattern,可以将core文件统一生成到/corefile目录下,产生的文件名为core-命令名-pid-时间戳 以下是参数列表: %p - insert pid into filename 添加pid %u - insert current uid into filename 添加当前uid %g - insert current gid into filename 添加当前gid %s - insert signal that caused the coredump into the filename 添加导致产生core的信号 %t - insert UNIX time that the coredump occurred into filename 添加core文件生成时的unix时间 %h - insert hostname where the coredump happened into filename 添加主机名 %e - insert coredumping executable name into filename 添加命令名
3.core文件的查看
core文件需要使用gdb来查看。 gdb ./a.out core-file core.xxxx 使用bt命令即可看到程序出错的地方。 以下两种命令方式具有相同的效果,但是在有些环境下不生效,所以推荐使用上面的命令。 1)gdb -core=core.xxxx file ./a.out bt 2)gdb -c core.xxxx file ./a.out bt
4.开发板上使用core文件调试
如果开发板的操作系统也是linux,core调试方法依然适用。如果开发板上不支持gdb,可将开发板的环境(依赖库)、可执行文件和core文件拷贝到PC的linux下。 在PC上调试开发板上产生的core文件,需要使用交叉编译器自带的gdb,并且需要在gdb中指定solib-absolute-prefix和solib-search-path两个变量以保证gdb能够找到可执行程序的依赖库路径。有一种建立配置文件的方法,不需要每次启动gdb都配置以上变量,即:在待运行gdb的路径下建立.gdbinit。 配置文件内容: set solib-absolute-prefix YOUR_CROSS_COMPILE_PATH set solib-search-path YOUR_CROSS_COMPILE_PATH set solib-search-path YOUR_DEVELOPER_TOOLS_LIB_PATH handle SIG32 nostop noprint pass 注意:待调试的可执行文件,在编译的时候需要加-g,core文件才能正常显示出错信息!有时候core信息很大,超出了开发板的空间限制,生成的core信息会残缺不全而无法使用,可以通过挂载到PC的方式来规避这一点。
5.一个能产生core文件的小型案例
#include <stdio.h>;
#include <stdlib.h>;
int crash()
{
char *xxx = "crash!!";
xxx[1] = 'D'; // 写只读存储区!
return 2;
}
int foo()
{
return crash();
}
int main()
{
return foo();
}
gcc -g a.cpp -o a
./a
Segmentation fault (core dumped)
如何查看进程挂在哪里了?如何定位到行?
gdb a /home/corefile/core-1002-a-20327-1565785192
Core was generated by `./a’. Program terminated with signal 11, Segmentation fault. #0 0x00000000004004e1 in crash () at a.c:7 7 xxx[1] = ‘D’; // 写只读存储区!
GDB 常用操作
上边的程序比较简单,不需要另外的操作就能直接找到问题所在。现实却不是这样的,常常需要进行单步跟踪,设置断点之类的操作才能顺利定位问题。下边列出了GDB一些常用的操作。
- 启动程序:run
-
设置断点:b 行号 函数名 - 删除断点:delete 断点编号
- 禁用断点:disable 断点编号
- 启用断点:enable 断点编号
- 单步跟踪:next 也可以简写 n
- 单步跟踪:step 也可以简写 s
- 打印变量:print 变量名字
- 设置变量:set var=value
- 查看变量类型:ptype var
- 顺序执行到结束:cont
- 顺序执行到某一行: util lineno
- 打印堆栈信息:bt
1. 显示版本信息
使用gdb时,如果想查看gdb版本信息,可以使用“show version
”命令:
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-114.el7
Copyright (C) 2013 Free Software Foundation, Inc.
2. 显示gdb版权相关信息
使用gdb时,如果想查看gdb版权相关信息,可以使用“show copying
”命令
或者“show warranty
”命令
3. 启动时不显示提示信息
gdb在启动时会显示提示信息。如果不想显示这个信息,则可以使用-q选项把提示信息关掉
$ gdb -q
4. gdb退出时不显示提示信息
可以在gdb中使用如下命令把提示信息关掉:
(gdb) set confirm off
5. 输出信息多时不会暂停输出
使用“set pagination off
”或者“set height 0
”命令。这样gdb就会全部输出,中间不会暂停。
6. 列出函数的名字
使用“info functions
”命令可以列出可执行文件的所有函数名称
(gdb) info functions thre*
可以看到gdb只会列出名字里包含“thre
”的函数。
7. 是否进入带调试信息的函数
使用gdb调试遇到函数时,使用step命令(缩写为s)可以进入函数(函数必须有调试信息)。
可以使用next命令(缩写为n)不进入函数,gdb会等函数执行完,再显示下一行要执行的程序代码。
8. 进入不带调试信息的函数
printf函数不带调试信息,所以“s”命令(s是“step”缩写)无法进入printf函数。
可以执行“set step-mode on”命令,这样gdb就不会跳过没有调试信息的函数
9. 退出正在调试的函数
当单步调试一个函数时,如果不想继续跟踪下去了,可以有两种方式退出。
第一种用“finish”命令,这样函数会继续执行完,并且打印返回值,然后等待输入接下来的命令。
第二种用“return”命令,这样函数不会继续执行下面的语句,而是直接返回。
10. 直接执行函数
使用gdb调试程序时,可以使用“call
”或“print
”命令直接调用函数执行。
(gdb) start
(gdb) call func()
(gdb) print func()
11. 打印函数堆栈帧信息
可以使用“i frame
”命令(i
是info
命令缩写)显示函数堆栈帧信息。
(gdb) i frame
(gdb) i registers
(gdb) disassemble func
执行“i frame
”命令后,输出了当前函数堆栈帧的地址,指令寄存器的值,局部变量地址及值等信息,可以对照当前寄存器的值和函数的汇编指令看一下。
12. 打印尾调用堆栈帧信息
查看main
函数汇编代码:
(gdb) disassemble main
在函数a
入口处打上断点,程序停止后,打印堆栈帧信息
(gdb) i frame
设置“debug entry-values
”选项为非0的值,这样除了输出正常的函数堆栈帧信息以外,还可以输出尾调用的相关信息
(gdb) set debug entry-values 1
(gdb) b test.c:4
(gdb) r
(gdb) i frame
可以看到输出了“tailcall: initial:
”信息
13. 选择函数堆栈帧
当程序暂停后,可以用“frame n
”命令选择函数堆栈帧,其中n
是层数。
(gdb) b test.c:5
(gdb) r
(gdb) bt
(gdb) frame 2
(gdb) i frame
(gdb) frame 0x7fffffffe568
使用“i frame
”命令可以知道0x7fffffffe568
是func2
的函数堆栈帧地址,使用“frame 0x7fffffffe568
”可以切换到func2
的函数堆栈帧。
14. 向上或向下切换函数堆栈帧
用gdb调试程序时,当程序暂停后,可以用“up n
”或“down n
”命令向上或向下选择函数堆栈帧,其中n
是层数。
(gdb) b test.c:5
(gdb) r
(gdb) bt
(gdb) frame 2
(gdb) up 1
(gdb) down 2
先执行“frame 2
”命令,切换到fun3
函数。
执行“up 1
”命令,此时会切换到main
函数,也就是会往外层的堆栈帧移动一层。
执行“down 2
”命令后,又会向内层堆栈帧移动二层。如果不指定n
,则n
默认为1
.
“up-silently n
”和“down-silently n
”这两个命令,与“up n
”和“down n
”命令区别在于,切换堆栈帧后,不会打印信息
15. 在匿名空间设置断点
如果要对namespace Foo中的foo函数设置断点``
(gdb) b Foo::foo
如果要对匿名空间中的bar函数设置断点
(gdb) b (anonymous namespace)::bar
16. 在程序地址上打断点
当调试汇编程序,或者没有调试信息的程序时,经常需要在程序地址上打断点,方法为b *address
(gdb) b *0x400522
17. 在程序入口处打断点
获取程序入口:
方法一:
$ strip a.out
$ readelf -h a.out
得到:
``
Entry point address: 0x400440
方法二:
``
$ gdb a.out
>>> info files
得到:
``
Entry point address: 0x400440
(gdb) b *0x400440
(gdb) r
18. 在文件行号上打断点
如果要在当前文件中的某一行打断点,直接b linenum
即可
(gdb) b 7
显式指定文件,b file:linenum
(gdb) b file.c:6
(gdb) i breakpoints
(gdb) b a/file.c:6
19. 保存已经设置的断点
(gdb) save breakpoints file-name-to-save
(gdb) source file-name-to-save
(gdb) info breakpoints
20. 设置临时断点
如果想让断点只生效一次,可以使用“tbreak”命令(缩写为:tb)
(gdb) tb a.c:15
(gdb) i b
(gdb) r
(gdb) i b
21. 设置条件断点
只有在条件满足时,断点才会被触发,命令是“break … if cond
”
(gdb) start
(gdb) b 10 if i==101
(gdb) r
(gdb) p sum
22. 忽略断点
在设置断点以后,可以忽略断点,命令是“ignore bnum count
”:意思是接下来count
次编号为bnum
的断点触发都不会让程序中断,只有第count + 1
次断点触发才会让程序中断。
(gdb) b 10
(gdb) ignore 1 5
(gdb) r
(gdb) p i
$1 = 6
23. 设置观察点
使用“watch
”命令设置观察点,也就是当一个变量值发生变化时,程序会停下来。
(gdb) start
(gdb) watch a
(gdb) r
(gdb) c
也可以使用“watch *(data type*)address
”这样的命令
(gdb) p &a
(gdb) watch *(int*)0x6009c8
(gdb) r
(gdb) c
从同事那里学来的gdb调试小技巧
1) set scheduler-locking on // 关闭线程调度器, 只有当前线程会活动,其他线程被阻塞
2) set logging on //打开屏幕输出到本地文件,当前文件夹下生成gdb.txt 记录所有gdb调试结果
3) b 文件名:行号 if 判断条件 // 条件断点
4) shell ls -l // gdb界面执行shell 命令
6) set pagination off // 关闭分页打印,设置不分页
gdb调试进阶
\1. 我们可以通过GDB attach 的方式来调试一个正在运行的线程:
- A. 是
- B. 否
\2. GDB下可以通过handle SIG35 nostop noprint 来屏蔽对35号信号的接管:
- A. 是
- B. 否
\3. 下面哪个命令可以查看调用栈:
- A. b
- B. bt
- C. step
- D. next
\4. 想要查看当前寄存器信息命令:
- A. info thread
- B. info local
- C. info r
- D. bt
\5. 我们想打印addr地址开始的汇编指令:
- A. x/20wx addr
- B. p/x addr
- C. x/20i addr
- D. p addr
课后小测
\1. 想要编译有调试信息的二进制文件,需要加-g选项:
- A. 是
- B. 否
\2. GDB下可以通过thread thread_num的方式来切换当前的任务现场:
- A. 是
- B. 否
\3. 想要看函数反汇编使用下面那个命令:
- A, readelf
- B, nm
- C, ar
- D, objdump
\4. 查看test.obj带有文件名和行号的反汇编命令是:
- A, objdump -dr test.obj
- B, objdump -d -S -l test.obj
- C, readelf -s test.obj
- D, objdump -Dr test.obj
\5. 查看test.obj带有重定位信息的反汇编命令是:
- A, objdump -dr test.obj
- B, objdump -d -S -l test.obj
- C, readelf -s test.obj
- D, objdump -D test.obj
课后小测
\1. mprotect 可以单独保护4字节内存:
- A. 是
- B. 否
\2. mprotect 将内存保护后,在释放内存前是否需要去保护:
- A. 是
- B. 否
\3. GDB watch 哪个是内存被读或写时停止
- A, rwatch
- B, watch
- C, awatch
\4. mprotect 设置内存可写可读但不能执行,flag应该传多少
- A, 1
- B, 2
- C, 3
- D, 7
\5. mprotect内存只读后内存如果被写,会产生哪个信号
- A, SIGILL
- B, SIG35
- C, SIGSEGV
- D, SIGTERM
\1. arm32 环境,R13保存sp指针:
- A. 是
- B. 否
\2. arm64 环境,X30保存sp指针:
- A. 是
- B. 否
\3. x8632和x8664 cpu 传参寄存器有几个:
- A, 4, 6
- B, 4, 4
- C, 6,4
- D, 6,6
\4. arm32和arm64 cpu 传参寄存器有几个:
- A, 4, 4
- B, 4, 8
- C, 8,4
- D, 8,8
\5. 任务栈中除mips32, 一般不会存在以下哪个信息:
- A, 函数入参信息
- B, 函数指令
- C, 函数调用关系
- D, 局部变量信息
综合测试
一、单选题
\1. 假设有全部变量g_uvAddr, 如何以16进制查看该变量值:
- A. p/x g_uvAddr
- B. p/s g_uvAddr
- C. p/d g_uvAddr
- d. p/c g_uvAddr
\2. 只给线程2的funcA打断点,以下操作哪个是对的:
- A. b funcA
- B. b funcA thread 2
- C. thread apply 2 b funcA
- D. thread apply all b funcA
\3. 若当前使用内存页大小为4K,addr=0x80010110, len=0x3000, mprotect中地址和长度填写分别是多少:
- A. 0x80010110,0x3000
- B. 0x80010000,0x2000
- C. 0x80011000,0x2000
- D. 0x80010000,0x3000
\4. arm64 入参寄存器是以下哪个:
- A. EDI,ESI,EDX,ECX
- B. R0~R3
- C. X0~X7
- D. RDI,RSI,RDX,RCX,R8,R9
\5. 当前我们的调试版本为release版本,定位时需要获取汇编函数对应的文件名和行号,我们需要增加哪个编译选项后重新编译函数obj:
- A. -g 正确
- B. -o2
- C. -fPIC
- D. -Wall
二、多选题
\6. 下面的命令哪些属于GDB的调试方法:
- A. gdb ./test.out
- B. gdb -c corefile test.out
- C. gdb attach 1117
\7. GDB watch断点的方式有哪几类:
- A. watch
- B. rwatch
- C. awatch
- D. xwatch
\8. 从任务栈中我们一般可以获取哪些信息:
- A. 局部变量
- B. 函数调用关系
- C. 函数入参
D. 函数反汇编
\9. 下面哪几个命令可以查看进程号为1234的线程2(线程ID为1235)的调用栈:
- A. gdb –batch -ex “thread 2” -ex “bt” -batch -p 1234
- B. thread apply all bt
- C. thread apply 2 bt
- D. gdb –batch -ex “bt” -batch -p 1235
\10. 定位一个异常问题,我们一般需要获取哪些信息:
- A. 调用栈
- B. 寄存器信息
- C. 函数反汇编
- D. 任务栈内存
gdb图形化界面
gdb的TUI(Text User Interface) 模式还是很不错的 要注意的一点是,最好以下面的方式进入tui模式,
gdb -tui a.out 而不要打开gdb后,再通过Ctrl-x a切换到tui模式。否则在tui模式中可能不能正常使用类似Ctrl-x a 等的快捷键;按下Ctrl-x a可能只能在gdb的命令行显示^xa。
(gdb)focus 可以进入tui模式
(gdb)refresh 可以进行刷新
(gdb) layout asm 可以切换到汇编代码窗口
(gdb) winheight src + 5 代码窗口的高度扩大5行代码
(gdb) focus next/prev 调整焦点位置
(gdb) focus cmd 把焦点移到 cmd 窗口
(gdb) winheight cmd + 10
https://rockhong.github.io/gdb-tui-mode.html
https://blog.csdn.net/baidu_41388533/article/details/109468392
(gdb) t a a bt 查看所有线程号
(gdb) set sysroot [path]
(gdb) set solib-search-path [path]
(gdb) file [filename]
(gdb) core [corename]
gdb配置文件调试方法
gdb ./build/vdb_test ./osd.cfg
gdb不可以这么传参的
正确方法:
gdb vdb_test
进入后set args ./osd.cfg
或者:
gdb --args ./build/vdb_test ./osd.cfg
gstack (卡住问题定位)
gstack <pid> 运行中查看进程状态
pstack
pstack <pid> 运行中查看进程状态
参考链接:
-
blog.csdn.net/shaovey/article/details/2744487
Linux下core文件调试方法
-
andyniu.iteye.com/blog/1965571
linux下生成core dump文件调试方法及设置
-
https://blog.csdn.net/madpointer/article/details/8856677
gdb实践(1): 进程CPU 100%排查
4.http://3ms.huawei.com/hi/group/3288655/wiki_5378755.html
mysqld 主备启动 gdb 调试
- https://www.cnblogs.com/huchong/p/10065246.html
linux每日命令(34):ps命令和pstree命令
ps –ef | grep “进程名” |
gdb attach 4891
在完成调试之后,不要忘记用detach命令断开连接,让被调试的进程可以继续正常运行。
pstree –p 4891
以树状图显示进程PID为的进程以及子孙进程,如果有-p参数则同时显示每个进程的PID
-
https://www.ibm.com/developerworks/cn/linux/l-cn-gdbmp/index.html
使用 GDB 调试多进程程序
7.https://wizardforcel.gitbooks.io/100-gdb-tips/part1.html
100个gdb小技巧
-
gdb参考手册
https://www.eecs.umich.edu/courses/eecs373/readings/Debugger.pdf
-
gdb 调试多线程 https://blog.csdn.net/zhangye3017/article/details/80382496
-
gdb tui及cgdb的使用 https://blog.csdn.net/baidu_41388533/article/details/109468392
https://blog.csdn.net/niyaozuozuihao/article/details/91802994