编辑
2025-09-06
记录知识
0

目录

core文件在哪里
实战
使用pwndbg
总结

在系统开发过程中,部署在设备的版本通常是release版本,所以一般是不带调试信息的,当问题出现时,我们如何排查呢? 实际上在linux中提供了coredump的功能,当程序出现崩溃时,会给我们转储core文件,只要我们拿到core文件,就相当于拿到了问题现场,接下来就是利用gdb来分析问题了。

core文件在哪里

对于linux系统,我们首先要查看systemd-coredump是否安装,如果systemd-coredump不安装,那么core文件是存放在默认的内核位置,内核会将其写成core,故core文件会存在启动用户的根目录,假设当前启动用户是kylin,那么core文件就在如下

# file /home/kylin/core /home/kylin/core: ELF 64-bit LSB core file, ARM aarch64, version 1 (SYSV), SVR4-style, from '/usr/bin/ukui-panel', real uid: 1000, effective uid: 1000, real gid: 1000, effective gid: 1000, execfn: '/usr/bin/ukui-panel', platform: 'aarch64'

同样的我们可以通过内核提供的proc文件查看core的位置,如下

# cat /proc/sys/kernel/core_pattern core

如果我们安装了systemd-coredump包,那么core文件就由systemd接管,实际位置如下

# cat /proc/sys/kernel/core_pattern |/lib/systemd/systemd-coredump %P %u %g %s %t 9223372036854775808 %h

此时core文件的位置在/var/lib/systemd/coredump/

实战

当系统内有二进制出现崩溃的时候,core文件会生成,接下来以一个实际的例子来介绍如何调试core文件。这个经验对于调试操作系统而言,非常之有用。

我手动创造了一个崩溃示例,此时可以发现coredump目录下存在如下文件

core.kylin-vpn.1000.930a6eece73248149cd465ddbe02d695.17632.1757114505000000000000.lz4

从这个信息可以知道,是kylin-vpn出现了崩溃,我们应该调试kylin-vpn,首先需要解压这个lz4文件,如下

# lz4 core.kylin-vpn.1000.930a6eece73248149cd465ddbe02d695.17632.1757114505000000000000.lz4 # file core.kylin-vpn.1000.930a6eece73248149cd465ddbe02d695.17632.1757114505000000000000 core.kylin-vpn.1000.930a6eece73248149cd465ddbe02d695.17632.1757114505000000000000: ELF 64-bit LSB core file, ARM aarch64, version 1 (SYSV), SVR4-style, from '/usr/bin/kylin-vpn', real uid: 1000, effective uid: 1000, real gid: 1000, effective gid: 1000, execfn: '/usr/bin/kylin-vpn', platform: 'aarch64'

此时假设我们直接gdb调试,看看什么效果

# gdb -c core.kylin-vpn.1000.930a6eece73248149cd465ddbe02d695.17632.1757114505000000000000 /usr/bin/kylin-vpn Reading symbols from /usr/bin/kylin-vpn... (No debugging symbols found in /usr/bin/kylin-vpn) Core was generated by `/usr/bin/kylin-vpn'. Program terminated with signal SIGSEGV, Segmentation fault. #0 0x0000007fa009f5a0 in QDBusMetaType::typeToSignature(int) () from /lib/aarch64-linux-gnu/libQt5DBus.so.5

我把关键信息贴在上面,可以看到代码出现了段错误,出现在libQt5DBus.so.5的QDBusMetaType::typeToSignature(int)函数上。

此时尝试反汇编QDBusMetaType::typeToSignature函数,发现并不能正常反汇编,如下

(gdb) disassemble QDBusMetaType::typeToSignature No symbol "QDBusMetaType" in current context.

这是什么原因呢?其实在《gdb调试方法(4)-调试信息》里面提到过,这个动态库是发行版的动态库,自然被strip过,而strip过的动态库是找不到对应符号的。所以这种情况很正常,不要慌张。接下来逐步调试问题。

虽然我们知道了问题出现在QDBusMetaType::typeToSignature,但是我们还是要一步一步判断和调试问题。首先,我们留意到kylin-vpn并没有符号,所以先打上对应符号,如下

# dpkg -S /usr/bin/kylin-vpn kylin-nm: /usr/bin/kylin-vpn # dpkg -i kylin-nm-dbgsym_3.20.1.7-0k0.1tablet8.egf0.6_arm64.ddeb

上述安装实际上就是把kylin-vpn的符号打进系统,有gdb根据build-id加载对应的debuginfo,ddeb的内容如下

# dpkg -L kylin-nm-dbgsym /. /usr /usr/lib /usr/lib/debug /usr/lib/debug/.build-id /usr/lib/debug/.build-id/0f /usr/lib/debug/.build-id/0f/470de270e27775339fd5c2cc71f2f5610d10c2.debug /usr/lib/debug/.build-id/0f/afcd08ad5eb832b1efb97a785767c90458ef82.debug /usr/lib/debug/.build-id/1f /usr/lib/debug/.build-id/1f/42ddf71452964a5f507f24d4db18507046b9c7.debug /usr/lib/debug/.build-id/41 /usr/lib/debug/.build-id/41/36dbbc90afa8a433f445f7bb964f9beea34fe2.debug /usr/lib/debug/.build-id/63 /usr/lib/debug/.build-id/63/a064e1ef52b9ab4039a21bf4093082f844ac9f.debug /usr/lib/debug/.build-id/83 /usr/lib/debug/.build-id/83/90208727fef2f884cf665bf868d46469056e3e.debug /usr/lib/debug/.build-id/da /usr/lib/debug/.build-id/da/f42bf2c9e3f18cf6716cdc49ad1811e130ee46.debug

此时重新gdb挂载,可以看到二进制的符号打进去了,如下

Reading symbols from /usr/bin/kylin-vpn... Reading symbols from /usr/lib/debug/.build-id/0f/afcd08ad5eb832b1efb97a785767c90458ef82.debug...

但是,我们还是无法看到代码行数,如下

(gdb) l 66 main.cpp: 没有那个文件或目录.

所以还需要把对应的代码放进来,如下

scp -r source/src-vpn dest@kylin:~/

然后用gdb去加载自己需要的源码片段,如下

# gdb -c core.kylin-vpn.1000.930a6eece73248149cd465ddbe02d695.17632.1757114505000000000000 /usr/bin/kylin-vpn -d ~/src-vpn/

此时gdb就能正常加载所有kylin-vpn的代码了,如下

(gdb) l 66 break; 67 case QtCriticalMsg: 68 fprintf(log_file? log_file: stderr, "Critical: %s: %s (%s:%u, %s)\n", currentDateTime.constData(), localMsg.constData(), file, context.line, function); 69 break; 70 case QtFatalMsg: 71 fprintf(log_file? log_file: stderr, "Fatal: %s: %s (%s:%u, %s)\n", currentDateTime.constData(), localMsg.constData(), file, context.line, function); 72 break; 73 } 74 75 if (log_file)

我们可以基于kylin-vpn进行源码分析了。

但是,我们的问题刚刚发现实际上是在QDBusMetaType::typeToSignature函数,此时我们再反汇编一下,如下

(gdb) disassemble QDBusMetaType::typeToSignature There is no field named typeToSignature

可以看到,因为我们加载了二进制的源码和符号,但是出问题的地方并不是我们kylin-vpn这个二进制,所以这个错误变了,从No symbol 到 on field named 。也就是说,我们需要针对typeToSignature函数来进一步调试。

根据这个信息,我们知道出问题的动态库是libQt5Dbus.so.5

#0 0x0000007fa009f5a0 in QDBusMetaType::typeToSignature(int) () from /lib/aarch64-linux-gnu/libQt5DBus.so.5

此时我们安装对应的ddeb即可,如下

# dpkg -S /usr/lib/aarch64-linux-gnu/libQt5DBus.so.5 libqt5dbus5:arm64: /usr/lib/aarch64-linux-gnu/libQt5DBus.so.5 # dpkg -i libqt5dbus5-dbgsym_5.12.8+dfsg_arm64.ddeb

这里有个小的注意点,那就是默认情况下,一些系统例如麒麟是user-merged,也就是将lib目录迁移到/usr/lib下,所以我们dpkg -S找包的时候需要追加/usr目录。

关于user merged 不是文章重点,有兴趣可以查看如下文章

https://wiki.gentoo.org/wiki/Merge-usr

回到问题,我们再gdb调试看一下什么情况了

# gdb -c core.kylin-vpn.1000.930a6eece73248149cd465ddbe02d695.17632.1757114505000000000000 /usr/bin/kylin-vpn -d src-vpn/ Core was generated by `/usr/bin/kylin-vpn'. Program terminated with signal SIGSEGV, Segmentation fault. #0 QDBusMetaType::typeToSignature (type=8) at qdbusmetatype.cpp:462 462 qdbusmetatype.cpp: 没有那个文件或目录. [Current thread is 1 (Thread 0x7f812179b0 (LWP 17851))] (gdb) list qdbusmetatype.cpp:1 1 in qdbusmetatype.cpp

可以看到,此时libQt5DBus.so.5的符号已经打上了,已经尝试在找DWARF的源码位置了,源码是qdbusmetatype.cpp,但是源码找不到。

所以反汇编是对应不到源码的,如下

(gdb) disassemble /m QDBusMetaType::typeToSignature Dump of assembler code for function QDBusMetaType::typeToSignature(int): 152 in qdbusmetatype.cpp 0x0000007fa009f4f4 <+420>: add x25, x22, #0x20 0x0000007fa009f4f8 <+424>: ldarb w0, [x25] 0x0000007fa009f4fc <+428>: tbz w0, #0, 0x7fa009f5b4 <QDBusMetaType::typeToSignature(int)+612> 0x0000007fa009f500 <+432>: add x19, x19, #0x20 0x0000007fa009f504 <+436>: add x23, x19, #0x28 153 in qdbusmetatype.cpp 154 in qdbusmetatype.cpp 155 in qdbusmetatype.cpp 156 in qdbusmetatype.cpp 157 in qdbusmetatype.cpp 158 in qdbusmetatype.cpp 159 in qdbusmetatype.cpp 160 in qdbusmetatype.cpp

所以我们需要从代码库将此文件找到,scp到机器上,如下

scp src/dbus/qdbusmetatype.cpp root@91:~/core/

如果在gdb的运行目录,则无需手动加载,如果不在,就需要手动加载,手动加载的方式如下

(gdb) directory . Source directories searched: ~/core:~/core/src-vpn:$cdir:$cwd

此时可以看到源码了

(gdb) list qdbusmetatype.cpp:1 1 /**************************************************************************** 2 ** 3 ** Copyright (C) 2016 The Qt Company Ltd. 4 ** Contact: https://www.qt.io/licensing/ 5 ** 6 ** This file is part of the QtDBus module of the Qt Toolkit. 7 ** 8 ** $QT_BEGIN_LICENSE:LGPL$ 9 ** Commercial License Usage 10 ** Licensees holding valid commercial Qt licenses may use this file in

然后再反汇编就找得到源码了,我们找到出问题的地方,如下

462 if (type >= ct->size()) 0x0000007fa009f51c <+460>: ldr x24, [x23] => 0x0000007fa009f5a0 <+592>: ldr x24, [x23] 463 return 0; // type not registered with us

可以看到这句汇编,将x23的内容加载到x24寄存器中。其源码逻辑是 type >= ct->size()我们根据上下文分析一下先

=> 0x0000007fa009f5a0 <+592>: ldr x24, [x23] 0x0000007fa009f5a4 <+596>: ldr w0, [x24, #4] 0x0000007fa009f5a8 <+600>: cmp w20, w0

可以看到,这里w20是type的值,w0是ct->size()的值,为了取ct->size(),需要先取出ct的指针,也就是ldr x24, [x23],然后用ct的指针取size位置的值到w0,也就是偏移4的位置ldr w0, [x24, #4]

这里如果出现段错误,那么就是x23的值取不到了,在linux系统中,默认将0地址作为不可访问段错误地址,用来有效提醒用户。所以猜测x23的值是0。验证一下

(gdb) p $x23 $2 = 0 (gdb) p $w20 $1 = 8 (gdb) p type $3 = 8

可以看到,因为ct已经是nullptr了,所以取size自然就段错误了,我们翻阅一下qdbusmetatype.cpp找一下ct的初始化,如下

Q_GLOBAL_STATIC(QVector<QDBusCustomTypeInfo>, customTypes) QVector<QDBusCustomTypeInfo> *ct = customTypes();

可以看到,customTypes实际上是静态全局变量vector,为何会被置空为nullptr呢?

其实问题很清晰明了了,kylin-vpn会调用dbus从而调用qtbase,在系统关机过程中,qt会析构,而静态变量的析构顺序和链接顺序有关(可参考文章《使用ASAN定位全局变量构造顺序问题》),也就是说kylin-vpn析构之前,qt的这个qdbusmetatype.o已经析构了,所以kylin-vpn在访问dbus从而访问了qdbusmetatype.o的静态变量,从而出现了空指针引用的问题。

处理这个问题就是从操作系统设计上考虑,在关机时注意多个进程之间的时序问题,从而避免不必要的coredump产生。

到这里通过gdb分析core的一次实战已经完成了。几乎所有遇到的coredump文件,通过上述这套方法都能快准狠的找出问题。

使用pwndbg

上面基于gdb已经能够完成调试目的了,这里再补充一下pwndbg是如何迅速找到问题的。

首先启用pwndbg,如下

# cat ~/.gdbinit source ~/pwndbg/gdbinit.py

然后gdb加载core文件,此时pwndbg会在崩溃出打印context,如下

► 0x7fa009f5a0 <QDBusMetaType::typeToSignature(int)+592> ldr x24, [x23] <Cannot dereference [0]> 0x7fa009f5a4 <QDBusMetaType::typeToSignature(int)+596> ldr w0, [x24, #4]
► 462 if (type >= ct->size()) 463 return 0; // type not registered with us
X23 0

原因之间展示在眼前了,无需分析,pwndbg直接就告诉你问题是Cannot dereference [0]。迅速定位到问题。

总结

本文分享了非常常用的调试core的万能方法,通过此方法,后面遇到任何coredump的问题都能迎刃而解。如果使用gdb去分析和计算还是比较累,可以尝试在设备上部署pwndbg,这样存在的问题pwndbg就直接在加载时告诉你答案了,无需分析。