aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorthestig@chromium.org <thestig@chromium.org@4c0a9323-5329-0410-9bdc-e9ce6186880e>2010-08-14 01:41:39 +0000
committerthestig@chromium.org <thestig@chromium.org@4c0a9323-5329-0410-9bdc-e9ce6186880e>2010-08-14 01:41:39 +0000
commitf5c8f6fb61ce609452361ee9b61a343570f62d12 (patch)
tree65726ef8d0093bde007ca244fc6ee349b6f23d83 /src
parentMiscellaneous improvements to minidump-2-core. (diff)
downloadbreakpad-f5c8f6fb61ce609452361ee9b61a343570f62d12.tar.xz
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
Diffstat (limited to 'src')
-rw-r--r--src/client/linux/handler/exception_handler.cc10
-rw-r--r--src/client/linux/minidump_writer/linux_dumper.cc36
-rw-r--r--src/client/linux/minidump_writer/minidump_writer.cc278
-rw-r--r--src/common/linux/memory.h18
4 files changed, 311 insertions, 31 deletions
diff --git a/src/client/linux/handler/exception_handler.cc b/src/client/linux/handler/exception_handler.cc
index 038612f2..6e58ff2c 100644
--- a/src/client/linux/handler/exception_handler.cc
+++ b/src/client/linux/handler/exception_handler.cc
@@ -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_)) {
diff --git a/src/client/linux/minidump_writer/linux_dumper.cc b/src/client/linux/minidump_writer/linux_dumper.cc
index c693e907..af479c8d 100644
--- a/src/client/linux/minidump_writer/linux_dumper.cc
+++ b/src/client/linux/minidump_writer/linux_dumper.cc
@@ -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, &regs) == -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() {
diff --git a/src/client/linux/minidump_writer/minidump_writer.cc b/src/client/linux/minidump_writer/minidump_writer.cc
index 1ee90981..a7d44c9f 100644
--- a/src/client/linux/minidump_writer/minidump_writer.cc
+++ b/src/client/linux/minidump_writer/minidump_writer.cc
@@ -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);
-
- size_t done = 0;
- while (done < 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 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;
}
diff --git a/src/common/linux/memory.h b/src/common/linux/memory.h
index f10a194b..725ed8b3 100644
--- a/src/common/linux/memory.h
+++ b/src/common/linux/memory.h
@@ -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];
}