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
9 changed files with 173 additions and 8 deletions
|
@ -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_;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -529,7 +529,7 @@ bool ExceptionHandler::WriteMinidump() {
|
|||
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);
|
||||
|
||||
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<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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<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
|
||||
// unhelpful.
|
||||
if (static_cast<pid_t>(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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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__ */
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue