diff --git a/src/client/linux/crash_generation/client_info.h b/src/client/linux/crash_generation/client_info.h index 173b34d8..75fe1a8a 100644 --- a/src/client/linux/crash_generation/client_info.h +++ b/src/client/linux/crash_generation/client_info.h @@ -34,7 +34,16 @@ namespace google_breakpad { class CrashGenerationServer; -struct ClientInfo { +class ClientInfo { + public: + ClientInfo(pid_t pid, CrashGenerationServer* crash_server) + : crash_server_(crash_server_), + pid_(pid) {} + + CrashGenerationServer* crash_server() const { return crash_server_; } + pid_t pid() const { return pid_; } + + private: CrashGenerationServer* crash_server_; pid_t pid_; }; diff --git a/src/client/linux/crash_generation/crash_generation_server.cc b/src/client/linux/crash_generation/crash_generation_server.cc index c5c59743..ff891353 100644 --- a/src/client/linux/crash_generation/crash_generation_server.cc +++ b/src/client/linux/crash_generation/crash_generation_server.cc @@ -396,10 +396,7 @@ CrashGenerationServer::ClientEvent(short revents) } if (dump_callback_) { - ClientInfo info; - - info.crash_server_ = this; - info.pid_ = crashing_pid; + ClientInfo info(crashing_pid, this); dump_callback_(dump_context_, &info, &minidump_filename); } diff --git a/src/client/linux/handler/exception_handler.cc b/src/client/linux/handler/exception_handler.cc index 6f0b8de4..aad2a0e0 100644 --- a/src/client/linux/handler/exception_handler.cc +++ b/src/client/linux/handler/exception_handler.cc @@ -529,7 +529,7 @@ bool ExceptionHandler::WriteMinidump() { static_cast(ftruncate(minidump_descriptor_.fd(), 0)); } - // Allow ourselves to be dumped. + // Allow this process to be dumped. sys_prctl(PR_SET_DUMPABLE, 1); CrashContext context; @@ -543,6 +543,22 @@ bool ExceptionHandler::WriteMinidump() { #endif context.tid = sys_gettid(); + // Add an exception stream to the minidump for better reporting. + memset(&context.siginfo, 0, sizeof(context.siginfo)); + context.siginfo.si_signo = MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED; +#if defined(__i386__) + context.siginfo.si_addr = + reinterpret_cast(context.context.uc_mcontext.gregs[REG_EIP]); +#elif defined(__x86_64__) + context.siginfo.si_addr = + reinterpret_cast(context.context.uc_mcontext.gregs[REG_RIP]); +#elif defined(__arm__) + context.siginfo.si_addr = + reinterpret_cast(context.context.uc_mcontext.arm_pc); +#else +#error "This code has not been ported to your platform yet." +#endif + return GenerateDump(&context); } @@ -586,4 +602,21 @@ void ExceptionHandler::UnregisterAppMemory(void* ptr) { } } +// static +bool ExceptionHandler::WriteMinidumpForChild(pid_t child, + pid_t child_blamed_thread, + const string& dump_path, + MinidumpCallback callback, + void* callback_context) { + // This function is not run in a compromised context. + MinidumpDescriptor descriptor(dump_path); + descriptor.UpdatePath(); + if (!google_breakpad::WriteMinidump(descriptor.path(), + child, + child_blamed_thread)) + return false; + + return callback ? callback(descriptor, callback_context, true) : true; +} + } // namespace google_breakpad diff --git a/src/client/linux/handler/exception_handler.h b/src/client/linux/handler/exception_handler.h index 68953adf..7e6f8649 100644 --- a/src/client/linux/handler/exception_handler.h +++ b/src/client/linux/handler/exception_handler.h @@ -167,6 +167,23 @@ class ExceptionHandler { MinidumpCallback callback, void* callback_context); + // Write a minidump of |child| immediately. This can be used to + // capture the execution state of |child| independently of a crash. + // Pass a meaningful |child_blamed_thread| to make that thread in + // the child process the one from which a crash signature is + // extracted. + // + // WARNING: the return of this function *must* happen before + // the code that will eventually reap |child| executes. + // Otherwise there's a pernicious race condition in which |child| + // exits, is reaped, another process created with its pid, then that + // new process dumped. + static bool WriteMinidumpForChild(pid_t child, + pid_t child_blamed_thread, + const string& dump_path, + MinidumpCallback callback, + void* callback_context); + // This structure is passed to minidump_writer.h:WriteMinidump via an opaque // blob. It shouldn't be needed in any user code. struct CrashContext { diff --git a/src/client/linux/handler/exception_handler_unittest.cc b/src/client/linux/handler/exception_handler_unittest.cc index 341d2fb0..80839730 100644 --- a/src/client/linux/handler/exception_handler_unittest.cc +++ b/src/client/linux/handler/exception_handler_unittest.cc @@ -895,6 +895,25 @@ TEST(ExceptionHandlerTest, ExternalDumper) { unlink(templ.c_str()); } +TEST(ExceptionHandlerTest, WriteMinidumpExceptionStream) { + AutoTempDir temp_dir; + ExceptionHandler handler(MinidumpDescriptor(temp_dir.path()), NULL, NULL, + NULL, false, -1); + ASSERT_TRUE(handler.WriteMinidump()); + + string minidump_path = handler.minidump_descriptor().path(); + + // Read the minidump and check the exception stream. + Minidump minidump(minidump_path); + ASSERT_TRUE(minidump.Read()); + MinidumpException* exception = minidump.GetException(); + ASSERT_TRUE(exception); + const MDRawExceptionStream* raw = exception->exception(); + ASSERT_TRUE(raw); + EXPECT_EQ(MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED, + raw->exception_record.exception_code); +} + TEST(ExceptionHandlerTest, GenerateMultipleDumpsWithFD) { AutoTempDir temp_dir; std::string path; @@ -1021,3 +1040,50 @@ TEST(ExceptionHandlerTest, AdditionalMemoryRemove) { delete[] memory; } + +static bool SimpleCallback(const MinidumpDescriptor& descriptor, + void* context, + bool succeeded) { + string* filename = reinterpret_cast(context); + *filename = descriptor.path(); + return true; +} + +TEST(ExceptionHandlerTest, WriteMinidumpForChild) { + int fds[2]; + ASSERT_NE(-1, pipe(fds)); + + const pid_t child = fork(); + if (child == 0) { + close(fds[1]); + char b; + HANDLE_EINTR(read(fds[0], &b, sizeof(b))); + close(fds[0]); + syscall(__NR_exit); + } + close(fds[0]); + + AutoTempDir temp_dir; + string minidump_filename; + ASSERT_TRUE( + ExceptionHandler::WriteMinidumpForChild(child, child, + temp_dir.path(), SimpleCallback, + (void*)&minidump_filename)); + + Minidump minidump(minidump_filename); + ASSERT_TRUE(minidump.Read()); + // Check that the crashing thread is the main thread of |child| + MinidumpException* exception = minidump.GetException(); + ASSERT_TRUE(exception); + u_int32_t thread_id; + ASSERT_TRUE(exception->GetThreadID(&thread_id)); + EXPECT_EQ(child, thread_id); + + const MDRawExceptionStream* raw = exception->exception(); + ASSERT_TRUE(raw); + EXPECT_EQ(MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED, + raw->exception_record.exception_code); + + close(fds[1]); + unlink(minidump_filename.c_str()); +} diff --git a/src/client/linux/minidump_writer/minidump_writer.cc b/src/client/linux/minidump_writer/minidump_writer.cc index b081b298..02055640 100644 --- a/src/client/linux/minidump_writer/minidump_writer.cc +++ b/src/client/linux/minidump_writer/minidump_writer.cc @@ -686,6 +686,7 @@ class MinidumpWriter { // signal handler with the alternative stack, which would be deeply // unhelpful. if (static_cast(thread.thread_id) == GetCrashThread() && + ucontext_ && !dumper_->IsPostMortem()) { const void* stack; size_t stack_len; @@ -776,8 +777,13 @@ class MinidumpWriter { PopSeccompStackFrame(cpu.get(), thread, stack_copy); thread.thread_context = cpu.location(); if (dumper_->threads()[i] == GetCrashThread()) { - assert(dumper_->IsPostMortem()); crashing_thread_context_ = cpu.location(); + if (!dumper_->IsPostMortem()) { + // This is the crashing thread of a live process, but + // no context was provided, so set the crash address + // while the instruction pointer is already here. + dumper_->set_crash_address(GetInstructionPointer(info)); + } } } @@ -1092,6 +1098,10 @@ class MinidumpWriter { uintptr_t GetInstructionPointer() { return ucontext_->uc_mcontext.gregs[REG_EIP]; } + + uintptr_t GetInstructionPointer(const ThreadInfo& info) { + return info.regs.eip; + } #elif defined(__x86_64) uintptr_t GetStackPointer() { return ucontext_->uc_mcontext.gregs[REG_RSP]; @@ -1100,6 +1110,10 @@ class MinidumpWriter { uintptr_t GetInstructionPointer() { return ucontext_->uc_mcontext.gregs[REG_RIP]; } + + uintptr_t GetInstructionPointer(const ThreadInfo& info) { + return info.regs.rip; + } #elif defined(__ARM_EABI__) uintptr_t GetStackPointer() { return ucontext_->uc_mcontext.arm_sp; @@ -1108,6 +1122,10 @@ class MinidumpWriter { uintptr_t GetInstructionPointer() { return ucontext_->uc_mcontext.arm_pc; } + + uintptr_t GetInstructionPointer(const ThreadInfo& info) { + return info.regs.uregs[15]; + } #else #error "This code has not been ported to your platform yet." #endif @@ -1435,6 +1453,19 @@ bool WriteMinidump(int minidump_fd, pid_t crashing_process, MappingList(), AppMemoryList()); } +bool WriteMinidump(const char* minidump_path, pid_t process, + pid_t process_blamed_thread) { + LinuxPtraceDumper dumper(process); + // MinidumpWriter will set crash address + dumper.set_crash_signal(MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED); + dumper.set_crash_thread(process_blamed_thread); + MinidumpWriter writer(minidump_path, -1, NULL, MappingList(), + AppMemoryList(), &dumper); + if (!writer.Init()) + return false; + return writer.Dump(); +} + bool WriteMinidump(const char* minidump_path, pid_t crashing_process, const void* blob, size_t blob_size, const MappingList& mappings, diff --git a/src/client/linux/minidump_writer/minidump_writer.h b/src/client/linux/minidump_writer/minidump_writer.h index 1370d938..28bfbdaf 100644 --- a/src/client/linux/minidump_writer/minidump_writer.h +++ b/src/client/linux/minidump_writer/minidump_writer.h @@ -83,6 +83,14 @@ bool WriteMinidump(const char* minidump_path, pid_t crashing_process, bool WriteMinidump(int minidump_fd, pid_t crashing_process, const void* blob, size_t blob_size); +// Alternate form of WriteMinidump() that works with processes that +// are not expected to have crashed. If |process_blamed_thread| is +// meaningful, it will be the one from which a crash signature is +// extracted. It is not expected that this function will be called +// from a compromised context, but it is safe to do so. +bool WriteMinidump(const char* minidump_path, pid_t process, + pid_t process_blamed_thread); + // These overloads also allow passing a list of known mappings and // a list of additional memory regions to be included in the minidump. bool WriteMinidump(const char* minidump_path, pid_t crashing_process, diff --git a/src/google_breakpad/common/minidump_exception_linux.h b/src/google_breakpad/common/minidump_exception_linux.h index d52c7519..c8c87568 100644 --- a/src/google_breakpad/common/minidump_exception_linux.h +++ b/src/google_breakpad/common/minidump_exception_linux.h @@ -79,7 +79,8 @@ typedef enum { MD_EXCEPTION_CODE_LIN_SIGWINCH = 28, /* Window size change (4.3 BSD, Sun) */ MD_EXCEPTION_CODE_LIN_SIGIO = 29, /* I/O now possible (4.2 BSD) */ MD_EXCEPTION_CODE_LIN_SIGPWR = 30, /* Power failure restart (System V) */ - MD_EXCEPTION_CODE_LIN_SIGSYS = 31 /* Bad system call */ + MD_EXCEPTION_CODE_LIN_SIGSYS = 31, /* Bad system call */ + MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED = -1 /* No exception, dump requested */ } MDExceptionCodeLinux; #endif /* GOOGLE_BREAKPAD_COMMON_MINIDUMP_EXCEPTION_LINUX_H__ */ diff --git a/src/processor/minidump_processor.cc b/src/processor/minidump_processor.cc index 6dbe81ec..0f9d8d75 100644 --- a/src/processor/minidump_processor.cc +++ b/src/processor/minidump_processor.cc @@ -939,6 +939,9 @@ string MinidumpProcessor::GetCrashReason(Minidump *dump, u_int64_t *address) { case MD_EXCEPTION_CODE_LIN_SIGSYS: reason = "SIGSYS"; break; + case MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED: + reason = "DUMP_REQUESTED"; + break; default: BPLOG(INFO) << "Unknown exception reason " << reason; break;