Fix a couple of bugs where we generate incorrect minidump files on Linux.o
Patch by Markus Gutschke <markus@chromium.org>. R=thestig Review URL: http://breakpad.appspot.com/150001 Review URL: http://breakpad.appspot.com/155001 git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@649 4c0a9323-5329-0410-9bdc-e9ce6186880e
This commit is contained in:
parent
3a69e0e1d1
commit
f5c8f6fb61
4 changed files with 310 additions and 30 deletions
|
@ -72,6 +72,7 @@
|
|||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/signal.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/ucontext.h>
|
||||
|
@ -268,7 +269,10 @@ void ExceptionHandler::SignalHandler(int sig, siginfo_t* info, void* uc) {
|
|||
// crashed. The default action for all the signals which we catch is Core, so
|
||||
// this is the end of us.
|
||||
signal(sig, SIG_DFL);
|
||||
tgkill(getpid(), sys_gettid(), sig);
|
||||
|
||||
// TODO(markus): mask signal and return to caller
|
||||
tgkill(getpid(), syscall(__NR_gettid), sig);
|
||||
_exit(1);
|
||||
|
||||
// not reached.
|
||||
}
|
||||
|
@ -296,7 +300,7 @@ bool ExceptionHandler::HandleSignal(int sig, siginfo_t* info, void* uc) {
|
|||
return false;
|
||||
|
||||
// Allow ourselves to be dumped.
|
||||
sys_prctl(PR_SET_DUMPABLE, 1);
|
||||
prctl(PR_SET_DUMPABLE, 1);
|
||||
CrashContext context;
|
||||
memcpy(&context.siginfo, info, sizeof(siginfo_t));
|
||||
memcpy(&context.context, uc, sizeof(struct ucontext));
|
||||
|
@ -309,7 +313,7 @@ bool ExceptionHandler::HandleSignal(int sig, siginfo_t* info, void* uc) {
|
|||
sizeof(context.float_state));
|
||||
}
|
||||
#endif
|
||||
context.tid = sys_gettid();
|
||||
context.tid = syscall(__NR_gettid);
|
||||
if (crash_handler_ != NULL) {
|
||||
if (crash_handler_(&context, sizeof(context),
|
||||
callback_context_)) {
|
||||
|
|
|
@ -75,6 +75,26 @@ static bool SuspendThread(pid_t pid) {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
#if defined(__i386) || defined(__x86_64)
|
||||
// On x86, the stack pointer is NULL or -1, when executing trusted code in
|
||||
// the seccomp sandbox. Not only does this cause difficulties down the line
|
||||
// when trying to dump the thread's stack, it also results in the minidumps
|
||||
// containing information about the trusted threads. This information is
|
||||
// generally completely meaningless and just pollutes the minidumps.
|
||||
// We thus test the stack pointer and exclude any threads that are part of
|
||||
// the seccomp sandbox's trusted code.
|
||||
user_regs_struct regs;
|
||||
if (sys_ptrace(PTRACE_GETREGS, pid, NULL, ®s) == -1 ||
|
||||
#if defined(__i386)
|
||||
!regs.esp
|
||||
#elif defined(__x86_64)
|
||||
!regs.rsp
|
||||
#endif
|
||||
) {
|
||||
sys_ptrace(PTRACE_DETACH, pid, NULL, NULL);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -111,11 +131,19 @@ bool LinuxDumper::Init() {
|
|||
bool LinuxDumper::ThreadsSuspend() {
|
||||
if (threads_suspended_)
|
||||
return true;
|
||||
bool good = true;
|
||||
for (size_t i = 0; i < threads_.size(); ++i)
|
||||
good &= SuspendThread(threads_[i]);
|
||||
for (size_t i = 0; i < threads_.size(); ++i) {
|
||||
if (!SuspendThread(threads_[i])) {
|
||||
// If the thread either disappeared before we could attach to it, or if
|
||||
// it was part of the seccomp sandbox's trusted code, it is OK to
|
||||
// silently drop it from the minidump.
|
||||
memmove(&threads_[i], &threads_[i+1],
|
||||
(threads_.size() - i - 1) * sizeof(threads_[i]));
|
||||
threads_.resize(threads_.size() - 1);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
threads_suspended_ = true;
|
||||
return good;
|
||||
return threads_.size() > 0;
|
||||
}
|
||||
|
||||
bool LinuxDumper::ThreadsResume() {
|
||||
|
|
|
@ -48,6 +48,7 @@
|
|||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <link.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/ucontext.h>
|
||||
|
@ -62,20 +63,10 @@
|
|||
#include "client/linux/handler/exception_handler.h"
|
||||
#include "client/linux/minidump_writer/line_reader.h"
|
||||
#include "client/linux/minidump_writer/linux_dumper.h"
|
||||
#include "client/linux/minidump_writer/minidump_extension_linux.h"
|
||||
#include "common/linux/linux_libc_support.h"
|
||||
#include "common/linux/linux_syscall_support.h"
|
||||
|
||||
// These are additional minidump stream values which are specific to the linux
|
||||
// breakpad implementation.
|
||||
enum {
|
||||
MD_LINUX_CPU_INFO = 0x47670003, /* /proc/cpuinfo */
|
||||
MD_LINUX_PROC_STATUS = 0x47670004, /* /proc/$x/status */
|
||||
MD_LINUX_LSB_RELEASE = 0x47670005, /* /etc/lsb-release */
|
||||
MD_LINUX_CMD_LINE = 0x47670006, /* /proc/$x/cmdline */
|
||||
MD_LINUX_ENVIRON = 0x47670007, /* /proc/$x/environ */
|
||||
MD_LINUX_AUXV = 0x47670008 /* /proc/$x/auxv */
|
||||
};
|
||||
|
||||
// Minidump defines register structures which are different from the raw
|
||||
// structures which we get from the kernel. These are platform specific
|
||||
// functions to juggle the ucontext and user structures into minidump format.
|
||||
|
@ -390,9 +381,26 @@ class MinidumpWriter {
|
|||
}
|
||||
|
||||
bool Dump() {
|
||||
// The dynamic linker makes information available that helps gdb find all
|
||||
// DSOs loaded into the program. If we can access this information, we dump
|
||||
// it to a MD_LINUX_DSO_DEBUG stream.
|
||||
struct r_debug* r_debug = NULL;
|
||||
uint32_t dynamic_length = 0;
|
||||
|
||||
for (int i = 0;;) {
|
||||
ElfW(Dyn) dyn;
|
||||
dynamic_length += sizeof(dyn);
|
||||
dumper_.CopyFromProcess(&dyn, crashing_tid_, _DYNAMIC+i++, sizeof(dyn));
|
||||
if (dyn.d_tag == DT_DEBUG) {
|
||||
r_debug = (struct r_debug*)dyn.d_un.d_ptr;
|
||||
continue;
|
||||
} else if (dyn.d_tag == DT_NULL)
|
||||
break;
|
||||
}
|
||||
|
||||
// A minidump file contains a number of tagged streams. This is the number
|
||||
// of stream which we write.
|
||||
static const unsigned kNumWriters = 11;
|
||||
const unsigned kNumWriters = 11 + !!r_debug;
|
||||
|
||||
TypedMDRVA<MDRawHeader> header(&minidump_writer_);
|
||||
TypedMDRVA<MDRawDirectory> dir(&minidump_writer_);
|
||||
|
@ -457,11 +465,18 @@ class MinidumpWriter {
|
|||
NullifyDirectoryEntry(&dirent);
|
||||
dir.CopyIndex(dir_index++, &dirent);
|
||||
|
||||
dirent.stream_type = MD_LINUX_AUXV;
|
||||
dirent.stream_type = MD_LINUX_MAPS;
|
||||
if (!WriteProcFile(&dirent.location, crashing_tid_, "maps"))
|
||||
NullifyDirectoryEntry(&dirent);
|
||||
dir.CopyIndex(dir_index++, &dirent);
|
||||
|
||||
if (r_debug) {
|
||||
dirent.stream_type = MD_LINUX_DSO_DEBUG;
|
||||
if (!WriteDSODebugStream(&dirent, r_debug, dynamic_length))
|
||||
NullifyDirectoryEntry(&dirent);
|
||||
dir.CopyIndex(dir_index++, &dirent);
|
||||
}
|
||||
|
||||
// If you add more directory entries, don't forget to update kNumWriters,
|
||||
// above.
|
||||
|
||||
|
@ -469,6 +484,123 @@ class MinidumpWriter {
|
|||
return true;
|
||||
}
|
||||
|
||||
// Check if the top of the stack is part of a system call that has been
|
||||
// redirected by the seccomp sandbox. If so, try to pop the stack frames
|
||||
// all the way back to the point where the interception happened.
|
||||
void PopSeccompStackFrame(RawContextCPU* cpu, const MDRawThread& thread,
|
||||
uint8_t* stack_copy) {
|
||||
#if defined(__x86_64)
|
||||
u_int64_t bp = cpu->rbp;
|
||||
u_int64_t top = thread.stack.start_of_memory_range;
|
||||
for (int i = 4; i--; ) {
|
||||
if (bp < top ||
|
||||
bp + sizeof(bp) > thread.stack.start_of_memory_range +
|
||||
thread.stack.memory.data_size ||
|
||||
bp & 1) {
|
||||
break;
|
||||
}
|
||||
uint64_t old_top = top;
|
||||
top = bp;
|
||||
u_int8_t* bp_addr = stack_copy + bp - thread.stack.start_of_memory_range;
|
||||
memcpy(&bp, bp_addr, sizeof(bp));
|
||||
if (bp == 0xDEADBEEFDEADBEEFull) {
|
||||
struct {
|
||||
uint64_t r15;
|
||||
uint64_t r14;
|
||||
uint64_t r13;
|
||||
uint64_t r12;
|
||||
uint64_t r11;
|
||||
uint64_t r10;
|
||||
uint64_t r9;
|
||||
uint64_t r8;
|
||||
uint64_t rdi;
|
||||
uint64_t rsi;
|
||||
uint64_t rdx;
|
||||
uint64_t rcx;
|
||||
uint64_t rbx;
|
||||
uint64_t deadbeef;
|
||||
uint64_t rbp;
|
||||
uint64_t fakeret;
|
||||
uint64_t ret;
|
||||
/* char redzone[128]; */
|
||||
} seccomp_stackframe;
|
||||
if (top - offsetof(typeof(seccomp_stackframe), deadbeef) < old_top ||
|
||||
top - offsetof(typeof(seccomp_stackframe), deadbeef) +
|
||||
sizeof(seccomp_stackframe) >
|
||||
thread.stack.start_of_memory_range+thread.stack.memory.data_size) {
|
||||
break;
|
||||
}
|
||||
memcpy(&seccomp_stackframe,
|
||||
bp_addr - offsetof(typeof(seccomp_stackframe), deadbeef),
|
||||
sizeof(seccomp_stackframe));
|
||||
cpu->rbx = seccomp_stackframe.rbx;
|
||||
cpu->rcx = seccomp_stackframe.rcx;
|
||||
cpu->rdx = seccomp_stackframe.rdx;
|
||||
cpu->rsi = seccomp_stackframe.rsi;
|
||||
cpu->rdi = seccomp_stackframe.rdi;
|
||||
cpu->rbp = seccomp_stackframe.rbp;
|
||||
cpu->rsp = top + 4*sizeof(uint64_t) + 128;
|
||||
cpu->r8 = seccomp_stackframe.r8;
|
||||
cpu->r9 = seccomp_stackframe.r9;
|
||||
cpu->r10 = seccomp_stackframe.r10;
|
||||
cpu->r11 = seccomp_stackframe.r11;
|
||||
cpu->r12 = seccomp_stackframe.r12;
|
||||
cpu->r13 = seccomp_stackframe.r13;
|
||||
cpu->r14 = seccomp_stackframe.r14;
|
||||
cpu->r15 = seccomp_stackframe.r15;
|
||||
cpu->rip = seccomp_stackframe.fakeret;
|
||||
return;
|
||||
}
|
||||
}
|
||||
#elif defined(__i386)
|
||||
u_int32_t bp = cpu->ebp;
|
||||
u_int32_t top = thread.stack.start_of_memory_range;
|
||||
for (int i = 4; i--; ) {
|
||||
if (bp < top ||
|
||||
bp + sizeof(bp) > thread.stack.start_of_memory_range +
|
||||
thread.stack.memory.data_size ||
|
||||
bp & 1) {
|
||||
break;
|
||||
}
|
||||
uint32_t old_top = top;
|
||||
top = bp;
|
||||
u_int8_t* bp_addr = stack_copy + bp - thread.stack.start_of_memory_range;
|
||||
memcpy(&bp, bp_addr, sizeof(bp));
|
||||
if (bp == 0xDEADBEEFu) {
|
||||
struct {
|
||||
uint32_t edi;
|
||||
uint32_t esi;
|
||||
uint32_t edx;
|
||||
uint32_t ecx;
|
||||
uint32_t ebx;
|
||||
uint32_t deadbeef;
|
||||
uint32_t ebp;
|
||||
uint32_t fakeret;
|
||||
uint32_t ret;
|
||||
} seccomp_stackframe;
|
||||
if (top - offsetof(typeof(seccomp_stackframe), deadbeef) < old_top ||
|
||||
top - offsetof(typeof(seccomp_stackframe), deadbeef) +
|
||||
sizeof(seccomp_stackframe) >
|
||||
thread.stack.start_of_memory_range+thread.stack.memory.data_size) {
|
||||
break;
|
||||
}
|
||||
memcpy(&seccomp_stackframe,
|
||||
bp_addr - offsetof(typeof(seccomp_stackframe), deadbeef),
|
||||
sizeof(seccomp_stackframe));
|
||||
cpu->ebx = seccomp_stackframe.ebx;
|
||||
cpu->ecx = seccomp_stackframe.ecx;
|
||||
cpu->edx = seccomp_stackframe.edx;
|
||||
cpu->esi = seccomp_stackframe.esi;
|
||||
cpu->edi = seccomp_stackframe.edi;
|
||||
cpu->ebp = seccomp_stackframe.ebp;
|
||||
cpu->esp = top + 4*sizeof(void*);
|
||||
cpu->eip = seccomp_stackframe.fakeret;
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Write information about the threads.
|
||||
bool WriteThreadListStream(MDRawDirectory* dirent) {
|
||||
const unsigned num_threads = dumper_.threads().size();
|
||||
|
@ -508,6 +640,7 @@ class MinidumpWriter {
|
|||
return false;
|
||||
my_memset(cpu.get(), 0, sizeof(RawContextCPU));
|
||||
CPUFillFromUContext(cpu.get(), ucontext_, float_state_);
|
||||
PopSeccompStackFrame(cpu.get(), thread, stack_copy);
|
||||
thread.thread_context = cpu.location();
|
||||
crashing_thread_context_ = cpu.location();
|
||||
} else {
|
||||
|
@ -529,6 +662,7 @@ class MinidumpWriter {
|
|||
return false;
|
||||
my_memset(cpu.get(), 0, sizeof(RawContextCPU));
|
||||
CPUFillFromThreadInfo(cpu.get(), info);
|
||||
PopSeccompStackFrame(cpu.get(), thread, stack_copy);
|
||||
thread.thread_context = cpu.location();
|
||||
}
|
||||
|
||||
|
@ -656,6 +790,83 @@ class MinidumpWriter {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool WriteDSODebugStream(MDRawDirectory* dirent, struct r_debug* r_debug,
|
||||
uint32_t dynamic_length) {
|
||||
// The caller provided us with a pointer to "struct r_debug". We can
|
||||
// look up the "r_map" field to get a linked list of all loaded DSOs.
|
||||
// Our list of DSOs potentially is different from the ones in the crashing
|
||||
// process. So, we have to be careful to never dereference pointers
|
||||
// directly. Instead, we use CopyFromProcess() everywhere.
|
||||
// See <link.h> for a more detailed discussion of the how the dynamic
|
||||
// loader communicates with debuggers.
|
||||
|
||||
// Count the number of loaded DSOs
|
||||
int dso_count = 0;
|
||||
struct r_debug debug_entry;
|
||||
dumper_.CopyFromProcess(&debug_entry, crashing_tid_, r_debug,
|
||||
sizeof(debug_entry));
|
||||
for (struct link_map* ptr = debug_entry.r_map; ptr; ) {
|
||||
struct link_map map;
|
||||
dumper_.CopyFromProcess(&map, crashing_tid_, ptr, sizeof(map));
|
||||
ptr = map.l_next;
|
||||
dso_count++;
|
||||
}
|
||||
|
||||
MDRVA linkmap_rva = minidump_writer_.kInvalidMDRVA;
|
||||
if (dso_count > 0) {
|
||||
// If we have at least one DSO, create an array of MDRawLinkMap
|
||||
// entries in the minidump file.
|
||||
TypedMDRVA<MDRawLinkMap> linkmap(&minidump_writer_);
|
||||
if (!linkmap.AllocateArray(dso_count))
|
||||
return false;
|
||||
linkmap_rva = linkmap.location().rva;
|
||||
int idx = 0;
|
||||
|
||||
// Iterate over DSOs and write their information to mini dump
|
||||
for (struct link_map* ptr = debug_entry.r_map; ptr; ) {
|
||||
struct link_map map;
|
||||
dumper_.CopyFromProcess(&map, crashing_tid_, ptr, sizeof(map));
|
||||
ptr = map.l_next;
|
||||
char filename[257] = { 0 };
|
||||
if (map.l_name) {
|
||||
dumper_.CopyFromProcess(filename, crashing_tid_, map.l_name,
|
||||
sizeof(filename) - 1);
|
||||
}
|
||||
MDLocationDescriptor location;
|
||||
if (!minidump_writer_.WriteString(filename, 0, &location))
|
||||
return false;
|
||||
MDRawLinkMap entry;
|
||||
entry.name = location.rva;
|
||||
entry.addr = (void*)map.l_addr;
|
||||
entry.ld = (void*)map.l_ld;
|
||||
linkmap.CopyIndex(idx++, &entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Write MD_LINUX_DSO_DEBUG record
|
||||
TypedMDRVA<MDRawDebug> debug(&minidump_writer_);
|
||||
if (!debug.AllocateObjectAndArray(1, dynamic_length))
|
||||
return false;
|
||||
my_memset(debug.get(), 0, sizeof(MDRawDebug));
|
||||
dirent->stream_type = MD_LINUX_DSO_DEBUG;
|
||||
dirent->location = debug.location();
|
||||
|
||||
debug.get()->version = debug_entry.r_version;
|
||||
debug.get()->map = linkmap_rva;
|
||||
debug.get()->dso_count = dso_count;
|
||||
debug.get()->brk = (void*)debug_entry.r_brk;
|
||||
debug.get()->ldbase = (void*)debug_entry.r_ldbase;
|
||||
debug.get()->dynamic = (void*)&_DYNAMIC;
|
||||
|
||||
char *dso_debug_data = new char[dynamic_length];
|
||||
dumper_.CopyFromProcess(dso_debug_data, crashing_tid_, &_DYNAMIC,
|
||||
dynamic_length);
|
||||
debug.CopyIndexAfterObject(0, dso_debug_data, dynamic_length);
|
||||
delete[] dso_debug_data;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
#if defined(__i386)
|
||||
uintptr_t GetStackPointer() {
|
||||
|
@ -721,7 +932,7 @@ class MinidumpWriter {
|
|||
i < sizeof(cpu_info_table) / sizeof(cpu_info_table[0]);
|
||||
i++) {
|
||||
CpuInfoEntry* entry = &cpu_info_table[i];
|
||||
if (entry->found)
|
||||
if (entry->found && i)
|
||||
continue;
|
||||
if (!strncmp(line, entry->info_name, strlen(entry->info_name))) {
|
||||
const char* value = strchr(line, ':');
|
||||
|
@ -807,29 +1018,48 @@ popline:
|
|||
// We can't stat the files because several of the files that we want to
|
||||
// read are kernel seqfiles, which always have a length of zero. So we have
|
||||
// to read as much as we can into a buffer.
|
||||
static const unsigned kMaxFileSize = 1024;
|
||||
uint8_t* data = (uint8_t*) dumper_.allocator()->Alloc(kMaxFileSize);
|
||||
static const unsigned kBufSize = 1024 - 2*sizeof(void*);
|
||||
struct Buffers {
|
||||
struct Buffers* next;
|
||||
size_t len;
|
||||
uint8_t data[kBufSize];
|
||||
} *buffers =
|
||||
(struct Buffers*) dumper_.allocator()->Alloc(sizeof(struct Buffers));
|
||||
buffers->next = NULL;
|
||||
buffers->len = 0;
|
||||
|
||||
size_t done = 0;
|
||||
while (done < kMaxFileSize) {
|
||||
size_t total = 0;
|
||||
for (struct Buffers* bufptr = buffers;;) {
|
||||
ssize_t r;
|
||||
do {
|
||||
r = sys_read(fd, data + done, kMaxFileSize - done);
|
||||
r = sys_read(fd, &bufptr->data[bufptr->len], kBufSize - bufptr->len);
|
||||
} while (r == -1 && errno == EINTR);
|
||||
|
||||
if (r < 1)
|
||||
break;
|
||||
done += r;
|
||||
|
||||
total += r;
|
||||
bufptr->len += r;
|
||||
if (bufptr->len == kBufSize) {
|
||||
bufptr->next =
|
||||
(struct Buffers*) dumper_.allocator()->Alloc(sizeof(struct Buffers));
|
||||
bufptr = bufptr->next;
|
||||
bufptr->next = NULL;
|
||||
bufptr->len = 0;
|
||||
}
|
||||
}
|
||||
sys_close(fd);
|
||||
|
||||
if (!done)
|
||||
if (!total)
|
||||
return false;
|
||||
|
||||
UntypedMDRVA memory(&minidump_writer_);
|
||||
if (!memory.Allocate(done))
|
||||
if (!memory.Allocate(total))
|
||||
return false;
|
||||
memory.Copy(data, done);
|
||||
for (MDRVA pos = memory.position(); buffers; buffers = buffers->next) {
|
||||
memory.Copy(pos, &buffers->data, buffers->len);
|
||||
pos += buffers->len;
|
||||
}
|
||||
*result = memory.location();
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -148,6 +148,24 @@ class wasteful_vector {
|
|||
return used_;
|
||||
}
|
||||
|
||||
void resize(unsigned sz, T c = T()) {
|
||||
// No need to test "sz >= 0", as "sz" is unsigned.
|
||||
if (sz <= used_) {
|
||||
used_ = sz;
|
||||
} else {
|
||||
unsigned a = allocated_;
|
||||
if (sz > a) {
|
||||
while (sz > a) {
|
||||
a *= 2;
|
||||
}
|
||||
Realloc(a);
|
||||
}
|
||||
while (sz > used_) {
|
||||
a_[used_++] = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
T& operator[](size_t index) {
|
||||
return a_[index];
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue