上下文:我刚进入公司,分配到了大数据存储组里,自研了hdfs,名为yhdfs,其底层是c++写的,用...

创建于:2025年8月16日

使用 GPT-5 Thinking 回答 Chat01

问题

上下文:我刚进入公司,分配到了大数据存储组里,自研了hdfs,名为yhdfs,其底层是c++写的,用了ufs的一些存储,但是自身应该也写了一些sdk,然后需要 实现hadoop客户端的一些接口,所以也涉及了一些java。我主要的工作是巡检 集群是否正常工作,是否有一些异常的报错。
我要研究一下,core-file (因为要找到 具体是哪里的代码出错了),你首先我给我介绍一下core-file吧,尽量全面。

思考

Explaining core files

The user asks about core files. These files capture program states when an application crashes. In Linux, this involves settings like ulimit -c, /proc/sys/kernel/core_pattern, and tools like gdb and apport. We can enable core dumps and analyze them with debuggers, ensuring we understand system details like memory segments and thread status. For platforms like macOS and Windows, the process varies, using crash reports and minidumps.

Analyzing core dumps with Java and C++

For mixed C++ and Java applications, such as JNI with Hadoop, I'll suggest using tools like jhsdb, jmap, and gdb. I'll also include options for analyzing core dumps, configuring system settings like ulimit, and containerization issues that may impact core dumps (e.g., Docker). I’ll address platform-specific details, such as the handling of ASLR or setting core patterns.

Handling core dump files

Core dump file names follow a specific pattern, and if core_pattern starts with |, it's piped to a user-space handler like systemd-coredump. I'll highlight how to examine core dumps with tools like coredumpctl and gdb. For issues like heap corruption or multi-threading, I'll suggest using specific commands. Also, I’ll explain setting debugging options for C++ and address sanitizer interactions with core files.

Debugging core dumps

When analyzing core dumps, I'll use commands like info files, info registers, and bt full in gdb. In production, system settings like ulimit and core dump configuration help. Containers like Docker require special settings for core dumps and permissions for security. I'll also note Java-specific options for generating core dumps, including using HotSpot settings for crashes. For Hadoop, core dumps may be generated from native crashes rather than Java exceptions.

Thought for 26s

回答

好的!下面给你一份尽量全面、工程一线可用的 core file(核心转储)介绍,重点偏向 Linux 上的 C/C++ 进程(你们的 yhdfs C++ 进程)以及 JNI/Java 场景(Hadoop 客户端接口)。

什么是 core file

当进程因为严重错误收到会触发“产生核心转储”的信号时(常见如 SIGSEGV、SIGABRT、SIGBUS、SIGILL、SIGFPE、SIGTRAP),内核把当时的进程内存快照、寄存器、线程栈、映射的共享库信息等写到一个文件里,这个文件就是 core。
它能让你在事后用调试器还原进程当时的状态,定位崩溃位置与调用栈,即使程序已经退出。

core 里包含什么

  • 可执行文件与各共享库的映射信息(地址、路径、Build ID)
  • 所有线程的寄存器与调用栈(含线程名)
  • 选定的内存段内容(代码段、栈、部分堆/匿名映射等)
  • 进程基本信息与环境变量/命令行(可能含敏感信息)
  • (ELF note 区)诸如 NT_PRSTATUS / NT_PRPSINFO / NT_AUXV / .note.gnu.build-id 等调试所需元数据

core 何时会(不会)生成

  • 会:收到会 core 的信号,且 RLIMIT_CORE(ulimit)允许、核心生成未被拦截。
  • 不会:进程被 SIGKILL(如 OOM killer)直接杀死、ulimit 为 0、系统配置将 core 重定向到收集服务但被丢弃、容器/权限限制、setuid 程序默认禁止等。

开启与存放位置

  1. 临时开启(当前 shell / 进程)
bash
ulimit -c unlimited # 允许无限大小 core # 验证 ulimit -a | grep 'core file size'
  1. 永久/服务级(systemd 管理的服务)
    在 unit 文件里:
LimitCORE=infinity

并重启服务。

  1. core 文件名与路径
  • 传统:/proc/sys/kernel/core_pattern 决定文件名/路径,例如:
    bash
    sudo 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 取用。
  1. setuid 程序与安全
bash
# 2 = 允许 setuid 程序的 core,写到安全目录 sudo sysctl -w fs.suid_dumpable=2
  1. 容器 / K8s 注意
  • 容器需要 --ulimit core=-1、适当的 SYS_PTRACE/CAP_SYS_RESOURCE 能力、以及主机层 core_pattern 配置。
  • systemd-coredump 常在宿主机上汇聚;想把 core 落到卷里需额外配置。

如何触发/收集一个 core(用于验证流水线)

  • 让程序自己崩溃:插入 *(int*)0=0;abort();
  • 给正在运行的进程生成 core(不中断/短暂停顿):
    bash
    gcore -o mycore <PID> # gdb/psmisc 提供的工具 # 或在 gdb 中: gdb -p <PID> (gdb) generate-core-file /tmp/core.myapp (gdb) detach; quit

分析 core 的常用流程(C/C++)

A. systemd-coredump 场景(推荐)

bash
# 列表/筛选 coredumpctl list mybinary # 直接进 gdb(自动匹配可执行文件/库) coredumpctl gdb <PID|COREDUMP_ID>

B. 传统 core 文件

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 # 打印变量

关键要点

  • 符号(symbols):编译时加 -g(DWARF),发布包可剥离符号并单独保存 .debug 文件;分析时通过 build-iddebuglink 自动匹配。没有符号也能定位到函数/偏移,但源码级定位困难。
  • 优化影响-O2/内联会让栈看起来“跳跃”;必要时用 -g3 -fno-omit-frame-pointer 改善可读性。
  • C++ 名字重整:gdb 会自动还原;也可 c++filt
  • 地址到行号:若只有地址,addr2line -e mybinary 0x7f...
  • 多线程thread apply all bt,关注持锁/等待线程。
  • 崩溃类型:从 info signaldmesg | tail(内核“segfault at … ip …”)确认是越界、非法指令还是除零等。

Java / JNI / Hadoop 相关

在你们场景里,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 两侧):

    1. 先读 hs_err_pid*.log 把高概率问题(哪个线程、哪条 JNI 调用)锁定;
    2. jhsdb(JDK 自带)直接打开 core:
      bash
      jhsdb hsdb --core /path/core --exe /path/java # 或更轻量的 jhsdb jstack --core /path/core --exe /path/java jhsdb jmap --core /path/core --exe /path/java
      这样能看到 Java 层栈 + native 桥接点,再跳到 gdb 看 C++ 具体函数。
    3. 直接 gdb 打开:
      bash
      gdb /path/java /path/core (gdb) thread apply all bt (gdb) where # 找到落入 libyhdfs.so / libhdfs.so / libjvm.so 的帧

你们组可落地的巡检/排障清单

启用与收集

  • 确认线上服务(如 yhdfs 的 Namenode/DataNode/SDK 守护进程)LimitCORE=infinity
  • 统一 core_pattern 到固定目录或使用 systemd-coredump,并设置保留/清理策略
  • 为所有产物建立符号服务器/仓库(保留 .debug 或带行号的映射,按 build-id 存储)
  • 容器化部署:设置 ulimit、挂载 core 目录、必要 capabilities

发生崩溃后

  • coredumpctl info/gdbgdb <exe> <core> 打开
  • thread apply all bt full 保存现场(脚本化):
    bash
    gdb -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 定位源码行
  • 检查与崩溃线程相关的锁/等待(死锁/竞态)
  • 结合日志时间线(崩溃前后 WARN/ERROR),以及 dmesg 里的 segfault 记录

Java 客户端/JNI

  • 收集 hs_err_pid*.logheap dump(若 OOM)
  • jhsdb jstack/jmap + gdb 对应帧,确认 JNI 参数/内存所有权、线程 attach/detach 是否规范
  • 检查 native 层是否混用不同版本的 libprotobuf/libssl 等共享库(符号冲突很常见)

常见“为什么没产 core”的坑

  • ulimit -c 0(服务或容器里没改到位)
  • core_pattern 指向了收集器但磁盘配额/保留策略把它清理掉了
  • SIGKILL 结束(如 OOM killer),不会有 core
  • 进程是 setuid 且 fs.suid_dumpable 未配置
  • 不同机器/镜像的二进制与符号不匹配,导致 gdb 无法还原到源码

辅助工具与技巧

  • eu-stack / eu-unstrip(elfutils),readelfobjdump -Cd --source
  • lldb --core core --file exe(偏 BSD/macOS/也支持 Linux)
  • 线上快速抓取:pstack <pid>(仅活跃进程),perf report(性能问题)
  • ASan/UBSan:若开启,崩溃前通常会有更可读的报告;与 core 共用更佳

如果你愿意,我可以给你们 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 会非常难写吗 会全是指针的写法吗

思考

Providing core dump guidance

I need to answer the user's questions in Chinese. The topic is about core dump configuration and analysis, and I don’t need to look up anything new—this is general knowledge. I'll provide an actionable guide covering core origins, Java JNI causes, file paths, configuration comparisons, and how to inspect and locate error-producing processes. This will include commands like coredumpctl info and tool usage steps like readelf.

Providing Yarn logs playbook

I think I should provide a playbook to help find logs, considering the user’s company-specific environment. I'll cover how to find the PID/EXE/command line from the core file and retrieve cgroup info, including Yarn logs location. I'll explain commands like gdb and config options like yarn.nodemanager.log-dirs.

Explaining core dumps and analysis tools

I should explain how to extract useful info from a core dump. readelf -n core helps show process names and file mappings. coredumpctl info is great for container info. I'll highlight the differences between traditional core dumps and systemd-coredump, covering storage, retention, and configuration settings in /etc/systemd/coredump.conf.

Explaining core limits and crashes

I should explain the differences between global and per-process core limits, like RLIMIT_CORE and prlimit. Then, for the crash question, I'll discuss various types of native crashes, like null pointer dereference, out-of-bounds access, and what signals such as SIGSEGV, SIGABRT, and SIGFPE mean. I'll also provide examples like Java JNI crashes due to improper thread attachment.

Explaining crash and core analysis

I'll cover various crash types, like SIGSEGV, SIGABRT, and differences between intentional abort versus segfault. I'll also describe how to locate logs from Yarn containers and systemd, using yarn logs, coredumpctl info, and /proc/sys/kernel/core_pattern. For crash diagnosis, I'll share commands for inspecting core dumps and setting proper configurations for core file handling. I'll simplify technical points, like registers, to make it easier for the user to understand.

Thought for 56s

回答

很好,这些都是一线排障会遇到的真实疑惑。下面我按“为什么会有 core、怎么配置、怎么从一个 core 追到进程/容器/日志、以及你提到的底层细节与 C++/JNI 常见坑”来讲,尽量把每一步做成可执行的清单。


1) 为什么会有 core,它从哪儿来?

  • 触发机制:进程在收到会触发转储的信号(常见:SIGSEGV 访问非法内存、SIGABRT 主动 abort()SIGILL 非法指令、SIGBUS 总线错误、SIGFPE 除零等)时,内核把当时的进程状态写成一个“核心转储文件”(core)。
  • 是不是 JNI/SDK 导致?
    可能,但不一定。只要进程里的任何本地代码(包括你们的 C++ SDK、第三方库、JVM 自身的本地部分、甚至驱动)触发了上述信号,就会 core。Java 侧经常见的情况是:Java→JNI→C++ 里发生越界/空指针/已释放内存访问,JVM 随之收到信号并把整个 Java 进程 core 出来(同时产生 hs_err_pid*.log)。
  • 保存了什么:当时的所有线程的寄存器/调用栈、映射的可执行文件与共享库、部分内存段、少量元数据(ELF notes),足够让你事后用调试器复盘。

2) 我们现在是怎么“存储 core”的?配在哪儿看?

core 的“保存方式”由两层共同决定:系统级写到哪里 + 进程是否允许写

A) 系统级(对整机上所有进程生效)

  1. 核心路径与方式
    bash
    cat /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
  2. 是否允许产生 core(大小上限):进程的 RLIMIT_CORE
    • 登录同一 shell 验证:
      bash
      ulimit -a | grep 'core file size'
    • 正在运行的进程(含服务):
      bash
      cat /proc/<pid>/limits | grep CORE

B) 服务/用户级(覆盖“是否允许写”和保留路径)

  • systemd 管理的服务(推荐你们线上都检查):
    bash
    systemctl cat <your-service> | grep -i -E 'LimitCORE|ExecStart' # 若无,考虑加上: # LimitCORE=infinity
  • PAM/登录会话/etc/security/limits.conf/etc/security/limits.d/*.conf(对交互式 shell 有效)。
  • 容器(Docker/K8s):
    • 容器启动是否带 --ulimit core=-1
    • namespace/capabilities 是否允许;
    • 宿主机的 core_pattern 通常仍生效(但 core 文件最终落在宿主收集器处)。

小结:core_pattern 是系统全局的;是否真的会落盘还要看每个进程/服务的 ulimit(LimitCORE)。两者都满足才会有 core。


3) 从“一个 core 文件”如何定位到“哪个进程/哪台容器/看哪儿的日志”

这是你现在巡检最需要的“作战流程”。我给你一份最短路径决策树。

如果机器上启用了 systemd-coredump(core_pattern 以 |...coredump 开头)

  1. 找清单
bash
coredumpctl list # 全部 coredumpctl list <comm> # 按可执行名过滤(如 java、yhdfs) coredumpctl info <PID或行号> # 详细元数据(时间、Cmdline、CGroup、Unit、容器信息等)
  1. 核心信息你要抄走(定位日志很关键)
  • Exe(可执行路径)
  • Cmdline(包含程序参数;YARN 容器很有可能带 application/container 线索)
  • CGroup / Unit(能直接告诉你是哪个 systemd 单元或容器)
  • Timestamp(对齐你们业务日志时间线)
  1. 进 gdb 或 jhsdb
bash
coredumpctl gdb <id> # 自动匹配可执行与库 # Java 进程的话,再: coredumpctl dump <id> -o /tmp/core.java jhsdb jstack --core /tmp/core.java --exe /usr/lib/jvm/.../bin/java
  1. 回溯到日志
  • systemd 服务
    journalctl -u <UnitName> --since 'YYYY-MM-DD HH:MM' --until '...'
    journalctl _PID=<崩溃时PID>
  • YARN(你说“容器里报错”这一类):
    • 应用聚合日志(RM/NM 配置 yarn.log-aggregation-enable=true 时):
      bash
      yarn logs -applicationId <application_...> # 拉整个应用 yarn logs -applicationId <...> -containerId <container_...> # 精确到容器
    • 本地 NodeManager 日志目录(常见):
      • yarn.nodemanager.log-dirs(本地容器日志根目录)
      • 远端聚合目录 yarn.nodemanager.remote-app-log-dir
    • 这些配置在 yarn-site.xml,你可以搜:
      bash
      grep -R "yarn.nodemanager.log-dirs\|remote-app-log-dir\|log-aggregation-enable" /etc/hadoop/conf/* 2>/dev/null
    • 怎么从 core 找到 application/containerId?
      • coredumpctl info 里的 CmdlineCGroup(经常带有 container/app 线索)。
      • 你的业务日志/进程参数里通常会打印 applicationId/containerId(你们可以推动在 SDK 或启动脚本里把这些 ID 放进进程名或参数里,能极大降低定位成本)。
      • 实在没有,用时间戳交叉对齐 NodeManager 的 yarn-yarn-nodemanager-*.log 与 ResourceManager 的 yarn-yarn-resourcemanager-*.log,按崩溃时间附近搜该节点上哪个 container 刚刚失败/退出。

如果是“传统 core 文件”(直接落成文件)

  1. 看看它是哪来的:
bash
file core.* readelf -n core.xxx | less # NT_PRPSINFO/NT_FILE/Build-ID/信号等
  1. 直接进 gdb:
bash
gdb /path/to/exe /path/to/core (gdb) set pagination off (gdb) info sharedlibrary (gdb) thread apply all bt (gdb) info registers
  1. “是谁”的线索放在:
  • gdb可执行路径(第 1 行就能看到);
  • readelf -n 里的 PRPSINFO(进程名)时间信号
  • NT_FILE 映射列表里经常能看到工作目录/库路径(能反推到哪个组件);
  • 若是 Java,还要去崩溃时工作目录找 hs_err_pid*.log(或你们通过 -XX:ErrorFile= 指定的路径)。

小贴士:在 systemd-coredump 场景,优先用 coredumpctl info,它把“日志/单元/容器线索”都帮你聚合好了;传统 core 就靠 readelf -n + gdb + 现场目录结构来反推。


4) 传统 core vs systemd-coredump(对比速览)

维度传统 core 文件systemd-coredump
写入方式直接写到 core_pattern 指定路径core_pattern 管道给收集器
存放位置你指定的目录(可能很散)统一在 /var/lib/systemd/coredump/,文件名含元数据
附带元数据仅 core 本体journald 里保存了 Cmdline/CGroup/Unit/主机/时间/信号 等
压缩/清理你自己做coredump.conf 可设压缩/保留空间上限
取用方式gdb exe corecoredumpctl list/info/gdb/dump
适合场景最小依赖、极简系统规模化运维、便捷检索与保留策略

5) “为什么进程会自己崩溃?”(几类典型)

  • 访问非法内存:空指针/越界/悬空指针 → SIGSEGV
  • 显式终止assert() 失败、abort()、glibc 检测到堆损坏(malloc(): memory corruption)→ SIGABRT
  • 未捕获异常:C++ 异常穿透到 mainnoexcept 违约 → std::terminate() → 通常 SIGABRT
  • 非法指令/对齐:坏的函数指针跳转、SSE/AVX 指令不受支持 → SIGILL;未对齐访问/坏映射 → SIGBUS
  • 算术错误:整型除 0、某些浮点异常 → SIGFPE

这些都可能在 JNI 调用栈里发生,从而把 Java 进程一起打成 core。


6) “寄存器/映射/ELF notes”该怎么读?(做最必要的)

x86_64 小抄

  • RIP:当前指令地址(崩溃点)
  • RSP:当前栈顶;RBP:栈帧基址(若未省略)
  • RDI,RSI,RDX,RCX,R8,R9:前 6 个整型/指针参数
  • RAX:返回值/临时;EFLAGS:标志位(ZF/CF …)
    在 gdb 里:
gdb
(gdb) info registers (gdb) disassemble /m $pc-32, $pc+64 # 看崩溃指令周围源码/汇编 (gdb) x/16gx $rsp # 看栈上的指针值 (gdb) bt full # 全栈 + 局部变量(有符号更清晰)

ELF notesreadelf -n core

  • NT_PRSTATUS/PRPSINFO:线程/进程基本信息、信号
  • NT_AUXV:运行时辅助向量
  • NT_FILE:内存映射到文件的列表(含路径)
    映射信息info sharedlibrary 看具体加载了哪些 .so,版本是否对得上。

7) “native 崩溃是怎么崩?”(JNI 相关高频坑)

  • 传错指针/越界:把 Java 数组指针拿出来用久了、越界读写 native buffer。
  • 线程未附着 JVM:在 C++ 新建的线程里用 JNIEnv*(没 AttachCurrentThread)→ 未定义行为。
  • 对象生命周期:把局部 jobject 缓存到全局用、或者忘记 NewGlobalRef/DeleteGlobalRef
  • 混 ABI/库版本:libprotobuf/libssl/zlib 等不同版本冲突,符号解析错位。
  • 内存所有权不清:Java 传给 C++ 的内存释放职责没说清,出现 double free/use-after-free。

8) “C++ 的 SDK 会不会很难、是不是到处都是指针?”

不一定。现代 C++ 写法可以把风险显著降下来:

  • RAII / 智能指针std::unique_ptr(唯一所有权)、std::shared_ptr(共享)、配合自定义 deleter;避免裸 new/delete
  • 少暴露裸指针:接口用 span/string_view/容器引用描述“借用”,谁分配谁释放。
  • 错误返回:用 absl::StatusOr<T> / std::expected<T,E>(C++23)而不是返回魔数。
  • 线程安全:明确哪一层创建线程、在哪一层做 AttachCurrentThread,JNI 入口/退出要“成对”。
  • 工具链:ASan/UBSan/TSan 能提前把大量“会导致 core 的 bug”在测试阶段就抓出来。

9) 哪些“非核心问题”先记录、不阻塞主流程?

优先把**能复盘到“谁在何时何地崩”**这条链路打通;下面这些先记账,等主流程稳定再看:

  • 提示“缺符号/符号不匹配”的 core(能定位到库但没行号):先记录 build-id,交给打包/符号仓同学补齐。
  • 与你们组件无关(Exe/Cmdline 看起来是别的进程)的 core:拉个 issue 分派给对应团队。
  • 历史很久/版本已下线的 core:汇总后按版本批处理。
  • 只见 SIGKILL(OOM killer 导致)且没有 core 的:转入“内存/资源”类巡检而不是崩溃定位。

10) 你可以直接抄用的命令串(SOP 迷你版)

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
分析一下 以上内容的命令,命令的作用是什么
分别是用来产出什么信息的,

思考

Explaining readelf and GDB output

I need to explain the readelf output and GDB commands, covering sections like NT_PRSTATUS, PRPSINFO, SIGINFO, AUXV, and NT_FILE. I'll walk through what these sections mean and highlight key details, like the Java path for a JDK process. I'll also touch on notable mappings, page sizes, and the significance of file paths and addresses in the context of a Yarn container.

Explaining gdb commands and YARN logs

We can guide users to map logs using yarn logs -applicationId along with the containerId. I'll explain gdb commands like:

  • set pagination off to prevent pause prompts during batch outputs.
  • info sharedlibrary for checking loaded libraries and addresses.
  • thread apply all bt for stack traces (with full for local variables).
  • info registers to display CPU registers for thread inspection. I'll also mention using frame 0 and disassemble in gdb.

Analyzing readelf NT_FILE output

We’ll focus on analyzing the readelf NT_FILE output. For each entry, we’ll break down the Start/End address mapping, Page Offset, and clarify the actual file offset (Page Offset * Page size, e.g., 4096 bytes). For example, addresses like 0x0000000000400000 - 0x401000 correspond to the Java text segment, and entries such as /export/App/jdk1.8.0_60/bin/java relate to code/data segments.

Interpreting libraries and symbols

The cldrdata.jar is loaded, and the "deleted" .so under the YARN container tmp folder refers to a library unlinked after being loaded (common for unpacked libs). While the core still references it, the symbolizer won't find it unless a debug copy is available. The path to this includes application/container IDs. Additionally, the entries for liblzo2 and libstdc++ show versions, which can help identify version mismatches during symbolization.

Explaining commands and libraries

The cldrdata.jar is loaded, and the "deleted" .so in the YARN container signifies an unloaded library still referenced by the core (typical for unpacked libs). Without a debug copy, the symbolizer can't find it. Paths include application/container IDs for context. I'll also explain how to use gdb's sharedlibrary with set solib-search-path to load libraries, and we can explain the NT_SIGINFO data in the segment extraction. I'll answer the user's query in Chinese, clearly and concisely.

Thought for 45s

回答

太好了,这段 readelf 输出已经能给出很多线索。你贴的几条 gdb 命令也都是分析 core 的“基础四件套”。下面我把每条命令的作用 → 会产出什么信息 → 怎么用这些信息定位进程/容器/日志给你逐条说清楚,并顺手解析一下你这份 readelf 片段。


1) 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)
    
    • 路径里直接带 applicationIdcontainerId
      • application_1754969617538_4473
      • container_e41_1754969617538_4473_01_000127
    • 后缀 (deleted) 的意义:该 .so 在进程运行期间被 unlink(删除)了,但内存里依然映射着它(因此 core 还能看到路径)。这在 YARN 容器把 native 依赖临时解包到 tmp/unpacked-* 然后退出时很常见。
    • 有了这两个 ID,你就能精确拉 YARN 聚合日志(见第 4 节)。

小结:readelf -n 主要让你在不进 gdb 前就拿到“是谁(Java)、为什么(信号)、加载了什么(NT_FILE)、在哪个 YARN 容器(路径里包含 application/containerId)”。


2) gdb 里的四条命令

先假设你是这样进 gdb 的:

bash
gdb /export/App/jdk1.8.0_60/bin/java /path/to/core

a) set pagination off

作用:关掉 gdb 的分页提示(那行 “--Type <return> to continue--”)。
产出:不是数据,而是让后续打印一口气吐完,方便重定向保存:

bash
gdb ... -ex 'set pagination off' -ex 'thread apply all bt full' ...

b) info sharedlibrary

作用:列出当前进程(core 中的状态)加载的所有共享库、它们的加载基址(0x...)、以及 gdb 是否成功为它们加载了符号(Yes/No)。
产出:一张“库 → 加载地址 → 符号是否可用”的清单。
怎么用

  • 确认版本是否对得上(例如 libstdc++.so.6.0.25)。
  • 如果某库 Symbols: no,说明没有调试符号;解决方案是:
    • 在相同版本机器上安装 -debuginfo 包 / .debug 文件;或
    • set debug-file-directory / set solib-search-path 指向你们的符号仓
  • 崩溃回溯里出现的地址是相对这个加载基址可还原源码行的(配合 addr2linelist)。

c) thread apply all bt

作用:对所有线程执行 bt(backtrace)。
产出:每个线程的调用栈(函数名 + 偏移 + 地址)。
提示

  • 推荐用更信息量大的:
    gdb
    thread apply all bt full
    full 会打印局部变量/参数值(有符号时很有用),快速看出是空指针越界还是已释放内存
  • 结合 info threads 找到触发信号的线程(通常在回溯顶部能看到 SIGSEGV 等字样,或看 Program terminated with signal ...)。

d) info registers

作用:打印当前选中线程的寄存器(x86_64 的 RIP/RSP/RBP/RDI/...)。
产出:崩溃点地址(RIP)、栈顶(RSP)、函数参数寄存器(RDI/RSI/RDX/...)等。
怎么用

  • RIP 就是崩溃指令地址,常配合:
    gdb
    disassemble /m $pc-32, $pc+64
    看出错指令附近的源码与汇编。
  • 参数寄存器结合 bt full 的形参,能判断是不是传了野指针/空指针
  • 想切换到崩溃线程后再看寄存器:
    gdb
    thread <崩溃线程号> info registers

3) 有了这些输出,下一步怎么定位日志与容器(结合你这份 core)

你这份 NT_FILE 已经暴露了 applicationIdcontainerId,因此可以直接拉 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 栈相互印证。


4) 看到 (deleted) 要不要慌?

不慌。它只是说明该 .so 在文件系统里被删除(或临时文件已清理),但仍在进程地址空间里。
对定位的影响

  • 优点:路径里露出 application/containerId,给了你直达日志的钥匙。
  • 注意:符号解析可能找不到这份已删除的 .so。解决方法:
    • 在构建/发版阶段把对应版本的 .so.debug 放进符号仓(按 Build-ID 存放最佳);
    • gdb 里通过 set solib-search-path 指向这份仓库,或 add-symbol-file 加载符号。

5) 最后给你一个“最短命令清单”(直接复制用)

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

  • 1 Thread 0x7fe263264700 (LWP 3843817) 0x00007fe2e02cf70f in raise () from /lib64/libc.so.6
    2 Thread 0x7fe2dc2fb700 (LWP 3843696) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    3 Thread 0x7fe2e0eadb80 (LWP 3843691) 0x00007fe2e0a81485 in pthread_rwlock_wrlock () from /lib64/libpthread.so.0
    4 Thread 0x7fe2c7beb700 (LWP 3843718) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    5 Thread 0x7fe2bd0c9700 (LWP 3843731) 0x00007fe2e0a82878 in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    6 Thread 0x7fe2bcfc8700 (LWP 3843732) 0x00007fe2e0a82878 in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    7 Thread 0x7fe2c86f6700 (LWP 3843707) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    8 Thread 0x7fe2c87f7700 (LWP 3843706) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    9 Thread 0x7fe2bd8d1700 (LWP 3843720) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    10 Thread 0x7fe25e333700 (LWP 3843861) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    11 Thread 0x7fe29a00b700 (LWP 3843781) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    12 Thread 0x7fe2c8dfd700 (LWP 3843700) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    13 Thread 0x7fe2c8afa700 (LWP 3843703) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    14 Thread 0x7fe2bd7d0700 (LWP 3843721) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    15 Thread 0x7fe2c8fff700 (LWP 3843698) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    16 Thread 0x7fe2bcdc6700 (LWP 3843734) 0x00007fe2e0a82878 in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    17 Thread 0x7fe2c80f0700 (LWP 3843713) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    18 Thread 0x7fe2c7cec700 (LWP 3843717) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    19 Thread 0x7fe2c89f9700 (LWP 3843704) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    20 Thread 0x7fe260a60700 (LWP 3843821) 0x00007fe2e03941b7 in epoll_wait () from /lib64/libc.so.6
    21 Thread 0x7fe2c81f1700 (LWP 3843712) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    22 Thread 0x7fe2bd6cf700 (LWP 3843725) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    23 Thread 0x7fe2c7fef700 (LWP 3843714) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    24 Thread 0x7fe2bccc5700 (LWP 3843735) 0x00007fe2e0a82878 in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    25 Thread 0x7fe29645a700 (LWP 3843805) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    26 Thread 0x7fe2c8cfc700 (LWP 3843701) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    27 Thread 0x7fe2c85f5700 (LWP 3843708) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    28 Thread 0x7fe293c56700 (LWP 3843809) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    29 Thread 0x7fe26786b700 (LWP 3843810) 0x00007fe2e03941b7 in epoll_wait () from /lib64/libc.so.6
    30 Thread 0x7fe2bcec7700 (LWP 3843733) 0x00007fe2e0a82878 in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    31 Thread 0x7fe2bd4cd700 (LWP 3843727) 0x00007fe2e0a82878 in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    32 Thread 0x7fe2bcbc4700 (LWP 3843736) 0x00007fe2e0a82878 in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    33 Thread 0x7fe2bd2cb700 (LWP 3843729) 0x00007fe2e0a82878 in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    34 Thread 0x7fe2c83f3700 (LWP 3843710) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    35 Thread 0x7fe2bc9c2700 (LWP 3843738) 0x00007fe2e0a82878 in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    36 Thread 0x7fe2c7eee700 (LWP 3843715) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    37 Thread 0x7fe2bc6bf700 (LWP 3843741) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    38 Thread 0x7fe2bc8c1700 (LWP 3843739) 0x00007fe2e0a82878 in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    39 Thread 0x7fe2bcac3700 (LWP 3843737) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    40 Thread 0x7fe2c88f8700 (LWP 3843705) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    41 Thread 0x7fe261461700 (LWP 3843820) 0x00007fe2e03941b7 in epoll_wait () from /lib64/libc.so.6
    42 Thread 0x7fe2bd1ca700 (LWP 3843730) 0x00007fe2e0a82878 in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    43 Thread 0x7fe29e6e4700 (LWP 3843766) 0x00007fe2e0a82878 in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    44 Thread 0x7fe261e62700 (LWP 3843819) 0x00007fe2e03941b7 in epoll_wait () from /lib64/libc.so.6
    45 Thread 0x7fe265067700 (LWP 3843814) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    46 Thread 0x7fe266469700 (LWP 3843812) 0x00007fe2e03941b7 in epoll_wait () from /lib64/libc.so.6
    47 Thread 0x7fe264666700 (LWP 3843815) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    48 Thread 0x7fe299ef1700 (LWP 3843895) 0x00007fe2e0360238 in nanosleep () from /lib64/libc.so.6
    49 Thread 0x7fe2bc7c0700 (LWP 3843740) 0x00007fe2e0a82878 in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    50 Thread 0x7fe2bd5ce700 (LWP 3843726) 0x00007fe2e0a82878 in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    51 Thread 0x7fe295a59700 (LWP 3843806) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    52 Thread 0x7fe2bd3cc700 (LWP 3843728) 0x00007fe2e0a82878 in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    53 Thread 0x7fe2c8bfb700 (LWP 3843702) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    54 Thread 0x7fe2c82f2700 (LWP 3843711) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    55 Thread 0x7fe2dc1fa700 (LWP 3843697) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    56 Thread 0x7fe2c84f4700 (LWP 3843709) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    57 Thread 0x7fe2c7ded700 (LWP 3843716) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    --Type <RET> for more, q to quit, c to continue without paging--
    58 Thread 0x7fe2c8efe700 (LWP 3843699) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    59 Thread 0x7fe262863700 (LWP 3843818) 0x00007fe2e03941b7 in epoll_wait () from /lib64/libc.so.6
    60 Thread 0x7fe295058700 (LWP 3843807) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    61 Thread 0x7fe266e6a700 (LWP 3843811) 0x00007fe2e03941b7 in epoll_wait () from /lib64/libc.so.6
    62 Thread 0x7fe263c65700 (LWP 3843816) 0x00007fe2e03941b7 in epoll_wait () from /lib64/libc.so.6
    63 Thread 0x7fe265a68700 (LWP 3843813) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    64 Thread 0x7fe294657700 (LWP 3843808) 0x00007fe2e0a8248c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    (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

思考

Explaining command outputs and crash analysis

I’m looking at command outputs and concepts for debugging, focusing on load base addresses, backtraces, invalid pointers, crashes in native threads, and JVM signals. I'll break down bt information and spdlog logger frames, which signify a native thread crash with JVM conversion. I'll also mention checking the hs_err log and signal types like SIGABRT for more detailed crash investigation.

Analyzing JVM crash and debugging

I’m considering that the JVM installed a signal handler after a potential SIGSEGV, which led to a crash. The stack trace might not show the faulting frame because the handler replaced it. To find the crashing thread, I need to use thread apply all bt full and search for relevant frames. I’ll explain base address loading and how to map function addresses using eu-addr2line.

Explaining pointer issues and debugging

I’m comparing wild vs null pointers. By checking si_addr in the backtrace and using GDB to examine memory, I can see if a pointer is null or wild. For wild pointers, the address may not be mapped, and a non-null null deref will appear near 0x0. UAF issues can be tricky without tools like ASan. Using dmesg helps detect faults linked to memory corruption.

Analysing crash signal and debugging

I’m considering how the signal handler frame (#6) appears, but the earlier site is lost

Thought for 23s

回答

超级好的样本!我按你给的输出,把“4 条命令的作用/产出/怎么读”说清楚,再回答你几个关键概念(加载基址、bt vs thread apply all bt full、如何判定空/野指针),最后结合你这份回溯给出“下一步怎么查”。


一、4 条命令到底做什么、产出什么

1) 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 容器


2) set pagination off

做什么:关闭 gdb 的分页提示。
产出:不是数据,而是让接下来的输出一口气打印,便于 | tee 保存。


3) 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 映射)。

用途:确认库版本符号是否齐全、以及崩溃栈里地址如何做“基址+偏移”的还原。


4) 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,是否与崩溃线程有资源关系(比如持有某个互斥/读写锁)。

二、关键概念与你的具体问题

1) “加载基址”是什么?

内存里每个共享库(*.so)会被映射到一个基址(base address),后续函数在该库内的地址 = 基址 + 偏移

  • 由于 ASLR(地址空间随机化),基址每次都可能不同
  • gdb 的 info sharedlibraryFrom 就可以看作“当前这次运行时的加载基址”(精确来说是该库某段的起始映射地址)。
  • 当你只有“崩溃地址”但库没有行号时,可以用:
    1. 计算偏移offset = 崩溃地址 - 加载基址(From)
    2. addr2line -e /path/to/lib.so 0x<offset> 还原到源码行(前提是你手里这份 lib.so 带符号或有 .debug 文件)。

2) bt vs thread apply all bt full 的关系?

  • bt = 当前线程回溯。
  • thread apply all bt full = 所有线程的回溯 + 形参/局部变量(如果有符号)。
    为什么 core 里会有这么多线程?
    因为 core 是整个进程的快照,触发崩溃的是其中一个线程(收到信号或调用 abort()),但为了帮助你排查线程间问题,内核把 所有线程 的上下文都写进 core。

3) 如何判断空指针还是野指针

常用的三把锤子:

A. 看 NT_SIGINFOsi_addrreadelf -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 framesiginfo(最直接),或
  • readelf -nNT_SIGINFO.si_addr

三、把你这份输出连起来看(重点提示你下一步怎么查)

1) 从 info sharedlibrary

  • 崩溃进程是 JDK 1.8.0_60java
  • 关键本地库:libjvm.solibstdc++.so.6/export/Data/tmp/hadoop-tmp/libjdufs_sdk.so(你们的 UFS SDK?)、libhadoop.so、以及 两处 Syms Read: No 的临时 JNI so(jffi*.solibgplcompression.so (deleted))。
  • 结论:native 侧有大量参与者,必须保证“定位真正出错帧的那个 .so 有符号”。

2) 从 info threads

  • 绝大多数线程在 pthread_cond_wait/epoll_wait,说明系统正常“多数线程在等”。
  • 星号线程(#1)在 raise -> abort,这是“正在执行致命错误流程的线程”。

3) 从 bt

  • #0 raise -> #1 abort -> #2 os::abort -> #3 VMError::report_and_die -> #4/#5 JVM_handle_linux_signal/signalHandler
    这是 HotSpot 捕获信号、打印 fatal error、最终走 abort 的标准路径
  • #6 <signal handler called>:表明之前发生过一个信号(通常是 SIGSEGV/SIGBUS/SIGILL/SIGFPE 之一),被 HotSpot 的 handler 接住。
  • #7 之后进入你们 native 代码(spdlog)**的调用链**(TcpTransportExtend::doRecv` 等)
    这说明
    出事的那个线程是 native 线程(std::thread),在做网络/日志相关工作**。
    • 这里有两种常见可能:
      1. 真正的非法内存访问发生在 doRecv(或它调用链更深处),被 JVM handler 捕获;
      2. 并不是非法访问,而是 JVM/GLIBC 主动 abort(如堆破坏 malloc(): memory corruption、致命断言),导致这条 abort 栈(也就会看见 raise/abort 顶在前面)。

下一步最关键的判断:到底是“信号类崩溃(比如 SIGSEGV)”还是“主动 abort(如断言失败/堆损坏)”。

  • 去 core 里看 NT_SIGINFOsi_signo/si_addr
  • hs_err_pid*.log(如果开启了 -XX:ErrorFile 更容易找),看 Problematic framesiginfo

四、你可以直接照做的检查清单(针对这份 core)

  1. 看信号细节(最重要一步)
bash
# 核心转储里的信号信息 readelf -n /path/to/core | sed -n '1,160p' # 找 NT_SIGINFO 段,抄 si_signo/si_addr
  • 如果 si_signo = SIGSEGVsi_addr = 0x0空指针很可能;
  • 如果 si_addr 是一个奇怪的大地址 → 倾向野指针/越界
  • 如果信号是 SIGABRT → 倾向主动 abort(断言、堆损坏等),要找stderr / hs_err 里的报错关键字。
  1. 把“出事线程”细看一遍
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 定位源码。
  1. hs_err_pid*.log
bash
find /export -name 'hs_err_pid*.log' -mmin -180 # 或者查看 JVM 启动参数是否有 -XX:ErrorFile 指定
  • 里面会明确写:# A fatal error has been detected by the Java Runtime Environment:
    SIGSEGV/SIGABRTProblematic frame: ... in <lib>.so+0x<offset>,这能直接告诉你哪个库的哪个偏移触发了问题。
  1. 抓 YARN 日志
    你之前 NT_FILE 里已经给了 application_...container_...,直接拉:
bash
yarn logs -applicationId application_1754969617538_4473 \ -containerId container_e41_1754969617538_4473_01_000127 \ > container_127.log
  • 如果是主动 abort(比如 glibc 报堆损坏),常能在容器日志里看到原始报错语句

五、顺手回答:为什么会有多个线程“对 core 产生影响”?

  • 核心转储包含整个进程的所有线程。真正触发崩溃的是其中一个(收到信号/调用 abort()),但其它线程的状态对判断死锁、资源竞争、是否有后台刷日志/网络 I/O 卡住很重要。所以我们总是用 thread apply all bt full 把“群像”拿到手,再重点分析“致命线程”。

六、针对你这份回溯的两个提醒

  1. Syms Read: No 的两个 JNI so(jffi*.solibgplcompression.so)如果参与了调用链,必须补符号(或保留一份未删除的 so),否则就算看到“库+偏移”,也无法快速还原到源码行。
  2. spdlog 出现在崩溃线程的链路上(flush_ / sink_it_ / log_it_),不一定是 spdlog 的错,更可能是你们在 doRecv 里写日志时用了无效内存(空/野指针),导致格式化参数访问出错。bt full 里看形参(尤其是 string/buffer 指针与长度)往往一眼能看出来。

需要的话,你把 readelf -nNT_SIGINFO 的三项(si_signo/si_code/si_addr)贴一下,再加上 bt full#12 doRecv 那几帧的“形参和局部变量”部分(去掉敏感内容)。我就能告诉你是空指针还是野指针/越界的概率更大,并给出具体的“该看哪段代码/该打哪些保护(边界检查/nullptr 检查)”。

问题

符号表 和 调试符号,补符号是什么意思
每个共享库 之间是完全连着的吗,这和操作系统的段页式存储管理方式有关吗?
去 core 里看NT_SIGINFO的si_signo/si_addr这个是什么意思?
跳到 #12 doRecv 的目的是什么?

是否需要定位到具体哪个so 报错的?定位了以后,再结合日志,查看上下文是怎么样的,然后分析出 到底出了什么问题,导致 野指针 或者 空指针 ?

思考

Thought for 14s

回答

很好的问题!我按你问的点逐个讲清楚,并给出落地做法与常用命令。


1) “符号表”“调试符号”“补符号”分别是什么?

可执行/共享库(ELF)里可能有三类和符号相关的东西:

  1. .dynsym(动态符号表)

    • 动态链接用,只有对外可见的符号(导出函数、全局变量等),信息很“瘦”。
    • 有了它,gdb 至少能把很多帧还原成函数名,但没有源码行号、没有局部变量
  2. .symtab(完整符号表)

    • 含更多本地符号,但通常随发布会被剥离(strip)以减小体积、隐藏实现。
  3. DWARF 调试信息(.debug_* 段)

    • 真正让 gdb 显示源码文件/行号、形参与局部变量、内联展开、类型信息的部分。
    • 这部分一般放在单独的 debuginfo 文件里(例如 libxyz.so.debug),或通过 build-id 仓库统一管理。

gdb 里 “Syms Read: Yes (*)” 的含义

  • Yes (*):读到了符号,但缺调试信息(只有函数名、没有行号/局部变量)。
  • Yes(无星号):通常表示能拿到足够的调试信息(具体是否有行号要看构建方式)。
  • No:连函数名都没有,只能看裸地址。

“补符号” 就是:
与你崩溃二进制完全一致的调试符号文件(或未 strip 的二进制)找回来,让 gdb 能显示行号/局部变量。常见做法:

  • 发行版 debuginfo 包
    • RHEL/CentOS:debuginfo-install glibc libstdc++ ...
    • Debian/Ubuntu:apt-get install libc6-dbg ...
  • 你们自家构建产物:打包时保留 .debug 文件,按 Build-ID 存在“符号仓”(/usr/lib/debug/.build-id/xx/yyyyyy.debug),分析机器上 debug-file-directory 指到这个仓库即可。
  • 临时:把同版本未 strip 的 .so 放到一台“分析机”,gdb 用 set solib-search-path 指过去。

2) “共享库之间是完全连着的吗?和段页式管理有关吗?”

不是“拼成一个大文件”的那种连着。运行时的情况是:

  • 每个 .so 都被内核按页(通常 4K)映射同一进程的虚拟地址空间不同区域(这才会有你看到的 From/To 加载区间)。
  • 调用跨库函数时,通过动态链接器(PLT/GOT、重定位表)把“符号名”解析到“某库的实际地址”,从而跳转。
  • ASLR 让每次运行同一库的加载基址不同,所以我们常常用“加载基址 + 偏移”来定位库内具体指令。
  • 段页式内存管理是操作系统的底层实现(虚拟内存、按页映射、权限控制),它让“把很多库映射到一个进程的地址空间里”成为可能,但排障时你关注的是映射结果(地址区间/基址),不需要深究 MMU 细节。

一句话:它们在同一进程里“彼此可调用”,但物理上是各自独立的文件,被映射到不同虚拟区间


3) “去 core 里看 NT_SIGINFO 的 si_signo / si_addr 是啥意思?”

readelf -n coreNT_SIGINFO 段里:

  • si_signo:触发核心转储的信号编号(如 SIGSEGVSIGABRTSIGBUSSIGFPESIGILL)。
  • 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 由信号触发)

4) “跳到 #12 doRecv 的目的是什么?”

你的 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 里访问了坏内存)。所以我们要“下潜到你的代码帧”。


5) “是否需要定位到具体哪个 .so 报错?然后结合日志分析上下文?”

是的,这是标准流程。 一个靠谱的闭环通常是:

  1. 先确定信号与关键帧

    • NT_SIGINFO.si_signo/si_addr,判断“信号类型/出错地址”;
    • 在 gdb 栈里找到业务相关的那一帧(比如你的 doRecv 或紧邻它的库调用)。
  2. 定位到具体库 + 偏移 + 源码行

    • 如果 bt 已经给出“文件:行号”,很棒;
    • 如果只看到“库 + 地址”,就用加载基址换算偏移,再 addr2line
      bash
      # 例:fault_ip = 0x7fe296c20ab7, 基址 From = 0x7fe296...(见 info sharedlibrary) # offset = fault_ip - base addr2line -e /path/to/libxxx.so 0x<offset>
    • 缺行号/缺局部变量 → “补符号”。
  3. 拉对应容器/进程的日志,拼时间线

    • 你已经从 NT_FILE 拿到 applicationId/containerId,用 yarn logs -applicationId ... -containerId ...
    • 再找 JVM 的 hs_err_pid*.log(若有):里面会写 Problematic frame: ... in libYYY.so+0xOFFSET,对齐 core 结果。
    • 看崩溃前是否有**“malloc(): memory corruption”“double free”“stack smashing”** 等致命语句;或是业务 WARN/ERROR。
  4. 判定空指针/野指针/堆损坏

    • 空指针si_addr=0x0info args/locals 里某对象/指针为 0,却被解引用。
    • 野指针/越界si_addr 在未映射区,或明显越界(例如长度 16 的 buffer 被写到 200 字节);
    • 堆损坏/主动 abortsi_signo=SIGABRT,日志或 hs_err 里有 glibc/ASan 的报错;回溯里常见 __libc_malloc/free 附近帧。
  5. 注意“当前出错帧未必是根因”

    • 常见情况是更早的越界写把堆/对象破坏了,稍后spdlog 或别的库里读到这块坏内存才崩。
    • 这时线上只能定位“报错点”;要在测试环境复现并用 ASan/UBSan/TSan/Valgrind 把“首次非法写”抓出来。

一些你可以直接用的命令/套路

  • 看信号信息

    bash
    readelf -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>
  • 补符号的两种路径

    1. 系统库:debuginfo-install glibc libstdc++ zlib ...apt-get install ...-dbg
    2. 你们自家库:把同一构建的 .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

    bash
    yarn logs -applicationId application_... -containerId container_... > container.log find /export -name 'hs_err_pid*.log' -mmin 180

小结(把你的问题串成一句话的答案)

  • 符号表让 gdb 知道函数名,调试符号让 gdb 知道行号与变量;“补符号”就是把与崩溃二进制匹配的调试信息补齐到分析环境。
  • 共享库不是“拼接在一起”,而是各自映射到同一进程地址空间,通过动态链接机制互相调用;这依赖 OS 的虚拟内存/分页,但排障时你只需关心加载基址与映射区间
  • NT_SIGINFOsi_signo/si_addr 告诉你是什么信号、访问了哪个地址,常用于判断空指针(0 地址)还是野指针/越界(怪地址)。
  • 跳到 #12 doRecv 是为了在你的业务代码帧里看参数/局部变量,结合加载基址/偏移容器/JVM 日志,最终判定“是哪一个 .so 的哪一行”出问题,以及它是空指针/野指针/堆损坏中的哪一类。

如果你愿意,把 p $_siginfo 的输出和 frame 12info 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 把“首次非法写”抓出来,如何理解 ?

思考

Providing guidance on readelf output

It looks like the user's not seeing detailed NT_SIGINFO information in the readelf output because readelf -n only shows the header, not the full content. The NT_SIGINFO section can sometimes be truncated or not fully expanded. I’ll suggest using gdb with commands like p $_siginfo or info signals to get more details, or exploring eu-readelf -n for expanded output. Another option is using eu-unstrip -n, or tools like crash.

Thought for 14s

回答

太棒了,这些都是把 core 用“内行人方式”吃透的关键点。我分 6 段来答:

  1. 你说“没看到 si_signo/si_addr”,到底去哪儿看;
  2. “符号/调试符号/补符号”再捋一遍;
  3. 动态链接、.so、Java 如何调用 .so、为啥要 strip;
  4. 虚拟地址空间 & “加载基址”到底怎么回事;
  5. 为什么要跳到 #12 doRecv
  6. “早写晚崩”与 ASan/UBSan/TSan/Valgrind 怎么理解 & 落地。

1) 为啥你在 readelf -n 里没看到 si_signo/si_addr?应该去哪儿看

  • 你贴的 readelf -n 输出只是 NOTE header 的目录(“这里有个 NT_SIGINFO 块”),GNU readelf 默认不展开 siginfo_t 的字段,所以不会直接显示 si_signo/si_addr。这没问题,工具就是这样。
  • 正确的、最直接的看法是在 gdb 里读 core 的“信号现场”:
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 先看结构
  • 如果你暂时不进 gdb,也可从 内核日志看“faulting address”:
    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


2) 符号表 / 调试符号 / “补符号”到底是啥

  • 动态符号表 .dynsym:给动态链接器用,含“导出函数/变量”的符号名。有它,gdb 至少能显示函数名;但没有源码行/局部变量
  • 完整符号表 .symtab:更多本地符号,发布时常被 strip 掉。
  • 调试信息(DWARF,.debug_* 段):让 gdb 有源码文件/行号、内联、局部变量和类型

info sharedlibrary 的 “Syms Read” 列:

  • Yes (*) = 读到符号无调试信息(能见函数名,但无行号/局部变量)。
  • Yes(无星号)= 通常表示能拿到调试信息(视构建而定)。
  • No = 连函数名都没有,只是地址。

“补符号”就是:把与你崩溃时二进制完全一致的调试信息放回分析环境,让 gdb 能“指到行、看变量”。做法:

  • 系统库:安装 debuginfo 包(RHEL/CentOS 用 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

3) 动态链接 / .so / Java 怎么调 .so / 为什么 strip

  • 动态链接:你的可执行文件在运行时通过动态链接器/lib64/ld-linux-*.so.2)把需要的共享库(.so映射进进程,并把函数调用通过 PLT/GOT 解析到正确地址。这样可复用库、节省内存、便于更新。
  • .so 是什么:ELF 共享库(Shared Object)。被多个进程共享映射(代码段只读共享)。
  • Java 如何用 .so:通过 JNI/JNA/JNR。常见调用:
    java
    System.loadLibrary("hadoop"); // 走 java.library.path System.load("/abs/path/libxxx.so"); // 绝对路径
    然后 JVM 里 native 方法就能跳到 C/C++ 的实现。
  • 为啥 strip:减小体积、降低符号泄露风险。strip 后线上库更小更快,但分析时需要单独的 .debug 文件来还原。

4) 虚拟地址空间 & “加载基址”怎么理解(跟“看偏移”强相关)

  • 虚拟地址空间(VAS):每个进程看到的是“自己的”连续地址空间;不同 .so 会被映射到不同的区间(你在 NT_FILE 里看到的 Start/End)。物理内存由内核和 MMU 管理;进程之间相互隔离
  • 加载基址(base address):某个 .so 本次运行被映射到的起始地址(ASLR 每次可能不同)。
    info sharedlibraryFrom 就是“可以当作基址”的值。
  • 为什么老在算“偏移”:因为地址随机化,库内的崩溃点= 基址 + 偏移。我们把 崩溃地址 - 基址 = 偏移,再用:
    bash
    addr2line -fe /path/to/libxxx.so 0x<偏移>
    定位源码行(前提:该库有调试信息或 .debug 文件)。

5) 为什么要跳到 #12 doRecv(而不是停在 raise/abort/libjvm.so

  • 你贴的 bt 顶部是 raise → abort → libjvm.so 的 fatal path,这是JVM 在处理致命信号/错误时的通用“收尾栈”,信息价值有限。
  • 真正有价值的是“事发前的业务栈”:在你的样本里就是 #12 doRecv 往下的几帧(spdlog::... → doRecv → NetEvent...)。
  • 目的:
    1. 看形参/局部变量(需要调试符号):是否有 NULL/长度异常/悬垂引用;
    2. 结合 $_siginfosi_signo/si_addr,判断这是空指针/野指针/越界还是主动 abort
    3. 再回头看容器/JVM 日志里崩溃前的 WARN/ERROR,拼出现场。

操作小抄:

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

6) “早写晚崩”的理解 & ASan/UBSan/TSan/Valgrind 怎么用

很多 native 崩溃是更早的“非法写”破坏了内存过一会儿别的代码(比如 spdlog 格式化字符串)“碰到这块坏内存”才崩。线上 core 只能告诉你“撞墙点”,不一定能告诉你“第一次挖坑点”。

怎么抓“第一次挖坑点”(测试/预发环境):

  1. 编译开 Sanitizer(推荐)

    • AddressSanitizer(ASan):抓越界、UAF、double free
    • UndefinedBehaviorSanitizer(UBSan):抓未定义行为
    • ThreadSanitizer(TSan):抓数据竞争
      典型编译参数(GCC/Clang):
    bash
    # Debug 或预发构建 -g -O1 -fno-omit-frame-pointer \ -fsanitize=address,undefined # ASan + UBSan # 多线程问题排查专用 -fsanitize=thread # TSan(与 ASan 不能同时开)

    运行时会直接在第一次非法写发生的那一行报错并堆栈,价值极高。

  2. Valgrind(Memcheck)

    • 不需要重编译,但严谨。
    • 运行:
      bash
      valgrind --tool=memcheck --leak-check=full --track-origins=yes ./your_binary ...
    • 能报告无效读写/越界/泄漏以及“这个指针最初从哪儿来”。
  3. 复现策略

    • 复刻与线上相同的输入/配置
    • 关闭过度优化(-O0/-O1),保留帧指针(-fno-omit-frame-pointer);
    • 日志等级调高,在 suspect 路径加边界检查与断言,一旦发现异常 abort() 让现场尽早暴露。

实战心法:线上 core 定“撞墙点”日志/指标定位“触发条件”测试/预发开 Sanitizer 抓“第一次挖坑点”


把你的问题串成“行动项”

  1. 打开 core 于 gdb,直接看信号:
gdb
gdb /export/App/jdk1.8.0_60/bin/java /path/to/core (gdb) p $_siginfo # 看 si_signo/si_addr
  1. 找到业务帧并看变量:
gdb
(gdb) f 12 (gdb) info args (gdb) info locals
  1. 不够细?补符号:
  • libjdufs_sdk.solibgplcompression.so(那两个 (deleted) JNI so)补同版本的符号或 .debug
  1. 把“哪一个 .so 的哪一个偏移”钉住(必要时用基址→偏移→addr2line):
bash
# 基址看 info sharedlibrary 的 From addr2line -fe /path/to/libxxx.so 0x<fault_offset>
  1. 拼日志时间线:
  • 你已经在 NT_FILE 看到了 application_...container_...
bash
yarn logs -applicationId application_... \ -containerId container_... > container.log
  • hs_err_pid*.logProblematic frame
  1. 在测试/预发用 ASan/UBSan(或 Valgrind)复现,抓第一次非法写的位置

如果你把 gdb 里的 p $_siginfo 结果和 frame 12info 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 NTFILENT_FILE 3 = void
(gdb) p ntfilent_file 4 = void
帮我分析一下

思考

Analyzing GDB Outputs

I’ll provide guidance on diagnosing the issue from the GDB output. The lack of a source file is likely due to missing debug symbols or a path mismatch. I recommend using set substitute-path to map source files correctly. If $_siginfo isn't available, try p $_siginfo to explore SIGABRT paths. For optimized-out variables, I suggest recompiling with specific flags to retain debugging info.

Checking pointer validity and crash cause

I’ll check the validity of ctx and point out that optimized variables might be hidden. If the type is known, use print *ctx to check it. If missing symbols, only address will show. The crash seems related to spdlog, possibly due to format string issues or invalid arguments. The stack traces indicate a signal occurred during the spdlog call, implying it's part of the crash. To pinpoint the signal, use $_siginfo. $NT_FILE isn’t available here.

Troubleshooting GDB issue

To resolve the "No such file" issue in GDB, try using set substitute-path to map source directories. You can also use info sharedlibrary and addr2line to map addresses offline. To find the faulting address, use info registers and $pc, then inspect the problematic frame. If it's a HotSpot fatal error, it's likely due to a native signal like SIGSEGV or SIGBUS; check dmesg for more info.

Identifying the problematic frame

When dealing with the frame address 0x7fe296c20ab7, I’ve traced it back to being inside the libjdufs_sdk.so library, not the spdlog library. This frame corresponds to code compiled into the SDK, not a standard spdlog issue. The mapping aligns with previous library info. Seems the crash is in the SDK itself.

Thought for 25s

回答

给的这些栈和 readelf 线索已经很关键了。我把你贴出来的结果“逐个拆解 + 落地招式”给你:


你现在这份 core 的关键信息(先下结论)

  • 这是 Java 进程/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”?

  • gdb 里已经有行号信息(所以显示了 tcp_transport_extend.cpp:421),但你的分析机上没有这条路径对应的源码
  • 解决(任选其一):
    1. 把同一版本的源码放到相同路径;或
    2. 用路径替换把“构建机路径 → 本机源码路径”映射起来:
      gdb
      (gdb) set substitute-path /export/code2/ljd/testrepo/src /home/me/src (gdb) dir /home/me/src (gdb) f 12 (gdb) list
    3. 如果仍无源码,走“基址→偏移→addr2line”:
      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
    (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=SIGSEGVsi_addr=0x0 → 空指针概率极高;
  • si_addr 是个怪地址、又不在任何映射内 → 野指针/越界写概率高;
  • SIGABRT → 多为主动 abort()(断言失败/堆损坏),去容器日志或 hs_err_pid*.log 看 fatal 文字更快。

4 个 gdb 动作,帮你把“到底错在谁、哪一行”钉死

  1. 确定信号与地址

    gdb
    (gdb) p $_siginfo (gdb) info registers # 看 $pc(RIP)、参数寄存器(RDI/RSI/...)
  2. 看事发帧的参数/局部

    gdb
    (gdb) f 12 (gdb) info args (gdb) info locals (gdb) p this (gdb) disassemble /m $pc-32, $pc+64
    • 变量被标成 <optimized out>:说明二进制是 -O2 且没带完整调试符号。要想看见更多变量,需要补符号(见下)。
  3. 确认库 + 偏移 + 行号

    gdb
    (gdb) info sharedlibrary | grep -n 'libjdufs_sdk.so' # 记下 From 基址(≈ 0x7fe296a12000) # 计算 offset,然后: addr2line -fe /export/Data/tmp/hadoop-tmp/libjdufs_sdk.so 0x20eab7
  4. 拼容器/JVM 日志时间线(你已经有 appId/containerId)

    bash
    yarn 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
  • 需要:
    1. 系统库:安装 debuginfo(例如 glibclibstdc++zlib 等)。
    2. 自家库(尤其 libjdufs_sdk.solibgplcompression.so 那个已删除的临时 so):把同一构建的 .debug 文件或未 strip 版本放到分析机:
      gdb
      (gdb) set debug-file-directory /path/to/debug-repo (gdb) set solib-search-path /path/to/so-repo
    3. 下次构建尽量启用:-g -O1 -fno-omit-frame-pointer(线上可 strip,debug 包单独存)。

“动态链接 / .so / Java 怎么调 .so / 为什么 strip”一段话讲清

  • 动态链接:可执行在运行时由动态链接器把依赖的 .so 按页映射到本进程虚拟地址空间,函数调用靠符号解析(PLT/GOT)跳到正确地址。
  • .so:ELF 共享库文件。可被多个进程共享,代码段只读共享。
  • Java 调 .so:走 JNI/JNR/JNA,如 System.loadLibrary("hadoop") / System.load("/abs/path/libxxx.so"),然后 native 方法跳到 C/C++ 实现。
  • 为什么 strip:减体积/减少符号泄漏;分析时就需要把同版本 .debug 文件“补”回来。

“虚拟地址空间 & 加载基址”两句话

  • 同一进程里,每个 .so 被映射到不同的地址区间(你在 NT_FILE 里看到了 Start/End),这就是“虚拟地址空间”的局部视图。
  • ASLR 让每次基址不同,所以定位库内崩溃点要用“基址 + 偏移”;addr2line 接受的就是偏移

可能的根因方向(结合你这条栈)

  • 错误发生在 doRecv →(warn 级别)spdlog 打日志这个链路里。
  • 典型高发:
    • 空/野指针作为日志参数(string_view/%s 指向已释放/未初始化内存);
    • 对缓冲区的越界写更早发生,稍后spdlog 读到坏内存时崩(“早写晚崩”)。
  • 你要做的就是:
    1. $_siginfo 判别信号/地址;
    2. f 12 看参数/局部(补符号后效果更好);
    3. addr2line0x20eab7 精确到源码;
    4. 看容器日志/hs_err 的异常文案;
    5. 在测试/预发用 ASan-fsanitize=address)+ UBSan 复现,这能把“第一次非法写”当场报出来(而不是等到 spdlog 才爆)。

你可以直接执行的 8 行指令

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 siginfo_siginfo 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>

思考

Analyzing crash details

It seems the crash was triggered by a SIGABRT in the JVM error handler, following a possible earlier SIGSEGV. The core dump records the final SIGABRT signal, but earlier details might be found in the hs_err_pid log. I’d recommend looking up that log alongside JVM ErrorFile settings. For more details, use tools like addr2line and gdb to analyze the crash further.

Providing debugging steps

It looks like the issue is caused by a missing source path, so we should use the "set substitute-path" command. To explore the context (ctx) pointer, we can cast and print fields, though without debug symbols, many locals are optimized out. For SIGABRT, si_code -6 likely maps to SI_TKILL. I’d also suggest using jhsdb to examine the hs_err file and checking memory offsets with info proc mappings.

Providing debugging steps

To diagnose the issue, I’ll suggest extracting the problematic frame address with addr2line and checking container logs for memory corruption errors. I'll also explain how to compute the mapping offset and perform mental checks using printf. For SIGAB

Thought for 22s

回答

好消息:你已经把“信号现场”和“业务栈”都拿到了,现在可以下清晰结论并推进到精确定位哪一行了。下面我把你这次的输出解读、结论、以及下一步“落地动作”给全了。


1) 先读你给的关键信息(结论)

p $_siginfo

text
si_signo = 6 si_code = -6 _sigfault.si_addr = 0x3aa66b
  • si_signo=6SIGABRT(终止信号),不是 SEGFAULT。
  • si_code=-6 在 Linux 上通常对应 SI_TKILL(线程自发的信号,常见于 abort()/tgkill() 路径)。
  • 对于 SIGABRT_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()
  • HotSpot 的致命错误处理已接管,然后调用 abort() → 所以 core 的终止信号是 SIGABRT
  • 真正触发致命错误的“最初原因”不在当前信号字段里,通常写在 hs_err_pid*.log 里(Problematic frame、原始 SIGSEGV/SIGBUS、或堆损坏等信息)。
    也可能是 glibc 自检(如 malloc(): memory corruption)触发了 abort,这类文案会出现在容器 stdout/stderr 或 hs_err 里。

业务帧

#12 kunlun::netlib::TcpTransportExtend::doRecv(...):421
  • gdb 知道行号,但本机没有源码路径(所以提示 No such file or directory)。
  • 局部变量大多 <optimized out> → 需要补调试符号才能看到更多细节。

2) 把“崩溃点”钉到具体源码行(不依赖本机源码路径)

你可以直接把第 7 帧的地址换算成 libjdufs_sdk.so 内偏移,再 addr2line 还原行号:

  • 第 7 帧地址:0x7fe296c20ab7
  • 从你之前的映射看,libjdufs_sdk.so加载基址约为:0x7fe296a12000
  • 偏移:0x7fe296c20ab7 - 0x7fe296a12000 = 0x20eab7
bash
addr2line -fe /export/Data/tmp/hadoop-tmp/libjdufs_sdk.so 0x20eab7 # -f 显示函数名,-e 指定库文件

这样即使你没有把源码放回相同路径,也能直接得到“库内偏移 → 源文件:行号”。
若这一步报不出行号,说明 libjdufs_sdk.so 缺调试符号:把同版本的 .debug 文件放到分析机(或不 strip 的 so),然后重跑。


3) 为什么是 SIGABRT?“空/野指针”还怎么判断?

  • 当前 core 的终止信号是 SIGABRT(abort())。abort 的常见来源:
    1. HotSpot 捕获了原始的致命信号(如 SIGSEGV)后,打印 fatal 报告,最后 abort()
    2. glibc/库自检触发(堆损坏、断言失败等),直接 abort()
    3. 代码显式 abort()
  • 因为是 SIGABRTsi_addr 对“判空/野指针”不再适用。想知道原始错误是“空/野指针/堆损坏”,需要:
    • hs_err_pid*.log:里面会写 A fatal error has been detected...Problematic frame:EXCEPTION_ACCESS_VIOLATION/SIGSEGV 等原始信息;
    • 查容器日志里崩溃前的 stderr:是否出现 malloc(): memory corruptiondouble free*** buffer overflow detected *** 等字样。

你已经通过 NT_FILE 拿到了 applicationId/containerId,直接拉日志:

bash
yarn logs -applicationId application_1754969617538_4473 \ -containerId container_e41_1754969617538_4473_01_000127 \ > container.log find /export -name 'hs_err_pid*.log' -mmin 240

4) “No such file or directory”怎么解决(让 gdb 能显示源码)

任选一种:

  1. 把对应版本源码同步到相同路径:/export/code2/ljd/testrepo/src/...
  2. 用路径替换(推荐):
    gdb
    set substitute-path /export/code2/ljd/testrepo/src /home/andrew/src dir /home/andrew/src f 12 list
  3. 不折腾路径,直接走 addr2line(上面已经给出)。

5) 变量都是 <optimized out> 怎么办(“补符号”的落地)

  • 现象:info locals 里大量 <optimized out>,说明编译用了 -O2没有完整 DWARF
  • 解决:
    • 系统库:安装 debuginfo 包(glibc、libstdc++、zlib…),这样能看更清楚的系统栈。
    • 自家库(重点是 libjdufs_sdk.so:把同一构建.debug 放到分析机,并让 gdb 能找到:
      gdb
      set debug-file-directory /path/to/debug-repo set solib-search-path /path/to/so-repo
    • 下次构建建议:-g -O1 -fno-omit-frame-pointer(线上可 strip,调试符号单独存 Build-ID 仓)。

6) 现在就可以做的 6 步 SOP(你几乎都准备好了)

  1. 确认终止信号(你已做)
    → SIGABRT(si_signo=6),si_addr 忽略。

  2. 将崩溃点指到源码行(即使没源码路径)

    bash
    addr2line -fe /export/Data/tmp/hadoop-tmp/libjdufs_sdk.so 0x20eab7
  3. 查看容器/JVM fatal 文案(原始原因多在这里)

    bash
    yarn 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断言触发
  4. 补符号后再次查看第 12 帧

    gdb
    f 12 info args info locals p this # 结合源码,检查传入的 buffer 指针/长度/生命周期
  5. 排查“日志参数”是否有问题

    • 你的栈显示崩在 spdlog::logger::log_* 这一侧。常见坑:日志格式字符串引用了无效内存(空/野指针),或 fmt 的参数类型与实际不符。
    • doRecv/上游调用点增加保护:
      • 对所有 const char*/string_view 参数 判空
      • 对长度/边界 校验
      • 避免把悬垂引用(指向临时对象的引用/指针)传给日志。
  6. 在测试/预发用 Sanitizer 抓“第一次非法写”(如果容器日志提示堆损坏/随机崩)

    • 重新编译可执行/so:
      -g -O1 -fno-omit-frame-pointer -fsanitize=address,undefined
      
    • 运行复现用例:ASan/UBSan 会在第一次非法访问时立刻报栈,而不是等到 spdlog 时才崩。

快速问答(对你现阶段最关键的几个点)

  • 这是空指针/野指针吗?
    仅凭这个 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 条具体防御性检查(参数判空、长度校验、所有权/生命周期约束)的代码位点。

分享这个问答