Differentiate crash from other process sending signal

Recently we were debugging a Ruby crash by looking at a core dump and
realized that there was no crash, it was another process sending a
SIGABRT signal to the process. There was no differentiation in the crash
report for this case. Now, when another process sends a SIGABRT, SIGSEGV,
etc to a process it shows the PID of the sender. This way we can know right
away that it was not a real crash.

The crash message now looks like this:

```
[BUG] Aborted at 0x00000001831c150c (sent by pid 53859)
```

If "(sent by pid XXX)" or "(sent by another process)" is included in the crash
message, the signal was sent by another process.
This commit is contained in:
Luke Gruber authored and Luke Gruber committed 2026-09-14 10:12:22 -04:00
1 parent e7551c0883
commit c4e06b4e30
2 files changed
+64 -1
Notes: git 2026-09-14 14:12:56 +00:00

No files matched your search

+35 -1
View File
@@ -896,7 +896,41 @@ check_stack_overflow(int sig, const void *addr)
# else
# define CHECK_STACK_OVERFLOW_() check_stack_overflow(sig, FAULT_ADDRESS)
# endif
# define MESSAGE_FAULT_ADDRESS " at %p", FAULT_ADDRESS
/* Only the si_code values whose siginfo_t carries a sender pid; SI_TIMER and
* SI_SIGIO overlay si_tid/si_band at si_pid's offset. */
static bool
is_user_generated(const siginfo_t *info)
{
int code = info->si_code;
if (code == SI_USER) return true;
# ifdef SI_QUEUE
if (code == SI_QUEUE) return true;
# endif
# ifdef SI_TKILL
if (code == SI_TKILL) return true;
# endif
return code == 0;
}
static const char *
signal_sender_message(const siginfo_t *info)
{
/* check_reserved_signal_ lets only the first fatal signal get this far. */
static char buf[64];
if (info && is_user_generated(info)) {
rb_pid_t pid = info->si_pid;
if (pid == getpid()) return "";
/* si_pid is 0 when the sender is not visible in our PID namespace. */
if (pid == 0) return " (sent by another process)";
snprintf(buf, sizeof(buf), " (sent by pid %"PRI_PIDT_PREFIX"d)", pid);
return buf;
}
return "";
}
# define MESSAGE_FAULT_ADDRESS " at %p%s", FAULT_ADDRESS, signal_sender_message(info)
# define SIGNAL_FROM_USER_P() ((info)->si_code == SI_USER)
# define CHECK_STACK_OVERFLOW() (SIGNAL_FROM_USER_P() ? (void)0 : CHECK_STACK_OVERFLOW_())
# endif
+29
View File
@@ -973,6 +973,35 @@ class TestRubyOptions < Test::Unit::TestCase
end
end
# The sender pid is only reported for a signal another process sent us.
def abrt_crash_report(code)
Dir.mktmpdir("ruby_crash_report") do |dir|
IO.popen([{"RUBY_CRASH_REPORT" => "abrt.log", "RUBY_ON_BUG" => nil},
EnvUtil.rubybin, "--disable-gems", "-e", "STDOUT.sync = true; puts; #{code}"],
chdir: dir, err: File::NULL, rlimit_core: 0) do |child|
child.gets
yield child if block_given?
end
break File.read(File.join(dir, "abrt.log"))
end
end
def test_crash_report_sender_pid
omit "needs siginfo" unless (macos? || linux?)
report = abrt_crash_report("sleep") {|child| Process.kill(:ABRT, child.pid)}
assert_include(report, "[BUG] Aborted")
assert_include(report, "(sent by pid #{Process.pid})")
end
def test_crash_report_no_sender_pid_when_self_inflicted
omit "needs siginfo" unless (macos? || linux?)
report = abrt_crash_report("Process.kill(:ABRT, $$)")
assert_include(report, "[BUG] Aborted")
assert_not_include(report, "sent by pid")
end
def test_DATA
Tempfile.create(["test_ruby_test_rubyoption", ".rb"]) {|t|
t.puts "puts DATA.read.inspect"