Index: src/client/linux/minidump_writer/minidump_writer.cc |
=================================================================== |
--- src/client/linux/minidump_writer/minidump_writer.cc (revision 648) |
+++ src/client/linux/minidump_writer/minidump_writer.cc (working copy) |
@@ -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 @@ |
} |
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 @@ |
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 @@ |
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 @@ |
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 @@ |
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 @@ |
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 @@ |
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 @@ |
// 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; |
} |