diff options
author | nealsid <nealsid@4c0a9323-5329-0410-9bdc-e9ce6186880e> | 2009-08-17 23:12:53 +0000 |
---|---|---|
committer | nealsid <nealsid@4c0a9323-5329-0410-9bdc-e9ce6186880e> | 2009-08-17 23:12:53 +0000 |
commit | b0baafc4da1f3ffb84e267dd19d176db3de1c14e (patch) | |
tree | 3953f64180195ad40e27ab834f9b175e29042025 /src/client/linux | |
parent | Fix build errors with gcc 4.4. Patch by Silvius Rus <rus@google.com>. (diff) | |
download | breakpad-b0baafc4da1f3ffb84e267dd19d176db3de1c14e.tar.xz |
Merge of Breakpad Chrome Linux fork
A=agl, Lei Zhang
R=nealsid, agl
git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@384 4c0a9323-5329-0410-9bdc-e9ce6186880e
Diffstat (limited to 'src/client/linux')
19 files changed, 2668 insertions, 2192 deletions
diff --git a/src/client/linux/handler/Makefile b/src/client/linux/handler/Makefile index 73be56d8..7fcf1b21 100644 --- a/src/client/linux/handler/Makefile +++ b/src/client/linux/handler/Makefile @@ -1,55 +1,45 @@ CXX=g++ CC=gcc -CXXFLAGS=-gstabs+ -I../../.. -Wall -D_REENTRANT +CXXFLAGS=-gstabs+ -I../../../ -I../../../testing/gtest/include -I../../../testing/include -I../../../testing/gtest -D_REENTRANT -m32 +CFLAGS=$(CXXFLAGS) LDFLAGS=-lpthread OBJ_DIR=. BIN_DIR=. -THREAD_SRC=linux_thread.cc -SHARE_SRC=../../minidump_file_writer.cc\ - ../../../common/string_conversion.cc\ - ../../../common/linux/file_id.cc\ - minidump_generator.cc -HANDLER_SRC=exception_handler.cc\ - ../../../common/linux/guid_creator.cc -SHARE_C_SRC=../../../common/convert_UTF.c - -THREAD_TEST_SRC=linux_thread_test.cc -MINIDUMP_TEST_SRC=minidump_test.cc -EXCEPTION_TEST_SRC=exception_handler_test.cc - -THREAD_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o,$(THREAD_SRC)) -SHARE_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o,$(SHARE_SRC)) -HANDLER_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o,$(HANDLER_SRC)) -SHARE_C_OBJ=$(patsubst %.c,$(OBJ_DIR)/%.o,$(SHARE_C_SRC)) md5.o -THREAD_TEST_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o, $(THREAD_TEST_SRC))\ - $(THREAD_OBJ) -MINIDUMP_TEST_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o, $(MINIDUMP_TEST_SRC))\ - $(THREAD_OBJ) $(SHARE_OBJ) $(SHARE_C_OBJ) -EXCEPTION_TEST_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o, $(EXCEPTION_TEST_SRC))\ - $(THREAD_OBJ) $(SHARE_OBJ) $(SHARE_C_OBJ) $(HANDLER_OBJ) - -BIN=$(BIN_DIR)/minidump_test\ - $(BIN_DIR)/linux_thread_test\ - $(BIN_DIR)/exception_handler_test +TEST_CC_SRC=exception_handler_unittest.cc \ + exception_handler.cc \ + ../../../testing/gtest/src/gtest-all.cc \ + ../../../common/linux/guid_creator.cc \ + ../minidump_writer/minidump_writer.cc \ + ../../minidump_file_writer.cc \ + ../minidump_writer/linux_dumper.cc \ + ../../../testing/gtest/src/gtest_main.cc \ + ../../../common/string_conversion.cc \ + ../minidump_writer/directory_reader_unittest.cc \ + ../minidump_writer/line_reader_unittest.cc \ + ../minidump_writer/linux_dumper_unittest.cc \ + ../minidump_writer/minidump_writer_unittest.cc -.PHONY:all clean +TEST_C_SRC = ../../../common/convert_UTF.c -all:$(BIN) +TEST_CC_OBJ=$(patsubst %.cc, $(OBJ_DIR)/%.o,$(TEST_CC_SRC)) +TEST_C_OBJ=$(patsubst %.c, $(OBJ_DIR)/%.o, $(TEST_C_SRC)) -$(BIN_DIR)/linux_thread_test:$(THREAD_TEST_OBJ) - $(CXX) $(CXXFLAGS) $(LDFLAGS) $^ -o $@ +LINUX_CLIENT_BIN=$(BIN_DIR)/linux_client_test -$(BIN_DIR)/minidump_test:$(MINIDUMP_TEST_OBJ) - $(CXX) $(CXXFLAGS) $(LDFLAGS) $^ -o $@ +BIN=$(LINUX_CLIENT_BIN) -$(BIN_DIR)/exception_handler_test:$(EXCEPTION_TEST_OBJ) - $(CXX) $(CXXFLAGS) $(LDFLAGS) $^ -o $@ +.PHONY:all clean + +check:$(BIN) + $(LINUX_CLIENT_BIN) -md5.o:../../../common/md5.c - $(CC) $(CXXFLAGS) -c $^ +all:$(BIN) + +$(BIN_DIR)/linux_client_test:$(TEST_CC_OBJ) $(TEST_C_OBJ) + $(CXX) $(CXXFLAGS) $(LDFLAGS) $^ -o $@ clean: - rm -f $(BIN) *.o *.dmp + rm -f $(BIN) $(TEST_CC_OBJ) $(TEST_C_OBJ) diff --git a/src/client/linux/handler/exception_handler.cc b/src/client/linux/handler/exception_handler.cc index 43f513c0..8738b27d 100644 --- a/src/client/linux/handler/exception_handler.cc +++ b/src/client/linux/handler/exception_handler.cc @@ -1,8 +1,6 @@ -// Copyright (c) 2006, Google Inc. +// Copyright (c) 2009, 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: @@ -29,48 +27,87 @@ // (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 <signal.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> +// The ExceptionHandler object installs signal handlers for a number of +// signals. We rely on the signal handler running on the thread which crashed +// in order to identify it. This is true of the synchronous signals (SEGV etc), +// but not true of ABRT. Thus, if you send ABRT to yourself in a program which +// uses ExceptionHandler, you need to use tgkill to direct it to the current +// thread. +// +// The signal flow looks like this: +// +// SignalHandler (uses a global stack of ExceptionHandler objects to find +// | one to handle the signal. If the first rejects it, try +// | the second etc...) +// V +// HandleSignal ----------------------------| (clones a new process which +// | | shares an address space with +// (wait for cloned | the crashed process. This +// process) | allows us to ptrace the crashed +// | | process) +// V V +// (set signal handler to ThreadEntry (static function to bounce +// SIG_DFL and rethrow, | back into the object) +// killing the crashed | +// process) V +// DoDump (writes minidump) +// | +// V +// sys_exit +// -#include <cassert> -#include <cstdlib> -#include <cstdio> -#include <ctime> -#include <linux/limits.h> +// This code is a little fragmented. Different functions of the ExceptionHandler +// class run in a number of different contexts. Some of them run in a normal +// context and are easy to code, others run in a compromised context and the +// restrictions at the top of minidump_writer.cc apply: no libc and use the +// alternative malloc. Each function should have comment above it detailing the +// context which it runs in. #include "client/linux/handler/exception_handler.h" + +#include <errno.h> +#include <fcntl.h> +#include <linux/limits.h> +#include <sched.h> +#include <signal.h> +#include <stdio.h> +#include <sys/mman.h> +#include <sys/signal.h> +#include <sys/syscall.h> +#include <sys/ucontext.h> +#include <sys/user.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "common/linux/linux_libc_support.h" +#include "common/linux/linux_syscall_support.h" +#include "common/linux/memory.h" +#include "client/linux/minidump_writer//minidump_writer.h" #include "common/linux/guid_creator.h" -#include "google_breakpad/common/minidump_format.h" + +// A wrapper for the tgkill syscall: send a signal to a specific thread. +static int tgkill(pid_t tgid, pid_t tid, int sig) { + syscall(__NR_tgkill, tgid, tid, sig); +} namespace google_breakpad { -// Signals that we are interested. -int SigTable[] = { -#if defined(SIGSEGV) - SIGSEGV, -#endif -#ifdef SIGABRT - SIGABRT, -#endif -#ifdef SIGFPE - SIGFPE, -#endif -#ifdef SIGILL - SIGILL, -#endif -#ifdef SIGBUS - SIGBUS, -#endif +// The list of signals which we consider to be crashes. The default action for +// all these signals must be Core (see man 7 signal) because we rethrow the +// signal after handling it and expect that it'll be fatal. +static const int kExceptionSignals[] = { + SIGSEGV, SIGABRT, SIGFPE, SIGILL, SIGBUS, -1 }; -std::vector<ExceptionHandler*> *ExceptionHandler::handler_stack_ = NULL; -int ExceptionHandler::handler_stack_index_ = 0; +// We can stack multiple exception handlers. In that case, this is the global +// which holds the stack. +std::vector<ExceptionHandler*>* ExceptionHandler::handler_stack_ = NULL; +unsigned ExceptionHandler::handler_stack_index_ = 0; pthread_mutex_t ExceptionHandler::handler_stack_mutex_ = -PTHREAD_MUTEX_INITIALIZER; + PTHREAD_MUTEX_INITIALIZER; -ExceptionHandler::ExceptionHandler(const string &dump_path, +// Runs before crashing: normal context. +ExceptionHandler::ExceptionHandler(const std::string &dump_path, FilterCallback filter, MinidumpCallback callback, void *callback_context, @@ -79,227 +116,207 @@ ExceptionHandler::ExceptionHandler(const string &dump_path, callback_(callback), callback_context_(callback_context), dump_path_(), - installed_handler_(install_handler) { + handler_installed_(install_handler), + crash_handler_(NULL) { set_dump_path(dump_path); - act_.sa_handler = HandleException; - act_.sa_flags = SA_ONSTACK; - sigemptyset(&act_.sa_mask); - // now, make sure we're blocking all the signals we are handling - // when we're handling any of them - for ( size_t i = 0; i < sizeof(SigTable) / sizeof(SigTable[0]); ++i) { - sigaddset(&act_.sa_mask, SigTable[i]); - } - if (install_handler) { - SetupHandler(); + InstallHandlers(); + pthread_mutex_lock(&handler_stack_mutex_); - if (handler_stack_ == NULL) - handler_stack_ = new std::vector<ExceptionHandler *>; - handler_stack_->push_back(this); + if (handler_stack_ == NULL) + handler_stack_ = new std::vector<ExceptionHandler *>; + handler_stack_->push_back(this); pthread_mutex_unlock(&handler_stack_mutex_); } } +// Runs before crashing: normal context. ExceptionHandler::~ExceptionHandler() { - TeardownAllHandler(); - pthread_mutex_lock(&handler_stack_mutex_); - if (handler_stack_->back() == this) { - handler_stack_->pop_back(); - } else { - fprintf(stderr, "warning: removing Breakpad handler out of order\n"); - for (std::vector<ExceptionHandler *>::iterator iterator = - handler_stack_->begin(); - iterator != handler_stack_->end(); - ++iterator) { - if (*iterator == this) { - handler_stack_->erase(iterator); - } - } - } - - if (handler_stack_->empty()) { - // When destroying the last ExceptionHandler that installed a handler, - // clean up the handler stack. - delete handler_stack_; - handler_stack_ = NULL; - } - pthread_mutex_unlock(&handler_stack_mutex_); + UninstallHandlers(); } -bool ExceptionHandler::WriteMinidump() { - bool success = InternalWriteMinidump(0, 0, NULL); - UpdateNextID(); - return success; -} +// Runs before crashing: normal context. +bool ExceptionHandler::InstallHandlers() { + // We run the signal handlers on an alternative stack because we might have + // crashed because of a stack overflow. -// static -bool ExceptionHandler::WriteMinidump(const string &dump_path, - MinidumpCallback callback, - void *callback_context) { - ExceptionHandler handler(dump_path, NULL, callback, - callback_context, false); - return handler.InternalWriteMinidump(0, 0, NULL); -} + // We use this value rather than SIGSTKSZ because we would end up overrunning + // such a small stack. + static const unsigned kSigStackSize = 8192; -void ExceptionHandler::SetupHandler() { - // Signal on a different stack to avoid using the stack - // of the crashing thread. - struct sigaltstack sig_stack; - sig_stack.ss_sp = malloc(MINSIGSTKSZ); - if (sig_stack.ss_sp == NULL) - return; - sig_stack.ss_size = MINSIGSTKSZ; - sig_stack.ss_flags = 0; + signal_stack = malloc(kSigStackSize); + stack_t stack; + memset(&stack, 0, sizeof(stack)); + stack.ss_sp = signal_stack; + stack.ss_size = kSigStackSize; - if (sigaltstack(&sig_stack, NULL) < 0) - return; - for (size_t i = 0; i < sizeof(SigTable) / sizeof(SigTable[0]); ++i) - SetupHandler(SigTable[i]); -} + if (sigaltstack(&stack, NULL) == -1) + return false; -void ExceptionHandler::SetupHandler(int signo) { + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sigemptyset(&sa.sa_mask); - // We're storing pointers to the old signal action - // structure, rather than copying the structure - // because we can't count on the sa_mask field to - // be scalar. - struct sigaction *old_act = &old_actions_[signo]; + // mask all exception signals when we're handling one of them. + for (unsigned i = 0; kExceptionSignals[i] != -1; ++i) + sigaddset(&sa.sa_mask, kExceptionSignals[i]); - if (sigaction(signo, &act_, old_act) < 0) - return; -} + sa.sa_sigaction = SignalHandler; + sa.sa_flags = SA_ONSTACK | SA_SIGINFO; -void ExceptionHandler::TeardownHandler(int signo) { - TeardownHandler(signo, NULL); + for (unsigned i = 0; kExceptionSignals[i] != -1; ++i) { + struct sigaction* old = new struct sigaction; + if (sigaction(kExceptionSignals[i], &sa, old) == -1) + return false; + old_handlers_.push_back(std::make_pair(kExceptionSignals[i], old)); + } } -void ExceptionHandler::TeardownHandler(int signo, struct sigaction *final_handler) { - if (old_actions_[signo].sa_handler) { - struct sigaction *act = &old_actions_[signo]; - sigaction(signo, act, final_handler); - memset(&old_actions_[signo], 0x0, sizeof(struct sigaction)); +// Runs before crashing: normal context. +void ExceptionHandler::UninstallHandlers() { + for (unsigned i = 0; i < old_handlers_.size(); ++i) { + struct sigaction *action = + reinterpret_cast<struct sigaction*>(old_handlers_[i].second); + sigaction(old_handlers_[i].first, action, NULL); + delete action; } + + old_handlers_.clear(); } -void ExceptionHandler::TeardownAllHandler() { - for (size_t i = 0; i < sizeof(SigTable) / sizeof(SigTable[0]); ++i) { - TeardownHandler(SigTable[i]); +// Runs before crashing: normal context. +void ExceptionHandler::UpdateNextID() { + GUID guid; + char guid_str[kGUIDStringLength + 1]; + if (CreateGUID(&guid) && GUIDToString(&guid, guid_str, sizeof(guid_str))) { + next_minidump_id_ = guid_str; + next_minidump_id_c_ = next_minidump_id_.c_str(); + + char minidump_path[PATH_MAX]; + snprintf(minidump_path, sizeof(minidump_path), "%s/%s.dmp", + dump_path_c_, + guid_str); + + next_minidump_path_ = minidump_path; + next_minidump_path_c_ = next_minidump_path_.c_str(); } } +// This function runs in a compromised context: see the top of the file. +// Runs on the crashing thread. // static -void ExceptionHandler::HandleException(int signo) { - // In Linux, the context information about the signal is put on the stack of - // the signal handler frame as value parameter. For some reasons, the - // prototype of the handler doesn't declare this information as parameter, we - // will do it by hand. It is the second parameter above the signal number. - // However, if we are being called by another signal handler passing the - // signal up the chain, then we may not have this random extra parameter, - // so we may have to walk the stack to find it. We do the actual work - // on another thread, where it's a little safer, but we want the ebp - // from this frame to find it. - uintptr_t current_ebp = 0; - asm volatile ("movl %%ebp, %0" - :"=m"(current_ebp)); +void ExceptionHandler::SignalHandler(int sig, siginfo_t* info, void* uc) { + // All the exception signals are blocked at this point. pthread_mutex_lock(&handler_stack_mutex_); - ExceptionHandler *current_handler = - handler_stack_->at(handler_stack_->size() - ++handler_stack_index_); - pthread_mutex_unlock(&handler_stack_mutex_); - // Restore original handler. - struct sigaction old_action; - current_handler->TeardownHandler(signo, &old_action); - - struct sigcontext *sig_ctx = NULL; - if (current_handler->InternalWriteMinidump(signo, current_ebp, &sig_ctx)) { - // Fully handled this exception, safe to exit. - exit(EXIT_FAILURE); - } else { - // Exception not fully handled, will call the next handler in stack to - // process it. - if (old_action.sa_handler != NULL && sig_ctx != NULL) { - - // Have our own typedef, because of the comment above w.r.t signal - // context on the stack - typedef void (*SignalHandler)(int signo, struct sigcontext); - - SignalHandler old_handler = - reinterpret_cast<SignalHandler>(old_action.sa_handler); - - sigset_t old_set; - // Use SIG_BLOCK here because we don't want to unblock a signal - // that the signal handler we're currently in needs to block - sigprocmask(SIG_BLOCK, &old_action.sa_mask, &old_set); - old_handler(signo, *sig_ctx); - sigprocmask(SIG_SETMASK, &old_set, NULL); - } + if (!handler_stack_->size()) { + pthread_mutex_unlock(&handler_stack_mutex_); + return; + } + for (int i = handler_stack_->size() - 1; i >= 0; --i) { + if ((*handler_stack_)[i]->HandleSignal(sig, info, uc)) { + // successfully handled: We are in an invalid state since an exception + // signal has been delivered. We don't call the exit handlers because + // they could end up corrupting on-disk state. + break; + } } - pthread_mutex_lock(&handler_stack_mutex_); - current_handler->SetupHandler(signo); - --handler_stack_index_; - // All the handlers in stack have been invoked to handle the exception, - // normally the process should be terminated and should not reach here. - // In case we got here, ask the OS to handle it to avoid endless loop, - // normally the OS will generate a core and termiate the process. This - // may be desired to debug the program. - if (handler_stack_index_ == 0) - signal(signo, SIG_DFL); pthread_mutex_unlock(&handler_stack_mutex_); + + // Terminate ourselves with the same signal so that our parent knows that we + // 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); + + // not reached. +} + +struct ThreadArgument { + pid_t pid; // the crashing process + ExceptionHandler* handler; + const void* context; // a CrashContext structure + size_t context_size; +}; + +// This is the entry function for the cloned process. We are in a compromised +// context here: see the top of the file. +// static +int ExceptionHandler::ThreadEntry(void *arg) { + const ThreadArgument *thread_arg = reinterpret_cast<ThreadArgument*>(arg); + return thread_arg->handler->DoDump(thread_arg->pid, thread_arg->context, + thread_arg->context_size) == false; } -bool ExceptionHandler::InternalWriteMinidump(int signo, - uintptr_t sighandler_ebp, - struct sigcontext **sig_ctx) { +// This function runs in a compromised context: see the top of the file. +// Runs on the crashing thread. +bool ExceptionHandler::HandleSignal(int sig, siginfo_t* info, void* uc) { if (filter_ && !filter_(callback_context_)) return false; - bool success = false; - // Block all the signals we want to process when writting minidump. - // We don't want it to be interrupted. - sigset_t sig_blocked, sig_old; - bool blocked = true; - sigfillset(&sig_blocked); - for (size_t i = 0; i < sizeof(SigTable) / sizeof(SigTable[0]); ++i) - sigdelset(&sig_blocked, SigTable[i]); - if (sigprocmask(SIG_BLOCK, &sig_blocked, &sig_old) != 0) { - blocked = false; - fprintf(stderr, "google_breakpad::ExceptionHandler::HandleException: " - "failed to block signals.\n"); - } + // Allow ourselves to be dumped. + sys_prctl(PR_SET_DUMPABLE, 1); + + CrashContext context; + memcpy(&context.siginfo, info, sizeof(siginfo_t)); + memcpy(&context.context, uc, sizeof(struct ucontext)); + memcpy(&context.float_state, ((struct ucontext *)uc)->uc_mcontext.fpregs, + sizeof(context.float_state)); + context.tid = sys_gettid(); - success = minidump_generator_.WriteMinidumpToFile( - next_minidump_path_c_, signo, sighandler_ebp, sig_ctx); + if (crash_handler_ && crash_handler_(&context, sizeof(context), + callback_context_)) + return true; - // Unblock the signals. - if (blocked) { - sigprocmask(SIG_SETMASK, &sig_old, NULL); + static const unsigned kChildStackSize = 8000; + PageAllocator allocator; + uint8_t* stack = (uint8_t*) allocator.Alloc(kChildStackSize); + if (!stack) + return false; + // clone() needs the top-most address. (scrub just to be safe) + stack += kChildStackSize; + my_memset(stack - 16, 0, 16); + + ThreadArgument thread_arg; + thread_arg.handler = this; + thread_arg.pid = getpid(); + thread_arg.context = &context; + thread_arg.context_size = sizeof(context); + + const pid_t child = sys_clone( + ThreadEntry, stack, CLONE_FILES | CLONE_FS | CLONE_UNTRACED, + &thread_arg, NULL, NULL, NULL); + int r, status; + do { + r = sys_waitpid(child, &status, __WALL); + } while (r == -1 && errno == EINTR); + + if (r == -1) { + static const char msg[] = "ExceptionHandler::HandleSignal: waitpid failed:"; + sys_write(2, msg, sizeof(msg) - 1); + sys_write(2, strerror(errno), strlen(strerror(errno))); + sys_write(2, "\n", 1); } + bool success = r != -1 && WIFEXITED(status) && WEXITSTATUS(status) == 0; + if (callback_) success = callback_(dump_path_c_, next_minidump_id_c_, - callback_context_, success); + callback_context_, success); + return success; } -void ExceptionHandler::UpdateNextID() { - GUID guid; - char guid_str[kGUIDStringLength + 1]; - if (CreateGUID(&guid) && GUIDToString(&guid, guid_str, sizeof(guid_str))) { - next_minidump_id_ = guid_str; - next_minidump_id_c_ = next_minidump_id_.c_str(); - - char minidump_path[PATH_MAX]; - snprintf(minidump_path, sizeof(minidump_path), "%s/%s.dmp", - dump_path_c_, - guid_str); - - next_minidump_path_ = minidump_path; - next_minidump_path_c_ = next_minidump_path_.c_str(); - } +// This function runs in a compromised context: see the top of the file. +// Runs on the cloned process. +bool ExceptionHandler::DoDump(pid_t crashing_process, const void* context, + size_t context_size) { + return google_breakpad::WriteMinidump( + next_minidump_path_c_, crashing_process, context, context_size); } } // namespace google_breakpad diff --git a/src/client/linux/handler/exception_handler.h b/src/client/linux/handler/exception_handler.h index 6ea09a11..b579a6a9 100644 --- a/src/client/linux/handler/exception_handler.h +++ b/src/client/linux/handler/exception_handler.h @@ -1,8 +1,6 @@ -// Copyright (c) 2006, Google Inc. +// Copyright (c) 2009, 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: @@ -29,26 +27,16 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#ifndef CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H__ -#define CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H__ - -#include <pthread.h> +#ifndef CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_ +#define CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_ -#include <map> -#include <string> -#include <signal.h> #include <vector> +#include <string> -#include "client/linux/handler/minidump_generator.h" - -// Context information when exception occured. -struct sigcontex; +#include <signal.h> namespace google_breakpad { -using std::string; - -// // ExceptionHandler // // ExceptionHandler can write a minidump file when an exception occurs, @@ -73,7 +61,6 @@ using std::string; // // Caller should try to make the callbacks as crash-friendly as possible, // it should avoid use heap memory allocation as much as possible. -// class ExceptionHandler { public: // A callback function to run before Breakpad performs any substantial @@ -108,6 +95,15 @@ class ExceptionHandler { void *context, bool succeeded); + // In certain cases, a user may wish to handle the generation of the minidump + // themselves. In this case, they can install a handler callback which is + // called when a crash has occured. If this function returns true, no other + // processing of occurs and the process will shortly be crashed. If this + // returns false, the normal processing continues. + typedef bool (*HandlerCallback)(const void* crash_context, + size_t crash_context_size, + void* context); + // Creates a new ExceptionHandler instance to handle writing minidumps. // Before writing a minidump, the optional filter callback will be called. // Its return value determines whether or not Breakpad should write a @@ -116,111 +112,87 @@ class ExceptionHandler { // If install_handler is true, then a minidump will be written whenever // an unhandled exception occurs. If it is false, minidumps will only // be written when WriteMinidump is called. - ExceptionHandler(const string &dump_path, + ExceptionHandler(const std::string &dump_path, FilterCallback filter, MinidumpCallback callback, void *callback_context, bool install_handler); ~ExceptionHandler(); // Get and set the minidump path. - string dump_path() const { return dump_path_; } - void set_dump_path(const string &dump_path) { + std::string dump_path() const { return dump_path_; } + void set_dump_path(const std::string &dump_path) { dump_path_ = dump_path; dump_path_c_ = dump_path_.c_str(); UpdateNextID(); } + void set_crash_handler(HandlerCallback callback) { + crash_handler_ = callback; + } + // Writes a minidump immediately. This can be used to capture the // execution state independently of a crash. Returns true on success. bool WriteMinidump(); // Convenience form of WriteMinidump which does not require an // ExceptionHandler instance. - static bool WriteMinidump(const string &dump_path, + static bool WriteMinidump(const std::string &dump_path, MinidumpCallback callback, void *callback_context); - private: - // Setup crash handler. - void SetupHandler(); - // Setup signal handler for a signal. - void SetupHandler(int signo); - // Teardown the handler for a signal. - void TeardownHandler(int signo); - // Teardown the handler for a signal. - void TeardownHandler(int signo, struct sigaction *old); - // Teardown all handlers. - void TeardownAllHandler(); - - // Signal handler. - static void HandleException(int signo); - - // If called from a signal handler, sighandler_ebp is the ebp of - // that signal handler's frame, and sig_ctx is an out parameter - // that will be set to point at the sigcontext that was placed - // on the stack by the kernel. You can pass zero and NULL - // for the second and third parameters if you are not calling - // this from a signal handler. - bool InternalWriteMinidump(int signo, uintptr_t sighandler_ebp, - struct sigcontext **sig_ctx); - - // Generates a new ID and stores it in next_minidump_id, and stores the - // path of the next minidump to be written in next_minidump_path_. - void UpdateNextID(); + // This structure is passed to minidump_writer.h:WriteMinidump via an opaque + // blob. It shouldn't be needed in any user code. + struct CrashContext { + siginfo_t siginfo; + pid_t tid; // the crashing thread. + struct ucontext context; + struct _libc_fpstate float_state; + }; private: - FilterCallback filter_; - MinidumpCallback callback_; - void *callback_context_; + bool InstallHandlers(); + void UninstallHandlers(); + void PreresolveSymbols(); - // The directory in which a minidump will be written, set by the dump_path - // argument to the constructor, or set_dump_path. - string dump_path_; + void UpdateNextID(); + static void SignalHandler(int sig, siginfo_t* info, void* uc); + bool HandleSignal(int sig, siginfo_t* info, void* uc); + static int ThreadEntry(void* arg); + bool DoDump(pid_t crashing_process, const void* context, + size_t context_size); - // The basename of the next minidump to be written, without the extension - string next_minidump_id_; + const FilterCallback filter_; + const MinidumpCallback callback_; + void* const callback_context_; - // The full pathname of the next minidump to be written, including the file - // extension - string next_minidump_path_; + std::string dump_path_; + std::string next_minidump_path_; + std::string next_minidump_id_; // Pointers to C-string representations of the above. These are set // when the above are set so we can avoid calling c_str during // an exception. - const char *dump_path_c_; - const char *next_minidump_id_c_; - const char *next_minidump_path_c_; + const char* dump_path_c_; + const char* next_minidump_path_c_; + const char* next_minidump_id_c_; - // True if the ExceptionHandler installed an unhandled exception filter - // when created (with an install_handler parameter set to true). - bool installed_handler_; + const bool handler_installed_; + void* signal_stack; // the handler stack. + HandlerCallback crash_handler_; // The global exception handler stack. This is need becuase there may exist // multiple ExceptionHandler instances in a process. Each will have itself // registered in this stack. - static std::vector<ExceptionHandler *> *handler_stack_; + static std::vector<ExceptionHandler*> *handler_stack_; // The index of the handler that should handle the next exception. - static int handler_stack_index_; + static unsigned handler_stack_index_; static pthread_mutex_t handler_stack_mutex_; - // The minidump generator. - MinidumpGenerator minidump_generator_; - - // disallow copy ctor and operator= - explicit ExceptionHandler(const ExceptionHandler &); - void operator=(const ExceptionHandler &); - - // The sigactions structure we use for each signal - struct sigaction act_; - - - // Keep the previous handlers for the signal. - // We're wasting a bit of memory here since we only change - // the handler for some signals but i want to avoid allocating - // memory in the signal handler - struct sigaction old_actions_[NSIG]; + // A vector of the old signal handlers. The void* is a pointer to a newly + // allocated sigaction structure to avoid pulling in too many includes. + std::vector<std::pair<int, void *> > old_handlers_; }; } // namespace google_breakpad -#endif // CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H__ +#endif // CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_ diff --git a/src/client/linux/handler/exception_handler_test.cc b/src/client/linux/handler/exception_handler_test.cc deleted file mode 100644 index 2d94553d..00000000 --- a/src/client/linux/handler/exception_handler_test.cc +++ /dev/null @@ -1,124 +0,0 @@ -// 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 <pthread.h> -#include <unistd.h> - -#include <cassert> -#include <cstdio> -#include <cstdlib> -#include <cstring> - -#include "client/linux/handler/exception_handler.h" -#include "client/linux/handler/linux_thread.h" - -using namespace google_breakpad; - -// Thread use this to see if it should stop working. -static bool should_exit = false; - -static int foo2(int arg) { - // Stack variable, used for debugging stack dumps. - /*DDDebug*/printf("%s:%d\n", __FUNCTION__, __LINE__); - int c = 0xcccccccc; - fprintf(stderr, "Thread trying to crash: %x\n", getpid()); - c = *reinterpret_cast<int *>(0x5); - return c; -} - -static int foo(int arg) { - // Stack variable, used for debugging stack dumps. - int b = 0xbbbbbbbb; - b = foo2(b); - return b; -} - -static void *thread_crash(void *) { - // Stack variable, used for debugging stack dumps. - int a = 0xaaaaaaaa; - sleep(1); - a = foo(a); - printf("%x\n", a); - return NULL; -} - -static void *thread_main(void *) { - while (!should_exit) - sleep(1); - return NULL; -} - -static void CreateCrashThread() { - pthread_t h; - pthread_create(&h, NULL, thread_crash, NULL); - pthread_detach(h); -} - -// Create working threads. -static void CreateThread(int num) { - pthread_t h; - for (int i = 0; i < num; ++i) { - pthread_create(&h, NULL, thread_main, NULL); - pthread_detach(h); - } -} - -// Callback when minidump written. -static bool MinidumpCallback(const char *dump_path, - const char *minidump_id, - void *context, - bool succeeded) { - int index = reinterpret_cast<int>(context); - printf("%d %s: %s is dumped\n", index, __FUNCTION__, minidump_id); - if (index == 0) { - should_exit = true; - return true; - } - // Don't process it. - return false; -} - -int main(int argc, char *argv[]) { - int handler_index = 0; - ExceptionHandler handler_ignore(".", NULL, MinidumpCallback, - (void*)handler_index, true); - ++handler_index; - ExceptionHandler handler_process(".", NULL, MinidumpCallback, - (void*)handler_index, true); - CreateCrashThread(); - CreateThread(10); - - while (true) - sleep(1); - should_exit = true; - - return 0; -} diff --git a/src/client/linux/handler/exception_handler_unittest.cc b/src/client/linux/handler/exception_handler_unittest.cc new file mode 100644 index 00000000..157c495a --- /dev/null +++ b/src/client/linux/handler/exception_handler_unittest.cc @@ -0,0 +1,256 @@ +// Copyright (c) 2009, Google Inc. +// All rights reserved. +// +// 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 <string> + +#include <stdint.h> +#include <unistd.h> +#include <signal.h> +#include <sys/poll.h> +#include <sys/socket.h> +#include <sys/uio.h> + +#include "client/linux/handler//exception_handler.h" +#include "client/linux/minidump_writer/minidump_writer.h" +#include "common/linux/linux_libc_support.h" +#include "common/linux/linux_syscall_support.h" +#include "breakpad_googletest_includes.h" + +// This provides a wrapper around system calls which may be +// interrupted by a signal and return EINTR. See man 7 signal. +#define HANDLE_EINTR(x) ({ \ + typeof(x) __eintr_result__; \ + do { \ + __eintr_result__ = x; \ + } while (__eintr_result__ == -1 && errno == EINTR); \ + __eintr_result__;\ +}) + +using namespace google_breakpad; + +static void sigchld_handler(int signo) { } + +class ExceptionHandlerTest : public ::testing::Test { + protected: + void SetUp() { + // We need to be able to wait for children, so SIGCHLD cannot be SIG_IGN. + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = sigchld_handler; + ASSERT_NE(sigaction(SIGCHLD, &sa, &old_action), -1); + } + + void TearDown() { + sigaction(SIGCHLD, &old_action, NULL); + } + + struct sigaction old_action; +}; + +TEST(ExceptionHandlerTest, Simple) { + ExceptionHandler handler("/tmp", NULL, NULL, NULL, true); +} + +static bool DoneCallback(const char* dump_path, + const char* minidump_id, + void* context, + bool succeeded) { + if (!succeeded) + return succeeded; + + int fd = (int) context; + uint32_t len = my_strlen(minidump_id); + HANDLE_EINTR(sys_write(fd, &len, sizeof(len))); + HANDLE_EINTR(sys_write(fd, minidump_id, len)); + sys_close(fd); + + return true; +} + +TEST(ExceptionHandlerTest, ChildCrash) { + int fds[2]; + ASSERT_NE(pipe(fds), -1); + + const pid_t child = fork(); + if (child == 0) { + close(fds[0]); + ExceptionHandler handler("/tmp", NULL, DoneCallback, (void*) fds[1], + true); + *reinterpret_cast<int*>(NULL) = 0; + } + close(fds[1]); + + int status; + ASSERT_NE(HANDLE_EINTR(waitpid(child, &status, 0)), -1); + ASSERT_TRUE(WIFSIGNALED(status)); + ASSERT_EQ(WTERMSIG(status), SIGSEGV); + + struct pollfd pfd; + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = fds[0]; + pfd.events = POLLIN | POLLERR; + + const int r = HANDLE_EINTR(poll(&pfd, 1, 0)); + ASSERT_EQ(r, 1); + ASSERT_TRUE(pfd.revents & POLLIN); + + uint32_t len; + ASSERT_EQ(read(fds[0], &len, sizeof(len)), sizeof(len)); + ASSERT_LT(len, 2048); + char* filename = reinterpret_cast<char*>(malloc(len + 1)); + ASSERT_EQ(read(fds[0], filename, len), len); + filename[len] = 0; + close(fds[0]); + + const std::string minidump_filename = std::string("/tmp/") + filename + + ".dmp"; + + struct stat st; + ASSERT_EQ(stat(minidump_filename.c_str(), &st), 0); + ASSERT_GT(st.st_size, 0u); + unlink(minidump_filename.c_str()); +} + +static const unsigned kControlMsgSize = + CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(struct ucred)); + +static bool +CrashHandler(const void* crash_context, size_t crash_context_size, + void* context) { + const int fd = (int) context; + int fds[2]; + pipe(fds); + + struct kernel_msghdr msg = {0}; + struct kernel_iovec iov; + iov.iov_base = const_cast<void*>(crash_context); + iov.iov_len = crash_context_size; + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + char cmsg[kControlMsgSize]; + memset(cmsg, 0, kControlMsgSize); + msg.msg_control = cmsg; + msg.msg_controllen = sizeof(cmsg); + + struct cmsghdr *hdr = CMSG_FIRSTHDR(&msg); + hdr->cmsg_level = SOL_SOCKET; + hdr->cmsg_type = SCM_RIGHTS; + hdr->cmsg_len = CMSG_LEN(sizeof(int)); + *((int*) CMSG_DATA(hdr)) = fds[1]; + hdr = CMSG_NXTHDR((struct msghdr*) &msg, hdr); + hdr->cmsg_level = SOL_SOCKET; + hdr->cmsg_type = SCM_CREDENTIALS; + hdr->cmsg_len = CMSG_LEN(sizeof(struct ucred)); + struct ucred *cred = reinterpret_cast<struct ucred*>(CMSG_DATA(hdr)); + cred->uid = getuid(); + cred->gid = getgid(); + cred->pid = getpid(); + + HANDLE_EINTR(sys_sendmsg(fd, &msg, 0)); + sys_close(fds[1]); + + char b; + HANDLE_EINTR(sys_read(fds[0], &b, 1)); + + return true; +} + +TEST(ExceptionHandlerTest, ExternalDumper) { + int fds[2]; + ASSERT_NE(socketpair(AF_UNIX, SOCK_DGRAM, 0, fds), -1); + static const int on = 1; + setsockopt(fds[0], SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)); + setsockopt(fds[1], SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)); + + const pid_t child = fork(); + if (child == 0) { + close(fds[0]); + ExceptionHandler handler("/tmp", NULL, NULL, (void*) fds[1], true); + handler.set_crash_handler(CrashHandler); + *reinterpret_cast<int*>(NULL) = 0; + } + + close(fds[1]); + struct msghdr msg = {0}; + struct iovec iov; + static const unsigned kCrashContextSize = + sizeof(ExceptionHandler::CrashContext); + char context[kCrashContextSize]; + char control[kControlMsgSize]; + iov.iov_base = context; + iov.iov_len = kCrashContextSize; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control; + msg.msg_controllen = kControlMsgSize; + + const ssize_t n = HANDLE_EINTR(recvmsg(fds[0], &msg, 0)); + ASSERT_EQ(n, kCrashContextSize); + ASSERT_EQ(msg.msg_controllen, kControlMsgSize); + ASSERT_EQ(msg.msg_flags, 0); + + pid_t crashing_pid = -1; + int signal_fd = -1; + for (struct cmsghdr *hdr = CMSG_FIRSTHDR(&msg); hdr; + hdr = CMSG_NXTHDR(&msg, hdr)) { + if (hdr->cmsg_level != SOL_SOCKET) + continue; + if (hdr->cmsg_type == SCM_RIGHTS) { + const unsigned len = hdr->cmsg_len - + (((uint8_t*)CMSG_DATA(hdr)) - (uint8_t*)hdr); + ASSERT_EQ(len, sizeof(int)); + signal_fd = *((int *) CMSG_DATA(hdr)); + } else if (hdr->cmsg_type == SCM_CREDENTIALS) { + const struct ucred *cred = + reinterpret_cast<struct ucred*>(CMSG_DATA(hdr)); + crashing_pid = cred->pid; + } + } + + ASSERT_NE(crashing_pid, -1); + ASSERT_NE(signal_fd, -1); + + char templ[] = "/tmp/exception-handler-unittest-XXXXXX"; + mktemp(templ); + ASSERT_TRUE(WriteMinidump(templ, crashing_pid, context, + kCrashContextSize)); + static const char b = 0; + HANDLE_EINTR(write(signal_fd, &b, 1)); + + int status; + ASSERT_NE(HANDLE_EINTR(waitpid(child, &status, 0)), -1); + ASSERT_TRUE(WIFSIGNALED(status)); + ASSERT_EQ(WTERMSIG(status), SIGSEGV); + + struct stat st; + ASSERT_EQ(stat(templ, &st), 0); + ASSERT_GT(st.st_size, 0u); + unlink(templ); +} diff --git a/src/client/linux/handler/linux_thread.cc b/src/client/linux/handler/linux_thread.cc index c8ac4926..e69de29b 100644 --- a/src/client/linux/handler/linux_thread.cc +++ b/src/client/linux/handler/linux_thread.cc @@ -1,411 +0,0 @@ -// 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 <errno.h> -#include <dirent.h> -#include <fcntl.h> -#include <sys/ptrace.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> -#include <sys/wait.h> -#include <string.h> - -#include <algorithm> -#include <cassert> -#include <cstdio> -#include <cstdlib> -#include <functional> - -#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<AddressValidatingContext *>(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<void*>(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<ThreadCallback> *thread_callback = - reinterpret_cast<CallbackParam<ThreadCallback> *>(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<PidCallback> 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<PidCallback> callback_param(ResumeThread, NULL); - IterateProcSelfTask(pid_, &callback_param); -} - -int LinuxThread::GetThreadCount() const { - return IterateProcSelfTask(pid_, NULL); -} - -int LinuxThread::ListThreads( - CallbackParam<ThreadCallback> *thread_callback_param) const { - CallbackParam<PidCallback> 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<void*>(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<void **>(current_ebp); - void **previous_sp = sp; - while (sp && IsAddressMapped((uintptr_t)sp)) { - previous_sp = sp; - sp = reinterpret_cast<void **>(GetNextFrame(sp)); - } - return (uintptr_t)previous_sp; -} - -int LinuxThread::GetModuleCount() const { - return ListModules(NULL); -} - -int LinuxThread::ListModules( - CallbackParam<ModuleCallback> *callback_param) const { - char line[512]; - const 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<PidCallback> *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<ModuleCallback> callback_param(IsAddressInModuleCallback, - &addr); - ListModules(&callback_param); - return addr.is_mapped; -} - -bool LinuxThread::FindSigContext(uintptr_t sighandler_ebp, - struct sigcontext **sig_ctx) { - uintptr_t previous_ebp; - const int MAX_STACK_DEPTH = 10; - int depth_counter = 0; - - do { - // We're looking for a |struct sigcontext| as the second parameter - // to a signal handler function call. Luckily, the sigcontext - // has an ebp member which should match the ebp pointed to - // by the ebp of the signal handler frame. - previous_ebp = reinterpret_cast<uintptr_t>(GetNextFrame( - reinterpret_cast<void**>(sighandler_ebp))); - // The stack looks like this: - // | previous ebp | previous eip | first param | second param |, - // so we need to offset by 3 to get to the second parameter. - *sig_ctx = reinterpret_cast<struct sigcontext*>(sighandler_ebp + - 3 * sizeof(uintptr_t)); - sighandler_ebp = previous_ebp; - depth_counter++; - } while(previous_ebp != (*sig_ctx)->ebp && sighandler_ebp != 0 && - IsAddressMapped(sighandler_ebp) && depth_counter < MAX_STACK_DEPTH); - - return previous_ebp == (*sig_ctx)->ebp && previous_ebp != 0; -} - -} // namespace google_breakpad diff --git a/src/client/linux/handler/linux_thread.h b/src/client/linux/handler/linux_thread.h deleted file mode 100644 index f738c2e0..00000000 --- a/src/client/linux/handler/linux_thread.h +++ /dev/null @@ -1,204 +0,0 @@ -// 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. -// -#ifndef CLIENT_LINUX_HANDLER_LINUX_THREAD_H__ -#define CLIENT_LINUX_HANDLER_LINUX_THREAD_H__ - -#include <stdint.h> -#include <sys/user.h> - -namespace google_breakpad { - -// Max module path name length. -#define kMaxModuleNameLength 256 - -// Holding information about a thread in the process. -struct ThreadInfo { - // Id of the thread group. - int tgid; - // Id of the thread. - int pid; - // Id of the parent process. - int ppid; -}; - -// Holding infomaton about a module in the process. -struct ModuleInfo { - char name[kMaxModuleNameLength]; - uintptr_t start_addr; - int size; -}; - -// Holding debug registers. -struct DebugRegs { - int dr0; - int dr1; - int dr2; - int dr3; - int dr4; - int dr5; - int dr6; - int dr7; -}; - -// A callback to run when got a thread in the process. -// Return true will go on to the next thread while return false will stop the -// iteration. -typedef bool (*ThreadCallback)(const ThreadInfo &thread_info, void *context); - -// A callback to run when a new module is found in the process. -// Return true will go on to the next module while return false will stop the -// iteration. -typedef bool (*ModuleCallback)(const ModuleInfo &module_info, void *context); - -// Holding the callback information. -template<class CallbackFunc> -struct CallbackParam { - // Callback function address. - CallbackFunc call_back; - // Callback context; - void *context; - - CallbackParam() : call_back(NULL), context(NULL) { - } - - CallbackParam(CallbackFunc func, void *func_context) : - call_back(func), context(func_context) { - } -}; - -/////////////////////////////////////////////////////////////////////////////// - -// -// LinuxThread -// -// Provides handy support for operation on linux threads. -// It uses ptrace to get thread registers. Since ptrace only works in a -// different process other than the one being ptraced, user of this class -// should create another process before using the class. -// -// The process should be created in the following way: -// int cloned_pid = clone(ProcessEntryFunction, stack_address, -// CLONE_VM | CLONE_FILES | CLONE_FS | CLONE_UNTRACED, -// (void*)&arguments); -// waitpid(cloned_pid, NULL, __WALL); -// -// If CLONE_VM is not used, GetThreadStackBottom, GetThreadStackDump -// will not work since it just use memcpy to get the stack dump. -// -class LinuxThread { - public: - // Create a LinuxThread instance to list all the threads in a process. - explicit LinuxThread(int pid); - ~LinuxThread(); - - // Stop all the threads in the process. - // Return the number of stopped threads in the process. - // Return -1 means failed to stop threads. - int SuspendAllThreads(); - - // Resume all the suspended threads. - void ResumeAllThreads() const; - - // Get the count of threads in the process. - // Return -1 means error. - int GetThreadCount() const; - - // List the threads of process. - // Whenever there is a thread found, the callback will be invoked to process - // the information. - // Return number of threads listed. - int ListThreads(CallbackParam<ThreadCallback> *thread_callback_param) const; - - // Get the general purpose registers of a thread. - // The caller must get the thread pid by ListThreads. - bool GetRegisters(int pid, user_regs_struct *regs) const; - - // Get the floating-point registers of a thread. - // The caller must get the thread pid by ListThreads. - bool GetFPRegisters(int pid, user_fpregs_struct *regs) const; - - // Get all the extended floating-point registers. May not work on all - // machines. - // The caller must get the thread pid by ListThreads. - bool GetFPXRegisters(int pid, user_fpxregs_struct *regs) const; - - // Get the debug registers. - // The caller must get the thread pid by ListThreads. - bool GetDebugRegisters(int pid, DebugRegs *regs) const; - - // Get the stack memory dump. - int GetThreadStackDump(uintptr_t current_ebp, - uintptr_t current_esp, - void *buf, - int buf_size) const; - - // Get the module count of the current process. - int GetModuleCount() const; - - // Get the mapped modules in the address space. - // Whenever a module is found, the callback will be invoked to process the - // information. - // Return how may modules are found. - int ListModules(CallbackParam<ModuleCallback> *callback_param) const; - - // Get the bottom of the stack from ebp. - uintptr_t GetThreadStackBottom(uintptr_t current_ebp) const; - - // Finds a sigcontext on the stack given the ebp of our signal handler. - bool FindSigContext(uintptr_t sighandler_ebp, struct sigcontext **sig_ctx); - - private: - // This callback will run when a new thread has been found. - typedef bool (*PidCallback)(int pid, void *context); - - // Read thread information from /proc/$pid/task. - // Whenever a thread has been found, and callback will be invoked with - // the pid of the thread. - // Return number of threads found. - // Return -1 means the directory doesn't exist. - int IterateProcSelfTask(int pid, - CallbackParam<PidCallback> *callback_param) const; - - // Check if the address is a valid virtual address. - bool IsAddressMapped(uintptr_t address) const; - - private: - // The pid of the process we are listing threads. - int pid_; - - // Mark if we have suspended the threads. - bool threads_suspened_; -}; - -} // namespace google_breakpad - -#endif // CLIENT_LINUX_HANDLER_LINUX_THREAD_H__ diff --git a/src/client/linux/handler/linux_thread_test.cc b/src/client/linux/handler/linux_thread_test.cc deleted file mode 100644 index aeb5e64c..00000000 --- a/src/client/linux/handler/linux_thread_test.cc +++ /dev/null @@ -1,224 +0,0 @@ -// 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 <pthread.h> -#include <sys/types.h> -#include <unistd.h> -#include <sys/wait.h> - -#include <cstdio> -#include <cstdlib> -#include <cstring> - -#include "client/linux/handler/linux_thread.h" - -using namespace google_breakpad; - -// Thread use this to see if it should stop working. -static bool should_exit = false; - -static void foo2(int *a) { - // Stack variable, used for debugging stack dumps. - int c = 0xcccccccc; - c = c; - while (!should_exit) - sleep(1); -} - -static void foo() { - // Stack variable, used for debugging stack dumps. - int a = 0xaaaaaaaa; - foo2(&a); -} - -static void *thread_main(void *) { - // Stack variable, used for debugging stack dumps. - int b = 0xbbbbbbbb; - b = b; - while (!should_exit) { - foo(); - } - return NULL; -} - -static void CreateThreads(int num) { - pthread_t handle; - for (int i = 0; i < num; i++) { - if (0 != pthread_create(&handle, NULL, thread_main, NULL)) - fprintf(stderr, "Failed to create thread.\n"); - else - pthread_detach(handle); - } -} - -static bool ProcessOneModule(const struct ModuleInfo &module_info, - void *context) { - printf("0x%x[%8d] %s\n", module_info.start_addr, module_info.size, - module_info.name); - return true; -} - -static bool ProcessOneThread(const struct ThreadInfo &thread_info, - void *context) { - printf("\n\nPID: %d, TGID: %d, PPID: %d\n", - thread_info.pid, - thread_info.tgid, - thread_info.ppid); - - struct user_regs_struct regs; - struct user_fpregs_struct fp_regs; - struct user_fpxregs_struct fpx_regs; - struct DebugRegs dbg_regs; - - LinuxThread *threads = reinterpret_cast<LinuxThread *>(context); - memset(®s, 0, sizeof(regs)); - if (threads->GetRegisters(thread_info.pid, ®s)) { - printf(" gs = 0x%lx\n", regs.xgs); - printf(" fs = 0x%lx\n", regs.xfs); - printf(" es = 0x%lx\n", regs.xes); - printf(" ds = 0x%lx\n", regs.xds); - printf(" edi = 0x%lx\n", regs.edi); - printf(" esi = 0x%lx\n", regs.esi); - printf(" ebx = 0x%lx\n", regs.ebx); - printf(" edx = 0x%lx\n", regs.edx); - printf(" ecx = 0x%lx\n", regs.ecx); - printf(" eax = 0x%lx\n", regs.eax); - printf(" ebp = 0x%lx\n", regs.ebp); - printf(" eip = 0x%lx\n", regs.eip); - printf(" cs = 0x%lx\n", regs.xcs); - printf(" eflags = 0x%lx\n", regs.eflags); - printf(" esp = 0x%lx\n", regs.esp); - printf(" ss = 0x%lx\n", regs.xss); - } else { - fprintf(stderr, "ERROR: Failed to get general purpose registers\n"); - } - memset(&fp_regs, 0, sizeof(fp_regs)); - if (threads->GetFPRegisters(thread_info.pid, &fp_regs)) { - printf("\n Floating point registers:\n"); - printf(" fctl = 0x%lx\n", fp_regs.cwd); - printf(" fstat = 0x%lx\n", fp_regs.swd); - printf(" ftag = 0x%lx\n", fp_regs.twd); - printf(" fioff = 0x%lx\n", fp_regs.fip); - printf(" fiseg = 0x%lx\n", fp_regs.fcs); - printf(" fooff = 0x%lx\n", fp_regs.foo); - printf(" foseg = 0x%lx\n", fp_regs.fos); - int st_space_size = sizeof(fp_regs.st_space) / sizeof(fp_regs.st_space[0]); - printf(" st_space[%2d] = 0x", st_space_size); - for (int i = 0; i < st_space_size; ++i) - printf("%02lx", fp_regs.st_space[i]); - printf("\n"); - } else { - fprintf(stderr, "ERROR: Failed to get floating-point registers\n"); - } - memset(&fpx_regs, 0, sizeof(fpx_regs)); - if (threads->GetFPXRegisters(thread_info.pid, &fpx_regs)) { - printf("\n Extended floating point registers:\n"); - printf(" fctl = 0x%x\n", fpx_regs.cwd); - printf(" fstat = 0x%x\n", fpx_regs.swd); - printf(" ftag = 0x%x\n", fpx_regs.twd); - printf(" fioff = 0x%lx\n", fpx_regs.fip); - printf(" fiseg = 0x%lx\n", fpx_regs.fcs); - printf(" fooff = 0x%lx\n", fpx_regs.foo); - printf(" foseg = 0x%lx\n", fpx_regs.fos); - printf(" fop = 0x%x\n", fpx_regs.fop); - printf(" mxcsr = 0x%lx\n", fpx_regs.mxcsr); - int space_size = sizeof(fpx_regs.st_space) / sizeof(fpx_regs.st_space[0]); - printf(" st_space[%2d] = 0x", space_size); - for (int i = 0; i < space_size; ++i) - printf("%02lx", fpx_regs.st_space[i]); - printf("\n"); - space_size = sizeof(fpx_regs.xmm_space) / sizeof(fpx_regs.xmm_space[0]); - printf(" xmm_space[%2d] = 0x", space_size); - for (int i = 0; i < space_size; ++i) - printf("%02lx", fpx_regs.xmm_space[i]); - printf("\n"); - } - if (threads->GetDebugRegisters(thread_info.pid, &dbg_regs)) { - printf("\n Debug registers:\n"); - printf(" dr0 = 0x%x\n", dbg_regs.dr0); - printf(" dr1 = 0x%x\n", dbg_regs.dr1); - printf(" dr2 = 0x%x\n", dbg_regs.dr2); - printf(" dr3 = 0x%x\n", dbg_regs.dr3); - printf(" dr4 = 0x%x\n", dbg_regs.dr4); - printf(" dr5 = 0x%x\n", dbg_regs.dr5); - printf(" dr6 = 0x%x\n", dbg_regs.dr6); - printf(" dr7 = 0x%x\n", dbg_regs.dr7); - printf("\n"); - } - if (regs.esp != 0) { - // Print the stack content. - int size = 1024 * 2; - char *buf = new char[size]; - size = threads->GetThreadStackDump(regs.ebp, - regs.esp, - (void*)buf, size); - printf(" Stack content: = 0x"); - size /= sizeof(unsigned long); - unsigned long *p_buf = (unsigned long *)(buf); - for (int i = 0; i < size; i += 1) - printf("%.8lx ", p_buf[i]); - delete []buf; - printf("\n"); - } - return true; -} - -static int PrintAllThreads(void *argument) { - int pid = (int)argument; - - LinuxThread threads(pid); - int total_thread = threads.SuspendAllThreads(); - printf("There are %d threads in the process: %d\n", total_thread, pid); - int total_module = threads.GetModuleCount(); - printf("There are %d modules in the process: %d\n", total_module, pid); - CallbackParam<ModuleCallback> module_callback(ProcessOneModule, &threads); - threads.ListModules(&module_callback); - CallbackParam<ThreadCallback> thread_callback(ProcessOneThread, &threads); - threads.ListThreads(&thread_callback); - return 0; -} - -int main(int argc, char **argv) { - int pid = getpid(); - printf("Main thread is %d\n", pid); - CreateThreads(1); - // Create stack for the process. - char *stack = new char[1024 * 100]; - int cloned_pid = clone(PrintAllThreads, stack + 1024 * 100, - CLONE_VM | CLONE_FILES | CLONE_FS | CLONE_UNTRACED, - (void*)getpid()); - waitpid(cloned_pid, NULL, __WALL); - should_exit = true; - printf("Test finished.\n"); - - delete []stack; - return 0; -} diff --git a/src/client/linux/handler/minidump_generator.cc b/src/client/linux/handler/minidump_generator.cc deleted file mode 100644 index d172092c..00000000 --- a/src/client/linux/handler/minidump_generator.cc +++ /dev/null @@ -1,816 +0,0 @@ -// 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 <fcntl.h> -#include <pthread.h> -#include <signal.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> -#include <sys/utsname.h> -#include <sys/wait.h> - -#include <cstdlib> -#include <cstdio> -#include <ctime> -#include <string.h> - -#include "common/linux/file_id.h" -#include "client/linux/handler/linux_thread.h" -#include "client/minidump_file_writer.h" -#include "client/minidump_file_writer-inl.h" -#include "google_breakpad/common/minidump_format.h" -#include "client/linux/handler/minidump_generator.h" - -#ifndef CLONE_UNTRACED -#define CLONE_UNTRACED 0x00800000 -#endif - -// This unnamed namespace contains helper functions. -namespace { - -using namespace google_breakpad; - -// Argument for the writer function. -struct WriterArgument { - MinidumpFileWriter *minidump_writer; - - // Context for the callback. - void *version_context; - - // Pid of the thread who called WriteMinidumpToFile - int requester_pid; - - // The stack bottom of the thread which caused the dump. - // Mainly used to find the thread id of the crashed thread since signal - // handler may not be called in the thread who caused it. - uintptr_t crashed_stack_bottom; - - // Pid of the crashing thread. - int crashed_pid; - - // Signal number when crash happed. Can be 0 if this is a requested dump. - int signo; - - // The ebp of the signal handler frame. Can be zero if this - // is a requested dump. - uintptr_t sighandler_ebp; - - // Signal context when crash happed. Can be NULL if this is a requested dump. - // This is actually an out parameter, but it will be filled in at the start - // of the writer thread. - struct sigcontext *sig_ctx; - - // Used to get information about the threads. - LinuxThread *thread_lister; -}; - -// Holding context information for the callback of finding the crashing thread. -struct FindCrashThreadContext { - const LinuxThread *thread_lister; - uintptr_t crashing_stack_bottom; - int crashing_thread_pid; - - FindCrashThreadContext() : - thread_lister(NULL), - crashing_stack_bottom(0UL), - crashing_thread_pid(-1) { - } -}; - -// Callback for list threads. -// It will compare the stack bottom of the provided thread with the stack -// bottom of the crashed thread, it they are eqaul, this is thread is the one -// who crashed. -bool IsThreadCrashedCallback(const ThreadInfo &thread_info, void *context) { - FindCrashThreadContext *crashing_context = - static_cast<FindCrashThreadContext *>(context); - const LinuxThread *thread_lister = crashing_context->thread_lister; - struct user_regs_struct regs; - if (thread_lister->GetRegisters(thread_info.pid, ®s)) { - uintptr_t last_ebp = regs.ebp; - uintptr_t stack_bottom = thread_lister->GetThreadStackBottom(last_ebp); - if (stack_bottom > last_ebp && - stack_bottom == crashing_context->crashing_stack_bottom) { - // Got it. Stop iteration. - crashing_context->crashing_thread_pid = thread_info.pid; - return false; - } - } - return true; -} - -// Find the crashing thread id. -// This is done based on stack bottom comparing. -int FindCrashingThread(uintptr_t crashing_stack_bottom, - int requester_pid, - const LinuxThread *thread_lister) { - FindCrashThreadContext context; - context.thread_lister = thread_lister; - context.crashing_stack_bottom = crashing_stack_bottom; - CallbackParam<ThreadCallback> callback_param(IsThreadCrashedCallback, - &context); - thread_lister->ListThreads(&callback_param); - return context.crashing_thread_pid; -} - -// Write the thread stack info minidump. -bool WriteThreadStack(uintptr_t last_ebp, - uintptr_t last_esp, - const LinuxThread *thread_lister, - UntypedMDRVA *memory, - MDMemoryDescriptor *loc) { - // Maximum stack size for a thread. - uintptr_t stack_bottom = thread_lister->GetThreadStackBottom(last_ebp); - if (stack_bottom > last_esp) { - int size = stack_bottom - last_esp; - if (size > 0) { - if (!memory->Allocate(size)) - return false; - memory->Copy(reinterpret_cast<void*>(last_esp), size); - loc->start_of_memory_range = 0 | last_esp; - loc->memory = memory->location(); - } - return true; - } - return false; -} - -// Write CPU context based on signal context. -bool WriteContext(MDRawContextX86 *context, const struct sigcontext *sig_ctx, - const DebugRegs *debug_regs) { - assert(sig_ctx != NULL); - context->context_flags = MD_CONTEXT_X86_FULL; - context->gs = sig_ctx->gs; - context->fs = sig_ctx->fs; - context->es = sig_ctx->es; - context->ds = sig_ctx->ds; - context->cs = sig_ctx->cs; - context->ss = sig_ctx->ss; - context->edi = sig_ctx->edi; - context->esi = sig_ctx->esi; - context->ebp = sig_ctx->ebp; - context->esp = sig_ctx->esp; - context->ebx = sig_ctx->ebx; - context->edx = sig_ctx->edx; - context->ecx = sig_ctx->ecx; - context->eax = sig_ctx->eax; - context->eip = sig_ctx->eip; - context->eflags = sig_ctx->eflags; - if (sig_ctx->fpstate != NULL) { - context->context_flags = MD_CONTEXT_X86_FULL | - MD_CONTEXT_X86_FLOATING_POINT; - context->float_save.control_word = sig_ctx->fpstate->cw; - context->float_save.status_word = sig_ctx->fpstate->sw; - context->float_save.tag_word = sig_ctx->fpstate->tag; - context->float_save.error_offset = sig_ctx->fpstate->ipoff; - context->float_save.error_selector = sig_ctx->fpstate->cssel; - context->float_save.data_offset = sig_ctx->fpstate->dataoff; - context->float_save.data_selector = sig_ctx->fpstate->datasel; - memcpy(context->float_save.register_area, sig_ctx->fpstate->_st, - sizeof(context->float_save.register_area)); - } - - if (debug_regs != NULL) { - context->context_flags |= MD_CONTEXT_X86_DEBUG_REGISTERS; - context->dr0 = debug_regs->dr0; - context->dr1 = debug_regs->dr1; - context->dr2 = debug_regs->dr2; - context->dr3 = debug_regs->dr3; - context->dr6 = debug_regs->dr6; - context->dr7 = debug_regs->dr7; - } - return true; -} - -// Write CPU context based on provided registers. -bool WriteContext(MDRawContextX86 *context, - const struct user_regs_struct *regs, - const struct user_fpregs_struct *fp_regs, - const DebugRegs *dbg_regs) { - if (!context || !regs) - return false; - - context->context_flags = MD_CONTEXT_X86_FULL; - - context->cs = regs->xcs; - context->ds = regs->xds; - context->es = regs->xes; - context->fs = regs->xfs; - context->gs = regs->xgs; - context->ss = regs->xss; - context->edi = regs->edi; - context->esi = regs->esi; - context->ebx = regs->ebx; - context->edx = regs->edx; - context->ecx = regs->ecx; - context->eax = regs->eax; - context->ebp = regs->ebp; - context->eip = regs->eip; - context->esp = regs->esp; - context->eflags = regs->eflags; - - if (dbg_regs != NULL) { - context->context_flags |= MD_CONTEXT_X86_DEBUG_REGISTERS; - context->dr0 = dbg_regs->dr0; - context->dr1 = dbg_regs->dr1; - context->dr2 = dbg_regs->dr2; - context->dr3 = dbg_regs->dr3; - context->dr6 = dbg_regs->dr6; - context->dr7 = dbg_regs->dr7; - } - - if (fp_regs != NULL) { - context->context_flags |= MD_CONTEXT_X86_FLOATING_POINT; - context->float_save.control_word = fp_regs->cwd; - context->float_save.status_word = fp_regs->swd; - context->float_save.tag_word = fp_regs->twd; - context->float_save.error_offset = fp_regs->fip; - context->float_save.error_selector = fp_regs->fcs; - context->float_save.data_offset = fp_regs->foo; - context->float_save.data_selector = fp_regs->fos; - context->float_save.data_selector = fp_regs->fos; - - memcpy(context->float_save.register_area, fp_regs->st_space, - sizeof(context->float_save.register_area)); - } - return true; -} - -// Write information about a crashed thread. -// When a thread crash, kernel will write something on the stack for processing -// signal. This makes the current stack not reliable, and our stack walker -// won't figure out the whole call stack for this. So we write the stack at the -// time of the crash into the minidump file, not the current stack. -bool WriteCrashedThreadStream(MinidumpFileWriter *minidump_writer, - const WriterArgument *writer_args, - const ThreadInfo &thread_info, - MDRawThread *thread) { - assert(writer_args->sig_ctx != NULL); - - thread->thread_id = thread_info.pid; - - UntypedMDRVA memory(minidump_writer); - if (!WriteThreadStack(writer_args->sig_ctx->ebp, - writer_args->sig_ctx->esp, - writer_args->thread_lister, - &memory, - &thread->stack)) - return false; - - TypedMDRVA<MDRawContextX86> context(minidump_writer); - if (!context.Allocate()) - return false; - thread->thread_context = context.location(); - memset(context.get(), 0, sizeof(MDRawContextX86)); - return WriteContext(context.get(), writer_args->sig_ctx, NULL); -} - -// Write information about a thread. -// This function only processes thread running normally at the crash. -bool WriteThreadStream(MinidumpFileWriter *minidump_writer, - const LinuxThread *thread_lister, - const ThreadInfo &thread_info, - MDRawThread *thread) { - thread->thread_id = thread_info.pid; - - struct user_regs_struct regs; - memset(®s, 0, sizeof(regs)); - if (!thread_lister->GetRegisters(thread_info.pid, ®s)) { - perror(NULL); - return false; - } - - UntypedMDRVA memory(minidump_writer); - if (!WriteThreadStack(regs.ebp, - regs.esp, - thread_lister, - &memory, - &thread->stack)) - return false; - - struct user_fpregs_struct fp_regs; - DebugRegs dbg_regs; - memset(&fp_regs, 0, sizeof(fp_regs)); - // Get all the registers. - thread_lister->GetFPRegisters(thread_info.pid, &fp_regs); - thread_lister->GetDebugRegisters(thread_info.pid, &dbg_regs); - - // Write context - TypedMDRVA<MDRawContextX86> context(minidump_writer); - if (!context.Allocate()) - return false; - thread->thread_context = context.location(); - memset(context.get(), 0, sizeof(MDRawContextX86)); - return WriteContext(context.get(), ®s, &fp_regs, &dbg_regs); -} - -bool WriteCPUInformation(MDRawSystemInfo *sys_info) { - const char *proc_cpu_path = "/proc/cpuinfo"; - char line[128]; - char vendor_id[13]; - const char vendor_id_name[] = "vendor_id"; - const size_t vendor_id_name_length = sizeof(vendor_id_name) - 1; - - struct CpuInfoEntry { - const char *info_name; - int value; - } cpu_info_table[] = { - { "processor", -1 }, - { "model", 0 }, - { "stepping", 0 }, - { "cpuid level", 0 }, - { NULL, -1 }, - }; - - memset(vendor_id, 0, sizeof(vendor_id)); - - FILE *fp = fopen(proc_cpu_path, "r"); - if (fp != NULL) { - while (fgets(line, sizeof(line), fp)) { - CpuInfoEntry *entry = &cpu_info_table[0]; - while (entry->info_name != NULL) { - if (!strncmp(line, entry->info_name, strlen(entry->info_name))) { - char *value = strchr(line, ':'); - value++; - if (value != NULL) - sscanf(value, " %d", &(entry->value)); - } - entry++; - } - - // special case for vendor_id - if (!strncmp(line, vendor_id_name, vendor_id_name_length)) { - char *value = strchr(line, ':'); - if (value == NULL) - continue; - - value++; - while (*value && isspace(*value)) - value++; - if (*value) { - size_t length = strlen(value); - // we don't want the trailing newline - if (value[length - 1] == '\n') - length--; - // ensure we have space for the value - if (length < sizeof(vendor_id)) - strncpy(vendor_id, value, length); - } - } - } - fclose(fp); - } - - // /proc/cpuinfo contains cpu id, change it into number by adding one. - cpu_info_table[0].value++; - - sys_info->number_of_processors = cpu_info_table[0].value; - sys_info->processor_level = cpu_info_table[3].value; - sys_info->processor_revision = cpu_info_table[1].value << 8 | - cpu_info_table[2].value; - - sys_info->processor_architecture = MD_CPU_ARCHITECTURE_UNKNOWN; - struct utsname uts; - if (uname(&uts) == 0) { - // Match i*86 and x86* as X86 architecture. - if ((strstr(uts.machine, "x86") == uts.machine) || - (strlen(uts.machine) == 4 && - uts.machine[0] == 'i' && - uts.machine[2] == '8' && - uts.machine[3] == '6')) { - sys_info->processor_architecture = MD_CPU_ARCHITECTURE_X86; - if (vendor_id[0] != '\0') - memcpy(sys_info->cpu.x86_cpu_info.vendor_id, vendor_id, - sizeof(sys_info->cpu.x86_cpu_info.vendor_id)); - } - } - return true; -} - -bool WriteOSInformation(MinidumpFileWriter *minidump_writer, - MDRawSystemInfo *sys_info) { - sys_info->platform_id = MD_OS_LINUX; - - struct utsname uts; - if (uname(&uts) == 0) { - char os_version[512]; - size_t space_left = sizeof(os_version); - memset(os_version, 0, space_left); - const char *os_info_table[] = { - uts.sysname, - uts.release, - uts.version, - uts.machine, - "GNU/Linux", - NULL - }; - for (const char **cur_os_info = os_info_table; - *cur_os_info != NULL; - cur_os_info++) { - if (cur_os_info != os_info_table && space_left > 1) { - strcat(os_version, " "); - space_left--; - } - if (space_left > strlen(*cur_os_info)) { - strcat(os_version, *cur_os_info); - space_left -= strlen(*cur_os_info); - } else { - break; - } - } - - MDLocationDescriptor location; - if (!minidump_writer->WriteString(os_version, 0, &location)) - return false; - sys_info->csd_version_rva = location.rva; - } - return true; -} - -// Callback context for get writting thread information. -struct ThreadInfoCallbackCtx { - MinidumpFileWriter *minidump_writer; - const WriterArgument *writer_args; - TypedMDRVA<MDRawThreadList> *list; - int thread_index; -}; - -// Callback run for writing threads information in the process. -bool ThreadInfomationCallback(const ThreadInfo &thread_info, - void *context) { - ThreadInfoCallbackCtx *callback_context = - static_cast<ThreadInfoCallbackCtx *>(context); - bool success = true; - MDRawThread thread; - memset(&thread, 0, sizeof(MDRawThread)); - if (thread_info.pid != callback_context->writer_args->crashed_pid || - callback_context->writer_args->sig_ctx == NULL) { - success = WriteThreadStream(callback_context->minidump_writer, - callback_context->writer_args->thread_lister, - thread_info, &thread); - } else { - success = WriteCrashedThreadStream(callback_context->minidump_writer, - callback_context->writer_args, - thread_info, &thread); - } - if (success) { - callback_context->list->CopyIndexAfterObject( - callback_context->thread_index++, - &thread, sizeof(MDRawThread)); - } - return success; -} - -// Stream writers -bool WriteThreadListStream(MinidumpFileWriter *minidump_writer, - const WriterArgument *writer_args, - MDRawDirectory *dir) { - // Get the thread information. - const LinuxThread *thread_lister = writer_args->thread_lister; - int thread_count = thread_lister->GetThreadCount(); - if (thread_count < 0) - return false; - TypedMDRVA<MDRawThreadList> list(minidump_writer); - if (!list.AllocateObjectAndArray(thread_count, sizeof(MDRawThread))) - return false; - dir->stream_type = MD_THREAD_LIST_STREAM; - dir->location = list.location(); - list.get()->number_of_threads = thread_count; - - ThreadInfoCallbackCtx context; - context.minidump_writer = minidump_writer; - context.writer_args = writer_args; - context.list = &list; - context.thread_index = 0; - CallbackParam<ThreadCallback> callback_param(ThreadInfomationCallback, - &context); - int written = thread_lister->ListThreads(&callback_param); - return written == thread_count; -} - -bool WriteCVRecord(MinidumpFileWriter *minidump_writer, - MDRawModule *module, - const char *module_path) { - TypedMDRVA<MDCVInfoPDB70> cv(minidump_writer); - - // Only return the last path component of the full module path - const char *module_name = strrchr(module_path, '/'); - // Increment past the slash - if (module_name) - ++module_name; - else - module_name = "<Unknown>"; - - size_t module_name_length = strlen(module_name); - if (!cv.AllocateObjectAndArray(module_name_length + 1, sizeof(u_int8_t))) - return false; - if (!cv.CopyIndexAfterObject(0, const_cast<char *>(module_name), - module_name_length)) - return false; - - module->cv_record = cv.location(); - MDCVInfoPDB70 *cv_ptr = cv.get(); - memset(cv_ptr, 0, sizeof(MDCVInfoPDB70)); - cv_ptr->cv_signature = MD_CVINFOPDB70_SIGNATURE; - cv_ptr->age = 0; - - // Get the module identifier - FileID file_id(module_path); - unsigned char identifier[16]; - - if (file_id.ElfFileIdentifier(identifier)) { - cv_ptr->signature.data1 = (uint32_t)identifier[0] << 24 | - (uint32_t)identifier[1] << 16 | (uint32_t)identifier[2] << 8 | - (uint32_t)identifier[3]; - cv_ptr->signature.data2 = (uint32_t)identifier[4] << 8 | identifier[5]; - cv_ptr->signature.data3 = (uint32_t)identifier[6] << 8 | identifier[7]; - cv_ptr->signature.data4[0] = identifier[8]; - cv_ptr->signature.data4[1] = identifier[9]; - cv_ptr->signature.data4[2] = identifier[10]; - cv_ptr->signature.data4[3] = identifier[11]; - cv_ptr->signature.data4[4] = identifier[12]; - cv_ptr->signature.data4[5] = identifier[13]; - cv_ptr->signature.data4[6] = identifier[14]; - cv_ptr->signature.data4[7] = identifier[15]; - } - return true; -} - -struct ModuleInfoCallbackCtx { - MinidumpFileWriter *minidump_writer; - const WriterArgument *writer_args; - TypedMDRVA<MDRawModuleList> *list; - int module_index; -}; - -bool ModuleInfoCallback(const ModuleInfo &module_info, - void *context) { - ModuleInfoCallbackCtx *callback_context = - static_cast<ModuleInfoCallbackCtx *>(context); - // Skip those modules without name, or those that are not modules. - if (strlen(module_info.name) == 0 || - !strchr(module_info.name, '/')) - return true; - - MDRawModule module; - memset(&module, 0, sizeof(module)); - MDLocationDescriptor loc; - if (!callback_context->minidump_writer->WriteString(module_info.name, 0, - &loc)) - return false; - module.base_of_image = (u_int64_t)module_info.start_addr; - module.size_of_image = module_info.size; - module.module_name_rva = loc.rva; - - if (!WriteCVRecord(callback_context->minidump_writer, &module, - module_info.name)) - return false; - callback_context->list->CopyIndexAfterObject( - callback_context->module_index++, &module, MD_MODULE_SIZE); - return true; -} - -bool WriteModuleListStream(MinidumpFileWriter *minidump_writer, - const WriterArgument *writer_args, - MDRawDirectory *dir) { - TypedMDRVA<MDRawModuleList> list(minidump_writer); - int module_count = writer_args->thread_lister->GetModuleCount(); - if (module_count <= 0 || - !list.AllocateObjectAndArray(module_count, MD_MODULE_SIZE)) - return false; - dir->stream_type = MD_MODULE_LIST_STREAM; - dir->location = list.location(); - list.get()->number_of_modules = module_count; - ModuleInfoCallbackCtx context; - context.minidump_writer = minidump_writer; - context.writer_args = writer_args; - context.list = &list; - context.module_index = 0; - CallbackParam<ModuleCallback> callback(ModuleInfoCallback, &context); - return writer_args->thread_lister->ListModules(&callback) == module_count; -} - -bool WriteSystemInfoStream(MinidumpFileWriter *minidump_writer, - const WriterArgument *writer_args, - MDRawDirectory *dir) { - TypedMDRVA<MDRawSystemInfo> sys_info(minidump_writer); - if (!sys_info.Allocate()) - return false; - dir->stream_type = MD_SYSTEM_INFO_STREAM; - dir->location = sys_info.location(); - - return WriteCPUInformation(sys_info.get()) && - WriteOSInformation(minidump_writer, sys_info.get()); -} - -bool WriteExceptionStream(MinidumpFileWriter *minidump_writer, - const WriterArgument *writer_args, - MDRawDirectory *dir) { - // This happenes when this is not a crash, but a requested dump. - if (writer_args->sig_ctx == NULL) - return false; - - TypedMDRVA<MDRawExceptionStream> exception(minidump_writer); - if (!exception.Allocate()) - return false; - - dir->stream_type = MD_EXCEPTION_STREAM; - dir->location = exception.location(); - exception.get()->thread_id = writer_args->crashed_pid; - exception.get()->exception_record.exception_code = writer_args->signo; - exception.get()->exception_record.exception_flags = 0; - if (writer_args->sig_ctx != NULL) { - exception.get()->exception_record.exception_address = - writer_args->sig_ctx->eip; - } else { - return true; - } - - // Write context of the exception. - TypedMDRVA<MDRawContextX86> context(minidump_writer); - if (!context.Allocate()) - return false; - exception.get()->thread_context = context.location(); - memset(context.get(), 0, sizeof(MDRawContextX86)); - return WriteContext(context.get(), writer_args->sig_ctx, NULL); -} - -bool WriteMiscInfoStream(MinidumpFileWriter *minidump_writer, - const WriterArgument *writer_args, - MDRawDirectory *dir) { - TypedMDRVA<MDRawMiscInfo> info(minidump_writer); - if (!info.Allocate()) - return false; - - dir->stream_type = MD_MISC_INFO_STREAM; - dir->location = info.location(); - info.get()->size_of_info = sizeof(MDRawMiscInfo); - info.get()->flags1 = MD_MISCINFO_FLAGS1_PROCESS_ID; - info.get()->process_id = writer_args->requester_pid; - - return true; -} - -bool WriteBreakpadInfoStream(MinidumpFileWriter *minidump_writer, - const WriterArgument *writer_args, - MDRawDirectory *dir) { - TypedMDRVA<MDRawBreakpadInfo> info(minidump_writer); - if (!info.Allocate()) - return false; - - dir->stream_type = MD_BREAKPAD_INFO_STREAM; - dir->location = info.location(); - - info.get()->validity = MD_BREAKPAD_INFO_VALID_DUMP_THREAD_ID | - MD_BREAKPAD_INFO_VALID_REQUESTING_THREAD_ID; - info.get()->dump_thread_id = getpid(); - info.get()->requesting_thread_id = writer_args->requester_pid; - return true; -} - -// Prototype of writer functions. -typedef bool (*WriteStringFN)(MinidumpFileWriter *, - const WriterArgument *, - MDRawDirectory *); - -// Function table to writer a full minidump. -WriteStringFN writers[] = { - WriteThreadListStream, - WriteModuleListStream, - WriteSystemInfoStream, - WriteExceptionStream, - WriteMiscInfoStream, - WriteBreakpadInfoStream, -}; - -// Will call each writer function in the writers table. -// It runs in a different process from the crashing process, but sharing -// the same address space. This enables it to use ptrace functions. -int Write(void *argument) { - WriterArgument *writer_args = - static_cast<WriterArgument *>(argument); - - if (!writer_args->thread_lister->SuspendAllThreads()) - return -1; - - if (writer_args->sighandler_ebp != 0 && - writer_args->thread_lister->FindSigContext(writer_args->sighandler_ebp, - &writer_args->sig_ctx)) { - writer_args->crashed_stack_bottom = - writer_args->thread_lister->GetThreadStackBottom( - writer_args->sig_ctx->ebp); - int crashed_pid = FindCrashingThread(writer_args->crashed_stack_bottom, - writer_args->requester_pid, - writer_args->thread_lister); - if (crashed_pid > 0) - writer_args->crashed_pid = crashed_pid; - } - - - MinidumpFileWriter *minidump_writer = writer_args->minidump_writer; - TypedMDRVA<MDRawHeader> header(minidump_writer); - TypedMDRVA<MDRawDirectory> dir(minidump_writer); - if (!header.Allocate()) - return 0; - - int writer_count = sizeof(writers) / sizeof(writers[0]); - // Need directory space for all writers. - if (!dir.AllocateArray(writer_count)) - return 0; - header.get()->signature = MD_HEADER_SIGNATURE; - header.get()->version = MD_HEADER_VERSION; - header.get()->time_date_stamp = time(NULL); - header.get()->stream_count = writer_count; - header.get()->stream_directory_rva = dir.position(); - - int dir_index = 0; - MDRawDirectory local_dir; - for (int i = 0; i < writer_count; ++i) { - if (writers[i](minidump_writer, writer_args, &local_dir)) - dir.CopyIndex(dir_index++, &local_dir); - } - - writer_args->thread_lister->ResumeAllThreads(); - return 0; -} - -} // namespace - -namespace google_breakpad { - -MinidumpGenerator::MinidumpGenerator() { - AllocateStack(); -} - -MinidumpGenerator::~MinidumpGenerator() { -} - -void MinidumpGenerator::AllocateStack() { - stack_.reset(new char[kStackSize]); -} - -bool MinidumpGenerator::WriteMinidumpToFile(const char *file_pathname, - int signo, - uintptr_t sighandler_ebp, - struct sigcontext **sig_ctx) const { - assert(file_pathname != NULL); - assert(stack_ != NULL); - - if (stack_ == NULL || file_pathname == NULL) - return false; - - MinidumpFileWriter minidump_writer; - if (minidump_writer.Open(file_pathname)) { - WriterArgument argument; - memset(&argument, 0, sizeof(argument)); - LinuxThread thread_lister(getpid()); - argument.thread_lister = &thread_lister; - argument.minidump_writer = &minidump_writer; - argument.requester_pid = getpid(); - argument.crashed_pid = getpid(); - argument.signo = signo; - argument.sighandler_ebp = sighandler_ebp; - argument.sig_ctx = NULL; - - int cloned_pid = clone(Write, stack_.get() + kStackSize, - CLONE_VM | CLONE_FILES | CLONE_FS | CLONE_UNTRACED, - (void*)&argument); - waitpid(cloned_pid, NULL, __WALL); - if (sig_ctx != NULL) - *sig_ctx = argument.sig_ctx; - return true; - } - - return false; -} - -} // namespace google_breakpad diff --git a/src/client/linux/minidump_writer/directory_reader.h b/src/client/linux/minidump_writer/directory_reader.h new file mode 100644 index 00000000..4698b7e5 --- /dev/null +++ b/src/client/linux/minidump_writer/directory_reader.h @@ -0,0 +1,105 @@ +// Copyright (c) 2009, Google Inc. +// All rights reserved. +// +// 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. + +#ifndef CLIENT_LINUX_MINIDUMP_WRITER_DIRECTORY_READER_H_ +#define CLIENT_LINUX_MINIDUMP_WRITER_DIRECTORY_READER_H_ + +#include <stdint.h> +#include <unistd.h> +#include <limits.h> +#include <assert.h> +#include <errno.h> +#include <string.h> + +#include "common/linux/linux_syscall_support.h" + +namespace google_breakpad { + +// A class for enumerating a directory without using diropen/readdir or other +// functions which may allocate memory. +class DirectoryReader { + public: + DirectoryReader(int fd) + : fd_(fd), + buf_used_(0) { + } + + // Return the next entry from the directory + // name: (output) the NUL terminated entry name + // + // Returns true iff successful (false on EOF). + // + // After calling this, one must call |PopEntry| otherwise you'll get the same + // entry over and over. + bool GetNextEntry(const char** name) { + struct kernel_dirent* const dent = + reinterpret_cast<kernel_dirent*>(buf_); + + if (buf_used_ == 0) { + // need to read more entries. + const int n = sys_getdents(fd_, dent, sizeof(buf_)); + if (n < 0) { + return false; + } else if (n == 0) { + hit_eof_ = true; + } else { + buf_used_ += n; + } + } + + if (buf_used_ == 0 && hit_eof_) + return false; + + assert(buf_used_ > 0); + + *name = dent->d_name; + return true; + } + + void PopEntry() { + if (!buf_used_) + return; + + const struct kernel_dirent* const dent = + reinterpret_cast<kernel_dirent*>(buf_); + + buf_used_ -= dent->d_reclen; + memmove(buf_, buf_ + dent->d_reclen, buf_used_); + } + + private: + const int fd_; + bool hit_eof_; + unsigned buf_used_; + uint8_t buf_[sizeof(struct kernel_dirent) + NAME_MAX + 1]; +}; + +} // namespace google_breakpad + +#endif // CLIENT_LINUX_MINIDUMP_WRITER_DIRECTORY_READER_H_ diff --git a/src/client/linux/minidump_writer/directory_reader_unittest.cc b/src/client/linux/minidump_writer/directory_reader_unittest.cc new file mode 100644 index 00000000..3034e619 --- /dev/null +++ b/src/client/linux/minidump_writer/directory_reader_unittest.cc @@ -0,0 +1,77 @@ +// Copyright (c) 2009, Google Inc. +// All rights reserved. +// +// 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 <set> +#include <string> + +#include <dirent.h> +#include <fcntl.h> +#include <sys/types.h> + +#include "client/linux/minidump_writer/directory_reader.h" +#include "breakpad_googletest_includes.h" + +using namespace google_breakpad; + +namespace { +typedef testing::Test DirectoryReaderTest; +} + +TEST(DirectoryReaderTest, CompareResults) { + std::set<std::string> dent_set; + + DIR *const dir = opendir("/proc/self"); + ASSERT_TRUE(dir != NULL); + + struct dirent* dent; + while ((dent = readdir(dir))) + dent_set.insert(dent->d_name); + + closedir(dir); + + const int fd = open("/proc/self", O_DIRECTORY | O_RDONLY); + ASSERT_GE(fd, 0); + + DirectoryReader dir_reader(fd); + unsigned seen = 0; + + const char* name; + while (dir_reader.GetNextEntry(&name)) { + ASSERT_TRUE(dent_set.find(name) != dent_set.end()); + seen++; + dir_reader.PopEntry(); + } + + ASSERT_TRUE(dent_set.find("status") != dent_set.end()); + ASSERT_TRUE(dent_set.find("stat") != dent_set.end()); + ASSERT_TRUE(dent_set.find("cmdline") != dent_set.end()); + + ASSERT_EQ(dent_set.size(), seen); + close(fd); +} diff --git a/src/client/linux/minidump_writer/line_reader.h b/src/client/linux/minidump_writer/line_reader.h new file mode 100644 index 00000000..5c0a1154 --- /dev/null +++ b/src/client/linux/minidump_writer/line_reader.h @@ -0,0 +1,130 @@ +// Copyright (c) 2009, Google Inc. +// All rights reserved. +// +// 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. + +#ifndef CLIENT_LINUX_MINIDUMP_WRITER_LINE_READER_H_ +#define CLIENT_LINUX_MINIDUMP_WRITER_LINE_READER_H_ + +#include <stdint.h> +#include <assert.h> +#include <string.h> + +#include "common/linux/linux_syscall_support.h" + +namespace google_breakpad { + +// A class for reading a file, line by line, without using fopen/fgets or other +// functions which may allocate memory. +class LineReader { + public: + LineReader(int fd) + : fd_(fd), + hit_eof_(false), + buf_used_(0) { + } + + // The maximum length of a line. + static const size_t kMaxLineLen = 512; + + // Return the next line from the file. + // line: (output) a pointer to the start of the line. The line is NUL + // terminated. + // len: (output) the length of the line (not inc the NUL byte) + // + // Returns true iff successful (false on EOF). + // + // One must call |PopLine| after this function, otherwise you'll continue to + // get the same line over and over. + bool GetNextLine(const char **line, unsigned *len) { + for (;;) { + if (buf_used_ == 0 && hit_eof_) + return false; + + for (unsigned i = 0; i < buf_used_; ++i) { + if (buf_[i] == '\n' || buf_[i] == 0) { + buf_[i] = 0; + *len = i; + *line = buf_; + return true; + } + } + + if (buf_used_ == sizeof(buf_)) { + // we scanned the whole buffer and didn't find an end-of-line marker. + // This line is too long to process. + return false; + } + + // We didn't find any end-of-line terminators in the buffer. However, if + // this is the last line in the file it might not have one: + if (hit_eof_) { + assert(buf_used_); + // There's room for the NUL because of the buf_used_ == sizeof(buf_) + // check above. + buf_[buf_used_] = 0; + *len = buf_used_; + buf_used_ += 1; // since we appended the NUL. + *line = buf_; + return true; + } + + // Otherwise, we should pull in more data from the file + const ssize_t n = sys_read(fd_, buf_ + buf_used_, + sizeof(buf_) - buf_used_); + if (n < 0) { + return false; + } else if (n == 0) { + hit_eof_ = true; + } else { + buf_used_ += n; + } + + // At this point, we have either set the hit_eof_ flag, or we have more + // data to process... + } + } + + void PopLine(unsigned len) { + // len doesn't include the NUL byte at the end. + + assert(buf_used_ >= len + 1); + buf_used_ -= len + 1; + memmove(buf_, buf_ + len + 1, buf_used_); + } + + private: + const int fd_; + + bool hit_eof_; + unsigned buf_used_; + char buf_[kMaxLineLen]; +}; + +} // namespace google_breakpad + +#endif // CLIENT_LINUX_MINIDUMP_WRITER_LINE_READER_H_ diff --git a/src/client/linux/minidump_writer/line_reader_unittest.cc b/src/client/linux/minidump_writer/line_reader_unittest.cc new file mode 100644 index 00000000..222a098e --- /dev/null +++ b/src/client/linux/minidump_writer/line_reader_unittest.cc @@ -0,0 +1,184 @@ +// Copyright (c) 2009, Google Inc. +// All rights reserved. +// +// 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 <stdlib.h> +#include <unistd.h> +#include <sys/types.h> + +#include "client/linux/minidump_writer/line_reader.h" +#include "breakpad_googletest_includes.h" + +using namespace google_breakpad; + +static int TemporaryFile() { + static const char templ[] = "/tmp/line-reader-unittest-XXXXXX"; + char templ_copy[sizeof(templ)]; + memcpy(templ_copy, templ, sizeof(templ)); + const int fd = mkstemp(templ_copy); + if (fd >= 0) + unlink(templ_copy); + + return fd; +} + +namespace { +typedef testing::Test LineReaderTest; +} + +TEST(LineReaderTest, EmptyFile) { + const int fd = TemporaryFile(); + LineReader reader(fd); + + const char *line; + unsigned len; + ASSERT_FALSE(reader.GetNextLine(&line, &len)); + + close(fd); +} + +TEST(LineReaderTest, OneLineTerminated) { + const int fd = TemporaryFile(); + write(fd, "a\n", 2); + lseek(fd, 0, SEEK_SET); + LineReader reader(fd); + + const char *line; + unsigned len; + ASSERT_TRUE(reader.GetNextLine(&line, &len)); + ASSERT_EQ(len, 1); + ASSERT_EQ(line[0], 'a'); + ASSERT_EQ(line[1], 0); + reader.PopLine(len); + + ASSERT_FALSE(reader.GetNextLine(&line, &len)); + + close(fd); +} + +TEST(LineReaderTest, OneLine) { + const int fd = TemporaryFile(); + write(fd, "a", 1); + lseek(fd, 0, SEEK_SET); + LineReader reader(fd); + + const char *line; + unsigned len; + ASSERT_TRUE(reader.GetNextLine(&line, &len)); + ASSERT_EQ(len, 1); + ASSERT_EQ(line[0], 'a'); + ASSERT_EQ(line[1], 0); + reader.PopLine(len); + + ASSERT_FALSE(reader.GetNextLine(&line, &len)); + + close(fd); +} + +TEST(LineReaderTest, TwoLinesTerminated) { + const int fd = TemporaryFile(); + write(fd, "a\nb\n", 4); + lseek(fd, 0, SEEK_SET); + LineReader reader(fd); + + const char *line; + unsigned len; + ASSERT_TRUE(reader.GetNextLine(&line, &len)); + ASSERT_EQ(len, 1); + ASSERT_EQ(line[0], 'a'); + ASSERT_EQ(line[1], 0); + reader.PopLine(len); + + ASSERT_TRUE(reader.GetNextLine(&line, &len)); + ASSERT_EQ(len, 1); + ASSERT_EQ(line[0], 'b'); + ASSERT_EQ(line[1], 0); + reader.PopLine(len); + + ASSERT_FALSE(reader.GetNextLine(&line, &len)); + + close(fd); +} + +TEST(LineReaderTest, TwoLines) { + const int fd = TemporaryFile(); + write(fd, "a\nb", 3); + lseek(fd, 0, SEEK_SET); + LineReader reader(fd); + + const char *line; + unsigned len; + ASSERT_TRUE(reader.GetNextLine(&line, &len)); + ASSERT_EQ(len, 1); + ASSERT_EQ(line[0], 'a'); + ASSERT_EQ(line[1], 0); + reader.PopLine(len); + + ASSERT_TRUE(reader.GetNextLine(&line, &len)); + ASSERT_EQ(len, 1); + ASSERT_EQ(line[0], 'b'); + ASSERT_EQ(line[1], 0); + reader.PopLine(len); + + ASSERT_FALSE(reader.GetNextLine(&line, &len)); + + close(fd); +} + +TEST(LineReaderTest, MaxLength) { + const int fd = TemporaryFile(); + char l[LineReader::kMaxLineLen - 1]; + memset(l, 'a', sizeof(l)); + write(fd, l, sizeof(l)); + lseek(fd, 0, SEEK_SET); + LineReader reader(fd); + + const char *line; + unsigned len; + ASSERT_TRUE(reader.GetNextLine(&line, &len)); + ASSERT_EQ(len, sizeof(l)); + ASSERT_TRUE(memcmp(l, line, sizeof(l)) == 0); + ASSERT_EQ(line[len], 0); + + close(fd); +} + +TEST(LineReaderTest, TooLong) { + const int fd = TemporaryFile(); + char l[LineReader::kMaxLineLen]; + memset(l, 'a', sizeof(l)); + write(fd, l, sizeof(l)); + lseek(fd, 0, SEEK_SET); + LineReader reader(fd); + + const char *line; + unsigned len; + ASSERT_FALSE(reader.GetNextLine(&line, &len)); + + close(fd); +} diff --git a/src/client/linux/minidump_writer/linux_dumper.cc b/src/client/linux/minidump_writer/linux_dumper.cc new file mode 100644 index 00000000..e821db7d --- /dev/null +++ b/src/client/linux/minidump_writer/linux_dumper.cc @@ -0,0 +1,419 @@ +// Copyright (c) 2009, Google Inc. +// All rights reserved. +// +// 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. + +// This code deals with the mechanics of getting information about a crashed +// process. Since this code may run in a compromised address space, the same +// rules apply as detailed at the top of minidump_writer.h: no libc calls and +// use the alternative allocator. + +#include "client/linux/minidump_writer/linux_dumper.h" + +#include <assert.h> +#include <limits.h> +#include <stddef.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> + +#include <sys/types.h> +#include <sys/ptrace.h> +#include <sys/wait.h> + +#include "client/linux/minidump_writer/directory_reader.h" +#include "client/linux/minidump_writer/line_reader.h" +#include "common/linux/linux_libc_support.h" +#include "common/linux/linux_syscall_support.h" + +// Suspend a thread by attaching to it. +static bool SuspendThread(pid_t pid) { + // This may fail if the thread has just died or debugged. + errno = 0; + if (sys_ptrace(PTRACE_ATTACH, pid, NULL, NULL) != 0 && + errno != 0) { + return false; + } + while (sys_waitpid(pid, NULL, __WALL) < 0) { + if (errno != EINTR) { + sys_ptrace(PTRACE_DETACH, pid, NULL, NULL); + return false; + } + } + return true; +} + +// Resume a thread by detaching from it. +static bool ResumeThread(pid_t pid) { + return sys_ptrace(PTRACE_DETACH, pid, NULL, NULL) >= 0; +} + +namespace google_breakpad { + +LinuxDumper::LinuxDumper(int pid) + : pid_(pid), + threads_suspened_(false), + threads_(&allocator_, 8), + mappings_(&allocator_) { +} + +bool LinuxDumper::Init() { + return EnumerateThreads(&threads_) && + EnumerateMappings(&mappings_); +} + +bool LinuxDumper::ThreadsSuspend() { + if (threads_suspened_) + return true; + bool good = true; + for (size_t i = 0; i < threads_.size(); ++i) + good &= SuspendThread(threads_[i]); + threads_suspened_ = true; + return good; +} + +bool LinuxDumper::ThreadsResume() { + if (!threads_suspened_) + return false; + bool good = true; + for (size_t i = 0; i < threads_.size(); ++i) + good &= ResumeThread(threads_[i]); + threads_suspened_ = false; + return good; +} + +void +LinuxDumper::BuildProcPath(char* path, pid_t pid, const char* node) const { + assert(path); + if (!path) { + return; + } + + path[0] = '\0'; + + const unsigned pid_len = my_int_len(pid); + + assert(node); + if (!node) { + return; + } + + size_t node_len = my_strlen(node); + assert(node_len < NAME_MAX); + if (node_len >= NAME_MAX) { + return; + } + + assert(node_len > 0); + if (node_len == 0) { + return; + } + + assert(pid > 0); + if (pid <= 0) { + return; + } + + const size_t total_length = 6 + pid_len + 1 + node_len; + + assert(total_length < NAME_MAX); + if (total_length >= NAME_MAX) { + return; + } + + memcpy(path, "/proc/", 6); + my_itos(path + 6, pid, pid_len); + memcpy(path + 6 + pid_len, "/", 1); + memcpy(path + 6 + pid_len + 1, node, node_len); + memcpy(path + total_length, "\0", 1); +} + +void* +LinuxDumper::FindBeginningOfLinuxGateSharedLibrary(const pid_t pid) const { + char auxv_path[80]; + BuildProcPath(auxv_path, pid, "auxv"); + + // If BuildProcPath errors out due to invalid input, we'll handle it when + // we try to sys_open the file. + + // Find the AT_SYSINFO_EHDR entry for linux-gate.so + // See http://www.trilithium.com/johan/2005/08/linux-gate/ for more + // information. + int fd = sys_open(auxv_path, O_RDONLY, 0); + if (fd < 0) { + return NULL; + } + + elf_aux_entry one_aux_entry; + while (sys_read(fd, + &one_aux_entry, + sizeof(elf_aux_entry)) == sizeof(elf_aux_entry) && + one_aux_entry.a_type != AT_NULL) { + if (one_aux_entry.a_type == AT_SYSINFO_EHDR) { + close(fd); + return reinterpret_cast<void*>(one_aux_entry.a_un.a_val); + } + } + close(fd); + return NULL; +} + +bool +LinuxDumper::EnumerateMappings(wasteful_vector<MappingInfo*>* result) const { + char maps_path[80]; + BuildProcPath(maps_path, pid_, "maps"); + + // linux_gate_loc is the beginning of the kernel's mapping of + // linux-gate.so in the process. It doesn't actually show up in the + // maps list as a filename, so we use the aux vector to find it's + // load location and special case it's entry when creating the list + // of mappings. + const void* linux_gate_loc; + linux_gate_loc = FindBeginningOfLinuxGateSharedLibrary(pid_); + + const int fd = sys_open(maps_path, O_RDONLY, 0); + if (fd < 0) + return false; + LineReader* const line_reader = new(allocator_) LineReader(fd); + + const char* line; + unsigned line_len; + while (line_reader->GetNextLine(&line, &line_len)) { + uintptr_t start_addr, end_addr, offset; + + const char* i1 = my_read_hex_ptr(&start_addr, line); + if (*i1 == '-') { + const char* i2 = my_read_hex_ptr(&end_addr, i1 + 1); + if (*i2 == ' ') { + const char* i3 = my_read_hex_ptr(&offset, i2 + 6 /* skip ' rwxp ' */); + if (*i3 == ' ') { + MappingInfo* const module = new(allocator_) MappingInfo; + memset(module, 0, sizeof(MappingInfo)); + module->start_addr = start_addr; + module->size = end_addr - start_addr; + module->offset = offset; + const char* name = NULL; + // Only copy name if the name is a valid path name, or if + // we've found the VDSO image + if ((name = my_strchr(line, '/')) != NULL) { + const unsigned l = my_strlen(name); + if (l < sizeof(module->name)) + memcpy(module->name, name, l); + } else if (linux_gate_loc && + reinterpret_cast<void*>(module->start_addr) == + linux_gate_loc) { + memcpy(module->name, + kLinuxGateLibraryName, + my_strlen(kLinuxGateLibraryName)); + module->offset = 0; + } + result->push_back(module); + } + } + } + line_reader->PopLine(line_len); + } + + sys_close(fd); + + return result->size() > 0; +} + +// Parse /proc/$pid/task to list all the threads of the process identified by +// pid. +bool LinuxDumper::EnumerateThreads(wasteful_vector<pid_t>* result) const { + char task_path[80]; + BuildProcPath(task_path, pid_, "task"); + + const int fd = sys_open(task_path, O_RDONLY | O_DIRECTORY, 0); + if (fd < 0) + return false; + DirectoryReader* dir_reader = new(allocator_) DirectoryReader(fd); + + // The directory may contain duplicate entries which we filter by assuming + // that they are consecutive. + int last_tid = -1; + const char* dent_name; + while (dir_reader->GetNextEntry(&dent_name)) { + if (my_strcmp(dent_name, ".") && + my_strcmp(dent_name, "..")) { + int tid = 0; + if (my_strtoui(&tid, dent_name) && + last_tid != tid) { + last_tid = tid; + result->push_back(tid); + } + } + dir_reader->PopEntry(); + } + + sys_close(fd); + return true; +} + +// Read thread info from /proc/$pid/status. +// Fill out the |tgid|, |ppid| and |pid| members of |info|. If unavailible, +// these members are set to -1. Returns true iff all three members are +// availible. +bool LinuxDumper::ThreadInfoGet(pid_t tid, ThreadInfo* info) { + assert(info != NULL); + char status_path[80]; + BuildProcPath(status_path, tid, "status"); + + const int fd = open(status_path, O_RDONLY); + if (fd < 0) + return false; + + LineReader* const line_reader = new(allocator_) LineReader(fd); + const char* line; + unsigned line_len; + + info->ppid = info->tgid = -1; + + while (line_reader->GetNextLine(&line, &line_len)) { + if (my_strncmp("Tgid:\t", line, 6) == 0) { + my_strtoui(&info->tgid, line + 6); + } else if (my_strncmp("PPid:\t", line, 6) == 0) { + my_strtoui(&info->ppid, line + 6); + } + + line_reader->PopLine(line_len); + } + + if (info->ppid == -1 || info->tgid == -1) + return false; + + if (sys_ptrace(PTRACE_GETREGS, tid, NULL, &info->regs) == -1 || + sys_ptrace(PTRACE_GETFPREGS, tid, NULL, &info->fpregs) == -1) { + return false; + } + +#if defined(__i386) || defined(__x86_64) + if (sys_ptrace(PTRACE_GETFPXREGS, tid, NULL, &info->fpxregs) == -1) + return false; + + for (unsigned i = 0; i < ThreadInfo::kNumDebugRegisters; ++i) { + if (sys_ptrace( + PTRACE_PEEKUSER, tid, + reinterpret_cast<void*> (offsetof(struct user, + u_debugreg[0]) + i * + sizeof(debugreg_t)), + &info->dregs[i]) == -1) { + return false; + } + } +#endif + + const uint8_t* stack_pointer; +#if defined(__i386) + memcpy(&stack_pointer, &info->regs.esp, sizeof(info->regs.esp)); +#elif defined(__x86_64) + memcpy(&stack_pointer, &info->regs.rsp, sizeof(info->regs.rsp)); +#else +#error "This code hasn't been ported to your platform yet." +#endif + + if (!GetStackInfo(&info->stack, &info->stack_len, + (uintptr_t) stack_pointer)) + return false; + + return true; +} + +// Get information about the stack, given the stack pointer. We don't try to +// walk the stack since we might not have all the information needed to do +// unwind. So we just grab, up to, 32k of stack. +bool LinuxDumper::GetStackInfo(const void** stack, size_t* stack_len, + uintptr_t int_stack_pointer) { +#if defined(__i386) || defined(__x86_64) + static const bool stack_grows_down = true; + static const uintptr_t page_size = 4096; +#else +#error "This code has not been ported to your platform yet." +#endif + // Move the stack pointer to the bottom of the page that it's in. + uint8_t* const stack_pointer = + reinterpret_cast<uint8_t*>(int_stack_pointer & ~(page_size - 1)); + + // The number of bytes of stack which we try to capture. + static unsigned kStackToCapture = 32 * 1024; + + const MappingInfo* mapping = FindMapping(stack_pointer); + if (!mapping) + return false; + if (stack_grows_down) { + const ptrdiff_t offset = stack_pointer - (uint8_t*) mapping->start_addr; + const ptrdiff_t distance_to_end = + static_cast<ptrdiff_t>(mapping->size) - offset; + *stack_len = distance_to_end > kStackToCapture ? + kStackToCapture : distance_to_end; + *stack = stack_pointer; + } else { + const ptrdiff_t offset = stack_pointer - (uint8_t*) mapping->start_addr; + *stack_len = offset > kStackToCapture ? kStackToCapture : offset; + *stack = stack_pointer - *stack_len; + } + + return true; +} + +// static +void LinuxDumper::CopyFromProcess(void* dest, pid_t child, const void* src, + size_t length) { + unsigned long tmp; + size_t done = 0; + static const size_t word_size = sizeof(tmp); + uint8_t* const local = (uint8_t*) dest; + uint8_t* const remote = (uint8_t*) src; + + while (done < length) { + const size_t l = length - done > word_size ? word_size : length - done; + if (sys_ptrace(PTRACE_PEEKDATA, child, remote + done, &tmp) == -1) + tmp = 0; + memcpy(local + done, &tmp, l); + done += l; + } +} + +// Find the mapping which the given memory address falls in. +const MappingInfo* LinuxDumper::FindMapping(const void* address) const { + const uintptr_t addr = (uintptr_t) address; + + for (size_t i = 0; i < mappings_.size(); ++i) { + const uintptr_t start = static_cast<uintptr_t>(mappings_[i]->start_addr); + if (addr >= start && addr - start < mappings_[i]->size) + return mappings_[i]; + } + + return NULL; +} + +} // namespace google_breakpad diff --git a/src/client/linux/minidump_writer/linux_dumper.h b/src/client/linux/minidump_writer/linux_dumper.h new file mode 100644 index 00000000..252e9ee6 --- /dev/null +++ b/src/client/linux/minidump_writer/linux_dumper.h @@ -0,0 +1,142 @@ +// Copyright (c) 2009, Google Inc. +// All rights reserved. +// +// 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. + +#ifndef CLIENT_LINUX_MINIDUMP_WRITER_LINUX_DUMPER_H_ +#define CLIENT_LINUX_MINIDUMP_WRITER_LINUX_DUMPER_H_ + +#include <elf.h> +#include <stdint.h> +#include <sys/user.h> +#include <linux/limits.h> + +#include "common/linux/memory.h" + +namespace google_breakpad { + +typedef typeof(((struct user*) 0)->u_debugreg[0]) debugreg_t; + +// Typedef for our parsing of the auxv variables in /proc/pid/auxv. +#if defined(__i386) +typedef Elf32_auxv_t elf_aux_entry; +#elif defined(__x86_64__) +typedef Elf64_auxv_t elf_aux_entry; +#endif +// When we find the VDSO mapping in the process's address space, this +// is the name we use for it when writing it to the minidump. +// This should always be less than NAME_MAX! +const char kLinuxGateLibraryName[] = "linux-gate.so"; + +// We produce one of these structures for each thread in the crashed process. +struct ThreadInfo { + pid_t tgid; // thread group id + pid_t ppid; // parent process + + // Even on platforms where the stack grows down, the following will point to + // the smallest address in the stack. + const void* stack; // pointer to the stack area + size_t stack_len; // length of the stack to copy + + user_regs_struct regs; + user_fpregs_struct fpregs; +#if defined(__i386) || defined(__x86_64) + user_fpxregs_struct fpxregs; + + static const unsigned kNumDebugRegisters = 8; + debugreg_t dregs[8]; +#endif +}; + +// One of these is produced for each mapping in the process (i.e. line in +// /proc/$x/maps). +struct MappingInfo { + uintptr_t start_addr; + size_t size; + size_t offset; // offset into the backed file. + char name[NAME_MAX]; +}; + +class LinuxDumper { + public: + explicit LinuxDumper(pid_t pid); + + // Parse the data for |threads| and |mappings|. + bool Init(); + + // Suspend/resume all threads in the given process. + bool ThreadsSuspend(); + bool ThreadsResume(); + + // Read information about the given thread. Returns true on success. One must + // have called |ThreadsSuspend| first. + bool ThreadInfoGet(pid_t tid, ThreadInfo* info); + + // These are only valid after a call to |Init|. + const wasteful_vector<pid_t> &threads() { return threads_; } + const wasteful_vector<MappingInfo*> &mappings() { return mappings_; } + const MappingInfo* FindMapping(const void* address) const; + + // Find a block of memory to take as the stack given the top of stack pointer. + // stack: (output) the lowest address in the memory area + // stack_len: (output) the length of the memory area + // stack_top: the current top of the stack + bool GetStackInfo(const void** stack, size_t* stack_len, uintptr_t stack_top); + + PageAllocator* allocator() { return &allocator_; } + + // memcpy from a remote process. + static void CopyFromProcess(void* dest, pid_t child, const void* src, + size_t length); + + // Builds a proc path for a certain pid for a node. path is a + // character array that is overwritten, and node is the final node + // without any slashes. + void BuildProcPath(char* path, pid_t pid, const char* node) const; + + // Utility method to find the location of where the kernel has + // mapped linux-gate.so in memory(shows up in /proc/pid/maps as + // [vdso], but we can't guarantee that it's the only virtual dynamic + // shared object. Parsing the auxilary vector for AT_SYSINFO_EHDR + // is the safest way to go.) + void* FindBeginningOfLinuxGateSharedLibrary(const pid_t pid) const; + private: + bool EnumerateMappings(wasteful_vector<MappingInfo*>* result) const; + bool EnumerateThreads(wasteful_vector<pid_t>* result) const; + + const pid_t pid_; + + mutable PageAllocator allocator_; + + bool threads_suspened_; + wasteful_vector<pid_t> threads_; // the ids of all the threads + wasteful_vector<MappingInfo*> mappings_; // info from /proc/<pid>/maps +}; + +} // namespace google_breakpad + +#endif // CLIENT_LINUX_HANDLER_LINUX_DUMPER_H_ diff --git a/src/client/linux/minidump_writer/linux_dumper_unittest.cc b/src/client/linux/minidump_writer/linux_dumper_unittest.cc new file mode 100644 index 00000000..f5ed914b --- /dev/null +++ b/src/client/linux/minidump_writer/linux_dumper_unittest.cc @@ -0,0 +1,118 @@ +// Copyright (c) 2009, Google Inc. +// All rights reserved. +// +// 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 <unistd.h> + +#include "client/linux/minidump_writer/linux_dumper.h" +#include "breakpad_googletest_includes.h" + +using namespace google_breakpad; + +namespace { +typedef testing::Test LinuxDumperTest; +} + +TEST(LinuxDumperTest, Setup) { + LinuxDumper dumper(getpid()); +} + +TEST(LinuxDumperTest, FindMappings) { + LinuxDumper dumper(getpid()); + ASSERT_TRUE(dumper.Init()); + + ASSERT_TRUE(dumper.FindMapping(reinterpret_cast<void*>(getpid))); + ASSERT_TRUE(dumper.FindMapping(reinterpret_cast<void*>(printf))); + ASSERT_FALSE(dumper.FindMapping(NULL)); +} + +TEST(LinuxDumperTest, ThreadList) { + LinuxDumper dumper(getpid()); + ASSERT_TRUE(dumper.Init()); + + ASSERT_GE(dumper.threads().size(), 1); + bool found = false; + for (size_t i = 0; i < dumper.threads().size(); ++i) { + if (dumper.threads()[i] == getpid()) { + found = true; + break; + } + } +} + +TEST(LinuxDumperTest, BuildProcPath) { + const pid_t pid = getpid(); + LinuxDumper dumper(pid); + + char maps_path[256] = "dummymappath"; + char maps_path_expected[256]; + snprintf(maps_path_expected, sizeof(maps_path_expected), + "/proc/%d/maps", pid); + dumper.BuildProcPath(maps_path, pid, "maps"); + ASSERT_STREQ(maps_path, maps_path_expected); + + // In release mode, we expect BuildProcPath to handle the invalid + // parameters correctly and fill map_path with an empty + // NULL-terminated string. +#ifdef NDEBUG + snprintf(maps_path, sizeof(maps_path), "dummymappath"); + dumper.BuildProcPath(maps_path, 0, "maps"); + EXPECT_STREQ(maps_path, ""); + + snprintf(maps_path, sizeof(maps_path), "dummymappath"); + dumper.BuildProcPath(maps_path, getpid(), ""); + EXPECT_STREQ(maps_path, ""); + + snprintf(maps_path, sizeof(maps_path), "dummymappath"); + dumper.BuildProcPath(maps_path, getpid(), NULL); + EXPECT_STREQ(maps_path, ""); +#endif +} + +TEST(LinuxDumperTest, MappingsIncludeLinuxGate) { + LinuxDumper dumper(getpid()); + ASSERT_TRUE(dumper.Init()); + + void* linux_gate_loc = dumper.FindBeginningOfLinuxGateSharedLibrary(getpid()); + if (linux_gate_loc) { + bool found_linux_gate = false; + + const wasteful_vector<MappingInfo*> mappings = dumper.mappings(); + const MappingInfo* mapping; + for (unsigned i = 0; i < mappings.size(); ++i) { + mapping = mappings[i]; + if (!strcmp(mapping->name, kLinuxGateLibraryName)) { + found_linux_gate = true; + break; + } + } + EXPECT_TRUE(found_linux_gate); + EXPECT_EQ(linux_gate_loc, reinterpret_cast<void*>(mapping->start_addr)); + EXPECT_EQ(0, memcmp(linux_gate_loc, ELFMAG, SELFMAG)); + } +} diff --git a/src/client/linux/minidump_writer/minidump_writer.cc b/src/client/linux/minidump_writer/minidump_writer.cc new file mode 100644 index 00000000..a99309f3 --- /dev/null +++ b/src/client/linux/minidump_writer/minidump_writer.cc @@ -0,0 +1,872 @@ +// Copyright (c) 2009, Google Inc. +// All rights reserved. +// +// 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. + +// This code writes out minidump files: +// http://msdn.microsoft.com/en-us/library/ms680378(VS.85,loband).aspx +// +// Minidumps are a Microsoft format which Breakpad uses for recording crash +// dumps. This code has to run in a compromised environment (the address space +// may have received SIGSEGV), thus the following rules apply: +// * You may not enter the dynamic linker. This means that we cannot call +// any symbols in a shared library (inc libc). Because of this we replace +// libc functions in linux_libc_support.h. +// * You may not call syscalls via the libc wrappers. This rule is a subset +// of the first rule but it bears repeating. We have direct wrappers +// around the system calls in linux_syscall_support.h. +// * You may not malloc. There's an alternative allocator in memory.h and +// a canonical instance in the LinuxDumper object. We use the placement +// new form to allocate objects and we don't delete them. + +#include "client/linux/minidump_writer/minidump_writer.h" +#include "client/minidump_file_writer-inl.h" + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/ucontext.h> +#include <sys/user.h> +#include <sys/utsname.h> + +#include "client/minidump_file_writer.h" +#include "google_breakpad/common/minidump_format.h" +#include "google_breakpad/common/minidump_cpu_amd64.h" +#include "google_breakpad/common/minidump_cpu_x86.h" + +#include "client/linux/handler/exception_handler.h" +#include "client/linux/minidump_writer/line_reader.h" +#include "client/linux/minidump_writer//linux_dumper.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. +#if defined(__i386) +typedef MDRawContextX86 RawContextCPU; + +// Write a uint16_t to memory +// out: memory location to write to +// v: value to write. +static void U16(void* out, uint16_t v) { + memcpy(out, &v, sizeof(v)); +} + +// Write a uint32_t to memory +// out: memory location to write to +// v: value to write. +static void U32(void* out, uint32_t v) { + memcpy(out, &v, sizeof(v)); +} + +// Juggle an x86 user_(fp|fpx|)regs_struct into minidump format +// out: the minidump structure +// info: the collection of register structures. +static void CPUFillFromThreadInfo(MDRawContextX86 *out, + const google_breakpad::ThreadInfo &info) { + out->context_flags = MD_CONTEXT_X86_ALL; + + out->dr0 = info.dregs[0]; + out->dr1 = info.dregs[1]; + out->dr2 = info.dregs[2]; + out->dr3 = info.dregs[3]; + // 4 and 5 deliberatly omitted because they aren't included in the minidump + // format. + out->dr6 = info.dregs[6]; + out->dr7 = info.dregs[7]; + + out->gs = info.regs.xgs; + out->fs = info.regs.xfs; + out->es = info.regs.xes; + out->ds = info.regs.xds; + + out->edi = info.regs.edi; + out->esi = info.regs.esi; + out->ebx = info.regs.ebx; + out->edx = info.regs.edx; + out->ecx = info.regs.ecx; + out->eax = info.regs.eax; + + out->ebp = info.regs.ebp; + out->eip = info.regs.eip; + out->cs = info.regs.xcs; + out->eflags = info.regs.eflags; + out->esp = info.regs.esp; + out->ss = info.regs.xss; + + out->float_save.control_word = info.fpregs.cwd; + out->float_save.status_word = info.fpregs.swd; + out->float_save.tag_word = info.fpregs.twd; + out->float_save.error_offset = info.fpregs.fip; + out->float_save.error_selector = info.fpregs.fcs; + out->float_save.data_offset = info.fpregs.foo; + out->float_save.data_selector = info.fpregs.fos; + + // 8 registers * 10 bytes per register. + memcpy(out->float_save.register_area, info.fpregs.st_space, 10 * 8); + + // This matches the Intel fpsave format. + U16(out->extended_registers + 0, info.fpregs.cwd); + U16(out->extended_registers + 2, info.fpregs.swd); + U16(out->extended_registers + 4, info.fpregs.twd); + U16(out->extended_registers + 6, info.fpxregs.fop); + U32(out->extended_registers + 8, info.fpxregs.fip); + U16(out->extended_registers + 12, info.fpxregs.fcs); + U32(out->extended_registers + 16, info.fpregs.foo); + U16(out->extended_registers + 20, info.fpregs.fos); + U32(out->extended_registers + 24, info.fpxregs.mxcsr); + + memcpy(out->extended_registers + 32, &info.fpxregs.st_space, 128); + memcpy(out->extended_registers + 160, &info.fpxregs.xmm_space, 128); +} + +// Juggle an x86 ucontext into minidump format +// out: the minidump structure +// info: the collection of register structures. +static void CPUFillFromUContext(MDRawContextX86 *out, const ucontext *uc, + const struct _libc_fpstate* fp) { + const greg_t* regs = uc->uc_mcontext.gregs; + + out->context_flags = MD_CONTEXT_X86_FULL | + MD_CONTEXT_X86_FLOATING_POINT; + + out->gs = regs[REG_GS]; + out->fs = regs[REG_FS]; + out->es = regs[REG_ES]; + out->ds = regs[REG_DS]; + + out->edi = regs[REG_EDI]; + out->esi = regs[REG_ESI]; + out->ebx = regs[REG_EBX]; + out->edx = regs[REG_EDX]; + out->ecx = regs[REG_ECX]; + out->eax = regs[REG_EAX]; + + out->ebp = regs[REG_EBP]; + out->eip = regs[REG_EIP]; + out->cs = regs[REG_CS]; + out->eflags = regs[REG_EFL]; + out->esp = regs[REG_UESP]; + out->ss = regs[REG_SS]; + + out->float_save.control_word = fp->cw; + out->float_save.status_word = fp->sw; + out->float_save.tag_word = fp->tag; + out->float_save.error_offset = fp->ipoff; + out->float_save.error_selector = fp->cssel; + out->float_save.data_offset = fp->dataoff; + out->float_save.data_selector = fp->datasel; + + // 8 registers * 10 bytes per register. + memcpy(out->float_save.register_area, fp->_st, 10 * 8); +} + +#elif defined(__x86_64) +typedef MDRawContextAMD64 RawContextCPU; + +static void CPUFillFromThreadInfo(MDRawContextAMD64 *out, + const google_breakpad::ThreadInfo &info) { + out->context_flags = MD_CONTEXT_AMD64_FULL | + MD_CONTEXT_AMD64_SEGMENTS; + + out->cs = info.regs.cs; + + out->ds = info.regs.ds; + out->es = info.regs.es; + out->fs = info.regs.fs; + out->gs = info.regs.gs; + + out->ss = info.regs.ss; + out->eflags = info.regs.eflags; + + out->dr0 = info.dregs[0]; + out->dr1 = info.dregs[1]; + out->dr2 = info.dregs[2]; + out->dr3 = info.dregs[3]; + // 4 and 5 deliberatly omitted because they aren't included in the minidump + // format. + out->dr6 = info.dregs[6]; + out->dr7 = info.dregs[7]; + + out->rax = info.regs.rax; + out->rcx = info.regs.rcx; + out->rdx = info.regs.rdx; + out->rbx = info.regs.rbx; + + out->rsp = info.regs.rsp; + + out->rbp = info.regs.rbp; + out->rsi = info.regs.rsi; + out->rdi = info.regs.rdi; + out->r8 = info.regs.r8; + out->r9 = info.regs.r9; + out->r10 = info.regs.r10; + out->r11 = info.regs.r11; + out->r12 = info.regs.r12; + out->r13 = info.regs.r13; + out->r14 = info.regs.r14; + out->r15 = info.regs.r15; + + out->rip = info.regs.rip; + + out->flt_save.control_word = info.fpregs.cwd; + out->flt_save.status_word = info.fpregs.swd; + out->flt_save.tag_word = info.fpregs.twd; + out->flt_save.error_opcode = info.fpregs.fop; + out->flt_save.error_offset = info.fpregs.rip; + out->flt_save.error_selector = 0; // We don't have this. + out->flt_save.data_offset = info.fpregs.rdp; + out->flt_save.data_selector = 0; // We don't have this. + out->flt_save.mx_csr = info.fpregs.mxcsr; + out->flt_save.mx_csr_mask = info.fpregs.mxcsr_mask; + memcpy(&out->flt_save.float_registers, &info.fpregs.st_space, 8 * 16); + memcpy(&out->flt_save.xmm_registers, &info.fpregs.xmm_space, 16 * 16); +} + +static void CPUFillFromUContext(MDRawContextAMD64 *out, const ucontext *uc, + const struct _libc_fpstate* fpregs) { + const greg_t* regs = uc->gregs; + + out->context_flags = MD_CONTEXT_AMD64_FULL; + + out->cs = regs[REG_CSGSFS] & 0xffff; + + out->fs = (regs[REG_CSGSFS] >> 32) & 0xffff; + out->gs = (regs[REG_CSGSFS] >> 16) & 0xffff; + + out->eflags = regs[REG_EFL]; + + out->rax = regs[REG_RAX]; + out->rcx = regs[REG_RCX]; + out->rdx = regs[REG_RDX]; + out->rbx = regs[REG_RBX]; + + out->rsp = regs[REG_RSP]; + out->rbp = regs[REG_RBP]; + out->rsi = regs[REG_RSI]; + out->rdi = regs[REG_RDI]; + out->r8 = regs[REG_R8]; + out->r9 = regs[REG_R9]; + out->r10 = regs[REG_R10]; + out->r11 = regs[REG_R11]; + out->r12 = regs[REG_R12]; + out->r13 = regs[REG_R13]; + out->r14 = regs[REG_R14]; + out->r15 = regs[REG_R15]; + + out->rip = regs[REG_RIP]; + + out->flt_save.control_word = fpregs->cwd; + out->flt_save.status_word = fpregs->swd; + out->flt_save.tag_word = fpregs->ftw; + out->flt_save.error_opcode = fpregs->fop; + out->flt_save.error_offset = fpregs->rip; + out->flt_save.data_offset = fpregs->rdp; + out->flt_save.error_selector = 0; // We don't have this. + out->flt_save.data_selector = 0; // We don't have this. + out->flt_save.mx_csr = fpregs->mxcsr; + out->flt_save.mx_csr_mask = fpregs->mxcsr_mask; + memcpy(&out->flt_save.float_registers, &fpregs->_st, 8 * 16); + memcpy(&out->flt_save.xmm_registers, &fpregs->_xmm, 16 * 16); +} + +#else +#error "This code has not been ported to your platform yet." +#endif + +namespace google_breakpad { + +class MinidumpWriter { + public: + MinidumpWriter(const char* filename, + pid_t crashing_pid, + const ExceptionHandler::CrashContext* context) + : filename_(filename), + siginfo_(&context->siginfo), + ucontext_(&context->context), + float_state_(&context->float_state), + crashing_tid_(context->tid), + dumper_(crashing_pid) { + } + + bool Init() { + return dumper_.Init() && minidump_writer_.Open(filename_) && + dumper_.ThreadsSuspend(); + } + + ~MinidumpWriter() { + minidump_writer_.Close(); + dumper_.ThreadsResume(); + } + + bool Dump() { + // A minidump file contains a number of tagged streams. This is the number + // of stream which we write. + static const unsigned kNumWriters = 11; + + TypedMDRVA<MDRawHeader> header(&minidump_writer_); + TypedMDRVA<MDRawDirectory> dir(&minidump_writer_); + if (!header.Allocate()) + return false; + if (!dir.AllocateArray(kNumWriters)) + return false; + memset(header.get(), 0, sizeof(MDRawHeader)); + + header.get()->signature = MD_HEADER_SIGNATURE; + header.get()->version = MD_HEADER_VERSION; + header.get()->time_date_stamp = time(NULL); + header.get()->stream_count = kNumWriters; + header.get()->stream_directory_rva = dir.position(); + + unsigned dir_index = 0; + MDRawDirectory dirent; + + if (!WriteThreadListStream(&dirent)) + return false; + dir.CopyIndex(dir_index++, &dirent); + + if (!WriteMappings(&dirent)) + return false; + dir.CopyIndex(dir_index++, &dirent); + + if (!WriteExceptionStream(&dirent)) + return false; + dir.CopyIndex(dir_index++, &dirent); + + if (!WriteSystemInfoStream(&dirent)) + return false; + dir.CopyIndex(dir_index++, &dirent); + + dirent.stream_type = MD_LINUX_CPU_INFO; + if (!WriteFile(&dirent.location, "/proc/cpuinfo")) + NullifyDirectoryEntry(&dirent); + dir.CopyIndex(dir_index++, &dirent); + + dirent.stream_type = MD_LINUX_PROC_STATUS; + if (!WriteProcFile(&dirent.location, crashing_tid_, "status")) + NullifyDirectoryEntry(&dirent); + dir.CopyIndex(dir_index++, &dirent); + + dirent.stream_type = MD_LINUX_LSB_RELEASE; + if (!WriteFile(&dirent.location, "/etc/lsb-release")) + NullifyDirectoryEntry(&dirent); + dir.CopyIndex(dir_index++, &dirent); + + dirent.stream_type = MD_LINUX_CMD_LINE; + if (!WriteProcFile(&dirent.location, crashing_tid_, "cmdline")) + NullifyDirectoryEntry(&dirent); + dir.CopyIndex(dir_index++, &dirent); + + dirent.stream_type = MD_LINUX_ENVIRON; + if (!WriteProcFile(&dirent.location, crashing_tid_, "environ")) + NullifyDirectoryEntry(&dirent); + dir.CopyIndex(dir_index++, &dirent); + + dirent.stream_type = MD_LINUX_AUXV; + if (!WriteProcFile(&dirent.location, crashing_tid_, "auxv")) + NullifyDirectoryEntry(&dirent); + dir.CopyIndex(dir_index++, &dirent); + + dirent.stream_type = MD_LINUX_AUXV; + if (!WriteProcFile(&dirent.location, crashing_tid_, "maps")) + NullifyDirectoryEntry(&dirent); + dir.CopyIndex(dir_index++, &dirent); + + // If you add more directory entries, don't forget to update kNumWriters, + // above. + + dumper_.ThreadsResume(); + return true; + } + + // Write information about the threads. + bool WriteThreadListStream(MDRawDirectory* dirent) { + const unsigned num_threads = dumper_.threads().size(); + + TypedMDRVA<uint32_t> list(&minidump_writer_); + if (!list.AllocateObjectAndArray(num_threads, sizeof(MDRawThread))) + return false; + + dirent->stream_type = MD_THREAD_LIST_STREAM; + dirent->location = list.location(); + + *list.get() = num_threads; + + for (unsigned i = 0; i < num_threads; ++i) { + MDRawThread thread; + my_memset(&thread, 0, sizeof(thread)); + thread.thread_id = dumper_.threads()[i]; + // We have a different source of information for the crashing thread. If + // we used the actual state of the thread we would find it running in the + // signal handler with the alternative stack, which would be deeply + // unhelpful. + if (thread.thread_id == crashing_tid_) { + const void* stack; + size_t stack_len; + if (!dumper_.GetStackInfo(&stack, &stack_len, GetStackPointer())) + return false; + UntypedMDRVA memory(&minidump_writer_); + if (!memory.Allocate(stack_len)) + return false; + uint8_t* stack_copy = (uint8_t*) dumper_.allocator()->Alloc(stack_len); + dumper_.CopyFromProcess(stack_copy, thread.thread_id, stack, stack_len); + memory.Copy(stack_copy, stack_len); + thread.stack.start_of_memory_range = (uintptr_t) (stack); + thread.stack.memory = memory.location(); + TypedMDRVA<RawContextCPU> cpu(&minidump_writer_); + if (!cpu.Allocate()) + return false; + my_memset(cpu.get(), 0, sizeof(RawContextCPU)); + CPUFillFromUContext(cpu.get(), ucontext_, float_state_); + thread.thread_context = cpu.location(); + crashing_thread_context_ = cpu.location(); + } else { + ThreadInfo info; + if (!dumper_.ThreadInfoGet(dumper_.threads()[i], &info)) + return false; + UntypedMDRVA memory(&minidump_writer_); + if (!memory.Allocate(info.stack_len)) + return false; + uint8_t* stack_copy = + (uint8_t*) dumper_.allocator()->Alloc(info.stack_len); + dumper_.CopyFromProcess(stack_copy, thread.thread_id, info.stack, + info.stack_len); + memory.Copy(stack_copy, info.stack_len); + thread.stack.start_of_memory_range = (uintptr_t)(info.stack); + thread.stack.memory = memory.location(); + TypedMDRVA<RawContextCPU> cpu(&minidump_writer_); + if (!cpu.Allocate()) + return false; + my_memset(cpu.get(), 0, sizeof(RawContextCPU)); + CPUFillFromThreadInfo(cpu.get(), info); + thread.thread_context = cpu.location(); + } + + list.CopyIndexAfterObject(i, &thread, sizeof(thread)); + } + + return true; + } + + static bool ShouldIncludeMapping(const MappingInfo& mapping) { + if (mapping.name[0] == 0 || // we only want modules with filenames. + mapping.offset || // we only want to include one mapping per shared lib. + mapping.size < 4096) { // too small to get a signature for. + return false; + } + + return true; + } + + // Write information about the mappings in effect. Because we are using the + // minidump format, the information about the mappings is pretty limited. + // Because of this, we also include the full, unparsed, /proc/$x/maps file in + // another stream in the file. + bool WriteMappings(MDRawDirectory* dirent) { + const unsigned num_mappings = dumper_.mappings().size(); + unsigned num_output_mappings = 0; + + for (unsigned i = 0; i < dumper_.mappings().size(); ++i) { + const MappingInfo& mapping = *dumper_.mappings()[i]; + if (ShouldIncludeMapping(mapping)) + num_output_mappings++; + } + + TypedMDRVA<uint32_t> list(&minidump_writer_); + if (!list.AllocateObjectAndArray(num_output_mappings, sizeof(MDRawModule))) + return false; + + dirent->stream_type = MD_MODULE_LIST_STREAM; + dirent->location = list.location(); + *list.get() = num_output_mappings; + + for (unsigned i = 0, j = 0; i < num_mappings; ++i) { + const MappingInfo& mapping = *dumper_.mappings()[i]; + if (!ShouldIncludeMapping(mapping)) + continue; + + MDRawModule mod; + my_memset(&mod, 0, sizeof(mod)); + mod.base_of_image = mapping.start_addr; + mod.size_of_image = mapping.size; + const size_t filepath_len = my_strlen(mapping.name); + + // Figure out file name from path + const char* filename_ptr = mapping.name + filepath_len - 1; + while (filename_ptr >= mapping.name) { + if (*filename_ptr == '/') + break; + filename_ptr--; + } + filename_ptr++; + const size_t filename_len = mapping.name + filepath_len - filename_ptr; + + uint8_t cv_buf[MDCVInfoPDB70_minsize + NAME_MAX]; + uint8_t* cv_ptr = cv_buf; + UntypedMDRVA cv(&minidump_writer_); + if (!cv.Allocate(MDCVInfoPDB70_minsize + filename_len + 1)) + return false; + + const uint32_t cv_signature = MD_CVINFOPDB70_SIGNATURE; + memcpy(cv_ptr, &cv_signature, sizeof(cv_signature)); + cv_ptr += sizeof(cv_signature); + + { + // We XOR the first page of the file to get a signature for it. + uint8_t xor_buf[sizeof(MDGUID)]; + size_t done = 0; + uint8_t* signature = cv_ptr; + cv_ptr += sizeof(xor_buf); + + my_memset(signature, 0, sizeof(xor_buf)); + while (done < 4096) { + dumper_.CopyFromProcess(xor_buf, crashing_tid_, + (void *) (mod.base_of_image + done), + sizeof(xor_buf)); + for (unsigned i = 0; i < sizeof(xor_buf); ++i) + signature[i] ^= xor_buf[i]; + done += sizeof(xor_buf); + } + my_memset(cv_ptr, 0, sizeof(uint32_t)); // Set age to 0 on Linux. + cv_ptr += sizeof(uint32_t); + } + + // Write pdb_file_name + memcpy(cv_ptr, filename_ptr, filename_len + 1); + cv.Copy(cv_buf, MDCVInfoPDB70_minsize + filename_len + 1); + + mod.cv_record = cv.location(); + + MDLocationDescriptor ld; + if (!minidump_writer_.WriteString(mapping.name, filepath_len, &ld)) + return false; + mod.module_name_rva = ld.rva; + + list.CopyIndexAfterObject(j++, &mod, sizeof(mod)); + } + + return true; + } + + bool WriteExceptionStream(MDRawDirectory* dirent) { + TypedMDRVA<MDRawExceptionStream> exc(&minidump_writer_); + if (!exc.Allocate()) + return false; + my_memset(exc.get(), 0, sizeof(MDRawExceptionStream)); + + dirent->stream_type = MD_EXCEPTION_STREAM; + dirent->location = exc.location(); + + exc.get()->thread_id = crashing_tid_; + exc.get()->exception_record.exception_code = siginfo_->si_signo; + exc.get()->exception_record.exception_address = + (uintptr_t) siginfo_->si_addr; + exc.get()->thread_context = crashing_thread_context_; + + return true; + } + + bool WriteSystemInfoStream(MDRawDirectory* dirent) { + TypedMDRVA<MDRawSystemInfo> si(&minidump_writer_); + if (!si.Allocate()) + return false; + my_memset(si.get(), 0, sizeof(MDRawSystemInfo)); + + dirent->stream_type = MD_SYSTEM_INFO_STREAM; + dirent->location = si.location(); + + WriteCPUInformation(si.get()); + WriteOSInformation(si.get()); + + return true; + } + + private: +#if defined(__i386) + uintptr_t GetStackPointer() { + return ucontext_->uc_mcontext.gregs[REG_ESP]; + } +#elif defined(__x86_64) + uintptr_t GetStackPointer() { + return ucontext_->uc_mcontext.gregs[REG_RSP]; + } +#else +#error "This code has not been ported to your platform yet." +#endif + + void NullifyDirectoryEntry(MDRawDirectory* dirent) { + dirent->stream_type = 0; + dirent->location.data_size = 0; + dirent->location.rva = 0; + } + + bool WriteCPUInformation(MDRawSystemInfo* sys_info) { + char vendor_id[sizeof(sys_info->cpu.x86_cpu_info.vendor_id) + 1] = {0}; + static const char vendor_id_name[] = "vendor_id"; + static const size_t vendor_id_name_length = sizeof(vendor_id_name) - 1; + + struct CpuInfoEntry { + const char* info_name; + int value; + bool found; + } cpu_info_table[] = { + { "processor", -1, false }, + { "model", 0, false }, + { "stepping", 0, false }, + { "cpuid level", 0, false }, + }; + + // processor_architecture should always be set, do this first + sys_info->processor_architecture = +#if defined(__i386) + MD_CPU_ARCHITECTURE_X86; +#elif defined(__x86_64) + MD_CPU_ARCHITECTURE_AMD64; +#else +#error "Unknown CPU arch" +#endif + + const int fd = sys_open("/proc/cpuinfo", O_RDONLY, 0); + if (fd < 0) + return false; + + { + PageAllocator allocator; + LineReader* const line_reader = new(allocator) LineReader(fd); + const char* line; + unsigned line_len; + while (line_reader->GetNextLine(&line, &line_len)) { + for (size_t i = 0; + i < sizeof(cpu_info_table) / sizeof(cpu_info_table[0]); + i++) { + CpuInfoEntry* entry = &cpu_info_table[i]; + if (entry->found) + continue; + if (!strncmp(line, entry->info_name, strlen(entry->info_name))) { + const char* value = strchr(line, ':'); + if (!value) + continue; + + // the above strncmp only matches the prefix, it might be the wrong + // line. i.e. we matched "model name" instead of "model". + // check and make sure there is only spaces between the prefix and + // the colon. + const char* space_ptr = line + strlen(entry->info_name); + for (; space_ptr < value; space_ptr++) { + if (!isspace(*space_ptr)) { + break; + } + } + if (space_ptr != value) + continue; + + sscanf(++value, " %d", &(entry->value)); + entry->found = true; + } + } + + // special case for vendor_id + if (!strncmp(line, vendor_id_name, vendor_id_name_length)) { + const char* value = strchr(line, ':'); + if (!value) + goto popline; + + // skip ':" and all the spaces that follows + do { + value++; + } while (isspace(*value)); + + if (*value) { + size_t length = strlen(value); + if (length == 0) + goto popline; + // we don't want the trailing newline + if (value[length - 1] == '\n') + length--; + // ensure we have space for the value + if (length < sizeof(vendor_id)) + strncpy(vendor_id, value, length); + } + } + +popline: + line_reader->PopLine(line_len); + } + sys_close(fd); + } + + // make sure we got everything we wanted + for (size_t i = 0; + i < sizeof(cpu_info_table) / sizeof(cpu_info_table[0]); + i++) { + if (!cpu_info_table[i].found) { + return false; + } + } + // /proc/cpuinfo contains cpu id, change it into number by adding one. + cpu_info_table[0].value++; + + sys_info->number_of_processors = cpu_info_table[0].value; + sys_info->processor_level = cpu_info_table[3].value; + sys_info->processor_revision = cpu_info_table[1].value << 8 | + cpu_info_table[2].value; + + if (vendor_id[0] != '\0') { + memcpy(sys_info->cpu.x86_cpu_info.vendor_id, vendor_id, + sizeof(sys_info->cpu.x86_cpu_info.vendor_id)); + } + return true; + } + + bool WriteFile(MDLocationDescriptor* result, const char* filename) { + const int fd = sys_open(filename, O_RDONLY, 0); + if (fd < 0) + return false; + + // 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) { + ssize_t r; + do { + r = sys_read(fd, data + done, kMaxFileSize - done); + } while (r == -1 && errno == EINTR); + + if (r < 1) + break; + done += r; + } + sys_close(fd); + + if (!done) + return false; + + UntypedMDRVA memory(&minidump_writer_); + if (!memory.Allocate(done)) + return false; + memory.Copy(data, done); + *result = memory.location(); + return true; + } + + bool WriteOSInformation(MDRawSystemInfo* sys_info) { + sys_info->platform_id = MD_OS_LINUX; + + struct utsname uts; + if (uname(&uts)) + return false; + + static const size_t buf_len = 512; + char buf[buf_len] = {0}; + size_t space_left = buf_len - 1; + const char* info_table[] = { + uts.sysname, + uts.release, + uts.version, + uts.machine, + NULL + }; + bool first_item = true; + for (const char** cur_info = info_table; *cur_info; cur_info++) { + static const char* separator = " "; + size_t separator_len = strlen(separator); + size_t info_len = strlen(*cur_info); + if (info_len == 0) + continue; + + if (space_left < info_len + (first_item ? 0 : separator_len)) + break; + + if (!first_item) { + strcat(buf, separator); + space_left -= separator_len; + } + + first_item = false; + strcat(buf, *cur_info); + space_left -= info_len; + } + + MDLocationDescriptor location; + if (!minidump_writer_.WriteString(buf, 0, &location)) + return false; + sys_info->csd_version_rva = location.rva; + + return true; + } + + bool WriteProcFile(MDLocationDescriptor* result, pid_t pid, + const char* filename) { + char buf[80]; + memcpy(buf, "/proc/", 6); + const unsigned pid_len = my_int_len(pid); + my_itos(buf + 6, pid, pid_len); + buf[6 + pid_len] = '/'; + memcpy(buf + 6 + pid_len + 1, filename, my_strlen(filename) + 1); + return WriteFile(result, buf); + } + + const char* const filename_; // output filename + const siginfo_t* const siginfo_; // from the signal handler (see sigaction) + const struct ucontext* const ucontext_; // also from the signal handler + const struct _libc_fpstate* const float_state_; // ditto + const pid_t crashing_tid_; // the process which actually crashed + LinuxDumper dumper_; + MinidumpFileWriter minidump_writer_; + MDLocationDescriptor crashing_thread_context_; +}; + +bool WriteMinidump(const char* filename, pid_t crashing_process, + const void* blob, size_t blob_size) { + if (blob_size != sizeof(ExceptionHandler::CrashContext)) + return false; + const ExceptionHandler::CrashContext* context = + reinterpret_cast<const ExceptionHandler::CrashContext*>(blob); + MinidumpWriter writer(filename, crashing_process, context); + if (!writer.Init()) + return false; + return writer.Dump(); +} + +} // namespace google_breakpad diff --git a/src/client/linux/handler/minidump_generator.h b/src/client/linux/minidump_writer/minidump_writer.h index 7c0511f5..579f68cd 100644 --- a/src/client/linux/handler/minidump_generator.h +++ b/src/client/linux/minidump_writer/minidump_writer.h @@ -1,8 +1,6 @@ -// Copyright (c) 2006, Google Inc. +// Copyright (c) 2009, 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: @@ -29,45 +27,27 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#ifndef CLIENT_LINUX_HANDLER_MINIDUMP_GENERATOR_H__ -#define CLIENT_LINUX_HANDLER_MINIDUMP_GENERATOR_H__ +#ifndef CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_ +#define CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_ #include <stdint.h> - -#include "google_breakpad/common/breakpad_types.h" -#include "processor/scoped_ptr.h" - -struct sigcontext; +#include <unistd.h> namespace google_breakpad { +// Write a minidump to the filesystem. This function does not malloc nor use +// libc functions which may. Thus, it can be used in contexts where the state +// of the heap may be corrupt. +// filename: the filename to write to. This is opened O_EXCL and fails if +// open fails. +// crashing_process: the pid of the crashing process. This must be trusted. +// blob: a blob of data from the crashing process. See exception_handler.h +// blob_size: the length of |blob|, in bytes // -// MinidumpGenerator -// -// Write a minidump to file based on the signo and sig_ctx. -// A minidump generator should be created before any exception happen. -// -class MinidumpGenerator { - public: - MinidumpGenerator(); - - ~MinidumpGenerator(); - - // Write minidump. - bool WriteMinidumpToFile(const char *file_pathname, - int signo, - uintptr_t sighandler_ebp, - struct sigcontext **sig_ctx) const; - private: - // Allocate memory for stack. - void AllocateStack(); - - private: - // Stack size of the writer thread. - static const int kStackSize = 1024 * 1024; - scoped_array<char> stack_; -}; +// Returns true iff successful. +bool WriteMinidump(const char* filename, pid_t crashing_process, + const void* blob, size_t blob_size); } // namespace google_breakpad -#endif // CLIENT_LINUX_HANDLER_MINIDUMP_GENERATOR_H__ +#endif // CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_ diff --git a/src/client/linux/handler/minidump_test.cc b/src/client/linux/minidump_writer/minidump_writer_unittest.cc index f8c4e784..5ff336ce 100644 --- a/src/client/linux/handler/minidump_test.cc +++ b/src/client/linux/minidump_writer/minidump_writer_unittest.cc @@ -1,8 +1,6 @@ -// Copyright (c) 2006, Google Inc. +// Copyright (c) 2009, 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: @@ -29,58 +27,53 @@ // (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 <pthread.h> #include <unistd.h> +#include <sys/syscall.h> -#include <cassert> -#include <cstdio> -#include <cstdlib> -#include <cstring> - -#include "client/linux/handler/minidump_generator.h" +#include "client/linux/handler/exception_handler.h" +#include "client/linux/minidump_writer/minidump_writer.h" +#include "breakpad_googletest_includes.h" using namespace google_breakpad; -// Thread use this to see if it should stop working. -static bool should_exit = false; - -static void foo2(int arg) { - // Stack variable, used for debugging stack dumps. - int c = arg; - c = 0xcccccccc; - while (!should_exit) - sleep(1); -} +// This provides a wrapper around system calls which may be +// interrupted by a signal and return EINTR. See man 7 signal. +#define HANDLE_EINTR(x) ({ \ + typeof(x) __eintr_result__; \ + do { \ + __eintr_result__ = x; \ + } while (__eintr_result__ == -1 && errno == EINTR); \ + __eintr_result__;\ +}) -static void foo(int arg) { - // Stack variable, used for debugging stack dumps. - int b = arg; - b = 0xbbbbbbbb; - foo2(b); +namespace { +typedef testing::Test MinidumpWriterTest; } -static void *thread_main(void *) { - // Stack variable, used for debugging stack dumps. - int a = 0xaaaaaaaa; - foo(a); - return NULL; -} +TEST(MinidumpWriterTest, Setup) { + int fds[2]; + ASSERT_NE(-1, pipe(fds)); -static void CreateThread(int num) { - pthread_t h; - for (int i = 0; i < num; ++i) { - pthread_create(&h, NULL, thread_main, NULL); - pthread_detach(h); + const pid_t child = fork(); + if (child == 0) { + close(fds[1]); + char b; + HANDLE_EINTR(read(fds[0], &b, sizeof(b))); + close(fds[0]); + syscall(__NR_exit); } -} + close(fds[0]); + + ExceptionHandler::CrashContext context; + memset(&context, 0, sizeof(context)); + + char templ[] = "/tmp/minidump-writer-unittest-XXXXXX"; + mktemp(templ); + ASSERT_TRUE(WriteMinidump(templ, child, &context, sizeof(context))); + struct stat st; + ASSERT_EQ(stat(templ, &st), 0); + ASSERT_GT(st.st_size, 0u); + unlink(templ); -int main(int argc, char *argv[]) { - CreateThread(10); - google_breakpad::MinidumpGenerator mg; - if (mg.WriteMinidumpToFile("minidump_test.out", -1, 0, NULL)) - printf("Succeeded written minidump\n"); - else - printf("Failed to write minidump\n"); - should_exit = true; - return 0; + close(fds[1]); } |