我的段错误去哪了?
摘要
作者调查了使用 entr 运行 C 程序时为何不显示段错误,发现 bash 的 exec 优化会抑制错误信息,除非在子 shell 或脚本中运行命令。
<p><a href="https://lobste.rs/s/tedtzz/where_did_my_segfault_go">评论</a></p>
查看缓存全文
缓存时间: 2026/07/11 21:27
# 我的段错误去哪儿了?
来源:https://rmpr.xyz/Where-did-my-segfault-go/
前几天,我在用 `entr`(https://eradman.com/entrproject/)迭代一个小型 C 程序:
```
ls hello.c | entr -s "gcc -o hello hello.c && ./hello"
```
`hello` 碰巧段错误了,但 `entr` 却什么都没显示……没有 `Segmentation fault`,没有非零退出代码,编译输出之后就是一片寂静。嗯,很奇怪。试试在前面加上 `bash -c`:
```
ls hello.c | entr -s "bash -c 'gcc -o hello hello.c && ./hello'"
```
仍然没有动静。那把命令放到一个脚本里呢:
```
#!/bin/bash
gcc -o hello hello.c && ./hello
```
然后把脚本指向 entr:
```
ls hello.c | entr -s ./run.sh
./run.sh: line 2: 104465 Segmentation fault (core dumped) ./hello
```
这下段错误出来了!
## 原因
> 感谢 superuser(https://superuser.com/a/415954)(还记得我们在 LLM 出现之前常用的那个网站吗?)为我指明了方向。问题并不完全相同,但依然有帮助。一开始我以为问题出在 `entr` 上,因为我在 bash 和 fish 里都能复现。
**到底是谁打印了 “Segmentation fault”?**显然不是崩溃的程序。它已经死了,不可能打印。这条信息是由 *shell* 在回收一个因 `SIGSEGV` 死亡的子进程时打印的。没有父 shell,就没有消息,就这么简单。
但等等,为什么连显式的 `bash -c 'gcc && ./hello'` 版本也保持沉默?
原来 bash 喜欢在可能的情况下用 `exec` 来执行命令。当你运行 `bash -c "some_command"` 并且 `some_command` 实际上是唯一要做的事情时,bash 不会为它分叉一个新进程(https://cgit.git.savannah.gnu.org/cgit/bash.git/tree/execute_cmd.c?id=ecb2456d6357bfd3b2965aafe2f2021d1ced5b72#n5827)。它直接 `execve` 进去,替换掉自己。这是一个不错的优化,99% 的情况下你都察觉不到。
在脚本版本中,entr 把 `./run.sh` 作为子进程运行,而脚本的 shebang 启动了一个新的 `bash` 来解析该文件。这个 bash 运行 `gcc`,为 `./hello` 分叉,等待,发现它因 `SIGSEGV` 而死,于是尽职尽责地在退出前打印了 `Segmentation fault`。
## 后续
但接着我想到,也许你根本不需要一个包装脚本。如果把崩溃命令放在一个子 shell 里运行,会怎样呢?
```
$ ls hello.c | entr -s "gcc -o hello hello.c && (./hello)"
hello.c: line 1: 106595 Segmentation fault (core dumped) ( ./hello )
```
没错,我想我从未因为看到程序段错误而如此满足过。这里的括号强制 `./hello` 运行在一个分叉的子 shell 中,这样 bash 就不能通过 `exec` 摆脱父进程的角色。当子 shell 因 `SIGSEGV` 死亡时,bash 回收它并像正常情况一样打印消息。
我本可以就此打住,但我还想用另一种方法验证我的第二个假设。如果只是让 shell 在崩溃命令之后还有事可做,会怎样呢?
```
$ ls hello.c | entr -s "bash -c 'gcc -o hello hello.c && ./hello; true'"
bash: line 1: 109516 Segmentation fault (core dumped) ./hello
```
成功了!
现在我大概该回去写点 C 代码了……
相似文章
追踪内核安装时段错误的原因
作者调试了内核安装过程中遇到的段错误,将其追溯到 dracut 调用的 'hardlink' 命令,并使用 gdb 调查崩溃。
突破防御:利用段错误绕过 Intel CET
该仓库提供了面向段错误编程(SFOP)的工件,这是一种利用信号处理器绕过 Intel CET 的新型利用技术。它包含针对 Nginx 和 Ladybird 的 PoC 利用和演示。
你的可执行文件是一个SQLite数据库
一篇博客文章解释了一种Linux模式,其中SQLite数据库文件被格式化为ELF可执行文件,使用自定义解释器和binfmt_misc实现直接执行。
调试挂起的Go程序的技巧
一份实用指南,涵盖了调试挂起的Go程序的三种方法:使用SIGQUIT打印堆栈跟踪、附加delve调试器以及保存核心转储供后续分析。
objdump -g 中的任意代码执行
objdump -g 中存在一个安全漏洞,由于 FR30 重定位处理程序缺少边界检查,通过精心构造的 FR30 目标文件可实现任意代码执行,单个漏洞利用即可绕过 ASLR 及其他缓解措施。