gdb基础

四月 08, 2022 [gdb] #gdb

gdb基础

Debug是啥?

Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序 Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用

本地运行没问题,更换服务器就跑不起来 这个问题就是Debug版本带了一些调试信息,这可能会调用一些dll文件动态加载,其他主机可能缺少,运行报错... 解决办法就是生成Release版本.

对比debug release

测试代码 test.cpp

#include <iostream>
#include <string>
using namespace std;

void func(const string srcstring& des) {
  des = src;
}
int main() {
  string s = "hellow world";
  string ss;
  cout << "hello world" << endl;
  func(sss);
  return 0;
}

编译测试代码大小/符号

gcc -g -o test-debug      test.c
gcc -o    test-debug-temp test.c
objcopy --strip-debug test-debug-temp test-release
strip test-release

readelf -s test-debug
readelf -s test-debug-temp
readelf -s test-release

调试release版本代码

由于release没有符号信息,在输出的时候只打印了地址,无法直观的看到函数调用位置 Debug模式下可以使用断言,但是在Release模式下断言无效,当条件不满足的时候继续往下执行. 在经过优化后,编译器可能会直接从寄存器中取值,而不从内存中进行取值. 在嵌入式系统中存储容量有限,调试版本可能无法保存到存储器中进行运行 使用版本控制工具进行管理代码,每次发布一个版本在出问题之后都可以及时通过源码分析问题. 由于在Release版本中不包含调试信息,调试起来难度比较大,我们可以使用发行版本的源码生成一个携带调试信息的版本来进行调试.

第一种方式是从调试版本软件中提取符号表,利用Binutils工具获取符号表:

objcopy --only-keep-debug debug.exe debug.symbol
gdb --symbol=debug.symbol -exec=release

加载成功之后就可以像debug版本一样设置断点和查看局部变量信息.

第二种方式是直接指定从调试版本中获取符号:

gdb --symbol=debug.exe -exec=release 如果不是很确定是否包含调试信息readelf -S debug.exe|grep debug

断点设置

gdb断点分类: 以设置断点的命令分类: breakpoint 可以根据行号,函数,条件生成断点. watchpoint 监测变量或者表达式的值发生变化时产生断点. catchpoint 监测信号的产生.例如c++的throw,或者加载库的时候. gdb中的变量从1开始标号,不同的断点采用变量标号同一管理,可以 用enable,disable等命令管理,同时支持断点范围的操作,比如有些命令接受断点范围作为参数.

break及break变种详解:

相关命令有break,tbreak,rbreak,hbreak,thbreak,后两种是基于硬件的,先不介绍.

break 与 tbeak

break,tbreak可以根据行号,函数,条件生成断点.tbreak设置方法与break相同,只不过tbreak只在断点停一次,过后会自动将断点删除,break需要手动控制断点的删除和使能.

break 可带如下参数: linenum 本地行号,即list命令可见的行号 filename:linenum 制定个文件的行号 function 函数,可以是自定义函数也可是库函数,如open filename:function 制定文件中的函数 condtion 条件 *address 地址,可是函数,变量的地址,此地址可以通过info add命令得到.

例如:

break 10
break test.c:10
break main
break test.c:main
break system
break open

如果想在指定的地址设置断点,比如在main函数的地址出设断点. 可用info add main 获得main的地址如0x80484624,然后用break *0x80484624. 条件断点就是在如上述指定断点的同时指定进入断点的条件. 例如:(假如有int 类型变量 index)

break 10 if index == 3
tbreak 12 if index == 5

rbreak

rbreak 可以跟一个规则表达式.rbreak + 表达式的用法与grep + 表达式相似.即在所有与表达式匹配的函数入口都设置断点. rbreak list_* 即在所有以 list_ 为开头字符的函数地方都设置断点. rbreak ^list_ 功能与上同.

查看断点信息

info break [break num ] info break 可列出所有断点信息,info break 后也可设置要查看的break num如: info break 1 列出断点号是1的断点信息

用gdb怎么在同名文件中的同名函数设置断点?

问题是这样的,程序加载了几个动态库,每个动态库提供同样的调用接口,想在某一个动态库的一个接口函数中设置断点. 比如b fun(),gdb只会在它找到的第一个动态库的fun()设置断点,请问有什么办法能指定动态库设置断点.每个动态库的接口函数所在的文件名都是一样的,函数名也相同. 也许可以这样解决:

b absolute/path/to/yourfile: linenumber

gdb还可以在函数地址哪里下断点, 如果有函数地址 break *address

打印表达式

print 表达式:简记为 p ,其中"表达式"可以是任何当前正在被测试程序的有效表达式,比如当前正在调试C语言的程序,那么"表达式"可以是任何C语言的有效表达式,包括数字,变量甚至是函数调用. print a:将显示整数 a 的值.除了这种方式外,还有一些特殊场景. 如果一个变量在多个文件或函数中有定义,使用p 'testGdb.h'::a的方式打印testGdb.h文件中的变量;使用p 'main'::b的方式打印main函数中的变量.

  1. 使用p *ptr来打印指针指向的内容.如果是个数组,那么可以通过@指定打印的长度,比如p *ptr@a可以打印a长度的数据,或者只跟@,那么会把所有的内容都打出来.
  2. 使用$打印上一个变量,假如有一个链表,有next成员变量,那么使用p *linkNode之后,可以使用p *$.next不间断的打印链表的下一个节点的信息.
  3. 如果打印一个数组的成员,可以通过这种方式连续的打印.set $index=0,p b[$index + 1],使用类似环境变量的方式定义一个变量,然后累加打印,不用自己再手动修改下标打印了.
  4. 除了使用默认的格式打印外,还可以指定格式打印,可以指定的格式有很多,比如: p/x 按十六进制格式显示变量. p/d 按十进制格式显示变量. p/u 按十进制格式显示无符号整型. p/o 按八进制格式显示变量. p/t 按二进制格式显示变量. p/a 按十六进制格式显示变量. p/c 按字符格式显示变量. p/f 按浮点数格式显示变量.
  5. print ++a:将把 a 中的值加1,并显示出来
  6. print name:将显示字符串 name 的值
  7. print gdb_test(22):将以整数22作为参数调用 gdb_test() 函数
  8. print gdb_test(a):将以变量 a 作为参数调用 gdb_test() 函数
  9. examine(简写x):可以查看内存地址中的值,用法为x/[n][f][u] addr,详细介绍如下.一个例子是x/4tb &e,打印4个字节,以二进制的形式打印. n,表示要显示的内存单元数,默认值为1. f,表示要打印的格式,见print的控制格式. u,要打印的单元长度,常见的长度有b 字节,h 两个字节,w 四个字节,g 八字节. addr,要打印的地址.
  10. display 表达式:在单步运行时将非常有用,使用display命令设置一个表达式后,它将在每次单步进行指令后,紧接着输出被设置的表达式及值.如: display a
  11. watch 表达式:设置一个监视点,一旦被监视的"表达式"的值改变,gdb将强行终止正在被调试的程序.如: watch a
  12. whatis :查询变量或函数
  13. info function: 查询函数扩展
  14. info locals: 显示当前堆栈页的所有变量
  15. info registers:打印寄存器信息
  16. info frame:打印栈帧中存储的信息

pretty printer打印自己的数据结构

比如你有一个结构体很多数据成员

struct MyStruct {
  std::name mName;
  std::map mField1;
  std::set mField2;
  int mI;
  int mj;
};

但是你大部分时候打印都是只看字段mName和mI,那么就可以定义一个针对这个数据结构的pretty printer, 这样大部分时候你就只看到需要的字段.而不用在几十个字段找你所关心. 如果不使用任何的pretty printer,打印一个MyStruct的数据结构会得到

$2 = {mName = {static npos = <optimized out>,
    _M_dataplus = {<std::allocator<char>> = {<__gnu_cxx::new_allocator<char>> = {<No data fields>}, <No data fields>},
      _M_p = 0x618c38 "student"}}, mField1 = {_M_t = {
      _M_impl = {<std::allocator<std::_Rb_tree_node<std::pair<int const, std::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >> = {<__gnu_cxx::new_allocator<std::_Rb_tree_node<std::pair<int const, std::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >> = {<No data fields>}, <No data fields>}, <std::_Rb_tree_key_compare<std::less<int> >> = {
          _M_key_compare = {<std::binary_function<int, int, bool>> = {<No data fields>}, <No data fields>}}, <std::_Rb_tree_header> = {
          _M_header = {_M_color = std::_S_red, _M_parent = 0x0, _M_left = 0x7fffffffe4e0, _M_right = 0x7fffffffe4e0},
          _M_node_count = 0}, <No data fields>}}}, mField2 = {_M_t = {
      _M_impl = {<std::allocator<std::_Rb_tree_node<std::basic_string<char, std::char_traits<char>, std::allocator<char> > > >> = {<__gnu_cxx::new_allocator<std::_Rb_tree_node<std::basic_string<char, std::char_traits<char>, std::allocator<char> > > >> = {<No data fields>}, <No data fields>}, <std::_Rb_tree_key_compare<std::less<std::basic_string<char, std::char_traits<char>, std::allocator<char> > > >> = {
          _M_key_compare = {<std::binary_function<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool>> = {<No data fields>}, <No data fields>}}, <std::_Rb_tree_header> = {_M_header = {
            _M_color = std::_S_red, _M_parent = 0x0, _M_left = 0x7fffffffe510, _M_right = 0x7fffffffe510},
          _M_node_count = 0}, <No data fields>}}}, mI = 3, mj = 4}

看起来会头皮发麻!如果使用gdb 自带的STL pretty printer,那么我们会得到如下简洁的结果,

(gdb) p s
$1 = {mName = "student", mField1 = std::map with 0 elements, mField2 = std::set with 0 elements, mI = 3, mj = 4}

如果自己编写pretty printer,那么就会得到如下的结果,

(gdb) p s
$2 = MyStruct
 name: "student"  integer: 3

这样子,只会打印自己关心的数据,如果希望看看原始的数据,那么p /r s

整体思路

需要做的是三件事情:

  1. 定义打印类,提供to_string()方法,这个方法返回你要打印出来的字符串。
  2. 判断一个value,是否需要使用你定义的类来打印。
  3. 注册你的判断函数到gdb pretty printing里面

定义打印类

class MyPrinter:
    def __init__(self, val):
        self.val = val
    def to_string(self):
         return ”name: {}  integer: {}".format(self.val['mName'], self.val['mI']

判断一个value,是否需要使用自己定义的打印类

def lookup_pretty_printer(val):
    if val.type.code == gdb.TYPE_CODE_PTR:
        return None # to add
    if 'MyStruct' == val.type.tag:
        return MyPrinter(val)
    return None

注册到gdb

gdb.printing.register_pretty_printer(
    gdb.current_objfile(),
    lookup_pretty_printer, replace=True)

将下面的程序编译,并测试

struct MyStruct {
  std::string mName;
  std::map<int, std::string> mField1;
  std::set<std::string> mField2;
  int mI;
  int mj;
};

int main() {
   MyStruct s = {std::string("student"), lm, ls, 3, 4}
   return 0;
}

Pretty-Printer

gdb printer the printer only handles one type, whereas a library typically has several types. One could install a lookup function for each desired type in the library, but one could also have a single lookup function recognize several types. The latter is the conventional way this is handled. If a pretty-printer can handle multiple data types, then its subprinters are the printers for the individual data types. The gdb.printing module provides a formal way of solving these problems (see gdb.printing). Here is another example that handles multiple types.

hese are the types we are going to pretty-print:

struct foo { int a, b; };
struct bar { struct foo x, y; };

Here are the printers:

class fooPrinter:
    """Print a foo object."""

    def __init__(self, val):
        self.val = val

    def to_string(self):
        return ("a=<" + str(self.val["a"]) +
                "> b=<" + str(self.val["b"]) + ">")

class barPrinter:
    """Print a bar object."""

    def __init__(self, val):
        self.val = val

    def to_string(self):
        return ("x=<" + str(self.val["x"]) +
                "> y=<" + str(self.val["y"]) + ">")

This example doesn’t need a lookup function, that is handled by the gdb.printing module. Instead a function is provided to build up the object that handles the lookup.

import gdb.printing

def build_pretty_printer():
    pp = gdb.printing.RegexpCollectionPrettyPrinter(
        "my_library")
    pp.add_printer('foo', '^foo$', fooPrinter)
    pp.add_printer('bar', '^bar$', barPrinter)
    return pp

And here is the autoload support:

import gdb.printing
import my_library
gdb.printing.register_pretty_printer(
    gdb.current_objfile(),
    my_library.build_pretty_printer())

Finally, when this printer is loaded into GDB, here is the corresponding output of ‘info pretty-printer’:

(gdb) info pretty-printer
my_library.so:
  my_library
    foo
    bar

查询运行信息

where/bt :当前运行的堆栈列表; bt/backtrace 显示当前调用堆栈 up/down 改变堆栈显示的深度 set args 参数:指定运行时的参数 show args:查看设置好的参数 info program: 来查看程序的是否在运行,进程号,被暂停的原因.

分割窗口

layout:用于分割窗口,可以一边查看代码,一边测试: layout src:显示源代码窗口 layout asm:显示反汇编窗口,默认汇编以att格式显示,如果想改为intel的格式,那么执行命令set disassembly-flavor intel即可改为intel格式.查看当前的汇编格式可以通过命令show disassembly-flavor查看. layout regs:显示源代码/反汇编和CPU寄存器窗口 layout split:显示源代码和反汇编窗口 info win: 显示所有的窗口 layout prev: 切换到前一个窗口 layout next: 切换到后一个窗口 fs <窗口名>: 将焦点移动到某个窗口上 Ctrl + L:刷新窗口

使用GDB的源代码

在调试程序的过程中,可以自由地查看相关的源代码(如果有源代码的话)是一项最基本的特性. GDB之所以能够知道对应的源代码,是因为调试版的可执行程序中记录了源代码的位置; 因为源代码的位置在编译之后可能会移动到其它地方,所以GDB还会在当前目录中查找源代码,另外GDB也允许明确指定源代码的搜索位置. 默认情况下,GDB在编译时目录中搜索,如果失败则在当前目录中搜索,即$cdir:$cwd,其中$cdir指的是编译时目录(compilation directory),$cwd指的是当前工作目录(current working directory). 在GDB中使用查看源代码相关的命令时,有一个当前文件的概念,当命令的位置参数没有限定一个文件的时候(不论是明确限定还是隐含限定),将使用当前文件.当前文件默认是main函数所在文件,如果程序当前正处于断点位置,则断点所在文件即为当前文件. 与当前文件的概念类似,还存在一个当前行的概念,它默认为main函数的开始处.如果使用gdb载入一个可执行文件,然后单单执行一条简单的list命令,你会发现输出的源代码并非是从第一行开始的,这是因为当前行默认在main函数附近处的缘故.

1.设置和获取源代码显示数量:

默认情况下,GDB显示指定位置处以及其前后的10行代码,但是这是一个可设置的值.

set listsize count:设置list命令显示的源代码数量最多为count行,0表示不限制行数.
show listsize:显示listsize的值.

2.编辑源代码:

在一些情况下,我们希望在编辑器中显示或者编辑源代码,GDB允许我们使用自己喜欢的编辑器. 可在环境变量EDITOR中指定GDB使用的编辑器,例如:EDITOR=/usr/bin/gedit;export EDITOR; gdb edit location:在编辑器中编辑位置location处的源代码,如果省略location,则编辑当前位置.

3.搜索源代码:

有的时候,我们希望在当前文件中进行搜索,GDB提供了这样的命令. search regexp:从当前行的下一行开始向前搜索. rev regexp :从当前行的上一行开始向后搜索. 有的时候,你会发现search命令总是提示"Expression not found",这是因为当前行可能已经是最后一行了,特别是文件很短的时候. 这里需要注意的是,任何list命令都会影响当前行的位置,并且由于每次都是多行输出,所以对当前行的影响并非简单地向前一行或者向后一行. search命令本身也会影响当前行的位置.

4.源代码位置:

GDB之所以可以查看到源代码,是因为它知道源代码放在哪里. 在一个调试会话中,GDB维护了一个源代码查找目录列表,默认值是编译目录和当前工作目录.当GDB需要一个源文件的时候,它依次在这些目录中查找,直到找到一个或者抛出错误. GDB还维护了一个路径替换规则,将要搜索的原始路径按照找到的第一个规则做前缀替换,然后再在源码搜索目录中查找文件. GDB允许明确指定源代码位置,或者路径替换规则,以应付源代码位置迁移的情况.

directory path-list:将一个或者多个源代码搜索目录加入到当前源码搜索目录列表的前面,目录之间使用空格间隔.
directory:不带参数的directory将源码搜索目录恢复为默认值.
set directories path-list:将源码目录设置为path-list,但是会补上默认目录.
show directories:显示源码搜索目录列表.
set substitute-path from to:设置目录替换规则,放置在规则列表的末端.
unset substitute-path [path]:删除path对应的替换规则,或者删除所有的替换规则.
show substitute-path [path]:显示path对应的替换规则,或者显示所有的替换规则.

5.查看机器码:

在一些必要的时候,我们需要查看汇编代码来诊断问题.GDB提供了这种可能. GDB提供了两种能力:显示源代码位置与指令地址之间的映射;显示指定位置的汇编代码.

info line linespec:显示源代码linespec处对应的汇编地址范围.
info line *addr:显示地址addr处对应的源代码位置.
disassemble,disassemble /m,disassemble /r:显示指定地址范围内的汇编代码,
    有4种使用形式,第一种不带参数,显示当前正在执行的函数的汇编代码;
                  第二种是一个参数,显示该地址所在函数的汇编代码;
                  第三种是两个参数的disassemble start,end,显示地址[start,end)内的汇编代码;
                  第四种是两个参数的disassemble start,+length,显示地址[start,start+length)内的汇编代码.参数可以是16进制的地址,
                      也可以是函数名./m表示混合输出源代码和汇编代码,/r表示混合输出二进制和汇编代码.
set disassembly-flavor instruction-set:设置显示汇编代码时使用的风格,目前只针对intel x86系列,可取的值为att和intel,默认是att.
show disassembly-flavor:显示disassembly-flavor设置
set disassemble-next-line on|off|auto:当程序停止下来的时候,是否显示下一行源代码的汇编代码,默认为off.
show disassemble-next-line:显示disassemble-next-line设置.

6.显示指定位置的源代码:

list命令可用于显示指定位置处的源代码.list命令会影响当前行和当前文件.
list命令有多种方式指定要显示的源代码范围,可以是行号,函数名,甚至是指令地址.
常用的如下:
list linenum:显示指定行数附近的代码.
list function:显示指定函数附近的代码.
list *addr:显示指定地址附近的代码.

如何在GDB中关联源代码

如果你手头上有一个你自己或者别人开发的程序,但它有一些bug.或者你只是想知道这个程序是如何工作的.怎么办呢?你需要一个调试工具. 现在很少有人会直接对着汇编指令进行调试,通常情况下,大家都希望能对照着源代码进行调试.但是,你调试使用的主机,一般来说并不是构建程序的那台,因此你会看到如下这个令人沮丧的消息:

$ gdb -q python3.7 Reading symbols from python3.7...done. (gdb) l 6 ./Programs/python.c: No such file or directory. 我经常会看到这些报错信息,并且对于调试程序来说,这也非常重要.所以,我认为我们需要详细了解一下GDB是如何在调试会话中显示源代码的.

调试信息

首先,我们从调试信息开始.调试信息是由编译器生成的存在于二进制文件中的特殊段,供调试器和其他相关的工具使用. 在GCC中,有一个著名的-g标志用于生成调试信息.大多数使用某种构建系统的项目都会在构建时默认包含或者通过一些标志来添加调试信息. 例如,在CPython中,你需要执行以下命令:

$ ./configure --with-pydebug $ make -j -with-pydebug会在调用GCC时添加-g选项.

这个-g选项会生成二进制的调试段,并插入到程序的二进制文件中.调试段通常采用DWARF格式.对于ELF二进制文件来说,调试段的名称一般都是像.debug_ *这样的,例如 .debug_info或者.debug_loc.这些调试段使得调试程序成为可能,可以这么说,它是汇编级别的指令与源代码之间的映射.

要查看程序是否包含调试符号,你可以使用objdump命令列出二进制文件的所有段: $ objdump -h ./python

python: file format elf64-x86-64

Sections: Idx Name Size VMA LMA File off Algn 0 .interp 0000001c 0000000000400238 0000000000400238 00000238 20CONTENTS, ALLOC, LOAD, READONLY, DATA1 .note.ABI-tag 00000020 0000000000400254 0000000000400254 00000254 22 CONTENTS, ALLOC, LOAD, READONLY, DATA ... 25 .bss 00031f70 00000000008d9e00 00000000008d9e00 002d9dfe 25ALLOC26 .comment 00000058 0000000000000000 0000000000000000 002d9dfe 20 CONTENTS, READONLY 27 .debug_aranges 000017f0 0000000000000000 0000000000000000 002d9e56 20CONTENTS, READONLY, DEBUGGING28 .debug_info 00377bac 0000000000000000 0000000000000000 002db646 20 CONTENTS, READONLY, DEBUGGING 29 .debug_abbrev 0001fcd7 0000000000000000 0000000000000000 006531f2 20CONTENTS, READONLY, DEBUGGING30 .debug_line 0008b441 0000000000000000 0000000000000000 00672ec9 20 CONTENTS, READONLY, DEBUGGING 31 .debug_str 00031f18 0000000000000000 0000000000000000 006fe30a 20CONTENTS, READONLY, DEBUGGING32 .debug_loc 0034190c 0000000000000000 0000000000000000 00730222 20 CONTENTS, READONLY, DEBUGGING 33 .debug_ranges 00062e10 0000000000000000 0000000000000000 00a71b2e 2**0 CONTENTS, READONLY, DEBUGGING

或者使用readelf命令:

$ readelf -S ./python

There are 38 section headers, starting at offset 0xb41840:

Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .interp PROGBITS 0000000000400238 00000238 000000000000001c 0000000000000000 A 0 0 1

...

[26] .bss NOBITS 00000000008d9e00 002d9dfe 0000000000031f70 0000000000000000 WA 0 0 32 [27] .comment PROGBITS 0000000000000000 002d9dfe 0000000000000058 0000000000000001 MS 0 0 1 [28] .debug_aranges PROGBITS 0000000000000000 002d9e56 00000000000017f0 0000000000000000 0 0 1 [29] .debug_info PROGBITS 0000000000000000 002db646 0000000000377bac 0000000000000000 0 0 1 [30] .debug_abbrev PROGBITS 0000000000000000 006531f2 000000000001fcd7 0000000000000000 0 0 1 [31] .debug_line PROGBITS 0000000000000000 00672ec9 000000000008b441 0000000000000000 0 0 1 [32] .debug_str PROGBITS 0000000000000000 006fe30a 0000000000031f18 0000000000000001 MS 0 0 1 [33] .debug_loc PROGBITS 0000000000000000 00730222 000000000034190c 0000000000000000 0 0 1 [34] .debug_ranges PROGBITS 0000000000000000 00a71b2e 0000000000062e10 0000000000000000 0 0 1 [35] .shstrtab STRTAB 0000000000000000 00b416d5 0000000000000165 0000000000000000 0 0 1 [36] .symtab SYMTAB 0000000000000000 00ad4940 000000000003f978 0000000000000018 37 8762 8 [37] .strtab STRTAB 0000000000000000 00b142b8 000000000002d41d 0000000000000000 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), l (large) I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific)

在我们刚刚编译的Python程序中,我们可以看到.debug_ *段,因此它是包含调试信息的. 调试信息是DIE(调试信息条目)的一个集合.每个DIE都有一个标签,用来表示DIE的类型以及它的属性,就像变量的名称和行号一样.

GDB如何寻找源代码

为了寻找源代码,GDB会解析.debug_info段并查找所有带有DW_TAG_compile_unit标签的DIE.具有此标签的DIE有两个主要属性DW_AT_comp_dir(编译目录)和DW_AT_name(名称),这就是源代码的路径.把这两个属性结合起来就是某个特定编译单元(对象文件)对应的源文件的完整路径. 要解析调试信息,你仍然可以使用objdump命令: 你可以看到这些解析出来的调试信息:

objdump -g ./python | vim -

Contents of the .debug_info section:

Compilation Unit @ offset 0x0: Length: 0x222d (32-bit) Version: 4 Abbrev Offset: 0x0 Pointer Size: 8 <0>: Abbrev Number: 1 (DW_TAG_compile_unit) DW_AT_producer : (indirect string, offset: 0xb6b): GNU C99 6.3.1 20161221 (Red Hat 6.3.1-1) -mtune=generic -march=x86-64 -g -Og -std=c99 <10> DW_AT_language : 12 (ANSI C99) <11> DW_AT_name : (indirect string, offset: 0x10ec): ./Programs/python.c <15> DW_AT_comp_dir : (indirect string, offset: 0x7a): /home/avd/dev/cpython <19> DW_AT_low_pc : 0x41d2f6 <21> DW_AT_high_pc : 0x1b3 <29> DW_AT_stmt_list : 0x04

GDB是这样读取的:地址从DW_AT_low_pc = 0×41d2f6到DW_AT_low_pc + DW_AT_high_pc = 0×41d2f6 + 0×1b3 = 0×41d4a9对应的源代码文件是位于/home/avd/dev/cpython路径下的./Programs/python.c文件,相当简单吧. 这是GDB向你显示源代码的整个过程:

解析.debug_info查找当前对象文件的DW_AT_name属性的DW_AT_comp_dir属性 按照路径DW_AT_comp_dir/DW_AT_name打开文件 显示文件的内容

如何告诉GDB源代码的位置

所以,要解决./Programs/python.c: No such file or directory.这个问题,我们必须在目标主机上存放源代码(复制或git clone过来),并执行以下任意一个操作:

1.重建源代码路径

你可以在目标主机上重建源代码路径,这样,GDB就能找到对应的源代码了.这是个愚蠢的办法,但是还是很有用的. 在我这个例子中,我执行了这个命令git clone https://github.com/python/cpython.git /home/avd/dev/cpython来检出所需的版本.

2.修改GDB源代码路径

你可以在调试会话中使用directory

命令让GDB关联正确的源代码路径:

(gdb) list 6 ./Programs/python.c: No such file or directory. (gdb) directory /usr/src/python Source directories searched: /usr/src/python:$cdir:$cwd (gdb) list 6 FreeBSD 7 <fenv.h> 8
9 10 MS_WINDOWS 11 int 12 wmain(int argc, wchar_t **argv) 13 { 14 return Py_Main(argc, argv); 15 }

3.设置GDB路径替换规则

如果目录结构层次比较复杂,有时候添加源代码路径是不够的.在这种情况下,你可以使用set substitute-path命令来添加源路径的替换规则.

(gdb) list 6 ./Programs/python.c: No such file or directory. (gdb) set substitute-path /home/avd/dev/cpython /usr/src/python (gdb) list 6 FreeBSD 7 <fenv.h> 8
9 10 MS_WINDOWS 11 int 12 wmain(int argc, wchar_t **argv) 13 { 14 return Py_Main(argc, argv); 15 }

4.把二进制文件移到源代码目录

你可以通过将二进制文件移动到源代码目录来改变GDB源代码路径. mv python /home/user/sources/cpython 因为GDB会试着在当前目录($cwd)下寻找源代码,所以这个做法也是可以的.

5.编译时增加-fdebug-prefix-map选项

你可以使用-fdebug-prefix-map = old_path = new_path编译选项来替代构建阶段的源路径.下面是在CPython项目中执行此操作的例子:

$ make distclean ## start clean $./configure CFLAGS="-fdebug-prefix-map=$(pwd)=/usr/src/python" --with-pydebug $ make -j 这样,我们就有了新的源代码路径:

$ objdump -g ./python ... <0>: Abbrev Number: 1 (DW_TAG_compile_unit) DW_AT_producer : (indirect string, offset: 0xb65): GNU C99 6.3.1 20161221 (Red Hat 6.3.1-1) -mtune=generic -march=x86-64 -g -Og -std=c99 <10> DW_AT_language : 12 (ANSI C99) <11> DW_AT_name : (indirect string, offset: 0x10ff): ./Programs/python.c <15> DW_AT_comp_dir : (indirect string, offset: 0x558): /usr/src/python <19> DW_AT_low_pc : 0x41d336 <21> DW_AT_high_pc : 0x1b3 <29> DW_AT_stmt_list : 0x0 ...

这个办法是最粗暴了,因为你可以将其设置为类似于`/usr/src/project-name’这样的路径,把源代码包安装到这个路径下,然后就可以任性地调试了.

总结

GDB通过以DWARF格式存储的调试信息来查找源代码信息.DWARF是一种非常简单的格式,实际上,它是一棵DIE(调试信息条目)树,它描述了程序的对象文件以及变量和函数. 有很多种方法可以让GDB找到源代码,其中最简单的方法是使用directory和set substitute-path命令,而-fdebug-prefix-map是最最强大的.

完整的例子

程序在编译的过程中会记录源文件的名字和路径,这个路径可能是绝对路径,比如 /mnt/d/main.cpp,也可能是相对路径 ../main.cpp ,究竟是哪一种取决于编译时使用的参数. 我们以绝对路径为例,比如文件名为 /mnt/d/main.cpp,我们可以把它拆分成包含路径和不包含路径两种形式:/mnt/d/main.cpp 和 main.cpp,当 SourcePathSet 中包含一个路径叫 /mnt/e时, gdb 搜索的路径包括以下几种:

/mnt/d/main.cpp /mnt/e/mnt/d/main.cpp /mnt/e/main.cpp 当源文件是相对路径 ../main.cpp 的时候,那么搜索的路径就变成了下面两个: /mnt/e/../main.cpp /mnt/e/main.cpp 说到这里你可能就明白了,当 gdb 找不到源文件的时候,修改 SourcePathSet 就可以了,把想让它搜索的路径添加到 SourcePathSet,如果符合它的搜索规则,那么就可以找到了.

目录集合的默认值

SourcePathSet 在 gdb 启动后开始生效,默认值并不是空,而是 $cdir:$cwd, 这又是什么鬼?其中的$cdir叫做编译目录,是代码在编译时记录到程序中的,$cwd 表示当前的调试目录,可以通过cd命令来修改, 要注意这个cd修改的是gdb会话中的当前目录,不会影响启动 gdb 前文件系统中的目录位置. 假设$cdir的值是/usr,cwd的值是/home/albert,我们又添加了/mnt/e到SourcePathSet中,那么此时SourcePathSet的值为/mnt/e:$cdir:$cwd,如果源文件的是/mnt/d/main.cpp,查找的目录就会出现以下几种:

/mnt/d/main.cpp /mnt/e/mnt/d/main.cpp /usr/mnt/d/main.cpp /home/albert/mnt/d/main.cpp /mnt/e/main.cpp /usr/main.cpp /home/albert/main.cpp 查看源代码文件名和编译目录 info source show dir 命令就可以显示 SourcePathSet 这个集合中都有哪些目录 pwd 查看当前目录

具体示例

新建三个文件 mainpro.cpp,mymath.h,mymath.cpp,目录结构和内容如下:

albert@home-pc:/mnt/d$ tree /mnt/d/mainpro/ /mnt/d/mainpro/ |-- core | -- mainpro.cpp -- kit |-- mymath.cpp `-- mymath.h

albert@home-pc:/mnt/d/mainpro$ g++ /mnt/d/mainpro/core/mainpro.cpp /mnt/d/mainpro/kit/mymath.cpp -g -o mainpro
albert@home-pc:/mnt/d/mainpro$ tree
.
|-- core
|   `-- mainpro.cpp
|-- kit
|   |-- mymath.cpp
|   `-- mymath.h
`-- mainpro

#2 directories, 4 files
albert@home-pc:/mnt/d/mainpro$ mkdir /mnt/e/newpro
albert@home-pc:/mnt/d/mainpro$ sudo mv core/ /mnt/e/newpro/
albert@home-pc:/mnt/d/mainpro$ sudo mv kit/ /mnt/e/newpro/
albert@home-pc:/mnt/d/mainpro$ mv mainpro /home/albert/

在 /home/albert 目录下启动 gdb 开始调试,先在 main 函数打断点,查询源文件路径和编译目录等信息;

albert@home-pc:~$ gdb -q mainpro
Reading symbols from mainpro...done.
(gdb) b main
Breakpoint 1 at 0x4008de: file /mnt/d/mainpro/core/mainpro.cpp, line 7.
(gdb) run
Starting program: /home/albert/mainpro

Breakpoint 1, main () at /mnt/d/mainpro/core/mainpro.cpp:7
7   /mnt/d/mainpro/core/mainpro.cpp: No such file or directory.
(gdb) info source
Current source file is /mnt/d/mainpro/core/mainpro.cpp
Compilation directory is /mnt/d/mainpro
Source language is c++.
Producer is GNU C++ 5.4.0 20160609 -mtune=generic -march=x86-64 -g -fstack-protector-strong.
Compiled with DWARF 2 debugging format.
Does not include preprocessor macro info.
(gdb) list
2   in /mnt/d/mainpro/core/mainpro.cpp
(gdb) pwd
Working directory /home/albert.
(gdb) show dir
Source directories searched: $cdir:$cwd
(gdb)

果然找不到源代码了,从上面的调试信息来看,可以得到以下信息:

源代码文件为 /mnt/d/mainpro/core/mainpro.cpp 程序编译目录为 /mnt/d/mainpro 当前目录为 /home/albert 而源代码查找列表中只有 $cdir:$cwd,说明只包含 /mnt/d/mainpro 和 /home/albert,那么查找的目录有:

/mnt/d/mainpro/core/mainpro.cpp /mnt/d/mainpro/mnt/d/mainpro/core/mainpro.cpp /home/albert/mnt/d/mainpro/core/mainpro.cpp /mnt/d/mainpro/mainpro.cpp /home/albert/mainpro.cpp 这些目录显然找不到源代码文件了,因为文件已经被我移动到 /mnt/e/newpro/ 目录下了,也就是 /mnt/e/newpro/core/mainpro.cpp,下面来尝试一些解决方法.

使用 dir 命令解决

刚才说了源代码查找集合 SourcePathSet 中只有 $cdir:$cwd,我们可以自己加一个

(gdb) dir /mnt/e/newpro/core/
Source directories searched: /mnt/e/newpro/core:$cdir:$cwd
(gdb) list
2   #include <iostream>
3   using namespace std;
4
5   int main()
6   {
7       int a = 1, b = 2;
8       mymath* m = new mymath();
9
10      int c = m->add(a, b);
11      cout << "c = " << c << endl;
(gdb)

这样就可以找到了,我们接着在 add 函数上下个断点,继续执行

(gdb) b mymath::add
Breakpoint 2 at 0x4009a6: file /mnt/d/mainpro/kit/mymath.cpp, line 6.
(gdb) c
Continuing.

Breakpoint 2, mymath::add (this=0x613c20, a=1, b=2) at /mnt/d/mainpro/kit/mymath.cpp:6
6   /mnt/d/mainpro/kit/mymath.cpp: No such file or directory.
(gdb) list
1   in /mnt/d/mainpro/kit/mymath.cpp
(gdb) info source
Current source file is /mnt/d/mainpro/kit/mymath.cpp
Source language is c++.
Producer is GNU C++ 5.4.0 20160609 -mtune=generic -march=x86-64 -g -fstack-protector-strong.
Compiled with DWARF 2 debugging format.
Does not include preprocessor macro info.
(gdb)

结果发现又找不到文件 /mnt/d/mainpro/kit/mymath.cpp 了,因为和之前不是一个文件,这个文件在其他的目录下,所以还要使用 dir 命令,把新的目录加到源代码查找集合 SourcePathSet 中:

(gdb) dir /mnt/e/newpro/kit/
Source directories searched: /mnt/e/newpro/kit:/mnt/e/newpro/core:$cdir:$cwd
(gdb) list
1   #include "../kit/mymath.h"
2   #include <iostream>
3   using namespace std;
4
5   int main()
6   {
7       int a = 1, b = 2;
8       mymath* m = new mymath();
9
10      int c = m->add(a, b);
(gdb)

这次又能成功找到了,可是如果有好多个文件要调试,难道要把所有的目录都加进去吗? 其实可以有简便方法的,在启动 gdb的时候可以指定搜索的源代码路径,这些路径都会被加到到源代码查找集合 SourcePathSet 中, 具体操作如下,先退出gdb,然后重新加参数启动如下:

albert@home-pc:~$ gdb -q mainpro `find /mnt/e/newpro/ -type d -printf '-d %p '`
Reading symbols from mainpro...done.
(gdb) show dir
Source directories searched: /mnt/e/newpro/kit:/mnt/e/newpro/core:/mnt/e/newpro:$cdir:$cwd
(gdb)

其实这条命令的本来面目是 gdb -q mainpro -d xxxxx,只不过这组合了 find 命令以后使用起来更加方便了,可以把指定目录下的子目录全都添加到参数中

使用 cd 命令解决

如果是临时调试倒是用不到上面设置启动参数那么麻烦,因为变量 $cwd 也在搜索集合中,既然在编译时记录的源文件被改变了位置,那么我们调整我们的当前位置,让代码出现搜索路径中,还是上面的这个例子:

albert@home-pc:~$ pwd
/home/albert
albert@home-pc:~$ gdb -q mainpro
Reading symbols from mainpro...done.
(gdb) b main
Breakpoint 1 at 0x4008de: file /mnt/d/mainpro/core/mainpro.cpp, line 7.
(gdb) r
Starting program: /home/albert/mainpro

Breakpoint 1, main () at /mnt/d/mainpro/core/mainpro.cpp:7
7   /mnt/d/mainpro/core/mainpro.cpp: No such file or directory.
(gdb) list
2   in /mnt/d/mainpro/core/mainpro.cpp
(gdb) cd /mnt/e/newpro/core/
Working directory /mnt/e/newpro/core.
(gdb) list
2   #include <iostream>
3   using namespace std;
4
5   int main()
6   {
7       int a = 1, b = 2;
8       mymath* m = new mymath();
9
10      int c = m->add(a, b);
11      cout << "c = " << c << endl;
(gdb)

上面的操作通过 cd /mnt/e/newpro/core/ 命令直接进入了源代码目录,当然就找到了,但是这还是会有点问题,当碰到需要调试好几个文件的时候就需要使用 cd 命令跳来跳去,要想一劳永逸,请看下面这个方法.

使用 set substitute-path 命令解决

我们移动源代码的时候往往会整个目录移动,或者说开发机和发布机上面的代码文件组织结构是一样,只是所在的磁盘位置是不一样的, 所以如果可以设置用一个路径替换原代码文件的路径就好了, set substitute-path from-path to-path 这个命令就可以达到想要的目的,这个命令还可以简写成 set substitute from-path to-path, 比如还是前面的例子,源代码从 /mnt/d/mainrpo 目录整体移动到了 /mnt/e/newpro 目录, 调试时找不到源代码可以使用 set substitute /mnt/d/mainrpo /mnt/e/newpro 命令来指定替换目录,这样就可以找到源代码啦,下面来测试一下:

albert@home-pc:~$ gdb -q mainpro
Reading symbols from mainpro...done.
(gdb) set substitute-path /mnt/d/mainrpo /mnt/e/newpro
(gdb) b main
Breakpoint 1 at 0x4008de: file /mnt/d/mainpro/core/mainpro.cpp, line 7.
(gdb) run
Starting program: /home/albert/mainpro

Breakpoint 1, main () at /mnt/d/mainpro/core/mainpro.cpp:7
7   /mnt/d/mainpro/core/mainpro.cpp: No such file or directory.
(gdb) cd /mnt/e/newpro/
Working directory /mnt/e/newpro.
(gdb) list
2   /mnt/d/mainpro/core/mainpro.cpp: No such file or directory.
(gdb) set substitute-path /mnt/d/mainpro /mnt/e/newpro
(gdb) list 0
1   #include "../kit/mymath.h"
2   #include <iostream>
3   using namespace std;
4
5   int main()
6   {
7       int a = 1, b = 2;
8       mymath* m = new mymath();
9
10      int c = m->add(a, b);
(gdb) info source
Current source file is /mnt/d/mainpro/core/mainpro.cpp
Compilation directory is /mnt/d/mainpro
Located in /mnt/e/newpro/core/mainpro.cpp
Contains 14 lines.
Source language is c++.
Producer is GNU C++ 5.4.0 20160609 -mtune=generic -march=x86-64 -g -fstack-protector-strong.
Compiled with DWARF 2 debugging format.
Does not include preprocessor macro info.
(gdb) pwd
Working directory /home/albert.
(gdb)

通过调试信息 Located in /mnt/e/newpro/core/mainpro.cpp 可以看到,果然在新的位置找到了源代码.

编译时使用相对路径和绝对路径解决时稍有区别

调试的时候找不到源码有多种解决方法,需要根据实际情况选择最合适的解决方案. 编译时使用绝对路径时,推荐使用 set substitute-path from-path to-path 的方式. 编译时使用相对路径时,使用 set substitute from-path to-path 或者 dir new-path 都可以. 对于临时查找一个问题,单独调试某一个文件时使用 cd 命令就可以搞定了. 直接在 gdb 环境输入 dir 命令回车确认,可以重置 dir 目录 或者 set dir 目录 命令修改过的源代码搜索目录集合.

远程gdb调试

启动gdbserver
一种是用gdbserver将进程启动:如 gdbserver IP:PORT release.exe.
另外一种是对于远程机器上已有的进程进行调试:gdbserver --attach IP:PORT PID,其中PID可以通过ps命令查看得到release版本的进程号:gdbserver --attach 192.168.1.1:8000 5925

在本地机器上通过gdb连接到上述远程待调试机器的gdbserver端口: gdb debug.exe,然后执行命令target remote IP:PORT,IP:port为上述gdbserver中启动的IP:Port ... (gdb) target remote IP:PORT

此时远程机器显示已经收到客户端的调试连接,如: Remote debugging from host 192.168.1.2

本地机器连接到远程机器以后,远程的进程会被halt住,此时可以在本地机器执行gdb命令来使远程进程继续执行.

gdb和gdbserver版本要一样?

编译gdbserver

编译GDB源码时只需要编译出gdbserver就可以了,交叉编译器自带arm-linux-gdb(client端)可以使用.

gdb-6.7.1 gdb-6.7.1/gdb/gdbserver/ --host=arm-linux --prefix=/work/install/gdbserver install 这时会在/work/install/gdbserver目录下生成bin/gdbserver,将其拷贝到nfs文件系统 /work/install/gdbserver bin/gdbserver /work/nfs/rootfs/bin

库问题

这里需要注意的是运行gdbserver还需要libthread_db库,若你自己做的文件系统内没有这个库的话需要从交叉编译器内拷一个过去.

-h (target) gdbserver: error while loading shared libraries: libthread_db.so.1: cannot open shared object file: No such file or directory

-avf lib/libthread_db* /work/nfs/rootfs_bluetooth_omap/lib/ /lib/libthread_db-1.0.so' -> /work/nfs/rootfs/lib/libthread_db-1.0.so' /lib/libthread_db.so.1' -> /work/nfs/rootfs/lib/libthread_db.so.1' 注:若不知道缺少什么库可以根据运行时错误提示拷贝或者用先用strace跟踪一下: -f -F -o strace.log gdbserver -h strace.log 发现如下字段: 872 writev(2, [{"gdbserver", 9}, {": ", 2}, {"error while loading shared libra"..., 36}, {": ", 2}, {"libthread_db.so.1", 17}, {": ", 2}, {"cannot open shared object file", 30}, {": ", 2}, {"No such file or directory", 25}, {"\n", 1}], 10) = 126 872 exit_group(127) = ? 得知缺少libthread_db.so.1库(红色部分标出).

调试过程

1)Target端建立远程调试服务

192.168.167.170:1234 obexftp (target) Process obexftp created; pid = 858 Listening on port 1234

2)Host端GDB加载要调试的程序 这里要调试的程序得是交叉编译过的,并且加了-g参数.不过大部分编译程序默认就是加了-g参数的,这点可以从编译时的log看出.

obexftp GNU gdb 6.6.50.20070301 (MontaVista 6.6.50-2.0.1.0702865 2007-03-26) Copyright (C) 2007 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "--host=i686-pc-linux-gnu --target=armv5tl-montavista-linux-gnueabi"...

3)连接到目标板调试服务

(gdb) target remote 192.168.167.15:1234 Remote debugging using 192.168.167.15:1234 Error while reading shared library symbols: Dwarf Error: Can't read DWARF data from '/opt/montavista/pro/devkit/arm/v5t_le/target/usr/lib/debug/lib/ld-2.5.90.so.debug' 0x400007a0 in _start () from /opt/montavista/pro/devkit/arm/v5t_le/target/lib/ld-linux.so.3 注:上面两行错误信息暂时不用管,原因还不清楚,但是暂时发现不影响使用. 连接成功后ARM板上的信息应该是这样的: 192.168.0.29:1234 arm0702_8.out Process arm0702_8.out created; pid = 228 Remote debugging from host 192.168.0.29 <----多出这一行 上面这行表示宿主机和开发板连接成功.现在我们就可以在Host端像调试本地程序一样调试ARM板上程序.不过,需要注意的是这里执行程序要用"c",不能用"r".因为程序已经在Target Board上面由gdbserver启动了.

调试过程如下:

(gdb) b main Breakpoint 1 at 0x9870: file obexftp.c, line 376. (gdb) info b Num Type Disp Enb Address What 1 breakpoint keep y 0x00009870 in main at obexftp.c:376 (gdb) c Continuing. Error while mapping shared library sections: /work/install/bluetooth//lib/libobexftp.so.0: No such file or directory. Error while mapping shared library sections: /work/install/bluetooth//lib/libc.so.6: No such file or directory. Breakpoint 1, main (argc=1, argv=0xbed0dec4) at obexftp.c:384 384 if (strstr(argv[0], "ls") != NULL) most_recent_cmd = 'l'; 若产生这个错误主要是由于该调试的应用程序使用到了额外的库,而这个库在gdb默认的搜索路径内没有 因此,这里我们需要修改一下gdb默认的共享库搜索路径. 修改的办法是设置GDB的环境变量: (gdb) show solib-absolute-prefix The current system root is "/opt/montavista/pro/devkit/arm/v5t_le/target". 上面这个路径即GDB默认的绝对搜索路径,即交叉编译器库路径 (gdb) show solib-search-path The search path for loading non-absolute shared library symbol files is . (gdb) set solib-search-path /work/install/bluetooth/lib 这个路径为若在solib-absolute-prefix指定的路径内没有搜索到库,则再继续尝试从该路径进行搜索. 这点倒有点类似于系统默认库搜索路径与LD_LIBRARY_PATH的关系.

设置好solib-search-path后再运行程序:

(gdb) set solib-search-path /work/install/bluetooth/lib/ (gdb) c Continuing. Error while reading shared library symbols: Dwarf Error: Can't read DWARF data from '/opt/montavista/pro/devkit/arm/v5t_le/target/usr/lib/debug/lib/ld-2.5.90.so.debug' Breakpoint 1, main (argc=1, argv=0xbe896eb4) at obexftp.c:384 384 if (strstr(argv[0], "ls") != NULL) most_recent_cmd = 'l'; (gdb) l 379 char *output_file = NULL; 380 char move_src = NULL; 381 / char *inbox; / 382 383 / preset mode of operation depending on our name */ 384 if (strstr(argv[0], "ls") != NULL) most_recent_cmd = 'l'; 385 if (strstr(argv[0], "get") != NULL) most_recent_cmd = 'g'; 386 if (strstr(argv[0], "put") != NULL) most_recent_cmd = 'p'; 387 if (strstr(argv[0], "rm") != NULL) most_recent_cmd = 'k'; 388 (gdb)

Core Dump调试

打开Core dump功能

在终端中输入命令ulimit -c,输出的结果为 0,说明默认是关闭 core dump 的,即当程序异常终止时,也不会生成 core dump 文件. 我们可以使用命令ulimit -c unlimited来开启 core dump 功能,并且不限制 core dump 文件的大小; 如果需要限制文件的大小,将 unlimited 改成你想生成 core 文件最大的大小,注意单位为 blocks(KB). 用上面命令只会对当前的终端环境有效,如果想需要永久生效,可以修改文件 /etc/security/limits.conf文件,关于此文件的设置参看这里 .增加一行: line describes a limit for a user in the form: ##

  •    soft     core   unlimited
    

修改core文件保存的路径

默认生成的 core 文件保存在可执行文件所在的目录下,文件名就为 core. 通过修改 /proc/sys/kernel/core_uses_pid 文件可以让生成 core 文件名是否自动加上 pid 号.例如 echo 1 > /proc/sys/kernel/core_uses_pid ,生成的 core 文件名将会变成 core.pid,其中 pid 表示该进程的 PID. 还可以通过修改 /proc/sys/kernel/core_pattern 来控制生成 core 文件保存的位置以及文件名格式.例如可以用 echo "/tmp/corefile-%e-%p-%t" > /proc/sys/kernel/core_pattern 设置生成的 core 文件保存在 "/tmp/corefile" 目录下,文件名格式为 "core-命令名-pid-时间戳".这里 有更多详细的说明!

使用gdp调试core文件

为了使用gdb调试core文件,首先需要使生成的可执行文件带上调试信息,在使用gcc编译生成可执行文件时,使用-g参数就可以增加调试信息. 在开启core文件生成后,当程序崩溃时,就会自动生成core文件了,使用命令gdb program core命令就可以查看core文件.其中program是可执行文件的名称,而core则是刚生成的core文件名. 打开core文件后,就可以按照正常的gdb命令来查看core文件的信息了.

其他的一些命令

  1. 如果电脑没有打开coredump,程序又挂了的时候,可以使用命令dmesg -T,说不定会有一些收获.
  2. 如果在dmesg的输出中看到了崩溃时的ip寄存器的信息,可以使用命令addr2line就能够定位到出问题的代码位置了.
  3. 调试C++的时候,符号信息不好阅读,可以使用命令c++filt将符号信息转为未修饰的函数名.

一些最常用的GDB命令:

file [filename]   装入想要调试的可执行文件
kill [filename]   终止正在调试的程序
break [file:]func 在(file文件的)function函数中设置一个断点
clear             删除一个断点,这个命令需要指定代码行或者函数名作为参数
run [arglist]     运行您的程序 (如果指定了arglist,则将arglist作为参数运行程序)
bt Backtrace:     显示程序堆栈信息
print expr        打印表达式的值
continue          继续运行您的程序 (在停止之后,比如在一个断点之后)
list              列出产生执行文件的源代码的一部分
next              单步执行 (在停止之后); 跳过函数调用
nexti             执行下一行的源代码中的一条汇编指令
set               设置变量的值.例如:set nval=54 将把54保存到nval变量中
step              单步执行 (在停止之后); 进入函数调用
stepi             继续执行程序下一行源代码中的汇编指令.如果是函数调用,这个命令将进入函数的内部,单步执行函数中的汇编代码
watch             使你能监视一个变量的值而不管它何时被改变
rwatch            指定一个变量,如果这个变量被读,则暂停程序运行,在调试器中显示信息,并等待下一个调试命令.参考rwatch和watch命令
awatch            指定一个变量,如果这个变量被读或者被写,则暂停程序运行,在调试器中显示信息,并等待下一个调试命令.参考rwatch和watch命令
Ctrl-C            在当前位置停止执行正在执行的程序,断点在当前行
disable           禁止断点功能,这个命令需要禁止的断点在断点列表索引值作为参数
display           在断点的停止的地方,显示指定的表达式的值.(显示变量)
undisplay         删除一个display设置的变量显示.这个命令需要将display list中的索引做参数
enable            允许断点功能,这个命令需要允许的断点在断点列表索引值作为参数
finish            继续执行,直到当前函数返回
ignore            忽略某个断点制定的次数.例:ignore 4 23 忽略断点4的23次运行,在第24次的时候中断
info [name]       查看name信息
load              动态载入一个可执行文件到调试器
xbreak            在当前函数的退出的点上设置一个断点
whatis            显示变量的值和类型
ptype             显示变量的类型
return            强制从当前函数返回
txbreak           在当前函数的退出的点上设置一个临时的断点(只可使用一次)
make              使你能不退出 gdb 就可以重新产生可执行文件
shell             使你能不离开 gdb 就执行 UNIX shell 命令
help [name]       显示GDB命令的信息,或者显示如何使用GDB的总体信息
quit              退出gdb.

选项

-symbols=file
-s file
    读出文件(file)中的符号表.
-write
    开通(enable)往可执行文件和核心文件写的权限.
-exec=file
-e file
    在适当时候把File作为可执行的文件执行,来检测与core dump结合的数据.
-se File
    从File读取符号表并把它作为可执行文件.
-core File
-c File
    把File作为core dump来执行.
-command=File
-x File
    从File中执行GDB命令.
-directory=Directory
-d Directory
    把Dicrctory加入源文件搜索的路径中.
-nx
-n
    不从任何.gdbinit初始化文件中执行命令.通常情况下,这些文件中的命令是在所有命令选项和参数处理完后才执行.
-quiet
-q
    "Quiet".不输入介绍和版权信息.这些信息输出在batch模式下也被关闭.
-batch
    运行batch模式.在处理完所有用'-x'选项指定的命令文件(还有'.gdbi-nit',如果没禁用)后退出,并返回状态码0.如果在命令文件中的命令被
    执行时发生错误,则退出,并返回状态码非0.batch模式对于运行GDB作为过滤器也许很有用,比如要从另一台电脑上下载并运行一个程序;为了让这些更有用,当
    在batch模式下运行时,消息:Program exited normally.(不论什么时候,一个程序在GDB控制下终止运行,这条消息都会正常发出.),将不会发出.
-cd=Directory
    运行GDB,使用Directory作为它的工作目录,取代当前工作目录.
-fullname
-f
    当Emacs让GDB作为一个子进程运行时,设置这个选项.它告诉GDB每当一个堆栈结构(栈帧)显示出来(包括每次程序停止)就用标准的,认同的方式
    输出文件全名和行号.这里,认同的格式看起来像两个' 32'字符,紧跟文件名,行号和字符位置(由冒号,换行符分隔).Emacs同GDB的接口程序使用这两个' 32'字
    符作为一个符号为框架来显示源代码.
-b Bps
    设置行速(波特率或bits/s).在远程调试中GDB在任何串行接口中使用的行速.
-tty=Device
    使用Device作为你程序运行的标准输入输出.

1. gdb <program>
   program也就是执行文件,一般在当前目录下.
2. gdb <program> core
   用gdb同时调试一个运行程序和core文件,core是程序非法执行后,core dump后产生的文件.
3. gdb <program> <PID>
   如果程序是一个服务程序,那么可以指定这个服务程序运行时的进程ID.gdb会自动attach上去,并调试它.program应该在PATH环境变量中搜索得到.