gdb+gdbserver远程调试
一. gdb+gdbserver交叉编译
1.源码下载
https://ftp.gnu.org/gnu/gdb/
2.编译gdb
$ sudo apt install texinfo #安装依赖
$ tar zxvf ./gdb-9.2.tar.gz
$ cd gdb-9.2
$ mkdir build
$ cd build/
$ /home/pz/tool/gdb-9.2/configure --target=aarch64-linux-gnu
$ make
gdb 运行在虚拟机上,所以它不需要交叉编译。–build 和 --host 参数留空,实际使用的是虚拟机的平台参数。gdb 虽运行在虚拟机上,但它处理的是开发板平台的程序,所以指定 --target 为 aarch64-linux-gnu,值取的是交叉编译工具链前缀。
3.交叉编译gdbserver
$ cd gdb-9.2/gdb/gdbserver
$ ./configure --prefix=/home/zf/tool/build/gdb --host=aarch64-linux-gnu LD=aarch64-linux-gnu-ld CC=aarch64-linux-gnu-gcc CXX=aarch64-linux-gnu-g++
$ sudo make -j8
$ sudo make install
gdbserver 运行在开发板上,所以需要交叉编译。
二. 远程使用gdbserver
1、目标开发板上输入:gdbserver [设备ip]:[端口] [需要运行的程序]
$ gdbserver 192.168.1.0:8000 test
设备ip:可忽略不写;
端口:用于远程连接的端口,默认为2345,这里使用8000;
需要运行的程序:该程序必须在编译时打开-g选项,编译成可调试版本,例子为test;
2、PC端/虚拟机
$ cd tool/gdb-9.2/build/gdb
$ ./gdb
$ target remote 192.168.1.0:8000
1.attach调试运行中程序
1、通过top等命令获取程序的pid。
2、设备上输入:gdbserver [设备ip]:[端口] --attach [运行中程序的pid]
$ gdbserver 192.168.1.0:8000 --attach pid
3、PC端执行gdb,输入:target remote [目标设备ip]:[端口]
$ cd tool/gdb-9.2/build/gdb
$ ./gdb
$ target remote 192.168.1.0:8000
2. 使用core文件进行调试
2.1. core文件的生成
使用ulimit -c unlimited
命令解除core文件的大小限制是生成core文件的前提,程序出现异常退出时,会自动在程序的当前路径下生成core文件;
当程序没有出现异常退出时想要生成core文件,可以通过向程序发送SEGSEGV
信号 (kill -11 [pid]
)使程序退出并生成core文件;
2.2. core文件在pc上调试
1、将core文件、库文件(设备上的/usr/lib文件夹)与对应的应用程序test转移到pc上,放在与gdb可执行文件的上一级。
2、使用gdb执行应用程序
$ ./gdb test
3、使用:core-file [core文件路径] 读取core文件
$ core-file ../core #会报错:找不到库
$ set solib-search-path ../lib #加载库
$ core-file ../core #再次执行core文件
三. gdb部分指令
1. 启动程序:r/run、start
根据不同场景的需要,GDB 调试器提供了多种方式来启动目标程序,其中最常用的就是 r/run 指令,其次为 start 指令。也就是说,run 和 start 指令都可以用来在 GDB 调试器中启动程序。
run与start的区别:
默认情况下,run 指令会一直执行程序,直到执行结束。如果程序中手动设置有断点,则 run 指令会执行程序至第一个断点处;
start 指令会执行程序至 main() 主函数的起始位置,即在 main() 函数的第一行语句处停止执行(该行代码尚未执行)。
2. 继续运行程序:c/continue
运行程序直到程序结束或遇到断点,使用方式:
$ c/continue [n]
如果从断点处使用c命令,可以使用参数n
n:表示忽略该断点n-1次,直到第n次到该断点处程序才会停下
3. 运行程序到指定位置:u/until
运行程序到指定位置后停下,相当于在指定位置打临时断点然后运行到断点处,使用起来比临时断点方便
使用方式:
$ u/until [行号或函数地址]
4. 单步执行:n/next、s/step
单步调试代码,其最大的特点是当遇到包含调用函数的语句时,无论函数内部包含多少行代码,next 指令都会一步执行完。也就是说,对于调用的函数来说,next 命令只会将其视作一行代码。
$ next [count]
参数 count 表示单步执行多少行代码,默认为 1 行。
step 命令和 next 命令的功能相同,都是单步执行程序。不同之处在于,当 step 命令所执行的代码行中包含函数时,会进入该函数内部,并在函数第一行代码处停止执行。
step [count]
参数 count 表示一次执行的行数,默认为 1 行。
5. 结束函数:finish、return
finish命令:执行函数到正常退出该函数
finish
return命令:立即结束执行当前函数并返回,可指定返回值
return [...]
注:使用return时,即使当前函数还有剩余的代码未执行完毕,也不会执行了
6. 设置断点b/break命令
b/break命令用于设置断点,当程序运行到断点所在位置且满足设置的条件时,程序将在断点处停止执行
break 命令(可以用 b 代替)常用的语法格式有以下 2 种。
break[location][thread threadnum] [if cond]
location:用于指定打断点的具体位置,其表示方式有多种,如下表所示。
location 的值 | 含义 |
---|---|
linenum | linenum 是一个整数,表示要打断点处代码的行号。 |
filename:linenum | filename 表示源程序文件名;linenum 为整数,表示具体行数。 |
+ offset - offset | offset 为行数偏移值,整数 |
function | function 表示程序中包含的函数的函数名 |
filename:function | filename 表示远程文件名;function 表示程序中函数的函数名。 |
thread threadnu:指定断点的触发线程,其中thread为线程编号
$ b HighAccuracyTimeStamp(void*) thread 1
$ info b #查看断点详情
if cond:指定断点的触发条件,其中cond为某个表达式。
$ b 280 if index == 10
注:设置断点后,程序会运行到达当前帧中大于指定位置的源行,即在第10行设置断点,程序在运行完第10行的代码处停止
7. 删除断点:delete、clear
delete 命令(可以缩写为 d )通常用来删除所有断点:
delete [断点编号] #不指定断点编号时删除全部断点
clear 命令可以删除指定位置处的所有断点:
clear [location]
参数 location 通常为某一行代码的行号或者某个具体的函数名。当 location 参数为某个函数的函数名时,表示删除位于该函数入口处的所有断点。
8. 设置观察断点:watch
watch 命令的功能是:当被监控变量(表达式)的值发生改变,程序才会停止运行
$ watch [cond]
#其中,cond 指的就是要监控的变量或表达式。
#注:1、若局部变量(表达式)失效,则监控操作也随即失效
# 2、如果监控的是一个指针变量(例如 *p),则 watch *p 和 watch p 是有区别的,前者监控的是 p 所指数据的变化情况,而后者监控的是 p 指针本身有没有改变指向
# 3、watch可以用于监控数组中元素值的变化情况,例如对于 a[10] 这个数组,watch a 表示只要 a 数组中存储的数据发生改变,程序就会停止执行
和 watch 命令功能相似的,还有 rwatch
和 awatch
命令。其中:
rwatch
命令:只要程序中出现读取目标变量(表达式)的值的操作,程序就会停止运行;
awatch
命令:只要程序中出现读取目标变量(表达式)的值或者改变值的操作,程序就会停止运行。
9. 信号处理:handle
handle 命令的语法格式如下:
$ handle [signal] [mode]
signal :要设定的目标信号,它通常为某个信号的全名(SIGINT
)或者简称(去除‘SIG’后的部分,如 INT);如果要指定所有信号,可以用 all 表示。
mode :明确 GDB 处理该目标信息的方式:
nostop:当信号发生时,GDB 不会暂停程序,其可以继续执行,但会打印出一条提示信息,告诉我们信号已经发生;
stop:当信号发生时,GDB 会暂停程序执行。
noprint:当信号发生时,GDB 不会打印出任何提示信息;
print:当信号发生时,GDB 会打印出必要的提示信息;
nopass(或者 ignore):GDB 捕获目标信号的同时,不允许程序自行处理该信号;
pass(或者 noignore):GDB 调试在捕获目标信号的同时,也允许程序自动处理该信号。
由于在接收到信号后,gdb的默认处理方式会使程序暂停,因此在项目中进行gdb调试时,先输入:
$ handle SIGUSR1 SIGUSR2 SIG44 SIG45 noprint nostop
$ info signal [信号名] #查看当前gdb对该信号的处理方式,不加信号名默认全部
使程序能流畅执行,不被频繁打断。若需要在某一个信号的信号处理函数处暂停,可自行修改此命令。
10. 查看堆栈信息:bt
$ bt [full] [n]
$ bt full #显示bt命令显示的内容外,还会显示每一层栈内变量的值
full:打印局部变量的值
n:一个整数值,当为正整数时,表示打印最里层的 n 个栈帧的信息;n 为负整数时,那么表示打印最外层 n 个栈帧的信息
11. 跳转到某一层栈:f
$ f [栈帧的编号] #跳转到栈后才可以查看该栈帧内的局部变量
12. 各线程状态:info threads
1 带有*的线程为gdb调试的当前线程,默认会在1号线程
2 gdb调试的线程编号
3 linux系统中的线程id
4 线程当前执行函数
5 线程当前执行的位置(文件、行号)
线程处于__GI___clock_nanosleep
说明该线程正在sleep 。
线程处于__lll_lock_wait
说明线程正在等锁。
13. 跳转到某一个线程:thread
$ thread [线程编号]
使用thread命令跳转线程时使用的线程编号是gdb调试时的线程编号而不是系统的pid。
跳转到线程后可以使用bt命令查看当前线程的调用栈
14. 打印表达式
1. p/print命令
使用:p[/输出类型] [变量名/表达式]
/x 以十六进制的形式打印出整数。
/d 以有符号、十进制的形式打印出整数。
/u 以无符号、十进制的形式打印出整数。
/o 以八进制的形式打印出整数。
/t 以二进制的形式打印出整数。
/f 以浮点数的形式打印变量或表达式的值。
/c 以字符形式打印变量或表达式的值。
$ p 0xfec0
$ p/x 66667
$ p/d (short)(0xfec0)
$ p [结构体名] #可以直接得到结构体内的所有值
$ p *(pthread_mutex_t*)[锁地址] #可以看到锁的持有者 例如 __owner = 29263 说明当前锁的持有者的pid为29263
2. 查看内存
x命令用于读取内存地址中的值,语法:
# x/<n/f/u> <addr>
$ x/10xb 0xffffaa7fb790
n:显示内存的长度,也就是说从当前地址向后显示几个数字(数字的长度由u参数决定)的内容。
f:表示显示的格式。如果地址所指的是字符串,那么格式可以是s,如果地址是指令地址,那么格式可以是i。
x 按十六进制格式显示变量
d 按十进制格式显示变量
u 按十六进制格式显示无符号整型
o 按八进制格式显示变量
t 按二进制格式显示变量
a 按十六进制格式地址显示变量
c 按字符格式显示变量
f 按浮点数格式显示变量
u:表示从当前地址往后请求的字节数,指定了字节长度后,GDB会从指内存定的内存地址开始,读写指定字节,并把其当作一个值取出来,如果不指定的话,GDB默认是4个bytes。
b表示单字节
h表示双字节
w表示四字节
g表示八字节
addr:内存地址
3. display命令
display 相当于自动输出的 x 或 print。display 会在每次暂停的时候输出表达式的值。display 会根据指定的模式自动选择使用 x 或是 print:如果使用i或s格式, 会使用 x 输出值,其他情况下使用 print。
display命令的使用方式与print类似:
$ display[/输出类型] [变量名/表达式] #输出类型参考x命令
$ info display #查看当前设置的显示内容
$ undisplay [变量或表达式的编号] #删除不需要打印显示的变量或表达式
$ delete display [变量或表达式的编号] #删除不需要打印显示的变量或表达式
$ enable/disable display [变量或表达式的编号] #以激活/禁用变量或表达式的打印
15. 其它
反汇编:disassemble
强制调用函数:print、call
显示源码:l/list