linux_core_dumper: support setting exception_information

Many signals in Linux support additional metadata on a per-signal
basis.  We can extract that from NT_SIGINFO and pass it through
in the exception_information fields.

The current core dumper logic doesn't set exception_information
at all, so this is an improvement.

Bug: google-breakpad:791
Change-Id: I38b78d6494e9bc682441750d98ac9be5b0656f5a
Reviewed-on: https://chromium-review.googlesource.com/c/breakpad/breakpad/+/1497662
Reviewed-by: Mark Mentovai <mark@chromium.org>
diff --git a/src/client/linux/minidump_writer/linux_core_dumper.cc b/src/client/linux/minidump_writer/linux_core_dumper.cc
index 3bb2ee7..b24573b 100644
--- a/src/client/linux/minidump_writer/linux_core_dumper.cc
+++ b/src/client/linux/minidump_writer/linux_core_dumper.cc
@@ -241,6 +241,16 @@
             crash_address_ = reinterpret_cast<uintptr_t>(info->si_addr);
             break;
         }
+
+        // Set crash_exception_info for common signals.
+        switch (info->si_signo) {
+          case MD_EXCEPTION_CODE_LIN_SIGKILL:
+            set_crash_exception_info({info->si_pid, info->si_uid});
+            break;
+          case MD_EXCEPTION_CODE_LIN_SIGSYS:
+            set_crash_exception_info({info->si_syscall, info->si_arch});
+            break;
+        }
         break;
       }
 #if defined(__i386) || defined(__x86_64)
diff --git a/src/client/linux/minidump_writer/linux_core_dumper_unittest.cc b/src/client/linux/minidump_writer/linux_core_dumper_unittest.cc
index f5b299f..927d5aa 100644
--- a/src/client/linux/minidump_writer/linux_core_dumper_unittest.cc
+++ b/src/client/linux/minidump_writer/linux_core_dumper_unittest.cc
@@ -109,7 +109,7 @@
   EXPECT_TRUE(dumper.ThreadsSuspend());
   EXPECT_TRUE(dumper.ThreadsResume());
 
-  // LinuxCoreDumper cannot determine the crash address and thus it always
+  // Linux does not set the crash address with SIGABRT, so make sure it always
   // sets the crash address to 0.
   EXPECT_EQ(0U, dumper.crash_address());
   EXPECT_EQ(kCrashSignal, dumper.crash_signal());
@@ -126,3 +126,48 @@
     EXPECT_EQ(getpid(), info.ppid);
   }
 }
+
+TEST(LinuxCoreDumperTest, VerifyExceptionDetails) {
+  CrashGenerator crash_generator;
+  if (!crash_generator.HasDefaultCorePattern()) {
+    fprintf(stderr, "LinuxCoreDumperTest.VerifyDumpWithMultipleThreads test "
+            "is skipped due to non-default core pattern\n");
+    return;
+  }
+
+  const unsigned kNumOfThreads = 2;
+  const unsigned kCrashThread = 1;
+  const int kCrashSignal = SIGSYS;
+  pid_t child_pid;
+  ASSERT_TRUE(crash_generator.CreateChildCrash(kNumOfThreads, kCrashThread,
+                                               kCrashSignal, &child_pid));
+
+  const string core_file = crash_generator.GetCoreFilePath();
+  const string procfs_path = crash_generator.GetDirectoryOfProcFilesCopy();
+
+#if defined(__ANDROID__)
+  struct stat st;
+  if (stat(core_file.c_str(), &st) != 0) {
+    fprintf(stderr, "LinuxCoreDumperTest.VerifyExceptionDetails test is "
+            "skipped due to no core file being generated");
+    return;
+  }
+#endif
+
+  LinuxCoreDumper dumper(child_pid, core_file.c_str(), procfs_path.c_str());
+
+  EXPECT_TRUE(dumper.Init());
+
+  EXPECT_TRUE(dumper.IsPostMortem());
+
+  // Check the exception details.
+  EXPECT_NE(0U, dumper.crash_address());
+  EXPECT_EQ(kCrashSignal, dumper.crash_signal());
+  EXPECT_EQ(crash_generator.GetThreadId(kCrashThread),
+            dumper.crash_thread());
+
+  // We check the length, but not the actual fields.  We sent SIGSYS ourselves
+  // instead of the kernel, so the extended fields are garbage.
+  const std::vector<uint64_t> info(dumper.crash_exception_info());
+  EXPECT_EQ(2U, info.size());
+}
diff --git a/src/client/linux/minidump_writer/linux_dumper.h b/src/client/linux/minidump_writer/linux_dumper.h
index 76448fa..f4a75d9 100644
--- a/src/client/linux/minidump_writer/linux_dumper.h
+++ b/src/client/linux/minidump_writer/linux_dumper.h
@@ -38,6 +38,7 @@
 #ifndef CLIENT_LINUX_MINIDUMP_WRITER_LINUX_DUMPER_H_
 #define CLIENT_LINUX_MINIDUMP_WRITER_LINUX_DUMPER_H_
 
+#include <assert.h>
 #include <elf.h>
 #if defined(__ANDROID__)
 #include <link.h>
@@ -47,6 +48,8 @@
 #include <sys/types.h>
 #include <sys/user.h>
 
+#include <vector>
+
 #include "client/linux/dump_writer_common/mapping_info.h"
 #include "client/linux/dump_writer_common/thread_info.h"
 #include "common/linux/file_id.h"
@@ -184,6 +187,14 @@
   void set_crash_signal_code(int code) { crash_signal_code_ = code; }
   int crash_signal_code() const { return crash_signal_code_; }
 
+  void set_crash_exception_info(const std::vector<uint64_t>& exception_info) {
+    assert(exception_info.size() <= MD_EXCEPTION_MAXIMUM_PARAMETERS);
+    crash_exception_info_ = exception_info;
+  }
+  const std::vector<uint64_t>& crash_exception_info() const {
+    return crash_exception_info_;
+  }
+
   pid_t crash_thread() const { return crash_thread_; }
   void set_crash_thread(pid_t crash_thread) { crash_thread_ = crash_thread; }
 
@@ -236,6 +247,9 @@
   // The code associated with |crash_signal_|.
   int crash_signal_code_;
 
+  // The additional fields associated with |crash_signal_|.
+  std::vector<uint64_t> crash_exception_info_;
+
   // ID of the crashed thread.
   pid_t crash_thread_;
 
diff --git a/src/client/linux/minidump_writer/minidump_writer.cc b/src/client/linux/minidump_writer/minidump_writer.cc
index 31e7bd6..e436bf0 100644
--- a/src/client/linux/minidump_writer/minidump_writer.cc
+++ b/src/client/linux/minidump_writer/minidump_writer.cc
@@ -713,6 +713,12 @@
     stream->exception_record.exception_code = dumper_->crash_signal();
     stream->exception_record.exception_flags = dumper_->crash_signal_code();
     stream->exception_record.exception_address = dumper_->crash_address();
+    const std::vector<uint64_t> crash_exception_info =
+        dumper_->crash_exception_info();
+    stream->exception_record.number_parameters = crash_exception_info.size();
+    memcpy(stream->exception_record.exception_information,
+           crash_exception_info.data(),
+           sizeof(uint64_t) * crash_exception_info.size());
     stream->thread_context = crashing_thread_context_;
 
     return true;