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; +} diff --git a/src/common/linux/dump_symbols.cc b/src/common/linux/dump_symbols.cc new file mode 100644 index 00000000..a21b9114 --- /dev/null +++ b/src/common/linux/dump_symbols.cc @@ -0,0 +1,613 @@ +// Copyright (c) 2006, 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "common/linux/dump_symbols.h" +#include "common/linux/file_id.h" +#include "common/linux/guid_creator.h" +#include "processor/scoped_ptr.h" + +// This namespace contains helper functions. +namespace { + +// Infomation of a line. +struct LineInfo { + // Offset from start of the function. + // Load from stab symbol. + ElfW(Off) rva_to_func; + // Offset from base of the loading binary. + ElfW(Off) rva_to_base; + // Size of the line. + // It is the difference of the starting address of the line and starting + // address of the next N_SLINE, N_FUN or N_SO. + uint32_t size; + // Line number. + uint32_t line_num; +}; + +// Information of a function. +struct FuncInfo { + // Name of the function. + const char *name; + // Offset from the base of the loading address. + ElfW(Off) rva_to_base; + // Virtual address of the function. + // Load from stab symbol. + ElfW(Addr) addr; + // Size of the function. + // It is the difference of the starting address of the function and starting + // address of the next N_FUN or N_SO. + uint32_t size; + // Total size of stack parameters. + uint32_t stack_param_size; + // Is the function defined in included function? + bool is_sol; + // Line information array. + std::vector line_info; +}; + +// Information of a source file. +struct SourceFileInfo { + // Name of the source file. + const char *name; + // Starting address of the source file. + ElfW(Addr) addr; + // Id of the source file. + int source_id; + // Functions information. + std::vector func_info; +}; + +// Information of a symbol table. +// This is the root of all types of symbol. +struct SymbolInfo { + std::vector source_file_info; +}; + +// Stab section name. +const char *kStabName = ".stab"; + +// Stab str section name. +const char *kStabStrName = ".stabstr"; + +// Demangle using abi call. +// Older GCC may not support it. +std::string Demangle(const char *mangled) { + int status = 0; + char *demangled = abi::__cxa_demangle(mangled, NULL, NULL, &status); + if (status == 0 && demangled != NULL) { + std::string str(demangled); + free(demangled); + return str; + } + return std::string(mangled); +} + +// Fix offset into virtual address by adding the mapped base into offsets. +// Make life easier when want to find something by offset. +void FixAddress(void *obj_base) { + ElfW(Word) base = reinterpret_cast(obj_base); + ElfW(Ehdr) *elf_header = static_cast(obj_base); + elf_header->e_phoff += base; + elf_header->e_shoff += base; + ElfW(Shdr) *sections = reinterpret_cast(elf_header->e_shoff); + for (int i = 0; i < elf_header->e_shnum; ++i) + sections[i].sh_offset += base; +} + +// Find the prefered loading address of the binary. +// It is the PT_LOAD segment with offset to zero. +ElfW(Addr) GetLoadingAddress(const ElfW(Phdr) *program_headers, int nheader) { + for (int i = 0; i < nheader; ++i) { + const ElfW(Phdr) &header = program_headers[i]; + if (header.p_type == PT_LOAD && + header.p_offset == 0) + return header.p_vaddr; + } + assert(!"Should get a valid loading address"); + return 0; +} + +bool WriteFormat(int fd, const char *fmt, ...) { + va_list list; + char buffer[4096]; + ssize_t expected, written; + va_start(list, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, list); + expected = strlen(buffer); + written = write(fd, buffer, strlen(buffer)); + va_end(list); + return expected == written; +} + +bool IsValidElf(const ElfW(Ehdr) *elf_header) { + return memcmp(elf_header, ELFMAG, SELFMAG) == 0; +} + +const ElfW(Shdr) *FindSectionByName(const char *name, + const ElfW(Shdr) *sections, + const ElfW(Shdr) *strtab, + int nsection) { + assert(name != NULL); + assert(sections != NULL); + assert(nsection > 0); + + int name_len = strlen(name); + if (name_len == 0) + return NULL; + + for (int i = 0; i < nsection; ++i) { + const char *section_name = + (char*)(strtab->sh_offset + sections[i].sh_name); + if (!strncmp(name, section_name, name_len)) + return sections + i; + } + return NULL; +} + +// TODO(liuli): Computer the stack parameter size. +// Expect parameter variables are immediately following the N_FUN symbol. +// Will need to parse the type information to get a correct size. +int LoadStackParamSize(struct nlist *list, + struct nlist *list_end, + struct FuncInfo *func_info) { + struct nlist *cur_list = list; + assert(cur_list->n_type == N_FUN); + ++cur_list; + int step = 1; + while (cur_list < list_end && cur_list->n_type == N_PSYM) { + ++cur_list; + ++step; + } + func_info->stack_param_size = 0; + return step; +} + +int LoadLineInfo(struct nlist *list, + struct nlist *list_end, + struct FuncInfo *func_info) { + struct nlist *cur_list = list; + func_info->is_sol = false; + do { + // Skip non line information. + while (cur_list < list_end && cur_list->n_type != N_SLINE) { + // Only exit when got another function, or source file. + if (cur_list->n_type == N_FUN || cur_list->n_type == N_SO) + return cur_list - list; + if (cur_list->n_type == N_SOL) + func_info->is_sol = true; + ++cur_list; + } + struct LineInfo line; + while (cur_list < list_end && cur_list->n_type == N_SLINE) { + line.rva_to_func = cur_list->n_value; + line.line_num = cur_list->n_desc; + func_info->line_info.push_back(line); + ++cur_list; + } + } while (list < list_end); + + return cur_list - list; +} + +int LoadFuncSymbols(struct nlist *list, + struct nlist *list_end, + const ElfW(Shdr) *stabstr_section, + struct SourceFileInfo *source_file_info) { + struct nlist *cur_list = list; + assert(cur_list->n_type == N_SO); + ++cur_list; + + source_file_info->func_info.clear(); + while (cur_list < list_end) { + // Go until the function symbol. + while (cur_list < list_end && cur_list->n_type != N_FUN) { + if (cur_list->n_type == N_SO) { + return cur_list - list; + } + ++cur_list; + continue; + } + if (cur_list->n_type == N_FUN) { + struct FuncInfo func_info; + memset(&func_info, 0, sizeof(func_info)); + func_info.name = + reinterpret_cast(cur_list->n_un.n_strx + + stabstr_section->sh_offset); + func_info.addr = cur_list->n_value; + // Stack parameter size. + cur_list += LoadStackParamSize(cur_list, list_end, &func_info); + // Line info. + cur_list += LoadLineInfo(cur_list, list_end, &func_info); + // Functions in this module should have address bigger than the module + // startring address. + if (func_info.addr >= source_file_info->addr) { + source_file_info->func_info.push_back(func_info); + } + } + } + return cur_list - list; +} + +// Comapre the address. +// The argument should have a memeber named "addr" +template +bool CompareAddress(T1 *a, T2 *b) { + return a->addr < b->addr; +} + +// Sort the array into increasing ordered array based on the virtual address. +// Return vector of pointers to the elements in the incoming array. So caller +// should make sure the returned vector lives longer than the incoming vector. +template +std::vector SortByAddress(std::vector *array) { + std::vector sorted_array_ptr; + sorted_array_ptr.reserve(array->size()); + for (size_t i = 0; i < array->size(); ++i) + sorted_array_ptr.push_back(&(array->at(i))); + std::sort(sorted_array_ptr.begin(), + sorted_array_ptr.end(), + std::ptr_fun(CompareAddress)); + + return sorted_array_ptr; +} + +// Find the address of the next function or source file symbol in the symbol +// table. The address should be bigger than the current function's address. +ElfW(Addr) NextAddress(std::vector *sorted_functions, + std::vector *sorted_files, + const struct FuncInfo &func_info) { + std::vector::iterator next_func_iter = + std::find_if(sorted_functions->begin(), + sorted_functions->end(), + std::bind1st( + std::ptr_fun( + CompareAddress + ), + &func_info) + ); + if (next_func_iter != sorted_functions->end()) + return (*next_func_iter)->addr; + + std::vector::iterator next_file_iter = + std::find_if(sorted_files->begin(), + sorted_files->end(), + std::bind1st( + std::ptr_fun( + CompareAddress + ), + &func_info) + ); + if (next_file_iter != sorted_files->end()) { + return (*next_file_iter)->addr; + } + return 0; +} + +// Compute size and rva information based on symbols loaded from stab section. +bool ComputeSizeAndRVA(ElfW(Addr) loading_addr, struct SymbolInfo *symbols) { + std::vector sorted_files = + SortByAddress(&(symbols->source_file_info)); + for (size_t i = 0; i < sorted_files.size(); ++i) { + struct SourceFileInfo &source_file = *sorted_files[i]; + std::vector sorted_functions = + SortByAddress(&(source_file.func_info)); + for (size_t j = 0; j < sorted_functions.size(); ++j) { + struct FuncInfo &func_info = *sorted_functions[j]; + assert(func_info.addr >= loading_addr); + func_info.rva_to_base = func_info.addr - loading_addr; + func_info.size = 0; + ElfW(Addr) next_addr = NextAddress(&sorted_functions, + &sorted_files, + func_info); + if (next_addr == 0) + continue; + func_info.size = next_addr - func_info.addr; + + // Compute line size. + for (size_t k = 0; k < func_info.line_info.size(); ++k) { + struct LineInfo &line_info = func_info.line_info[k]; + line_info.size = 0; + if (k + 1 < func_info.line_info.size()) { + line_info.size = + func_info.line_info[k + 1].rva_to_func - line_info.rva_to_func; + } else { + if (next_addr != 0) { + ElfW(Off) next_addr_offset = next_addr - func_info.addr; + line_info.size = next_addr_offset - line_info.rva_to_func; + } + } + line_info.rva_to_base = line_info.rva_to_func + func_info.rva_to_base; + } // for each line. + } // for each function. + } // for each source file. + return true; +} + +bool LoadSymbols(const ElfW(Shdr) *stab_section, + const ElfW(Shdr) *stabstr_section, + ElfW(Addr) loading_addr, + struct SymbolInfo *symbols) { + if (stab_section == NULL || stabstr_section == NULL) + return false; + + struct nlist *lists = + reinterpret_cast(stab_section->sh_offset); + int nstab = stab_section->sh_size / sizeof(struct nlist); + int source_id = 0; + // First pass, load all symbols from the object file. + for (int i = 0; i < nstab; ) { + int step = 1; + struct nlist *cur_list = lists + i; + if (cur_list->n_type == N_SO) { + // FUNC
+ struct SourceFileInfo source_file_info; + source_file_info.name = reinterpret_cast(cur_list->n_un.n_strx + + stabstr_section->sh_offset); + source_file_info.addr = cur_list->n_value; + if (strchr(source_file_info.name, '.')) + source_file_info.source_id = source_id++; + else + source_file_info.source_id = -1; + step = LoadFuncSymbols(cur_list, lists + nstab, + stabstr_section, &source_file_info); + symbols->source_file_info.push_back(source_file_info); + } + i += step; + } + // Second pass, compute the size of functions and lines. + return ComputeSizeAndRVA(loading_addr, symbols); +} + +bool LoadSymbols(ElfW(Ehdr) *elf_header, struct SymbolInfo *symbols) { + // Translate all offsets in section headers into address. + FixAddress(elf_header); + ElfW(Addr) loading_addr = GetLoadingAddress( + reinterpret_cast(elf_header->e_phoff), + elf_header->e_phnum); + if (loading_addr == 0) + return false; + + const ElfW(Shdr) *sections = + reinterpret_cast(elf_header->e_shoff); + const ElfW(Shdr) *strtab = sections + elf_header->e_shstrndx; + const ElfW(Shdr) *stab_section = + FindSectionByName(kStabName, sections, strtab, elf_header->e_shnum); + if (stab_section == NULL) { + fprintf(stderr, "Stab section not found.\n"); + return false; + } + const ElfW(Shdr) *stabstr_section = stab_section->sh_link + sections; + + // Load symbols. + return LoadSymbols(stab_section, stabstr_section, loading_addr, symbols); +} + +bool WriteModuleInfo(int fd, ElfW(Half) arch, const std::string &obj_file) { + const char *arch_name = NULL; + if (arch == EM_386) + arch_name = "x86"; + else if (arch == EM_X86_64) + arch_name = "x86_64"; + else + return false; + + unsigned char identifier[16]; + google_breakpad::FileID file_id(obj_file.c_str()); + if (file_id.ElfFileIdentifier(identifier)) { + char identifier_str[40]; + file_id.ConvertIdentifierToString(identifier, + identifier_str, sizeof(identifier_str)); + char id_no_dash[40]; + int id_no_dash_len = 0; + memset(id_no_dash, 0, sizeof(id_no_dash)); + for (int i = 0; identifier_str[i] != '\0'; ++i) + if (identifier_str[i] != '-') + id_no_dash[id_no_dash_len++] = identifier_str[i]; + // Add an extra "0" by the end. + id_no_dash[id_no_dash_len++] = '0'; + std::string filename = obj_file; + size_t slash_pos = obj_file.find_last_of("/"); + if (slash_pos != std::string::npos) + filename = obj_file.substr(slash_pos + 1); + return WriteFormat(fd, "MODULE Linux %s %s 1 %s\n", arch_name, + id_no_dash, filename.c_str()); + } + return false; +} + +bool WriteSourceFileInfo(int fd, const struct SymbolInfo &symbols) { + for (size_t i = 0; i < symbols.source_file_info.size(); ++i) { + if (symbols.source_file_info[i].source_id != -1) { + const char *name = symbols.source_file_info[i].name; + if (!WriteFormat(fd, "FILE %d %s\n", + symbols.source_file_info[i].source_id, name)) + return false; + } + } + return true; +} + +bool WriteOneFunction(int fd, int source_id, + const struct FuncInfo &func_info){ + // Discard the ending part of the name. + std::string func_name(func_info.name); + std::string::size_type last_colon = func_name.find_last_of(':'); + if (last_colon != std::string::npos) + func_name = func_name.substr(0, last_colon); + func_name = Demangle(func_name.c_str()); + + if (func_info.size <= 0) + return true; + + if (WriteFormat(fd, "FUNC %lx %lx %d %s\n", + func_info.rva_to_base, + func_info.size, + func_info.stack_param_size, + func_name.c_str())) { + for (size_t i = 0; i < func_info.line_info.size(); ++i) { + const struct LineInfo &line_info = func_info.line_info[i]; + if (!WriteFormat(fd, "%lx %lx %d %d\n", + line_info.rva_to_base, + line_info.size, + line_info.line_num, + source_id)) + return false; + } + return true; + } + return false; +} + +bool WriteFunctionInfo(int fd, const struct SymbolInfo &symbols) { + for (size_t i = 0; i < symbols.source_file_info.size(); ++i) { + const struct SourceFileInfo &file_info = symbols.source_file_info[i]; + for (size_t j = 0; j < file_info.func_info.size(); ++j) { + const struct FuncInfo &func_info = file_info.func_info[j]; + if (!WriteOneFunction(fd, file_info.source_id, func_info)) + return false; + } + } + return true; +} + +bool DumpStabSymbols(int fd, const struct SymbolInfo &symbols) { + return WriteSourceFileInfo(fd, symbols) && + WriteFunctionInfo(fd, symbols); +} + +// +// FDWrapper +// +// Wrapper class to make sure opened file is closed. +// +class FDWrapper { + public: + explicit FDWrapper(int fd) : + fd_(fd) { + } + ~FDWrapper() { + if (fd_ != -1) + close(fd_); + } + int get() { + return fd_; + } + int release() { + int fd = fd_; + fd_ = -1; + return fd; + } + private: + int fd_; +}; + +// +// MmapWrapper +// +// Wrapper class to make sure mapped regions are unmapped. +// +class MmapWrapper { + public: + MmapWrapper(void *mapped_address, size_t mapped_size) : + base_(mapped_address), size_(mapped_size) { + } + ~MmapWrapper() { + if (base_ != NULL) { + assert(size_ > 0); + munmap(base_, size_); + } + } + void release() { + base_ = NULL; + size_ = 0; + } + + private: + void *base_; + size_t size_; +}; + +} // namespace + +namespace google_breakpad { + +bool DumpSymbols::WriteSymbolFile(const std::string &obj_file, + const std::string &symbol_file) { + int obj_fd = open(obj_file.c_str(), O_RDONLY); + if (obj_fd < 0) + return false; + FDWrapper obj_fd_wrapper(obj_fd); + struct stat st; + if (fstat(obj_fd, &st) != 0 && st.st_size <= 0) + return false; + void *obj_base = mmap(NULL, st.st_size, + PROT_READ | PROT_WRITE, MAP_PRIVATE, obj_fd, 0); + if (!obj_base) + return false; + MmapWrapper map_wrapper(obj_base, st.st_size); + ElfW(Ehdr) *elf_header = reinterpret_cast(obj_base); + if (!IsValidElf(elf_header)) + return false; + struct SymbolInfo symbols; + if (!LoadSymbols(elf_header, &symbols)) + return false; + // Write to symbol file. + int sym_fd = open(symbol_file.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0666); + if (sym_fd < 0) + return false; + FDWrapper sym_fd_wrapper(sym_fd); + if (WriteModuleInfo(sym_fd, elf_header->e_machine, obj_file) && + DumpStabSymbols(sym_fd, symbols)) + return true; + + // Remove the symbol file if failed to write the symbols. + unlink(symbol_file.c_str()); + return false; +} + +} // namespace google_breakpad diff --git a/src/common/linux/dump_symbols.h b/src/common/linux/dump_symbols.h new file mode 100644 index 00000000..e0cbdf66 --- /dev/null +++ b/src/common/linux/dump_symbols.h @@ -0,0 +1,48 @@ +// Copyright (c) 2006, 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. +// +// dump_symbols.cc: Implements a linux stab debugging format dumper. +// + +#ifndef COMMON_LINUX_DUMP_SYMBOLS_H__ +#define COMMON_LINUX_DUMP_SYMBOLS_H__ + +#include + +namespace google_breakpad { + +class DumpSymbols { + public: + bool WriteSymbolFile(const std::string &obj_file, + const std::string &symbol_file); +}; + +} // namespace google_breakpad + +#endif // COMMON_LINUX_DUMP_SYMBOLS_H__ diff --git a/src/common/linux/file_id.cc b/src/common/linux/file_id.cc new file mode 100644 index 00000000..db074fe1 --- /dev/null +++ b/src/common/linux/file_id.cc @@ -0,0 +1,143 @@ +// Copyright (c) 2006, 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. +// +// file_id.cc: Return a unique identifier for a file +// +// See file_id.h for documentation +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/linux/file_id.h" + +namespace google_breakpad { + +static bool FindElfTextSection(const void *elf_mapped_base, + const void **text_start, + int *text_size) { + assert(elf_mapped_base); + assert(text_start); + assert(text_size); + + const unsigned char *elf_base = + static_cast(elf_mapped_base); + const ElfW(Ehdr) *elf_header = + reinterpret_cast(elf_base); + if (memcmp(elf_header, ELFMAG, SELFMAG) != 0) + return false; + *text_start = NULL; + *text_size = 0; + const ElfW(Shdr) *sections = + reinterpret_cast(elf_base + elf_header->e_shoff); + const char *text_section_name = ".text"; + int name_len = strlen(text_section_name); + const ElfW(Shdr) *string_section = sections + elf_header->e_shstrndx; + const ElfW(Shdr) *text_section = NULL; + for (int i = 0; i < elf_header->e_shnum; ++i) { + if (sections[i].sh_type == SHT_PROGBITS) { + const char *section_name = (char*)(elf_base + + string_section->sh_offset + + sections[i].sh_name); + if (!strncmp(section_name, text_section_name, name_len)) { + text_section = §ions[i]; + break; + } + } + } + if (text_section != NULL && text_section->sh_size > 0) { + int text_section_size = text_section->sh_size; + *text_start = elf_base + text_section->sh_offset; + *text_size = text_section_size; + } + return true; +} + +FileID::FileID(const char *path) { + strncpy(path_, path, sizeof(path_)); +} + +bool FileID::ElfFileIdentifier(unsigned char identifier[16]) { + int fd = open(path_, O_RDONLY); + if (fd < 0) + return false; + struct stat st; + if (fstat(fd, &st) != 0 && st.st_size <= 0) { + close(fd); + return false; + } + void *base = mmap(NULL, st.st_size, + PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); + if (!base) { + close(fd); + return false; + } + bool success = false; + const void *text_section = NULL; + int text_size = 0; + if (FindElfTextSection(base, &text_section, &text_size) && (text_size > 0)) { + MD5_CTX md5; + MD5_Init(&md5); + MD5_Update(&md5, text_section, text_size); + MD5_Final(identifier, &md5); + success = true; + } + + close(fd); + munmap(base, st.st_size); + return success; +} + +// static +void FileID::ConvertIdentifierToString(const unsigned char identifier[16], + char *buffer, int buffer_length) { + int buffer_idx = 0; + for (int idx = 0; (buffer_idx < buffer_length) && (idx < 16); ++idx) { + int hi = (identifier[idx] >> 4) & 0x0F; + int lo = (identifier[idx]) & 0x0F; + + if (idx == 4 || idx == 6 || idx == 8 || idx == 10) + buffer[buffer_idx++] = '-'; + + buffer[buffer_idx++] = (hi >= 10) ? 'A' + hi - 10 : '0' + hi; + buffer[buffer_idx++] = (lo >= 10) ? 'A' + lo - 10 : '0' + lo; + } + + // NULL terminate + buffer[(buffer_idx < buffer_length) ? buffer_idx : buffer_idx - 1] = 0; +} + +} // namespace google_breakpad diff --git a/src/common/linux/file_id.h b/src/common/linux/file_id.h new file mode 100644 index 00000000..5e1cd6e1 --- /dev/null +++ b/src/common/linux/file_id.h @@ -0,0 +1,66 @@ +// Copyright (c) 2006, 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. +// +// file_id.h: Return a unique identifier for a file +// + +#ifndef COMMON_LINUX_FILE_ID_H__ +#define COMMON_LINUX_FILE_ID_H__ + +#include + +namespace google_breakpad { + +class FileID { + public: + FileID(const char *path); + ~FileID() {}; + + // Load the identifier for the elf file path specified in the constructor into + // |identifier|. Return false if the identifier could not be created for the + // file. + // The current implementation will return the MD5 hash of the file's bytes. + bool ElfFileIdentifier(unsigned char identifier[16]); + + // Convert the |identifier| data to a NULL terminated string. The string will + // be formatted as a UUID (e.g., 22F065BB-FC9C-49F7-80FE-26A7CEBD7BCE). + // The |buffer| should be at least 37 bytes long to receive all of the data + // and termination. Shorter buffers will contain truncated data. + static void ConvertIdentifierToString(const unsigned char identifier[16], + char *buffer, int buffer_length); + + private: + // Storage for the path specified + char path_[PATH_MAX]; +}; + +} // namespace google_breakpad + +#endif // COMMON_LINUX_FILE_ID_H__ + diff --git a/src/common/linux/guid_creator.cc b/src/common/linux/guid_creator.cc new file mode 100644 index 00000000..d6d4a5bb --- /dev/null +++ b/src/common/linux/guid_creator.cc @@ -0,0 +1,82 @@ +// Copyright (c) 2006, 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 +#include +#include +#include +#include + +#include "common/linux/guid_creator.h" + +// +// GUIDGenerator +// +// This class is used to generate random GUID. +// Currently use random number to generate a GUID since Linux has +// no native GUID generator. This should be OK since we don't expect +// crash to happen very offen. +// +class GUIDGenerator { + public: + GUIDGenerator() { + srandom(time(NULL)); + } + + bool CreateGUID(GUID *guid) const { + guid->data1 = random(); + guid->data2 = (u_int16_t)(random()); + guid->data3 = (u_int16_t)(random()); + *reinterpret_cast(&guid->data4[0]) = random(); + *reinterpret_cast(&guid->data4[4]) = random(); + return true; + } +}; + +// Guid generator. +const GUIDGenerator kGuidGenerator; + +bool CreateGUID(GUID *guid) { + return kGuidGenerator.CreateGUID(guid); +}; + +// Parse guid to string. +bool GUIDToString(const GUID *guid, char *buf, int buf_len) { + // Should allow more space the the max length of GUID. + assert(buf_len > kGUIDStringLength); + int num = snprintf(buf, buf_len, kGUIDFormatString, + guid->data1, guid->data2, guid->data3, + *reinterpret_cast(&(guid->data4[0])), + *reinterpret_cast(&(guid->data4[4]))); + if (num != kGUIDStringLength) + return false; + + buf[num] = '\0'; + return true; +} diff --git a/src/common/linux/guid_creator.h b/src/common/linux/guid_creator.h new file mode 100644 index 00000000..c86d856c --- /dev/null +++ b/src/common/linux/guid_creator.h @@ -0,0 +1,48 @@ +// Copyright (c) 2006, 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 COMMON_LINUX_GUID_CREATOR_H__ +#define COMMON_LINUX_GUID_CREATOR_H__ + +#include "google_breakpad/common/minidump_format.h" + +typedef MDGUID GUID; + +// Format string for parsing GUID. +#define kGUIDFormatString "%08x-%04x-%04x-%08x-%08x" +// Length of GUID string. Don't count the ending '\0'. +#define kGUIDStringLength 36 + +// Create a guid. +bool CreateGUID(GUID *guid); + +// Get the string from guid. +bool GUIDToString(const GUID *guid, char *buf, int buf_len); + +#endif diff --git a/src/common/linux/http_upload.cc b/src/common/linux/http_upload.cc new file mode 100644 index 00000000..8556cfc0 --- /dev/null +++ b/src/common/linux/http_upload.cc @@ -0,0 +1,146 @@ +// Copyright (c) 2006, 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 +#include +#include +#include + +#include "common/linux/http_upload.h" + +namespace { + +// Callback to get the response data from server. +static size_t WriteCallback(void *ptr, size_t size, + size_t nmemb, void *userp) { + if (!userp) + return 0; + + std::string *response = reinterpret_cast(userp); + size_t real_size = size * nmemb; + response->append(reinterpret_cast(ptr), real_size); + return real_size; +} + +} // namespace + +namespace google_breakpad { + +static const char kUserAgent[] = "Breakpad/1.0 (Linux)"; + +// static +bool HTTPUpload::SendRequest(const string &url, + const map ¶meters, + const string &upload_file, + const string &file_part_name, + const string &proxy, + const string &proxy_user_pwd, + string *response_body) { + if (!CheckParameters(parameters)) + return false; + + CURL *curl = curl_easy_init(); + CURLcode err_code = CURLE_OK; + + if (curl) { + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_USERAGENT, kUserAgent); + // Set proxy information if necessary. + if (!proxy.empty()) + curl_easy_setopt(curl, CURLOPT_PROXY, proxy.c_str()); + if (!proxy_user_pwd.empty()) + curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, proxy_user_pwd.c_str()); + + struct curl_httppost *formpost = NULL; + struct curl_httppost *lastptr = NULL; + // Add form data. + map::const_iterator iter = parameters.begin(); + for (; iter != parameters.end(); ++iter) + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, iter->first.c_str(), + CURLFORM_COPYCONTENTS, iter->second.c_str(), + CURLFORM_END); + + // Add form file. + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, file_part_name.c_str(), + CURLFORM_FILE, upload_file.c_str(), + CURLFORM_END); + + curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost); + + // Disable 100-continue header. + struct curl_slist *headerlist = NULL; + char buf[] = "Expect:"; + headerlist = curl_slist_append(headerlist, buf); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist); + + if (response_body != NULL) { + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, + reinterpret_cast(response_body)); + } + + err_code = curl_easy_perform(curl); +#ifndef NDEBUG + if (err_code != CURLE_OK) + fprintf(stderr, "Failed to send http request to %s, error: %s\n", + url.c_str(), + curl_easy_strerror(err_code)); +#endif + + if (curl != NULL) + curl_easy_cleanup(curl); + if (formpost != NULL) + curl_formfree(formpost); + if (headerlist != NULL) + curl_slist_free_all(headerlist); + return err_code == CURLE_OK; + } + return false; +} + +// static +bool HTTPUpload::CheckParameters(const map ¶meters) { + for (map::const_iterator pos = parameters.begin(); + pos != parameters.end(); ++pos) { + const string &str = pos->first; + if (str.size() == 0) + return false; // disallow empty parameter names + for (unsigned int i = 0; i < str.size(); ++i) { + int c = str[i]; + if (c < 32 || c == '"' || c > 127) { + return false; + } + } + } + return true; +} + +} // namespace google_breakpad diff --git a/src/common/linux/http_upload.h b/src/common/linux/http_upload.h new file mode 100644 index 00000000..040fd2de --- /dev/null +++ b/src/common/linux/http_upload.h @@ -0,0 +1,80 @@ +// Copyright (c) 2006, 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. + +// HTTPUpload provides a "nice" API to send a multipart HTTP(S) POST +// request using libcurl. It currently supports requests that contain +// a set of string parameters (key/value pairs), and a file to upload. + +#ifndef COMMON_LINUX_HTTP_UPLOAD_H__ +#define COMMON_LINUX_HTTP_UPLOAD_H__ + +#include +#include + +namespace google_breakpad { + +using std::string; +using std::map; + +class HTTPUpload { + public: + // Sends the given set of parameters, along with the contents of + // upload_file, as a multipart POST request to the given URL. + // file_part_name contains the name of the file part of the request + // (i.e. it corresponds to the name= attribute on an . + // Parameter names must contain only printable ASCII characters, + // and may not contain a quote (") character. + // Only HTTP(S) URLs are currently supported. Returns true on success. + // If the request is successful and response_body is non-NULL, + // the response body will be returned in response_body. + static bool SendRequest(const string &url, + const map ¶meters, + const string &upload_file, + const string &file_part_name, + const string &proxy, + const string &proxy_user_pwd, + string *response_body); + + private: + // Checks that the given list of parameters has only printable + // ASCII characters in the parameter name, and does not contain + // any quote (") characters. Returns true if so. + static bool CheckParameters(const map ¶meters); + + // No instances of this class should be created. + // Disallow all constructors, destructors, and operator=. + HTTPUpload(); + explicit HTTPUpload(const HTTPUpload &); + void operator=(const HTTPUpload &); + ~HTTPUpload(); +}; + +} // namespace google_breakpad + +#endif // COMMON_LINUX_HTTP_UPLOAD_H__ diff --git a/src/tools/linux/dump_syms/Makefile b/src/tools/linux/dump_syms/Makefile new file mode 100644 index 00000000..87491996 --- /dev/null +++ b/src/tools/linux/dump_syms/Makefile @@ -0,0 +1,28 @@ +CPP=g++ +CC=gcc + +CPPFLAGS=-gstabs -I../../.. -Wall -D_REENTRANT +LDFLAGS=-lssl + +.PHONY:all clean + +BIN=dump_syms + +all:$(BIN) + +DUMP_OBJ=dump_symbols.o guid_creator.o dump_syms.o file_id.o + +dump_syms:$(DUMP_OBJ) + $(CPP) $(CPPFLAGS) $(LDFLAGS) -o $@ $^ + +dump_symbols.o:../../../common/linux/dump_symbols.cc + $(CPP) $(CPPFLAGS) -c $^ + +guid_creator.o:../../../common/linux/guid_creator.cc + $(CPP) $(CPPFLAGS) -c $^ + +file_id.o:../../../common/linux/file_id.cc + $(CPP) $(CPPFLAGS) -c $^ + +clean: + rm -f $(BIN) $(DUMP_OBJ) diff --git a/src/tools/linux/dump_syms/dump_syms.cc b/src/tools/linux/dump_syms/dump_syms.cc new file mode 100644 index 00000000..3eca3745 --- /dev/null +++ b/src/tools/linux/dump_syms/dump_syms.cc @@ -0,0 +1,56 @@ +// Copyright (c) 2006, 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 + +#include "common/linux/dump_symbols.h" + +using namespace google_breakpad; + +int main(int argc, char **argv) { + if (argc < 2 || argc > 3) { + fprintf(stderr, + "Usage: %s [output-symbol-file]\n", + argv[0]); + return 1; + } + + const char *binary = argv[1]; + std::string symbol_file(binary); + symbol_file += ".sym"; + if (argc == 3) + symbol_file = argv[2]; + + DumpSymbols dumper; + if (dumper.WriteSymbolFile(binary, symbol_file)) + printf("Symbol file successfully written: %s\n", symbol_file.c_str()); + else + printf("Failed to write symbol file.\n"); + return 0; +} diff --git a/src/tools/linux/symupload/Makefile b/src/tools/linux/symupload/Makefile new file mode 100644 index 00000000..7d4257d8 --- /dev/null +++ b/src/tools/linux/symupload/Makefile @@ -0,0 +1,24 @@ +CC=g++ + +CPPFLAGS=-gstabs -I../../.. -Wall -D_REENTRANT + +.PHONY:all clean + +BIN=minidump_upload sym_upload + +all:$(BIN) + +DUMP_UPLOAD_OBJ=minidump_upload.o http_upload.o +SYM_UPLOAD_OBJ=sym_upload.o http_upload.o + +minidump_upload:$(DUMP_UPLOAD_OBJ) + $(CC) $(CPPFLAGS) `curl-config --libs` -o $@ $^ + +sym_upload:$(SYM_UPLOAD_OBJ) + $(CC) $(CPPFLAGS) `curl-config --libs` -o $@ $^ + +http_upload.o:../../../common/linux/http_upload.cc + $(CC) $(CPPFLAGS) `curl-config --cflags` -c $^ + +clean: + rm *.o $(BIN) diff --git a/src/tools/linux/symupload/minidump_upload.cc b/src/tools/linux/symupload/minidump_upload.cc new file mode 100644 index 00000000..de9f7092 --- /dev/null +++ b/src/tools/linux/symupload/minidump_upload.cc @@ -0,0 +1,143 @@ +// Copyright (c) 2006, 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. + +// minidump_upload.m: Upload a minidump to a HTTP server. The upload is sent as +// a multipart/form-data POST request with the following parameters: +// prod: the product name +// ver: the product version +// symbol_file: the breakpad format symbol file + +#include + +#include + +#include "common/linux/http_upload.h" + +using google_breakpad::HTTPUpload; + +struct Options { + std::string minidumpPath; + std::string uploadURLStr; + std::string product; + std::string version; + std::string proxy; + std::string proxy_user_pwd; + bool success; +}; + +//============================================================================= +static void Start(Options *options) { + std::map parameters; + // Add parameters + parameters["prod"] = options->product; + parameters["ver"] = options->version; + + // Send it + std::string response; + bool success = HTTPUpload::SendRequest(options->uploadURLStr, + parameters, + options->minidumpPath, + "upload_file_minidump", + options->proxy, + options->proxy_user_pwd, + &response); + + if (success) { + printf("Successfully sent the minidump file.\n"); + } else { + printf("Failed to send minidump\n"); + printf("Response:\n"); + printf("%s\n", response.c_str()); + } + options->success = success; +} + +//============================================================================= +static void +Usage(int argc, const char *argv[]) { + fprintf(stderr, "Submit minidump information.\n"); + fprintf(stderr, "Usage: %s [options...] -p -v " + "\n", argv[0]); + fprintf(stderr, "Options:\n"); + fprintf(stderr, " should be a minidump.\n"); + fprintf(stderr, " is the destination for the upload\n"); + + fprintf(stderr, "-p:\t Product name\n"); + fprintf(stderr, "-v:\t Product version\n"); + fprintf(stderr, "-x:\t Use HTTP proxy on given port\n"); + fprintf(stderr, "-u:\t Set proxy user and password\n"); + fprintf(stderr, "-h:\t Usage\n"); + fprintf(stderr, "-?:\t Usage\n"); +} + +//============================================================================= +static void +SetupOptions(int argc, const char *argv[], Options *options) { + extern int optind; + char ch; + + while ((ch = getopt(argc, (char * const *)argv, "p:u:v:x:h?")) != -1) { + switch (ch) { + case 'p': + options->product = optarg; + break; + case 'u': + options->proxy_user_pwd = optarg; + break; + case 'v': + options->version = optarg; + break; + case 'x': + options->proxy = optarg; + break; + + default: + Usage(argc, argv); + exit(0); + break; + } + } + + if ((argc - optind) != 2) { + fprintf(stderr, "%s: Missing symbols file and/or upload-URL\n", argv[0]); + Usage(argc, argv); + exit(1); + } + + options->minidumpPath = argv[optind]; + options->uploadURLStr = argv[optind + 1]; +} + +//============================================================================= +int main (int argc, const char * argv[]) { + Options options; + SetupOptions(argc, argv, &options); + Start(&options); + return options.success ? 0 : 1; +} diff --git a/src/tools/linux/symupload/sym_upload.cc b/src/tools/linux/symupload/sym_upload.cc new file mode 100644 index 00000000..eb1e71d1 --- /dev/null +++ b/src/tools/linux/symupload/sym_upload.cc @@ -0,0 +1,219 @@ +// Copyright (c) 2006, 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. + +// symupload.cc: Upload a symbol file to a HTTP server. The upload is sent as +// a multipart/form-data POST request with the following parameters: +// code_file: the basename of the module, e.g. "app" +// debug_file: the basename of the debugging file, e.g. "app" +// debug_identifier: the debug file's identifier, usually consisting of +// the guid and age embedded in the pdb, e.g. +// "11111111BBBB3333DDDD555555555555F" +// version: the file version of the module, e.g. "1.2.3.4" +// os: the operating system that the module was built for +// cpu: the CPU that the module was built for +// symbol_file: the contents of the breakpad-format symbol file + +#include + +#include +#include +#include +#include +#include +#include + +#include "common/linux/http_upload.h" + +using google_breakpad::HTTPUpload; + +typedef struct { + std::string symbolsPath; + std::string uploadURLStr; + std::string proxy; + std::string proxy_user_pwd; + std::string version; + bool success; +} Options; + +static void TokenizeByChar(const std::string &source_string, + int c, std::vector *results) { + assert(results); + std::string::size_type cur_pos = 0, next_pos = 0; + while ((next_pos = source_string.find(c, cur_pos)) != std::string::npos) { + if (next_pos != cur_pos) + results->push_back(source_string.substr(cur_pos, next_pos - cur_pos)); + cur_pos = next_pos + 1; + } + if (cur_pos < source_string.size() && next_pos != cur_pos) + results->push_back(source_string.substr(cur_pos)); +} + +//============================================================================= +// Parse out the module line which have 6 parts. +// MODULE +static bool ModuleDataForSymbolFile(const std::string &file, + std::vector *module_parts) { + assert(module_parts); + const size_t kModulePartNumber = 6; + FILE *fp = fopen(file.c_str(), "r"); + if (fp) { + char buffer[1024]; + if (fgets(buffer, sizeof(buffer), fp)) { + std::string line(buffer); + std::string::size_type line_break_pos = line.find_first_of('\n'); + if (line_break_pos == std::string::npos) { + assert(!"The file is invalid!"); + fclose(fp); + return false; + } + line.resize(line_break_pos); + const char kDelimiter = ' '; + TokenizeByChar(line, kDelimiter, module_parts); + if (module_parts->size() != kModulePartNumber) + module_parts->clear(); + } + fclose(fp); + } + + return module_parts->size() == kModulePartNumber; +} + +//============================================================================= +static std::string CompactIdentifier(const std::string &uuid, + const std::string &age) { + std::vector components; + TokenizeByChar(uuid, '-', &components); + std::string result; + for (size_t i = 0; i < components.size(); ++i) + result += components[i]; + result += age; + return result; +} + +//============================================================================= +static void Start(Options *options) { + std::map parameters; + options->success = false; + std::vector module_parts; + if (!ModuleDataForSymbolFile(options->symbolsPath, &module_parts)) { + fprintf(stderr, "Failed to parse symbol file!\n"); + return; + } + + std::string compacted_id = CompactIdentifier(module_parts[3], + module_parts[4]); + + // Add parameters + if (!options->version.empty()) + parameters["version"] = options->version; + + // MODULE + // 0 1 2 3 4 5 + parameters["age"] = "1"; + parameters["os"] = module_parts[1]; + parameters["cpu"] = module_parts[2]; + parameters["debug_file"] = module_parts[5]; + parameters["code_file"] = module_parts[5]; + parameters["debug_identifier"] = compacted_id; + std::string response; + bool success = HTTPUpload::SendRequest(options->uploadURLStr, + parameters, + options->symbolsPath, + "symbol_file", + options->proxy, + options->proxy_user_pwd, + &response); + + if (success) { + printf("Successfully sent the symbol file.\n"); + } else { + printf("Failed to send symbol file.\n"); + printf("Response:\n"); + printf("%s\n", response.c_str()); + } + options->success = success; +} + +//============================================================================= +static void +Usage(int argc, const char *argv[]) { + fprintf(stderr, "Submit symbol information.\n"); + fprintf(stderr, "Usage: %s [options...] \n", argv[0]); + fprintf(stderr, "Options:\n"); + fprintf(stderr, " should be created by using the dump_syms tool.\n"); + fprintf(stderr, " is the destination for the upload\n"); + fprintf(stderr, "-v:\t Version information (e.g., 1.2.3.4)\n"); + fprintf(stderr, "-x:\t Use HTTP proxy on given port\n"); + fprintf(stderr, "-u:\t Set proxy user and password\n"); + fprintf(stderr, "-h:\t Usage\n"); + fprintf(stderr, "-?:\t Usage\n"); +} + +//============================================================================= +static void +SetupOptions(int argc, const char *argv[], Options *options) { + extern int optind; + char ch; + + while ((ch = getopt(argc, (char * const *)argv, "u:v:x:h?")) != -1) { + switch (ch) { + case 'u': + options->proxy_user_pwd = optarg; + break; + case 'v': + options->version = optarg; + break; + case 'x': + options->proxy = optarg; + break; + + default: + Usage(argc, argv); + exit(0); + break; + } + } + + if ((argc - optind) != 2) { + fprintf(stderr, "%s: Missing symbols file and/or upload-URL\n", argv[0]); + Usage(argc, argv); + exit(1); + } + + options->symbolsPath = argv[optind]; + options->uploadURLStr = argv[optind + 1]; +} + +//============================================================================= +int main (int argc, const char * argv[]) { + Options options; + SetupOptions(argc, argv, &options); + Start(&options); + return options.success ? 0 : 1; +}