上下文:我刚进入公司,分配到了大数据存储组里,自研了hdfs,名为yhdfs,其底层是c++写的,用...
创建于:2025年8月16日
使用 GPT-5 Thinking 回答 Chat01
创建于:2025年8月16日
使用 GPT-5 Thinking 回答 Chat01
上下文:我刚进入公司,分配到了大数据存储组里,自研了hdfs,名为yhdfs,其底层是c++写的,用了ufs的一些存储,但是自身应该也写了一些sdk,然后需要 实现hadoop客户端的一些接口,所以也涉及了一些java。我主要的工作是巡检 集群是否正常工作,是否有一些异常的报错。
我要研究一下,core-file (因为要找到 具体是哪里的代码出错了),你首先我给我介绍一下core-file吧,尽量全面。
好的!下面给你一份尽量全面、工程一线可用的 core file(核心转储)介绍,重点偏向 Linux 上的 C/C++ 进程(你们的 yhdfs C++ 进程)以及 JNI/Java 场景(Hadoop 客户端接口)。
当进程因为严重错误收到会触发“产生核心转储”的信号时(常见如 SIGSEGV、SIGABRT、SIGBUS、SIGILL、SIGFPE、SIGTRAP),内核把当时的进程内存快照、寄存器、线程栈、映射的共享库信息等写到一个文件里,这个文件就是 core。
它能让你在事后用调试器还原进程当时的状态,定位崩溃位置与调用栈,即使程序已经退出。
NT_PRSTATUS / NT_PRPSINFO / NT_AUXV / .note.gnu.build-id
等调试所需元数据RLIMIT_CORE
(ulimit)允许、核心生成未被拦截。SIGKILL
(如 OOM killer)直接杀死、ulimit 为 0、系统配置将 core 重定向到收集服务但被丢弃、容器/权限限制、setuid 程序默认禁止等。bashulimit -c unlimited # 允许无限大小 core # 验证 ulimit -a | grep 'core file size'
LimitCORE=infinity
并重启服务。
/proc/sys/kernel/core_pattern
决定文件名/路径,例如:
bashsudo sysctl -w kernel.core_pattern='/var/core/core.%e.%p.%t'
%e
程序名,%p
PID,%t
时间戳。core_pattern
以 |
开头(如 |/usr/lib/systemd/systemd-coredump
),说明 core 被管道到收集器(如 systemd-coredump)。此时文件一般在 /var/lib/systemd/coredump/
,用 coredumpctl
取用。bash# 2 = 允许 setuid 程序的 core,写到安全目录 sudo sysctl -w fs.suid_dumpable=2
--ulimit core=-1
、适当的 SYS_PTRACE
/CAP_SYS_RESOURCE
能力、以及主机层 core_pattern
配置。*(int*)0=0;
或 abort();
bashgcore -o mycore <PID> # gdb/psmisc 提供的工具 # 或在 gdb 中: gdb -p <PID> (gdb) generate-core-file /tmp/core.myapp (gdb) detach; quit
bash# 列表/筛选 coredumpctl list mybinary # 直接进 gdb(自动匹配可执行文件/库) coredumpctl gdb <PID|COREDUMP_ID>
bash# 你需要:core 文件 + 产生 core 的同版本可执行文件/共享库 + 符号文件 gdb /path/to/mybinary /path/to/core (gdb) set pagination off (gdb) info proc cmdline (gdb) info sharedlibrary (gdb) thread apply all bt full # 所有线程完整栈 (gdb) f 0; info registers # 观察崩溃现场寄存器 (gdb) disassemble /m $pc-32, $pc+64 # 汇编+源码对照 (gdb) p someVar # 打印变量
-g
(DWARF),发布包可剥离符号并单独保存 .debug
文件;分析时通过 build-id
或 debuglink
自动匹配。没有符号也能定位到函数/偏移,但源码级定位困难。-O2
/内联会让栈看起来“跳跃”;必要时用 -g3 -fno-omit-frame-pointer
改善可读性。c++filt
。addr2line -e mybinary 0x7f...
。thread apply all bt
,关注持锁/等待线程。info signal
、dmesg | tail
(内核“segfault at … ip …”)确认是越界、非法指令还是除零等。在你们场景里,Java 客户端通过 JNI 调用 yhdfs C++ SDK,很容易出现“Java 进程因 native 崩溃产生 core”的情况。
HotSpot 会生成:
hs_err_pid<pid>.log
(极其重要,纯文本,含崩溃线程、寄存器、堆栈、已加载库、JVM 参数)ulimit -c
允许时,还会把整个进程 core 出来常用选项:
text-XX:+CreateCoredumpOnCrash # JVM 崩溃额外生成 core -XX:ErrorFile=/path/hs_err_pid%p.log # 定位 hs_err 日志位置 -XX:OnError="gcore %p; /bin/sleep 5" # 崩溃瞬间执行命令(如再打一个 core) -XX:+HeapDumpOnOutOfMemoryError # OOM 时生成 heap dump(不是 core) -XX:HeapDumpPath=/path
分析 Java 进程的 core(看 native/JNI 与 Java 两侧):
hs_err_pid*.log
把高概率问题(哪个线程、哪条 JNI 调用)锁定;这样能看到 Java 层栈 + native 桥接点,再跳到 gdb 看 C++ 具体函数。bashjhsdb hsdb --core /path/core --exe /path/java # 或更轻量的 jhsdb jstack --core /path/core --exe /path/java jhsdb jmap --core /path/core --exe /path/java
bashgdb /path/java /path/core (gdb) thread apply all bt (gdb) where # 找到落入 libyhdfs.so / libhdfs.so / libjvm.so 的帧
启用与收集
LimitCORE=infinity
core_pattern
到固定目录或使用 systemd-coredump
,并设置保留/清理策略.debug
或带行号的映射,按 build-id 存储)发生崩溃后
coredumpctl info/gdb
或 gdb <exe> <core>
打开thread apply all bt full
保存现场(脚本化):
bashgdb -q /path/exe /path/core -batch \ -ex 'set pagination off' \ -ex 'info sharedlibrary' \ -ex 'thread apply all bt full' \ -ex 'quit' | tee backtrace.txt
addr2line
/disassemble /m
定位源码行dmesg
里的 segfault 记录Java 客户端/JNI
hs_err_pid*.log
、heap dump
(若 OOM)jhsdb jstack/jmap
+ gdb 对应帧,确认 JNI 参数/内存所有权、线程 attach/detach 是否规范ulimit -c 0
(服务或容器里没改到位)core_pattern
指向了收集器但磁盘配额/保留策略把它清理掉了SIGKILL
结束(如 OOM killer),不会有 corefs.suid_dumpable
未配置eu-stack
/ eu-unstrip
(elfutils),readelf
、objdump -Cd --source
lldb --core core --file exe
(偏 BSD/macOS/也支持 Linux)pstack <pid>
(仅活跃进程),perf report
(性能问题)如果你愿意,我可以给你们 yhdfs 的服务/容器写一份标准化 core 采集与分析 SOP(systemd unit、core_pattern、清理策略、gdb 批处理脚本、jhsdb 用法),让巡检时一键收集关键信息。
对流程的疑惑,我不知道这个定位是哪来的,为什么会存储core-file
是因为java调JNI 使用SDK的时候 SDK 出了什么具体的问题吗? 触发了一些信号,然后就把这个线程的所有东西全都保存下来?
我怎么看 我们目前是怎么存储core-file的 在什么配置文件里查看。
这个core-file的配置 应该是针对系统上运行的所有进程吧?
我现在的需求是 针对一个core-file 我要定位到哪个进程产生的错误,找到具体的日志路径,有些可能是yarn 执行的容器上报错,但是我不知道去哪里找容器 光gdb 和 bt似乎不会显示具体的进程报错的位置。然后因为我对 c++ 不太了解 所以比较艰难
有一些 问题 但不是核心流程的话 我应该放弃对他的执行,先把问题记录下来,然后后续一起拓展一下
如何理解寄存器 或者 其他的内容呢?
可执行文件与各共享库的映射信息(地址、路径、Build ID)
所有线程的寄存器与调用栈(含线程名)
选定的内存段内容(代码段、栈、部分堆/匿名映射等)
进程基本信息与环境变量/命令行(可能含敏感信息)
(ELF note 区)诸如NT_PRSTATUS / NT_PRPSINFO / NT_AUXV / .note.gnu.build-id等调试所需元数据
为什么进程会自己崩溃掉? 插入*(int*)0=0;或abort();
传统 core文件 和 systemd-coredump文件有什么区别?
native崩溃 是怎么崩溃的? c++的sdk 会非常难写吗 会全是指针的写法吗
很好,这些都是一线排障会遇到的真实疑惑。下面我按“为什么会有 core、怎么配置、怎么从一个 core 追到进程/容器/日志、以及你提到的底层细节与 C++/JNI 常见坑”来讲,尽量把每一步做成可执行的清单。
SIGSEGV
访问非法内存、SIGABRT
主动 abort()
、SIGILL
非法指令、SIGBUS
总线错误、SIGFPE
除零等)时,内核把当时的进程状态写成一个“核心转储文件”(core)。hs_err_pid*.log
)。core 的“保存方式”由两层共同决定:系统级写到哪里 + 进程是否允许写。
bashcat /proc/sys/kernel/core_pattern
/var/core/core.%e.%p.%t
),说明直接把 core 文件写到这个目录。|
开头(如 |/usr/lib/systemd/systemd-coredump
),说明核心被管道给收集器(常见是 systemd-coredump),实际存放在它管理的位置(通常 /var/lib/systemd/coredump/
),配在:
bash# systemd-coredump 行为/保留策略 grep -v '^\s*#' /etc/systemd/coredump.conf 2>/dev/null # 或 systemctl cat systemd-coredump
RLIMIT_CORE
。
bashulimit -a | grep 'core file size'
bashcat /proc/<pid>/limits | grep CORE
bashsystemctl cat <your-service> | grep -i -E 'LimitCORE|ExecStart' # 若无,考虑加上: # LimitCORE=infinity
/etc/security/limits.conf
或 /etc/security/limits.d/*.conf
(对交互式 shell 有效)。--ulimit core=-1
;core_pattern
通常仍生效(但 core 文件最终落在宿主收集器处)。小结:core_pattern 是系统全局的;是否真的会落盘还要看每个进程/服务的 ulimit(LimitCORE)。两者都满足才会有 core。
这是你现在巡检最需要的“作战流程”。我给你一份最短路径决策树。
|...coredump
开头)bashcoredumpctl list # 全部 coredumpctl list <comm> # 按可执行名过滤(如 java、yhdfs) coredumpctl info <PID或行号> # 详细元数据(时间、Cmdline、CGroup、Unit、容器信息等)
Exe
(可执行路径)Cmdline
(包含程序参数;YARN 容器很有可能带 application/container 线索)CGroup
/ Unit
(能直接告诉你是哪个 systemd 单元或容器)Timestamp
(对齐你们业务日志时间线)bashcoredumpctl gdb <id> # 自动匹配可执行与库 # Java 进程的话,再: coredumpctl dump <id> -o /tmp/core.java jhsdb jstack --core /tmp/core.java --exe /usr/lib/jvm/.../bin/java
journalctl -u <UnitName> --since 'YYYY-MM-DD HH:MM' --until '...'
journalctl _PID=<崩溃时PID>
。yarn.log-aggregation-enable=true
时):
bashyarn logs -applicationId <application_...> # 拉整个应用 yarn logs -applicationId <...> -containerId <container_...> # 精确到容器
yarn.nodemanager.log-dirs
(本地容器日志根目录)yarn.nodemanager.remote-app-log-dir
yarn-site.xml
,你可以搜:
bashgrep -R "yarn.nodemanager.log-dirs\|remote-app-log-dir\|log-aggregation-enable" /etc/hadoop/conf/* 2>/dev/null
coredumpctl info
里的 Cmdline
与 CGroup
(经常带有 container/app 线索)。yarn-yarn-nodemanager-*.log
与 ResourceManager 的 yarn-yarn-resourcemanager-*.log
,按崩溃时间附近搜该节点上哪个 container 刚刚失败/退出。bashfile core.* readelf -n core.xxx | less # NT_PRPSINFO/NT_FILE/Build-ID/信号等
bashgdb /path/to/exe /path/to/core (gdb) set pagination off (gdb) info sharedlibrary (gdb) thread apply all bt (gdb) info registers
gdb
的 可执行路径(第 1 行就能看到);readelf -n
里的 PRPSINFO(进程名)、时间、信号;NT_FILE
映射列表里经常能看到工作目录/库路径(能反推到哪个组件);hs_err_pid*.log
(或你们通过 -XX:ErrorFile=
指定的路径)。小贴士:在 systemd-coredump 场景,优先用
coredumpctl info
,它把“日志/单元/容器线索”都帮你聚合好了;传统 core 就靠readelf -n
+gdb
+ 现场目录结构来反推。
维度 | 传统 core 文件 | systemd-coredump |
---|---|---|
写入方式 | 直接写到 core_pattern 指定路径 | core_pattern 管道给收集器 |
存放位置 | 你指定的目录(可能很散) | 统一在 /var/lib/systemd/coredump/ ,文件名含元数据 |
附带元数据 | 仅 core 本体 | journald 里保存了 Cmdline/CGroup/Unit/主机/时间/信号 等 |
压缩/清理 | 你自己做 | coredump.conf 可设压缩/保留空间上限 |
取用方式 | gdb exe core | coredumpctl list/info/gdb/dump |
适合场景 | 最小依赖、极简系统 | 规模化运维、便捷检索与保留策略 |
SIGSEGV
assert()
失败、abort()
、glibc 检测到堆损坏(malloc(): memory corruption
)→ SIGABRT
main
或 noexcept
违约 → std::terminate()
→ 通常 SIGABRT
SIGILL
;未对齐访问/坏映射 → SIGBUS
SIGFPE
这些都可能在 JNI 调用栈里发生,从而把 Java 进程一起打成 core。
x86_64 小抄
RIP
:当前指令地址(崩溃点)RSP
:当前栈顶;RBP
:栈帧基址(若未省略)RDI,RSI,RDX,RCX,R8,R9
:前 6 个整型/指针参数RAX
:返回值/临时;EFLAGS
:标志位(ZF/CF …)gdb(gdb) info registers (gdb) disassemble /m $pc-32, $pc+64 # 看崩溃指令周围源码/汇编 (gdb) x/16gx $rsp # 看栈上的指针值 (gdb) bt full # 全栈 + 局部变量(有符号更清晰)
ELF notes(readelf -n core
)
NT_PRSTATUS/PRPSINFO
:线程/进程基本信息、信号NT_AUXV
:运行时辅助向量NT_FILE
:内存映射到文件的列表(含路径)info sharedlibrary
看具体加载了哪些 .so
,版本是否对得上。JNIEnv*
(没 AttachCurrentThread
)→ 未定义行为。jobject
缓存到全局用、或者忘记 NewGlobalRef/DeleteGlobalRef
。不一定。现代 C++ 写法可以把风险显著降下来:
std::unique_ptr
(唯一所有权)、std::shared_ptr
(共享)、配合自定义 deleter;避免裸 new/delete
。span
/string_view
/容器引用描述“借用”,谁分配谁释放。absl::StatusOr<T>
/ std::expected<T,E>
(C++23)而不是返回魔数。AttachCurrentThread
,JNI 入口/退出要“成对”。优先把**能复盘到“谁在何时何地崩”**这条链路打通;下面这些先记账,等主流程稳定再看:
Exe
/Cmdline
看起来是别的进程)的 core:拉个 issue 分派给对应团队。SIGKILL
(OOM killer 导致)且没有 core 的:转入“内存/资源”类巡检而不是崩溃定位。bash# A. 判断全局策略 cat /proc/sys/kernel/core_pattern ulimit -a | grep core # B. systemd-coredump 路径(若启用) coredumpctl list coredumpctl info <id> | sed -n '1,120p' # 抄 Exe/Cmdline/CGroup/Unit/Timestamp/Signal coredumpctl gdb <id> -A # 进 gdb # Java 额外 coredumpctl dump <id> -o /tmp/core.java jhsdb jstack --core /tmp/core.java --exe /usr/bin/java | tee /tmp/jstack.txt # C. 传统 core file core.* readelf -n core.* | sed -n '1,200p' gdb /path/to/exe /path/to/core -ex 'set pagination off' \ -ex 'info sharedlibrary' -ex 'thread apply all bt full' -ex 'quit' | tee /tmp/bt.txt # D. YARN 日志(已知 app/containerId) yarn logs -applicationId application_... > app.log yarn logs -applicationId application_... -containerId container_... > container.log # 若未知,按崩溃时间去 NodeManager/ResourceManager 日志里 grep 附近的失败记录
如果你愿意,把你们一台线上节点的 core_pattern
、某个 core 的 coredumpctl info
输出(去除敏感字段)贴给我,我就按这条 SOP 帮你把**“core → 进程/容器 → 日志路径”**这条链路走一遍,并且一起把你们的巡检清单(哪些立即做、哪些先记录)固化下来。
readelf的结果是这样的
Displaying notes found at file offset 0x00007e78 with length 0x00025380:
Owner Data size Description
CORE 0x00000150 NT_PRSTATUS (prstatus structure)
CORE 0x00000088 NT_PRPSINFO (prpsinfo structure)
CORE 0x00000080 NT_SIGINFO (siginfo_t data)
CORE 0x00000140 NT_AUXV (auxiliary vector)
CORE 0x00009de5 NT_FILE (mapped files)
Page size: 4096
Start End Page Offset
0x0000000000400000 0x0000000000401000 0x0000000000000000
/export/App/jdk1.8.0_60/bin/java
0x0000000000600000 0x0000000000601000 0x0000000000000000
/export/App/jdk1.8.0_60/bin/java
0x00007fe25dc11000 0x00007fe25dc2d000 0x0000000000000393
/export/App/jdk1.8.0_60/jre/lib/ext/cldrdata.jar
0x00007fe25e435000 0x00007fe25e456000 0x0000000000000000
/usr/lib64/liblzo2.so.2.0.0
0x00007fe25e456000 0x00007fe25e655000 0x0000000000000021
/usr/lib64/liblzo2.so.2.0.0
0x00007fe25e655000 0x00007fe25e656000 0x0000000000000020
/usr/lib64/liblzo2.so.2.0.0
0x00007fe25e657000 0x00007fe25e65a000 0x0000000000000000
/export/Data/tmp/hadoop-tmp/nm-local-dir/usercache/root/appcache/application_1754969617538_4473/container_e41_1754969617538_4473_01_000127/tmp/unpacked-5290626419451733540-libgplcompression.so (deleted)
0x00007fe25e65a000 0x00007fe25e85a000 0x0000000000000003
/export/Data/tmp/hadoop-tmp/nm-local-dir/usercache/root/appcache/application_1754969617538_4473/container_e41_1754969617538_4473_01_000127/tmp/unpacked-5290626419451733540-libgplcompression.so (deleted)
0x00007fe25e85a000 0x00007fe25e85b000 0x0000000000000003
/export/Data/tmp/hadoop-tmp/nm-local-dir/usercache/root/appcache/application_1754969617538_4473/container_e41_1754969617538_4473_01_000127/tmp/unpacked-5290626419451733540-libgplcompression.so (deleted)
0x00007fe25e85b000 0x00007fe25e85c000 0x0000000000000004
/export/Data/tmp/hadoop-tmp/nm-local-dir/usercache/root/appcache/application_1754969617538_4473/container_e41_1754969617538_4473_01_000127/tmp/unpacked-5290626419451733540-libgplcompression.so (deleted)
0x00007fe29645b000 0x00007fe2965e1000 0x0000000000000000
/usr/lib64/libstdc++.so.6.0.25
0x00007fe2965e1000 0x00007fe2967e0000 0x0000000000000186
/usr/lib64/libstdc++.so.6.0.25
0x00007fe2967e0000 0x00007fe2967ec000 0x0000000000000185
/usr/lib64/libstdc++.so.6.0.25
0x00007fe2967ec000 0x00007fe2967ed000 0x0000000000000191
(gdb) set pagination off
(gdb) info sharedlibrary
(gdb) thread apply all bt
(gdb) info registers
分析一下 以上内容的命令,命令的作用是什么
分别是用来产出什么信息的,
太好了,这段 readelf
输出已经能给出很多线索。你贴的几条 gdb 命令也都是分析 core 的“基础四件套”。下面我把每条命令的作用 → 会产出什么信息 → 怎么用这些信息定位进程/容器/日志给你逐条说清楚,并顺手解析一下你这份 readelf
片段。
readelf -n core.xxx
(读 core 里的 ELF notes)作用:把 core 文件里“NOTE 区”的元数据(不是代码数据)打印出来。
会产出(你贴出来的几项):
NT_PRSTATUS
:线程状态快照(寄存器、当前信号、栈指针等)。用于确认崩溃线程的寄存器。NT_PRPSINFO
:进程信息(进程名、PID、PPID、UID/GID、短的命令行摘要)。用于确认是谁的进程。NT_SIGINFO
:触发 core 的信号详情(si_signo/si_code/si_addr
等,si_addr
常是出错访问的地址)。NT_AUXV
:进程的 auxiliary vector(平台/加载器参数,平时很少直接用)。NT_FILE
:内存映射的文件列表(页大小、每个映射的 Start/End、页偏移、文件路径)。这是定位“到底加载了哪些 .so/.jar/可执行文件”的关键。你这份 NT_FILE
的几个关键信息我帮你圈一下(很有用):
Page size: 4096
:页大小,后面“Page Offset”列是以页为单位的偏移;真实文件偏移 = Page Offset * 4096
。jdk1.8.0_60/bin/java
的映射(0x400000...
和 0x600000...
)说明崩溃的就是 Java 进程(JDK 1.8.0_60)。.../jre/lib/ext/cldrdata.jar
、/usr/lib64/liblzo2.so.2.0.0
、/usr/lib64/libstdc++.so.6.0.25
等,说明进程加载了这些库/资源。/export/Data/tmp/hadoop-tmp/nm-local-dir/usercache/root/appcache/application_1754969617538_4473/container_e41_1754969617538_4473_01_000127/tmp/unpacked-...-libgplcompression.so (deleted)
1754969617538_4473
e41_1754969617538_4473_01_000127
(deleted)
的意义:该 .so 在进程运行期间被 unlink(删除)了,但内存里依然映射着它(因此 core 还能看到路径)。这在 YARN 容器把 native 依赖临时解包到 tmp/unpacked-*
然后退出时很常见。小结:
readelf -n
主要让你在不进 gdb 前就拿到“是谁(Java)、为什么(信号)、加载了什么(NT_FILE)、在哪个 YARN 容器(路径里包含 application/containerId)”。
gdb
里的四条命令先假设你是这样进 gdb 的:
bashgdb /export/App/jdk1.8.0_60/bin/java /path/to/core
set pagination off
作用:关掉 gdb 的分页提示(那行 “--Type <return> to continue--”)。
产出:不是数据,而是让后续打印一口气吐完,方便重定向保存:
bashgdb ... -ex 'set pagination off' -ex 'thread apply all bt full' ...
info sharedlibrary
作用:列出当前进程(core 中的状态)加载的所有共享库、它们的加载基址(0x...
)、以及 gdb 是否成功为它们加载了符号(Yes/No
)。
产出:一张“库 → 加载地址 → 符号是否可用”的清单。
怎么用:
libstdc++.so.6.0.25
)。Symbols: no
,说明没有调试符号;解决方案是:
-debuginfo
包 / .debug
文件;或set debug-file-directory
/ set solib-search-path
指向你们的符号仓。addr2line
或 list
)。thread apply all bt
作用:对所有线程执行 bt
(backtrace)。
产出:每个线程的调用栈(函数名 + 偏移 + 地址)。
提示:
gdbthread apply all bt full
full
会打印局部变量/参数值(有符号时很有用),快速看出是空指针、越界还是已释放内存。info threads
找到触发信号的线程(通常在回溯顶部能看到 SIGSEGV
等字样,或看 Program terminated with signal ...
)。info registers
作用:打印当前选中线程的寄存器(x86_64 的 RIP/RSP/RBP/RDI/...
)。
产出:崩溃点地址(RIP
)、栈顶(RSP
)、函数参数寄存器(RDI/RSI/RDX/...
)等。
怎么用:
RIP
就是崩溃指令地址,常配合:
看出错指令附近的源码与汇编。gdbdisassemble /m $pc-32, $pc+64
bt full
的形参,能判断是不是传了野指针/空指针。gdbthread <崩溃线程号> info registers
你这份 NT_FILE
已经暴露了 applicationId 与 containerId,因此可以直接拉 YARN 聚合日志:
bash# 拉整个应用 yarn logs -applicationId application_1754969617538_4473 > app.log # 精确到容器 yarn logs -applicationId application_1754969617538_4473 \ -containerId container_e41_1754969617538_4473_01_000127 > container_127.log
另外再查 JVM 自带的崩溃文件(如果有配置更好):
bash# 如果 JVM 参数里设了 -XX:ErrorFile=... # 不知道的话,先在预计工作目录或节点上全局搜 find /export -maxdepth 5 -name 'hs_err_pid*.log' -mmin -120
hs_err_pid*.log
能直接告诉你崩溃线程/信号/Java 栈以及 JNI 桥接点,和 gdb 的 native 栈相互印证。
(deleted)
要不要慌?不慌。它只是说明该 .so 在文件系统里被删除(或临时文件已清理),但仍在进程地址空间里。
对定位的影响:
.so
和 .debug
放进符号仓(按 Build-ID 存放最佳);set solib-search-path
指向这份仓库,或 add-symbol-file
加载符号。bash# 1) 先看 core notes,拿到容器/应用线索 readelf -n /path/to/core | less # 2) 用 gdb 出全量回溯(保存到文件) gdb /export/App/jdk1.8.0_60/bin/java /path/to/core \ -ex 'set pagination off' \ -ex 'info sharedlibrary' \ -ex 'thread apply all bt full' \ -ex 'quit' | tee /tmp/bt.txt # 3) 通过 readelf 里看到的 applicationId/containerId 拉日志 yarn logs -applicationId application_1754969617538_4473 > app.log yarn logs -applicationId application_1754969617538_4473 \ -containerId container_e41_1754969617538_4473_01_000127 > container_127.log # 4)(可选)找 hs_err(如果 JVM 崩溃会写) find /export -name 'hs_err_pid*.log' -mmin -120
如果你愿意,把这份 core 的触发信号(NT_SIGINFO
)以及你跑完 thread apply all bt full
的前 30 行贴过来(去敏),我可以帮你判断是“空指针/越界/已释放内存/断言失败/库冲突”里的哪一类,并给出下一步要抓的符号或日志点。
首先对4条命令的输出,进行详细探讨
此外有一些问题,
什么是加载基址
bt 和 thread apply all bt full有什么关系? 为什么会有多个线程 都对这个core-file产生影响?
如何判断野指针 空指针;然后接下来是一些命令和具体的输出
(gdb) info sharedlibrary
From To Syms Read Shared Object Library
0x00007fe2e0a7a8a0 0x00007fe2e0a88dc5 Yes () /lib64/libpthread.so.0
0x00007fe2e0860310 0x00007fe2e086d588 Yes () /export/App/jdk1.8.0_60/bin/../lib/amd64/jli/libjli.so
0x00007fe2e065b090 0x00007fe2e065be89 Yes () /lib64/libdl.so.2
0x00007fe2e02b9900 0x00007fe2e0403f0f Yes () /lib64/libc.so.6
0x00007fe2e0c95050 0x00007fe2e0cb4854 Yes () /lib64/ld-linux-x86-64.so.2
0x00007fe2df4dc130 0x00007fe2dfda5da8 Yes () /export/App/jdk1.8.0_60/jre/lib/amd64/server/libjvm.so
0x00007fe2def48510 0x00007fe2defe77ca Yes () /lib64/libm.so.6
0x00007fe2ded355e0 0x00007fe2ded38940 Yes () /lib64/librt.so.1
0x00007fe2deb282a0 0x00007fe2deb2f2a8 Yes () /export/App/jdk1.8.0_60/jre/lib/amd64/libverify.so
0x00007fe2de9055b0 0x00007fe2de91ba68 Yes () /export/App/jdk1.8.0_60/jre/lib/amd64/libjava.so
0x00007fe2de6f07a0 0x00007fe2de6f56b6 Yes () /lib64/libnss_sss.so.2
0x00007fe2ddcda720 0x00007fe2ddce0b3c Yes () /lib64/libnss_files.so.2
0x00007fe2ddabf750 0x00007fe2ddad0578 Yes () /export/App/jdk1.8.0_60/jre/lib/amd64/libzip.so
0x00007fe29e8e8b10 0x00007fe29e8eb538 Yes () /export/App/jdk1.8.0_60/jre/lib/amd64/libmanagement.so
0x00007fe29e3904b0 0x00007fe29e3a10d8 Yes /export/Instances/nodemgr-hadoop-hd-pre/9.nodemgr-hadoop-hd-pre/runtime/nodemanager/lib/native/libhadoop.so
0x00007fe29e177a10 0x00007fe29e185c58 Yes () /export/App/jdk1.8.0_60/jre/lib/amd64/libnet.so
0x00007fe29ae87b70 0x00007fe29ae8eb18 Yes () /export/App/jdk1.8.0_60/jre/lib/amd64/libnio.so
0x00007fe29ac806e0 0x00007fe29ac80a98 Yes () /export/App/jdk1.8.0_60/jre/lib/amd64/libjaas_unix.so
0x00007fe29a83a510 0x00007fe29a857b68 Yes () /export/App/jdk1.8.0_60/jre/lib/amd64/libsunec.so
0x00007fe29a610e00 0x00007fe29a621a55 Yes () /lib64/libgcc_s.so.1
No /export/Data/tmp/hadoop-tmp/nm-local-dir/usercache/root/appcache/application_1754969617538_4473/container_e41_1754969617538_4473_01_000127/tmp/jffi5762508752498775304.so
0x00007fe298f1c810 0x00007fe298fab204 Yes () /lib64/libnss_systemd.so.2
0x00007fe298d0a890 0x00007fe298d0c30a Yes () /lib64/libcap.so.2
0x00007fe298abda10 0x00007fe298af3852 Yes () /lib64/libmount.so.1
0x00007fe298868620 0x00007fe298899392 Yes () /lib64/libblkid.so.1
0x00007fe298656a50 0x00007fe29865a481 Yes () /lib64/libuuid.so.1
0x00007fe298431aa0 0x00007fe2984498ff Yes () /lib64/libselinux.so.1
0x00007fe2981a8350 0x00007fe298203803 Yes () /lib64/libpcre2-8.so.0
0x00007fe297f65dc0 0x00007fe297f96058 Yes /lib64/libnss_doorgod.so.2
0x00007fe296a93ee0 0x00007fe2971f9544 Yes /export/Data/tmp/hadoop-tmp/libjdufs_sdk.so
0x00007fe2967f2700 0x00007fe2967ff987 Yes () /lib64/libz.so.1
0x00007fe2964eab90 0x00007fe2965a0be2 Yes () /lib64/libstdc++.so.6
No /export/Data/tmp/hadoop-tmp/nm-local-dir/usercache/root/appcache/application_1754969617538_4473/container_e41_1754969617538_4473_01_000127/tmp/unpacked-5290626419451733540-libgplcompression.so
0x00007fe25e437720 0x00007fe25e452580 Yes () /lib64/liblzo2.so.2
(): Shared library is missing debugging information.
(gdb) info threads
Id Target Id Frame
超级好的样本!我按你给的输出,把“4 条命令的作用/产出/怎么读”说清楚,再回答你几个关键概念(加载基址、bt vs thread apply all bt full、如何判定空/野指针),最后结合你这份回溯给出“下一步怎么查”。
readelf -n core.xxx
做什么:读取 core 里的 ELF notes(内核写进 core 的“元数据”)。
产出(你看到的):
NT_PRSTATUS
:某个线程的寄存器、当时收到的信号等。NT_PRPSINFO
:进程名、PID、UID/GID、命令行摘要。NT_SIGINFO
:触发 core 的具体信号(si_signo/si_code/si_addr
)。si_addr
常是出错地址。NT_AUXV
:辅助向量(平台/加载器参数)。NT_FILE
:所有内存映射文件(Start/End/页偏移/路径)。/export/App/jdk1.8.0_60/bin/java
(说明崩的是 Java 进程)。application_...
和 container_...
(YARN 应用与容器 ID),而且若干 .so
标注 (deleted),典型 YARN 临时解包后清理了文件,但仍在内存映射中。用途:即便不进 gdb,也能知道谁崩的、什么信号、加载了哪些库、是哪一个 YARN 容器。
set pagination off
做什么:关闭 gdb 的分页提示。
产出:不是数据,而是让接下来的输出一口气打印,便于 | tee
保存。
info sharedlibrary
做什么:列出当时加载的所有共享库,每个库的加载基址范围(From/To)以及是否读到了符号。
你这份输出怎么读:
From
/To
:这是加载基址区间(下面有解释“什么是加载基址”)。ASLR 环境里每次地址会变。Syms Read
列:
Yes (*)
:加载到了符号表,但没有调试符号(即只有函数名,缺行号/局部变量);Yes
(无星号):既有符号也可能含调试信息(是否有行号要看具体包是否带 debuginfo);No
:没有符号(gdb 只能看到裸地址,几乎没有可读性)。No
很关键:
/.../tmp/jffi....so
(JNR/JFFI 生成的 JNI 动态库)/.../unpacked-...-libgplcompression.so
(已删除的临时 .so)
这两处如果参与崩溃,必须补齐对应版本的符号或把 .so 文件再放回来(或从符号仓通过 Build-ID 映射)。用途:确认库版本、符号是否齐全、以及崩溃栈里地址如何做“基址+偏移”的还原。
info threads
/ bt
/ thread apply all bt full
info threads
:列出所有线程(每个都有 gdb 的 Id、LWP(内核线程号)、当前栈顶函数)。
pthread_cond_wait
/ epoll_wait
/ nanosleep
,这是很正常:核心转储时,只有“收到致命信号/主动 abort 的那个线程”是“出事线程”,其他线程只是被一并快照,方便你排查死锁/卡住/线程间关系。bt
:当前选中线程的回溯(栈)。你粘贴的 bt
是线程 1(带星号的那行)。thread apply all bt full
:对所有线程做 bt full
。
full
的意义是:尽量打印形参/局部变量的值,配合调试符号能看到指针/长度等,更易判断空指针/越界。bt
的关系:bt
只看一个线程;thread apply all bt full
一次性看所有线程,信息量最大,通常用于把现场完整保存到文件。用途:
- 快速锁定出事的线程(回溯里往往能看到
raise/abort/signalHandler
、或“Program terminated with signal…”);- 观察其它线程是否有死锁/卡 wait,是否与崩溃线程有资源关系(比如持有某个互斥/读写锁)。
内存里每个共享库(*.so)会被映射到一个基址(base address),后续函数在该库内的地址 = 基址 + 偏移。
info sharedlibrary
里 From
就可以看作“当前这次运行时的加载基址”(精确来说是该库某段的起始映射地址)。offset = 崩溃地址 - 加载基址(From)
addr2line -e /path/to/lib.so 0x<offset>
还原到源码行(前提是你手里这份 lib.so 带符号或有 .debug
文件)。bt
vs thread apply all bt full
的关系?bt
= 当前线程回溯。thread apply all bt full
= 所有线程的回溯 + 形参/局部变量(如果有符号)。abort()
),但为了帮助你排查线程间问题,内核把 所有线程 的上下文都写进 core。常用的三把锤子:
A. 看 NT_SIGINFO
的 si_addr
(readelf -n core
或 gdb 里 info signals
/p $_siginfo
)
si_addr = 0x0
或非常小的地址:极大概率空指针解引用(SIGSEGV
)。B. 在 gdb 里看崩溃线程的寄存器/参数
gdb# 选中崩溃线程 (gdb) thread <id> (gdb) info registers # 看 RDI/RSI/RDX...(x86_64 调用前6个参数寄存器) (gdb) bt full # 看形参打印出来的指针值/长度 (gdb) x/gx 0xDEADBEEF... # 尝试读该地址(读不出来往往是未映射)
0x0
被当作对象/指针使用 → 空指针。x/gx
报错或落在 unmapped 区域 → 野指针。C. 查内核日志 / dmesg
dmesg
里常有一行:segfault at <addr> ip <pc> sp <sp> error ... in libX.so[base+off]
<addr>
与 ip
提示了“访问的内存地址”和“执行位置”,配合 info sharedlibrary
的基址能快速换算到库内偏移。注意:你这份
bt
的顶部是raise -> abort -> libjvm.so
,看起来是 JVM 主动触发了致命错误流程(可能是捕获到某个信号后调用report_and_die()
再abort()
)。这种情况下,原始的故障地址不要只看#0~#3
,要去找:
#6 <signal handler called>
之前的原始帧(如果保留在 core 里能看到),或- JVM 的
hs_err_pid*.log
里的Problematic frame
与siginfo
(最直接),或readelf -n
的NT_SIGINFO.si_addr
。
info sharedlibrary
java
。libjvm.so
、libstdc++.so.6
、/export/Data/tmp/hadoop-tmp/libjdufs_sdk.so
(你们的 UFS SDK?)、libhadoop.so
、以及 两处 Syms Read: No
的临时 JNI so(jffi*.so
、libgplcompression.so (deleted)
)。info threads
pthread_cond_wait
/epoll_wait
,说明系统正常“多数线程在等”。raise -> abort
上,这是“正在执行致命错误流程的线程”。bt
#0 raise -> #1 abort -> #2 os::abort -> #3 VMError::report_and_die -> #4/#5 JVM_handle_linux_signal/signalHandler
:#6 <signal handler called>
:表明之前发生过一个信号(通常是 SIGSEGV/SIGBUS/SIGILL/SIGFPE
之一),被 HotSpot 的 handler 接住。#7 之后进入你们 native 代码(spdlog)**的调用链**(
TcpTransportExtend::doRecv` 等):doRecv
(或它调用链更深处),被 JVM handler 捕获;malloc(): memory corruption
、致命断言),导致这条 abort 栈(也就会看见 raise/abort
顶在前面)。下一步最关键的判断:到底是“信号类崩溃(比如 SIGSEGV
)”还是“主动 abort(如断言失败/堆损坏)”。
NT_SIGINFO
的 si_signo/si_addr
;hs_err_pid*.log
(如果开启了 -XX:ErrorFile
更容易找),看 Problematic frame
和 siginfo
。bash# 核心转储里的信号信息 readelf -n /path/to/core | sed -n '1,160p' # 找 NT_SIGINFO 段,抄 si_signo/si_addr
si_signo = SIGSEGV
且 si_addr = 0x0
→ 空指针很可能;si_addr
是一个奇怪的大地址 → 倾向野指针/越界;SIGABRT
→ 倾向主动 abort(断言、堆损坏等),要找stderr / hs_err 里的报错关键字。gdb(gdb) thread 1 # 你的星号在线程 1 (gdb) bt full (gdb) f 12 # 跳到 #12 doRecv (gdb) list # 看源码(需有调试符号) (gdb) info args; info locals # 看形参/局部(指针/长度) (gdb) p this (gdb) x/gx this # 这类指针是否合理(非 0、落在映射内)
addr2line
定位源码。hs_err_pid*.log
bashfind /export -name 'hs_err_pid*.log' -mmin -180 # 或者查看 JVM 启动参数是否有 -XX:ErrorFile 指定
# A fatal error has been detected by the Java Runtime Environment:
SIGSEGV
/SIGABRT
、Problematic frame: ... in <lib>.so+0x<offset>
,这能直接告诉你哪个库的哪个偏移触发了问题。NT_FILE
里已经给了 application_...
与 container_...
,直接拉:bashyarn logs -applicationId application_1754969617538_4473 \ -containerId container_e41_1754969617538_4473_01_000127 \ > container_127.log
abort()
),但其它线程的状态对判断死锁、资源竞争、是否有后台刷日志/网络 I/O 卡住很重要。所以我们总是用 thread apply all bt full
把“群像”拿到手,再重点分析“致命线程”。Syms Read: No
的两个 JNI so(jffi*.so
、libgplcompression.so
)如果参与了调用链,必须补符号(或保留一份未删除的 so),否则就算看到“库+偏移”,也无法快速还原到源码行。spdlog
出现在崩溃线程的链路上(flush_ / sink_it_ / log_it_
),不一定是 spdlog 的错,更可能是你们在 doRecv
里写日志时用了无效内存(空/野指针),导致格式化参数访问出错。bt full
里看形参(尤其是 string/buffer 指针与长度)往往一眼能看出来。需要的话,你把 readelf -n
里 NT_SIGINFO
的三项(si_signo/si_code/si_addr
)贴一下,再加上 bt full
中 #12 doRecv
那几帧的“形参和局部变量”部分(去掉敏感内容)。我就能告诉你是空指针还是野指针/越界的概率更大,并给出具体的“该看哪段代码/该打哪些保护(边界检查/nullptr 检查)”。
符号表 和 调试符号,补符号是什么意思
每个共享库 之间是完全连着的吗,这和操作系统的段页式存储管理方式有关吗?
去 core 里看NT_SIGINFO的si_signo/si_addr这个是什么意思?
跳到 #12 doRecv 的目的是什么?
是否需要定位到具体哪个so 报错的?定位了以后,再结合日志,查看上下文是怎么样的,然后分析出 到底出了什么问题,导致 野指针 或者 空指针 ?
很好的问题!我按你问的点逐个讲清楚,并给出落地做法与常用命令。
可执行/共享库(ELF)里可能有三类和符号相关的东西:
.dynsym
(动态符号表)
.symtab
(完整符号表)
DWARF 调试信息(.debug_*
段)
libxyz.so.debug
),或通过 build-id
仓库统一管理。gdb 里 “Syms Read: Yes (*)” 的含义
Yes (*)
:读到了符号,但缺调试信息(只有函数名、没有行号/局部变量)。Yes
(无星号):通常表示能拿到足够的调试信息(具体是否有行号要看构建方式)。No
:连函数名都没有,只能看裸地址。“补符号” 就是:
把与你崩溃二进制完全一致的调试符号文件(或未 strip 的二进制)找回来,让 gdb 能显示行号/局部变量。常见做法:
debuginfo-install glibc libstdc++ ...
apt-get install libc6-dbg ...
.debug
文件,按 Build-ID 存在“符号仓”(/usr/lib/debug/.build-id/xx/yyyyyy.debug
),分析机器上 debug-file-directory
指到这个仓库即可。.so
放到一台“分析机”,gdb 用 set solib-search-path
指过去。不是“拼成一个大文件”的那种连着。运行时的情况是:
.so
都被内核按页(通常 4K)映射到同一进程的虚拟地址空间不同区域(这才会有你看到的 From/To
加载区间)。一句话:它们在同一进程里“彼此可调用”,但物理上是各自独立的文件,被映射到不同虚拟区间。
在 readelf -n core
的 NT_SIGINFO
段里:
si_signo
:触发核心转储的信号编号(如 SIGSEGV
、SIGABRT
、SIGBUS
、SIGFPE
、SIGILL
)。si_code
:信号来源和子类型(例如 SEGV_MAPERR
未映射地址、SEGV_ACCERR
权限错误;FPE_INTDIV
整数除零等)。si_addr
:对内存访问类信号(SEGV/BUS),这里给出出错访问的地址:
0x0
或极小 → 空指针解引用高度可疑;SIGABRT
通常无意义(是主动 abort()
)。怎么看(两种方式)
bash# 直接从 core 读取 readelf -n /path/to/core | sed -n '1,160p' # 找 NT_SIGINFO 小节 # 在 gdb 里(更快) (gdb) p $_siginfo # 打印当前线程的 siginfo_t(若 core 由信号触发)
你的 bt
里第 12 帧是:
text#12 kunlun::netlib::TcpTransportExtend::doRecv(...) at .../tcp_transport_extend.cpp:421
目的:定位到发生问题的业务代码“上下文”,检查函数参数/局部变量是否异常(空/越界/悬空),从而判断根因。
怎么做:
gdb(gdb) frame 12 # 或 f 12,选中那一帧 (gdb) list # 看 421 行上下代码(需要调试符号) (gdb) info args # 打印形参(指针、长度、引用等) (gdb) info locals # 打印局部变量 (gdb) p this # 看 this 是否合理(非 0) (gdb) x/gx 0x... # 对可疑指针地址做内存探针(看是否映射/可读)
为什么不只看最顶上几帧(raise/abort/libjvm)?
那是JVM 的致命错误处理路径;真正的错误很可能在再下一层(例如你的 doRecv
、或 spdlog
里访问了坏内存)。所以我们要“下潜到你的代码帧”。
是的,这是标准流程。 一个靠谱的闭环通常是:
先确定信号与关键帧
NT_SIGINFO.si_signo/si_addr
,判断“信号类型/出错地址”;doRecv
或紧邻它的库调用)。定位到具体库 + 偏移 + 源码行
bt
已经给出“文件:行号”,很棒;addr2line
:
bash# 例:fault_ip = 0x7fe296c20ab7, 基址 From = 0x7fe296...(见 info sharedlibrary) # offset = fault_ip - base addr2line -e /path/to/libxxx.so 0x<offset>
拉对应容器/进程的日志,拼时间线
NT_FILE
拿到 applicationId/containerId
,用 yarn logs -applicationId ... -containerId ...
;hs_err_pid*.log
(若有):里面会写 Problematic frame: ... in libYYY.so+0xOFFSET
,对齐 core 结果。判定空指针/野指针/堆损坏
si_addr=0x0
,info args/locals
里某对象/指针为 0,却被解引用。si_addr
在未映射区,或明显越界(例如长度 16 的 buffer 被写到 200 字节);si_signo=SIGABRT
,日志或 hs_err
里有 glibc/ASan 的报错;回溯里常见 __libc_malloc
/free
附近帧。注意“当前出错帧未必是根因”
spdlog
或别的库里读到这块坏内存才崩。看信号信息
bashreadelf -n core | sed -n '1,160p' # 找 NT_SIGINFO # 或 gdb 内 (gdb) p $_siginfo
基址换算偏移 + 行号
bash# 先看基址 (gdb) info sharedlibrary | grep libjdufs_sdk.so # 计算 offset = fault_ip - base,然后: addr2line -fe /export/Data/tmp/hadoop-tmp/libjdufs_sdk.so 0x<offset>
补符号的两种路径
debuginfo-install glibc libstdc++ zlib ...
或 apt-get install ...-dbg
.debug
放到 /usr/lib/debug/.build-id/xx/xxxx.debug
,或用
gdb(gdb) set debug-file-directory /path/to/debug-repo (gdb) set solib-search-path /path/to/so-repo
选中关键帧看变量
gdb(gdb) f 12 (gdb) list (gdb) info args (gdb) info locals (gdb) disassemble /m $pc-32, $pc+64
拉容器日志与 hs_err
bashyarn logs -applicationId application_... -containerId container_... > container.log find /export -name 'hs_err_pid*.log' -mmin 180
NT_SIGINFO
的 si_signo/si_addr
告诉你是什么信号、访问了哪个地址,常用于判断空指针(0 地址)还是野指针/越界(怪地址)。#12 doRecv
是为了在你的业务代码帧里看参数/局部变量,结合加载基址/偏移和容器/JVM 日志,最终判定“是哪一个 .so
的哪一行”出问题,以及它是空指针/野指针/堆损坏中的哪一类。如果你愿意,把 p $_siginfo
的输出和 frame 12
的 info args/locals
(去敏)贴一下,我就按上述套路帮你直接判空/野指针的大概率方向,并给出要补哪份符号、看哪段日志。
我这没有 si_signo 呀 还有 si_addr ,你帮我看下
readelf -n core-reactor_ufs-cli-3843691-1755478322 | sed -n '1,160p'
Displaying notes found at file offset 0x00007e78 with length 0x00025380:
Owner Data size Description
CORE 0x00000150 NT_PRSTATUS (prstatus structure)
CORE 0x00000088 NT_PRPSINFO (prpsinfo structure)
CORE 0x00000080 NT_SIGINFO (siginfo_t data)
CORE 0x00000140 NT_AUXV (auxiliary vector)
CORE 0x00009de5 NT_FILE (mapped files)
Page size: 4096
Start End Page Offset
0x0000000000400000 0x0000000000401000 0x0000000000000000
/export/App/jdk1.8.0_60/bin/java
0x0000000000600000 0x0000000000601000 0x0000000000000000
/export/App/jdk1.8.0_60/bin/java
0x00007fe25dc11000 0x00007fe25dc2d000 0x0000000000000393
/export/App/jdk1.8.0_60/jre/lib/ext/cldrdata.jar
0x00007fe25e435000 0x00007fe25e456000 0x0000000000000000
/usr/lib64/liblzo2.so.2.0.0
0x00007fe25e456000 0x00007fe25e655000 0x0000000000000021
/usr/lib64/liblzo2.so.2.0.0
0x00007fe25e655000 0x00007fe25e656000 0x0000000000000020
/usr/lib64/liblzo2.so.2.0.0
0x00007fe25e657000 0x00007fe25e65a000 0x0000000000000000
/export/Data/tmp/hadoop-tmp/nm-local-dir/usercache/root/appcache/application_1754969617538_4473/container_e41_1754969617538_4473_01_000127/tmp/unpacked-5290626419451733540-libgplcompression.so (deleted)
0x00007fe25e65a000 0x00007fe25e85a000 0x0000000000000003
/export/Data/tmp/hadoop-tmp/nm-local-dir/usercache/root/appcache/application_1754969617538_4473/container_e41_1754969617538_4473_01_000127/tmp/unpacked-5290626419451733540-libgplcompression.so (deleted)
0x00007fe25e85a000 0x00007fe25e85b000 0x0000000000000003
/export/Data/tmp/hadoop-tmp/nm-local-dir/usercache/root/appcache/application_1754969617538_4473/container_e41_1754969617538_4473_01_000127/tmp/unpacked-5290626419451733540-libgplcompression.so (deleted)
0x00007fe25e85b000 0x00007fe25e85c000 0x0000000000000004
/export/Data/tmp/hadoop-tmp/nm-local-dir/usercache/root/appcache/application_1754969617538_4473/container_e41_1754969617538_4473_01_000127/tmp/unpacked-5290626419451733540-libgplcompression.so (deleted)
0x00007fe29645b000 0x00007fe2965e1000 0x0000000000000000
/usr/lib64/libstdc++.so.6.0.25
0x00007fe2965e1000 0x00007fe2967e0000 0x0000000000000186
/usr/lib64/libstdc++.so.6.0.25
0x00007fe2967e0000 0x00007fe2967ec000 0x0000000000000185
/usr/lib64/libstdc++.so.6.0.25
0x00007fe2967ec000 0x00007fe2967ed000 0x0000000000000191
/usr/lib64/libstdc++.so.6.0.25
0x00007fe2967f0000 0x00007fe296806000 0x0000000000000000
/usr/lib64/libz.so.1.2.11
0x00007fe296806000 0x00007fe296a05000 0x0000000000000016
/usr/lib64/libz.so.1.2.11
0x00007fe296a05000 0x00007fe296a06000 0x0000000000000015
/usr/lib64/libz.so.1.2.11
0x00007fe296a08000 0x00007fe296a12000 0x0000000000000116
/export/App/jdk1.8.0_60/jre/lib/ext/localedata.jar
0x00007fe296a12000 0x00007fe2974b4000 0x0000000000000000
/export/Data/tmp/hadoop-tmp/libjdufs_sdk.so
0x00007fe2974b4000 0x00007fe2976b3000 0x0000000000000aa2
/export/Data/tmp/hadoop-tmp/libjdufs_sdk.so
0x00007fe2976b3000 0x00007fe2976dc000 0x0000000000000aa1
/export/Data/tmp/hadoop-tmp/libjdufs_sdk.so
0x00007fe2976dc000 0x00007fe2976e3000 0x0000000000000aca
/export/Data/tmp/hadoop-tmp/libjdufs_sdk.so
0x00007fe297946000 0x00007fe297f63000 0x0000000000000000
/var/lib/sss/mc/group
0x00007fe297f63000 0x00007fe297fa5000 0x0000000000000000
/usr/lib64/libnss_doorgod.so.2
0x00007fe297fa5000 0x00007fe2981a4000 0x0000000000000042
/usr/lib64/libnss_doorgod.so.2
0x00007fe2981a4000 0x00007fe2981a5000 0x0000000000000041
/usr/lib64/libnss_doorgod.so.2
0x00007fe2981a6000 0x00007fe298229000 0x0000000000000000
/usr/lib64/libpcre2-8.so.0.7.1
0x00007fe298229000 0x00007fe298428000 0x0000000000000083
/usr/lib64/libpcre2-8.so.0.7.1
0x00007fe298428000 0x00007fe298429000 0x0000000000000082
/usr/lib64/libpcre2-8.so.0.7.1
0x00007fe298429000 0x00007fe29842a000 0x0000000000000083
/usr/lib64/libpcre2-8.so.0.7.1
0x00007fe29842a000 0x00007fe298451000 0x0000000000000000
/usr/lib64/libselinux.so.1
0x00007fe298451000 0x00007fe298651000 0x0000000000000027
/usr/lib64/libselinux.so.1
0x00007fe298651000 0x00007fe298652000 0x0000000000000027
/usr/lib64/libselinux.so.1
0x00007fe298652000 0x00007fe298653000 0x0000000000000028
/usr/lib64/libselinux.so.1
0x00007fe298655000 0x00007fe29865b000 0x0000000000000000
/usr/lib64/libuuid.so.1.3.0
0x00007fe29865b000 0x00007fe29885b000 0x0000000000000006
/usr/lib64/libuuid.so.1.3.0
0x00007fe29885b000 0x00007fe29885c000 0x0000000000000006
/usr/lib64/libuuid.so.1.3.0
0x00007fe29885d000 0x00007fe2988a9000 0x0000000000000000
/usr/lib64/libblkid.so.1.1.0
0x00007fe2988a9000 0x00007fe298aa8000 0x000000000000004c
/usr/lib64/libblkid.so.1.1.0
0x00007fe298aa8000 0x00007fe298aad000 0x000000000000004b
/usr/lib64/libblkid.so.1.1.0
0x00007fe298aad000 0x00007fe298aae000 0x0000000000000050
/usr/lib64/libblkid.so.1.1.0
0x00007fe298aaf000 0x00007fe298b04000 0x0000000000000000
/usr/lib64/libmount.so.1.1.0
0x00007fe298b04000 0x00007fe298d04000 0x0000000000000055
/usr/lib64/libmount.so.1.1.0
0x00007fe298d04000 0x00007fe298d07000 0x0000000000000055
/usr/lib64/libmount.so.1.1.0
0x00007fe298d07000 0x00007fe298d08000 0x0000000000000058
/usr/lib64/libmount.so.1.1.0
0x00007fe298d09000 0x00007fe298d0d000 0x0000000000000000
/usr/lib64/libcap.so.2.26
0x00007fe298d0d000 0x00007fe298f0d000 0x0000000000000004
/usr/lib64/libcap.so.2.26
0x00007fe298f0d000 0x00007fe298f0e000 0x0000000000000004
/usr/lib64/libcap.so.2.26
0x00007fe298f0e000 0x00007fe298f0f000 0x0000000000000005
/usr/lib64/libcap.so.2.26
0x00007fe298f0f000 0x00007fe298fe1000 0x0000000000000000
/usr/lib64/libnss_systemd.so.2
0x00007fe298fe1000 0x00007fe2991e1000 0x00000000000000d2
/usr/lib64/libnss_systemd.so.2
0x00007fe2991e1000 0x00007fe2991e5000 0x00000000000000d2
/usr/lib64/libnss_systemd.so.2
0x00007fe2991e5000 0x00007fe2991e6000 0x00000000000000d6
/usr/lib64/libnss_systemd.so.2
0x00007fe2991eb000 0x00007fe2991f2000 0x0000000000000000
/usr/lib64/gconv/gconv-modules.cache
0x00007fe2991f2000 0x00007fe299bdf000 0x0000000000000000
/var/lib/sss/mc/initgroups
0x00007fe299bdf000 0x00007fe299bf1000 0x0000000000000000
/export/Data/tmp/hadoop-tmp/nm-local-dir/usercache/root/appcache/application_1754969617538_4473/container_e41_1754969617538_4473_01_000127/tmp/jffi5762508752498775304.so (deleted)
0x00007fe299bf1000 0x00007fe299df0000 0x0000000000000012
/export/Data/tmp/hadoop-tmp/nm-local-dir/usercache/root/appcache/application_1754969617538_4473/container_e41_1754969617538_4473_01_000127/tmp/jffi5762508752498775304.so (deleted)
0x00007fe299df0000 0x00007fe299df1000 0x0000000000000011
/export/Data/tmp/hadoop-tmp/nm-local-dir/usercache/root/appcache/application_1754969617538_4473/container_e41_1754969617538_4473_01_000127/tmp/jffi5762508752498775304.so (deleted)
0x00007fe299ef2000 0x00007fe299ef8000 0x00000000000002f8
/export/App/jdk1.8.0_60/jre/lib/charsets.jar
0x00007fe299ef8000 0x00007fe299f0b000 0x0000000000000344
/export/App/jdk1.8.0_60/jre/lib/resources.jar
0x00007fe29a60e000 0x00007fe29a625000 0x0000000000000000
/usr/lib64/libgcc_s-8-20210514.so.1
0x00007fe29a625000 0x00007fe29a824000 0x0000000000000017
/usr/lib64/libgcc_s-8-20210514.so.1
0x00007fe29a824000 0x00007fe29a825000 0x0000000000000016
/usr/lib64/libgcc_s-8-20210514.so.1
0x00007fe29a825000 0x00007fe29a826000 0x0000000000000017
/usr/lib64/libgcc_s-8-20210514.so.1
0x00007fe29a82b000 0x00007fe29a831000 0x000000000000003f
/export/App/jdk1.8.0_60/jre/lib/ext/sunjce_provider.jar
0x00007fe29a831000 0x00007fe29a863000 0x0000000000000000
/export/App/jdk1.8.0_60/jre/lib/amd64/libsunec.so
0x00007fe29a863000 0x00007fe29aa62000 0x0000000000000032
/export/App/jdk1.8.0_60/jre/lib/amd64/libsunec.so
0x00007fe29aa62000 0x00007fe29aa68000 0x0000000000000031
/export/App/jdk1.8.0_60/jre/lib/amd64/libsunec.so
0x00007fe29aa78000 0x00007fe29aa79000 0x0000000000000009
/export/App/jdk1.8.0_60/jre/lib/ext/sunec.jar
0x00007fe29aa79000 0x00007fe29aa7c000 0x000000000000001a
/export/App/jdk1.8.0_60/jre/lib/jce.jar
0x00007fe29ac7c000 0x00007fe29ac80000 0x0000000000000095
/export/App/jdk1.8.0_60/jre/lib/jsse.jar
0x00007fe29ac80000 0x00007fe29ac81000 0x0000000000000000
/export/App/jdk1.8.0_60/jre/lib/amd64/libjaas_unix.so
0x00007fe29ac81000 0x00007fe29ae80000 0x0000000000000001
/export/App/jdk1.8.0_60/jre/lib/amd64/libjaas_unix.so
0x00007fe29ae80000 0x00007fe29ae81000 0x0000000000000000
/export/App/jdk1.8.0_60/jre/lib/amd64/libjaas_unix.so
0x00007fe29ae81000 0x00007fe29ae92000 0x0000000000000000
/export/App/jdk1.8.0_60/jre/lib/amd64/libnio.so
此外我还有一些其他的问题:
什么是动态链接,so文件是什么,java怎么调用这些so文件的,这些so文件为什么要strip呢?
虚拟地址空间是什么? 是指物理空间上虽然 不连着,但是虚拟空间上连着是吧,互相调用的时候 也只需要映射 就能找到所需的文件?
常见情况是更早的越界写把堆/对象破坏了,稍后在 spdlog 或别的库里读到这块坏内存才崩。这时线上只能定位“报错点”;要在测试环境复现并用 ASan/UBSan/TSan/Valgrind 把“首次非法写”抓出来,如何理解 ?
太棒了,这些都是把 core 用“内行人方式”吃透的关键点。我分 6 段来答:
.so
、Java 如何调用 .so
、为啥要 strip;#12 doRecv
;readelf -n
里没看到 si_signo/si_addr?应该去哪儿看readelf -n
输出只是 NOTE header 的目录(“这里有个 NT_SIGINFO 块”),GNU readelf
默认不展开 siginfo_t
的字段,所以不会直接显示 si_signo/si_addr
。这没问题,工具就是这样。gdb# 打开 core gdb /export/App/jdk1.8.0_60/bin/java /path/to/core (gdb) p $_siginfo # 打印 siginfo_t(含 si_signo/si_code/si_addr 等) # 也可以单看字段 (gdb) p $_siginfo.si_signo (gdb) p $_siginfo.si_code (gdb) p $_siginfo._sifields._sigfault.si_addr # 有的架构字段名略有差异,用 p $_siginfo 先看结构
dmesg | grep -E 'segfault|trap|general protection|bus error'
常见格式:segfault at <addr> ip <pc> sp <sp> error ... in <lib>[base+off]
,里面的 <addr>
就是“访问的地址”(空指针常见为 0)。结论:
readelf -n
只告诉你“有 NT_SIGINFO”;想看si_signo/si_addr
,进 gdb 用p $_siginfo
。
.dynsym
:给动态链接器用,含“导出函数/变量”的符号名。有它,gdb 至少能显示函数名;但没有源码行/局部变量。.symtab
:更多本地符号,发布时常被 strip 掉。.debug_*
段):让 gdb 有源码文件/行号、内联、局部变量和类型。info sharedlibrary
的 “Syms Read” 列:
Yes (*)
= 读到符号但无调试信息(能见函数名,但无行号/局部变量)。Yes
(无星号)= 通常表示能拿到调试信息(视构建而定)。No
= 连函数名都没有,只是地址。“补符号”就是:把与你崩溃时二进制完全一致的调试信息放回分析环境,让 gdb 能“指到行、看变量”。做法:
debuginfo-install glibc libstdc++ ...
;Debian/Ubuntu 用 *-dbg
)。.debug
或不 strip 的版本,按 Build-ID 存到符号仓(/usr/lib/debug/.build-id/xx/yyyyyy.debug
);分析时:
gdb(gdb) set debug-file-directory /path/to/debug-repo (gdb) set solib-search-path /path/to/so-repo
.so
/ Java 怎么调 .so
/ 为什么 strip/lib64/ld-linux-*.so.2
)把需要的共享库(.so
)映射进进程,并把函数调用通过 PLT/GOT 解析到正确地址。这样可复用库、节省内存、便于更新。.so
是什么:ELF 共享库(Shared Object)。被多个进程共享映射(代码段只读共享)。.so
:通过 JNI/JNA/JNR。常见调用:
然后 JVM 里 native 方法就能跳到 C/C++ 的实现。javaSystem.loadLibrary("hadoop"); // 走 java.library.path System.load("/abs/path/libxxx.so"); // 绝对路径
.debug
文件来还原。.so
会被映射到不同的区间(你在 NT_FILE
里看到的 Start/End
)。物理内存由内核和 MMU 管理;进程之间相互隔离。.so
本次运行被映射到的起始地址(ASLR 每次可能不同)。info sharedlibrary
的 From
就是“可以当作基址”的值。基址 + 偏移
。我们把 崩溃地址 - 基址 = 偏移
,再用:
定位源码行(前提:该库有调试信息或bashaddr2line -fe /path/to/libxxx.so 0x<偏移>
.debug
文件)。#12 doRecv
(而不是停在 raise/abort/libjvm.so
)bt
顶部是 raise → abort → libjvm.so 的 fatal path
,这是JVM 在处理致命信号/错误时的通用“收尾栈”,信息价值有限。#12 doRecv
往下的几帧(spdlog::... → doRecv → NetEvent...
)。$_siginfo
的 si_signo/si_addr
,判断这是空指针/野指针/越界还是主动 abort;操作小抄:
gdb(gdb) f 12 (gdb) list (gdb) info args (gdb) info locals (gdb) p this (gdb) p $_siginfo (gdb) disassemble /m $pc-32, $pc+64
很多 native 崩溃是更早的“非法写”破坏了内存,过一会儿别的代码(比如 spdlog
格式化字符串)“碰到这块坏内存”才崩。线上 core 只能告诉你“撞墙点”,不一定能告诉你“第一次挖坑点”。
怎么抓“第一次挖坑点”(测试/预发环境):
编译开 Sanitizer(推荐)
bash# Debug 或预发构建 -g -O1 -fno-omit-frame-pointer \ -fsanitize=address,undefined # ASan + UBSan # 多线程问题排查专用 -fsanitize=thread # TSan(与 ASan 不能同时开)
运行时会直接在第一次非法写发生的那一行报错并堆栈,价值极高。
Valgrind(Memcheck)
bashvalgrind --tool=memcheck --leak-check=full --track-origins=yes ./your_binary ...
复现策略
-O0/-O1
),保留帧指针(-fno-omit-frame-pointer
);abort()
让现场尽早暴露。实战心法:线上 core 定“撞墙点” → 日志/指标定位“触发条件” → 测试/预发开 Sanitizer 抓“第一次挖坑点”。
gdbgdb /export/App/jdk1.8.0_60/bin/java /path/to/core (gdb) p $_siginfo # 看 si_signo/si_addr
gdb(gdb) f 12 (gdb) info args (gdb) info locals
libjdufs_sdk.so
、libgplcompression.so
(那两个 (deleted) JNI so)补同版本的符号或 .debug
。.so
的哪一个偏移”钉住(必要时用基址→偏移→addr2line
):bash# 基址看 info sharedlibrary 的 From addr2line -fe /path/to/libxxx.so 0x<fault_offset>
NT_FILE
看到了 application_...
和 container_...
:bashyarn logs -applicationId application_... \ -containerId container_... > container.log
hs_err_pid*.log
看 Problematic frame
。如果你把 gdb
里的 p $_siginfo
结果和 frame 12
的 info args/locals
(去敏)贴一下,我可以直接判断你这个更像空指针还是野指针/堆损坏,并给出“在 doRecv
里该加哪几处边界/空值校验”的具体建议。
#12 kunlun::netlib::TcpTransportExtend::doRecv (this=0x7fe2c0418a60) at /export/code2/ljd/testrepo/src/jd.com/kun-lun-project/kunlun-ofs/netlib/transport/tcp_transport_extend.cpp:421
421 /export/code2/ljd/testrepo/src/jd.com/kun-lun-project/kunlun-ofs/netlib/transport/tcp_transport_extend.cpp: No such file or directory.
(gdb) bt
#0 0x00007fe2e02cf70f in raise () from /lib64/libc.so.6
#1 0x00007fe2e02b9b25 in abort () from /lib64/libc.so.6
#2 0x00007fe2dfbd96b5 in os::abort(bool) () from /export/App/jdk1.8.0_60/jre/lib/amd64/server/libjvm.so
#3 0x00007fe2dfd77bf3 in VMError::report_and_die() () from /export/App/jdk1.8.0_60/jre/lib/amd64/server/libjvm.so
#4 0x00007fe2dfbdeedf in JVM_handle_linux_signal () from /export/App/jdk1.8.0_60/jre/lib/amd64/server/libjvm.so
#5 0x00007fe2dfbd5673 in signalHandler(int, siginfo*, void*) () from /export/App/jdk1.8.0_60/jre/lib/amd64/server/libjvm.so
#6 <signal handler called>
#7 0x00007fe296c20ab7 in spdlog::logger::flush_ (this=0x7fe2d9755f80) at /usr/include/c++/8/bits/shared_ptr_base.h:1018
#8 0x00007fe296c208d5 in spdlog::logger::sink_it_ (this=0x7fe2d9755f80, msg=...) at /export/code2/ljd/testrepo/src/jd.com/kun-lun-project/dependency/spdlog/include/spdlog/logger-inl.h:199
#9 0x00007fe296c17dd0 in spdlog::logger::log_it_ (this=0x7fe2d9755f80, log_msg=..., log_enabled=<optimized out>, traceback_enabled=<optimized out>)
at /export/code2/ljd/testrepo/src/jd.com/kun-lun-project/dependency/spdlog/include/spdlog/logger-inl.h:175
#10 0x00007fe29700e823 in spdlog::logger::log_<fmt::v7::basic_string_view<char>, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, unsigned int&, unsigned short&, unsigned long&> (this=0x7fe2d9755f80, loc=..., lvl=spdlog::level::warn, fmt=...) at /export/code2/ljd/testrepo/src/jd.com/kun-lun-project/dependency/spdlog/include/spdlog/details/os-inl.h:353
#11 0x00007fe297025b9e in spdlog::logger::log<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, unsigned int&, unsigned short&, unsigned long&> (fmt=...,
lvl=spdlog::level::warn, loc=..., this=0x7fe2d9755f80) at /export/code2/ljd/testrepo/src/jd.com/kun-lun-project/dependency/spdlog/include/spdlog/logger.h:93
#12 kunlun::netlib::TcpTransportExtend::doRecv (this=0x7fe2c0418a60) at /export/code2/ljd/testrepo/src/jd.com/kun-lun-project/kunlun-ofs/netlib/transport/tcp_transport_extend.cpp:421
#13 0x00007fe296f6f033 in std::function<void ()>::operator()() const (this=0x7fe2c0418d88) at /usr/include/c++/8/bits/std_function.h:682
#14 kunlun::netlib::NetEventHandler::handleEvent (this=0x7fe2c0418d70) at /export/code2/ljd/testrepo/src/jd.com/kun-lun-project/kunlun-ofs/netlib/net_runtime/event_loop/net_event_handler.cpp:30
#15 0x00007fe296f6b048 in kunlun::netlib::NetEventLoop::loopImpl (this=0x7fe2c03fadd0, once=once@entry=false, blocking=true)
at /export/code2/ljd/testrepo/src/jd.com/kun-lun-project/kunlun-ofs/netlib/net_runtime/event_loop/net_event_loop.cpp:209
#16 0x00007fe296f7afa9 in kunlun::netlib::NetEventLoop::LoopForever (this=<optimized out>) at /export/code2/ljd/testrepo/src/jd.com/kun-lun-project/kunlun-ofs/netlib/net_runtime/net_event_loop.h:79
#17 kunlun::netlib::NormalNetContainer::<lambda(std::promise<void>)>::operator() (__closure=0x7fe2dac0ba90, pr=0x7fe2dabf7390)
at /export/code2/ljd/testrepo/src/jd.com/kun-lun-project/kunlun-ofs/netlib/net_runtime/container/normal_net_container.cpp:34
#18 std::__invoke_impl<void, kunlun::netlib::NormalNetContainer::startImpl()::<lambda(std::promise<void>)>, std::promise<void>> (__f=...) at /usr/include/c++/8/bits/invoke.h:60
#19 std::__invoke<kunlun::netlib::NormalNetContainer::startImpl()::<lambda(std::promise<void>)>, std::promise<void>> (__fn=...) at /usr/include/c++/8/bits/invoke.h:95
#20 std::thread::_Invoker<std::tuple<kunlun::netlib::NormalNetContainer::startImpl()::<lambda(std::promise<void>)>, std::promise<void>> >::_M_invoke<0, 1> (this=0x7fe2dac0ba88)
at /usr/include/c++/8/thread:244
#21 std::thread::_Invoker<std::tuple<kunlun::netlib::NormalNetContainer::startImpl()::<lambda(std::promise<void>)>, std::promise<void>> >::operator() (this=0x7fe2dac0ba88)
at /usr/include/c++/8/thread:253
#22 std::thread::_State_impl<std::thread::_Invoker<std::tuple<kunlun::netlib::NormalNetContainer::startImpl()::<lambda(std::promise<void>)>, std::promise<void>*> > >::_M_run(void) (
this=0x7fe2dac0ba80) at /usr/include/c++/8/thread:196
#23 0x00007fe29651dba3 in execute_native_thread_routine () from /lib64/libstdc++.so.6
#24 0x00007fe2e0a7c2de in start_thread () from /lib64/libpthread.so.0
#25 0x00007fe2e0393e83 in clone () from /lib64/libc.so.6
(gdb) f 12
#12 kunlun::netlib::TcpTransportExtend::doRecv (this=0x7fe2c0418a60) at /export/code2/ljd/testrepo/src/jd.com/kun-lun-project/kunlun-ofs/netlib/transport/tcp_transport_extend.cpp:421
421 in /export/code2/ljd/testrepo/src/jd.com/kun-lun-project/kunlun-ofs/netlib/transport/tcp_transport_extend.cpp
(gdb) info args
this = 0x7fe2c0418a60
(gdb) info locals
SPDLOG_LOGGER_CALL_capture_logger = <optimized out>
vconn = <optimized out>
ctx = 0x7fe2daf700b0
FUNCTION = "doRecv"
rc = <optimized out>
(gdb) p 3 = void
(gdb) p 4 = void
帮我分析一下
给的这些栈和 readelf
线索已经很关键了。我把你贴出来的结果“逐个拆解 + 落地招式”给你:
/export/App/jdk1.8.0_60/bin/java
)崩溃,HotSpot 走了标准的 fatal error 路径(raise → abort → libjvm.so: report_and_die()
)。kunlun::netlib::TcpTransportExtend::doRecv
里调用 spdlog 过程中出错(#7–#12)。#7
的地址是 0x7fe296c20ab7
,这个地址落在你们的 SDK libjdufs_sdk.so
的映射区间里(不是“spdlog 动态库”——spdlog 是 header-only,被编进了你的 so 里)。
libjdufs_sdk.so
加载起始(基址)≈ 0x7fe296a12000
0x7fe296c20ab7 - 0x7fe296a12000 = 0x20eab7
addr2line
精确到源码行(前提:有调试符号)。frame 12
提示 “No such file or directory”?tcp_transport_extend.cpp:421
),但你的分析机上没有这条路径对应的源码。gdb(gdb) set substitute-path /export/code2/ljd/testrepo/src /home/me/src (gdb) dir /home/me/src (gdb) f 12 (gdb) list
bash# 基址看 info sharedlibrary 的 From(或 readelf NT_FILE 的 Start/End) addr2line -fe /export/Data/tmp/hadoop-tmp/libjdufs_sdk.so 0x20eab7
-f
可同时给出函数名。si_signo/si_addr
?readelf -n
只告诉你“有一个 NT_SIGINFO 块”,不会展开字段。gdb(gdb) p $_siginfo # 打印 siginfo_t (gdb) p $_siginfo.si_signo # 信号号:SIGSEGV/SIGBUS/SIGABRT... (gdb) p $_siginfo._sifields._sigfault.si_addr # 出错访问地址(不同平台字段名略异)
$NT_FILE
、$nt_file
不是 gdb 的“便利变量”,所以是 void
——只有 $_siginfo
这个是内置的。判别空/野指针:
si_signo=SIGSEGV
且si_addr=0x0
→ 空指针概率极高;si_addr
是个怪地址、又不在任何映射内 → 野指针/越界写概率高;SIGABRT
→ 多为主动abort()
(断言失败/堆损坏),去容器日志或hs_err_pid*.log
看 fatal 文字更快。
确定信号与地址
gdb(gdb) p $_siginfo (gdb) info registers # 看 $pc(RIP)、参数寄存器(RDI/RSI/...)
看事发帧的参数/局部
gdb(gdb) f 12 (gdb) info args (gdb) info locals (gdb) p this (gdb) disassemble /m $pc-32, $pc+64
<optimized out>
:说明二进制是 -O2
且没带完整调试符号。要想看见更多变量,需要补符号(见下)。确认库 + 偏移 + 行号
gdb(gdb) info sharedlibrary | grep -n 'libjdufs_sdk.so' # 记下 From 基址(≈ 0x7fe296a12000) # 计算 offset,然后: addr2line -fe /export/Data/tmp/hadoop-tmp/libjdufs_sdk.so 0x20eab7
拼容器/JVM 日志时间线(你已经有 appId/containerId)
bashyarn logs -applicationId application_1754969617538_4473 \ -containerId container_e41_1754969617538_4473_01_000127 > container.log # 如果启用了 JVM 错误文件: find /export -name 'hs_err_pid*.log' -mmin 180
hs_err
里会有 Problematic frame: ... in libjdufs_sdk.so+0x20eab7
之类的直接证据。
info locals
大量 <optimized out>
,而且 info sharedlibrary
里很多是 Yes (*)
或 No
。glibc
、libstdc++
、zlib
等)。libjdufs_sdk.so
、libgplcompression.so
那个已删除的临时 so):把同一构建的 .debug
文件或未 strip 版本放到分析机:
gdb(gdb) set debug-file-directory /path/to/debug-repo (gdb) set solib-search-path /path/to/so-repo
-g -O1 -fno-omit-frame-pointer
(线上可 strip,debug 包单独存)。.so
按页映射到本进程虚拟地址空间,函数调用靠符号解析(PLT/GOT)跳到正确地址。.so
:ELF 共享库文件。可被多个进程共享,代码段只读共享。.so
:走 JNI/JNR/JNA,如 System.loadLibrary("hadoop")
/ System.load("/abs/path/libxxx.so")
,然后 native
方法跳到 C/C++ 实现。.debug
文件“补”回来。.so
被映射到不同的地址区间(你在 NT_FILE
里看到了 Start/End
),这就是“虚拟地址空间”的局部视图。addr2line
接受的就是偏移。doRecv
→(warn 级别)spdlog 打日志这个链路里。string_view
/%s
指向已释放/未初始化内存);$_siginfo
判别信号/地址;f 12
看参数/局部(补符号后效果更好);addr2line
把 0x20eab7
精确到源码;hs_err
的异常文案;-fsanitize=address
)+ UBSan 复现,这能把“第一次非法写”当场报出来(而不是等到 spdlog 才爆)。gdb# 1) 打开 core gdb /export/App/jdk1.8.0_60/bin/java /path/to/core # 2) 看信号 p $_siginfo # 3) 切到业务帧 f 12 info args info locals # 4) 算偏移(基址用你机器上的值替换) # 例如:0x7fe296c20ab7 - 0x7fe296a12000 = 0x20eab7
bash# 5) 映射到源码行(需要对应调试符号) addr2line -fe /export/Data/tmp/hadoop-tmp/libjdufs_sdk.so 0x20eab7 # 6) 拉容器日志 yarn logs -applicationId application_1754969617538_4473 \ -containerId container_e41_1754969617538_4473_01_000127 > container.log # 7) 找 hs_err find /export -name 'hs_err_pid*.log' -mmin 180 # 8)(准备预发复现)用 ASan/UBSan 重编/重跑 CXXFLAGS="-g -O1 -fno-omit-frame-pointer -fsanitize=address,undefined"
如果你跑出了 p $_siginfo
的三项(si_signo/si_code/si_addr
)和 addr2line
的行号,我可以再帮你“拍板”:这是更像空指针、野指针/越界还是堆损坏/主动 abort,并给出在 doRecv
这段代码里要加的2–3个具体防护检查(参数判空、长度边界、所有权/生命周期校验)的位置。
我目前已经到了 新的阶段 分析一下
#12 kunlun::netlib::TcpTransportExtend::doRecv (this=0x7fe2c0418a60) at /export/code2/ljd/testrepo/src/jd.com/kun-lun-project/kunlun-ofs/netlib/transport/tcp_transport_extend.cpp:421
421 in /export/code2/ljd/testrepo/src/jd.com/kun-lun-project/kunlun-ofs/netlib/transport/tcp_transport_extend.cpp
(gdb) p 2 = {si_signo = 6, si_errno = 0, si_code = -6, _sifields = {_pad = {3843691, 0 <repeats 27 times>}, _kill = {si_pid = 3843691, si_uid = 0}, _timer = {si_tid = 3843691, si_overrun = 0, si_sigval = {
sival_int = 0, sival_ptr = 0x0}}, _rt = {si_pid = 3843691, si_uid = 0, si_sigval = {sival_int = 0, sival_ptr = 0x0}}, _sigchld = {si_pid = 3843691, si_uid = 0, si_status = 0, si_utime = 0,
si_stime = 0}, _sigfault = {si_addr = 0x3aa66b, _addr_lsb = 0, addr_bnd = {lower = 0x0, upper = 0x0}}, sigpoll = {si_band = 3843691, si_fd = 0}}}
(gdb) f 12
#12 kunlun::netlib::TcpTransportExtend::doRecv (this=0x7fe2c0418a60) at /export/code2/ljd/testrepo/src/jd.com/kun-lun-project/kunlun-ofs/netlib/transport/tcp_transport_extend.cpp:421
421 /export/code2/ljd/testrepo/src/jd.com/kun-lun-project/kunlun-ofs/netlib/transport/tcp_transport_extend.cpp: No such file or directory.
(gdb) bt
#0 0x00007fe2e02cf70f in raise () from /lib64/libc.so.6
#1 0x00007fe2e02b9b25 in abort () from /lib64/libc.so.6
#2 0x00007fe2dfbd96b5 in os::abort(bool) () from /export/App/jdk1.8.0_60/jre/lib/amd64/server/libjvm.so
#3 0x00007fe2dfd77bf3 in VMError::report_and_die() () from /export/App/jdk1.8.0_60/jre/lib/amd64/server/libjvm.so
#4 0x00007fe2dfbdeedf in JVM_handle_linux_signal () from /export/App/jdk1.8.0_60/jre/lib/amd64/server/libjvm.so
#5 0x00007fe2dfbd5673 in signalHandler(int, siginfo*, void*) () from /export/App/jdk1.8.0_60/jre/lib/amd64/server/libjvm.so
#6 <signal handler called>
#7 0x00007fe296c20ab7 in spdlog::logger::flush (this=0x7fe2d9755f80) at /usr/include/c++/8/bits/shared_ptr_base.h:1018
#8 0x00007fe296c208d5 in spdlog::logger::sink_it (this=0x7fe2d9755f80, msg=...) at /export/code2/ljd/testrepo/src/jd.com/kun-lun-project/dependency/spdlog/include/spdlog/logger-inl.h:199
#9 0x00007fe296c17dd0 in spdlog::logger::log_it (this=0x7fe2d9755f80, log_msg=..., log_enabled=<optimized out>, traceback_enabled=<optimized out>)
at /export/code2/ljd/testrepo/src/jd.com/kun-lun-project/dependency/spdlog/include/spdlog/logger-inl.h:175
#10 0x00007fe29700e823 in spdlog::logger::log<fmt::v7::basic_string_view<char>, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, unsigned int&, unsigned short&, unsigned long&> (this=0x7fe2d9755f80, loc=..., lvl=spdlog::level::warn, fmt=...) at /export/code2/ljd/testrepo/src/jd.com/kun-lun-project/dependency/spdlog/include/spdlog/details/os-inl.h:353
#11 0x00007fe297025b9e in spdlog::logger::log<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, unsigned int&, unsigned short&, unsigned long&> (fmt=...,
lvl=spdlog::level::warn, loc=..., this=0x7fe2d9755f80) at /export/code2/ljd/testrepo/src/jd.com/kun-lun-project/dependency/spdlog/include/spdlog/logger.h:93
#12 kunlun::netlib::TcpTransportExtend::doRecv (this=0x7fe2c0418a60) at /export/code2/ljd/testrepo/src/jd.com/kun-lun-project/kunlun-ofs/netlib/transport/tcp_transport_extend.cpp:421
#13 0x00007fe296f6f033 in std::function<void ()>::operator()() const (this=0x7fe2c0418d88) at /usr/include/c++/8/bits/std_function.h:682
#14 kunlun::netlib::NetEventHandler::handleEvent (this=0x7fe2c0418d70) at /export/code2/ljd/testrepo/src/jd.com/kun-lun-project/kunlun-ofs/netlib/net_runtime/event_loop/net_event_handler.cpp:30
#15 0x00007fe296f6b048 in kunlun::netlib::NetEventLoop::loopImpl (this=0x7fe2c03fadd0, once=once@entry=false, blocking=true)
at /export/code2/ljd/testrepo/src/jd.com/kun-lun-project/kunlun-ofs/netlib/net_runtime/event_loop/net_event_loop.cpp:209
#16 0x00007fe296f7afa9 in kunlun::netlib::NetEventLoop::LoopForever (this=<optimized out>) at /export/code2/ljd/testrepo/src/jd.com/kun-lun-project/kunlun-ofs/netlib/net_runtime/net_event_loop.h:79
#17 kunlun::netlib::NormalNetContainer::<lambda(std::promise<void>)>::operator() (__closure=0x7fe2dac0ba90, pr=0x7fe2dabf7390)
at /export/code2/ljd/testrepo/src/jd.com/kun-lun-project/kunlun-ofs/netlib/net_runtime/container/normal_net_container.cpp:34
#18 std::__invoke_impl<void, kunlun::netlib::NormalNetContainer::startImpl()::<lambda(std::promise<void>)>, std::promise<void>> (__f=...) at /usr/include/c++/8/bits/invoke.h:60
#19 std::__invoke<kunlun::netlib::NormalNetContainer::startImpl()::<lambda(std::promise<void>)>, std::promise<void>> (__fn=...) at /usr/include/c++/8/bits/invoke.h:95
#20 std::thread::_Invoker<std::tuple<kunlun::netlib::NormalNetContainer::startImpl()::<lambda(std::promise<void>)>, std::promise<void>> >::_M_invoke<0, 1> (this=0x7fe2dac0ba88)
at /usr/include/c++/8/thread:244
#21 std::thread::_Invoker<std::tuple<kunlun::netlib::NormalNetContainer::startImpl()::<lambda(std::promise<void>)>, std::promise<void>> >::operator() (this=0x7fe2dac0ba88)
at /usr/include/c++/8/thread:253
#22 std::thread::_State_impl<std::thread::_Invoker<std::tuple<kunlun::netlib::NormalNetContainer::startImpl()::<lambda(std::promise<void>)>, std::promise<void>*> > >::_M_run(void) (
this=0x7fe2dac0ba80) at /usr/include/c++/8/thread:196
#23 0x00007fe29651dba3 in execute_native_thread_routine () from /lib64/libstdc++.so.6
#24 0x00007fe2e0a7c2de in start_thread () from /lib64/libpthread.so.0
#25 0x00007fe2e0393e83 in clone () from /lib64/libc.so.6
(gdb) f 12
#12 kunlun::netlib::TcpTransportExtend::doRecv (this=0x7fe2c0418a60) at /export/code2/ljd/testrepo/src/jd.com/kun-lun-project/kunlun-ofs/netlib/transport/tcp_transport_extend.cpp:421
421 in /export/code2/ljd/testrepo/src/jd.com/kun-lun-project/kunlun-ofs/netlib/transport/tcp_transport_extend.cpp
(gdb) info args
this = 0x7fe2c0418a60
(gdb) info locals
SPDLOG_LOGGER_CALL_capture_logger = <optimized out>
vconn = <optimized out>
ctx = 0x7fe2daf700b0
FUNCTION = "doRecv"
rc = <optimized out>
好消息:你已经把“信号现场”和“业务栈”都拿到了,现在可以下清晰结论并推进到精确定位哪一行了。下面我把你这次的输出解读、结论、以及下一步“落地动作”给全了。
p $_siginfo
textsi_signo = 6 si_code = -6 _sigfault.si_addr = 0x3aa66b
si_signo=6
→ SIGABRT(终止信号),不是 SEGFAULT。si_code=-6
在 Linux 上通常对应 SI_TKILL(线程自发的信号,常见于 abort()
/tgkill()
路径)。_sigfault.si_addr
没有诊断意义(那是给 SEGV/BUS 用的出错访问地址)。所以这里的 0x3aa66b
不必解读。bt
)text#0 raise → #1 abort → #2 os::abort → #3 VMError::report_and_die #4/#5 JVM_handle_linux_signal/signalHandler #6 <signal handler called> #7~#12 spdlog → kunlun::...::doRecv()
abort()
→ 所以 core 的终止信号是 SIGABRT。hs_err_pid*.log
里(Problematic frame
、原始 SIGSEGV/SIGBUS
、或堆损坏等信息)。malloc(): memory corruption
)触发了 abort,这类文案会出现在容器 stdout/stderr 或 hs_err 里。#12 kunlun::netlib::TcpTransportExtend::doRecv(...):421
<optimized out>
→ 需要补调试符号才能看到更多细节。你可以直接把第 7 帧的地址换算成 libjdufs_sdk.so 内偏移,再 addr2line
还原行号:
0x7fe296c20ab7
libjdufs_sdk.so
的加载基址约为:0x7fe296a12000
0x7fe296c20ab7 - 0x7fe296a12000 = 0x20eab7
bashaddr2line -fe /export/Data/tmp/hadoop-tmp/libjdufs_sdk.so 0x20eab7 # -f 显示函数名,-e 指定库文件
这样即使你没有把源码放回相同路径,也能直接得到“库内偏移 → 源文件:行号”。
若这一步报不出行号,说明 libjdufs_sdk.so 缺调试符号:把同版本的.debug
文件放到分析机(或不 strip 的 so),然后重跑。
abort()
)。abort 的常见来源:
abort()
;abort()
;abort()
。si_addr
对“判空/野指针”不再适用。想知道原始错误是“空/野指针/堆损坏”,需要:
hs_err_pid*.log
:里面会写 A fatal error has been detected...
、Problematic frame:
、EXCEPTION_ACCESS_VIOLATION
/SIGSEGV
等原始信息;malloc(): memory corruption
、double free
、*** buffer overflow detected ***
等字样。你已经通过
NT_FILE
拿到了applicationId/containerId
,直接拉日志:
bashyarn logs -applicationId application_1754969617538_4473 \ -containerId container_e41_1754969617538_4473_01_000127 \ > container.log find /export -name 'hs_err_pid*.log' -mmin 240
任选一种:
/export/code2/ljd/testrepo/src/...
gdbset substitute-path /export/code2/ljd/testrepo/src /home/andrew/src dir /home/andrew/src f 12 list
<optimized out>
怎么办(“补符号”的落地)info locals
里大量 <optimized out>
,说明编译用了 -O2
且没有完整 DWARF。libjdufs_sdk.so
):把同一构建的 .debug
放到分析机,并让 gdb 能找到:
gdbset debug-file-directory /path/to/debug-repo set solib-search-path /path/to/so-repo
-g -O1 -fno-omit-frame-pointer
(线上可 strip,调试符号单独存 Build-ID 仓)。确认终止信号(你已做)
→ SIGABRT(si_signo=6
),si_addr
忽略。
将崩溃点指到源码行(即使没源码路径)
bashaddr2line -fe /export/Data/tmp/hadoop-tmp/libjdufs_sdk.so 0x20eab7
查看容器/JVM fatal 文案(原始原因多在这里)
bashyarn logs ... > container.log find /export -name 'hs_err_pid*.log' -mmin 240
SIGSEGV at 0x0
/EXCEPTION_ACCESS_VIOLATION
→ 大概率空指针;malloc(): memory corruption
/double free
→ 堆损坏;assert(...) failed
→ 断言触发。补符号后再次查看第 12 帧
gdbf 12 info args info locals p this # 结合源码,检查传入的 buffer 指针/长度/生命周期
排查“日志参数”是否有问题
spdlog::logger::log_*
这一侧。常见坑:日志格式字符串引用了无效内存(空/野指针),或 fmt
的参数类型与实际不符。doRecv
/上游调用点增加保护:
const char*
/string_view
参数 判空;在测试/预发用 Sanitizer 抓“第一次非法写”(如果容器日志提示堆损坏/随机崩)
-g -O1 -fno-omit-frame-pointer -fsanitize=address,undefined
这是空指针/野指针吗?
仅凭这个 core:不能直接下结论,因为终止信号是 SIGABRT。请以 hs_err_pid*.log
/容器日志 为准;若日志显示原始 SIGSEGV at 0x0
→ 空指针概率大。
为什么要算偏移再 addr2line
?
因为 ASLR 让每次“加载基址”不同。fault_ip - base = offset
是定位库内源码行的稳定方法。
为什么 gdb 说没文件?
行号信息在,但本机没那条路径的源码。用 set substitute-path
或直接 addr2line
。
把 addr2line
的结果(函数 + 行号)和 容器/hs_err 日志中的 fatal 文案贴出来(去敏即可),我就能帮你把根因进一步收敛到“空指针/越界/堆损坏/断言”的哪一类,并给到你在 doRecv
附近该加的 2–3 条具体防御性检查(参数判空、长度校验、所有权/生命周期约束)的代码位点。