From f5c8f6fb61ce609452361ee9b61a343570f62d12 Mon Sep 17 00:00:00 2001 From: "thestig@chromium.org" Date: Sat, 14 Aug 2010 01:41:39 +0000 Subject: Fix a couple of bugs where we generate incorrect minidump files on Linux.o Patch by Markus Gutschke . 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 --- src/client/linux/handler/exception_handler.cc | 10 +- src/client/linux/minidump_writer/linux_dumper.cc | 36 ++- .../linux/minidump_writer/minidump_writer.cc | 278 +++++++++++++++++++-- src/common/linux/memory.h | 18 ++ 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 #include #include +#include #include #include #include @@ -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, ®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() { 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 #include +#include #include #include #include @@ -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 header(&minidump_writer_); TypedMDRVA 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 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 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 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]; } -- cgit v1.2.1