Allow generating minidumps from live process on Linux via ExceptionHandler

Original patch by Chris Jones <jones.chris.g@gmail.com> at https://bugzilla.mozilla.org/show_bug.cgi?id=544936 and https://bugzilla.mozilla.org/show_bug.cgi?id=555309
R=mark at https://breakpad.appspot.com/449003/

git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@1043 4c0a9323-5329-0410-9bdc-e9ce6186880e
This commit is contained in:
ted.mielczarek@gmail.com 2012-09-18 18:51:56 +00:00
parent 6a5ab68d56
commit 67364c1326
9 changed files with 173 additions and 8 deletions

View file

@ -34,7 +34,16 @@ namespace google_breakpad {
class CrashGenerationServer; 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_; CrashGenerationServer* crash_server_;
pid_t pid_; pid_t pid_;
}; };

View file

@ -396,10 +396,7 @@ CrashGenerationServer::ClientEvent(short revents)
} }
if (dump_callback_) { if (dump_callback_) {
ClientInfo info; ClientInfo info(crashing_pid, this);
info.crash_server_ = this;
info.pid_ = crashing_pid;
dump_callback_(dump_context_, &info, &minidump_filename); dump_callback_(dump_context_, &info, &minidump_filename);
} }

View file

@ -529,7 +529,7 @@ bool ExceptionHandler::WriteMinidump() {
static_cast<void>(ftruncate(minidump_descriptor_.fd(), 0)); static_cast<void>(ftruncate(minidump_descriptor_.fd(), 0));
} }
// Allow ourselves to be dumped. // Allow this process to be dumped.
sys_prctl(PR_SET_DUMPABLE, 1); sys_prctl(PR_SET_DUMPABLE, 1);
CrashContext context; CrashContext context;
@ -543,6 +543,22 @@ bool ExceptionHandler::WriteMinidump() {
#endif #endif
context.tid = sys_gettid(); 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<void*>(context.context.uc_mcontext.gregs[REG_EIP]);
#elif defined(__x86_64__)
context.siginfo.si_addr =
reinterpret_cast<void*>(context.context.uc_mcontext.gregs[REG_RIP]);
#elif defined(__arm__)
context.siginfo.si_addr =
reinterpret_cast<void*>(context.context.uc_mcontext.arm_pc);
#else
#error "This code has not been ported to your platform yet."
#endif
return GenerateDump(&context); 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 } // namespace google_breakpad

View file

@ -167,6 +167,23 @@ class ExceptionHandler {
MinidumpCallback callback, MinidumpCallback callback,
void* callback_context); 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 // This structure is passed to minidump_writer.h:WriteMinidump via an opaque
// blob. It shouldn't be needed in any user code. // blob. It shouldn't be needed in any user code.
struct CrashContext { struct CrashContext {

View file

@ -895,6 +895,25 @@ TEST(ExceptionHandlerTest, ExternalDumper) {
unlink(templ.c_str()); 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) { TEST(ExceptionHandlerTest, GenerateMultipleDumpsWithFD) {
AutoTempDir temp_dir; AutoTempDir temp_dir;
std::string path; std::string path;
@ -1021,3 +1040,50 @@ TEST(ExceptionHandlerTest, AdditionalMemoryRemove) {
delete[] memory; delete[] memory;
} }
static bool SimpleCallback(const MinidumpDescriptor& descriptor,
void* context,
bool succeeded) {
string* filename = reinterpret_cast<string*>(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());
}

View file

@ -686,6 +686,7 @@ class MinidumpWriter {
// signal handler with the alternative stack, which would be deeply // signal handler with the alternative stack, which would be deeply
// unhelpful. // unhelpful.
if (static_cast<pid_t>(thread.thread_id) == GetCrashThread() && if (static_cast<pid_t>(thread.thread_id) == GetCrashThread() &&
ucontext_ &&
!dumper_->IsPostMortem()) { !dumper_->IsPostMortem()) {
const void* stack; const void* stack;
size_t stack_len; size_t stack_len;
@ -776,8 +777,13 @@ class MinidumpWriter {
PopSeccompStackFrame(cpu.get(), thread, stack_copy); PopSeccompStackFrame(cpu.get(), thread, stack_copy);
thread.thread_context = cpu.location(); thread.thread_context = cpu.location();
if (dumper_->threads()[i] == GetCrashThread()) { if (dumper_->threads()[i] == GetCrashThread()) {
assert(dumper_->IsPostMortem());
crashing_thread_context_ = cpu.location(); 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() { uintptr_t GetInstructionPointer() {
return ucontext_->uc_mcontext.gregs[REG_EIP]; return ucontext_->uc_mcontext.gregs[REG_EIP];
} }
uintptr_t GetInstructionPointer(const ThreadInfo& info) {
return info.regs.eip;
}
#elif defined(__x86_64) #elif defined(__x86_64)
uintptr_t GetStackPointer() { uintptr_t GetStackPointer() {
return ucontext_->uc_mcontext.gregs[REG_RSP]; return ucontext_->uc_mcontext.gregs[REG_RSP];
@ -1100,6 +1110,10 @@ class MinidumpWriter {
uintptr_t GetInstructionPointer() { uintptr_t GetInstructionPointer() {
return ucontext_->uc_mcontext.gregs[REG_RIP]; return ucontext_->uc_mcontext.gregs[REG_RIP];
} }
uintptr_t GetInstructionPointer(const ThreadInfo& info) {
return info.regs.rip;
}
#elif defined(__ARM_EABI__) #elif defined(__ARM_EABI__)
uintptr_t GetStackPointer() { uintptr_t GetStackPointer() {
return ucontext_->uc_mcontext.arm_sp; return ucontext_->uc_mcontext.arm_sp;
@ -1108,6 +1122,10 @@ class MinidumpWriter {
uintptr_t GetInstructionPointer() { uintptr_t GetInstructionPointer() {
return ucontext_->uc_mcontext.arm_pc; return ucontext_->uc_mcontext.arm_pc;
} }
uintptr_t GetInstructionPointer(const ThreadInfo& info) {
return info.regs.uregs[15];
}
#else #else
#error "This code has not been ported to your platform yet." #error "This code has not been ported to your platform yet."
#endif #endif
@ -1435,6 +1453,19 @@ bool WriteMinidump(int minidump_fd, pid_t crashing_process,
MappingList(), AppMemoryList()); 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, bool WriteMinidump(const char* minidump_path, pid_t crashing_process,
const void* blob, size_t blob_size, const void* blob, size_t blob_size,
const MappingList& mappings, const MappingList& mappings,

View file

@ -83,6 +83,14 @@ bool WriteMinidump(const char* minidump_path, pid_t crashing_process,
bool WriteMinidump(int minidump_fd, pid_t crashing_process, bool WriteMinidump(int minidump_fd, pid_t crashing_process,
const void* blob, size_t blob_size); 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 // These overloads also allow passing a list of known mappings and
// a list of additional memory regions to be included in the minidump. // a list of additional memory regions to be included in the minidump.
bool WriteMinidump(const char* minidump_path, pid_t crashing_process, bool WriteMinidump(const char* minidump_path, pid_t crashing_process,

View file

@ -79,7 +79,8 @@ typedef enum {
MD_EXCEPTION_CODE_LIN_SIGWINCH = 28, /* Window size change (4.3 BSD, Sun) */ 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_SIGIO = 29, /* I/O now possible (4.2 BSD) */
MD_EXCEPTION_CODE_LIN_SIGPWR = 30, /* Power failure restart (System V) */ 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; } MDExceptionCodeLinux;
#endif /* GOOGLE_BREAKPAD_COMMON_MINIDUMP_EXCEPTION_LINUX_H__ */ #endif /* GOOGLE_BREAKPAD_COMMON_MINIDUMP_EXCEPTION_LINUX_H__ */

View file

@ -939,6 +939,9 @@ string MinidumpProcessor::GetCrashReason(Minidump *dump, u_int64_t *address) {
case MD_EXCEPTION_CODE_LIN_SIGSYS: case MD_EXCEPTION_CODE_LIN_SIGSYS:
reason = "SIGSYS"; reason = "SIGSYS";
break; break;
case MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED:
reason = "DUMP_REQUESTED";
break;
default: default:
BPLOG(INFO) << "Unknown exception reason " << reason; BPLOG(INFO) << "Unknown exception reason " << reason;
break; break;