GDB调试无行号,报dwarf error问题解决

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: GDB调试无行号,报dwarf error问题解决

背景

近期我开发的一个C程序,在生产环境产生了coredump,但是在调试该core文件时,打出的debug信息并不全。

这种debug信息丢失,其实说白了,就是符号表丢失。一般由两种情况造成,一种是编译的时候没有加-g参数,另一种是dwarf版本不对。

首先排除第一种可能,因为编译脚本是我自己写的,-g参数是有的。而唯一可能出问题的地方,就是dwarf版本不对。

而之所以出现dwarf版本不对,还是编译环境的问题。我为了兼容编译C++17标准的另外一个cpp项目,就对编译环境做了容器化处理,在镜像里安装了gcc11.3,而在生产环境使用的时候,gdb版本仍然是4.8.5,由于gcc版本和gdb版本不匹配,就造成了该问题的出现。

为了验证这一点,我在物理机上重现了这种现象:

[root@ck08 ctest]# gcore `pidof flow`
Dwarf Error: wrong version in compilation unit header (is 5, should be 2, 3, or 4) [in module /root/chenyc/src/flow/flow]
[New LWP 3048]
[New LWP 3047]
[New LWP 3046]
[New LWP 3045]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
0x00007f50dfd850e3 in epoll_wait () from /lib64/libc.so.6
warning: target file /proc/3044/cmdline contained unexpected null characters
Saved corefile core.3044
[Inferior 1 (process 3044) detached]

我的物理机的gdb版本也是4.8.5, 我使用gcore命令生成core文件的时候,出现了下面的警告:Dwarf Error: wrong version in compilation unit header (is 5, should be 2, 3, or 4),这句话从字面意思很好理解,就是说,gdb支持的dwarf版本应该是23,或者4,但是当前二进制文件的dwarf版本是5,无法调试。

那么,何为dwarf?什么又是dwarf版本呢?

何为dwarf

所谓的dwarf,它是一种文件调试的格式。你可以将其简单理解为调试信息的组织模式。除了dwarf之外,常见的调试格式还有stabsCOFFpdb等。

除了pdb这种windows专用的调试格式外,绝大多数的调试格式都是支持Unix系统的。但随着时间的推移,逐渐被dwarf一统江山,被各大主流编译器所支持。其他的一些调试格式虽然还零星存在,但也是苟延残喘,名存实亡。

说到dwarf自身的发展,也是经历了好几个阶段,从1992年推出至今,已经迭代了5个版本。其中,dwarf1作为第一个版本,结构不紧凑,功能不成熟,很多编译器都已经不支持。dwarf2是1993年PLSIG机构在初版的基础上做了一些优化,减少了调试信息的大小,但只是有一个草案,并没有正式发布。

第一个正式发布的dwarf版本是Free Standards Group于2005年发布的dwarf3,该机构并于2010年发布了dwarf4。目前最新的dwarf版本是2017年发布的dwarf5

官方说法是这样的:

Produce debugging information in DWARF format (if that is supported). The value of version may be either 2, 3, 4 or 5; the default version for most targets is 5 (with the exception of VxWorks, TPF and Darwin/Mac OS X, which default to version 2, and AIX, which defaults to version 4).

Note that with DWARF Version 2, some ports require and always use some non-conflicting DWARF 3 extensions in the unwind tables.

Version 4 may require GDB 7.0 and -fvar-tracking-assignments for maximum benefit. Version 5 requires GDB 8.0 or higher.

GCC no longer supports DWARF Version 1, which is substantially different than Version 2 and later. For historical reasons, some other DWARF-related options such as -fno-dwarf2-cfi-asm) retain a reference to DWARF Version 2 in their names, but apply to all currently-supported versions of DWARF.

关于dwarf的调试文件格式,本文就不多做介绍了,如果展开来说,一个专题远远不够。但需要明白的是,各个dwarf版本之间,数据格式也是有所区别的,这也就造成了彼此之间的不兼容,因此才会出现文章开头出现的问题。

如何指定dwarf版本

那么,原因定位到了,我们如何解决这个问题呢?

难不成,我需要降级gcc版本?总不能逼着客户去升级生产环境的gdb版本吧?这明显都是不现实的。

不过好在gcc编译器提供了指定dwarf版本的选项。我们只需要在编译时,增加-gdwarf-version选项即可。

为了演示指定dwarf版本,我在这里准备了一个demo

C程序如下:

//hello.c
#include <stdio.h>
int main(void){
        char *p = "hello";
        printf("p = %s\n", p);
        p[3] = 'M';
        printf("p = %s\n", p);
        return 0;
}

容器内gcc版本如下:

[root@5b2c03891f42 tmp]# gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/local/libexec/gcc/x86_64-pc-linux-gnu/11.3.0/lto-wrapper
Target: x86_64-pc-linux-gnu
Configured with: ./configure --enable-languages=c,c++
Thread model: posix
Supported LTO compression algorithms: zlib
gcc version 11.3.0 (GCC)

在容器内编译:

gcc -o hello hello.c -g

该程序一定会产生core文件。我们在容器外运行,此时,这个core文件是无法调试的:

[root@ck08 ctest]# ulimit -c unlimited
[root@ck08 ctest]# ./hello 
p = hello
Segmentation fault (core dumped)
[root@ck08 ctest]# gdb ./hello core.30856 
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-120.el7
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /root/chenyc/src/ctest/hello...Dwarf Error: wrong version in compilation unit header (is 5, should be 2, 3, or 4) [in module /root/chenyc/src/ctest/hello]
(no debugging symbols found)...done.
[New LWP 30856]
Core was generated by `./hello'.
Program terminated with signal 11, Segmentation fault.
#0  0x0000000000401164 in main ()
Missing separate debuginfos, use: debuginfo-install glibc-2.17-326.el7_9.x86_64
(gdb) bt
#0  0x0000000000401164 in main ()
(gdb)

我们尝试指定dwarf版本编译:

gcc -gdwarf-4 -gstrict-dwarf -fvar-tracking-assignments -o hello hello.c

其中:

  • -gdwarf-4 指定dwarf版本为4
  • -fvar-tracking-assignments 在编译的早期对用户变量的赋值进行注释,并尝试在整个编译过程中将注释一直延续到最后,以尝试在优化的同时改进调试信息。
  • -gstrict-dwarf 禁用更高版本的的dwarf扩展,转而使用指定的dwarf版本的扩展
    此时我们可以看到,能够正常调试了。

通过上述的演示,理论上我们只需要在项目编译时,指定dwarf版本,就可以正常调试了。

然而,如果问题如此简单就能解决,那似乎没有必要专门写一篇文章的必要,事实上,我在使用的时候,又遇到了比较玄学的问题。

玄之又玄

截取部分编译输出,可以看到,我的确使用了dwarf-4版本:

但是我们在运行时,发现仍然报Dwarf Error:

[root@ck08 flow]# gdb ./flow core.10772 
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-120.el7
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /root/chenyc/src/flow/flow...Dwarf Error: wrong version in compilation unit header (is 5, should be 2, 3, or 4) [in module /root/chenyc/src/flow/flow]
(no debugging symbols found)...done.
[New LWP 10773]
[New LWP 10774]
[New LWP 10775]
[New LWP 10776]
[New LWP 10772]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Core was generated by `./flow'.
#0  0x00007f13b9ae7a35 in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
Missing separate debuginfos, use: debuginfo-install glibc-2.17-326.el7_9.x86_64
(gdb) bt
#0  0x00007f13b9ae7a35 in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
#1  0x00000000004117d5 in nxlog_worker_thread ()
#2  0x000000000040cdd5 in _thread_helper ()
#3  0x00007f13b9ae3ea5 in start_thread () from /lib64/libpthread.so.0
#4  0x00007f13b9400b0d in clone () from /lib64/libc.so.6
(gdb)

那么,问题出在哪呢?为什么设置了dwarf版本,但是不生效?

为了实锤我们设置的dwarf版本确实生效了,我使用objdump命令查看了一下:

[root@ck08 flow]# objdump --dwarf=info ./flow|more
./flow:     file format elf64-x86-64
Contents of the .debug_info section:
  Compilation Unit @ offset 0x0:
   Length:        0x3e07 (32-bit)
   Version:       4
   Abbrev Offset: 0x0
   Pointer Size:  8
 <0><b>: Abbrev Number: 1 (DW_TAG_compile_unit)
    <c>   DW_AT_producer    : (indirect string, offset: 0x31f): GNU C17 11.3.0 -mtune=generic -march=x86-64 -g -gdwarf-4 -gstrict-dwa
rf -O2 -fPIC
    <10>   DW_AT_language    : 12       (ANSI C99)
    <11>   DW_AT_name        : (indirect string, offset: 0x16ac): src/core/protocol.c
    <15>   DW_AT_comp_dir    : (indirect string, offset: 0x1c15): /tmp
    <19>   DW_AT_low_pc      : 0x4090c0
    <21>   DW_AT_high_pc     : 0x127c
    <29>   DW_AT_stmt_list   : 0x0

这里,能看到src/core/protocol.c文件编译出来的二进制文件,dwarf版本确实是4。那么,为什么gdb调试仍然会报dwarf版本是5呢?

那么,会不会是程序依赖的第三方库使用了dwarf-5

带着疑问,我查看了一下所有的version

发现确实有部分二进制文件使用到了dwarf-5版本。

先把dwarf.debug-info导出来:

objdump --dwarf=info ./flow > dwarf.info

直接定位到754527行:

可以定位到,是在编译bzip2库的时候,出现了dwarf-5的版本。

为了验证我的猜想,我直接到容器里找到了libbz2,果然它就是罪魁祸首。

[root@5703f261ff2b lib]# objdump --dwarf=info libbz2.a|grep Version
   Version:       5
   Version:       5
   Version:       5
   Version:       5
   Version:       5
   Version:       5
   Version:       5
    <1760>   DW_AT_name        : (indirect string, offset: 0x650): BZ2_bzlibVersion
[root@5703f261ff2b lib]#

那么问题来了,我是在容器里编译第三方依赖的,在编译之前统一设置过CC环境变量:

[root@5703f261ff2b tmp]# echo $CC
gcc -gdwarf-4 -gstrict-dwarf -fvar-tracking-assignments

截取部分Dockerfile内容:

Dockerfile可知,我们先设置了CC,然后依次编译openssllibaprbzip2,那为什么其他的依赖都没有问题,单单bzip2没有生效呢?

[root@5703f261ff2b lib]# objdump --dwarf=info libssl.a|grep Version
   Version:       4
   Version:       4
   Version:       4
   Version:       4
   Version:       4
   Version:       4
   Version:       4
   Version:       4
   Version:       4
   Version:       4

所以似乎还要到bzip2源码本身去找原因。于是我重新解压了bzip2的源码包,发现它是没有configure文件的,只有一个Makefile,打开Makefile,发现了端倪:

虽然我们在外面设置了CC的值,但是在Makefile里又将其覆盖掉了,使用的是gcc的默认dwarf版本,而我们的gcc11.3,所以默认使用了dwarf-5版本。

这里,明显看到bzip2开发者省了个懒,其实比较安全一点的写法应该是:

CC ?= gcc

我们将Makefile修改一下,重新编译,发现结果正确了:

[root@5703f261ff2b bzip2-1.0.8]# objdump --dwarf=info libbz2.a|grep Version
   Version:       4
   Version:       4
   Version:       4
   Version:       4
   Version:       4
   Version:       4
   Version:       4
    <1482>   DW_AT_name        : (indirect string, offset: 0x60c): BZ2_bzlibVersion

我使用新的bzip2库编译了一下程序,这时使用gcore生成core文件,已经不会报Dwarf Error了:

[root@ck08 flow]# gcore `pidof flow`
[New LWP 25963]
[New LWP 25962]
[New LWP 25961]
[New LWP 25960]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
0x00007f704555fb43 in select () from /lib64/libc.so.6
warning: target file /proc/25959/cmdline contained unexpected null characters
Saved corefile core.25959
[Inferior 1 (process 25959) detached]

使用gdb调试这个core文件也能拿到详细的调试信息:

[root@ck08 flow]# gdb ./flow core.25959
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-120.el7
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /root/chenyc/src/flow/flow...done.
[New LWP 25960]
[New LWP 25961]
[New LWP 25962]
[New LWP 25963]
[New LWP 25959]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Core was generated by `./flow'.
#0  0x00007f7045c52efd in open64 () from /lib64/libpthread.so.0
Missing separate debuginfos, use: debuginfo-install glibc-2.17-326.el7_9.x86_64
(gdb) bt
#0  0x00007f7045c52efd in open64 () from /lib64/libpthread.so.0
#1  0x000000000049b731 in apr_file_open (new=0x7f7034003320, 
    fname=0x7f7034002ad0 "/root/chenyc/test/dc/mave/probes/itoa-flow/data/utf-8_nolb.log", flag=1, perm=<optimized out>, 
    pool=0x7f7034003288) at file_io/unix/open.c:176
#2  0x000000000041c1b9 in im_file_ext_input_open (module=0x2313a00, file=0x7f7045253fd8, finfo=0x7f704524eaa0, readfromlast=false, 
    existed=true) at src/modules/input/fileExt/im_fileExt.c:976
#3  0x000000000041f51f in im_file_ext_check_file (module=<optimized out>, file=<optimized out>, fname=<optimized out>, 
    pool=<optimized out>) at src/modules/input/fileExt/im_fileExt.c:1315
#4  0x0000000000420294 in im_file_ext_check_files (module=0x2313a00, active_only=<optimized out>)
    at src/modules/input/fileExt/im_fileExt.c:1475
#5  0x000000000042076b in im_file_ext_read (module=0x2313a00) at src/modules/input/fileExt/im_fileExt.c:2981
#6  0x00000000004208f8 in im_file_ext_event (module=0x2313a00, event=0x7f702c0008c0) at src/modules/input/fileExt/im_fileExt.c:3583
#7  0x00000000004118da in nxlog_worker_thread (thd=0x22f1c08, data=<optimized out>) at src/core/nxlog.c:552
#8  0x000000000040cdd5 in _thread_helper (thd=0x22f1c08, d=0x7ffc646c4050) at src/core/core.c:85
#9  0x00007f7045c4bea5 in start_thread () from /lib64/libpthread.so.0
#10 0x00007f7045568b0d in clone () from /lib64/libc.so.6
(gdb)

总结

dwarf error的问题,网上很多资料说得很含糊,大多也都一知半解,真要深入研究,还是有很多坑的。反正总之从以下几个思路进行切入,基本都能找到解决方向:

  • dwarf error 一般出现在gcc编译环境版本与gdb调试环境版本不匹配导致,一般可以通过编译时指定dwarf版本解决
  • 除了我们自身的源码需要指定dwarf版本,程序所依赖的第三方库也需要使用指定的dwarf版本进行编译

推荐一个零声学院免费教程,个人觉得老师讲得不错,分享给大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,

fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,

TCP/IP,协程,DPDK等技术内容,点击立即学习: C/C++Linux服务器开发/高级架构师

相关实践学习
阿里云图数据库GDB入门与应用
图数据库(Graph Database,简称GDB)是一种支持Property Graph图模型、用于处理高度连接数据查询与存储的实时、可靠的在线数据库服务。它支持Apache TinkerPop Gremlin查询语言,可以帮您快速构建基于高度连接的数据集的应用程序。GDB非常适合社交网络、欺诈检测、推荐引擎、实时图谱、网络/IT运营这类高度互连数据集的场景。 GDB由阿里云自主研发,具备如下优势: 标准图查询语言:支持属性图,高度兼容Gremlin图查询语言。 高度优化的自研引擎:高度优化的自研图计算层和存储层,云盘多副本保障数据超高可靠,支持ACID事务。 服务高可用:支持高可用实例,节点故障迅速转移,保障业务连续性。 易运维:提供备份恢复、自动升级、监控告警、故障切换等丰富的运维功能,大幅降低运维成本。 产品主页:https://www.aliyun.com/product/gdb
目录
相关文章
|
6月前
|
NoSQL 搜索推荐 openCL
【C/C++ 调试 GDB指南 】gdb调试基本操作
【C/C++ 调试 GDB指南 】gdb调试基本操作
391 2
|
6月前
|
NoSQL Linux 开发工具
【深入解析git和gdb:版本控制与调试利器的终极指南】(下)
【深入解析git和gdb:版本控制与调试利器的终极指南】
|
3月前
|
NoSQL Linux C语言
Linux GDB 调试
Linux GDB 调试
65 10
|
3月前
|
NoSQL Linux C语言
嵌入式GDB调试Linux C程序或交叉编译(开发板)
【8月更文挑战第24天】本文档介绍了如何在嵌入式环境下使用GDB调试Linux C程序及进行交叉编译。调试步骤包括:编译程序时加入`-g`选项以生成调试信息;启动GDB并加载程序;设置断点;运行程序至断点;单步执行代码;查看变量值;继续执行或退出GDB。对于交叉编译,需安装对应架构的交叉编译工具链,配置编译环境,使用工具链编译程序,并将程序传输到开发板进行调试。过程中可能遇到工具链不匹配等问题,需针对性解决。
102 3
|
3月前
|
NoSQL
技术分享:如何使用GDB调试不带调试信息的可执行程序
【8月更文挑战第27天】在软件开发和调试过程中,我们有时会遇到需要调试没有调试信息的可执行程序的情况。这可能是由于程序在编译时没有加入调试信息,或者调试信息被剥离了。然而,即使面对这样的挑战,GDB(GNU Debugger)仍然提供了一些方法和技术来帮助我们进行调试。以下将详细介绍如何使用GDB调试不带调试信息的可执行程序。
107 0
|
5月前
|
NoSQL Linux C语言
Linux gdb调试的时候没有对应的c调试信息库怎么办?
Linux gdb调试的时候没有对应的c调试信息库怎么办?
45 1
|
5月前
|
NoSQL Linux C语言
Linux gdb调试的时候没有对应的c调试信息库怎么办?
Linux gdb调试的时候没有对应的c调试信息库怎么办?
32 0
|
5月前
|
NoSQL Linux C++
Linux C/C++ gdb调试正在运行的程序
Linux C/C++ gdb调试正在运行的程序
|
5月前
|
NoSQL Linux C++
Linux C/C++ gdb调试core文件
Linux C/C++ gdb调试core文件
|
5月前
|
NoSQL Linux C++
Linux C/C++ gdb调试
Linux C/C++ gdb调试
下一篇
无影云桌面