linux调试笔记

发布时间:2023年12月28日

基本

启动调试与附加进程

  • 转到程序运行目录下,执行gdb ./程序名即可
    只是附加了一个调试文件,并没有启动这个程序,需要执行run命令——简写r,来运行这个程序;启动前可以set args arg1 arg2 ...来设置启动参数,运行中可以通过show args查看启动参数
  • 如果要附加运行,可以执行gdb attach 进程ID即可;脱离程序可以执行detach
  • 在linux查看进程ID——一种方式直接在任务管理器里查看(如果是图形界面);或者在命令行里打印进程查找ps -ef | grep 程序名
  • 调试core文件——linux下的core文件就像windows下的dump文件,用于分析崩溃,命令很简单gdb ./程序名 core文件名,之后就可以用普通gdb调试用的命令查看切换堆栈、查看变量等
  • core文件的生成
    1. 首先去除生成core文件大小限制。只需要解除linux下core文件大小限制ulimit -c unlimited
      ulimit是限制linux下每个用户使用系统资源的命令,比如core文件大小限制、最大内存、最大堆栈大小、单次CPU最大占用时间等等,可以通过ulimit -a来查看当前所有的限制;在命令行中设置ulimit只是临时的,想要永久生效,需要修改/etc/security/limits.conf文件,如下:
    *               soft    core            unlimited
    root            hard    core            unlimited
    
    1. 然后运行程序直到崩溃,就会在/tmp文件夹下生成core开头的文件
      设置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

程序运行控制

  1. 单步(step over)——next单步执行,简写n;执行一次n之后可以直接enter继续执行n命令,因为gdb里没有输入命令就回车会执行上一次一样的命令
  2. 步进(step into)——step单步进入,简写s
  3. 运行到——until,执行到指定行
  4. 跳出当前函数——finish,继续执行完当前函数退出到堆栈上一级暂停
  5. 返回——return,直接跳出当前函数,不执行剩余内容
  6. 继续运行与中断——continue(简写c)继续运行程序;ctrl+c中断程序运行

tui模式

因为设置断点与单步执行,只能看到当前运行行附近几行,可以通过list命令显示更多行(默认显示10行),用法如下:

  • list lineNo,显示行号附近行
  • list file:lineNo,显示文件的第几行附近行
  • list function,显示当前文件中函数附近代码
  • list file:function,显示指定文件中函数附近代码
  • list from,to,显示从from到to的代码
    即使通过list多显示几行,仍然非常不友好,可以启用gdb的tui(text user interface)模式,只需要在启动gdb的时候加上-tui参数;或者在运行的时候ctrl+x+a进入,界面如下:
    在这里插入图片描述

查看堆栈与变量

  1. 查看堆栈命令backtrace(简写bt),显示的堆栈前会显示每一帧的帧号
  2. frame 帧号切换帧,简写f 帧号up/down n基于当前帧上下切换帧
  3. info信息——info args查看当前函数调用参数,info locals查看函数局部变量,info registers查看寄存器,info mem查看内存
  4. print命令——打印变量值,p top打印上一个变量的值
  5. whatis val,打印变量val的类型。有意思的是,在模板函数/模板类的成员函数断点断住后,whatis T也能获取到当前的泛型类型
  6. ptype val,打印变量类型,会打印出类型的定义,包括成员变量和成员函数等,更详细
  7. print val,打印显示变量的值;需要注意的是,print param=val可以在调试过程中修改变量的值;print *this输出当前对象各成员变量值

监视变量

  1. display val,可以在中断过程中始终显示某一个变量;info display查看当前有哪些实时display的变量;undisplay,取消指定序号变量的实时显示
  2. watch val,监视某一个变量,当发生变化时中断,即数据断点

多线程调试

  • info thread,查看都有哪些线程
  • thread index,切换到指定线程

扩展

自定义跳转命令

如果想要实现VS中一样的,拖动当前运行位置到其他行并执行,gdb里也有jump命令(简写j),使用格式如下:

  • jump 行号——跳转到指定行
  • jump +10——跳转到当前代码下面10行处
  • jump *0x12345678——跳转到0x12345678地址的代码处,地址前要加*
    跳转有两个注意事项——
  1. jump到指定位置,中间这些代码是直接跳过的(与VS是一样的)
  2. jump到指定位置后,如果该位置没有断点,是会继续往下运行的
    鉴于以上第2点,如果我们需要跳转到指定行后暂停,然后我们单步调试的话,需要先打一个临时断点
(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

以上:

  1. define定义了自定义命令,argc代表输入参数个数,arg0~arg9代表输入参数(最多十个),其中可以使用gdb的命令以及自定义命令,语法与一般语言差不多,if...else...endwhile...end等,continue对应loop_continue,break对应loop_break
  2. document代表给命令写文档,当执行help 命令的时候会输入这里面的内容
  3. 将以上脚本写入/etc/gdb/gdbinit文件即可在gdb启动的时候加载

解析自定义类型

在gdb中打印std::map,std::vector以及QString等自定义类型非常不友好,显示如下:

是否有方法优化下显示?

禁用动态库自动加载

gdb启动调试的时候,因为要加载的动态库符号表太多了,所以附加上去会卡很久,禁用启动时自动加载动态库符号表,等到需要时再手动加载可加速启动速度;另外,按需加载动态库符号表的话,使用步进命令也会快得多,因为原来步进需要查找进入的函数在哪个动态库里,现在禁用自动加载了之后,本来加载的符号表就不多,查找自然很快

  1. 禁用方式
    /etc/gdb/gdbinit文件里,加上如下设置即可:
set auto-solib-add off

然后附加调试之后,可以info sharedlibrary查看加载的动态库,Sym列为No的表示未加载(Read列标*的表示没有调试信息)
在这里插入图片描述

  1. 如何手动加载
  • 执行bt查看当前堆栈,看下是当前是哪个动态库符号表未加载导致看不到

  • 然后使用add-symbol-file命令加载该动态库文件到From内存地址即可
    在这里插入图片描述

  • 接下来再bt就可看到这个动态库的符号了

  1. 加载动态库符号还可以直接执行sharedlibrary regex(或者shared regex),按照正则表达式regex匹配需要加载的动态库

设置源码路径

调试时之所以能显示源代码,是因为查找到了源代码位置;如果调试时找不到源码,那可能是因为没有设置源码路径

  • show directory,显示当前源码路径,默认会带cdircwdcdir表示编译路径,cwd表示当前工作路径
  • directory path,添加path到源码查找路径路径,如果直接directory为空,则会清除当前所有的源码路径,但会加上默认的cdir,cwd

断点时执行命令

  1. 先下断点——b xxx
  2. 然后可以通过i b查看刚刚下的断点的编号
  3. 然后可以commands 编号来给指定编号的断点设置执行的命令,比如
    在这里插入图片描述

以上给断点2设置了命令,当命中断点2时,会打印一个“hide”字符串到命令行,然后continue(也就是继续程序的执行),这样像界面刷新一类频繁调到的函数我们可以不中断直接continue,又可以知道是否调过这个函数,甚至打印调用时候它的局部变量等信息
4. 如上处理后,如果输出内容太多的话,会触发gdb的分页显示,需要用户enter继续显示或者q退出显示,这样实际断点还是会中断,可以在/etc/gdb/gdbinit中设置关闭分页显示确认(也就是直接默认就显示,不需要确认)——set pagination off

gdbserver远程调试

  1. 远端运行gdbserver监听端口
    • 没有gdbserver则sudo apt install gdbserver即可
    • gdbserver IP:端口 程序名-监听指定IP主机,监听端口,IP可省略
    • 或者gdbserver IP:端口 --attach PID-附加到指定进程
  2. 本地端连接调试
    运行编译好的gdb,执行target remote IP:端口
  3. 像本地一样gdb调试
  4. 交叉编译gdb
    如果要调试的目标主机架构与本机不一致,本地需要交叉编译,生成目标架构的gdb程序
    • 下载gdb源码
    • 编译对应架构平台的gdb程序——解压源码,进入源码根目录,mkdir build && cd build && ../configure --target=aarch64-linux-gnu && make -j4,其中target参数指定调试目标平台(aarch-linux-gnu为ARM,mips为MIPS);--host指定编译出来的gdb将要运行于哪个平台,不需要指定,因为就是运行于当前平台(如果编译gdbserver,因为要运行于待调试的目标平台,要指定这个host参数)
    • 交叉编译这一点暂未验证

gdb脚本

gdb支持自己的脚本(调用gdb命令或者自定义的命令),也支持一些脚本语言(比如python),还可以通过shell命令调本地命令行,可以通过脚本的方式把一些重复繁琐的调试任务自动化
有待摸索…

QtCreator调试

Qt的调试基本和VS是一样的F5运行,F10步进,F11是step into,shift F11跳出等,不做赘述

  1. 禁用启动调试时自动加载所有符号文件
    在这里插入图片描述

修改~/.gdbinit文件,禁用自动加载符号,Qt Creator启动会读取这个配置文件
2. 手动加载用到的动态库符号
在这里插入图片描述

  • 未加载符号的动态库,符号列显示No,release版本也可以加载符号,只不过符号类型列会显示buildid(debug版本的是plain),plain表示动态库中就包含了符号的定义,而buildid表示动态库so文件中的符号是个链接到外部的引用,实际符号文件已经剥离
  • 未加载符号的话,断点打不上,断点上会显示一个沙漏(意思是等待未来加上符号后生效),正常是一个纯粹的红点
    在这里插入图片描述
  1. 拷贝源码到指定搜索路径
    未加载符号时中断程序,堆栈是看不到任何信息的,可以右键“创建完整回溯”,查看对应线程堆栈底下哪个动态库符号未识别,加载这个动态库符号,就能看到更多的堆栈的信息,继续以上步骤加载其他动态库符号就能将堆栈完整显示出来
    在这里插入图片描述

加载了动态库符号后,堆栈的帧可能仍然是灰色的,说明源码没找到,可以双击帧在编辑区查看对应源码路径,然后把源码拷贝到这个路径下(或者创建软链接映射过去):
在这里插入图片描述

需要注意的是,
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的源码文件直接打开
在这里插入图片描述

Linux下处理编译、运行时的一些问题

undefined symbol问题-nm命令

==>一般在编译时就会报出来,但是如果动态库是通过QLibrary在程序中显式加载(不是通过代码依赖隐式加载)的话,可能会在运行时报这个错,可以在QLibrary::load失败后面加一句QLibrary::errorString来获取加载时的错误,就可以看到undefined symbol之类的提示了
==>这个问题原因如提示所言,是符号未定义,

  1. 如果是同一个模块内,调用出现undefined symbol,检查是否调用的函数未定义
  2. 如果是跨模块调用出现undefined symbol,可以使用nm命令,判断是调用方的问题,还是依赖库的问题——nm命令用来查看动态库导出了哪些符号,如果导出符号中确实没有指定符号,则是依赖库问题(该文件没有编译,如没有该文件、cmake中指定了删除该文件、cmake中指定了不编译该文件等;或者该文件编译了但是没有正确导出该函数,如该函数cpp中未实现、该函数没有export导出等);如果动态库导出符号中有该符号,则是调用方使用不正确(cmake中没有配置依赖库等)
    如A模块调B模块的func函数,执行命令nm -D B.so | grep func,类似如下(一般T表示符号在文本区,正常,U的话是未定义,则不正常):
    在这里插入图片描述

或者用objdumpreadelf也是差不多的作用:
在这里插入图片描述
在这里插入图片描述

注:应该还有一个链接问题,如果调用库调用方法和依赖库的符号导出都没问题,还有可能是调用库压根调的不是这个依赖库(比如本地有多个版本,调用的时候找错了),可以ldd -s xxx.so,可以列出动态库依赖哪些动态库,以及其实际找的链接的是哪个动态库

系统环境变量问题

文章来源:https://blog.csdn.net/liyunxin_c_language/article/details/135279059
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。