gdb ./程序名
即可run
命令——简写r,来运行这个程序;启动前可以set args arg1 arg2 ...
来设置启动参数,运行中可以通过show args
查看启动参数gdb attach 进程ID
即可;脱离程序可以执行detach
ps -ef | grep 程序名
gdb ./程序名 core文件名
,之后就可以用普通gdb调试用的命令查看切换堆栈、查看变量等ulimit -c unlimited
ulimit -a
来查看当前所有的限制;在命令行中设置ulimit
只是临时的,想要永久生效,需要修改/etc/security/limits.conf
文件,如下:* soft core unlimited
root hard core unlimited
/tmp
文件夹下生成core开头的文件/proc/sys/kernel/core_pattern
文件内容为/tmp/core-%p-%e-%t
;或者执行 sysctl -w kernel.core_pattern=/tmp/core-%p-%e-%t
%p -进程ID
%e -进程名称
%t -生成时间
%u -用户ID
%g -组ID
%s -导致coredump生成的信号名
%h -主机名
断点的命令是break,简写b,基本用法:
info break
,查看当前所有的断点信息——其实info
(简写i)本身还可以搭配其他指令,也是查看信息的意思,比如info thread
查看当前所有线程信息break line
,在行号处打断点break function
,在当前文件指定函数开头处断点。需要注意的是,gdb会为所有该名称的函数打上断点,可以b class::method
给指定类的方法打上断点,或者b function(int)
给指定重载形式的函数打断点;更进一步,有rb 正则表达式
来给正则表达式能够匹配上的函数打上断点break file:line
,在指定文件指定行断点break file:function
,在指定文件指定函数断点break +/-offset
,在当前行前/后几行位置处断点break ... if cond
,设置条件断点tbreak ...
,设置一个临时的断点(临时断点断过一次后就会失效)i b
查看所有断点,可以enable 断点编号
,启用断点disable 断点编号
,禁用断点delete 断点编号
,删除断点,简写d因为设置断点与单步执行,只能看到当前运行行附近几行,可以通过list
命令显示更多行(默认显示10行),用法如下:
list lineNo
,显示行号附近行list file:lineNo
,显示文件的第几行附近行list function
,显示当前文件中函数附近代码list file:function
,显示指定文件中函数附近代码list from,to
,显示从from到to的代码-tui
参数;或者在运行的时候ctrl+x+a
进入,界面如下:backtrace
(简写bt),显示的堆栈前会显示每一帧的帧号frame 帧号
切换帧,简写f 帧号
;up/down n
基于当前帧上下切换帧info
信息——info args
查看当前函数调用参数,info locals
查看函数局部变量,info registers
查看寄存器,info mem
查看内存print
命令——打印变量值,p top
打印上一个变量的值whatis val
,打印变量val的类型。有意思的是,在模板函数/模板类的成员函数断点断住后,whatis T
也能获取到当前的泛型类型ptype val
,打印变量类型,会打印出类型的定义,包括成员变量和成员函数等,更详细print val
,打印显示变量的值;需要注意的是,print param=val
可以在调试过程中修改变量的值;print *this
输出当前对象各成员变量值display val
,可以在中断过程中始终显示某一个变量;info display
查看当前有哪些实时display的变量;undisplay
,取消指定序号变量的实时显示watch val
,监视某一个变量,当发生变化时中断,即数据断点info thread
,查看都有哪些线程thread index
,切换到指定线程如果想要实现VS中一样的,拖动当前运行位置到其他行并执行,gdb里也有jump命令(简写j),使用格式如下:
jump 行号
——跳转到指定行jump +10
——跳转到当前代码下面10行处jump *0x12345678
——跳转到0x12345678地址的代码处,地址前要加*(gdb)tbreak lineNum
(gdb)jump lineNum
以上跳转命令能否一次到位?gdb支持脚本,可以定义如下脚本,这样在gdb中可以使用自定义命令,move了
define move
if $argc != 1
help move
else
tbreak $arg0
jump $arg0
end
end
document move
go to specific line
usage:move line_number
end
以上:
if...else...end
与while...end
等,continue对应loop_continue
,break对应loop_break
document
代表给命令写文档,当执行help 命令
的时候会输入这里面的内容/etc/gdb/gdbinit
文件即可在gdb启动的时候加载在gdb中打印std::map,std::vector以及QString等自定义类型非常不友好,显示如下:
是否有方法优化下显示?
gdb启动调试的时候,因为要加载的动态库符号表太多了,所以附加上去会卡很久,禁用启动时自动加载动态库符号表,等到需要时再手动加载可加速启动速度;另外,按需加载动态库符号表的话,使用步进命令也会快得多,因为原来步进需要查找进入的函数在哪个动态库里,现在禁用自动加载了之后,本来加载的符号表就不多,查找自然很快
/etc/gdb/gdbinit
文件里,加上如下设置即可:set auto-solib-add off
然后附加调试之后,可以info sharedlibrary
查看加载的动态库,Sym列为No的表示未加载(Read列标*
的表示没有调试信息)
执行bt
查看当前堆栈,看下是当前是哪个动态库符号表未加载导致看不到
然后使用add-symbol-file
命令加载该动态库文件到From内存地址即可
接下来再bt
就可看到这个动态库的符号了
sharedlibrary regex
(或者shared regex
),按照正则表达式regex
匹配需要加载的动态库调试时之所以能显示源代码,是因为查找到了源代码位置;如果调试时找不到源码,那可能是因为没有设置源码路径
show directory
,显示当前源码路径,默认会带cdir
与cwd
,cdir
表示编译路径,cwd
表示当前工作路径directory path
,添加path到源码查找路径路径,如果直接directory
为空,则会清除当前所有的源码路径,但会加上默认的cdir,cwd
b xxx
i b
查看刚刚下的断点的编号commands 编号
来给指定编号的断点设置执行的命令,比如以上给断点2设置了命令,当命中断点2时,会打印一个“hide”字符串到命令行,然后continue(也就是继续程序的执行),这样像界面刷新一类频繁调到的函数我们可以不中断直接continue,又可以知道是否调过这个函数,甚至打印调用时候它的局部变量等信息
4. 如上处理后,如果输出内容太多的话,会触发gdb的分页显示,需要用户enter继续显示或者q退出显示,这样实际断点还是会中断,可以在/etc/gdb/gdbinit
中设置关闭分页显示确认(也就是直接默认就显示,不需要确认)——set pagination off
sudo apt install gdbserver
即可gdbserver IP:端口 程序名
-监听指定IP主机,监听端口,IP可省略gdbserver IP:端口 --attach PID
-附加到指定进程target remote IP:端口
mkdir build && cd build && ../configure --target=aarch64-linux-gnu && make -j4
,其中target
参数指定调试目标平台(aarch-linux-gnu为ARM,mips为MIPS);--host
指定编译出来的gdb将要运行于哪个平台,不需要指定,因为就是运行于当前平台(如果编译gdbserver,因为要运行于待调试的目标平台,要指定这个host参数)gdb支持自己的脚本(调用gdb命令或者自定义的命令),也支持一些脚本语言(比如python),还可以通过shell
命令调本地命令行,可以通过脚本的方式把一些重复繁琐的调试任务自动化
有待摸索…
Qt的调试基本和VS是一样的F5运行,F10步进,F11是step into,shift F11跳出等,不做赘述
修改~/.gdbinit文件,禁用自动加载符号,Qt Creator启动会读取这个配置文件
2. 手动加载用到的动态库符号
加载了动态库符号后,堆栈的帧可能仍然是灰色的,说明源码没找到,可以双击帧在编辑区查看对应源码路径,然后把源码拷贝到这个路径下(或者创建软链接映射过去):
需要注意的是,
1)这个路径如果太长可能显示不全,需要鼠标移动到堆栈的帧上悬停一会查看完整路径
2)路径中的每一个层级都必须要有,比如如果有个路径是/home/cbb-xn/work/cbbcode/GLD/../Glodon/src/GLD/Qt/ThemeEngine/GMPRibbonStyle.cpp
,源码拷贝到路径后实际为/home/cbb-xn/work/cbbcode/Glodon/src/GLD/Qt/ThemeEngine/GMPRibbonStyle.cpp
,路径/home/cbb-xn/work/cbbcode
下没有GLD
文件夹(这个是编译过程中生成的,直接clone下来的源码是没有这个文件夹的),但是这里源码路径里有,也必须创建一个,不然即使把源码拷贝到正确的路径也找不到
4. 设置定位器
源码拷贝到指定的搜索路径下,只是从堆栈中点击可以跳转过去,无法直接在qtcreator中搜索对应文件直接打开。其实qtcreator的Locator本身就像内置的一个everything一样,可以搜索打开文件,比如简单的用法可以通过点击Locator的编辑框,查看上方的提示——a 文件名
定位所有项目中的文件,c 类名
定位所有项目中的c++类,m 函数名
定位所有项目内所有函数
定位器本身可以扩展,点击编辑框左侧的放大镜,在上拉框里点击Configure...
,然后增加自己的定位器。我增加了GMP、GLD、GCP、GGDB、Qt等源码的路径,并设置搜索搜索前缀为a
,这样输入a 文件名
后不仅能搜索当前所有项目下所有文件,还能搜索外部GMP,GCP,GLD,GGDB,Qt的源码文件直接打开
nm
命令==>一般在编译时就会报出来,但是如果动态库是通过QLibrary
在程序中显式加载(不是通过代码依赖隐式加载)的话,可能会在运行时报这个错,可以在QLibrary::load
失败后面加一句QLibrary::errorString
来获取加载时的错误,就可以看到undefined symbol
之类的提示了
==>这个问题原因如提示所言,是符号未定义,
nm
命令,判断是调用方的问题,还是依赖库的问题——nm
命令用来查看动态库导出了哪些符号,如果导出符号中确实没有指定符号,则是依赖库问题(该文件没有编译,如没有该文件、cmake中指定了删除该文件、cmake中指定了不编译该文件等;或者该文件编译了但是没有正确导出该函数,如该函数cpp中未实现、该函数没有export导出等);如果动态库导出符号中有该符号,则是调用方使用不正确(cmake中没有配置依赖库等)nm -D B.so | grep func
,类似如下(一般T
表示符号在文本区,正常,U
的话是未定义,则不正常):或者用objdump
与readelf
也是差不多的作用:
注:应该还有一个链接问题,如果调用库调用方法和依赖库的符号导出都没问题,还有可能是调用库压根调的不是这个依赖库(比如本地有多个版本,调用的时候找错了),可以ldd -s xxx.so
,可以列出动态库依赖哪些动态库,以及其实际找的链接的是哪个动态库