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:
thestig@chromium.org 2010-08-14 01:41:39 +00:00
parent 3a69e0e1d1
commit f5c8f6fb61
4 changed files with 310 additions and 30 deletions

View file

@ -72,6 +72,7 @@
#include <signal.h> #include <signal.h>
#include <stdio.h> #include <stdio.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/signal.h> #include <sys/signal.h>
#include <sys/syscall.h> #include <sys/syscall.h>
#include <sys/ucontext.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 // crashed. The default action for all the signals which we catch is Core, so
// this is the end of us. // this is the end of us.
signal(sig, SIG_DFL); 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. // not reached.
} }
@ -296,7 +300,7 @@ bool ExceptionHandler::HandleSignal(int sig, siginfo_t* info, void* uc) {
return false; return false;
// Allow ourselves to be dumped. // Allow ourselves to be dumped.
sys_prctl(PR_SET_DUMPABLE, 1); prctl(PR_SET_DUMPABLE, 1);
CrashContext context; CrashContext context;
memcpy(&context.siginfo, info, sizeof(siginfo_t)); memcpy(&context.siginfo, info, sizeof(siginfo_t));
memcpy(&context.context, uc, sizeof(struct ucontext)); 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)); sizeof(context.float_state));
} }
#endif #endif
context.tid = sys_gettid(); context.tid = syscall(__NR_gettid);
if (crash_handler_ != NULL) { if (crash_handler_ != NULL) {
if (crash_handler_(&context, sizeof(context), if (crash_handler_(&context, sizeof(context),
callback_context_)) { callback_context_)) {

View file

@ -75,6 +75,26 @@ static bool SuspendThread(pid_t pid) {
return false; 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; return true;
} }
@ -111,11 +131,19 @@ bool LinuxDumper::Init() {
bool LinuxDumper::ThreadsSuspend() { bool LinuxDumper::ThreadsSuspend() {
if (threads_suspended_) if (threads_suspended_)
return true; return true;
bool good = true; for (size_t i = 0; i < threads_.size(); ++i) {
for (size_t i = 0; i < threads_.size(); ++i) if (!SuspendThread(threads_[i])) {
good &= 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; threads_suspended_ = true;
return good; return threads_.size() > 0;
} }
bool LinuxDumper::ThreadsResume() { bool LinuxDumper::ThreadsResume() {

View file

@ -48,6 +48,7 @@
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <link.h>
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
#include <sys/ucontext.h> #include <sys/ucontext.h>
@ -62,20 +63,10 @@
#include "client/linux/handler/exception_handler.h" #include "client/linux/handler/exception_handler.h"
#include "client/linux/minidump_writer/line_reader.h" #include "client/linux/minidump_writer/line_reader.h"
#include "client/linux/minidump_writer/linux_dumper.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_libc_support.h"
#include "common/linux/linux_syscall_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 // Minidump defines register structures which are different from the raw
// structures which we get from the kernel. These are platform specific // structures which we get from the kernel. These are platform specific
// functions to juggle the ucontext and user structures into minidump format. // functions to juggle the ucontext and user structures into minidump format.
@ -390,9 +381,26 @@ class MinidumpWriter {
} }
bool Dump() { 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 // A minidump file contains a number of tagged streams. This is the number
// of stream which we write. // of stream which we write.
static const unsigned kNumWriters = 11; const unsigned kNumWriters = 11 + !!r_debug;
TypedMDRVA<MDRawHeader> header(&minidump_writer_); TypedMDRVA<MDRawHeader> header(&minidump_writer_);
TypedMDRVA<MDRawDirectory> dir(&minidump_writer_); TypedMDRVA<MDRawDirectory> dir(&minidump_writer_);
@ -457,11 +465,18 @@ class MinidumpWriter {
NullifyDirectoryEntry(&dirent); NullifyDirectoryEntry(&dirent);
dir.CopyIndex(dir_index++, &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")) if (!WriteProcFile(&dirent.location, crashing_tid_, "maps"))
NullifyDirectoryEntry(&dirent); NullifyDirectoryEntry(&dirent);
dir.CopyIndex(dir_index++, &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, // If you add more directory entries, don't forget to update kNumWriters,
// above. // above.
@ -469,6 +484,123 @@ class MinidumpWriter {
return true; 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. // Write information about the threads.
bool WriteThreadListStream(MDRawDirectory* dirent) { bool WriteThreadListStream(MDRawDirectory* dirent) {
const unsigned num_threads = dumper_.threads().size(); const unsigned num_threads = dumper_.threads().size();
@ -508,6 +640,7 @@ class MinidumpWriter {
return false; return false;
my_memset(cpu.get(), 0, sizeof(RawContextCPU)); my_memset(cpu.get(), 0, sizeof(RawContextCPU));
CPUFillFromUContext(cpu.get(), ucontext_, float_state_); CPUFillFromUContext(cpu.get(), ucontext_, float_state_);
PopSeccompStackFrame(cpu.get(), thread, stack_copy);
thread.thread_context = cpu.location(); thread.thread_context = cpu.location();
crashing_thread_context_ = cpu.location(); crashing_thread_context_ = cpu.location();
} else { } else {
@ -529,6 +662,7 @@ class MinidumpWriter {
return false; return false;
my_memset(cpu.get(), 0, sizeof(RawContextCPU)); my_memset(cpu.get(), 0, sizeof(RawContextCPU));
CPUFillFromThreadInfo(cpu.get(), info); CPUFillFromThreadInfo(cpu.get(), info);
PopSeccompStackFrame(cpu.get(), thread, stack_copy);
thread.thread_context = cpu.location(); thread.thread_context = cpu.location();
} }
@ -656,6 +790,83 @@ class MinidumpWriter {
return true; 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: private:
#if defined(__i386) #if defined(__i386)
uintptr_t GetStackPointer() { uintptr_t GetStackPointer() {
@ -721,7 +932,7 @@ class MinidumpWriter {
i < sizeof(cpu_info_table) / sizeof(cpu_info_table[0]); i < sizeof(cpu_info_table) / sizeof(cpu_info_table[0]);
i++) { i++) {
CpuInfoEntry* entry = &cpu_info_table[i]; CpuInfoEntry* entry = &cpu_info_table[i];
if (entry->found) if (entry->found && i)
continue; continue;
if (!strncmp(line, entry->info_name, strlen(entry->info_name))) { if (!strncmp(line, entry->info_name, strlen(entry->info_name))) {
const char* value = strchr(line, ':'); 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 // 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 // read are kernel seqfiles, which always have a length of zero. So we have
// to read as much as we can into a buffer. // to read as much as we can into a buffer.
static const unsigned kMaxFileSize = 1024; static const unsigned kBufSize = 1024 - 2*sizeof(void*);
uint8_t* data = (uint8_t*) dumper_.allocator()->Alloc(kMaxFileSize); 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; size_t total = 0;
while (done < kMaxFileSize) { for (struct Buffers* bufptr = buffers;;) {
ssize_t r; ssize_t r;
do { 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); } while (r == -1 && errno == EINTR);
if (r < 1) if (r < 1)
break; 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); sys_close(fd);
if (!done) if (!total)
return false; return false;
UntypedMDRVA memory(&minidump_writer_); UntypedMDRVA memory(&minidump_writer_);
if (!memory.Allocate(done)) if (!memory.Allocate(total))
return false; 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(); *result = memory.location();
return true; return true;
} }

View file

@ -148,6 +148,24 @@ class wasteful_vector {
return used_; 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) { T& operator[](size_t index) {
return a_[index]; return a_[index];
} }