背景
近期我開發的一個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
版本應該是2
,3
,或者4
,但是當前二進位制檔案的dwarf
版本是5
,無法除錯。
那麼,何為dwarf
?什麼又是dwarf
版本呢?
何為dwarf
所謂的dwarf
,它是一種檔案除錯的格式。你可以將其簡單理解為除錯資訊的組織模式。除了dwarf
之外,常見的除錯格式還有stabs
, COFF
, pdb
等。
除了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
,然後依次編譯openssl
, libapr
, bzip2
,那為什麼其他的依賴都沒有問題,單單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
版本,而我們的gcc
是11.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
版本進行編譯