[转帖]gdb调试

gdb,调试 · 浏览次数 : 0

小编点评

#源代码搜索路径设置 <dir> <path> #设置断点  d <ids> #清除断点  clear <location> #设置条件断点  b <cond> #设置多个条件断点  b <cond1> <location1> <cond2> <location2> <...> #执行条件断点  b <cond> <location1> <target> #恢复临时disable的断点  enable <ids> #设置多个watchpoints  d <ids> #设置多个读写断点  d <ids>  d <expression>rwatch <expression>awatch <expression> #设置多个读写断点  d <ids>  d <expression>rwatch <expression>awatch <expression> #设置多个线程  b <thread-id-list | all> args #执行多个线程  b <thread-id-list | all> args  thread apply <thread-id-list | all> args

正文

https://zhuanlan.zhihu.com/p/395115890?utm_id=0

 

写这个文档的目的,是让有一定软件开发基础的同学,可以较为快速地入门gdb的使用。gdb的命令非常多,在做了简短的介绍后,我挑选了最高频使用的命令/场景,希望能够让读者一个小时拥有即战力。

为了让读者对gdb有更直观的认识,同时提供了一个demo,可以下载编译,拿着demo来练手。

对于有过使用gdb经验的同学,在一段时间不用后,也可以拿这个文档来回顾下,恢复战力。

1、 认识gdb

gdb是一个debugger,它可以帮助我们深入到正在运行的程序中去看到它到底在做什么,或者程序crash的时候正在做什么。

说人话,gdb是一个程序调试及分析工具,常用于linux下c/c++程序调试。主要提供以下四个功能:

  • 启动程序,并设定影响程序执行的一些条件(比如参数、环境变量等)
  • 指定地点或者条件下暂停程序(break, watch)
  • 程序暂停时一窥究竟(bt, print, x, info)
  • 动态改变程序执行环境(set), 让我们可以试验不同变量、参数等对程序的影响

2、gdb调试的几种启动方式

2.1 直接使用gdb启动一个程序

gdb program

这种方式通常直接在target上执行,其中program是要启动的程序。

为了调试时能够获取更多的信息(比如获取到堆栈的函数名等),我们通常使用带调试信息的版本进行gdb调试。

使用gcc的"-g"选项可以在调试的时候加入调试信息。

以下是示例:

 这里是一个实例程序

clone到本地后,执行编译命令, 直接在src目录下生成应用程序t1(linux环境下)

g++ -g --std=c++11 main.cpp -lpthread -o t1

执行"gdb t1"可以直接启动程序进行调试,使用r执行程序,调试完使用q命令退出。

图2-1 gdb启动程序实例图

2.2 使用attach调试已经在运行的程序

在系统中,很多程序是开机启动的,而且依赖启动时的环境,并不允许我们使用"gdb program"启动调试。

这个时候可以使用attach命令调试一个已经运行的进程

attach pid

其中pid是程序的进程id,可以通过ps + grep 命令获取(可以参考ps命令)

gdb attach一个进程后,首先会暂停该进程,使用continue/c命令可以让程序继续进行

使用detach命令可以释放调试进程

示例:

先运行2.1中的t1

./t1

然后通过ps命令获取pid

ps aux|grep t1

假设为102474, 执行attach命令

gdb attach 102474

设置断点等

b main.cpp:155

然后"c"继续程序的执行

c

这个示例程序我们设计了在执行目录创建名字为"dump"的命令可以进入dump_items

echo 0>dump

这个时候程序会在我们main.cpp:155这里停下来

图2-2 attach已经在运行的程序

2.3 调试core dump

core file是进程运行时时的image,它包含了所有的进程调试信息,比如硬件寄存器内容,进程状态,进程数据。

程序crashed时的core file称之为core dump,通常地说,它就是犯罪现场,是程序crash那一刹那的内存快照。

我们可以使用gdb + core dump来定位crash发生的地点和原因, 这是系统调试中很常见的调试场景。

使用gdb调试core dump的命令是

gdb <executable_path> <coredump_file_path>

其中<executable_path> 表示要调试的程序的路径,<coredump_file_path>表示core dump文件的路径。

需要注意的是,如果你的程序是运行在一个容器内,那么这个<executable_path> 为容器这个program,常见的比如小程序。

在系统调试的时候,通常一个可执行程序会连接很多动态链接库,要得到这些动态链接库的比如符号表,就要设置符号路径,可以使用如下命令设置。

set sysroot <sym_path>

这里sym_path就是符号表的路径。

对于系统调试者来说,可以从公司构建系统去拿到symbols。

gdb + core dump + symbol , 形成了程序crash时基本现场信息。

其它的调试手段,几种启动方式一样,后面进行讲述。

示例:

我们使用t1来生成core dump文件, 如果你的linux版本无法生成core dump, 可以参考linux下生成core dump文件方法及设置

执行t1 : ./t1

在程序当前目录下执行12次 "echo 0 > add", 程序就会因为数组越界出现segment fault,并在当前目录下生成coredump文件, 在我的机器上,coredump文件命名是core.<pid>, 比如core.16025。

此时执行命令"gdb t1 core.16025 -q", 这里加了-q选项不显示版权和介绍信息

图2-3 gdb调试core dump

从调用堆栈可以看到,程序死在handle_add_request的std::makeshared操作中,这里访问size为11的item_names数组的第12个元素。

3. 高频使用的场景及命令

3.1 获取帮助

启动gdb后,在gdb提示符下输入以下命令可以获取到帮助,是记性不好的同学的福音。

help

help class

help command

gdb将命令将命令了几个class(比如stack, breakpoints, data等)

help 可以获取到class 列表, 而help class可以获取到命令列表,最后help command可以获取到command。

3.2 堆栈回溯

堆栈回溯是超高频使用的一个场景,特别是在分析coredump的时候,bt几乎是大部分coredump分析的前两个命令(另一个是set sysroot)。

bt指令可以打印出当前的调用堆栈, 在前面的操作中我们都有演示。

调用堆栈由连续的堆栈帧(stack frames)组成,每一帧(frame)对应一个函数调用,每一帧都包含了参数信息(args), 局部变量信息(locals), 寄存器信息(registers)和函数执行时的地址。

gdb使用一个数值来标识堆栈帧的层级,最后被调用到函数为0,往上值递增。

比如利用gdb调起示例程序,设置函数断点add_item, 然后在程序目录下执行

"echo 0> add", 可以让程序在add_item停下来,这个时候执行bt

图3-1 堆栈回溯

上图handle_add_request就是frame 1。

通过 "frame <number> " 指令可以切换程序到指定的栈帧,从而方便结合info命令查看对应栈帧的信息。

比如info locals获取局部变量信息

info args获取函数入参信息

info frame获得当前栈帧信息

图3-2 栈帧信息

3.3 符号文件

set sysroot <path>是最常用的命令,用来设置被搜索符号文件路径的前缀,值只能是一个

如果被搜索的符号文件路径有多个,则使用set solib-search-path <path1:path2>命令

也可以使用symbol-file <filename>直接指定从<filename>读取符号表

通常系统里面较复杂的应用会链接多个动态库,可以使用info sharedlibrary来查看动态链接库的符号是否加载

3.4 查看数据

p 和 x 是gdb用来查看数据最常用的两个命令。

3.4.1 p命令查看变量

p是print的缩写,通常使用如下命令来查看变量值

p /f <expr>

<expr>表示变量名, /f 则用来设置输出格式(具体定义见Output-Formats),可以省略,默认以16进制输出。常用格式如下:

  • x 16进制
  • d 10进制
  • t 二进制
  • a 打印离这个地址最近的符号,可以用于查找一个未知地址处于哪个函数中
  • s 字符串

示例:

图3-3 p命令查看变量

3.4.2 x命令查看内存

查看一块buffer/内存地址对应的内容是调试时的一个常见需求。x是examine的缩写,可以使用x命令来查看内存

x/nfu <addr>

其中<addr>表示内存地址,/nfu是命令参数,用来指定要查看的内存的size和格式。

  • n 输出读取的单元的个数,默认是1
  • f 输出格式,和 p命令的格式一致,如果<addr>为指令地址,还可以用"i"输出指令
  • u 每次读取的字节数, b表示单字节, h表示双字节,w 表示四字节 g表示八字节

常见用法示例

x/24sb 0x404da8 //字符串形式看一块内存

x/24cb 0x404da8 //以char形式看一块内存

x/24xb 0x404da8 //以16进制形式看一块内存

x/24db 0x404da8 //以10进制形式看一块内存

x/8xw $sp //查看sp开始的8个子的内容

x/I $pc //查看下一条指令

图3-4 x命令查看内存地址

3.5 设置程序执行参数和环境变量

设置程序执行参数有三种方法

  • gdb <program>, 然后进入gdb后,输入 "r <arg1> <arg2> ...<argN>
  • gdb --args <program> <arg1> <arg2>...<argN>
  • 进入gdb后,使用"set args <arg1> <arg2>...<argN>" 设定参数,再运行r

"show args" 可以显示当前执行参数

"set evn varname [=value] "命令可以设置程序执行时的环境变量。

[=value]可选,如果不填,表示null

"unset env varname"命令则可以将varname从环境变量中移除

"show env [varname]"命令可以显示环境变量的值,如果[varname]没有传,则显示所有环境变量。

比如

set env USE_HTTPS=1

3.6 查看源代码

gdb提供的断点功能非常强大,如果你经常使用这个功能,建议认真阅读gdb文档Breakpoints这章节,可以提高效率。

3.6.1 设置源代码搜索路径

directory <dirname> 命令可以增加一个目录到源代码搜索路径

set substitute-path <from> <to> 命令可以设置源代码路径替换规则

比如target上的image通常是在公司的编译服务器上编译的,android surfaceflinger的一个源文件的路径是/disk2/jekins/workspace/android/frameworks/native/services/surfaceflinger/Client.cpp,

而我本机surface_flinger目录地址是/home/ututu/project/surfaceflinger

可以设置

set substitute-path /disk2/jekins/workspace/android/frameworks/native/service/surfaceflinger /home/ubutu/project/surfaceflinger

这样查询源代码的时候会自动替换对应的源代码路径。

当然,如果你下载的是整个aosp,你可以直接使用directory命令设置搜索路径

directory /home/ubuntu/branch/android_latest

3.6.2 查看源代码

使用"list <location> "来显示源代码

这里<location>可以是当前源代码的行号或者函数名

比如

list 120

list add_item

不带location的list命令显示更多行数

list -则是在当前显示的第一行往前显示

3.6.3 查看汇编

disassemble

disassemble /m

disassemble命令可以查看汇编代码

当带上 /m选项的时候,则可以同时显示汇编和源代码

3.7 断点

gdb断点功能非常丰富,如果你要非常频繁使用断点调试功能,gdb文档的Breakpoints这一章节值得花时间去看看。

3.6.1 常规操作

break <location> 命令用来设置一个断点

<location>可以是 main.cpp:169 这种行号, 可以是main.cpp:add_item这种函数函数名。 break则可以简写为b。

info b可以用来列出当前的断点

delete breakpoints <ids> 可以用来清除一个或者多个断点

其中<ids>对应的是info b里面列出来的断点的id列表,如果有多个,用空格分开。我更经常使用的是 d <ids>这个简写命令。

比如

d 6 7

删除id为6和7的断点

也可以使用clear <location>命令来清除一个断点,这里的<location>和设置断点用的location是一个意思。

断点还可以暂时disable

disable <ids>

如果要恢复暂时disable的命令,则使用enable命令

enable <ids>

3.6.2 条件断点

break <location> if <cond>

命令可以设置条件断点

示例:

图3-5 条件断点

3.6.3 watchpoints和catchpoints

广义的断点还包括watchpoints和catchpoints

watchpoints被称为数据断点,可以使用它来在表达式改变的时候停止执行,一般可以用来监控变量/内存值是否被读/写,从这个意义上,可以称之为读/写断点。

watch <expression>

rwatch <expression>

awatch <expression>

以上三个命令用来设置watchpoints,其中watch用来设置写断点,rwatch设置读断点,awatch设置读写断点。<expr>可以是变量,也可以是表达式。通常系统能够支持的watchpoint个数是有限的。

示例:

图3-6 watchpoints

可以使用Catchpoints来让debugger在某些程序事件,比如c++ exception,syscall,fork或者加载共享库的时候停下来。

catchpoints和watchpoints都依赖于target上系统的实现。

示例:

图3-7 catch-points

3.8 程序执行

当程序因为断点,停止执行的时候,使用如下命令来控制程序的执行。

r(un)

c(ontinue)

s(tep)

n(ext)

u(til)

finish

r命令用来执行程序,会从头开始。c命令则在当前断点下继续执行程序。s命令会跟踪到下一个指令,n命令则继续当前函数的下一个代码行。u命令让程序执行,直到指定的位置再停下来。finish指令则继续执行程序直到当前函数结束(也就是当前函数返回后,他的调用函数的下一行处停下来)。

3.8 多线程调试

"info threads" 命令可以列出当前运行的所有线程

"thread <id>" 可以切换线程

示例:

图3-8 多线程调试

设置断点的时候也可以指定线程

break <location> thread <id>

可以对所有的线程执行同一个命令

thread apply <thread-id-list | all> args

示例:

图3-9 多线程执行同一个命令

 

看到这里的同学,相信你一定有足够的耐心和悟性,现在需要的是拿起武器,在实战中积累你的经验!!!

参考

与[转帖]gdb调试相似的内容:

[转帖]gdb调试

https://zhuanlan.zhihu.com/p/395115890?utm_id=0 写这个文档的目的,是让有一定软件开发基础的同学,可以较为快速地入门gdb的使用。gdb的命令非常多,在做了简短的介绍后,我挑选了最高频使用的命令/场景,希望能够让读者一个小时拥有即战力。 为了让读者对gd

[转帖]GDB调试core文件

在Linux环境下C程序经常会出现A segmentation fault(段错误),如果我们的程序只有几十行,那么我们可以通过printf输出调试来找到哪个地方出现了异常,但如果是在项目中,如果我们还是通过print找查找错误,那么效率会很低。那么我们来学习一下Linux环境下通过core文件来找

[转帖]gdb调试常见命令详细总结(附示例操作)

一、简介 通过gdb调试我们可以监控程序执行的每一个细节,包括变量的值、函数的调用过程、内存中数据、线程的调度等,从而发现隐藏的错误或者低效的代码,程序的调试过程主要有:单步执行,跳入函数,跳出函数,设置断点,设置观察点,查看变量。 本文将主要介绍linux下的gdb调试工具常用的命令和具体的使用实

[转帖]gdb进阶调试技巧

https://www.jianshu.com/p/9bdaa0644dba 整理一下在linux下C/C++用gdb工具debug一些提高效率的操作。基本的gdb操作就不在这里赘述了。 打印各种变量x 命令在gdb中可以使用x命令,来打印内存中的值。具体的格式是x/nfu addr。 含义为以f格

[转帖]gdb 常用命令

https://www.cnblogs.com/xvic/p/15997498.html 栈信息 不管是操作转储文件还是用GDB设置断点进行调试,都可以输入 (gdb)bt 打印栈内容进行查看。一般的宕机BUG,看下宕机的位置,然后看下源代码基本就可以解决了。但是很多情况下简单的 (gdb)bt 还

[转帖]如何快速查看进程/子线程堆栈

背景:分析现网问题时,有时需要快速查看某个进程/子线程堆栈调用,便于进一步分析问题,现提供几种不同获取进程堆栈方法。 实现方法: 1.使用gdb attach 调试进程,使用gdb相关cmd调试进程 # gdb -p pid 进入gdb后,可通过 (gdb)bt 查看主进程堆栈 (gdb)info

[转帖]关于gdb相关的几个工具的说明

https://phpor.net/blog/post/846 使用rpm命名查看gdb的rpm包,主要由下面几个程序:/usr/bin/gcore/usr/bin/gdb/usr/bin/gdbserver/usr/bin/gdbtui/usr/bin/gstack 其中:gcore 是生成cor

[转帖]记一次使用gdb诊断gc问题全过程

https://www.cnblogs.com/codelogs/p/17092141.html 简介# 上次解决了GC长耗时问题后,系统果然平稳了许多,这是之前的文章《GC耗时高,原因竟是服务流量小?》然而,过了一段时间,我检查GC日志时,又发现了一个GC问题,如下:从这个图中可以发现,我们GC有

[转帖] 记一次使用gdb诊断gc问题全过程

记一次使用gdb诊断gc问题全过程 原创:扣钉日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处。 简介# 上次解决了GC长耗时问题后,系统果然平稳了许多,这是之前的文章《GC耗时高,原因竟是服务流量小?》然而,过了一段时间,我检查GC日志时,又发现了一个GC问题,如下:从这个图中可

[转帖]程序运行崩溃(segfault)的排查方法

这篇博文记录的非常详细:https://blog.csdn.net/zhaohaijie600/article/details/45246569 我的笔记: 写的C++程序老是运行两三天就挂了,关键是挂的时候连“segment fault”都不显示。动用了gdb、valgrind还是没办法,最后还是