C++ 调用 C++ 动态库时问题诊断
本文初衷是为了解决 Java 应用程序通过 JNA 调用 C++ 动态库时,C++ 代码运行崩溃导致整个 Java 应用程序崩溃而进行的研究。从一个 C++ 调用 C++ 写的动态库起步,记录它在什么情况下产生 core dump 文件,如何分析 core dump 文件等过程。可惜篇幅无法控制,不足以再加入 Java->JNA->C++ 动态库内容了,所以不得不单列此篇,并更名为 'C++ 调用 C++ 动态库时问题诊断'. 关于 Java JNA 到 C++ 的问题诊断只能另立一篇了。
下面我们来用 JNA 的方式来调用 C++ 动态库,演示当 C++ 代码崩溃时会发生什么,并试图找到好的诊断办法。以下演示在 Linux 下进行, 并且 Linux 发行版是 Amazon Linux 2023.
先写一个 C++ 动态库,创建一个条件让它会 crash, 比如输入为 devil 时产生重复释放内存。
| |
我们把它编译成动态库,命名为 libhello.so,编译命令如下
1g++ -g -shared -fPIC -o libhello.so hello.cpp
先用 C++ 来使用该动态库,编写一个测试程序 test.cpp,内容如下
1#include <stdio.h>
2
3extern void sayHello(char* name);
4
5int main(int argc, char* argv[]) {
6 if (argc < 2) {
7 printf("usage: %s <name>\n", argv[0]);
8 return 1;
9 }
10
11 sayHello(argv[1]);
12 return 0;
13}
编译并执行
1g++ -g -o test test.cpp -L. -lhello
2LD_LIBRARY_PATH=. ./test cpp
输出为
hello cpp
没问题,现在来输入 devil 让它崩溃
1LD_LIBRARY_PATH=. ./test devil
LD_LIBRARY_PATH=. ./test devil<br/ hello devil
buf: you are devil!
free(): double free detected in tcache 2
Aborted (core dumped)
提示说生成的 core dumped 文件,可是在当前目录中没有任何 core 文件。实际上 core dump 文件被 systemd 接管了,要用 coredumpctl 管理。
关于 ulimit -c 的值
在正式检视生成的 core dump 文件之前,还有个知识需补充,之所以会自动生成 core dump 文件是因为系统的 ulimit -c 足够大
1ulimit -c
2unlimited
修改它的值只对当前 shell 有效,要永久生效的话需修改 /etc/security/limits.conf 中的值,
1cat /etc/security/limits.conf
2* hard core 0
我们可以设置它为更小一点的值,如 1024,设置 0 的话则会禁止生成 core dump 文件,下面测试一下
1ulimit -c 0
2LD_LIBRARY_PATH=. ./test devil
3hello devil
4buf: you are devil!
5free(): double free detected in tcache 2
6Aborted (core dumped)
好像看到也生成的 core dump 文件,但用 coredumpctl list 看到的是
1coredumpctl list
2TIME PID UID GID SIG COREFILE EXE SIZE
3Sun 2026-02-22 20:44:13 UTC 67032 0 0 SIGABRT none /host/root/test
COREFILE 为 none, 即在 /var/lib/systemd/coredump/ 下没有相应的 coredump 文件, 实际上就是没能生成相应的 coredump 文件。
用 coredumpctl info 或 coredumpctl gdb 会看到提示
1 Storage: none
2 Message: Process 67032 (test) of user 0 terminated abnormally without generating a coredump.
执行 ulimit -c unlimited 恢复当前会话的的 ulimit -c 为无上限。
查看 core dump 文件的内容
假如我们在 ulimit -c unlimited 时生成了 core dump 文件,为什么默认被 systemd 的 coredumpctl 接管了呢?因为文件
/proc/sys/kernel/core_pattern 中的内容应该是
1cat /proc/sys/kernel/core_pattern
2|/usr/lib/systemd/systemd-coredump %P %u %g %s %t %c %h
我们可以修改文件的内容使其显式的在某个目录下生成 core dump 文件,这是后话。首先看如何用 coredumpctl 查看 core dump 文件
1coredumpctl list
2TIME PID UID GID SIG COREFILE EXE SIZE
3Sun 2026-02-22 21:04:53 UTC 68106 0 0 SIGABRT present /host/root/test 720.4K
coredumpctl info 查看最近一个 core dump 的信息
1coredumpctl info
2 PID: 68106 (test)
3 UID: 0 (root)
4 GID: 0 (root)
5 Signal: 6 (ABRT)
6 Timestamp: Sun 2026-02-22 21:04:52 UTC (6min ago)
7 Command Line: ./test devil
8 Executable: /host/root/test
9 Control Group: /system.slice/docker-9dea312e67c6fc26b72e672f7d5b18aa489278832105d31c5fe91d3b2ea8a61d.scope
10 Unit: docker-9dea312e67c6fc26b72e672f7d5b18aa489278832105d31c5fe91d3b2ea8a61d.scope
11 Slice: system.slice
12 Boot ID: 6faddeadbb624f478e4d6ba74ed272ff
13 Machine ID: ec2f3eafec356ee3b6fff46819df8912
14 Hostname: lin-0aff3de6.xxx.com
15 Storage: /var/lib/systemd/coredump/core.test.0.6faddeadbb624f478e4d6ba74ed272ff.68106.1771794292000000.xz (present)
16 Size on Disk: 720.4K
17 Message: Process 68106 (test) of user 0 dumped core.
coredumpctl gdb 进到 gdb 提示符下再输入 bt 就能清楚的看到问题所在了
1(gdb) bt
2#0 0x00007f97f248d02c in __pthread_kill_implementation () from /lib64/libc.so.6
3#1 0x00007f97f243fb86 in raise () from /lib64/libc.so.6
4#2 0x00007f97f2429873 in abort () from /lib64/libc.so.6
5#3 0x00007f97f242a1b2 in __libc_message.cold () from /lib64/libc.so.6
6#4 0x00007f97f24970d7 in malloc_printerr () from /lib64/libc.so.6
7#5 0x00007f97f24993ab in _int_free () from /lib64/libc.so.6
8#6 0x00007f97f249b923 in free () from /lib64/libc.so.6
9#7 0x00007f97f2c361fb in sayHello (name=0x7ffe72aaf7f9 "devil") at hello.cpp:14
10#8 0x000000000040117e in main (argc=2, argv=0x7ffe72aaee18) at test.cpp:11
很清楚了,问题出在 hello.cpp:14 第十四行 delete[] buf;.
这是用 g++ 编译时加了 -g 参数,所以能定位到具体的行号。如果没有 -g 参数,相当于 -g0, 二进制代码中不生成任何调试信息,
-g1, -g2, -g3 分别表示生成更丰富的调试信息, -g 相当于 -g2 包含行号,变量,宏等信息,-g3 还包含宏定义信息。
core dump 文件的导出
coredumpctl 或 coredumpctl list 看到的文件实际存储在 /var/lib/systemd/coredump/ 目录下。
1ls -l /var/lib/systemd/coredump/
2total 1456
3-rw-r-----. 1 root root 737736 Feb 22 21:04 core.test.0.6faddeadbb624f478e4d6ba74ed272ff.68106.1771794292000000.xz
4-rw-r-----. 1 root root 737592 Feb 22 21:13 core.test.0.6faddeadbb624f478e4d6ba74ed272ff.68586.1771794822000000.xz
一种办法是,用 xz 命令解压相应的文件,如
1xz -dck /var/lib/systemd/coredump/core.test.0.6faddeadbb624f478e4d6ba74ed272ff.68106.1771794292000000.xz > core.68106
或用 coredumpctl dump 命令导出 core dump 文件
1coredumpctl dump 68106 -o core.68106
对这个文件可直接用 gdb 来查看
1gdb ./test core.68106
2(gdb) bt
能看到与前面 coredumpctl gdb, bt 一样的输出信息。
编译动态库没带 -g 会如何
我们重新编译动态库和,但是不加 -g 参数,如
1g++ -g -shared -fPIC -o libhello.so hello.cpp
再执行 `LD_LIBRARY_PATH=. ./test devil' 让它生成 core dump 文件
再用 coredumpctl gdb, bt 来查看 core dump 文件的内容
1(gdb) bt
2#0 0x00007fa6cc08d02c in __pthread_kill_implementation () from /lib64/libc.so.6
3#1 0x00007fa6cc03fb86 in raise () from /lib64/libc.so.6
4#2 0x00007fa6cc029873 in abort () from /lib64/libc.so.6
5#3 0x00007fa6cc02a1b2 in __libc_message.cold () from /lib64/libc.so.6
6#4 0x00007fa6cc0970d7 in malloc_printerr () from /lib64/libc.so.6
7#5 0x00007fa6cc0993ab in _int_free () from /lib64/libc.so.6
8#6 0x00007fa6cc09b923 in free () from /lib64/libc.so.6
9#7 0x00007fa6cc7391fb in sayHello(char*) () from ./libhello.so
10#8 0x000000000040117e in main (argc=2, argv=0x7fff1926a1c8) at test.cpp:11
由于编译 test.cpp 时带了 -g 参数,所以能看到出错时 test.cpp 的行号为 11,在动态库 libhello.so 中就一无所知了。
core dump 文件的清理
命令如下
1ls -l /var/lib/systemd/coredump/
2rm -f /var/lib/systemd/coredump/*
3journalctl --rotate
4journalctl --vacuum-time=1s
这时候看到的 coredumpctl list 列表就为空了。
直接生成 core dump 文件
从前面我们了解了 ulimit -c 控制了是否能顺利生成 core dump 文件,要确保有 ulimit -c 的值不为 0,配置为 ulimited 就行了。
同时它是一个会话级别的值,要永久生效可以修改 /etc/security/limits.conf 中的值。比如修改其内容为
1* hard core unlimited
2* soft core unlimited
同时 /proc/sys/kernel/core_pattern 的内容
1cat /proc/sys/kernel/core_pattern
2|/usr/lib/systemd/systemd-coredump %P %u %g %s %t %c %h
决定了 core dump 由 coredumpctl 来管理,如果只想生成 core dump 文件在特定的目录中
如果要生成 core dump 在当前目录中,执行
1sysctl -w kernel.core_pattern=core.%p
可用的占位符有 %p 进程 PID, %e 可执行文件名, %t 时间戳, %s导致崩溃的信号编号, %u 用户 ID
此时看到的 /proc/sys/kernel/core_pattern 内容为
1cat /proc/sys/kernel/core_pattern
2core.%p
测试
1LD_LIBRARY_PATH=. ./test devil
当前目录下生成了一个 core.71686 core dump 文件,71686 为 %p 代表的进程号。
如查要生成另一个指定目录就用
1sysctl -w kernel.core_pattern=/tmp/cores/core.%p
同样的,修改 kernel.core_pattern 的值会影响 /proc/sys/kernel/core_pattern 的值,但它也是临时的,作用于当前会话。要永久有效的话需修改
/etc/sysctl.conf 文件的内容,在其中添加
1kernel.core_pattern = /tmp/cores/core.%p
然后执行 sysctl -p 使其立即生效,或者机器重启后依然保持有效。
最后做个测试,我们结合 ulimit -c 0, 看是否生成 core dump 文件
1ulimit -c 0
2sysctl -w kernel.core_pattern=core.%p
3LD_LIBRARY_PATH=. ./test devil
4hello devil
5buf: you are devil!
6free(): double free detected in tcache 2
7Aborted
无法生成 core dump 文件, 因为 ulimit -c 0 禁止了 core dump 文件的生成。如果设置 ulimit -c 1024 还是有机会生成 core dump 文件的。
总结
以上测试是为 Java 通过 JNA 调用 C++ 打基础的,具体情形与选择的 Linux 发行版本会有比较大的关系。本文件选择的 Linux 版本是 Amazon Linux 2023.
具体为
1uname -a
2Linux lin-0aff3de6.xxx.com 6.1.161-183.298.amzn2023.x86_64 #1 SMP PREEMPT_DYNAMIC Tue Jan 27 05:01:22 UTC 2026 x86_64 x86_64 x86_64 GNU/Linux
3cat /etc/os-release
4NAME="Amazon Linux"
5VERSION="2023"
6ID="amzn"
7ID_LIKE="fedora"
8VERSION_ID="2023"
9PLATFORM_ID="platform:al2023"
10PRETTY_NAME="Amazon Linux 2023.10.20260202"
11ANSI_COLOR="0;33"
12CPE_NAME="cpe:2.3:o:amazon:amazon_linux:2023"
13HOME_URL="https://aws.amazon.com/linux/amazon-linux-2023/"
14DOCUMENTATION_URL="https://docs.aws.amazon.com/linux/"
15SUPPORT_URL="https://aws.amazon.com/premiumsupport/"
16BUG_REPORT_URL="https://github.com/amazonlinux/amazon-linux-2023"
17VENDOR_NAME="AWS"
18VENDOR_URL="https://aws.amazon.com/"
19SUPPORT_END="2029-06-30"
在 C++ 崩溃时有两个参数决定了能否生成 core dump 文件,以及如何管理
ulimit -c限制了 core dump 文件的大小,如果为 0 则禁止生成 core dump 文件,设置为unlimited则不限制大小kernel.core_pattern决定了 core dump 文件的生成路径和文件名,如果为core则生成在当前目录下,文件名为 `coreulimit -c要永久生效需修改/etc/security/limits.conf文件kernel.core_pattern的修改会临时影响到/proc/sys/kernel/core_pattern的内容,我们知道/proc下的内容是动态的。要让kernel.core_pattern的修改要永久生效需修改/etc/sysctl.conf文件
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。