From bcd46f007919b5063164c8c5c6c2bd4dfb62681e Mon Sep 17 00:00:00 2001 From: luly81 Date: Mon, 12 Mar 2007 01:53:18 +0000 Subject: Add Linux exception handler. Add Linux stab symbol dumper. Add minidump & symbol uploader for Linux. git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@126 4c0a9323-5329-0410-9bdc-e9ce6186880e --- src/client/linux/handler/Makefile | 51 ++ src/client/linux/handler/exception_handler.cc | 236 +++++++ src/client/linux/handler/exception_handler.h | 194 ++++++ src/client/linux/handler/exception_handler_test.cc | 124 ++++ src/client/linux/handler/linux_thread.cc | 384 +++++++++++ src/client/linux/handler/linux_thread.h | 201 ++++++ src/client/linux/handler/linux_thread_test.cc | 224 ++++++ src/client/linux/handler/minidump_generator.cc | 766 +++++++++++++++++++++ src/client/linux/handler/minidump_generator.h | 70 ++ src/client/linux/handler/minidump_test.cc | 86 +++ 10 files changed, 2336 insertions(+) create mode 100644 src/client/linux/handler/Makefile create mode 100644 src/client/linux/handler/exception_handler.cc create mode 100644 src/client/linux/handler/exception_handler.h create mode 100644 src/client/linux/handler/exception_handler_test.cc create mode 100644 src/client/linux/handler/linux_thread.cc create mode 100644 src/client/linux/handler/linux_thread.h create mode 100644 src/client/linux/handler/linux_thread_test.cc create mode 100644 src/client/linux/handler/minidump_generator.cc create mode 100644 src/client/linux/handler/minidump_generator.h create mode 100644 src/client/linux/handler/minidump_test.cc (limited to 'src/client/linux/handler') diff --git a/src/client/linux/handler/Makefile b/src/client/linux/handler/Makefile new file mode 100644 index 00000000..8d615b5d --- /dev/null +++ b/src/client/linux/handler/Makefile @@ -0,0 +1,51 @@ +CC=g++ + +CPPFLAGS=-gstabs -I../../.. -Wall -DNDEBUG -D_REENTRANT +LDFLAGS=-lpthread -lssl + +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)) +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_SRC) + +BIN=$(BIN_DIR)/minidump_test\ + $(BIN_DIR)/linux_thread_test\ + $(BIN_DIR)/exception_handler_test + +.PHONY:all clean + +all:$(BIN) + +$(BIN_DIR)/linux_thread_test:$(THREAD_TEST_OBJ) + $(CC) $(CPPFLAGS) $(LDFLAGS) $^ -o $@ + +$(BIN_DIR)/minidump_test:$(MINIDUMP_TEST_OBJ) + $(CC) $(CPPFLAGS) $(LDFLAGS) $^ -o $@ + +$(BIN_DIR)/exception_handler_test:$(EXCEPTION_TEST_OBJ) + $(CC) $(CPPFLAGS) $(LDFLAGS) $^ -o $@ + +clean: + rm -f $(BIN) *.o *.dmp diff --git a/src/client/linux/handler/exception_handler.cc b/src/client/linux/handler/exception_handler.cc new file mode 100644 index 00000000..59b50df7 --- /dev/null +++ b/src/client/linux/handler/exception_handler.cc @@ -0,0 +1,236 @@ +// Copyright (c) 2006, Google Inc. +// All rights reserved. +// +// Author: Li Liu +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "client/linux/handler/exception_handler.h" +#include "common/linux/guid_creator.h" +#include "google_breakpad/common/minidump_format.h" + +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 +}; + +std::vector *ExceptionHandler::handler_stack_ = NULL; +int ExceptionHandler::handler_stack_index_ = 0; +pthread_mutex_t ExceptionHandler::handler_stack_mutex_ = +PTHREAD_MUTEX_INITIALIZER; + +ExceptionHandler::ExceptionHandler(const string &dump_path, + FilterCallback filter, + MinidumpCallback callback, + void *callback_context, + bool install_handler) + : filter_(filter), + callback_(callback), + callback_context_(callback_context), + dump_path_(), + installed_handler_(install_handler) { + set_dump_path(dump_path); + + if (install_handler) { + SetupHandler(); + pthread_mutex_lock(&handler_stack_mutex_); + if (handler_stack_ == NULL) + handler_stack_ = new std::vector; + handler_stack_->push_back(this); + pthread_mutex_unlock(&handler_stack_mutex_); + } +} + +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::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_); +} + +bool ExceptionHandler::WriteMinidump() { + return InternalWriteMinidump(0, NULL); +} + +// 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, NULL); +} + +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; + + if (sigaltstack(&sig_stack, NULL) < 0) + return; + for (size_t i = 0; i < sizeof(SigTable) / sizeof(SigTable[0]); ++i) + SetupHandler(SigTable[i]); +} + +void ExceptionHandler::SetupHandler(int signo) { + struct sigaction act, old_act; + act.sa_handler = HandleException; + act.sa_flags = SA_ONSTACK; + if (sigaction(signo, &act, &old_act) < 0) + return; + old_handlers_[signo] = old_act.sa_handler; +} + +void ExceptionHandler::TeardownHandler(int signo) { + if (old_handlers_.find(signo) != old_handlers_.end()) { + struct sigaction act; + act.sa_handler = old_handlers_[signo]; + act.sa_flags = 0; + sigaction(signo, &act, 0); + } +} + +void ExceptionHandler::TeardownAllHandler() { + for (size_t i = 0; i < sizeof(SigTable) / sizeof(SigTable[0]); ++i) { + TeardownHandler(SigTable[i]); + } +} + +// 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. + const struct sigcontext *sig_ctx = + reinterpret_cast(&signo + 1); + 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. + current_handler->TeardownHandler(signo); + if (current_handler->InternalWriteMinidump(signo, 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. + typedef void (*SignalHandler)(int signo, struct sigcontext); + SignalHandler old_handler = + reinterpret_cast(current_handler->old_handlers_[signo]); + if (old_handler != NULL) + old_handler(signo, *sig_ctx); + } + + 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_); +} + +bool ExceptionHandler::InternalWriteMinidump(int signo, + const struct sigcontext *sig_ctx) { + if (filter_ && !filter_(callback_context_)) + return false; + + GUID guid; + bool success = false;; + char guid_str[kGUIDStringLength + 1]; + if (CreateGUID(&guid) && GUIDToString(&guid, guid_str, sizeof(guid_str))) { + char minidump_path[PATH_MAX]; + snprintf(minidump_path, sizeof(minidump_path), "%s/%s.dmp", + dump_path_c_, + guid_str); + success = minidump_generator_.WriteMinidumpToFile( + minidump_path, signo, sig_ctx); + if (callback_) + success = callback_(dump_path_c_, guid_str, + callback_context_, success); + } + return success; +} + +} // namespace google_breakpad diff --git a/src/client/linux/handler/exception_handler.h b/src/client/linux/handler/exception_handler.h new file mode 100644 index 00000000..0c0a2e83 --- /dev/null +++ b/src/client/linux/handler/exception_handler.h @@ -0,0 +1,194 @@ +// 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_EXCEPTION_HANDLER_H__ +#define CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H__ + +#include + +#include +#include +#include + +#include "client/linux/handler/minidump_generator.h" + +// Context information when exception occured. +struct sigcontex; + +namespace google_breakpad { + +using std::string; + +// +// ExceptionHandler +// +// ExceptionHandler can write a minidump file when an exception occurs, +// or when WriteMinidump() is called explicitly by your program. +// +// To have the exception handler write minidumps when an uncaught exception +// (crash) occurs, you should create an instance early in the execution +// of your program, and keep it around for the entire time you want to +// have crash handling active (typically, until shutdown). +// (NOTE): There should be only be one this kind of exception handler +// object per process. +// +// If you want to write minidumps without installing the exception handler, +// you can create an ExceptionHandler with install_handler set to false, +// then call WriteMinidump. You can also use this technique if you want to +// use different minidump callbacks for different call sites. +// +// In either case, a callback function is called when a minidump is written, +// which receives the unqiue id of the minidump. The caller can use this +// id to collect and write additional application state, and to launch an +// external crash-reporting application. +// +// 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 + // processing of an exception. A FilterCallback is called before writing + // a minidump. context is the parameter supplied by the user as + // callback_context when the handler was created. + // + // If a FilterCallback returns true, Breakpad will continue processing, + // attempting to write a minidump. If a FilterCallback returns false, + // Breakpad will immediately report the exception as unhandled without + // writing a minidump, allowing another handler the opportunity to handle it. + typedef bool (*FilterCallback)(void *context); + + // A callback function to run after the minidump has been written. + // minidump_id is a unique id for the dump, so the minidump + // file is \.dmp. context is the parameter supplied + // by the user as callback_context when the handler was created. succeeded + // indicates whether a minidump file was successfully written. + // + // If an exception occurred and the callback returns true, Breakpad will + // treat the exception as fully-handled, suppressing any other handlers from + // being notified of the exception. If the callback returns false, Breakpad + // will treat the exception as unhandled, and allow another handler to handle + // it. If there are no other handlers, Breakpad will report the exception to + // the system as unhandled, allowing a debugger or native crash dialog the + // opportunity to handle the exception. Most callback implementations + // should normally return the value of |succeeded|, or when they wish to + // not report an exception of handled, false. Callbacks will rarely want to + // return true directly (unless |succeeded| is true). + typedef bool (*MinidumpCallback)(const char *dump_path, + const char *minidump_id, + void *context, + bool succeeded); + + // 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 + // minidump. Minidump files will be written to dump_path, and the optional + // callback is called after writing the dump file, as described above. + // 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, + 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) { + dump_path_ = dump_path; + dump_path_c_ = dump_path_.c_str(); + } + + // 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, + 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 all handlers. + void TeardownAllHandler(); + + // Signal handler. + static void HandleException(int signo); + + bool InternalWriteMinidump(int signo, const struct sigcontext *sig_ctx); + + private: + FilterCallback filter_; + MinidumpCallback callback_; + void *callback_context_; + + // 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_; + // C style dump path. Keep this when setting dump path, since calling + // c_str() of std::string when crashing may not be safe. + const char *dump_path_c_; + + // True if the ExceptionHandler installed an unhandled exception filter + // when created (with an install_handler parameter set to true). + bool installed_handler_; + + // Keep the previous handlers for the signal. + typedef void (*sighandler_t)(int); + std::map old_handlers_; + + // 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 *handler_stack_; + // The index of the handler that should handle the next exception. + static int 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 &); +}; + +} // namespace google_breakpad + +#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 new file mode 100644 index 00000000..9c4a35a7 --- /dev/null +++ b/src/client/linux/handler/exception_handler_test.cc @@ -0,0 +1,124 @@ +// Copyright (c) 2006, Google Inc. +// All rights reserved. +// +// Author: Li Liu +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include +#include + +#include +#include +#include +#include + +#include "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(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(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 = 1; + 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/linux_thread.cc b/src/client/linux/handler/linux_thread.cc new file mode 100644 index 00000000..eeedec6e --- /dev/null +++ b/src/client/linux/handler/linux_thread.cc @@ -0,0 +1,384 @@ +// Copyright (c) 2006, Google Inc. +// All rights reserved. +// +// Author: Li Liu +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "client/linux/handler/linux_thread.h" + +using namespace google_breakpad; + +// This unamed namespace contains helper function. +namespace { + +// Context information for the callbacks when validating address by listing +// modules. +struct AddressValidatingContext { + uintptr_t address; + bool is_mapped; + + AddressValidatingContext() : address(0UL), is_mapped(false) { + } +}; + +// Convert from string to int. +bool LocalAtoi(char *s, int *r) { + assert(s != NULL); + assert(r != NULL); + char *endptr = NULL; + int ret = strtol(s, &endptr, 10); + if (endptr == s) + return false; + *r = ret; + return true; +} + +// Fill the proc path of a thread given its id. +void FillProcPath(int pid, char *path, int path_size) { + char pid_str[32]; + snprintf(pid_str, sizeof(pid_str), "%d", pid); + snprintf(path, path_size, "/proc/%s/", pid_str); +} + +// Read thread info from /proc/$pid/status. +bool ReadThreadInfo(int pid, ThreadInfo *info) { + assert(info != NULL); + char status_path[80]; + // Max size we want to read from status file. + static const int kStatusMaxSize = 1024; + char status_content[kStatusMaxSize]; + + FillProcPath(pid, status_path, sizeof(status_path)); + strcat(status_path, "status"); + int fd = open(status_path, O_RDONLY, 0); + if (fd < 0) + return false; + + int num_read = read(fd, status_content, kStatusMaxSize - 1); + if (num_read < 0) { + close(fd); + return false; + } + close(fd); + status_content[num_read] = '\0'; + + char *tgid_start = strstr(status_content, "Tgid:"); + if (tgid_start) + sscanf(tgid_start, "Tgid:\t%d\n", &(info->tgid)); + else + // tgid not supported by kernel?? + info->tgid = 0; + + tgid_start = strstr(status_content, "Pid:"); + if (tgid_start) { + sscanf(tgid_start, "Pid:\t%d\n" "PPid:\t%d\n", &(info->pid), + &(info->ppid)); + return true; + } + return false; +} + +// Callback invoked for each mapped module. +// It use the module's adderss range to validate the address. +bool IsAddressInModuleCallback(const ModuleInfo &module_info, + void *context) { + AddressValidatingContext *addr = + reinterpret_cast(context); + addr->is_mapped = ((addr->address >= module_info.start_addr) && + (addr->address <= module_info.start_addr + + module_info.size)); + return !addr->is_mapped; +} + +#if defined(__i386__) && !defined(NO_FRAME_POINTER) +void *GetNextFrame(void **last_ebp) { + void *sp = *last_ebp; + if ((unsigned long)sp == (unsigned long)last_ebp) + return NULL; + if ((unsigned long)sp & (sizeof(void *) - 1)) + return NULL; + if ((unsigned long)sp - (unsigned long)last_ebp > 100000) + return NULL; + return sp; +} +#else +void *GetNextFrame(void **last_ebp) { + return reinterpret_cast(last_ebp); +} +#endif + +// Suspend a thread by attaching to it. +bool SuspendThread(int pid, void *context) { + // This may fail if the thread has just died or debugged. + errno = 0; + if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) != 0 && + errno != 0) { + return false; + } + while (waitpid(pid, NULL, __WALL) < 0) { + if (errno != EINTR) { + ptrace(PTRACE_DETACH, pid, NULL, NULL); + return false; + } + } + return true; +} + +// Resume a thread by detaching from it. +bool ResumeThread(int pid, void *context) { + return ptrace(PTRACE_DETACH, pid, NULL, NULL) >= 0; +} + +// Callback to get the thread information. +// Will be called for each thread found. +bool ThreadInfoCallback(int pid, void *context) { + CallbackParam *thread_callback = + reinterpret_cast *>(context); + ThreadInfo thread_info; + if (ReadThreadInfo(pid, &thread_info) && thread_callback) { + // Invoke callback from caller. + return (thread_callback->call_back)(thread_info, thread_callback->context); + } + return false; +} + +} // namespace + +namespace google_breakpad { + +LinuxThread::LinuxThread(int pid) : pid_(pid) , threads_suspened_(false) { +} + +LinuxThread::~LinuxThread() { + if (threads_suspened_) + ResumeAllThreads(); +} + +int LinuxThread::SuspendAllThreads() { + CallbackParam callback_param(SuspendThread, NULL); + int thread_count = 0; + if ((thread_count = IterateProcSelfTask(pid_, &callback_param)) > 0) + threads_suspened_ = true; + return thread_count; +} + +void LinuxThread::ResumeAllThreads() const { + CallbackParam callback_param(ResumeThread, NULL); + IterateProcSelfTask(pid_, &callback_param); +} + +int LinuxThread::GetThreadCount() const { + return IterateProcSelfTask(pid_, NULL); +} + +int LinuxThread::ListThreads( + CallbackParam *thread_callback_param) const { + CallbackParam callback_param(ThreadInfoCallback, + thread_callback_param); + return IterateProcSelfTask(pid_, &callback_param); +} + +bool LinuxThread::GetRegisters(int pid, user_regs_struct *regs) const { + assert(regs); + return (regs != NULL && + (ptrace(PTRACE_GETREGS, pid, NULL, regs) == 0) && + errno == 0); +} + +// Get the floating-point registers of a thread. +// The caller must get the thread pid by ListThreads. +bool LinuxThread::GetFPRegisters(int pid, user_fpregs_struct *regs) const { + assert(regs); + return (regs != NULL && + (ptrace(PTRACE_GETREGS, pid, NULL, regs) ==0) && + errno == 0); +} + +bool LinuxThread::GetFPXRegisters(int pid, user_fpxregs_struct *regs) const { + assert(regs); + return (regs != NULL && + (ptrace(PTRACE_GETFPREGS, pid, NULL, regs) != 0) && + errno == 0); +} + +bool LinuxThread::GetDebugRegisters(int pid, DebugRegs *regs) const { + assert(regs); + +#define GET_DR(name, num)\ + name->dr##num = ptrace(PTRACE_PEEKUSER, pid,\ + offsetof(struct user, u_debugreg[num]), NULL) + GET_DR(regs, 0); + GET_DR(regs, 1); + GET_DR(regs, 2); + GET_DR(regs, 3); + GET_DR(regs, 4); + GET_DR(regs, 5); + GET_DR(regs, 6); + GET_DR(regs, 7); + return true; +} + +int LinuxThread::GetThreadStackDump(uintptr_t current_ebp, + uintptr_t current_esp, + void *buf, + int buf_size) const { + assert(buf); + assert(buf_size > 0); + + uintptr_t stack_bottom = GetThreadStackBottom(current_ebp); + int size = stack_bottom - current_esp; + size = buf_size > size ? size : buf_size; + if (size > 0) + memcpy(buf, reinterpret_cast(current_esp), size); + return size; +} + +// Get the stack bottom of a thread by stack walking. It works +// unless the stack has been corrupted or the frame pointer has been omited. +// This is just a temporary solution before we get better ideas about how +// this can be done. +// +// We will check each frame address by checking into module maps. +// TODO(liuli): Improve it. +uintptr_t LinuxThread::GetThreadStackBottom(uintptr_t current_ebp) const { + void **sp = reinterpret_cast(current_ebp); + void **previous_sp = sp; + while (sp && IsAddressMapped((uintptr_t)sp)) { + previous_sp = sp; + sp = reinterpret_cast(GetNextFrame(sp)); + } + return (uintptr_t)previous_sp; +} + +int LinuxThread::GetModuleCount() const { + return ListModules(NULL); +} + +int LinuxThread::ListModules( + CallbackParam *callback_param) const { + char line[512]; + char *maps_path = "/proc/self/maps"; + + int module_count = 0; + FILE *fp = fopen(maps_path, "r"); + if (fp == NULL) + return -1; + + uintptr_t start_addr; + uintptr_t end_addr; + while (fgets(line, sizeof(line), fp) != NULL) { + if (sscanf(line, "%x-%x", &start_addr, &end_addr) == 2) { + ModuleInfo module; + memset(&module, 0, sizeof(module)); + module.start_addr = start_addr; + module.size = end_addr - start_addr; + char *name = NULL; + assert(module.size > 0); + // Only copy name if the name is a valid path name. + if ((name = strchr(line, '/')) != NULL) { + // Get rid of the last '\n' in line + char *last_return = strchr(line, '\n'); + if (last_return != NULL) + *last_return = '\0'; + // Keep a space for the ending 0. + strncpy(module.name, name, sizeof(module.name) - 1); + ++module_count; + } + if (callback_param && + !(callback_param->call_back(module, callback_param->context))) + break; + } + } + fclose(fp); + return module_count; +} + +// Parse /proc/$pid/tasks to list all the threads of the process identified by +// pid. +int LinuxThread::IterateProcSelfTask(int pid, + CallbackParam *callback_param) const { + char task_path[80]; + FillProcPath(pid, task_path, sizeof(task_path)); + strcat(task_path, "task"); + + DIR *dir = opendir(task_path); + if (dir == NULL) + return -1; + + int pid_number = 0; + // Record the last pid we've found. This is used for duplicated thread + // removal. Duplicated thread information can be found in /proc/$pid/tasks. + int last_pid = -1; + struct dirent *entry = NULL; + while ((entry = readdir(dir)) != NULL) { + if (strcmp(entry->d_name, ".") && + strcmp(entry->d_name, "..")) { + int tpid = 0; + if (LocalAtoi(entry->d_name, &tpid) && + last_pid != tpid) { + last_pid = tpid; + ++pid_number; + // Invoke the callback. + if (callback_param && + !(callback_param->call_back)(tpid, callback_param->context)) + break; + } + } + } + closedir(dir); + return pid_number; +} + +// Check if the address is a valid virtual address. +// If the address is in any of the mapped modules, we take it as valid. +// Otherwise it is invalid. +bool LinuxThread::IsAddressMapped(uintptr_t address) const { + AddressValidatingContext addr; + addr.address = address; + CallbackParam callback_param(IsAddressInModuleCallback, + &addr); + ListModules(&callback_param); + return addr.is_mapped; +} + +} // namespace google_breakpad diff --git a/src/client/linux/handler/linux_thread.h b/src/client/linux/handler/linux_thread.h new file mode 100644 index 00000000..9f5d479f --- /dev/null +++ b/src/client/linux/handler/linux_thread.h @@ -0,0 +1,201 @@ +// 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 +#include + +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 +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 *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 *callback_param) const; + + // Get the bottom of the stack from ebp. + uintptr_t GetThreadStackBottom(uintptr_t current_esp) const; + + 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 *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 new file mode 100644 index 00000000..aeb5e64c --- /dev/null +++ b/src/client/linux/handler/linux_thread_test.cc @@ -0,0 +1,224 @@ +// Copyright (c) 2006, Google Inc. +// All rights reserved. +// +// Author: Li Liu +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include +#include + +#include +#include +#include + +#include "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(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 module_callback(ProcessOneModule, &threads); + threads.ListModules(&module_callback); + CallbackParam 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 new file mode 100644 index 00000000..3e4befc9 --- /dev/null +++ b/src/client/linux/handler/minidump_generator.cc @@ -0,0 +1,766 @@ +// Copyright (c) 2006, Google Inc. +// All rights reserved. +// +// Author: Li Liu +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "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" + +// 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; + + // Signal contex when crash happed. Can be NULL if this is a requested dump. + const 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(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 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(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 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 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) { + char *proc_cpu_path = "/proc/cpuinfo"; + char line[128]; + + struct CpuInfoEntry { + char *info_name; + int value; + } cpu_info_table[] = { + { "processor", -1 }, + { "model", 0 }, + { "stepping", 0 }, + { "cpuid level", 0 }, + { NULL, -1 }, + }; + + 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++; + } + } + 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; + } + 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); + char *os_info_table[] = { + uts.sysname, + uts.release, + uts.version, + uts.machine, + "GNU/Linux", + NULL + }; + for (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 *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(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 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 callback_param(ThreadInfomationCallback, + &context); + return thread_lister->ListThreads(&callback_param) == thread_count; +} + +bool WriteCVRecord(MinidumpFileWriter *minidump_writer, + MDRawModule *module, + const char *module_path) { + TypedMDRVA cv(minidump_writer); + + // Only return the last path component of the full module path + char *module_name = strrchr(module_path, '/'); + // Increment past the slash + if (module_name) + ++module_name; + else + module_name = ""; + + 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, module_name, module_name_length)) + return false; + + module->cv_record = cv.location(); + MDCVInfoPDB70 *cv_ptr = cv.get(); + 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; + } + return false; +} + +struct ModuleInfoCallbackCtx { + MinidumpFileWriter *minidump_writer; + const WriterArgument *writer_args; + TypedMDRVA *list; + int module_index; +}; + +bool ModuleInfoCallback(const ModuleInfo &module_info, + void *context) { + ModuleInfoCallbackCtx *callback_context = + static_cast(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 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 callback(ModuleInfoCallback, &context); + return writer_args->thread_lister->ListModules(&callback) == module_count; +} + +bool WriteSystemInfoStream(MinidumpFileWriter *minidump_writer, + const WriterArgument *writer_args, + MDRawDirectory *dir) { + TypedMDRVA 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 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 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 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 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(argument); + + if (!writer_args->thread_lister->SuspendAllThreads()) + return -1; + + if (writer_args->sig_ctx != NULL) { + 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 header(minidump_writer); + TypedMDRVA 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, + const 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.sig_ctx = sig_ctx; + + int cloned_pid = clone(Write, stack_.get() + kStackSize, + CLONE_VM | CLONE_FILES | CLONE_FS | CLONE_UNTRACED, + (void*)&argument); + waitpid(cloned_pid, NULL, __WALL); + return true; + } + + return false; +} + +} // namespace google_breakpad diff --git a/src/client/linux/handler/minidump_generator.h b/src/client/linux/handler/minidump_generator.h new file mode 100644 index 00000000..db74f914 --- /dev/null +++ b/src/client/linux/handler/minidump_generator.h @@ -0,0 +1,70 @@ +// 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_MINIDUMP_GENERATOR_H__ +#define CLIENT_LINUX_HANDLER_MINIDUMP_GENERATOR_H__ + +#include "google_breakpad/common/breakpad_types.h" +#include "processor/scoped_ptr.h" + +struct sigcontext; + +namespace google_breakpad { + +// +// 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, + const 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 stack_; +}; + +} // namespace google_breakpad + +#endif // CLIENT_LINUX_HANDLER_MINIDUMP_GENERATOR_H__ diff --git a/src/client/linux/handler/minidump_test.cc b/src/client/linux/handler/minidump_test.cc new file mode 100644 index 00000000..a6ebcc20 --- /dev/null +++ b/src/client/linux/handler/minidump_test.cc @@ -0,0 +1,86 @@ +// Copyright (c) 2006, Google Inc. +// All rights reserved. +// +// Author: Li Liu +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include +#include + +#include +#include +#include +#include + +#include "client/linux/handler/minidump_generator.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); +} + +static void foo(int arg) { + // Stack variable, used for debugging stack dumps. + int b = arg; + b = 0xbbbbbbbb; + foo2(b); +} + +static void *thread_main(void *) { + // Stack variable, used for debugging stack dumps. + int a = 0xaaaaaaaa; + foo(a); + return NULL; +} + +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); + } +} + +int main(int argc, char *argv[]) { + CreateThread(10); + google_breakpad::MinidumpGenerator mg; + if (mg.WriteMinidumpToFile("minidump_test.out", -1, NULL)) + printf("Succeeded written minidump\n"); + else + printf("Failed to write minidump\n"); + should_exit = true; + return 0; +} -- cgit v1.2.1