From bcd46f007919b5063164c8c5c6c2bd4dfb62681e Mon Sep 17 00:00:00 2001 From: luly81 Date: Mon, 12 Mar 2007 01:53:18 +0000 Subject: Add Linux exception handler. Add Linux stab symbol dumper. Add minidump & symbol uploader for Linux. git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@126 4c0a9323-5329-0410-9bdc-e9ce6186880e --- src/client/linux/handler/linux_thread.cc | 384 +++++++++++++++++++++++++++++++ 1 file changed, 384 insertions(+) create mode 100644 src/client/linux/handler/linux_thread.cc (limited to 'src/client/linux/handler/linux_thread.cc') diff --git a/src/client/linux/handler/linux_thread.cc b/src/client/linux/handler/linux_thread.cc new file mode 100644 index 00000000..eeedec6e --- /dev/null +++ b/src/client/linux/handler/linux_thread.cc @@ -0,0 +1,384 @@ +// Copyright (c) 2006, Google Inc. +// All rights reserved. +// +// Author: Li Liu +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "client/linux/handler/linux_thread.h" + +using namespace google_breakpad; + +// This unamed namespace contains helper function. +namespace { + +// Context information for the callbacks when validating address by listing +// modules. +struct AddressValidatingContext { + uintptr_t address; + bool is_mapped; + + AddressValidatingContext() : address(0UL), is_mapped(false) { + } +}; + +// Convert from string to int. +bool LocalAtoi(char *s, int *r) { + assert(s != NULL); + assert(r != NULL); + char *endptr = NULL; + int ret = strtol(s, &endptr, 10); + if (endptr == s) + return false; + *r = ret; + return true; +} + +// Fill the proc path of a thread given its id. +void FillProcPath(int pid, char *path, int path_size) { + char pid_str[32]; + snprintf(pid_str, sizeof(pid_str), "%d", pid); + snprintf(path, path_size, "/proc/%s/", pid_str); +} + +// Read thread info from /proc/$pid/status. +bool ReadThreadInfo(int pid, ThreadInfo *info) { + assert(info != NULL); + char status_path[80]; + // Max size we want to read from status file. + static const int kStatusMaxSize = 1024; + char status_content[kStatusMaxSize]; + + FillProcPath(pid, status_path, sizeof(status_path)); + strcat(status_path, "status"); + int fd = open(status_path, O_RDONLY, 0); + if (fd < 0) + return false; + + int num_read = read(fd, status_content, kStatusMaxSize - 1); + if (num_read < 0) { + close(fd); + return false; + } + close(fd); + status_content[num_read] = '\0'; + + char *tgid_start = strstr(status_content, "Tgid:"); + if (tgid_start) + sscanf(tgid_start, "Tgid:\t%d\n", &(info->tgid)); + else + // tgid not supported by kernel?? + info->tgid = 0; + + tgid_start = strstr(status_content, "Pid:"); + if (tgid_start) { + sscanf(tgid_start, "Pid:\t%d\n" "PPid:\t%d\n", &(info->pid), + &(info->ppid)); + return true; + } + return false; +} + +// Callback invoked for each mapped module. +// It use the module's adderss range to validate the address. +bool IsAddressInModuleCallback(const ModuleInfo &module_info, + void *context) { + AddressValidatingContext *addr = + reinterpret_cast(context); + addr->is_mapped = ((addr->address >= module_info.start_addr) && + (addr->address <= module_info.start_addr + + module_info.size)); + return !addr->is_mapped; +} + +#if defined(__i386__) && !defined(NO_FRAME_POINTER) +void *GetNextFrame(void **last_ebp) { + void *sp = *last_ebp; + if ((unsigned long)sp == (unsigned long)last_ebp) + return NULL; + if ((unsigned long)sp & (sizeof(void *) - 1)) + return NULL; + if ((unsigned long)sp - (unsigned long)last_ebp > 100000) + return NULL; + return sp; +} +#else +void *GetNextFrame(void **last_ebp) { + return reinterpret_cast(last_ebp); +} +#endif + +// Suspend a thread by attaching to it. +bool SuspendThread(int pid, void *context) { + // This may fail if the thread has just died or debugged. + errno = 0; + if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) != 0 && + errno != 0) { + return false; + } + while (waitpid(pid, NULL, __WALL) < 0) { + if (errno != EINTR) { + ptrace(PTRACE_DETACH, pid, NULL, NULL); + return false; + } + } + return true; +} + +// Resume a thread by detaching from it. +bool ResumeThread(int pid, void *context) { + return ptrace(PTRACE_DETACH, pid, NULL, NULL) >= 0; +} + +// Callback to get the thread information. +// Will be called for each thread found. +bool ThreadInfoCallback(int pid, void *context) { + CallbackParam *thread_callback = + reinterpret_cast *>(context); + ThreadInfo thread_info; + if (ReadThreadInfo(pid, &thread_info) && thread_callback) { + // Invoke callback from caller. + return (thread_callback->call_back)(thread_info, thread_callback->context); + } + return false; +} + +} // namespace + +namespace google_breakpad { + +LinuxThread::LinuxThread(int pid) : pid_(pid) , threads_suspened_(false) { +} + +LinuxThread::~LinuxThread() { + if (threads_suspened_) + ResumeAllThreads(); +} + +int LinuxThread::SuspendAllThreads() { + CallbackParam callback_param(SuspendThread, NULL); + int thread_count = 0; + if ((thread_count = IterateProcSelfTask(pid_, &callback_param)) > 0) + threads_suspened_ = true; + return thread_count; +} + +void LinuxThread::ResumeAllThreads() const { + CallbackParam callback_param(ResumeThread, NULL); + IterateProcSelfTask(pid_, &callback_param); +} + +int LinuxThread::GetThreadCount() const { + return IterateProcSelfTask(pid_, NULL); +} + +int LinuxThread::ListThreads( + CallbackParam *thread_callback_param) const { + CallbackParam callback_param(ThreadInfoCallback, + thread_callback_param); + return IterateProcSelfTask(pid_, &callback_param); +} + +bool LinuxThread::GetRegisters(int pid, user_regs_struct *regs) const { + assert(regs); + return (regs != NULL && + (ptrace(PTRACE_GETREGS, pid, NULL, regs) == 0) && + errno == 0); +} + +// Get the floating-point registers of a thread. +// The caller must get the thread pid by ListThreads. +bool LinuxThread::GetFPRegisters(int pid, user_fpregs_struct *regs) const { + assert(regs); + return (regs != NULL && + (ptrace(PTRACE_GETREGS, pid, NULL, regs) ==0) && + errno == 0); +} + +bool LinuxThread::GetFPXRegisters(int pid, user_fpxregs_struct *regs) const { + assert(regs); + return (regs != NULL && + (ptrace(PTRACE_GETFPREGS, pid, NULL, regs) != 0) && + errno == 0); +} + +bool LinuxThread::GetDebugRegisters(int pid, DebugRegs *regs) const { + assert(regs); + +#define GET_DR(name, num)\ + name->dr##num = ptrace(PTRACE_PEEKUSER, pid,\ + offsetof(struct user, u_debugreg[num]), NULL) + GET_DR(regs, 0); + GET_DR(regs, 1); + GET_DR(regs, 2); + GET_DR(regs, 3); + GET_DR(regs, 4); + GET_DR(regs, 5); + GET_DR(regs, 6); + GET_DR(regs, 7); + return true; +} + +int LinuxThread::GetThreadStackDump(uintptr_t current_ebp, + uintptr_t current_esp, + void *buf, + int buf_size) const { + assert(buf); + assert(buf_size > 0); + + uintptr_t stack_bottom = GetThreadStackBottom(current_ebp); + int size = stack_bottom - current_esp; + size = buf_size > size ? size : buf_size; + if (size > 0) + memcpy(buf, reinterpret_cast(current_esp), size); + return size; +} + +// Get the stack bottom of a thread by stack walking. It works +// unless the stack has been corrupted or the frame pointer has been omited. +// This is just a temporary solution before we get better ideas about how +// this can be done. +// +// We will check each frame address by checking into module maps. +// TODO(liuli): Improve it. +uintptr_t LinuxThread::GetThreadStackBottom(uintptr_t current_ebp) const { + void **sp = reinterpret_cast(current_ebp); + void **previous_sp = sp; + while (sp && IsAddressMapped((uintptr_t)sp)) { + previous_sp = sp; + sp = reinterpret_cast(GetNextFrame(sp)); + } + return (uintptr_t)previous_sp; +} + +int LinuxThread::GetModuleCount() const { + return ListModules(NULL); +} + +int LinuxThread::ListModules( + CallbackParam *callback_param) const { + char line[512]; + char *maps_path = "/proc/self/maps"; + + int module_count = 0; + FILE *fp = fopen(maps_path, "r"); + if (fp == NULL) + return -1; + + uintptr_t start_addr; + uintptr_t end_addr; + while (fgets(line, sizeof(line), fp) != NULL) { + if (sscanf(line, "%x-%x", &start_addr, &end_addr) == 2) { + ModuleInfo module; + memset(&module, 0, sizeof(module)); + module.start_addr = start_addr; + module.size = end_addr - start_addr; + char *name = NULL; + assert(module.size > 0); + // Only copy name if the name is a valid path name. + if ((name = strchr(line, '/')) != NULL) { + // Get rid of the last '\n' in line + char *last_return = strchr(line, '\n'); + if (last_return != NULL) + *last_return = '\0'; + // Keep a space for the ending 0. + strncpy(module.name, name, sizeof(module.name) - 1); + ++module_count; + } + if (callback_param && + !(callback_param->call_back(module, callback_param->context))) + break; + } + } + fclose(fp); + return module_count; +} + +// Parse /proc/$pid/tasks to list all the threads of the process identified by +// pid. +int LinuxThread::IterateProcSelfTask(int pid, + CallbackParam *callback_param) const { + char task_path[80]; + FillProcPath(pid, task_path, sizeof(task_path)); + strcat(task_path, "task"); + + DIR *dir = opendir(task_path); + if (dir == NULL) + return -1; + + int pid_number = 0; + // Record the last pid we've found. This is used for duplicated thread + // removal. Duplicated thread information can be found in /proc/$pid/tasks. + int last_pid = -1; + struct dirent *entry = NULL; + while ((entry = readdir(dir)) != NULL) { + if (strcmp(entry->d_name, ".") && + strcmp(entry->d_name, "..")) { + int tpid = 0; + if (LocalAtoi(entry->d_name, &tpid) && + last_pid != tpid) { + last_pid = tpid; + ++pid_number; + // Invoke the callback. + if (callback_param && + !(callback_param->call_back)(tpid, callback_param->context)) + break; + } + } + } + closedir(dir); + return pid_number; +} + +// Check if the address is a valid virtual address. +// If the address is in any of the mapped modules, we take it as valid. +// Otherwise it is invalid. +bool LinuxThread::IsAddressMapped(uintptr_t address) const { + AddressValidatingContext addr; + addr.address = address; + CallbackParam callback_param(IsAddressInModuleCallback, + &addr); + ListModules(&callback_param); + return addr.is_mapped; +} + +} // namespace google_breakpad -- cgit v1.2.1