Where did my segfault go?
Where did my segfault go?
我的段错误(segfault)去哪了?
Where did my segfault go? The other day I was iterating on a small C program with entr: ls hello.c | entr -s "gcc -o hello hello.c && ./hello" hello happened to segfault, but entr showed me… nothing. No Segmentation fault, no non-zero exit visible, just complete silence after the compile output. Hmm, very weird.
我的段错误去哪了?前几天我正在用 entr 工具迭代一个小的 C 程序:ls hello.c | entr -s "gcc -o hello hello.c && ./hello"。程序发生了段错误,但 entr 却什么都没显示……没有“Segmentation fault”提示,也没有非零退出的迹象,编译输出后一片死寂。嗯,非常奇怪。
Let’s prefix with bash -c: ls hello.c | entr -s "bash -c 'gcc -o hello hello.c && ./hello'" Still nothing. What about moving the command into a script:
#!/bin/bash
gcc -o hello hello.c && ./hello
And point entr to it: ls hello.c | entr -s ./run.sh
./run.sh: line 2: 104465 Segmentation fault (core dumped) ./hello
There’s my 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
终于看到段错误了!
The reason(s) 原因分析
Thanks to superuser (remember that site we used to use pre LLMs?) for pointing me in the right direction. The question is not exactly the same, but it still helped me. At first, I thought the problem was with entr because I could reproduce both with bash and fish. Who actually prints “Segmentation fault”? Obviously not the crashing program. It can’t since it’s dead. The message is printed by the shell when it reaps a child that died from SIGSEGV. No parent shell, no message, simple enough.
感谢 Superuser(还记得我们在大模型时代之前常去的那个网站吗?)为我指明了方向。虽然问题不完全一样,但依然对我很有帮助。起初,我以为是 entr 的问题,因为我在 bash 和 fish 中都能复现这种情况。到底是谁打印了“Segmentation fault”?显然不是崩溃的程序本身,因为它已经死了,没法打印。这个消息是由 shell 在回收因 SIGSEGV 而死亡的子进程时打印的。没有父 shell,就没有消息,道理很简单。
But wait, why does even the explicit bash -c 'gcc && ./hello' version stay silent? Turns out bash likes to exec the command if it can. When you run bash -c "some_command" and some_command is effectively the only thing to do, bash doesn’t bother forking a new process for it. It just execves into it, replacing itself. It’s a nice optimization, invisible 99% of the time.
等等,为什么显式使用 bash -c 'gcc && ./hello' 时依然保持沉默?原来 bash 在可能的情况下喜欢直接 exec 命令。当你运行 bash -c "some_command" 且 some_command 实际上是唯一要执行的任务时,bash 不会费心去 fork 一个新进程,而是直接通过 execve 替换掉自己。这是一个很好的优化,在 99% 的情况下都是不可见的。
In the script version, entr runs ./run.sh as a child, and the script’s shebang starts a fresh bash to interpret the file. That bash runs gcc, forks for ./hello, waits, sees it died from SIGSEGV, and dutifully prints Segmentation fault before exiting.
在脚本版本中,entr 将 ./run.sh 作为子进程运行,脚本的 shebang 会启动一个新的 bash 来解释该文件。那个 bash 运行 gcc,为 ./hello fork 一个进程,等待并发现它死于 SIGSEGV,于是尽职尽责地在退出前打印了 Segmentation fault。
The follow-up 后续探索
But then it occurred to me, maybe you don’t need a wrapper script after all, what happens if I have the crashing command running in a subshell?
$ ls hello.c | entr -s "gcc -o hello hello.c && (./hello)"
hello.c: line 1: 106595 Segmentation fault (core dumped) ( ./hello )
Yep, I don’t think I have ever been that satisfied to see a program segfaulting. Here the parens force ./hello into a forked subshell, so bash can’t exec its way out of being the parent. When the subshell dies from SIGSEGV, bash reaps it and prints the message like normal.
但后来我想到,也许根本不需要包装脚本,如果让崩溃的命令在子 shell 中运行会怎样?
$ ls hello.c | entr -s "gcc -o hello hello.c && (./hello)"
hello.c: line 1: 106595 Segmentation fault (core dumped) ( ./hello )
没错,我想我从未如此满足地看到程序发生段错误。这里的括号强制将 ./hello 放入一个 fork 出来的子 shell 中,因此 bash 无法通过 exec 逃避作为父进程的职责。当子 shell 因 SIGSEGV 死亡时,bash 会回收它并正常打印消息。
I could have stopped there but I wanted a way to test my second hypothesis. What if you just make sure the shell has something to do after the crashing command?
$ ls hello.c | entr -s "bash -c 'gcc -o hello hello.c && ./hello; true'"
bash: line 1: 109516 Segmentation fault (core dumped) ./hello
Victory! Now I should probably get back to writing some C…
我本可以就此打住,但我还想验证我的第二个假设。如果确保 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 代码了……
Written on July 11, 2026 写于 2026 年 7 月 11 日