mirror of
https://github.com/ruby/ruby.git
synced 2026-10-28 16:36:20 +08:00
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:
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
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in new issue
Block a user