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:
parent
6a5ab68d56
commit
67364c1326
@ -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_;
|
||||||
};
|
};
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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());
|
||||||
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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__ */
|
||||||
|
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user