Merge of Breakpad Chrome Linux fork
A=agl, Lei Zhang R=nealsid, agl git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@384 4c0a9323-5329-0410-9bdc-e9ce6186880e
This commit is contained in:
parent
7c48629d49
commit
b0baafc4da
@ -1,55 +1,45 @@
|
|||||||
CXX=g++
|
CXX=g++
|
||||||
CC=gcc
|
CC=gcc
|
||||||
|
|
||||||
CXXFLAGS=-gstabs+ -I../../.. -Wall -D_REENTRANT
|
CXXFLAGS=-gstabs+ -I../../../ -I../../../testing/gtest/include -I../../../testing/include -I../../../testing/gtest -D_REENTRANT -m32
|
||||||
|
CFLAGS=$(CXXFLAGS)
|
||||||
LDFLAGS=-lpthread
|
LDFLAGS=-lpthread
|
||||||
|
|
||||||
OBJ_DIR=.
|
OBJ_DIR=.
|
||||||
BIN_DIR=.
|
BIN_DIR=.
|
||||||
|
|
||||||
THREAD_SRC=linux_thread.cc
|
TEST_CC_SRC=exception_handler_unittest.cc \
|
||||||
SHARE_SRC=../../minidump_file_writer.cc\
|
exception_handler.cc \
|
||||||
../../../common/string_conversion.cc\
|
../../../testing/gtest/src/gtest-all.cc \
|
||||||
../../../common/linux/file_id.cc\
|
../../../common/linux/guid_creator.cc \
|
||||||
minidump_generator.cc
|
../minidump_writer/minidump_writer.cc \
|
||||||
HANDLER_SRC=exception_handler.cc\
|
../../minidump_file_writer.cc \
|
||||||
../../../common/linux/guid_creator.cc
|
../minidump_writer/linux_dumper.cc \
|
||||||
SHARE_C_SRC=../../../common/convert_UTF.c
|
../../../testing/gtest/src/gtest_main.cc \
|
||||||
|
../../../common/string_conversion.cc \
|
||||||
|
../minidump_writer/directory_reader_unittest.cc \
|
||||||
|
../minidump_writer/line_reader_unittest.cc \
|
||||||
|
../minidump_writer/linux_dumper_unittest.cc \
|
||||||
|
../minidump_writer/minidump_writer_unittest.cc
|
||||||
|
|
||||||
THREAD_TEST_SRC=linux_thread_test.cc
|
TEST_C_SRC = ../../../common/convert_UTF.c
|
||||||
MINIDUMP_TEST_SRC=minidump_test.cc
|
|
||||||
EXCEPTION_TEST_SRC=exception_handler_test.cc
|
|
||||||
|
|
||||||
THREAD_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o,$(THREAD_SRC))
|
TEST_CC_OBJ=$(patsubst %.cc, $(OBJ_DIR)/%.o,$(TEST_CC_SRC))
|
||||||
SHARE_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o,$(SHARE_SRC))
|
TEST_C_OBJ=$(patsubst %.c, $(OBJ_DIR)/%.o, $(TEST_C_SRC))
|
||||||
HANDLER_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o,$(HANDLER_SRC))
|
|
||||||
SHARE_C_OBJ=$(patsubst %.c,$(OBJ_DIR)/%.o,$(SHARE_C_SRC)) md5.o
|
|
||||||
THREAD_TEST_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o, $(THREAD_TEST_SRC))\
|
|
||||||
$(THREAD_OBJ)
|
|
||||||
MINIDUMP_TEST_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o, $(MINIDUMP_TEST_SRC))\
|
|
||||||
$(THREAD_OBJ) $(SHARE_OBJ) $(SHARE_C_OBJ)
|
|
||||||
EXCEPTION_TEST_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o, $(EXCEPTION_TEST_SRC))\
|
|
||||||
$(THREAD_OBJ) $(SHARE_OBJ) $(SHARE_C_OBJ) $(HANDLER_OBJ)
|
|
||||||
|
|
||||||
BIN=$(BIN_DIR)/minidump_test\
|
LINUX_CLIENT_BIN=$(BIN_DIR)/linux_client_test
|
||||||
$(BIN_DIR)/linux_thread_test\
|
|
||||||
$(BIN_DIR)/exception_handler_test
|
BIN=$(LINUX_CLIENT_BIN)
|
||||||
|
|
||||||
.PHONY:all clean
|
.PHONY:all clean
|
||||||
|
|
||||||
|
check:$(BIN)
|
||||||
|
$(LINUX_CLIENT_BIN)
|
||||||
|
|
||||||
all:$(BIN)
|
all:$(BIN)
|
||||||
|
|
||||||
$(BIN_DIR)/linux_thread_test:$(THREAD_TEST_OBJ)
|
$(BIN_DIR)/linux_client_test:$(TEST_CC_OBJ) $(TEST_C_OBJ)
|
||||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) $^ -o $@
|
$(CXX) $(CXXFLAGS) $(LDFLAGS) $^ -o $@
|
||||||
|
|
||||||
$(BIN_DIR)/minidump_test:$(MINIDUMP_TEST_OBJ)
|
|
||||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) $^ -o $@
|
|
||||||
|
|
||||||
$(BIN_DIR)/exception_handler_test:$(EXCEPTION_TEST_OBJ)
|
|
||||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) $^ -o $@
|
|
||||||
|
|
||||||
md5.o:../../../common/md5.c
|
|
||||||
$(CC) $(CXXFLAGS) -c $^
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(BIN) *.o *.dmp
|
rm -f $(BIN) $(TEST_CC_OBJ) $(TEST_C_OBJ)
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) 2006, Google Inc.
|
// Copyright (c) 2009, Google Inc.
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
//
|
//
|
||||||
// Author: Li Liu
|
|
||||||
//
|
|
||||||
// Redistribution and use in source and binary forms, with or without
|
// Redistribution and use in source and binary forms, with or without
|
||||||
// modification, are permitted provided that the following conditions are
|
// modification, are permitted provided that the following conditions are
|
||||||
// met:
|
// met:
|
||||||
@ -29,48 +27,87 @@
|
|||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
#include <signal.h>
|
// The ExceptionHandler object installs signal handlers for a number of
|
||||||
#include <sys/stat.h>
|
// signals. We rely on the signal handler running on the thread which crashed
|
||||||
#include <sys/types.h>
|
// in order to identify it. This is true of the synchronous signals (SEGV etc),
|
||||||
#include <unistd.h>
|
// but not true of ABRT. Thus, if you send ABRT to yourself in a program which
|
||||||
|
// uses ExceptionHandler, you need to use tgkill to direct it to the current
|
||||||
|
// thread.
|
||||||
|
//
|
||||||
|
// The signal flow looks like this:
|
||||||
|
//
|
||||||
|
// SignalHandler (uses a global stack of ExceptionHandler objects to find
|
||||||
|
// | one to handle the signal. If the first rejects it, try
|
||||||
|
// | the second etc...)
|
||||||
|
// V
|
||||||
|
// HandleSignal ----------------------------| (clones a new process which
|
||||||
|
// | | shares an address space with
|
||||||
|
// (wait for cloned | the crashed process. This
|
||||||
|
// process) | allows us to ptrace the crashed
|
||||||
|
// | | process)
|
||||||
|
// V V
|
||||||
|
// (set signal handler to ThreadEntry (static function to bounce
|
||||||
|
// SIG_DFL and rethrow, | back into the object)
|
||||||
|
// killing the crashed |
|
||||||
|
// process) V
|
||||||
|
// DoDump (writes minidump)
|
||||||
|
// |
|
||||||
|
// V
|
||||||
|
// sys_exit
|
||||||
|
//
|
||||||
|
|
||||||
#include <cassert>
|
// This code is a little fragmented. Different functions of the ExceptionHandler
|
||||||
#include <cstdlib>
|
// class run in a number of different contexts. Some of them run in a normal
|
||||||
#include <cstdio>
|
// context and are easy to code, others run in a compromised context and the
|
||||||
#include <ctime>
|
// restrictions at the top of minidump_writer.cc apply: no libc and use the
|
||||||
#include <linux/limits.h>
|
// alternative malloc. Each function should have comment above it detailing the
|
||||||
|
// context which it runs in.
|
||||||
|
|
||||||
#include "client/linux/handler/exception_handler.h"
|
#include "client/linux/handler/exception_handler.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <linux/limits.h>
|
||||||
|
#include <sched.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <sys/signal.h>
|
||||||
|
#include <sys/syscall.h>
|
||||||
|
#include <sys/ucontext.h>
|
||||||
|
#include <sys/user.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "common/linux/linux_libc_support.h"
|
||||||
|
#include "common/linux/linux_syscall_support.h"
|
||||||
|
#include "common/linux/memory.h"
|
||||||
|
#include "client/linux/minidump_writer//minidump_writer.h"
|
||||||
#include "common/linux/guid_creator.h"
|
#include "common/linux/guid_creator.h"
|
||||||
#include "google_breakpad/common/minidump_format.h"
|
|
||||||
|
// A wrapper for the tgkill syscall: send a signal to a specific thread.
|
||||||
|
static int tgkill(pid_t tgid, pid_t tid, int sig) {
|
||||||
|
syscall(__NR_tgkill, tgid, tid, sig);
|
||||||
|
}
|
||||||
|
|
||||||
namespace google_breakpad {
|
namespace google_breakpad {
|
||||||
|
|
||||||
// Signals that we are interested.
|
// The list of signals which we consider to be crashes. The default action for
|
||||||
int SigTable[] = {
|
// all these signals must be Core (see man 7 signal) because we rethrow the
|
||||||
#if defined(SIGSEGV)
|
// signal after handling it and expect that it'll be fatal.
|
||||||
SIGSEGV,
|
static const int kExceptionSignals[] = {
|
||||||
#endif
|
SIGSEGV, SIGABRT, SIGFPE, SIGILL, SIGBUS, -1
|
||||||
#ifdef SIGABRT
|
|
||||||
SIGABRT,
|
|
||||||
#endif
|
|
||||||
#ifdef SIGFPE
|
|
||||||
SIGFPE,
|
|
||||||
#endif
|
|
||||||
#ifdef SIGILL
|
|
||||||
SIGILL,
|
|
||||||
#endif
|
|
||||||
#ifdef SIGBUS
|
|
||||||
SIGBUS,
|
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<ExceptionHandler*> *ExceptionHandler::handler_stack_ = NULL;
|
// We can stack multiple exception handlers. In that case, this is the global
|
||||||
int ExceptionHandler::handler_stack_index_ = 0;
|
// which holds the stack.
|
||||||
|
std::vector<ExceptionHandler*>* ExceptionHandler::handler_stack_ = NULL;
|
||||||
|
unsigned ExceptionHandler::handler_stack_index_ = 0;
|
||||||
pthread_mutex_t ExceptionHandler::handler_stack_mutex_ =
|
pthread_mutex_t ExceptionHandler::handler_stack_mutex_ =
|
||||||
PTHREAD_MUTEX_INITIALIZER;
|
PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
|
||||||
ExceptionHandler::ExceptionHandler(const string &dump_path,
|
// Runs before crashing: normal context.
|
||||||
|
ExceptionHandler::ExceptionHandler(const std::string &dump_path,
|
||||||
FilterCallback filter,
|
FilterCallback filter,
|
||||||
MinidumpCallback callback,
|
MinidumpCallback callback,
|
||||||
void *callback_context,
|
void *callback_context,
|
||||||
@ -79,212 +116,76 @@ ExceptionHandler::ExceptionHandler(const string &dump_path,
|
|||||||
callback_(callback),
|
callback_(callback),
|
||||||
callback_context_(callback_context),
|
callback_context_(callback_context),
|
||||||
dump_path_(),
|
dump_path_(),
|
||||||
installed_handler_(install_handler) {
|
handler_installed_(install_handler),
|
||||||
|
crash_handler_(NULL) {
|
||||||
set_dump_path(dump_path);
|
set_dump_path(dump_path);
|
||||||
|
|
||||||
act_.sa_handler = HandleException;
|
|
||||||
act_.sa_flags = SA_ONSTACK;
|
|
||||||
sigemptyset(&act_.sa_mask);
|
|
||||||
// now, make sure we're blocking all the signals we are handling
|
|
||||||
// when we're handling any of them
|
|
||||||
for ( size_t i = 0; i < sizeof(SigTable) / sizeof(SigTable[0]); ++i) {
|
|
||||||
sigaddset(&act_.sa_mask, SigTable[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (install_handler) {
|
if (install_handler) {
|
||||||
SetupHandler();
|
InstallHandlers();
|
||||||
|
|
||||||
pthread_mutex_lock(&handler_stack_mutex_);
|
pthread_mutex_lock(&handler_stack_mutex_);
|
||||||
if (handler_stack_ == NULL)
|
if (handler_stack_ == NULL)
|
||||||
handler_stack_ = new std::vector<ExceptionHandler *>;
|
handler_stack_ = new std::vector<ExceptionHandler *>;
|
||||||
handler_stack_->push_back(this);
|
handler_stack_->push_back(this);
|
||||||
pthread_mutex_unlock(&handler_stack_mutex_);
|
pthread_mutex_unlock(&handler_stack_mutex_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Runs before crashing: normal context.
|
||||||
ExceptionHandler::~ExceptionHandler() {
|
ExceptionHandler::~ExceptionHandler() {
|
||||||
TeardownAllHandler();
|
UninstallHandlers();
|
||||||
pthread_mutex_lock(&handler_stack_mutex_);
|
|
||||||
if (handler_stack_->back() == this) {
|
|
||||||
handler_stack_->pop_back();
|
|
||||||
} else {
|
|
||||||
fprintf(stderr, "warning: removing Breakpad handler out of order\n");
|
|
||||||
for (std::vector<ExceptionHandler *>::iterator iterator =
|
|
||||||
handler_stack_->begin();
|
|
||||||
iterator != handler_stack_->end();
|
|
||||||
++iterator) {
|
|
||||||
if (*iterator == this) {
|
|
||||||
handler_stack_->erase(iterator);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (handler_stack_->empty()) {
|
|
||||||
// When destroying the last ExceptionHandler that installed a handler,
|
|
||||||
// clean up the handler stack.
|
|
||||||
delete handler_stack_;
|
|
||||||
handler_stack_ = NULL;
|
|
||||||
}
|
|
||||||
pthread_mutex_unlock(&handler_stack_mutex_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ExceptionHandler::WriteMinidump() {
|
// Runs before crashing: normal context.
|
||||||
bool success = InternalWriteMinidump(0, 0, NULL);
|
bool ExceptionHandler::InstallHandlers() {
|
||||||
UpdateNextID();
|
// We run the signal handlers on an alternative stack because we might have
|
||||||
return success;
|
// crashed because of a stack overflow.
|
||||||
}
|
|
||||||
|
|
||||||
// static
|
// We use this value rather than SIGSTKSZ because we would end up overrunning
|
||||||
bool ExceptionHandler::WriteMinidump(const string &dump_path,
|
// such a small stack.
|
||||||
MinidumpCallback callback,
|
static const unsigned kSigStackSize = 8192;
|
||||||
void *callback_context) {
|
|
||||||
ExceptionHandler handler(dump_path, NULL, callback,
|
|
||||||
callback_context, false);
|
|
||||||
return handler.InternalWriteMinidump(0, 0, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ExceptionHandler::SetupHandler() {
|
signal_stack = malloc(kSigStackSize);
|
||||||
// Signal on a different stack to avoid using the stack
|
stack_t stack;
|
||||||
// of the crashing thread.
|
memset(&stack, 0, sizeof(stack));
|
||||||
struct sigaltstack sig_stack;
|
stack.ss_sp = signal_stack;
|
||||||
sig_stack.ss_sp = malloc(MINSIGSTKSZ);
|
stack.ss_size = kSigStackSize;
|
||||||
if (sig_stack.ss_sp == NULL)
|
|
||||||
return;
|
|
||||||
sig_stack.ss_size = MINSIGSTKSZ;
|
|
||||||
sig_stack.ss_flags = 0;
|
|
||||||
|
|
||||||
if (sigaltstack(&sig_stack, NULL) < 0)
|
if (sigaltstack(&stack, NULL) == -1)
|
||||||
return;
|
|
||||||
for (size_t i = 0; i < sizeof(SigTable) / sizeof(SigTable[0]); ++i)
|
|
||||||
SetupHandler(SigTable[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ExceptionHandler::SetupHandler(int signo) {
|
|
||||||
|
|
||||||
// We're storing pointers to the old signal action
|
|
||||||
// structure, rather than copying the structure
|
|
||||||
// because we can't count on the sa_mask field to
|
|
||||||
// be scalar.
|
|
||||||
struct sigaction *old_act = &old_actions_[signo];
|
|
||||||
|
|
||||||
if (sigaction(signo, &act_, old_act) < 0)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ExceptionHandler::TeardownHandler(int signo) {
|
|
||||||
TeardownHandler(signo, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ExceptionHandler::TeardownHandler(int signo, struct sigaction *final_handler) {
|
|
||||||
if (old_actions_[signo].sa_handler) {
|
|
||||||
struct sigaction *act = &old_actions_[signo];
|
|
||||||
sigaction(signo, act, final_handler);
|
|
||||||
memset(&old_actions_[signo], 0x0, sizeof(struct sigaction));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
|
||||||
// However, if we are being called by another signal handler passing the
|
|
||||||
// signal up the chain, then we may not have this random extra parameter,
|
|
||||||
// so we may have to walk the stack to find it. We do the actual work
|
|
||||||
// on another thread, where it's a little safer, but we want the ebp
|
|
||||||
// from this frame to find it.
|
|
||||||
uintptr_t current_ebp = 0;
|
|
||||||
asm volatile ("movl %%ebp, %0"
|
|
||||||
:"=m"(current_ebp));
|
|
||||||
|
|
||||||
pthread_mutex_lock(&handler_stack_mutex_);
|
|
||||||
ExceptionHandler *current_handler =
|
|
||||||
handler_stack_->at(handler_stack_->size() - ++handler_stack_index_);
|
|
||||||
pthread_mutex_unlock(&handler_stack_mutex_);
|
|
||||||
|
|
||||||
// Restore original handler.
|
|
||||||
struct sigaction old_action;
|
|
||||||
current_handler->TeardownHandler(signo, &old_action);
|
|
||||||
|
|
||||||
struct sigcontext *sig_ctx = NULL;
|
|
||||||
if (current_handler->InternalWriteMinidump(signo, current_ebp, &sig_ctx)) {
|
|
||||||
// Fully handled this exception, safe to exit.
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
} else {
|
|
||||||
// Exception not fully handled, will call the next handler in stack to
|
|
||||||
// process it.
|
|
||||||
if (old_action.sa_handler != NULL && sig_ctx != NULL) {
|
|
||||||
|
|
||||||
// Have our own typedef, because of the comment above w.r.t signal
|
|
||||||
// context on the stack
|
|
||||||
typedef void (*SignalHandler)(int signo, struct sigcontext);
|
|
||||||
|
|
||||||
SignalHandler old_handler =
|
|
||||||
reinterpret_cast<SignalHandler>(old_action.sa_handler);
|
|
||||||
|
|
||||||
sigset_t old_set;
|
|
||||||
// Use SIG_BLOCK here because we don't want to unblock a signal
|
|
||||||
// that the signal handler we're currently in needs to block
|
|
||||||
sigprocmask(SIG_BLOCK, &old_action.sa_mask, &old_set);
|
|
||||||
old_handler(signo, *sig_ctx);
|
|
||||||
sigprocmask(SIG_SETMASK, &old_set, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
uintptr_t sighandler_ebp,
|
|
||||||
struct sigcontext **sig_ctx) {
|
|
||||||
if (filter_ && !filter_(callback_context_))
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
bool success = false;
|
struct sigaction sa;
|
||||||
// Block all the signals we want to process when writting minidump.
|
memset(&sa, 0, sizeof(sa));
|
||||||
// We don't want it to be interrupted.
|
sigemptyset(&sa.sa_mask);
|
||||||
sigset_t sig_blocked, sig_old;
|
|
||||||
bool blocked = true;
|
// mask all exception signals when we're handling one of them.
|
||||||
sigfillset(&sig_blocked);
|
for (unsigned i = 0; kExceptionSignals[i] != -1; ++i)
|
||||||
for (size_t i = 0; i < sizeof(SigTable) / sizeof(SigTable[0]); ++i)
|
sigaddset(&sa.sa_mask, kExceptionSignals[i]);
|
||||||
sigdelset(&sig_blocked, SigTable[i]);
|
|
||||||
if (sigprocmask(SIG_BLOCK, &sig_blocked, &sig_old) != 0) {
|
sa.sa_sigaction = SignalHandler;
|
||||||
blocked = false;
|
sa.sa_flags = SA_ONSTACK | SA_SIGINFO;
|
||||||
fprintf(stderr, "google_breakpad::ExceptionHandler::HandleException: "
|
|
||||||
"failed to block signals.\n");
|
for (unsigned i = 0; kExceptionSignals[i] != -1; ++i) {
|
||||||
|
struct sigaction* old = new struct sigaction;
|
||||||
|
if (sigaction(kExceptionSignals[i], &sa, old) == -1)
|
||||||
|
return false;
|
||||||
|
old_handlers_.push_back(std::make_pair(kExceptionSignals[i], old));
|
||||||
}
|
}
|
||||||
|
|
||||||
success = minidump_generator_.WriteMinidumpToFile(
|
|
||||||
next_minidump_path_c_, signo, sighandler_ebp, sig_ctx);
|
|
||||||
|
|
||||||
// Unblock the signals.
|
|
||||||
if (blocked) {
|
|
||||||
sigprocmask(SIG_SETMASK, &sig_old, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (callback_)
|
|
||||||
success = callback_(dump_path_c_, next_minidump_id_c_,
|
|
||||||
callback_context_, success);
|
|
||||||
return success;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Runs before crashing: normal context.
|
||||||
|
void ExceptionHandler::UninstallHandlers() {
|
||||||
|
for (unsigned i = 0; i < old_handlers_.size(); ++i) {
|
||||||
|
struct sigaction *action =
|
||||||
|
reinterpret_cast<struct sigaction*>(old_handlers_[i].second);
|
||||||
|
sigaction(old_handlers_[i].first, action, NULL);
|
||||||
|
delete action;
|
||||||
|
}
|
||||||
|
|
||||||
|
old_handlers_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Runs before crashing: normal context.
|
||||||
void ExceptionHandler::UpdateNextID() {
|
void ExceptionHandler::UpdateNextID() {
|
||||||
GUID guid;
|
GUID guid;
|
||||||
char guid_str[kGUIDStringLength + 1];
|
char guid_str[kGUIDStringLength + 1];
|
||||||
@ -302,4 +203,120 @@ void ExceptionHandler::UpdateNextID() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This function runs in a compromised context: see the top of the file.
|
||||||
|
// Runs on the crashing thread.
|
||||||
|
// static
|
||||||
|
void ExceptionHandler::SignalHandler(int sig, siginfo_t* info, void* uc) {
|
||||||
|
// All the exception signals are blocked at this point.
|
||||||
|
|
||||||
|
pthread_mutex_lock(&handler_stack_mutex_);
|
||||||
|
|
||||||
|
if (!handler_stack_->size()) {
|
||||||
|
pthread_mutex_unlock(&handler_stack_mutex_);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = handler_stack_->size() - 1; i >= 0; --i) {
|
||||||
|
if ((*handler_stack_)[i]->HandleSignal(sig, info, uc)) {
|
||||||
|
// successfully handled: We are in an invalid state since an exception
|
||||||
|
// signal has been delivered. We don't call the exit handlers because
|
||||||
|
// they could end up corrupting on-disk state.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&handler_stack_mutex_);
|
||||||
|
|
||||||
|
// Terminate ourselves with the same signal so that our parent knows that we
|
||||||
|
// crashed. The default action for all the signals which we catch is Core, so
|
||||||
|
// this is the end of us.
|
||||||
|
signal(sig, SIG_DFL);
|
||||||
|
tgkill(getpid(), sys_gettid(), sig);
|
||||||
|
|
||||||
|
// not reached.
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ThreadArgument {
|
||||||
|
pid_t pid; // the crashing process
|
||||||
|
ExceptionHandler* handler;
|
||||||
|
const void* context; // a CrashContext structure
|
||||||
|
size_t context_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is the entry function for the cloned process. We are in a compromised
|
||||||
|
// context here: see the top of the file.
|
||||||
|
// static
|
||||||
|
int ExceptionHandler::ThreadEntry(void *arg) {
|
||||||
|
const ThreadArgument *thread_arg = reinterpret_cast<ThreadArgument*>(arg);
|
||||||
|
return thread_arg->handler->DoDump(thread_arg->pid, thread_arg->context,
|
||||||
|
thread_arg->context_size) == false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function runs in a compromised context: see the top of the file.
|
||||||
|
// Runs on the crashing thread.
|
||||||
|
bool ExceptionHandler::HandleSignal(int sig, siginfo_t* info, void* uc) {
|
||||||
|
if (filter_ && !filter_(callback_context_))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Allow ourselves to be dumped.
|
||||||
|
sys_prctl(PR_SET_DUMPABLE, 1);
|
||||||
|
|
||||||
|
CrashContext context;
|
||||||
|
memcpy(&context.siginfo, info, sizeof(siginfo_t));
|
||||||
|
memcpy(&context.context, uc, sizeof(struct ucontext));
|
||||||
|
memcpy(&context.float_state, ((struct ucontext *)uc)->uc_mcontext.fpregs,
|
||||||
|
sizeof(context.float_state));
|
||||||
|
context.tid = sys_gettid();
|
||||||
|
|
||||||
|
if (crash_handler_ && crash_handler_(&context, sizeof(context),
|
||||||
|
callback_context_))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
static const unsigned kChildStackSize = 8000;
|
||||||
|
PageAllocator allocator;
|
||||||
|
uint8_t* stack = (uint8_t*) allocator.Alloc(kChildStackSize);
|
||||||
|
if (!stack)
|
||||||
|
return false;
|
||||||
|
// clone() needs the top-most address. (scrub just to be safe)
|
||||||
|
stack += kChildStackSize;
|
||||||
|
my_memset(stack - 16, 0, 16);
|
||||||
|
|
||||||
|
ThreadArgument thread_arg;
|
||||||
|
thread_arg.handler = this;
|
||||||
|
thread_arg.pid = getpid();
|
||||||
|
thread_arg.context = &context;
|
||||||
|
thread_arg.context_size = sizeof(context);
|
||||||
|
|
||||||
|
const pid_t child = sys_clone(
|
||||||
|
ThreadEntry, stack, CLONE_FILES | CLONE_FS | CLONE_UNTRACED,
|
||||||
|
&thread_arg, NULL, NULL, NULL);
|
||||||
|
int r, status;
|
||||||
|
do {
|
||||||
|
r = sys_waitpid(child, &status, __WALL);
|
||||||
|
} while (r == -1 && errno == EINTR);
|
||||||
|
|
||||||
|
if (r == -1) {
|
||||||
|
static const char msg[] = "ExceptionHandler::HandleSignal: waitpid failed:";
|
||||||
|
sys_write(2, msg, sizeof(msg) - 1);
|
||||||
|
sys_write(2, strerror(errno), strlen(strerror(errno)));
|
||||||
|
sys_write(2, "\n", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool success = r != -1 && WIFEXITED(status) && WEXITSTATUS(status) == 0;
|
||||||
|
|
||||||
|
if (callback_)
|
||||||
|
success = callback_(dump_path_c_, next_minidump_id_c_,
|
||||||
|
callback_context_, success);
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function runs in a compromised context: see the top of the file.
|
||||||
|
// Runs on the cloned process.
|
||||||
|
bool ExceptionHandler::DoDump(pid_t crashing_process, const void* context,
|
||||||
|
size_t context_size) {
|
||||||
|
return google_breakpad::WriteMinidump(
|
||||||
|
next_minidump_path_c_, crashing_process, context, context_size);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace google_breakpad
|
} // namespace google_breakpad
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) 2006, Google Inc.
|
// Copyright (c) 2009, Google Inc.
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
//
|
//
|
||||||
// Author: Li Liu
|
|
||||||
//
|
|
||||||
// Redistribution and use in source and binary forms, with or without
|
// Redistribution and use in source and binary forms, with or without
|
||||||
// modification, are permitted provided that the following conditions are
|
// modification, are permitted provided that the following conditions are
|
||||||
// met:
|
// met:
|
||||||
@ -29,26 +27,16 @@
|
|||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
#ifndef CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H__
|
#ifndef CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_
|
||||||
#define CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H__
|
#define CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_
|
||||||
|
|
||||||
#include <pthread.h>
|
|
||||||
|
|
||||||
#include <map>
|
|
||||||
#include <string>
|
|
||||||
#include <signal.h>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include "client/linux/handler/minidump_generator.h"
|
#include <signal.h>
|
||||||
|
|
||||||
// Context information when exception occured.
|
|
||||||
struct sigcontex;
|
|
||||||
|
|
||||||
namespace google_breakpad {
|
namespace google_breakpad {
|
||||||
|
|
||||||
using std::string;
|
|
||||||
|
|
||||||
//
|
|
||||||
// ExceptionHandler
|
// ExceptionHandler
|
||||||
//
|
//
|
||||||
// ExceptionHandler can write a minidump file when an exception occurs,
|
// ExceptionHandler can write a minidump file when an exception occurs,
|
||||||
@ -73,7 +61,6 @@ using std::string;
|
|||||||
//
|
//
|
||||||
// Caller should try to make the callbacks as crash-friendly as possible,
|
// Caller should try to make the callbacks as crash-friendly as possible,
|
||||||
// it should avoid use heap memory allocation as much as possible.
|
// it should avoid use heap memory allocation as much as possible.
|
||||||
//
|
|
||||||
class ExceptionHandler {
|
class ExceptionHandler {
|
||||||
public:
|
public:
|
||||||
// A callback function to run before Breakpad performs any substantial
|
// A callback function to run before Breakpad performs any substantial
|
||||||
@ -108,6 +95,15 @@ class ExceptionHandler {
|
|||||||
void *context,
|
void *context,
|
||||||
bool succeeded);
|
bool succeeded);
|
||||||
|
|
||||||
|
// In certain cases, a user may wish to handle the generation of the minidump
|
||||||
|
// themselves. In this case, they can install a handler callback which is
|
||||||
|
// called when a crash has occured. If this function returns true, no other
|
||||||
|
// processing of occurs and the process will shortly be crashed. If this
|
||||||
|
// returns false, the normal processing continues.
|
||||||
|
typedef bool (*HandlerCallback)(const void* crash_context,
|
||||||
|
size_t crash_context_size,
|
||||||
|
void* context);
|
||||||
|
|
||||||
// Creates a new ExceptionHandler instance to handle writing minidumps.
|
// Creates a new ExceptionHandler instance to handle writing minidumps.
|
||||||
// Before writing a minidump, the optional filter callback will be called.
|
// Before writing a minidump, the optional filter callback will be called.
|
||||||
// Its return value determines whether or not Breakpad should write a
|
// Its return value determines whether or not Breakpad should write a
|
||||||
@ -116,111 +112,87 @@ class ExceptionHandler {
|
|||||||
// If install_handler is true, then a minidump will be written whenever
|
// If install_handler is true, then a minidump will be written whenever
|
||||||
// an unhandled exception occurs. If it is false, minidumps will only
|
// an unhandled exception occurs. If it is false, minidumps will only
|
||||||
// be written when WriteMinidump is called.
|
// be written when WriteMinidump is called.
|
||||||
ExceptionHandler(const string &dump_path,
|
ExceptionHandler(const std::string &dump_path,
|
||||||
FilterCallback filter, MinidumpCallback callback,
|
FilterCallback filter, MinidumpCallback callback,
|
||||||
void *callback_context,
|
void *callback_context,
|
||||||
bool install_handler);
|
bool install_handler);
|
||||||
~ExceptionHandler();
|
~ExceptionHandler();
|
||||||
|
|
||||||
// Get and set the minidump path.
|
// Get and set the minidump path.
|
||||||
string dump_path() const { return dump_path_; }
|
std::string dump_path() const { return dump_path_; }
|
||||||
void set_dump_path(const string &dump_path) {
|
void set_dump_path(const std::string &dump_path) {
|
||||||
dump_path_ = dump_path;
|
dump_path_ = dump_path;
|
||||||
dump_path_c_ = dump_path_.c_str();
|
dump_path_c_ = dump_path_.c_str();
|
||||||
UpdateNextID();
|
UpdateNextID();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void set_crash_handler(HandlerCallback callback) {
|
||||||
|
crash_handler_ = callback;
|
||||||
|
}
|
||||||
|
|
||||||
// Writes a minidump immediately. This can be used to capture the
|
// Writes a minidump immediately. This can be used to capture the
|
||||||
// execution state independently of a crash. Returns true on success.
|
// execution state independently of a crash. Returns true on success.
|
||||||
bool WriteMinidump();
|
bool WriteMinidump();
|
||||||
|
|
||||||
// Convenience form of WriteMinidump which does not require an
|
// Convenience form of WriteMinidump which does not require an
|
||||||
// ExceptionHandler instance.
|
// ExceptionHandler instance.
|
||||||
static bool WriteMinidump(const string &dump_path,
|
static bool WriteMinidump(const std::string &dump_path,
|
||||||
MinidumpCallback callback,
|
MinidumpCallback callback,
|
||||||
void *callback_context);
|
void *callback_context);
|
||||||
|
|
||||||
|
// This structure is passed to minidump_writer.h:WriteMinidump via an opaque
|
||||||
|
// blob. It shouldn't be needed in any user code.
|
||||||
|
struct CrashContext {
|
||||||
|
siginfo_t siginfo;
|
||||||
|
pid_t tid; // the crashing thread.
|
||||||
|
struct ucontext context;
|
||||||
|
struct _libc_fpstate float_state;
|
||||||
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Setup crash handler.
|
bool InstallHandlers();
|
||||||
void SetupHandler();
|
void UninstallHandlers();
|
||||||
// Setup signal handler for a signal.
|
void PreresolveSymbols();
|
||||||
void SetupHandler(int signo);
|
|
||||||
// Teardown the handler for a signal.
|
|
||||||
void TeardownHandler(int signo);
|
|
||||||
// Teardown the handler for a signal.
|
|
||||||
void TeardownHandler(int signo, struct sigaction *old);
|
|
||||||
// Teardown all handlers.
|
|
||||||
void TeardownAllHandler();
|
|
||||||
|
|
||||||
// Signal handler.
|
|
||||||
static void HandleException(int signo);
|
|
||||||
|
|
||||||
// If called from a signal handler, sighandler_ebp is the ebp of
|
|
||||||
// that signal handler's frame, and sig_ctx is an out parameter
|
|
||||||
// that will be set to point at the sigcontext that was placed
|
|
||||||
// on the stack by the kernel. You can pass zero and NULL
|
|
||||||
// for the second and third parameters if you are not calling
|
|
||||||
// this from a signal handler.
|
|
||||||
bool InternalWriteMinidump(int signo, uintptr_t sighandler_ebp,
|
|
||||||
struct sigcontext **sig_ctx);
|
|
||||||
|
|
||||||
// Generates a new ID and stores it in next_minidump_id, and stores the
|
|
||||||
// path of the next minidump to be written in next_minidump_path_.
|
|
||||||
void UpdateNextID();
|
void UpdateNextID();
|
||||||
|
static void SignalHandler(int sig, siginfo_t* info, void* uc);
|
||||||
|
bool HandleSignal(int sig, siginfo_t* info, void* uc);
|
||||||
|
static int ThreadEntry(void* arg);
|
||||||
|
bool DoDump(pid_t crashing_process, const void* context,
|
||||||
|
size_t context_size);
|
||||||
|
|
||||||
private:
|
const FilterCallback filter_;
|
||||||
FilterCallback filter_;
|
const MinidumpCallback callback_;
|
||||||
MinidumpCallback callback_;
|
void* const callback_context_;
|
||||||
void *callback_context_;
|
|
||||||
|
|
||||||
// The directory in which a minidump will be written, set by the dump_path
|
std::string dump_path_;
|
||||||
// argument to the constructor, or set_dump_path.
|
std::string next_minidump_path_;
|
||||||
string dump_path_;
|
std::string next_minidump_id_;
|
||||||
|
|
||||||
// The basename of the next minidump to be written, without the extension
|
|
||||||
string next_minidump_id_;
|
|
||||||
|
|
||||||
// The full pathname of the next minidump to be written, including the file
|
|
||||||
// extension
|
|
||||||
string next_minidump_path_;
|
|
||||||
|
|
||||||
// Pointers to C-string representations of the above. These are set
|
// Pointers to C-string representations of the above. These are set
|
||||||
// when the above are set so we can avoid calling c_str during
|
// when the above are set so we can avoid calling c_str during
|
||||||
// an exception.
|
// an exception.
|
||||||
const char *dump_path_c_;
|
const char* dump_path_c_;
|
||||||
const char *next_minidump_id_c_;
|
const char* next_minidump_path_c_;
|
||||||
const char *next_minidump_path_c_;
|
const char* next_minidump_id_c_;
|
||||||
|
|
||||||
// True if the ExceptionHandler installed an unhandled exception filter
|
const bool handler_installed_;
|
||||||
// when created (with an install_handler parameter set to true).
|
void* signal_stack; // the handler stack.
|
||||||
bool installed_handler_;
|
HandlerCallback crash_handler_;
|
||||||
|
|
||||||
// The global exception handler stack. This is need becuase there may exist
|
// The global exception handler stack. This is need becuase there may exist
|
||||||
// multiple ExceptionHandler instances in a process. Each will have itself
|
// multiple ExceptionHandler instances in a process. Each will have itself
|
||||||
// registered in this stack.
|
// registered in this stack.
|
||||||
static std::vector<ExceptionHandler *> *handler_stack_;
|
static std::vector<ExceptionHandler*> *handler_stack_;
|
||||||
// The index of the handler that should handle the next exception.
|
// The index of the handler that should handle the next exception.
|
||||||
static int handler_stack_index_;
|
static unsigned handler_stack_index_;
|
||||||
static pthread_mutex_t handler_stack_mutex_;
|
static pthread_mutex_t handler_stack_mutex_;
|
||||||
|
|
||||||
// The minidump generator.
|
// A vector of the old signal handlers. The void* is a pointer to a newly
|
||||||
MinidumpGenerator minidump_generator_;
|
// allocated sigaction structure to avoid pulling in too many includes.
|
||||||
|
std::vector<std::pair<int, void *> > old_handlers_;
|
||||||
// disallow copy ctor and operator=
|
|
||||||
explicit ExceptionHandler(const ExceptionHandler &);
|
|
||||||
void operator=(const ExceptionHandler &);
|
|
||||||
|
|
||||||
// The sigactions structure we use for each signal
|
|
||||||
struct sigaction act_;
|
|
||||||
|
|
||||||
|
|
||||||
// Keep the previous handlers for the signal.
|
|
||||||
// We're wasting a bit of memory here since we only change
|
|
||||||
// the handler for some signals but i want to avoid allocating
|
|
||||||
// memory in the signal handler
|
|
||||||
struct sigaction old_actions_[NSIG];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace google_breakpad
|
} // namespace google_breakpad
|
||||||
|
|
||||||
#endif // CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H__
|
#endif // CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_
|
||||||
|
@ -1,124 +0,0 @@
|
|||||||
// Copyright (c) 2006, Google Inc.
|
|
||||||
// All rights reserved.
|
|
||||||
//
|
|
||||||
// Author: Li Liu
|
|
||||||
//
|
|
||||||
// Redistribution and use in source and binary forms, with or without
|
|
||||||
// modification, are permitted provided that the following conditions are
|
|
||||||
// met:
|
|
||||||
//
|
|
||||||
// * Redistributions of source code must retain the above copyright
|
|
||||||
// notice, this list of conditions and the following disclaimer.
|
|
||||||
// * Redistributions in binary form must reproduce the above
|
|
||||||
// copyright notice, this list of conditions and the following disclaimer
|
|
||||||
// in the documentation and/or other materials provided with the
|
|
||||||
// distribution.
|
|
||||||
// * Neither the name of Google Inc. nor the names of its
|
|
||||||
// contributors may be used to endorse or promote products derived from
|
|
||||||
// this software without specific prior written permission.
|
|
||||||
//
|
|
||||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
#include "client/linux/handler/exception_handler.h"
|
|
||||||
#include "client/linux/handler/linux_thread.h"
|
|
||||||
|
|
||||||
using namespace google_breakpad;
|
|
||||||
|
|
||||||
// Thread use this to see if it should stop working.
|
|
||||||
static bool should_exit = false;
|
|
||||||
|
|
||||||
static int foo2(int arg) {
|
|
||||||
// Stack variable, used for debugging stack dumps.
|
|
||||||
/*DDDebug*/printf("%s:%d\n", __FUNCTION__, __LINE__);
|
|
||||||
int c = 0xcccccccc;
|
|
||||||
fprintf(stderr, "Thread trying to crash: %x\n", getpid());
|
|
||||||
c = *reinterpret_cast<int *>(0x5);
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int foo(int arg) {
|
|
||||||
// Stack variable, used for debugging stack dumps.
|
|
||||||
int b = 0xbbbbbbbb;
|
|
||||||
b = foo2(b);
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void *thread_crash(void *) {
|
|
||||||
// Stack variable, used for debugging stack dumps.
|
|
||||||
int a = 0xaaaaaaaa;
|
|
||||||
sleep(1);
|
|
||||||
a = foo(a);
|
|
||||||
printf("%x\n", a);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void *thread_main(void *) {
|
|
||||||
while (!should_exit)
|
|
||||||
sleep(1);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void CreateCrashThread() {
|
|
||||||
pthread_t h;
|
|
||||||
pthread_create(&h, NULL, thread_crash, NULL);
|
|
||||||
pthread_detach(h);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create working threads.
|
|
||||||
static void CreateThread(int num) {
|
|
||||||
pthread_t h;
|
|
||||||
for (int i = 0; i < num; ++i) {
|
|
||||||
pthread_create(&h, NULL, thread_main, NULL);
|
|
||||||
pthread_detach(h);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Callback when minidump written.
|
|
||||||
static bool MinidumpCallback(const char *dump_path,
|
|
||||||
const char *minidump_id,
|
|
||||||
void *context,
|
|
||||||
bool succeeded) {
|
|
||||||
int index = reinterpret_cast<int>(context);
|
|
||||||
printf("%d %s: %s is dumped\n", index, __FUNCTION__, minidump_id);
|
|
||||||
if (index == 0) {
|
|
||||||
should_exit = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Don't process it.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
|
||||||
int handler_index = 0;
|
|
||||||
ExceptionHandler handler_ignore(".", NULL, MinidumpCallback,
|
|
||||||
(void*)handler_index, true);
|
|
||||||
++handler_index;
|
|
||||||
ExceptionHandler handler_process(".", NULL, MinidumpCallback,
|
|
||||||
(void*)handler_index, true);
|
|
||||||
CreateCrashThread();
|
|
||||||
CreateThread(10);
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
sleep(1);
|
|
||||||
should_exit = true;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
256
src/client/linux/handler/exception_handler_unittest.cc
Normal file
256
src/client/linux/handler/exception_handler_unittest.cc
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
// Copyright (c) 2009, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <sys/poll.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/uio.h>
|
||||||
|
|
||||||
|
#include "client/linux/handler//exception_handler.h"
|
||||||
|
#include "client/linux/minidump_writer/minidump_writer.h"
|
||||||
|
#include "common/linux/linux_libc_support.h"
|
||||||
|
#include "common/linux/linux_syscall_support.h"
|
||||||
|
#include "breakpad_googletest_includes.h"
|
||||||
|
|
||||||
|
// This provides a wrapper around system calls which may be
|
||||||
|
// interrupted by a signal and return EINTR. See man 7 signal.
|
||||||
|
#define HANDLE_EINTR(x) ({ \
|
||||||
|
typeof(x) __eintr_result__; \
|
||||||
|
do { \
|
||||||
|
__eintr_result__ = x; \
|
||||||
|
} while (__eintr_result__ == -1 && errno == EINTR); \
|
||||||
|
__eintr_result__;\
|
||||||
|
})
|
||||||
|
|
||||||
|
using namespace google_breakpad;
|
||||||
|
|
||||||
|
static void sigchld_handler(int signo) { }
|
||||||
|
|
||||||
|
class ExceptionHandlerTest : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
void SetUp() {
|
||||||
|
// We need to be able to wait for children, so SIGCHLD cannot be SIG_IGN.
|
||||||
|
struct sigaction sa;
|
||||||
|
memset(&sa, 0, sizeof(sa));
|
||||||
|
sa.sa_handler = sigchld_handler;
|
||||||
|
ASSERT_NE(sigaction(SIGCHLD, &sa, &old_action), -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TearDown() {
|
||||||
|
sigaction(SIGCHLD, &old_action, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sigaction old_action;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST(ExceptionHandlerTest, Simple) {
|
||||||
|
ExceptionHandler handler("/tmp", NULL, NULL, NULL, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool DoneCallback(const char* dump_path,
|
||||||
|
const char* minidump_id,
|
||||||
|
void* context,
|
||||||
|
bool succeeded) {
|
||||||
|
if (!succeeded)
|
||||||
|
return succeeded;
|
||||||
|
|
||||||
|
int fd = (int) context;
|
||||||
|
uint32_t len = my_strlen(minidump_id);
|
||||||
|
HANDLE_EINTR(sys_write(fd, &len, sizeof(len)));
|
||||||
|
HANDLE_EINTR(sys_write(fd, minidump_id, len));
|
||||||
|
sys_close(fd);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ExceptionHandlerTest, ChildCrash) {
|
||||||
|
int fds[2];
|
||||||
|
ASSERT_NE(pipe(fds), -1);
|
||||||
|
|
||||||
|
const pid_t child = fork();
|
||||||
|
if (child == 0) {
|
||||||
|
close(fds[0]);
|
||||||
|
ExceptionHandler handler("/tmp", NULL, DoneCallback, (void*) fds[1],
|
||||||
|
true);
|
||||||
|
*reinterpret_cast<int*>(NULL) = 0;
|
||||||
|
}
|
||||||
|
close(fds[1]);
|
||||||
|
|
||||||
|
int status;
|
||||||
|
ASSERT_NE(HANDLE_EINTR(waitpid(child, &status, 0)), -1);
|
||||||
|
ASSERT_TRUE(WIFSIGNALED(status));
|
||||||
|
ASSERT_EQ(WTERMSIG(status), SIGSEGV);
|
||||||
|
|
||||||
|
struct pollfd pfd;
|
||||||
|
memset(&pfd, 0, sizeof(pfd));
|
||||||
|
pfd.fd = fds[0];
|
||||||
|
pfd.events = POLLIN | POLLERR;
|
||||||
|
|
||||||
|
const int r = HANDLE_EINTR(poll(&pfd, 1, 0));
|
||||||
|
ASSERT_EQ(r, 1);
|
||||||
|
ASSERT_TRUE(pfd.revents & POLLIN);
|
||||||
|
|
||||||
|
uint32_t len;
|
||||||
|
ASSERT_EQ(read(fds[0], &len, sizeof(len)), sizeof(len));
|
||||||
|
ASSERT_LT(len, 2048);
|
||||||
|
char* filename = reinterpret_cast<char*>(malloc(len + 1));
|
||||||
|
ASSERT_EQ(read(fds[0], filename, len), len);
|
||||||
|
filename[len] = 0;
|
||||||
|
close(fds[0]);
|
||||||
|
|
||||||
|
const std::string minidump_filename = std::string("/tmp/") + filename +
|
||||||
|
".dmp";
|
||||||
|
|
||||||
|
struct stat st;
|
||||||
|
ASSERT_EQ(stat(minidump_filename.c_str(), &st), 0);
|
||||||
|
ASSERT_GT(st.st_size, 0u);
|
||||||
|
unlink(minidump_filename.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
static const unsigned kControlMsgSize =
|
||||||
|
CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(struct ucred));
|
||||||
|
|
||||||
|
static bool
|
||||||
|
CrashHandler(const void* crash_context, size_t crash_context_size,
|
||||||
|
void* context) {
|
||||||
|
const int fd = (int) context;
|
||||||
|
int fds[2];
|
||||||
|
pipe(fds);
|
||||||
|
|
||||||
|
struct kernel_msghdr msg = {0};
|
||||||
|
struct kernel_iovec iov;
|
||||||
|
iov.iov_base = const_cast<void*>(crash_context);
|
||||||
|
iov.iov_len = crash_context_size;
|
||||||
|
|
||||||
|
msg.msg_iov = &iov;
|
||||||
|
msg.msg_iovlen = 1;
|
||||||
|
char cmsg[kControlMsgSize];
|
||||||
|
memset(cmsg, 0, kControlMsgSize);
|
||||||
|
msg.msg_control = cmsg;
|
||||||
|
msg.msg_controllen = sizeof(cmsg);
|
||||||
|
|
||||||
|
struct cmsghdr *hdr = CMSG_FIRSTHDR(&msg);
|
||||||
|
hdr->cmsg_level = SOL_SOCKET;
|
||||||
|
hdr->cmsg_type = SCM_RIGHTS;
|
||||||
|
hdr->cmsg_len = CMSG_LEN(sizeof(int));
|
||||||
|
*((int*) CMSG_DATA(hdr)) = fds[1];
|
||||||
|
hdr = CMSG_NXTHDR((struct msghdr*) &msg, hdr);
|
||||||
|
hdr->cmsg_level = SOL_SOCKET;
|
||||||
|
hdr->cmsg_type = SCM_CREDENTIALS;
|
||||||
|
hdr->cmsg_len = CMSG_LEN(sizeof(struct ucred));
|
||||||
|
struct ucred *cred = reinterpret_cast<struct ucred*>(CMSG_DATA(hdr));
|
||||||
|
cred->uid = getuid();
|
||||||
|
cred->gid = getgid();
|
||||||
|
cred->pid = getpid();
|
||||||
|
|
||||||
|
HANDLE_EINTR(sys_sendmsg(fd, &msg, 0));
|
||||||
|
sys_close(fds[1]);
|
||||||
|
|
||||||
|
char b;
|
||||||
|
HANDLE_EINTR(sys_read(fds[0], &b, 1));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ExceptionHandlerTest, ExternalDumper) {
|
||||||
|
int fds[2];
|
||||||
|
ASSERT_NE(socketpair(AF_UNIX, SOCK_DGRAM, 0, fds), -1);
|
||||||
|
static const int on = 1;
|
||||||
|
setsockopt(fds[0], SOL_SOCKET, SO_PASSCRED, &on, sizeof(on));
|
||||||
|
setsockopt(fds[1], SOL_SOCKET, SO_PASSCRED, &on, sizeof(on));
|
||||||
|
|
||||||
|
const pid_t child = fork();
|
||||||
|
if (child == 0) {
|
||||||
|
close(fds[0]);
|
||||||
|
ExceptionHandler handler("/tmp", NULL, NULL, (void*) fds[1], true);
|
||||||
|
handler.set_crash_handler(CrashHandler);
|
||||||
|
*reinterpret_cast<int*>(NULL) = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
close(fds[1]);
|
||||||
|
struct msghdr msg = {0};
|
||||||
|
struct iovec iov;
|
||||||
|
static const unsigned kCrashContextSize =
|
||||||
|
sizeof(ExceptionHandler::CrashContext);
|
||||||
|
char context[kCrashContextSize];
|
||||||
|
char control[kControlMsgSize];
|
||||||
|
iov.iov_base = context;
|
||||||
|
iov.iov_len = kCrashContextSize;
|
||||||
|
msg.msg_iov = &iov;
|
||||||
|
msg.msg_iovlen = 1;
|
||||||
|
msg.msg_control = control;
|
||||||
|
msg.msg_controllen = kControlMsgSize;
|
||||||
|
|
||||||
|
const ssize_t n = HANDLE_EINTR(recvmsg(fds[0], &msg, 0));
|
||||||
|
ASSERT_EQ(n, kCrashContextSize);
|
||||||
|
ASSERT_EQ(msg.msg_controllen, kControlMsgSize);
|
||||||
|
ASSERT_EQ(msg.msg_flags, 0);
|
||||||
|
|
||||||
|
pid_t crashing_pid = -1;
|
||||||
|
int signal_fd = -1;
|
||||||
|
for (struct cmsghdr *hdr = CMSG_FIRSTHDR(&msg); hdr;
|
||||||
|
hdr = CMSG_NXTHDR(&msg, hdr)) {
|
||||||
|
if (hdr->cmsg_level != SOL_SOCKET)
|
||||||
|
continue;
|
||||||
|
if (hdr->cmsg_type == SCM_RIGHTS) {
|
||||||
|
const unsigned len = hdr->cmsg_len -
|
||||||
|
(((uint8_t*)CMSG_DATA(hdr)) - (uint8_t*)hdr);
|
||||||
|
ASSERT_EQ(len, sizeof(int));
|
||||||
|
signal_fd = *((int *) CMSG_DATA(hdr));
|
||||||
|
} else if (hdr->cmsg_type == SCM_CREDENTIALS) {
|
||||||
|
const struct ucred *cred =
|
||||||
|
reinterpret_cast<struct ucred*>(CMSG_DATA(hdr));
|
||||||
|
crashing_pid = cred->pid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_NE(crashing_pid, -1);
|
||||||
|
ASSERT_NE(signal_fd, -1);
|
||||||
|
|
||||||
|
char templ[] = "/tmp/exception-handler-unittest-XXXXXX";
|
||||||
|
mktemp(templ);
|
||||||
|
ASSERT_TRUE(WriteMinidump(templ, crashing_pid, context,
|
||||||
|
kCrashContextSize));
|
||||||
|
static const char b = 0;
|
||||||
|
HANDLE_EINTR(write(signal_fd, &b, 1));
|
||||||
|
|
||||||
|
int status;
|
||||||
|
ASSERT_NE(HANDLE_EINTR(waitpid(child, &status, 0)), -1);
|
||||||
|
ASSERT_TRUE(WIFSIGNALED(status));
|
||||||
|
ASSERT_EQ(WTERMSIG(status), SIGSEGV);
|
||||||
|
|
||||||
|
struct stat st;
|
||||||
|
ASSERT_EQ(stat(templ, &st), 0);
|
||||||
|
ASSERT_GT(st.st_size, 0u);
|
||||||
|
unlink(templ);
|
||||||
|
}
|
@ -1,411 +0,0 @@
|
|||||||
// Copyright (c) 2006, Google Inc.
|
|
||||||
// All rights reserved.
|
|
||||||
//
|
|
||||||
// Author: Li Liu
|
|
||||||
//
|
|
||||||
// Redistribution and use in source and binary forms, with or without
|
|
||||||
// modification, are permitted provided that the following conditions are
|
|
||||||
// met:
|
|
||||||
//
|
|
||||||
// * Redistributions of source code must retain the above copyright
|
|
||||||
// notice, this list of conditions and the following disclaimer.
|
|
||||||
// * Redistributions in binary form must reproduce the above
|
|
||||||
// copyright notice, this list of conditions and the following disclaimer
|
|
||||||
// in the documentation and/or other materials provided with the
|
|
||||||
// distribution.
|
|
||||||
// * Neither the name of Google Inc. nor the names of its
|
|
||||||
// contributors may be used to endorse or promote products derived from
|
|
||||||
// this software without specific prior written permission.
|
|
||||||
//
|
|
||||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
//
|
|
||||||
#include <errno.h>
|
|
||||||
#include <dirent.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <sys/ptrace.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cassert>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
#include "client/linux/handler/linux_thread.h"
|
|
||||||
|
|
||||||
using namespace google_breakpad;
|
|
||||||
|
|
||||||
// This unamed namespace contains helper function.
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
// Context information for the callbacks when validating address by listing
|
|
||||||
// modules.
|
|
||||||
struct AddressValidatingContext {
|
|
||||||
uintptr_t address;
|
|
||||||
bool is_mapped;
|
|
||||||
|
|
||||||
AddressValidatingContext() : address(0UL), is_mapped(false) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Convert from string to int.
|
|
||||||
bool LocalAtoi(char *s, int *r) {
|
|
||||||
assert(s != NULL);
|
|
||||||
assert(r != NULL);
|
|
||||||
char *endptr = NULL;
|
|
||||||
int ret = strtol(s, &endptr, 10);
|
|
||||||
if (endptr == s)
|
|
||||||
return false;
|
|
||||||
*r = ret;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill the proc path of a thread given its id.
|
|
||||||
void FillProcPath(int pid, char *path, int path_size) {
|
|
||||||
char pid_str[32];
|
|
||||||
snprintf(pid_str, sizeof(pid_str), "%d", pid);
|
|
||||||
snprintf(path, path_size, "/proc/%s/", pid_str);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read thread info from /proc/$pid/status.
|
|
||||||
bool ReadThreadInfo(int pid, ThreadInfo *info) {
|
|
||||||
assert(info != NULL);
|
|
||||||
char status_path[80];
|
|
||||||
// Max size we want to read from status file.
|
|
||||||
static const int kStatusMaxSize = 1024;
|
|
||||||
char status_content[kStatusMaxSize];
|
|
||||||
|
|
||||||
FillProcPath(pid, status_path, sizeof(status_path));
|
|
||||||
strcat(status_path, "status");
|
|
||||||
int fd = open(status_path, O_RDONLY, 0);
|
|
||||||
if (fd < 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
int num_read = read(fd, status_content, kStatusMaxSize - 1);
|
|
||||||
if (num_read < 0) {
|
|
||||||
close(fd);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
close(fd);
|
|
||||||
status_content[num_read] = '\0';
|
|
||||||
|
|
||||||
char *tgid_start = strstr(status_content, "Tgid:");
|
|
||||||
if (tgid_start)
|
|
||||||
sscanf(tgid_start, "Tgid:\t%d\n", &(info->tgid));
|
|
||||||
else
|
|
||||||
// tgid not supported by kernel??
|
|
||||||
info->tgid = 0;
|
|
||||||
|
|
||||||
tgid_start = strstr(status_content, "Pid:");
|
|
||||||
if (tgid_start) {
|
|
||||||
sscanf(tgid_start, "Pid:\t%d\n" "PPid:\t%d\n", &(info->pid),
|
|
||||||
&(info->ppid));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Callback invoked for each mapped module.
|
|
||||||
// It use the module's adderss range to validate the address.
|
|
||||||
bool IsAddressInModuleCallback(const ModuleInfo &module_info,
|
|
||||||
void *context) {
|
|
||||||
AddressValidatingContext *addr =
|
|
||||||
reinterpret_cast<AddressValidatingContext *>(context);
|
|
||||||
addr->is_mapped = ((addr->address >= module_info.start_addr) &&
|
|
||||||
(addr->address <= module_info.start_addr +
|
|
||||||
module_info.size));
|
|
||||||
return !addr->is_mapped;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if defined(__i386__) && !defined(NO_FRAME_POINTER)
|
|
||||||
void *GetNextFrame(void **last_ebp) {
|
|
||||||
void *sp = *last_ebp;
|
|
||||||
if ((unsigned long)sp == (unsigned long)last_ebp)
|
|
||||||
return NULL;
|
|
||||||
if ((unsigned long)sp & (sizeof(void *) - 1))
|
|
||||||
return NULL;
|
|
||||||
if ((unsigned long)sp - (unsigned long)last_ebp > 100000)
|
|
||||||
return NULL;
|
|
||||||
return sp;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
void *GetNextFrame(void **last_ebp) {
|
|
||||||
return reinterpret_cast<void*>(last_ebp);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Suspend a thread by attaching to it.
|
|
||||||
bool SuspendThread(int pid, void *context) {
|
|
||||||
// This may fail if the thread has just died or debugged.
|
|
||||||
errno = 0;
|
|
||||||
if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) != 0 &&
|
|
||||||
errno != 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
while (waitpid(pid, NULL, __WALL) < 0) {
|
|
||||||
if (errno != EINTR) {
|
|
||||||
ptrace(PTRACE_DETACH, pid, NULL, NULL);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resume a thread by detaching from it.
|
|
||||||
bool ResumeThread(int pid, void *context) {
|
|
||||||
return ptrace(PTRACE_DETACH, pid, NULL, NULL) >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Callback to get the thread information.
|
|
||||||
// Will be called for each thread found.
|
|
||||||
bool ThreadInfoCallback(int pid, void *context) {
|
|
||||||
CallbackParam<ThreadCallback> *thread_callback =
|
|
||||||
reinterpret_cast<CallbackParam<ThreadCallback> *>(context);
|
|
||||||
ThreadInfo thread_info;
|
|
||||||
if (ReadThreadInfo(pid, &thread_info) && thread_callback) {
|
|
||||||
// Invoke callback from caller.
|
|
||||||
return (thread_callback->call_back)(thread_info, thread_callback->context);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace google_breakpad {
|
|
||||||
|
|
||||||
LinuxThread::LinuxThread(int pid) : pid_(pid) , threads_suspened_(false) {
|
|
||||||
}
|
|
||||||
|
|
||||||
LinuxThread::~LinuxThread() {
|
|
||||||
if (threads_suspened_)
|
|
||||||
ResumeAllThreads();
|
|
||||||
}
|
|
||||||
|
|
||||||
int LinuxThread::SuspendAllThreads() {
|
|
||||||
CallbackParam<PidCallback> callback_param(SuspendThread, NULL);
|
|
||||||
int thread_count = 0;
|
|
||||||
if ((thread_count = IterateProcSelfTask(pid_, &callback_param)) > 0)
|
|
||||||
threads_suspened_ = true;
|
|
||||||
return thread_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
void LinuxThread::ResumeAllThreads() const {
|
|
||||||
CallbackParam<PidCallback> callback_param(ResumeThread, NULL);
|
|
||||||
IterateProcSelfTask(pid_, &callback_param);
|
|
||||||
}
|
|
||||||
|
|
||||||
int LinuxThread::GetThreadCount() const {
|
|
||||||
return IterateProcSelfTask(pid_, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
int LinuxThread::ListThreads(
|
|
||||||
CallbackParam<ThreadCallback> *thread_callback_param) const {
|
|
||||||
CallbackParam<PidCallback> callback_param(ThreadInfoCallback,
|
|
||||||
thread_callback_param);
|
|
||||||
return IterateProcSelfTask(pid_, &callback_param);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LinuxThread::GetRegisters(int pid, user_regs_struct *regs) const {
|
|
||||||
assert(regs);
|
|
||||||
return (regs != NULL &&
|
|
||||||
(ptrace(PTRACE_GETREGS, pid, NULL, regs) == 0) &&
|
|
||||||
errno == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the floating-point registers of a thread.
|
|
||||||
// The caller must get the thread pid by ListThreads.
|
|
||||||
bool LinuxThread::GetFPRegisters(int pid, user_fpregs_struct *regs) const {
|
|
||||||
assert(regs);
|
|
||||||
return (regs != NULL &&
|
|
||||||
(ptrace(PTRACE_GETREGS, pid, NULL, regs) ==0) &&
|
|
||||||
errno == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LinuxThread::GetFPXRegisters(int pid, user_fpxregs_struct *regs) const {
|
|
||||||
assert(regs);
|
|
||||||
return (regs != NULL &&
|
|
||||||
(ptrace(PTRACE_GETFPREGS, pid, NULL, regs) != 0) &&
|
|
||||||
errno == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LinuxThread::GetDebugRegisters(int pid, DebugRegs *regs) const {
|
|
||||||
assert(regs);
|
|
||||||
|
|
||||||
#define GET_DR(name, num)\
|
|
||||||
name->dr##num = ptrace(PTRACE_PEEKUSER, pid,\
|
|
||||||
offsetof(struct user, u_debugreg[num]), NULL)
|
|
||||||
GET_DR(regs, 0);
|
|
||||||
GET_DR(regs, 1);
|
|
||||||
GET_DR(regs, 2);
|
|
||||||
GET_DR(regs, 3);
|
|
||||||
GET_DR(regs, 4);
|
|
||||||
GET_DR(regs, 5);
|
|
||||||
GET_DR(regs, 6);
|
|
||||||
GET_DR(regs, 7);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int LinuxThread::GetThreadStackDump(uintptr_t current_ebp,
|
|
||||||
uintptr_t current_esp,
|
|
||||||
void *buf,
|
|
||||||
int buf_size) const {
|
|
||||||
assert(buf);
|
|
||||||
assert(buf_size > 0);
|
|
||||||
|
|
||||||
uintptr_t stack_bottom = GetThreadStackBottom(current_ebp);
|
|
||||||
int size = stack_bottom - current_esp;
|
|
||||||
size = buf_size > size ? size : buf_size;
|
|
||||||
if (size > 0)
|
|
||||||
memcpy(buf, reinterpret_cast<void*>(current_esp), size);
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the stack bottom of a thread by stack walking. It works
|
|
||||||
// unless the stack has been corrupted or the frame pointer has been omited.
|
|
||||||
// This is just a temporary solution before we get better ideas about how
|
|
||||||
// this can be done.
|
|
||||||
//
|
|
||||||
// We will check each frame address by checking into module maps.
|
|
||||||
// TODO(liuli): Improve it.
|
|
||||||
uintptr_t LinuxThread::GetThreadStackBottom(uintptr_t current_ebp) const {
|
|
||||||
void **sp = reinterpret_cast<void **>(current_ebp);
|
|
||||||
void **previous_sp = sp;
|
|
||||||
while (sp && IsAddressMapped((uintptr_t)sp)) {
|
|
||||||
previous_sp = sp;
|
|
||||||
sp = reinterpret_cast<void **>(GetNextFrame(sp));
|
|
||||||
}
|
|
||||||
return (uintptr_t)previous_sp;
|
|
||||||
}
|
|
||||||
|
|
||||||
int LinuxThread::GetModuleCount() const {
|
|
||||||
return ListModules(NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
int LinuxThread::ListModules(
|
|
||||||
CallbackParam<ModuleCallback> *callback_param) const {
|
|
||||||
char line[512];
|
|
||||||
const char *maps_path = "/proc/self/maps";
|
|
||||||
|
|
||||||
int module_count = 0;
|
|
||||||
FILE *fp = fopen(maps_path, "r");
|
|
||||||
if (fp == NULL)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
uintptr_t start_addr;
|
|
||||||
uintptr_t end_addr;
|
|
||||||
while (fgets(line, sizeof(line), fp) != NULL) {
|
|
||||||
if (sscanf(line, "%x-%x", &start_addr, &end_addr) == 2) {
|
|
||||||
ModuleInfo module;
|
|
||||||
memset(&module, 0, sizeof(module));
|
|
||||||
module.start_addr = start_addr;
|
|
||||||
module.size = end_addr - start_addr;
|
|
||||||
char *name = NULL;
|
|
||||||
assert(module.size > 0);
|
|
||||||
// Only copy name if the name is a valid path name.
|
|
||||||
if ((name = strchr(line, '/')) != NULL) {
|
|
||||||
// Get rid of the last '\n' in line
|
|
||||||
char *last_return = strchr(line, '\n');
|
|
||||||
if (last_return != NULL)
|
|
||||||
*last_return = '\0';
|
|
||||||
// Keep a space for the ending 0.
|
|
||||||
strncpy(module.name, name, sizeof(module.name) - 1);
|
|
||||||
++module_count;
|
|
||||||
}
|
|
||||||
if (callback_param &&
|
|
||||||
!(callback_param->call_back(module, callback_param->context)))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fclose(fp);
|
|
||||||
return module_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse /proc/$pid/tasks to list all the threads of the process identified by
|
|
||||||
// pid.
|
|
||||||
int LinuxThread::IterateProcSelfTask(int pid,
|
|
||||||
CallbackParam<PidCallback> *callback_param) const {
|
|
||||||
char task_path[80];
|
|
||||||
FillProcPath(pid, task_path, sizeof(task_path));
|
|
||||||
strcat(task_path, "task");
|
|
||||||
|
|
||||||
DIR *dir = opendir(task_path);
|
|
||||||
if (dir == NULL)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
int pid_number = 0;
|
|
||||||
// Record the last pid we've found. This is used for duplicated thread
|
|
||||||
// removal. Duplicated thread information can be found in /proc/$pid/tasks.
|
|
||||||
int last_pid = -1;
|
|
||||||
struct dirent *entry = NULL;
|
|
||||||
while ((entry = readdir(dir)) != NULL) {
|
|
||||||
if (strcmp(entry->d_name, ".") &&
|
|
||||||
strcmp(entry->d_name, "..")) {
|
|
||||||
int tpid = 0;
|
|
||||||
if (LocalAtoi(entry->d_name, &tpid) &&
|
|
||||||
last_pid != tpid) {
|
|
||||||
last_pid = tpid;
|
|
||||||
++pid_number;
|
|
||||||
// Invoke the callback.
|
|
||||||
if (callback_param &&
|
|
||||||
!(callback_param->call_back)(tpid, callback_param->context))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
closedir(dir);
|
|
||||||
return pid_number;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the address is a valid virtual address.
|
|
||||||
// If the address is in any of the mapped modules, we take it as valid.
|
|
||||||
// Otherwise it is invalid.
|
|
||||||
bool LinuxThread::IsAddressMapped(uintptr_t address) const {
|
|
||||||
AddressValidatingContext addr;
|
|
||||||
addr.address = address;
|
|
||||||
CallbackParam<ModuleCallback> callback_param(IsAddressInModuleCallback,
|
|
||||||
&addr);
|
|
||||||
ListModules(&callback_param);
|
|
||||||
return addr.is_mapped;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LinuxThread::FindSigContext(uintptr_t sighandler_ebp,
|
|
||||||
struct sigcontext **sig_ctx) {
|
|
||||||
uintptr_t previous_ebp;
|
|
||||||
const int MAX_STACK_DEPTH = 10;
|
|
||||||
int depth_counter = 0;
|
|
||||||
|
|
||||||
do {
|
|
||||||
// We're looking for a |struct sigcontext| as the second parameter
|
|
||||||
// to a signal handler function call. Luckily, the sigcontext
|
|
||||||
// has an ebp member which should match the ebp pointed to
|
|
||||||
// by the ebp of the signal handler frame.
|
|
||||||
previous_ebp = reinterpret_cast<uintptr_t>(GetNextFrame(
|
|
||||||
reinterpret_cast<void**>(sighandler_ebp)));
|
|
||||||
// The stack looks like this:
|
|
||||||
// | previous ebp | previous eip | first param | second param |,
|
|
||||||
// so we need to offset by 3 to get to the second parameter.
|
|
||||||
*sig_ctx = reinterpret_cast<struct sigcontext*>(sighandler_ebp +
|
|
||||||
3 * sizeof(uintptr_t));
|
|
||||||
sighandler_ebp = previous_ebp;
|
|
||||||
depth_counter++;
|
|
||||||
} while(previous_ebp != (*sig_ctx)->ebp && sighandler_ebp != 0 &&
|
|
||||||
IsAddressMapped(sighandler_ebp) && depth_counter < MAX_STACK_DEPTH);
|
|
||||||
|
|
||||||
return previous_ebp == (*sig_ctx)->ebp && previous_ebp != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace google_breakpad
|
|
@ -1,204 +0,0 @@
|
|||||||
// Copyright (c) 2006, Google Inc.
|
|
||||||
// All rights reserved.
|
|
||||||
//
|
|
||||||
// Author: Li Liu
|
|
||||||
//
|
|
||||||
// Redistribution and use in source and binary forms, with or without
|
|
||||||
// modification, are permitted provided that the following conditions are
|
|
||||||
// met:
|
|
||||||
//
|
|
||||||
// * Redistributions of source code must retain the above copyright
|
|
||||||
// notice, this list of conditions and the following disclaimer.
|
|
||||||
// * Redistributions in binary form must reproduce the above
|
|
||||||
// copyright notice, this list of conditions and the following disclaimer
|
|
||||||
// in the documentation and/or other materials provided with the
|
|
||||||
// distribution.
|
|
||||||
// * Neither the name of Google Inc. nor the names of its
|
|
||||||
// contributors may be used to endorse or promote products derived from
|
|
||||||
// this software without specific prior written permission.
|
|
||||||
//
|
|
||||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
//
|
|
||||||
#ifndef CLIENT_LINUX_HANDLER_LINUX_THREAD_H__
|
|
||||||
#define CLIENT_LINUX_HANDLER_LINUX_THREAD_H__
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <sys/user.h>
|
|
||||||
|
|
||||||
namespace google_breakpad {
|
|
||||||
|
|
||||||
// Max module path name length.
|
|
||||||
#define kMaxModuleNameLength 256
|
|
||||||
|
|
||||||
// Holding information about a thread in the process.
|
|
||||||
struct ThreadInfo {
|
|
||||||
// Id of the thread group.
|
|
||||||
int tgid;
|
|
||||||
// Id of the thread.
|
|
||||||
int pid;
|
|
||||||
// Id of the parent process.
|
|
||||||
int ppid;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Holding infomaton about a module in the process.
|
|
||||||
struct ModuleInfo {
|
|
||||||
char name[kMaxModuleNameLength];
|
|
||||||
uintptr_t start_addr;
|
|
||||||
int size;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Holding debug registers.
|
|
||||||
struct DebugRegs {
|
|
||||||
int dr0;
|
|
||||||
int dr1;
|
|
||||||
int dr2;
|
|
||||||
int dr3;
|
|
||||||
int dr4;
|
|
||||||
int dr5;
|
|
||||||
int dr6;
|
|
||||||
int dr7;
|
|
||||||
};
|
|
||||||
|
|
||||||
// A callback to run when got a thread in the process.
|
|
||||||
// Return true will go on to the next thread while return false will stop the
|
|
||||||
// iteration.
|
|
||||||
typedef bool (*ThreadCallback)(const ThreadInfo &thread_info, void *context);
|
|
||||||
|
|
||||||
// A callback to run when a new module is found in the process.
|
|
||||||
// Return true will go on to the next module while return false will stop the
|
|
||||||
// iteration.
|
|
||||||
typedef bool (*ModuleCallback)(const ModuleInfo &module_info, void *context);
|
|
||||||
|
|
||||||
// Holding the callback information.
|
|
||||||
template<class CallbackFunc>
|
|
||||||
struct CallbackParam {
|
|
||||||
// Callback function address.
|
|
||||||
CallbackFunc call_back;
|
|
||||||
// Callback context;
|
|
||||||
void *context;
|
|
||||||
|
|
||||||
CallbackParam() : call_back(NULL), context(NULL) {
|
|
||||||
}
|
|
||||||
|
|
||||||
CallbackParam(CallbackFunc func, void *func_context) :
|
|
||||||
call_back(func), context(func_context) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
//
|
|
||||||
// LinuxThread
|
|
||||||
//
|
|
||||||
// Provides handy support for operation on linux threads.
|
|
||||||
// It uses ptrace to get thread registers. Since ptrace only works in a
|
|
||||||
// different process other than the one being ptraced, user of this class
|
|
||||||
// should create another process before using the class.
|
|
||||||
//
|
|
||||||
// The process should be created in the following way:
|
|
||||||
// int cloned_pid = clone(ProcessEntryFunction, stack_address,
|
|
||||||
// CLONE_VM | CLONE_FILES | CLONE_FS | CLONE_UNTRACED,
|
|
||||||
// (void*)&arguments);
|
|
||||||
// waitpid(cloned_pid, NULL, __WALL);
|
|
||||||
//
|
|
||||||
// If CLONE_VM is not used, GetThreadStackBottom, GetThreadStackDump
|
|
||||||
// will not work since it just use memcpy to get the stack dump.
|
|
||||||
//
|
|
||||||
class LinuxThread {
|
|
||||||
public:
|
|
||||||
// Create a LinuxThread instance to list all the threads in a process.
|
|
||||||
explicit LinuxThread(int pid);
|
|
||||||
~LinuxThread();
|
|
||||||
|
|
||||||
// Stop all the threads in the process.
|
|
||||||
// Return the number of stopped threads in the process.
|
|
||||||
// Return -1 means failed to stop threads.
|
|
||||||
int SuspendAllThreads();
|
|
||||||
|
|
||||||
// Resume all the suspended threads.
|
|
||||||
void ResumeAllThreads() const;
|
|
||||||
|
|
||||||
// Get the count of threads in the process.
|
|
||||||
// Return -1 means error.
|
|
||||||
int GetThreadCount() const;
|
|
||||||
|
|
||||||
// List the threads of process.
|
|
||||||
// Whenever there is a thread found, the callback will be invoked to process
|
|
||||||
// the information.
|
|
||||||
// Return number of threads listed.
|
|
||||||
int ListThreads(CallbackParam<ThreadCallback> *thread_callback_param) const;
|
|
||||||
|
|
||||||
// Get the general purpose registers of a thread.
|
|
||||||
// The caller must get the thread pid by ListThreads.
|
|
||||||
bool GetRegisters(int pid, user_regs_struct *regs) const;
|
|
||||||
|
|
||||||
// Get the floating-point registers of a thread.
|
|
||||||
// The caller must get the thread pid by ListThreads.
|
|
||||||
bool GetFPRegisters(int pid, user_fpregs_struct *regs) const;
|
|
||||||
|
|
||||||
// Get all the extended floating-point registers. May not work on all
|
|
||||||
// machines.
|
|
||||||
// The caller must get the thread pid by ListThreads.
|
|
||||||
bool GetFPXRegisters(int pid, user_fpxregs_struct *regs) const;
|
|
||||||
|
|
||||||
// Get the debug registers.
|
|
||||||
// The caller must get the thread pid by ListThreads.
|
|
||||||
bool GetDebugRegisters(int pid, DebugRegs *regs) const;
|
|
||||||
|
|
||||||
// Get the stack memory dump.
|
|
||||||
int GetThreadStackDump(uintptr_t current_ebp,
|
|
||||||
uintptr_t current_esp,
|
|
||||||
void *buf,
|
|
||||||
int buf_size) const;
|
|
||||||
|
|
||||||
// Get the module count of the current process.
|
|
||||||
int GetModuleCount() const;
|
|
||||||
|
|
||||||
// Get the mapped modules in the address space.
|
|
||||||
// Whenever a module is found, the callback will be invoked to process the
|
|
||||||
// information.
|
|
||||||
// Return how may modules are found.
|
|
||||||
int ListModules(CallbackParam<ModuleCallback> *callback_param) const;
|
|
||||||
|
|
||||||
// Get the bottom of the stack from ebp.
|
|
||||||
uintptr_t GetThreadStackBottom(uintptr_t current_ebp) const;
|
|
||||||
|
|
||||||
// Finds a sigcontext on the stack given the ebp of our signal handler.
|
|
||||||
bool FindSigContext(uintptr_t sighandler_ebp, struct sigcontext **sig_ctx);
|
|
||||||
|
|
||||||
private:
|
|
||||||
// This callback will run when a new thread has been found.
|
|
||||||
typedef bool (*PidCallback)(int pid, void *context);
|
|
||||||
|
|
||||||
// Read thread information from /proc/$pid/task.
|
|
||||||
// Whenever a thread has been found, and callback will be invoked with
|
|
||||||
// the pid of the thread.
|
|
||||||
// Return number of threads found.
|
|
||||||
// Return -1 means the directory doesn't exist.
|
|
||||||
int IterateProcSelfTask(int pid,
|
|
||||||
CallbackParam<PidCallback> *callback_param) const;
|
|
||||||
|
|
||||||
// Check if the address is a valid virtual address.
|
|
||||||
bool IsAddressMapped(uintptr_t address) const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
// The pid of the process we are listing threads.
|
|
||||||
int pid_;
|
|
||||||
|
|
||||||
// Mark if we have suspended the threads.
|
|
||||||
bool threads_suspened_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace google_breakpad
|
|
||||||
|
|
||||||
#endif // CLIENT_LINUX_HANDLER_LINUX_THREAD_H__
|
|
@ -1,224 +0,0 @@
|
|||||||
// Copyright (c) 2006, Google Inc.
|
|
||||||
// All rights reserved.
|
|
||||||
//
|
|
||||||
// Author: Li Liu
|
|
||||||
//
|
|
||||||
// Redistribution and use in source and binary forms, with or without
|
|
||||||
// modification, are permitted provided that the following conditions are
|
|
||||||
// met:
|
|
||||||
//
|
|
||||||
// * Redistributions of source code must retain the above copyright
|
|
||||||
// notice, this list of conditions and the following disclaimer.
|
|
||||||
// * Redistributions in binary form must reproduce the above
|
|
||||||
// copyright notice, this list of conditions and the following disclaimer
|
|
||||||
// in the documentation and/or other materials provided with the
|
|
||||||
// distribution.
|
|
||||||
// * Neither the name of Google Inc. nor the names of its
|
|
||||||
// contributors may be used to endorse or promote products derived from
|
|
||||||
// this software without specific prior written permission.
|
|
||||||
//
|
|
||||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
|
|
||||||
#include <cstdio>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
#include "client/linux/handler/linux_thread.h"
|
|
||||||
|
|
||||||
using namespace google_breakpad;
|
|
||||||
|
|
||||||
// Thread use this to see if it should stop working.
|
|
||||||
static bool should_exit = false;
|
|
||||||
|
|
||||||
static void foo2(int *a) {
|
|
||||||
// Stack variable, used for debugging stack dumps.
|
|
||||||
int c = 0xcccccccc;
|
|
||||||
c = c;
|
|
||||||
while (!should_exit)
|
|
||||||
sleep(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void foo() {
|
|
||||||
// Stack variable, used for debugging stack dumps.
|
|
||||||
int a = 0xaaaaaaaa;
|
|
||||||
foo2(&a);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void *thread_main(void *) {
|
|
||||||
// Stack variable, used for debugging stack dumps.
|
|
||||||
int b = 0xbbbbbbbb;
|
|
||||||
b = b;
|
|
||||||
while (!should_exit) {
|
|
||||||
foo();
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void CreateThreads(int num) {
|
|
||||||
pthread_t handle;
|
|
||||||
for (int i = 0; i < num; i++) {
|
|
||||||
if (0 != pthread_create(&handle, NULL, thread_main, NULL))
|
|
||||||
fprintf(stderr, "Failed to create thread.\n");
|
|
||||||
else
|
|
||||||
pthread_detach(handle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool ProcessOneModule(const struct ModuleInfo &module_info,
|
|
||||||
void *context) {
|
|
||||||
printf("0x%x[%8d] %s\n", module_info.start_addr, module_info.size,
|
|
||||||
module_info.name);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool ProcessOneThread(const struct ThreadInfo &thread_info,
|
|
||||||
void *context) {
|
|
||||||
printf("\n\nPID: %d, TGID: %d, PPID: %d\n",
|
|
||||||
thread_info.pid,
|
|
||||||
thread_info.tgid,
|
|
||||||
thread_info.ppid);
|
|
||||||
|
|
||||||
struct user_regs_struct regs;
|
|
||||||
struct user_fpregs_struct fp_regs;
|
|
||||||
struct user_fpxregs_struct fpx_regs;
|
|
||||||
struct DebugRegs dbg_regs;
|
|
||||||
|
|
||||||
LinuxThread *threads = reinterpret_cast<LinuxThread *>(context);
|
|
||||||
memset(®s, 0, sizeof(regs));
|
|
||||||
if (threads->GetRegisters(thread_info.pid, ®s)) {
|
|
||||||
printf(" gs = 0x%lx\n", regs.xgs);
|
|
||||||
printf(" fs = 0x%lx\n", regs.xfs);
|
|
||||||
printf(" es = 0x%lx\n", regs.xes);
|
|
||||||
printf(" ds = 0x%lx\n", regs.xds);
|
|
||||||
printf(" edi = 0x%lx\n", regs.edi);
|
|
||||||
printf(" esi = 0x%lx\n", regs.esi);
|
|
||||||
printf(" ebx = 0x%lx\n", regs.ebx);
|
|
||||||
printf(" edx = 0x%lx\n", regs.edx);
|
|
||||||
printf(" ecx = 0x%lx\n", regs.ecx);
|
|
||||||
printf(" eax = 0x%lx\n", regs.eax);
|
|
||||||
printf(" ebp = 0x%lx\n", regs.ebp);
|
|
||||||
printf(" eip = 0x%lx\n", regs.eip);
|
|
||||||
printf(" cs = 0x%lx\n", regs.xcs);
|
|
||||||
printf(" eflags = 0x%lx\n", regs.eflags);
|
|
||||||
printf(" esp = 0x%lx\n", regs.esp);
|
|
||||||
printf(" ss = 0x%lx\n", regs.xss);
|
|
||||||
} else {
|
|
||||||
fprintf(stderr, "ERROR: Failed to get general purpose registers\n");
|
|
||||||
}
|
|
||||||
memset(&fp_regs, 0, sizeof(fp_regs));
|
|
||||||
if (threads->GetFPRegisters(thread_info.pid, &fp_regs)) {
|
|
||||||
printf("\n Floating point registers:\n");
|
|
||||||
printf(" fctl = 0x%lx\n", fp_regs.cwd);
|
|
||||||
printf(" fstat = 0x%lx\n", fp_regs.swd);
|
|
||||||
printf(" ftag = 0x%lx\n", fp_regs.twd);
|
|
||||||
printf(" fioff = 0x%lx\n", fp_regs.fip);
|
|
||||||
printf(" fiseg = 0x%lx\n", fp_regs.fcs);
|
|
||||||
printf(" fooff = 0x%lx\n", fp_regs.foo);
|
|
||||||
printf(" foseg = 0x%lx\n", fp_regs.fos);
|
|
||||||
int st_space_size = sizeof(fp_regs.st_space) / sizeof(fp_regs.st_space[0]);
|
|
||||||
printf(" st_space[%2d] = 0x", st_space_size);
|
|
||||||
for (int i = 0; i < st_space_size; ++i)
|
|
||||||
printf("%02lx", fp_regs.st_space[i]);
|
|
||||||
printf("\n");
|
|
||||||
} else {
|
|
||||||
fprintf(stderr, "ERROR: Failed to get floating-point registers\n");
|
|
||||||
}
|
|
||||||
memset(&fpx_regs, 0, sizeof(fpx_regs));
|
|
||||||
if (threads->GetFPXRegisters(thread_info.pid, &fpx_regs)) {
|
|
||||||
printf("\n Extended floating point registers:\n");
|
|
||||||
printf(" fctl = 0x%x\n", fpx_regs.cwd);
|
|
||||||
printf(" fstat = 0x%x\n", fpx_regs.swd);
|
|
||||||
printf(" ftag = 0x%x\n", fpx_regs.twd);
|
|
||||||
printf(" fioff = 0x%lx\n", fpx_regs.fip);
|
|
||||||
printf(" fiseg = 0x%lx\n", fpx_regs.fcs);
|
|
||||||
printf(" fooff = 0x%lx\n", fpx_regs.foo);
|
|
||||||
printf(" foseg = 0x%lx\n", fpx_regs.fos);
|
|
||||||
printf(" fop = 0x%x\n", fpx_regs.fop);
|
|
||||||
printf(" mxcsr = 0x%lx\n", fpx_regs.mxcsr);
|
|
||||||
int space_size = sizeof(fpx_regs.st_space) / sizeof(fpx_regs.st_space[0]);
|
|
||||||
printf(" st_space[%2d] = 0x", space_size);
|
|
||||||
for (int i = 0; i < space_size; ++i)
|
|
||||||
printf("%02lx", fpx_regs.st_space[i]);
|
|
||||||
printf("\n");
|
|
||||||
space_size = sizeof(fpx_regs.xmm_space) / sizeof(fpx_regs.xmm_space[0]);
|
|
||||||
printf(" xmm_space[%2d] = 0x", space_size);
|
|
||||||
for (int i = 0; i < space_size; ++i)
|
|
||||||
printf("%02lx", fpx_regs.xmm_space[i]);
|
|
||||||
printf("\n");
|
|
||||||
}
|
|
||||||
if (threads->GetDebugRegisters(thread_info.pid, &dbg_regs)) {
|
|
||||||
printf("\n Debug registers:\n");
|
|
||||||
printf(" dr0 = 0x%x\n", dbg_regs.dr0);
|
|
||||||
printf(" dr1 = 0x%x\n", dbg_regs.dr1);
|
|
||||||
printf(" dr2 = 0x%x\n", dbg_regs.dr2);
|
|
||||||
printf(" dr3 = 0x%x\n", dbg_regs.dr3);
|
|
||||||
printf(" dr4 = 0x%x\n", dbg_regs.dr4);
|
|
||||||
printf(" dr5 = 0x%x\n", dbg_regs.dr5);
|
|
||||||
printf(" dr6 = 0x%x\n", dbg_regs.dr6);
|
|
||||||
printf(" dr7 = 0x%x\n", dbg_regs.dr7);
|
|
||||||
printf("\n");
|
|
||||||
}
|
|
||||||
if (regs.esp != 0) {
|
|
||||||
// Print the stack content.
|
|
||||||
int size = 1024 * 2;
|
|
||||||
char *buf = new char[size];
|
|
||||||
size = threads->GetThreadStackDump(regs.ebp,
|
|
||||||
regs.esp,
|
|
||||||
(void*)buf, size);
|
|
||||||
printf(" Stack content: = 0x");
|
|
||||||
size /= sizeof(unsigned long);
|
|
||||||
unsigned long *p_buf = (unsigned long *)(buf);
|
|
||||||
for (int i = 0; i < size; i += 1)
|
|
||||||
printf("%.8lx ", p_buf[i]);
|
|
||||||
delete []buf;
|
|
||||||
printf("\n");
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int PrintAllThreads(void *argument) {
|
|
||||||
int pid = (int)argument;
|
|
||||||
|
|
||||||
LinuxThread threads(pid);
|
|
||||||
int total_thread = threads.SuspendAllThreads();
|
|
||||||
printf("There are %d threads in the process: %d\n", total_thread, pid);
|
|
||||||
int total_module = threads.GetModuleCount();
|
|
||||||
printf("There are %d modules in the process: %d\n", total_module, pid);
|
|
||||||
CallbackParam<ModuleCallback> module_callback(ProcessOneModule, &threads);
|
|
||||||
threads.ListModules(&module_callback);
|
|
||||||
CallbackParam<ThreadCallback> thread_callback(ProcessOneThread, &threads);
|
|
||||||
threads.ListThreads(&thread_callback);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
|
||||||
int pid = getpid();
|
|
||||||
printf("Main thread is %d\n", pid);
|
|
||||||
CreateThreads(1);
|
|
||||||
// Create stack for the process.
|
|
||||||
char *stack = new char[1024 * 100];
|
|
||||||
int cloned_pid = clone(PrintAllThreads, stack + 1024 * 100,
|
|
||||||
CLONE_VM | CLONE_FILES | CLONE_FS | CLONE_UNTRACED,
|
|
||||||
(void*)getpid());
|
|
||||||
waitpid(cloned_pid, NULL, __WALL);
|
|
||||||
should_exit = true;
|
|
||||||
printf("Test finished.\n");
|
|
||||||
|
|
||||||
delete []stack;
|
|
||||||
return 0;
|
|
||||||
}
|
|
@ -1,816 +0,0 @@
|
|||||||
// Copyright (c) 2006, Google Inc.
|
|
||||||
// All rights reserved.
|
|
||||||
//
|
|
||||||
// Author: Li Liu
|
|
||||||
//
|
|
||||||
// Redistribution and use in source and binary forms, with or without
|
|
||||||
// modification, are permitted provided that the following conditions are
|
|
||||||
// met:
|
|
||||||
//
|
|
||||||
// * Redistributions of source code must retain the above copyright
|
|
||||||
// notice, this list of conditions and the following disclaimer.
|
|
||||||
// * Redistributions in binary form must reproduce the above
|
|
||||||
// copyright notice, this list of conditions and the following disclaimer
|
|
||||||
// in the documentation and/or other materials provided with the
|
|
||||||
// distribution.
|
|
||||||
// * Neither the name of Google Inc. nor the names of its
|
|
||||||
// contributors may be used to endorse or promote products derived from
|
|
||||||
// this software without specific prior written permission.
|
|
||||||
//
|
|
||||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <signal.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/utsname.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <ctime>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "common/linux/file_id.h"
|
|
||||||
#include "client/linux/handler/linux_thread.h"
|
|
||||||
#include "client/minidump_file_writer.h"
|
|
||||||
#include "client/minidump_file_writer-inl.h"
|
|
||||||
#include "google_breakpad/common/minidump_format.h"
|
|
||||||
#include "client/linux/handler/minidump_generator.h"
|
|
||||||
|
|
||||||
#ifndef CLONE_UNTRACED
|
|
||||||
#define CLONE_UNTRACED 0x00800000
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// This unnamed namespace contains helper functions.
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
using namespace google_breakpad;
|
|
||||||
|
|
||||||
// Argument for the writer function.
|
|
||||||
struct WriterArgument {
|
|
||||||
MinidumpFileWriter *minidump_writer;
|
|
||||||
|
|
||||||
// Context for the callback.
|
|
||||||
void *version_context;
|
|
||||||
|
|
||||||
// Pid of the thread who called WriteMinidumpToFile
|
|
||||||
int requester_pid;
|
|
||||||
|
|
||||||
// The stack bottom of the thread which caused the dump.
|
|
||||||
// Mainly used to find the thread id of the crashed thread since signal
|
|
||||||
// handler may not be called in the thread who caused it.
|
|
||||||
uintptr_t crashed_stack_bottom;
|
|
||||||
|
|
||||||
// Pid of the crashing thread.
|
|
||||||
int crashed_pid;
|
|
||||||
|
|
||||||
// Signal number when crash happed. Can be 0 if this is a requested dump.
|
|
||||||
int signo;
|
|
||||||
|
|
||||||
// The ebp of the signal handler frame. Can be zero if this
|
|
||||||
// is a requested dump.
|
|
||||||
uintptr_t sighandler_ebp;
|
|
||||||
|
|
||||||
// Signal context when crash happed. Can be NULL if this is a requested dump.
|
|
||||||
// This is actually an out parameter, but it will be filled in at the start
|
|
||||||
// of the writer thread.
|
|
||||||
struct sigcontext *sig_ctx;
|
|
||||||
|
|
||||||
// Used to get information about the threads.
|
|
||||||
LinuxThread *thread_lister;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Holding context information for the callback of finding the crashing thread.
|
|
||||||
struct FindCrashThreadContext {
|
|
||||||
const LinuxThread *thread_lister;
|
|
||||||
uintptr_t crashing_stack_bottom;
|
|
||||||
int crashing_thread_pid;
|
|
||||||
|
|
||||||
FindCrashThreadContext() :
|
|
||||||
thread_lister(NULL),
|
|
||||||
crashing_stack_bottom(0UL),
|
|
||||||
crashing_thread_pid(-1) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Callback for list threads.
|
|
||||||
// It will compare the stack bottom of the provided thread with the stack
|
|
||||||
// bottom of the crashed thread, it they are eqaul, this is thread is the one
|
|
||||||
// who crashed.
|
|
||||||
bool IsThreadCrashedCallback(const ThreadInfo &thread_info, void *context) {
|
|
||||||
FindCrashThreadContext *crashing_context =
|
|
||||||
static_cast<FindCrashThreadContext *>(context);
|
|
||||||
const LinuxThread *thread_lister = crashing_context->thread_lister;
|
|
||||||
struct user_regs_struct regs;
|
|
||||||
if (thread_lister->GetRegisters(thread_info.pid, ®s)) {
|
|
||||||
uintptr_t last_ebp = regs.ebp;
|
|
||||||
uintptr_t stack_bottom = thread_lister->GetThreadStackBottom(last_ebp);
|
|
||||||
if (stack_bottom > last_ebp &&
|
|
||||||
stack_bottom == crashing_context->crashing_stack_bottom) {
|
|
||||||
// Got it. Stop iteration.
|
|
||||||
crashing_context->crashing_thread_pid = thread_info.pid;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the crashing thread id.
|
|
||||||
// This is done based on stack bottom comparing.
|
|
||||||
int FindCrashingThread(uintptr_t crashing_stack_bottom,
|
|
||||||
int requester_pid,
|
|
||||||
const LinuxThread *thread_lister) {
|
|
||||||
FindCrashThreadContext context;
|
|
||||||
context.thread_lister = thread_lister;
|
|
||||||
context.crashing_stack_bottom = crashing_stack_bottom;
|
|
||||||
CallbackParam<ThreadCallback> callback_param(IsThreadCrashedCallback,
|
|
||||||
&context);
|
|
||||||
thread_lister->ListThreads(&callback_param);
|
|
||||||
return context.crashing_thread_pid;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the thread stack info minidump.
|
|
||||||
bool WriteThreadStack(uintptr_t last_ebp,
|
|
||||||
uintptr_t last_esp,
|
|
||||||
const LinuxThread *thread_lister,
|
|
||||||
UntypedMDRVA *memory,
|
|
||||||
MDMemoryDescriptor *loc) {
|
|
||||||
// Maximum stack size for a thread.
|
|
||||||
uintptr_t stack_bottom = thread_lister->GetThreadStackBottom(last_ebp);
|
|
||||||
if (stack_bottom > last_esp) {
|
|
||||||
int size = stack_bottom - last_esp;
|
|
||||||
if (size > 0) {
|
|
||||||
if (!memory->Allocate(size))
|
|
||||||
return false;
|
|
||||||
memory->Copy(reinterpret_cast<void*>(last_esp), size);
|
|
||||||
loc->start_of_memory_range = 0 | last_esp;
|
|
||||||
loc->memory = memory->location();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write CPU context based on signal context.
|
|
||||||
bool WriteContext(MDRawContextX86 *context, const struct sigcontext *sig_ctx,
|
|
||||||
const DebugRegs *debug_regs) {
|
|
||||||
assert(sig_ctx != NULL);
|
|
||||||
context->context_flags = MD_CONTEXT_X86_FULL;
|
|
||||||
context->gs = sig_ctx->gs;
|
|
||||||
context->fs = sig_ctx->fs;
|
|
||||||
context->es = sig_ctx->es;
|
|
||||||
context->ds = sig_ctx->ds;
|
|
||||||
context->cs = sig_ctx->cs;
|
|
||||||
context->ss = sig_ctx->ss;
|
|
||||||
context->edi = sig_ctx->edi;
|
|
||||||
context->esi = sig_ctx->esi;
|
|
||||||
context->ebp = sig_ctx->ebp;
|
|
||||||
context->esp = sig_ctx->esp;
|
|
||||||
context->ebx = sig_ctx->ebx;
|
|
||||||
context->edx = sig_ctx->edx;
|
|
||||||
context->ecx = sig_ctx->ecx;
|
|
||||||
context->eax = sig_ctx->eax;
|
|
||||||
context->eip = sig_ctx->eip;
|
|
||||||
context->eflags = sig_ctx->eflags;
|
|
||||||
if (sig_ctx->fpstate != NULL) {
|
|
||||||
context->context_flags = MD_CONTEXT_X86_FULL |
|
|
||||||
MD_CONTEXT_X86_FLOATING_POINT;
|
|
||||||
context->float_save.control_word = sig_ctx->fpstate->cw;
|
|
||||||
context->float_save.status_word = sig_ctx->fpstate->sw;
|
|
||||||
context->float_save.tag_word = sig_ctx->fpstate->tag;
|
|
||||||
context->float_save.error_offset = sig_ctx->fpstate->ipoff;
|
|
||||||
context->float_save.error_selector = sig_ctx->fpstate->cssel;
|
|
||||||
context->float_save.data_offset = sig_ctx->fpstate->dataoff;
|
|
||||||
context->float_save.data_selector = sig_ctx->fpstate->datasel;
|
|
||||||
memcpy(context->float_save.register_area, sig_ctx->fpstate->_st,
|
|
||||||
sizeof(context->float_save.register_area));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (debug_regs != NULL) {
|
|
||||||
context->context_flags |= MD_CONTEXT_X86_DEBUG_REGISTERS;
|
|
||||||
context->dr0 = debug_regs->dr0;
|
|
||||||
context->dr1 = debug_regs->dr1;
|
|
||||||
context->dr2 = debug_regs->dr2;
|
|
||||||
context->dr3 = debug_regs->dr3;
|
|
||||||
context->dr6 = debug_regs->dr6;
|
|
||||||
context->dr7 = debug_regs->dr7;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write CPU context based on provided registers.
|
|
||||||
bool WriteContext(MDRawContextX86 *context,
|
|
||||||
const struct user_regs_struct *regs,
|
|
||||||
const struct user_fpregs_struct *fp_regs,
|
|
||||||
const DebugRegs *dbg_regs) {
|
|
||||||
if (!context || !regs)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
context->context_flags = MD_CONTEXT_X86_FULL;
|
|
||||||
|
|
||||||
context->cs = regs->xcs;
|
|
||||||
context->ds = regs->xds;
|
|
||||||
context->es = regs->xes;
|
|
||||||
context->fs = regs->xfs;
|
|
||||||
context->gs = regs->xgs;
|
|
||||||
context->ss = regs->xss;
|
|
||||||
context->edi = regs->edi;
|
|
||||||
context->esi = regs->esi;
|
|
||||||
context->ebx = regs->ebx;
|
|
||||||
context->edx = regs->edx;
|
|
||||||
context->ecx = regs->ecx;
|
|
||||||
context->eax = regs->eax;
|
|
||||||
context->ebp = regs->ebp;
|
|
||||||
context->eip = regs->eip;
|
|
||||||
context->esp = regs->esp;
|
|
||||||
context->eflags = regs->eflags;
|
|
||||||
|
|
||||||
if (dbg_regs != NULL) {
|
|
||||||
context->context_flags |= MD_CONTEXT_X86_DEBUG_REGISTERS;
|
|
||||||
context->dr0 = dbg_regs->dr0;
|
|
||||||
context->dr1 = dbg_regs->dr1;
|
|
||||||
context->dr2 = dbg_regs->dr2;
|
|
||||||
context->dr3 = dbg_regs->dr3;
|
|
||||||
context->dr6 = dbg_regs->dr6;
|
|
||||||
context->dr7 = dbg_regs->dr7;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fp_regs != NULL) {
|
|
||||||
context->context_flags |= MD_CONTEXT_X86_FLOATING_POINT;
|
|
||||||
context->float_save.control_word = fp_regs->cwd;
|
|
||||||
context->float_save.status_word = fp_regs->swd;
|
|
||||||
context->float_save.tag_word = fp_regs->twd;
|
|
||||||
context->float_save.error_offset = fp_regs->fip;
|
|
||||||
context->float_save.error_selector = fp_regs->fcs;
|
|
||||||
context->float_save.data_offset = fp_regs->foo;
|
|
||||||
context->float_save.data_selector = fp_regs->fos;
|
|
||||||
context->float_save.data_selector = fp_regs->fos;
|
|
||||||
|
|
||||||
memcpy(context->float_save.register_area, fp_regs->st_space,
|
|
||||||
sizeof(context->float_save.register_area));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write information about a crashed thread.
|
|
||||||
// When a thread crash, kernel will write something on the stack for processing
|
|
||||||
// signal. This makes the current stack not reliable, and our stack walker
|
|
||||||
// won't figure out the whole call stack for this. So we write the stack at the
|
|
||||||
// time of the crash into the minidump file, not the current stack.
|
|
||||||
bool WriteCrashedThreadStream(MinidumpFileWriter *minidump_writer,
|
|
||||||
const WriterArgument *writer_args,
|
|
||||||
const ThreadInfo &thread_info,
|
|
||||||
MDRawThread *thread) {
|
|
||||||
assert(writer_args->sig_ctx != NULL);
|
|
||||||
|
|
||||||
thread->thread_id = thread_info.pid;
|
|
||||||
|
|
||||||
UntypedMDRVA memory(minidump_writer);
|
|
||||||
if (!WriteThreadStack(writer_args->sig_ctx->ebp,
|
|
||||||
writer_args->sig_ctx->esp,
|
|
||||||
writer_args->thread_lister,
|
|
||||||
&memory,
|
|
||||||
&thread->stack))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
TypedMDRVA<MDRawContextX86> context(minidump_writer);
|
|
||||||
if (!context.Allocate())
|
|
||||||
return false;
|
|
||||||
thread->thread_context = context.location();
|
|
||||||
memset(context.get(), 0, sizeof(MDRawContextX86));
|
|
||||||
return WriteContext(context.get(), writer_args->sig_ctx, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write information about a thread.
|
|
||||||
// This function only processes thread running normally at the crash.
|
|
||||||
bool WriteThreadStream(MinidumpFileWriter *minidump_writer,
|
|
||||||
const LinuxThread *thread_lister,
|
|
||||||
const ThreadInfo &thread_info,
|
|
||||||
MDRawThread *thread) {
|
|
||||||
thread->thread_id = thread_info.pid;
|
|
||||||
|
|
||||||
struct user_regs_struct regs;
|
|
||||||
memset(®s, 0, sizeof(regs));
|
|
||||||
if (!thread_lister->GetRegisters(thread_info.pid, ®s)) {
|
|
||||||
perror(NULL);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
UntypedMDRVA memory(minidump_writer);
|
|
||||||
if (!WriteThreadStack(regs.ebp,
|
|
||||||
regs.esp,
|
|
||||||
thread_lister,
|
|
||||||
&memory,
|
|
||||||
&thread->stack))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
struct user_fpregs_struct fp_regs;
|
|
||||||
DebugRegs dbg_regs;
|
|
||||||
memset(&fp_regs, 0, sizeof(fp_regs));
|
|
||||||
// Get all the registers.
|
|
||||||
thread_lister->GetFPRegisters(thread_info.pid, &fp_regs);
|
|
||||||
thread_lister->GetDebugRegisters(thread_info.pid, &dbg_regs);
|
|
||||||
|
|
||||||
// Write context
|
|
||||||
TypedMDRVA<MDRawContextX86> context(minidump_writer);
|
|
||||||
if (!context.Allocate())
|
|
||||||
return false;
|
|
||||||
thread->thread_context = context.location();
|
|
||||||
memset(context.get(), 0, sizeof(MDRawContextX86));
|
|
||||||
return WriteContext(context.get(), ®s, &fp_regs, &dbg_regs);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WriteCPUInformation(MDRawSystemInfo *sys_info) {
|
|
||||||
const char *proc_cpu_path = "/proc/cpuinfo";
|
|
||||||
char line[128];
|
|
||||||
char vendor_id[13];
|
|
||||||
const char vendor_id_name[] = "vendor_id";
|
|
||||||
const size_t vendor_id_name_length = sizeof(vendor_id_name) - 1;
|
|
||||||
|
|
||||||
struct CpuInfoEntry {
|
|
||||||
const char *info_name;
|
|
||||||
int value;
|
|
||||||
} cpu_info_table[] = {
|
|
||||||
{ "processor", -1 },
|
|
||||||
{ "model", 0 },
|
|
||||||
{ "stepping", 0 },
|
|
||||||
{ "cpuid level", 0 },
|
|
||||||
{ NULL, -1 },
|
|
||||||
};
|
|
||||||
|
|
||||||
memset(vendor_id, 0, sizeof(vendor_id));
|
|
||||||
|
|
||||||
FILE *fp = fopen(proc_cpu_path, "r");
|
|
||||||
if (fp != NULL) {
|
|
||||||
while (fgets(line, sizeof(line), fp)) {
|
|
||||||
CpuInfoEntry *entry = &cpu_info_table[0];
|
|
||||||
while (entry->info_name != NULL) {
|
|
||||||
if (!strncmp(line, entry->info_name, strlen(entry->info_name))) {
|
|
||||||
char *value = strchr(line, ':');
|
|
||||||
value++;
|
|
||||||
if (value != NULL)
|
|
||||||
sscanf(value, " %d", &(entry->value));
|
|
||||||
}
|
|
||||||
entry++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// special case for vendor_id
|
|
||||||
if (!strncmp(line, vendor_id_name, vendor_id_name_length)) {
|
|
||||||
char *value = strchr(line, ':');
|
|
||||||
if (value == NULL)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
value++;
|
|
||||||
while (*value && isspace(*value))
|
|
||||||
value++;
|
|
||||||
if (*value) {
|
|
||||||
size_t length = strlen(value);
|
|
||||||
// we don't want the trailing newline
|
|
||||||
if (value[length - 1] == '\n')
|
|
||||||
length--;
|
|
||||||
// ensure we have space for the value
|
|
||||||
if (length < sizeof(vendor_id))
|
|
||||||
strncpy(vendor_id, value, length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fclose(fp);
|
|
||||||
}
|
|
||||||
|
|
||||||
// /proc/cpuinfo contains cpu id, change it into number by adding one.
|
|
||||||
cpu_info_table[0].value++;
|
|
||||||
|
|
||||||
sys_info->number_of_processors = cpu_info_table[0].value;
|
|
||||||
sys_info->processor_level = cpu_info_table[3].value;
|
|
||||||
sys_info->processor_revision = cpu_info_table[1].value << 8 |
|
|
||||||
cpu_info_table[2].value;
|
|
||||||
|
|
||||||
sys_info->processor_architecture = MD_CPU_ARCHITECTURE_UNKNOWN;
|
|
||||||
struct utsname uts;
|
|
||||||
if (uname(&uts) == 0) {
|
|
||||||
// Match i*86 and x86* as X86 architecture.
|
|
||||||
if ((strstr(uts.machine, "x86") == uts.machine) ||
|
|
||||||
(strlen(uts.machine) == 4 &&
|
|
||||||
uts.machine[0] == 'i' &&
|
|
||||||
uts.machine[2] == '8' &&
|
|
||||||
uts.machine[3] == '6')) {
|
|
||||||
sys_info->processor_architecture = MD_CPU_ARCHITECTURE_X86;
|
|
||||||
if (vendor_id[0] != '\0')
|
|
||||||
memcpy(sys_info->cpu.x86_cpu_info.vendor_id, vendor_id,
|
|
||||||
sizeof(sys_info->cpu.x86_cpu_info.vendor_id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WriteOSInformation(MinidumpFileWriter *minidump_writer,
|
|
||||||
MDRawSystemInfo *sys_info) {
|
|
||||||
sys_info->platform_id = MD_OS_LINUX;
|
|
||||||
|
|
||||||
struct utsname uts;
|
|
||||||
if (uname(&uts) == 0) {
|
|
||||||
char os_version[512];
|
|
||||||
size_t space_left = sizeof(os_version);
|
|
||||||
memset(os_version, 0, space_left);
|
|
||||||
const char *os_info_table[] = {
|
|
||||||
uts.sysname,
|
|
||||||
uts.release,
|
|
||||||
uts.version,
|
|
||||||
uts.machine,
|
|
||||||
"GNU/Linux",
|
|
||||||
NULL
|
|
||||||
};
|
|
||||||
for (const char **cur_os_info = os_info_table;
|
|
||||||
*cur_os_info != NULL;
|
|
||||||
cur_os_info++) {
|
|
||||||
if (cur_os_info != os_info_table && space_left > 1) {
|
|
||||||
strcat(os_version, " ");
|
|
||||||
space_left--;
|
|
||||||
}
|
|
||||||
if (space_left > strlen(*cur_os_info)) {
|
|
||||||
strcat(os_version, *cur_os_info);
|
|
||||||
space_left -= strlen(*cur_os_info);
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MDLocationDescriptor location;
|
|
||||||
if (!minidump_writer->WriteString(os_version, 0, &location))
|
|
||||||
return false;
|
|
||||||
sys_info->csd_version_rva = location.rva;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Callback context for get writting thread information.
|
|
||||||
struct ThreadInfoCallbackCtx {
|
|
||||||
MinidumpFileWriter *minidump_writer;
|
|
||||||
const WriterArgument *writer_args;
|
|
||||||
TypedMDRVA<MDRawThreadList> *list;
|
|
||||||
int thread_index;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Callback run for writing threads information in the process.
|
|
||||||
bool ThreadInfomationCallback(const ThreadInfo &thread_info,
|
|
||||||
void *context) {
|
|
||||||
ThreadInfoCallbackCtx *callback_context =
|
|
||||||
static_cast<ThreadInfoCallbackCtx *>(context);
|
|
||||||
bool success = true;
|
|
||||||
MDRawThread thread;
|
|
||||||
memset(&thread, 0, sizeof(MDRawThread));
|
|
||||||
if (thread_info.pid != callback_context->writer_args->crashed_pid ||
|
|
||||||
callback_context->writer_args->sig_ctx == NULL) {
|
|
||||||
success = WriteThreadStream(callback_context->minidump_writer,
|
|
||||||
callback_context->writer_args->thread_lister,
|
|
||||||
thread_info, &thread);
|
|
||||||
} else {
|
|
||||||
success = WriteCrashedThreadStream(callback_context->minidump_writer,
|
|
||||||
callback_context->writer_args,
|
|
||||||
thread_info, &thread);
|
|
||||||
}
|
|
||||||
if (success) {
|
|
||||||
callback_context->list->CopyIndexAfterObject(
|
|
||||||
callback_context->thread_index++,
|
|
||||||
&thread, sizeof(MDRawThread));
|
|
||||||
}
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stream writers
|
|
||||||
bool WriteThreadListStream(MinidumpFileWriter *minidump_writer,
|
|
||||||
const WriterArgument *writer_args,
|
|
||||||
MDRawDirectory *dir) {
|
|
||||||
// Get the thread information.
|
|
||||||
const LinuxThread *thread_lister = writer_args->thread_lister;
|
|
||||||
int thread_count = thread_lister->GetThreadCount();
|
|
||||||
if (thread_count < 0)
|
|
||||||
return false;
|
|
||||||
TypedMDRVA<MDRawThreadList> list(minidump_writer);
|
|
||||||
if (!list.AllocateObjectAndArray(thread_count, sizeof(MDRawThread)))
|
|
||||||
return false;
|
|
||||||
dir->stream_type = MD_THREAD_LIST_STREAM;
|
|
||||||
dir->location = list.location();
|
|
||||||
list.get()->number_of_threads = thread_count;
|
|
||||||
|
|
||||||
ThreadInfoCallbackCtx context;
|
|
||||||
context.minidump_writer = minidump_writer;
|
|
||||||
context.writer_args = writer_args;
|
|
||||||
context.list = &list;
|
|
||||||
context.thread_index = 0;
|
|
||||||
CallbackParam<ThreadCallback> callback_param(ThreadInfomationCallback,
|
|
||||||
&context);
|
|
||||||
int written = thread_lister->ListThreads(&callback_param);
|
|
||||||
return written == thread_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WriteCVRecord(MinidumpFileWriter *minidump_writer,
|
|
||||||
MDRawModule *module,
|
|
||||||
const char *module_path) {
|
|
||||||
TypedMDRVA<MDCVInfoPDB70> cv(minidump_writer);
|
|
||||||
|
|
||||||
// Only return the last path component of the full module path
|
|
||||||
const char *module_name = strrchr(module_path, '/');
|
|
||||||
// Increment past the slash
|
|
||||||
if (module_name)
|
|
||||||
++module_name;
|
|
||||||
else
|
|
||||||
module_name = "<Unknown>";
|
|
||||||
|
|
||||||
size_t module_name_length = strlen(module_name);
|
|
||||||
if (!cv.AllocateObjectAndArray(module_name_length + 1, sizeof(u_int8_t)))
|
|
||||||
return false;
|
|
||||||
if (!cv.CopyIndexAfterObject(0, const_cast<char *>(module_name),
|
|
||||||
module_name_length))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
module->cv_record = cv.location();
|
|
||||||
MDCVInfoPDB70 *cv_ptr = cv.get();
|
|
||||||
memset(cv_ptr, 0, sizeof(MDCVInfoPDB70));
|
|
||||||
cv_ptr->cv_signature = MD_CVINFOPDB70_SIGNATURE;
|
|
||||||
cv_ptr->age = 0;
|
|
||||||
|
|
||||||
// Get the module identifier
|
|
||||||
FileID file_id(module_path);
|
|
||||||
unsigned char identifier[16];
|
|
||||||
|
|
||||||
if (file_id.ElfFileIdentifier(identifier)) {
|
|
||||||
cv_ptr->signature.data1 = (uint32_t)identifier[0] << 24 |
|
|
||||||
(uint32_t)identifier[1] << 16 | (uint32_t)identifier[2] << 8 |
|
|
||||||
(uint32_t)identifier[3];
|
|
||||||
cv_ptr->signature.data2 = (uint32_t)identifier[4] << 8 | identifier[5];
|
|
||||||
cv_ptr->signature.data3 = (uint32_t)identifier[6] << 8 | identifier[7];
|
|
||||||
cv_ptr->signature.data4[0] = identifier[8];
|
|
||||||
cv_ptr->signature.data4[1] = identifier[9];
|
|
||||||
cv_ptr->signature.data4[2] = identifier[10];
|
|
||||||
cv_ptr->signature.data4[3] = identifier[11];
|
|
||||||
cv_ptr->signature.data4[4] = identifier[12];
|
|
||||||
cv_ptr->signature.data4[5] = identifier[13];
|
|
||||||
cv_ptr->signature.data4[6] = identifier[14];
|
|
||||||
cv_ptr->signature.data4[7] = identifier[15];
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ModuleInfoCallbackCtx {
|
|
||||||
MinidumpFileWriter *minidump_writer;
|
|
||||||
const WriterArgument *writer_args;
|
|
||||||
TypedMDRVA<MDRawModuleList> *list;
|
|
||||||
int module_index;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool ModuleInfoCallback(const ModuleInfo &module_info,
|
|
||||||
void *context) {
|
|
||||||
ModuleInfoCallbackCtx *callback_context =
|
|
||||||
static_cast<ModuleInfoCallbackCtx *>(context);
|
|
||||||
// Skip those modules without name, or those that are not modules.
|
|
||||||
if (strlen(module_info.name) == 0 ||
|
|
||||||
!strchr(module_info.name, '/'))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
MDRawModule module;
|
|
||||||
memset(&module, 0, sizeof(module));
|
|
||||||
MDLocationDescriptor loc;
|
|
||||||
if (!callback_context->minidump_writer->WriteString(module_info.name, 0,
|
|
||||||
&loc))
|
|
||||||
return false;
|
|
||||||
module.base_of_image = (u_int64_t)module_info.start_addr;
|
|
||||||
module.size_of_image = module_info.size;
|
|
||||||
module.module_name_rva = loc.rva;
|
|
||||||
|
|
||||||
if (!WriteCVRecord(callback_context->minidump_writer, &module,
|
|
||||||
module_info.name))
|
|
||||||
return false;
|
|
||||||
callback_context->list->CopyIndexAfterObject(
|
|
||||||
callback_context->module_index++, &module, MD_MODULE_SIZE);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WriteModuleListStream(MinidumpFileWriter *minidump_writer,
|
|
||||||
const WriterArgument *writer_args,
|
|
||||||
MDRawDirectory *dir) {
|
|
||||||
TypedMDRVA<MDRawModuleList> list(minidump_writer);
|
|
||||||
int module_count = writer_args->thread_lister->GetModuleCount();
|
|
||||||
if (module_count <= 0 ||
|
|
||||||
!list.AllocateObjectAndArray(module_count, MD_MODULE_SIZE))
|
|
||||||
return false;
|
|
||||||
dir->stream_type = MD_MODULE_LIST_STREAM;
|
|
||||||
dir->location = list.location();
|
|
||||||
list.get()->number_of_modules = module_count;
|
|
||||||
ModuleInfoCallbackCtx context;
|
|
||||||
context.minidump_writer = minidump_writer;
|
|
||||||
context.writer_args = writer_args;
|
|
||||||
context.list = &list;
|
|
||||||
context.module_index = 0;
|
|
||||||
CallbackParam<ModuleCallback> callback(ModuleInfoCallback, &context);
|
|
||||||
return writer_args->thread_lister->ListModules(&callback) == module_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WriteSystemInfoStream(MinidumpFileWriter *minidump_writer,
|
|
||||||
const WriterArgument *writer_args,
|
|
||||||
MDRawDirectory *dir) {
|
|
||||||
TypedMDRVA<MDRawSystemInfo> sys_info(minidump_writer);
|
|
||||||
if (!sys_info.Allocate())
|
|
||||||
return false;
|
|
||||||
dir->stream_type = MD_SYSTEM_INFO_STREAM;
|
|
||||||
dir->location = sys_info.location();
|
|
||||||
|
|
||||||
return WriteCPUInformation(sys_info.get()) &&
|
|
||||||
WriteOSInformation(minidump_writer, sys_info.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WriteExceptionStream(MinidumpFileWriter *minidump_writer,
|
|
||||||
const WriterArgument *writer_args,
|
|
||||||
MDRawDirectory *dir) {
|
|
||||||
// This happenes when this is not a crash, but a requested dump.
|
|
||||||
if (writer_args->sig_ctx == NULL)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
TypedMDRVA<MDRawExceptionStream> exception(minidump_writer);
|
|
||||||
if (!exception.Allocate())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
dir->stream_type = MD_EXCEPTION_STREAM;
|
|
||||||
dir->location = exception.location();
|
|
||||||
exception.get()->thread_id = writer_args->crashed_pid;
|
|
||||||
exception.get()->exception_record.exception_code = writer_args->signo;
|
|
||||||
exception.get()->exception_record.exception_flags = 0;
|
|
||||||
if (writer_args->sig_ctx != NULL) {
|
|
||||||
exception.get()->exception_record.exception_address =
|
|
||||||
writer_args->sig_ctx->eip;
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write context of the exception.
|
|
||||||
TypedMDRVA<MDRawContextX86> context(minidump_writer);
|
|
||||||
if (!context.Allocate())
|
|
||||||
return false;
|
|
||||||
exception.get()->thread_context = context.location();
|
|
||||||
memset(context.get(), 0, sizeof(MDRawContextX86));
|
|
||||||
return WriteContext(context.get(), writer_args->sig_ctx, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WriteMiscInfoStream(MinidumpFileWriter *minidump_writer,
|
|
||||||
const WriterArgument *writer_args,
|
|
||||||
MDRawDirectory *dir) {
|
|
||||||
TypedMDRVA<MDRawMiscInfo> info(minidump_writer);
|
|
||||||
if (!info.Allocate())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
dir->stream_type = MD_MISC_INFO_STREAM;
|
|
||||||
dir->location = info.location();
|
|
||||||
info.get()->size_of_info = sizeof(MDRawMiscInfo);
|
|
||||||
info.get()->flags1 = MD_MISCINFO_FLAGS1_PROCESS_ID;
|
|
||||||
info.get()->process_id = writer_args->requester_pid;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WriteBreakpadInfoStream(MinidumpFileWriter *minidump_writer,
|
|
||||||
const WriterArgument *writer_args,
|
|
||||||
MDRawDirectory *dir) {
|
|
||||||
TypedMDRVA<MDRawBreakpadInfo> info(minidump_writer);
|
|
||||||
if (!info.Allocate())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
dir->stream_type = MD_BREAKPAD_INFO_STREAM;
|
|
||||||
dir->location = info.location();
|
|
||||||
|
|
||||||
info.get()->validity = MD_BREAKPAD_INFO_VALID_DUMP_THREAD_ID |
|
|
||||||
MD_BREAKPAD_INFO_VALID_REQUESTING_THREAD_ID;
|
|
||||||
info.get()->dump_thread_id = getpid();
|
|
||||||
info.get()->requesting_thread_id = writer_args->requester_pid;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prototype of writer functions.
|
|
||||||
typedef bool (*WriteStringFN)(MinidumpFileWriter *,
|
|
||||||
const WriterArgument *,
|
|
||||||
MDRawDirectory *);
|
|
||||||
|
|
||||||
// Function table to writer a full minidump.
|
|
||||||
WriteStringFN writers[] = {
|
|
||||||
WriteThreadListStream,
|
|
||||||
WriteModuleListStream,
|
|
||||||
WriteSystemInfoStream,
|
|
||||||
WriteExceptionStream,
|
|
||||||
WriteMiscInfoStream,
|
|
||||||
WriteBreakpadInfoStream,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Will call each writer function in the writers table.
|
|
||||||
// It runs in a different process from the crashing process, but sharing
|
|
||||||
// the same address space. This enables it to use ptrace functions.
|
|
||||||
int Write(void *argument) {
|
|
||||||
WriterArgument *writer_args =
|
|
||||||
static_cast<WriterArgument *>(argument);
|
|
||||||
|
|
||||||
if (!writer_args->thread_lister->SuspendAllThreads())
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
if (writer_args->sighandler_ebp != 0 &&
|
|
||||||
writer_args->thread_lister->FindSigContext(writer_args->sighandler_ebp,
|
|
||||||
&writer_args->sig_ctx)) {
|
|
||||||
writer_args->crashed_stack_bottom =
|
|
||||||
writer_args->thread_lister->GetThreadStackBottom(
|
|
||||||
writer_args->sig_ctx->ebp);
|
|
||||||
int crashed_pid = FindCrashingThread(writer_args->crashed_stack_bottom,
|
|
||||||
writer_args->requester_pid,
|
|
||||||
writer_args->thread_lister);
|
|
||||||
if (crashed_pid > 0)
|
|
||||||
writer_args->crashed_pid = crashed_pid;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
MinidumpFileWriter *minidump_writer = writer_args->minidump_writer;
|
|
||||||
TypedMDRVA<MDRawHeader> header(minidump_writer);
|
|
||||||
TypedMDRVA<MDRawDirectory> dir(minidump_writer);
|
|
||||||
if (!header.Allocate())
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
int writer_count = sizeof(writers) / sizeof(writers[0]);
|
|
||||||
// Need directory space for all writers.
|
|
||||||
if (!dir.AllocateArray(writer_count))
|
|
||||||
return 0;
|
|
||||||
header.get()->signature = MD_HEADER_SIGNATURE;
|
|
||||||
header.get()->version = MD_HEADER_VERSION;
|
|
||||||
header.get()->time_date_stamp = time(NULL);
|
|
||||||
header.get()->stream_count = writer_count;
|
|
||||||
header.get()->stream_directory_rva = dir.position();
|
|
||||||
|
|
||||||
int dir_index = 0;
|
|
||||||
MDRawDirectory local_dir;
|
|
||||||
for (int i = 0; i < writer_count; ++i) {
|
|
||||||
if (writers[i](minidump_writer, writer_args, &local_dir))
|
|
||||||
dir.CopyIndex(dir_index++, &local_dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
writer_args->thread_lister->ResumeAllThreads();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace google_breakpad {
|
|
||||||
|
|
||||||
MinidumpGenerator::MinidumpGenerator() {
|
|
||||||
AllocateStack();
|
|
||||||
}
|
|
||||||
|
|
||||||
MinidumpGenerator::~MinidumpGenerator() {
|
|
||||||
}
|
|
||||||
|
|
||||||
void MinidumpGenerator::AllocateStack() {
|
|
||||||
stack_.reset(new char[kStackSize]);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MinidumpGenerator::WriteMinidumpToFile(const char *file_pathname,
|
|
||||||
int signo,
|
|
||||||
uintptr_t sighandler_ebp,
|
|
||||||
struct sigcontext **sig_ctx) const {
|
|
||||||
assert(file_pathname != NULL);
|
|
||||||
assert(stack_ != NULL);
|
|
||||||
|
|
||||||
if (stack_ == NULL || file_pathname == NULL)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
MinidumpFileWriter minidump_writer;
|
|
||||||
if (minidump_writer.Open(file_pathname)) {
|
|
||||||
WriterArgument argument;
|
|
||||||
memset(&argument, 0, sizeof(argument));
|
|
||||||
LinuxThread thread_lister(getpid());
|
|
||||||
argument.thread_lister = &thread_lister;
|
|
||||||
argument.minidump_writer = &minidump_writer;
|
|
||||||
argument.requester_pid = getpid();
|
|
||||||
argument.crashed_pid = getpid();
|
|
||||||
argument.signo = signo;
|
|
||||||
argument.sighandler_ebp = sighandler_ebp;
|
|
||||||
argument.sig_ctx = NULL;
|
|
||||||
|
|
||||||
int cloned_pid = clone(Write, stack_.get() + kStackSize,
|
|
||||||
CLONE_VM | CLONE_FILES | CLONE_FS | CLONE_UNTRACED,
|
|
||||||
(void*)&argument);
|
|
||||||
waitpid(cloned_pid, NULL, __WALL);
|
|
||||||
if (sig_ctx != NULL)
|
|
||||||
*sig_ctx = argument.sig_ctx;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace google_breakpad
|
|
105
src/client/linux/minidump_writer/directory_reader.h
Normal file
105
src/client/linux/minidump_writer/directory_reader.h
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
// Copyright (c) 2009, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
#ifndef CLIENT_LINUX_MINIDUMP_WRITER_DIRECTORY_READER_H_
|
||||||
|
#define CLIENT_LINUX_MINIDUMP_WRITER_DIRECTORY_READER_H_
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "common/linux/linux_syscall_support.h"
|
||||||
|
|
||||||
|
namespace google_breakpad {
|
||||||
|
|
||||||
|
// A class for enumerating a directory without using diropen/readdir or other
|
||||||
|
// functions which may allocate memory.
|
||||||
|
class DirectoryReader {
|
||||||
|
public:
|
||||||
|
DirectoryReader(int fd)
|
||||||
|
: fd_(fd),
|
||||||
|
buf_used_(0) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the next entry from the directory
|
||||||
|
// name: (output) the NUL terminated entry name
|
||||||
|
//
|
||||||
|
// Returns true iff successful (false on EOF).
|
||||||
|
//
|
||||||
|
// After calling this, one must call |PopEntry| otherwise you'll get the same
|
||||||
|
// entry over and over.
|
||||||
|
bool GetNextEntry(const char** name) {
|
||||||
|
struct kernel_dirent* const dent =
|
||||||
|
reinterpret_cast<kernel_dirent*>(buf_);
|
||||||
|
|
||||||
|
if (buf_used_ == 0) {
|
||||||
|
// need to read more entries.
|
||||||
|
const int n = sys_getdents(fd_, dent, sizeof(buf_));
|
||||||
|
if (n < 0) {
|
||||||
|
return false;
|
||||||
|
} else if (n == 0) {
|
||||||
|
hit_eof_ = true;
|
||||||
|
} else {
|
||||||
|
buf_used_ += n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buf_used_ == 0 && hit_eof_)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
assert(buf_used_ > 0);
|
||||||
|
|
||||||
|
*name = dent->d_name;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PopEntry() {
|
||||||
|
if (!buf_used_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const struct kernel_dirent* const dent =
|
||||||
|
reinterpret_cast<kernel_dirent*>(buf_);
|
||||||
|
|
||||||
|
buf_used_ -= dent->d_reclen;
|
||||||
|
memmove(buf_, buf_ + dent->d_reclen, buf_used_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const int fd_;
|
||||||
|
bool hit_eof_;
|
||||||
|
unsigned buf_used_;
|
||||||
|
uint8_t buf_[sizeof(struct kernel_dirent) + NAME_MAX + 1];
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace google_breakpad
|
||||||
|
|
||||||
|
#endif // CLIENT_LINUX_MINIDUMP_WRITER_DIRECTORY_READER_H_
|
@ -0,0 +1,77 @@
|
|||||||
|
// Copyright (c) 2009, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include "client/linux/minidump_writer/directory_reader.h"
|
||||||
|
#include "breakpad_googletest_includes.h"
|
||||||
|
|
||||||
|
using namespace google_breakpad;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
typedef testing::Test DirectoryReaderTest;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DirectoryReaderTest, CompareResults) {
|
||||||
|
std::set<std::string> dent_set;
|
||||||
|
|
||||||
|
DIR *const dir = opendir("/proc/self");
|
||||||
|
ASSERT_TRUE(dir != NULL);
|
||||||
|
|
||||||
|
struct dirent* dent;
|
||||||
|
while ((dent = readdir(dir)))
|
||||||
|
dent_set.insert(dent->d_name);
|
||||||
|
|
||||||
|
closedir(dir);
|
||||||
|
|
||||||
|
const int fd = open("/proc/self", O_DIRECTORY | O_RDONLY);
|
||||||
|
ASSERT_GE(fd, 0);
|
||||||
|
|
||||||
|
DirectoryReader dir_reader(fd);
|
||||||
|
unsigned seen = 0;
|
||||||
|
|
||||||
|
const char* name;
|
||||||
|
while (dir_reader.GetNextEntry(&name)) {
|
||||||
|
ASSERT_TRUE(dent_set.find(name) != dent_set.end());
|
||||||
|
seen++;
|
||||||
|
dir_reader.PopEntry();
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_TRUE(dent_set.find("status") != dent_set.end());
|
||||||
|
ASSERT_TRUE(dent_set.find("stat") != dent_set.end());
|
||||||
|
ASSERT_TRUE(dent_set.find("cmdline") != dent_set.end());
|
||||||
|
|
||||||
|
ASSERT_EQ(dent_set.size(), seen);
|
||||||
|
close(fd);
|
||||||
|
}
|
130
src/client/linux/minidump_writer/line_reader.h
Normal file
130
src/client/linux/minidump_writer/line_reader.h
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
// Copyright (c) 2009, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
#ifndef CLIENT_LINUX_MINIDUMP_WRITER_LINE_READER_H_
|
||||||
|
#define CLIENT_LINUX_MINIDUMP_WRITER_LINE_READER_H_
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "common/linux/linux_syscall_support.h"
|
||||||
|
|
||||||
|
namespace google_breakpad {
|
||||||
|
|
||||||
|
// A class for reading a file, line by line, without using fopen/fgets or other
|
||||||
|
// functions which may allocate memory.
|
||||||
|
class LineReader {
|
||||||
|
public:
|
||||||
|
LineReader(int fd)
|
||||||
|
: fd_(fd),
|
||||||
|
hit_eof_(false),
|
||||||
|
buf_used_(0) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// The maximum length of a line.
|
||||||
|
static const size_t kMaxLineLen = 512;
|
||||||
|
|
||||||
|
// Return the next line from the file.
|
||||||
|
// line: (output) a pointer to the start of the line. The line is NUL
|
||||||
|
// terminated.
|
||||||
|
// len: (output) the length of the line (not inc the NUL byte)
|
||||||
|
//
|
||||||
|
// Returns true iff successful (false on EOF).
|
||||||
|
//
|
||||||
|
// One must call |PopLine| after this function, otherwise you'll continue to
|
||||||
|
// get the same line over and over.
|
||||||
|
bool GetNextLine(const char **line, unsigned *len) {
|
||||||
|
for (;;) {
|
||||||
|
if (buf_used_ == 0 && hit_eof_)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < buf_used_; ++i) {
|
||||||
|
if (buf_[i] == '\n' || buf_[i] == 0) {
|
||||||
|
buf_[i] = 0;
|
||||||
|
*len = i;
|
||||||
|
*line = buf_;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buf_used_ == sizeof(buf_)) {
|
||||||
|
// we scanned the whole buffer and didn't find an end-of-line marker.
|
||||||
|
// This line is too long to process.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We didn't find any end-of-line terminators in the buffer. However, if
|
||||||
|
// this is the last line in the file it might not have one:
|
||||||
|
if (hit_eof_) {
|
||||||
|
assert(buf_used_);
|
||||||
|
// There's room for the NUL because of the buf_used_ == sizeof(buf_)
|
||||||
|
// check above.
|
||||||
|
buf_[buf_used_] = 0;
|
||||||
|
*len = buf_used_;
|
||||||
|
buf_used_ += 1; // since we appended the NUL.
|
||||||
|
*line = buf_;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, we should pull in more data from the file
|
||||||
|
const ssize_t n = sys_read(fd_, buf_ + buf_used_,
|
||||||
|
sizeof(buf_) - buf_used_);
|
||||||
|
if (n < 0) {
|
||||||
|
return false;
|
||||||
|
} else if (n == 0) {
|
||||||
|
hit_eof_ = true;
|
||||||
|
} else {
|
||||||
|
buf_used_ += n;
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point, we have either set the hit_eof_ flag, or we have more
|
||||||
|
// data to process...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PopLine(unsigned len) {
|
||||||
|
// len doesn't include the NUL byte at the end.
|
||||||
|
|
||||||
|
assert(buf_used_ >= len + 1);
|
||||||
|
buf_used_ -= len + 1;
|
||||||
|
memmove(buf_, buf_ + len + 1, buf_used_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const int fd_;
|
||||||
|
|
||||||
|
bool hit_eof_;
|
||||||
|
unsigned buf_used_;
|
||||||
|
char buf_[kMaxLineLen];
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace google_breakpad
|
||||||
|
|
||||||
|
#endif // CLIENT_LINUX_MINIDUMP_WRITER_LINE_READER_H_
|
184
src/client/linux/minidump_writer/line_reader_unittest.cc
Normal file
184
src/client/linux/minidump_writer/line_reader_unittest.cc
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
// Copyright (c) 2009, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include "client/linux/minidump_writer/line_reader.h"
|
||||||
|
#include "breakpad_googletest_includes.h"
|
||||||
|
|
||||||
|
using namespace google_breakpad;
|
||||||
|
|
||||||
|
static int TemporaryFile() {
|
||||||
|
static const char templ[] = "/tmp/line-reader-unittest-XXXXXX";
|
||||||
|
char templ_copy[sizeof(templ)];
|
||||||
|
memcpy(templ_copy, templ, sizeof(templ));
|
||||||
|
const int fd = mkstemp(templ_copy);
|
||||||
|
if (fd >= 0)
|
||||||
|
unlink(templ_copy);
|
||||||
|
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
typedef testing::Test LineReaderTest;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LineReaderTest, EmptyFile) {
|
||||||
|
const int fd = TemporaryFile();
|
||||||
|
LineReader reader(fd);
|
||||||
|
|
||||||
|
const char *line;
|
||||||
|
unsigned len;
|
||||||
|
ASSERT_FALSE(reader.GetNextLine(&line, &len));
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LineReaderTest, OneLineTerminated) {
|
||||||
|
const int fd = TemporaryFile();
|
||||||
|
write(fd, "a\n", 2);
|
||||||
|
lseek(fd, 0, SEEK_SET);
|
||||||
|
LineReader reader(fd);
|
||||||
|
|
||||||
|
const char *line;
|
||||||
|
unsigned len;
|
||||||
|
ASSERT_TRUE(reader.GetNextLine(&line, &len));
|
||||||
|
ASSERT_EQ(len, 1);
|
||||||
|
ASSERT_EQ(line[0], 'a');
|
||||||
|
ASSERT_EQ(line[1], 0);
|
||||||
|
reader.PopLine(len);
|
||||||
|
|
||||||
|
ASSERT_FALSE(reader.GetNextLine(&line, &len));
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LineReaderTest, OneLine) {
|
||||||
|
const int fd = TemporaryFile();
|
||||||
|
write(fd, "a", 1);
|
||||||
|
lseek(fd, 0, SEEK_SET);
|
||||||
|
LineReader reader(fd);
|
||||||
|
|
||||||
|
const char *line;
|
||||||
|
unsigned len;
|
||||||
|
ASSERT_TRUE(reader.GetNextLine(&line, &len));
|
||||||
|
ASSERT_EQ(len, 1);
|
||||||
|
ASSERT_EQ(line[0], 'a');
|
||||||
|
ASSERT_EQ(line[1], 0);
|
||||||
|
reader.PopLine(len);
|
||||||
|
|
||||||
|
ASSERT_FALSE(reader.GetNextLine(&line, &len));
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LineReaderTest, TwoLinesTerminated) {
|
||||||
|
const int fd = TemporaryFile();
|
||||||
|
write(fd, "a\nb\n", 4);
|
||||||
|
lseek(fd, 0, SEEK_SET);
|
||||||
|
LineReader reader(fd);
|
||||||
|
|
||||||
|
const char *line;
|
||||||
|
unsigned len;
|
||||||
|
ASSERT_TRUE(reader.GetNextLine(&line, &len));
|
||||||
|
ASSERT_EQ(len, 1);
|
||||||
|
ASSERT_EQ(line[0], 'a');
|
||||||
|
ASSERT_EQ(line[1], 0);
|
||||||
|
reader.PopLine(len);
|
||||||
|
|
||||||
|
ASSERT_TRUE(reader.GetNextLine(&line, &len));
|
||||||
|
ASSERT_EQ(len, 1);
|
||||||
|
ASSERT_EQ(line[0], 'b');
|
||||||
|
ASSERT_EQ(line[1], 0);
|
||||||
|
reader.PopLine(len);
|
||||||
|
|
||||||
|
ASSERT_FALSE(reader.GetNextLine(&line, &len));
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LineReaderTest, TwoLines) {
|
||||||
|
const int fd = TemporaryFile();
|
||||||
|
write(fd, "a\nb", 3);
|
||||||
|
lseek(fd, 0, SEEK_SET);
|
||||||
|
LineReader reader(fd);
|
||||||
|
|
||||||
|
const char *line;
|
||||||
|
unsigned len;
|
||||||
|
ASSERT_TRUE(reader.GetNextLine(&line, &len));
|
||||||
|
ASSERT_EQ(len, 1);
|
||||||
|
ASSERT_EQ(line[0], 'a');
|
||||||
|
ASSERT_EQ(line[1], 0);
|
||||||
|
reader.PopLine(len);
|
||||||
|
|
||||||
|
ASSERT_TRUE(reader.GetNextLine(&line, &len));
|
||||||
|
ASSERT_EQ(len, 1);
|
||||||
|
ASSERT_EQ(line[0], 'b');
|
||||||
|
ASSERT_EQ(line[1], 0);
|
||||||
|
reader.PopLine(len);
|
||||||
|
|
||||||
|
ASSERT_FALSE(reader.GetNextLine(&line, &len));
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LineReaderTest, MaxLength) {
|
||||||
|
const int fd = TemporaryFile();
|
||||||
|
char l[LineReader::kMaxLineLen - 1];
|
||||||
|
memset(l, 'a', sizeof(l));
|
||||||
|
write(fd, l, sizeof(l));
|
||||||
|
lseek(fd, 0, SEEK_SET);
|
||||||
|
LineReader reader(fd);
|
||||||
|
|
||||||
|
const char *line;
|
||||||
|
unsigned len;
|
||||||
|
ASSERT_TRUE(reader.GetNextLine(&line, &len));
|
||||||
|
ASSERT_EQ(len, sizeof(l));
|
||||||
|
ASSERT_TRUE(memcmp(l, line, sizeof(l)) == 0);
|
||||||
|
ASSERT_EQ(line[len], 0);
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LineReaderTest, TooLong) {
|
||||||
|
const int fd = TemporaryFile();
|
||||||
|
char l[LineReader::kMaxLineLen];
|
||||||
|
memset(l, 'a', sizeof(l));
|
||||||
|
write(fd, l, sizeof(l));
|
||||||
|
lseek(fd, 0, SEEK_SET);
|
||||||
|
LineReader reader(fd);
|
||||||
|
|
||||||
|
const char *line;
|
||||||
|
unsigned len;
|
||||||
|
ASSERT_FALSE(reader.GetNextLine(&line, &len));
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
}
|
419
src/client/linux/minidump_writer/linux_dumper.cc
Normal file
419
src/client/linux/minidump_writer/linux_dumper.cc
Normal file
@ -0,0 +1,419 @@
|
|||||||
|
// Copyright (c) 2009, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
// This code deals with the mechanics of getting information about a crashed
|
||||||
|
// process. Since this code may run in a compromised address space, the same
|
||||||
|
// rules apply as detailed at the top of minidump_writer.h: no libc calls and
|
||||||
|
// use the alternative allocator.
|
||||||
|
|
||||||
|
#include "client/linux/minidump_writer/linux_dumper.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/ptrace.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
|
||||||
|
#include "client/linux/minidump_writer/directory_reader.h"
|
||||||
|
#include "client/linux/minidump_writer/line_reader.h"
|
||||||
|
#include "common/linux/linux_libc_support.h"
|
||||||
|
#include "common/linux/linux_syscall_support.h"
|
||||||
|
|
||||||
|
// Suspend a thread by attaching to it.
|
||||||
|
static bool SuspendThread(pid_t pid) {
|
||||||
|
// This may fail if the thread has just died or debugged.
|
||||||
|
errno = 0;
|
||||||
|
if (sys_ptrace(PTRACE_ATTACH, pid, NULL, NULL) != 0 &&
|
||||||
|
errno != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
while (sys_waitpid(pid, NULL, __WALL) < 0) {
|
||||||
|
if (errno != EINTR) {
|
||||||
|
sys_ptrace(PTRACE_DETACH, pid, NULL, NULL);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resume a thread by detaching from it.
|
||||||
|
static bool ResumeThread(pid_t pid) {
|
||||||
|
return sys_ptrace(PTRACE_DETACH, pid, NULL, NULL) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace google_breakpad {
|
||||||
|
|
||||||
|
LinuxDumper::LinuxDumper(int pid)
|
||||||
|
: pid_(pid),
|
||||||
|
threads_suspened_(false),
|
||||||
|
threads_(&allocator_, 8),
|
||||||
|
mappings_(&allocator_) {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LinuxDumper::Init() {
|
||||||
|
return EnumerateThreads(&threads_) &&
|
||||||
|
EnumerateMappings(&mappings_);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LinuxDumper::ThreadsSuspend() {
|
||||||
|
if (threads_suspened_)
|
||||||
|
return true;
|
||||||
|
bool good = true;
|
||||||
|
for (size_t i = 0; i < threads_.size(); ++i)
|
||||||
|
good &= SuspendThread(threads_[i]);
|
||||||
|
threads_suspened_ = true;
|
||||||
|
return good;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LinuxDumper::ThreadsResume() {
|
||||||
|
if (!threads_suspened_)
|
||||||
|
return false;
|
||||||
|
bool good = true;
|
||||||
|
for (size_t i = 0; i < threads_.size(); ++i)
|
||||||
|
good &= ResumeThread(threads_[i]);
|
||||||
|
threads_suspened_ = false;
|
||||||
|
return good;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
LinuxDumper::BuildProcPath(char* path, pid_t pid, const char* node) const {
|
||||||
|
assert(path);
|
||||||
|
if (!path) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
path[0] = '\0';
|
||||||
|
|
||||||
|
const unsigned pid_len = my_int_len(pid);
|
||||||
|
|
||||||
|
assert(node);
|
||||||
|
if (!node) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t node_len = my_strlen(node);
|
||||||
|
assert(node_len < NAME_MAX);
|
||||||
|
if (node_len >= NAME_MAX) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(node_len > 0);
|
||||||
|
if (node_len == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(pid > 0);
|
||||||
|
if (pid <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t total_length = 6 + pid_len + 1 + node_len;
|
||||||
|
|
||||||
|
assert(total_length < NAME_MAX);
|
||||||
|
if (total_length >= NAME_MAX) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(path, "/proc/", 6);
|
||||||
|
my_itos(path + 6, pid, pid_len);
|
||||||
|
memcpy(path + 6 + pid_len, "/", 1);
|
||||||
|
memcpy(path + 6 + pid_len + 1, node, node_len);
|
||||||
|
memcpy(path + total_length, "\0", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void*
|
||||||
|
LinuxDumper::FindBeginningOfLinuxGateSharedLibrary(const pid_t pid) const {
|
||||||
|
char auxv_path[80];
|
||||||
|
BuildProcPath(auxv_path, pid, "auxv");
|
||||||
|
|
||||||
|
// If BuildProcPath errors out due to invalid input, we'll handle it when
|
||||||
|
// we try to sys_open the file.
|
||||||
|
|
||||||
|
// Find the AT_SYSINFO_EHDR entry for linux-gate.so
|
||||||
|
// See http://www.trilithium.com/johan/2005/08/linux-gate/ for more
|
||||||
|
// information.
|
||||||
|
int fd = sys_open(auxv_path, O_RDONLY, 0);
|
||||||
|
if (fd < 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
elf_aux_entry one_aux_entry;
|
||||||
|
while (sys_read(fd,
|
||||||
|
&one_aux_entry,
|
||||||
|
sizeof(elf_aux_entry)) == sizeof(elf_aux_entry) &&
|
||||||
|
one_aux_entry.a_type != AT_NULL) {
|
||||||
|
if (one_aux_entry.a_type == AT_SYSINFO_EHDR) {
|
||||||
|
close(fd);
|
||||||
|
return reinterpret_cast<void*>(one_aux_entry.a_un.a_val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(fd);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
LinuxDumper::EnumerateMappings(wasteful_vector<MappingInfo*>* result) const {
|
||||||
|
char maps_path[80];
|
||||||
|
BuildProcPath(maps_path, pid_, "maps");
|
||||||
|
|
||||||
|
// linux_gate_loc is the beginning of the kernel's mapping of
|
||||||
|
// linux-gate.so in the process. It doesn't actually show up in the
|
||||||
|
// maps list as a filename, so we use the aux vector to find it's
|
||||||
|
// load location and special case it's entry when creating the list
|
||||||
|
// of mappings.
|
||||||
|
const void* linux_gate_loc;
|
||||||
|
linux_gate_loc = FindBeginningOfLinuxGateSharedLibrary(pid_);
|
||||||
|
|
||||||
|
const int fd = sys_open(maps_path, O_RDONLY, 0);
|
||||||
|
if (fd < 0)
|
||||||
|
return false;
|
||||||
|
LineReader* const line_reader = new(allocator_) LineReader(fd);
|
||||||
|
|
||||||
|
const char* line;
|
||||||
|
unsigned line_len;
|
||||||
|
while (line_reader->GetNextLine(&line, &line_len)) {
|
||||||
|
uintptr_t start_addr, end_addr, offset;
|
||||||
|
|
||||||
|
const char* i1 = my_read_hex_ptr(&start_addr, line);
|
||||||
|
if (*i1 == '-') {
|
||||||
|
const char* i2 = my_read_hex_ptr(&end_addr, i1 + 1);
|
||||||
|
if (*i2 == ' ') {
|
||||||
|
const char* i3 = my_read_hex_ptr(&offset, i2 + 6 /* skip ' rwxp ' */);
|
||||||
|
if (*i3 == ' ') {
|
||||||
|
MappingInfo* const module = new(allocator_) MappingInfo;
|
||||||
|
memset(module, 0, sizeof(MappingInfo));
|
||||||
|
module->start_addr = start_addr;
|
||||||
|
module->size = end_addr - start_addr;
|
||||||
|
module->offset = offset;
|
||||||
|
const char* name = NULL;
|
||||||
|
// Only copy name if the name is a valid path name, or if
|
||||||
|
// we've found the VDSO image
|
||||||
|
if ((name = my_strchr(line, '/')) != NULL) {
|
||||||
|
const unsigned l = my_strlen(name);
|
||||||
|
if (l < sizeof(module->name))
|
||||||
|
memcpy(module->name, name, l);
|
||||||
|
} else if (linux_gate_loc &&
|
||||||
|
reinterpret_cast<void*>(module->start_addr) ==
|
||||||
|
linux_gate_loc) {
|
||||||
|
memcpy(module->name,
|
||||||
|
kLinuxGateLibraryName,
|
||||||
|
my_strlen(kLinuxGateLibraryName));
|
||||||
|
module->offset = 0;
|
||||||
|
}
|
||||||
|
result->push_back(module);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
line_reader->PopLine(line_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
sys_close(fd);
|
||||||
|
|
||||||
|
return result->size() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse /proc/$pid/task to list all the threads of the process identified by
|
||||||
|
// pid.
|
||||||
|
bool LinuxDumper::EnumerateThreads(wasteful_vector<pid_t>* result) const {
|
||||||
|
char task_path[80];
|
||||||
|
BuildProcPath(task_path, pid_, "task");
|
||||||
|
|
||||||
|
const int fd = sys_open(task_path, O_RDONLY | O_DIRECTORY, 0);
|
||||||
|
if (fd < 0)
|
||||||
|
return false;
|
||||||
|
DirectoryReader* dir_reader = new(allocator_) DirectoryReader(fd);
|
||||||
|
|
||||||
|
// The directory may contain duplicate entries which we filter by assuming
|
||||||
|
// that they are consecutive.
|
||||||
|
int last_tid = -1;
|
||||||
|
const char* dent_name;
|
||||||
|
while (dir_reader->GetNextEntry(&dent_name)) {
|
||||||
|
if (my_strcmp(dent_name, ".") &&
|
||||||
|
my_strcmp(dent_name, "..")) {
|
||||||
|
int tid = 0;
|
||||||
|
if (my_strtoui(&tid, dent_name) &&
|
||||||
|
last_tid != tid) {
|
||||||
|
last_tid = tid;
|
||||||
|
result->push_back(tid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dir_reader->PopEntry();
|
||||||
|
}
|
||||||
|
|
||||||
|
sys_close(fd);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read thread info from /proc/$pid/status.
|
||||||
|
// Fill out the |tgid|, |ppid| and |pid| members of |info|. If unavailible,
|
||||||
|
// these members are set to -1. Returns true iff all three members are
|
||||||
|
// availible.
|
||||||
|
bool LinuxDumper::ThreadInfoGet(pid_t tid, ThreadInfo* info) {
|
||||||
|
assert(info != NULL);
|
||||||
|
char status_path[80];
|
||||||
|
BuildProcPath(status_path, tid, "status");
|
||||||
|
|
||||||
|
const int fd = open(status_path, O_RDONLY);
|
||||||
|
if (fd < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
LineReader* const line_reader = new(allocator_) LineReader(fd);
|
||||||
|
const char* line;
|
||||||
|
unsigned line_len;
|
||||||
|
|
||||||
|
info->ppid = info->tgid = -1;
|
||||||
|
|
||||||
|
while (line_reader->GetNextLine(&line, &line_len)) {
|
||||||
|
if (my_strncmp("Tgid:\t", line, 6) == 0) {
|
||||||
|
my_strtoui(&info->tgid, line + 6);
|
||||||
|
} else if (my_strncmp("PPid:\t", line, 6) == 0) {
|
||||||
|
my_strtoui(&info->ppid, line + 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
line_reader->PopLine(line_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info->ppid == -1 || info->tgid == -1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (sys_ptrace(PTRACE_GETREGS, tid, NULL, &info->regs) == -1 ||
|
||||||
|
sys_ptrace(PTRACE_GETFPREGS, tid, NULL, &info->fpregs) == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(__i386) || defined(__x86_64)
|
||||||
|
if (sys_ptrace(PTRACE_GETFPXREGS, tid, NULL, &info->fpxregs) == -1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < ThreadInfo::kNumDebugRegisters; ++i) {
|
||||||
|
if (sys_ptrace(
|
||||||
|
PTRACE_PEEKUSER, tid,
|
||||||
|
reinterpret_cast<void*> (offsetof(struct user,
|
||||||
|
u_debugreg[0]) + i *
|
||||||
|
sizeof(debugreg_t)),
|
||||||
|
&info->dregs[i]) == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const uint8_t* stack_pointer;
|
||||||
|
#if defined(__i386)
|
||||||
|
memcpy(&stack_pointer, &info->regs.esp, sizeof(info->regs.esp));
|
||||||
|
#elif defined(__x86_64)
|
||||||
|
memcpy(&stack_pointer, &info->regs.rsp, sizeof(info->regs.rsp));
|
||||||
|
#else
|
||||||
|
#error "This code hasn't been ported to your platform yet."
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!GetStackInfo(&info->stack, &info->stack_len,
|
||||||
|
(uintptr_t) stack_pointer))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get information about the stack, given the stack pointer. We don't try to
|
||||||
|
// walk the stack since we might not have all the information needed to do
|
||||||
|
// unwind. So we just grab, up to, 32k of stack.
|
||||||
|
bool LinuxDumper::GetStackInfo(const void** stack, size_t* stack_len,
|
||||||
|
uintptr_t int_stack_pointer) {
|
||||||
|
#if defined(__i386) || defined(__x86_64)
|
||||||
|
static const bool stack_grows_down = true;
|
||||||
|
static const uintptr_t page_size = 4096;
|
||||||
|
#else
|
||||||
|
#error "This code has not been ported to your platform yet."
|
||||||
|
#endif
|
||||||
|
// Move the stack pointer to the bottom of the page that it's in.
|
||||||
|
uint8_t* const stack_pointer =
|
||||||
|
reinterpret_cast<uint8_t*>(int_stack_pointer & ~(page_size - 1));
|
||||||
|
|
||||||
|
// The number of bytes of stack which we try to capture.
|
||||||
|
static unsigned kStackToCapture = 32 * 1024;
|
||||||
|
|
||||||
|
const MappingInfo* mapping = FindMapping(stack_pointer);
|
||||||
|
if (!mapping)
|
||||||
|
return false;
|
||||||
|
if (stack_grows_down) {
|
||||||
|
const ptrdiff_t offset = stack_pointer - (uint8_t*) mapping->start_addr;
|
||||||
|
const ptrdiff_t distance_to_end =
|
||||||
|
static_cast<ptrdiff_t>(mapping->size) - offset;
|
||||||
|
*stack_len = distance_to_end > kStackToCapture ?
|
||||||
|
kStackToCapture : distance_to_end;
|
||||||
|
*stack = stack_pointer;
|
||||||
|
} else {
|
||||||
|
const ptrdiff_t offset = stack_pointer - (uint8_t*) mapping->start_addr;
|
||||||
|
*stack_len = offset > kStackToCapture ? kStackToCapture : offset;
|
||||||
|
*stack = stack_pointer - *stack_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
void LinuxDumper::CopyFromProcess(void* dest, pid_t child, const void* src,
|
||||||
|
size_t length) {
|
||||||
|
unsigned long tmp;
|
||||||
|
size_t done = 0;
|
||||||
|
static const size_t word_size = sizeof(tmp);
|
||||||
|
uint8_t* const local = (uint8_t*) dest;
|
||||||
|
uint8_t* const remote = (uint8_t*) src;
|
||||||
|
|
||||||
|
while (done < length) {
|
||||||
|
const size_t l = length - done > word_size ? word_size : length - done;
|
||||||
|
if (sys_ptrace(PTRACE_PEEKDATA, child, remote + done, &tmp) == -1)
|
||||||
|
tmp = 0;
|
||||||
|
memcpy(local + done, &tmp, l);
|
||||||
|
done += l;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the mapping which the given memory address falls in.
|
||||||
|
const MappingInfo* LinuxDumper::FindMapping(const void* address) const {
|
||||||
|
const uintptr_t addr = (uintptr_t) address;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < mappings_.size(); ++i) {
|
||||||
|
const uintptr_t start = static_cast<uintptr_t>(mappings_[i]->start_addr);
|
||||||
|
if (addr >= start && addr - start < mappings_[i]->size)
|
||||||
|
return mappings_[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace google_breakpad
|
142
src/client/linux/minidump_writer/linux_dumper.h
Normal file
142
src/client/linux/minidump_writer/linux_dumper.h
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
// Copyright (c) 2009, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
#ifndef CLIENT_LINUX_MINIDUMP_WRITER_LINUX_DUMPER_H_
|
||||||
|
#define CLIENT_LINUX_MINIDUMP_WRITER_LINUX_DUMPER_H_
|
||||||
|
|
||||||
|
#include <elf.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <sys/user.h>
|
||||||
|
#include <linux/limits.h>
|
||||||
|
|
||||||
|
#include "common/linux/memory.h"
|
||||||
|
|
||||||
|
namespace google_breakpad {
|
||||||
|
|
||||||
|
typedef typeof(((struct user*) 0)->u_debugreg[0]) debugreg_t;
|
||||||
|
|
||||||
|
// Typedef for our parsing of the auxv variables in /proc/pid/auxv.
|
||||||
|
#if defined(__i386)
|
||||||
|
typedef Elf32_auxv_t elf_aux_entry;
|
||||||
|
#elif defined(__x86_64__)
|
||||||
|
typedef Elf64_auxv_t elf_aux_entry;
|
||||||
|
#endif
|
||||||
|
// When we find the VDSO mapping in the process's address space, this
|
||||||
|
// is the name we use for it when writing it to the minidump.
|
||||||
|
// This should always be less than NAME_MAX!
|
||||||
|
const char kLinuxGateLibraryName[] = "linux-gate.so";
|
||||||
|
|
||||||
|
// We produce one of these structures for each thread in the crashed process.
|
||||||
|
struct ThreadInfo {
|
||||||
|
pid_t tgid; // thread group id
|
||||||
|
pid_t ppid; // parent process
|
||||||
|
|
||||||
|
// Even on platforms where the stack grows down, the following will point to
|
||||||
|
// the smallest address in the stack.
|
||||||
|
const void* stack; // pointer to the stack area
|
||||||
|
size_t stack_len; // length of the stack to copy
|
||||||
|
|
||||||
|
user_regs_struct regs;
|
||||||
|
user_fpregs_struct fpregs;
|
||||||
|
#if defined(__i386) || defined(__x86_64)
|
||||||
|
user_fpxregs_struct fpxregs;
|
||||||
|
|
||||||
|
static const unsigned kNumDebugRegisters = 8;
|
||||||
|
debugreg_t dregs[8];
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
// One of these is produced for each mapping in the process (i.e. line in
|
||||||
|
// /proc/$x/maps).
|
||||||
|
struct MappingInfo {
|
||||||
|
uintptr_t start_addr;
|
||||||
|
size_t size;
|
||||||
|
size_t offset; // offset into the backed file.
|
||||||
|
char name[NAME_MAX];
|
||||||
|
};
|
||||||
|
|
||||||
|
class LinuxDumper {
|
||||||
|
public:
|
||||||
|
explicit LinuxDumper(pid_t pid);
|
||||||
|
|
||||||
|
// Parse the data for |threads| and |mappings|.
|
||||||
|
bool Init();
|
||||||
|
|
||||||
|
// Suspend/resume all threads in the given process.
|
||||||
|
bool ThreadsSuspend();
|
||||||
|
bool ThreadsResume();
|
||||||
|
|
||||||
|
// Read information about the given thread. Returns true on success. One must
|
||||||
|
// have called |ThreadsSuspend| first.
|
||||||
|
bool ThreadInfoGet(pid_t tid, ThreadInfo* info);
|
||||||
|
|
||||||
|
// These are only valid after a call to |Init|.
|
||||||
|
const wasteful_vector<pid_t> &threads() { return threads_; }
|
||||||
|
const wasteful_vector<MappingInfo*> &mappings() { return mappings_; }
|
||||||
|
const MappingInfo* FindMapping(const void* address) const;
|
||||||
|
|
||||||
|
// Find a block of memory to take as the stack given the top of stack pointer.
|
||||||
|
// stack: (output) the lowest address in the memory area
|
||||||
|
// stack_len: (output) the length of the memory area
|
||||||
|
// stack_top: the current top of the stack
|
||||||
|
bool GetStackInfo(const void** stack, size_t* stack_len, uintptr_t stack_top);
|
||||||
|
|
||||||
|
PageAllocator* allocator() { return &allocator_; }
|
||||||
|
|
||||||
|
// memcpy from a remote process.
|
||||||
|
static void CopyFromProcess(void* dest, pid_t child, const void* src,
|
||||||
|
size_t length);
|
||||||
|
|
||||||
|
// Builds a proc path for a certain pid for a node. path is a
|
||||||
|
// character array that is overwritten, and node is the final node
|
||||||
|
// without any slashes.
|
||||||
|
void BuildProcPath(char* path, pid_t pid, const char* node) const;
|
||||||
|
|
||||||
|
// Utility method to find the location of where the kernel has
|
||||||
|
// mapped linux-gate.so in memory(shows up in /proc/pid/maps as
|
||||||
|
// [vdso], but we can't guarantee that it's the only virtual dynamic
|
||||||
|
// shared object. Parsing the auxilary vector for AT_SYSINFO_EHDR
|
||||||
|
// is the safest way to go.)
|
||||||
|
void* FindBeginningOfLinuxGateSharedLibrary(const pid_t pid) const;
|
||||||
|
private:
|
||||||
|
bool EnumerateMappings(wasteful_vector<MappingInfo*>* result) const;
|
||||||
|
bool EnumerateThreads(wasteful_vector<pid_t>* result) const;
|
||||||
|
|
||||||
|
const pid_t pid_;
|
||||||
|
|
||||||
|
mutable PageAllocator allocator_;
|
||||||
|
|
||||||
|
bool threads_suspened_;
|
||||||
|
wasteful_vector<pid_t> threads_; // the ids of all the threads
|
||||||
|
wasteful_vector<MappingInfo*> mappings_; // info from /proc/<pid>/maps
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace google_breakpad
|
||||||
|
|
||||||
|
#endif // CLIENT_LINUX_HANDLER_LINUX_DUMPER_H_
|
118
src/client/linux/minidump_writer/linux_dumper_unittest.cc
Normal file
118
src/client/linux/minidump_writer/linux_dumper_unittest.cc
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
// Copyright (c) 2009, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "client/linux/minidump_writer/linux_dumper.h"
|
||||||
|
#include "breakpad_googletest_includes.h"
|
||||||
|
|
||||||
|
using namespace google_breakpad;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
typedef testing::Test LinuxDumperTest;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LinuxDumperTest, Setup) {
|
||||||
|
LinuxDumper dumper(getpid());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LinuxDumperTest, FindMappings) {
|
||||||
|
LinuxDumper dumper(getpid());
|
||||||
|
ASSERT_TRUE(dumper.Init());
|
||||||
|
|
||||||
|
ASSERT_TRUE(dumper.FindMapping(reinterpret_cast<void*>(getpid)));
|
||||||
|
ASSERT_TRUE(dumper.FindMapping(reinterpret_cast<void*>(printf)));
|
||||||
|
ASSERT_FALSE(dumper.FindMapping(NULL));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LinuxDumperTest, ThreadList) {
|
||||||
|
LinuxDumper dumper(getpid());
|
||||||
|
ASSERT_TRUE(dumper.Init());
|
||||||
|
|
||||||
|
ASSERT_GE(dumper.threads().size(), 1);
|
||||||
|
bool found = false;
|
||||||
|
for (size_t i = 0; i < dumper.threads().size(); ++i) {
|
||||||
|
if (dumper.threads()[i] == getpid()) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LinuxDumperTest, BuildProcPath) {
|
||||||
|
const pid_t pid = getpid();
|
||||||
|
LinuxDumper dumper(pid);
|
||||||
|
|
||||||
|
char maps_path[256] = "dummymappath";
|
||||||
|
char maps_path_expected[256];
|
||||||
|
snprintf(maps_path_expected, sizeof(maps_path_expected),
|
||||||
|
"/proc/%d/maps", pid);
|
||||||
|
dumper.BuildProcPath(maps_path, pid, "maps");
|
||||||
|
ASSERT_STREQ(maps_path, maps_path_expected);
|
||||||
|
|
||||||
|
// In release mode, we expect BuildProcPath to handle the invalid
|
||||||
|
// parameters correctly and fill map_path with an empty
|
||||||
|
// NULL-terminated string.
|
||||||
|
#ifdef NDEBUG
|
||||||
|
snprintf(maps_path, sizeof(maps_path), "dummymappath");
|
||||||
|
dumper.BuildProcPath(maps_path, 0, "maps");
|
||||||
|
EXPECT_STREQ(maps_path, "");
|
||||||
|
|
||||||
|
snprintf(maps_path, sizeof(maps_path), "dummymappath");
|
||||||
|
dumper.BuildProcPath(maps_path, getpid(), "");
|
||||||
|
EXPECT_STREQ(maps_path, "");
|
||||||
|
|
||||||
|
snprintf(maps_path, sizeof(maps_path), "dummymappath");
|
||||||
|
dumper.BuildProcPath(maps_path, getpid(), NULL);
|
||||||
|
EXPECT_STREQ(maps_path, "");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LinuxDumperTest, MappingsIncludeLinuxGate) {
|
||||||
|
LinuxDumper dumper(getpid());
|
||||||
|
ASSERT_TRUE(dumper.Init());
|
||||||
|
|
||||||
|
void* linux_gate_loc = dumper.FindBeginningOfLinuxGateSharedLibrary(getpid());
|
||||||
|
if (linux_gate_loc) {
|
||||||
|
bool found_linux_gate = false;
|
||||||
|
|
||||||
|
const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
|
||||||
|
const MappingInfo* mapping;
|
||||||
|
for (unsigned i = 0; i < mappings.size(); ++i) {
|
||||||
|
mapping = mappings[i];
|
||||||
|
if (!strcmp(mapping->name, kLinuxGateLibraryName)) {
|
||||||
|
found_linux_gate = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EXPECT_TRUE(found_linux_gate);
|
||||||
|
EXPECT_EQ(linux_gate_loc, reinterpret_cast<void*>(mapping->start_addr));
|
||||||
|
EXPECT_EQ(0, memcmp(linux_gate_loc, ELFMAG, SELFMAG));
|
||||||
|
}
|
||||||
|
}
|
872
src/client/linux/minidump_writer/minidump_writer.cc
Normal file
872
src/client/linux/minidump_writer/minidump_writer.cc
Normal file
@ -0,0 +1,872 @@
|
|||||||
|
// Copyright (c) 2009, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
// This code writes out minidump files:
|
||||||
|
// http://msdn.microsoft.com/en-us/library/ms680378(VS.85,loband).aspx
|
||||||
|
//
|
||||||
|
// Minidumps are a Microsoft format which Breakpad uses for recording crash
|
||||||
|
// dumps. This code has to run in a compromised environment (the address space
|
||||||
|
// may have received SIGSEGV), thus the following rules apply:
|
||||||
|
// * You may not enter the dynamic linker. This means that we cannot call
|
||||||
|
// any symbols in a shared library (inc libc). Because of this we replace
|
||||||
|
// libc functions in linux_libc_support.h.
|
||||||
|
// * You may not call syscalls via the libc wrappers. This rule is a subset
|
||||||
|
// of the first rule but it bears repeating. We have direct wrappers
|
||||||
|
// around the system calls in linux_syscall_support.h.
|
||||||
|
// * You may not malloc. There's an alternative allocator in memory.h and
|
||||||
|
// a canonical instance in the LinuxDumper object. We use the placement
|
||||||
|
// new form to allocate objects and we don't delete them.
|
||||||
|
|
||||||
|
#include "client/linux/minidump_writer/minidump_writer.h"
|
||||||
|
#include "client/minidump_file_writer-inl.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/ucontext.h>
|
||||||
|
#include <sys/user.h>
|
||||||
|
#include <sys/utsname.h>
|
||||||
|
|
||||||
|
#include "client/minidump_file_writer.h"
|
||||||
|
#include "google_breakpad/common/minidump_format.h"
|
||||||
|
#include "google_breakpad/common/minidump_cpu_amd64.h"
|
||||||
|
#include "google_breakpad/common/minidump_cpu_x86.h"
|
||||||
|
|
||||||
|
#include "client/linux/handler/exception_handler.h"
|
||||||
|
#include "client/linux/minidump_writer/line_reader.h"
|
||||||
|
#include "client/linux/minidump_writer//linux_dumper.h"
|
||||||
|
#include "common/linux/linux_libc_support.h"
|
||||||
|
#include "common/linux/linux_syscall_support.h"
|
||||||
|
|
||||||
|
// These are additional minidump stream values which are specific to the linux
|
||||||
|
// breakpad implementation.
|
||||||
|
enum {
|
||||||
|
MD_LINUX_CPU_INFO = 0x47670003, /* /proc/cpuinfo */
|
||||||
|
MD_LINUX_PROC_STATUS = 0x47670004, /* /proc/$x/status */
|
||||||
|
MD_LINUX_LSB_RELEASE = 0x47670005, /* /etc/lsb-release */
|
||||||
|
MD_LINUX_CMD_LINE = 0x47670006, /* /proc/$x/cmdline */
|
||||||
|
MD_LINUX_ENVIRON = 0x47670007, /* /proc/$x/environ */
|
||||||
|
MD_LINUX_AUXV = 0x47670008, /* /proc/$x/auxv */
|
||||||
|
};
|
||||||
|
|
||||||
|
// Minidump defines register structures which are different from the raw
|
||||||
|
// structures which we get from the kernel. These are platform specific
|
||||||
|
// functions to juggle the ucontext and user structures into minidump format.
|
||||||
|
#if defined(__i386)
|
||||||
|
typedef MDRawContextX86 RawContextCPU;
|
||||||
|
|
||||||
|
// Write a uint16_t to memory
|
||||||
|
// out: memory location to write to
|
||||||
|
// v: value to write.
|
||||||
|
static void U16(void* out, uint16_t v) {
|
||||||
|
memcpy(out, &v, sizeof(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write a uint32_t to memory
|
||||||
|
// out: memory location to write to
|
||||||
|
// v: value to write.
|
||||||
|
static void U32(void* out, uint32_t v) {
|
||||||
|
memcpy(out, &v, sizeof(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Juggle an x86 user_(fp|fpx|)regs_struct into minidump format
|
||||||
|
// out: the minidump structure
|
||||||
|
// info: the collection of register structures.
|
||||||
|
static void CPUFillFromThreadInfo(MDRawContextX86 *out,
|
||||||
|
const google_breakpad::ThreadInfo &info) {
|
||||||
|
out->context_flags = MD_CONTEXT_X86_ALL;
|
||||||
|
|
||||||
|
out->dr0 = info.dregs[0];
|
||||||
|
out->dr1 = info.dregs[1];
|
||||||
|
out->dr2 = info.dregs[2];
|
||||||
|
out->dr3 = info.dregs[3];
|
||||||
|
// 4 and 5 deliberatly omitted because they aren't included in the minidump
|
||||||
|
// format.
|
||||||
|
out->dr6 = info.dregs[6];
|
||||||
|
out->dr7 = info.dregs[7];
|
||||||
|
|
||||||
|
out->gs = info.regs.xgs;
|
||||||
|
out->fs = info.regs.xfs;
|
||||||
|
out->es = info.regs.xes;
|
||||||
|
out->ds = info.regs.xds;
|
||||||
|
|
||||||
|
out->edi = info.regs.edi;
|
||||||
|
out->esi = info.regs.esi;
|
||||||
|
out->ebx = info.regs.ebx;
|
||||||
|
out->edx = info.regs.edx;
|
||||||
|
out->ecx = info.regs.ecx;
|
||||||
|
out->eax = info.regs.eax;
|
||||||
|
|
||||||
|
out->ebp = info.regs.ebp;
|
||||||
|
out->eip = info.regs.eip;
|
||||||
|
out->cs = info.regs.xcs;
|
||||||
|
out->eflags = info.regs.eflags;
|
||||||
|
out->esp = info.regs.esp;
|
||||||
|
out->ss = info.regs.xss;
|
||||||
|
|
||||||
|
out->float_save.control_word = info.fpregs.cwd;
|
||||||
|
out->float_save.status_word = info.fpregs.swd;
|
||||||
|
out->float_save.tag_word = info.fpregs.twd;
|
||||||
|
out->float_save.error_offset = info.fpregs.fip;
|
||||||
|
out->float_save.error_selector = info.fpregs.fcs;
|
||||||
|
out->float_save.data_offset = info.fpregs.foo;
|
||||||
|
out->float_save.data_selector = info.fpregs.fos;
|
||||||
|
|
||||||
|
// 8 registers * 10 bytes per register.
|
||||||
|
memcpy(out->float_save.register_area, info.fpregs.st_space, 10 * 8);
|
||||||
|
|
||||||
|
// This matches the Intel fpsave format.
|
||||||
|
U16(out->extended_registers + 0, info.fpregs.cwd);
|
||||||
|
U16(out->extended_registers + 2, info.fpregs.swd);
|
||||||
|
U16(out->extended_registers + 4, info.fpregs.twd);
|
||||||
|
U16(out->extended_registers + 6, info.fpxregs.fop);
|
||||||
|
U32(out->extended_registers + 8, info.fpxregs.fip);
|
||||||
|
U16(out->extended_registers + 12, info.fpxregs.fcs);
|
||||||
|
U32(out->extended_registers + 16, info.fpregs.foo);
|
||||||
|
U16(out->extended_registers + 20, info.fpregs.fos);
|
||||||
|
U32(out->extended_registers + 24, info.fpxregs.mxcsr);
|
||||||
|
|
||||||
|
memcpy(out->extended_registers + 32, &info.fpxregs.st_space, 128);
|
||||||
|
memcpy(out->extended_registers + 160, &info.fpxregs.xmm_space, 128);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Juggle an x86 ucontext into minidump format
|
||||||
|
// out: the minidump structure
|
||||||
|
// info: the collection of register structures.
|
||||||
|
static void CPUFillFromUContext(MDRawContextX86 *out, const ucontext *uc,
|
||||||
|
const struct _libc_fpstate* fp) {
|
||||||
|
const greg_t* regs = uc->uc_mcontext.gregs;
|
||||||
|
|
||||||
|
out->context_flags = MD_CONTEXT_X86_FULL |
|
||||||
|
MD_CONTEXT_X86_FLOATING_POINT;
|
||||||
|
|
||||||
|
out->gs = regs[REG_GS];
|
||||||
|
out->fs = regs[REG_FS];
|
||||||
|
out->es = regs[REG_ES];
|
||||||
|
out->ds = regs[REG_DS];
|
||||||
|
|
||||||
|
out->edi = regs[REG_EDI];
|
||||||
|
out->esi = regs[REG_ESI];
|
||||||
|
out->ebx = regs[REG_EBX];
|
||||||
|
out->edx = regs[REG_EDX];
|
||||||
|
out->ecx = regs[REG_ECX];
|
||||||
|
out->eax = regs[REG_EAX];
|
||||||
|
|
||||||
|
out->ebp = regs[REG_EBP];
|
||||||
|
out->eip = regs[REG_EIP];
|
||||||
|
out->cs = regs[REG_CS];
|
||||||
|
out->eflags = regs[REG_EFL];
|
||||||
|
out->esp = regs[REG_UESP];
|
||||||
|
out->ss = regs[REG_SS];
|
||||||
|
|
||||||
|
out->float_save.control_word = fp->cw;
|
||||||
|
out->float_save.status_word = fp->sw;
|
||||||
|
out->float_save.tag_word = fp->tag;
|
||||||
|
out->float_save.error_offset = fp->ipoff;
|
||||||
|
out->float_save.error_selector = fp->cssel;
|
||||||
|
out->float_save.data_offset = fp->dataoff;
|
||||||
|
out->float_save.data_selector = fp->datasel;
|
||||||
|
|
||||||
|
// 8 registers * 10 bytes per register.
|
||||||
|
memcpy(out->float_save.register_area, fp->_st, 10 * 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif defined(__x86_64)
|
||||||
|
typedef MDRawContextAMD64 RawContextCPU;
|
||||||
|
|
||||||
|
static void CPUFillFromThreadInfo(MDRawContextAMD64 *out,
|
||||||
|
const google_breakpad::ThreadInfo &info) {
|
||||||
|
out->context_flags = MD_CONTEXT_AMD64_FULL |
|
||||||
|
MD_CONTEXT_AMD64_SEGMENTS;
|
||||||
|
|
||||||
|
out->cs = info.regs.cs;
|
||||||
|
|
||||||
|
out->ds = info.regs.ds;
|
||||||
|
out->es = info.regs.es;
|
||||||
|
out->fs = info.regs.fs;
|
||||||
|
out->gs = info.regs.gs;
|
||||||
|
|
||||||
|
out->ss = info.regs.ss;
|
||||||
|
out->eflags = info.regs.eflags;
|
||||||
|
|
||||||
|
out->dr0 = info.dregs[0];
|
||||||
|
out->dr1 = info.dregs[1];
|
||||||
|
out->dr2 = info.dregs[2];
|
||||||
|
out->dr3 = info.dregs[3];
|
||||||
|
// 4 and 5 deliberatly omitted because they aren't included in the minidump
|
||||||
|
// format.
|
||||||
|
out->dr6 = info.dregs[6];
|
||||||
|
out->dr7 = info.dregs[7];
|
||||||
|
|
||||||
|
out->rax = info.regs.rax;
|
||||||
|
out->rcx = info.regs.rcx;
|
||||||
|
out->rdx = info.regs.rdx;
|
||||||
|
out->rbx = info.regs.rbx;
|
||||||
|
|
||||||
|
out->rsp = info.regs.rsp;
|
||||||
|
|
||||||
|
out->rbp = info.regs.rbp;
|
||||||
|
out->rsi = info.regs.rsi;
|
||||||
|
out->rdi = info.regs.rdi;
|
||||||
|
out->r8 = info.regs.r8;
|
||||||
|
out->r9 = info.regs.r9;
|
||||||
|
out->r10 = info.regs.r10;
|
||||||
|
out->r11 = info.regs.r11;
|
||||||
|
out->r12 = info.regs.r12;
|
||||||
|
out->r13 = info.regs.r13;
|
||||||
|
out->r14 = info.regs.r14;
|
||||||
|
out->r15 = info.regs.r15;
|
||||||
|
|
||||||
|
out->rip = info.regs.rip;
|
||||||
|
|
||||||
|
out->flt_save.control_word = info.fpregs.cwd;
|
||||||
|
out->flt_save.status_word = info.fpregs.swd;
|
||||||
|
out->flt_save.tag_word = info.fpregs.twd;
|
||||||
|
out->flt_save.error_opcode = info.fpregs.fop;
|
||||||
|
out->flt_save.error_offset = info.fpregs.rip;
|
||||||
|
out->flt_save.error_selector = 0; // We don't have this.
|
||||||
|
out->flt_save.data_offset = info.fpregs.rdp;
|
||||||
|
out->flt_save.data_selector = 0; // We don't have this.
|
||||||
|
out->flt_save.mx_csr = info.fpregs.mxcsr;
|
||||||
|
out->flt_save.mx_csr_mask = info.fpregs.mxcsr_mask;
|
||||||
|
memcpy(&out->flt_save.float_registers, &info.fpregs.st_space, 8 * 16);
|
||||||
|
memcpy(&out->flt_save.xmm_registers, &info.fpregs.xmm_space, 16 * 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void CPUFillFromUContext(MDRawContextAMD64 *out, const ucontext *uc,
|
||||||
|
const struct _libc_fpstate* fpregs) {
|
||||||
|
const greg_t* regs = uc->gregs;
|
||||||
|
|
||||||
|
out->context_flags = MD_CONTEXT_AMD64_FULL;
|
||||||
|
|
||||||
|
out->cs = regs[REG_CSGSFS] & 0xffff;
|
||||||
|
|
||||||
|
out->fs = (regs[REG_CSGSFS] >> 32) & 0xffff;
|
||||||
|
out->gs = (regs[REG_CSGSFS] >> 16) & 0xffff;
|
||||||
|
|
||||||
|
out->eflags = regs[REG_EFL];
|
||||||
|
|
||||||
|
out->rax = regs[REG_RAX];
|
||||||
|
out->rcx = regs[REG_RCX];
|
||||||
|
out->rdx = regs[REG_RDX];
|
||||||
|
out->rbx = regs[REG_RBX];
|
||||||
|
|
||||||
|
out->rsp = regs[REG_RSP];
|
||||||
|
out->rbp = regs[REG_RBP];
|
||||||
|
out->rsi = regs[REG_RSI];
|
||||||
|
out->rdi = regs[REG_RDI];
|
||||||
|
out->r8 = regs[REG_R8];
|
||||||
|
out->r9 = regs[REG_R9];
|
||||||
|
out->r10 = regs[REG_R10];
|
||||||
|
out->r11 = regs[REG_R11];
|
||||||
|
out->r12 = regs[REG_R12];
|
||||||
|
out->r13 = regs[REG_R13];
|
||||||
|
out->r14 = regs[REG_R14];
|
||||||
|
out->r15 = regs[REG_R15];
|
||||||
|
|
||||||
|
out->rip = regs[REG_RIP];
|
||||||
|
|
||||||
|
out->flt_save.control_word = fpregs->cwd;
|
||||||
|
out->flt_save.status_word = fpregs->swd;
|
||||||
|
out->flt_save.tag_word = fpregs->ftw;
|
||||||
|
out->flt_save.error_opcode = fpregs->fop;
|
||||||
|
out->flt_save.error_offset = fpregs->rip;
|
||||||
|
out->flt_save.data_offset = fpregs->rdp;
|
||||||
|
out->flt_save.error_selector = 0; // We don't have this.
|
||||||
|
out->flt_save.data_selector = 0; // We don't have this.
|
||||||
|
out->flt_save.mx_csr = fpregs->mxcsr;
|
||||||
|
out->flt_save.mx_csr_mask = fpregs->mxcsr_mask;
|
||||||
|
memcpy(&out->flt_save.float_registers, &fpregs->_st, 8 * 16);
|
||||||
|
memcpy(&out->flt_save.xmm_registers, &fpregs->_xmm, 16 * 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
#error "This code has not been ported to your platform yet."
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace google_breakpad {
|
||||||
|
|
||||||
|
class MinidumpWriter {
|
||||||
|
public:
|
||||||
|
MinidumpWriter(const char* filename,
|
||||||
|
pid_t crashing_pid,
|
||||||
|
const ExceptionHandler::CrashContext* context)
|
||||||
|
: filename_(filename),
|
||||||
|
siginfo_(&context->siginfo),
|
||||||
|
ucontext_(&context->context),
|
||||||
|
float_state_(&context->float_state),
|
||||||
|
crashing_tid_(context->tid),
|
||||||
|
dumper_(crashing_pid) {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Init() {
|
||||||
|
return dumper_.Init() && minidump_writer_.Open(filename_) &&
|
||||||
|
dumper_.ThreadsSuspend();
|
||||||
|
}
|
||||||
|
|
||||||
|
~MinidumpWriter() {
|
||||||
|
minidump_writer_.Close();
|
||||||
|
dumper_.ThreadsResume();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Dump() {
|
||||||
|
// A minidump file contains a number of tagged streams. This is the number
|
||||||
|
// of stream which we write.
|
||||||
|
static const unsigned kNumWriters = 11;
|
||||||
|
|
||||||
|
TypedMDRVA<MDRawHeader> header(&minidump_writer_);
|
||||||
|
TypedMDRVA<MDRawDirectory> dir(&minidump_writer_);
|
||||||
|
if (!header.Allocate())
|
||||||
|
return false;
|
||||||
|
if (!dir.AllocateArray(kNumWriters))
|
||||||
|
return false;
|
||||||
|
memset(header.get(), 0, sizeof(MDRawHeader));
|
||||||
|
|
||||||
|
header.get()->signature = MD_HEADER_SIGNATURE;
|
||||||
|
header.get()->version = MD_HEADER_VERSION;
|
||||||
|
header.get()->time_date_stamp = time(NULL);
|
||||||
|
header.get()->stream_count = kNumWriters;
|
||||||
|
header.get()->stream_directory_rva = dir.position();
|
||||||
|
|
||||||
|
unsigned dir_index = 0;
|
||||||
|
MDRawDirectory dirent;
|
||||||
|
|
||||||
|
if (!WriteThreadListStream(&dirent))
|
||||||
|
return false;
|
||||||
|
dir.CopyIndex(dir_index++, &dirent);
|
||||||
|
|
||||||
|
if (!WriteMappings(&dirent))
|
||||||
|
return false;
|
||||||
|
dir.CopyIndex(dir_index++, &dirent);
|
||||||
|
|
||||||
|
if (!WriteExceptionStream(&dirent))
|
||||||
|
return false;
|
||||||
|
dir.CopyIndex(dir_index++, &dirent);
|
||||||
|
|
||||||
|
if (!WriteSystemInfoStream(&dirent))
|
||||||
|
return false;
|
||||||
|
dir.CopyIndex(dir_index++, &dirent);
|
||||||
|
|
||||||
|
dirent.stream_type = MD_LINUX_CPU_INFO;
|
||||||
|
if (!WriteFile(&dirent.location, "/proc/cpuinfo"))
|
||||||
|
NullifyDirectoryEntry(&dirent);
|
||||||
|
dir.CopyIndex(dir_index++, &dirent);
|
||||||
|
|
||||||
|
dirent.stream_type = MD_LINUX_PROC_STATUS;
|
||||||
|
if (!WriteProcFile(&dirent.location, crashing_tid_, "status"))
|
||||||
|
NullifyDirectoryEntry(&dirent);
|
||||||
|
dir.CopyIndex(dir_index++, &dirent);
|
||||||
|
|
||||||
|
dirent.stream_type = MD_LINUX_LSB_RELEASE;
|
||||||
|
if (!WriteFile(&dirent.location, "/etc/lsb-release"))
|
||||||
|
NullifyDirectoryEntry(&dirent);
|
||||||
|
dir.CopyIndex(dir_index++, &dirent);
|
||||||
|
|
||||||
|
dirent.stream_type = MD_LINUX_CMD_LINE;
|
||||||
|
if (!WriteProcFile(&dirent.location, crashing_tid_, "cmdline"))
|
||||||
|
NullifyDirectoryEntry(&dirent);
|
||||||
|
dir.CopyIndex(dir_index++, &dirent);
|
||||||
|
|
||||||
|
dirent.stream_type = MD_LINUX_ENVIRON;
|
||||||
|
if (!WriteProcFile(&dirent.location, crashing_tid_, "environ"))
|
||||||
|
NullifyDirectoryEntry(&dirent);
|
||||||
|
dir.CopyIndex(dir_index++, &dirent);
|
||||||
|
|
||||||
|
dirent.stream_type = MD_LINUX_AUXV;
|
||||||
|
if (!WriteProcFile(&dirent.location, crashing_tid_, "auxv"))
|
||||||
|
NullifyDirectoryEntry(&dirent);
|
||||||
|
dir.CopyIndex(dir_index++, &dirent);
|
||||||
|
|
||||||
|
dirent.stream_type = MD_LINUX_AUXV;
|
||||||
|
if (!WriteProcFile(&dirent.location, crashing_tid_, "maps"))
|
||||||
|
NullifyDirectoryEntry(&dirent);
|
||||||
|
dir.CopyIndex(dir_index++, &dirent);
|
||||||
|
|
||||||
|
// If you add more directory entries, don't forget to update kNumWriters,
|
||||||
|
// above.
|
||||||
|
|
||||||
|
dumper_.ThreadsResume();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write information about the threads.
|
||||||
|
bool WriteThreadListStream(MDRawDirectory* dirent) {
|
||||||
|
const unsigned num_threads = dumper_.threads().size();
|
||||||
|
|
||||||
|
TypedMDRVA<uint32_t> list(&minidump_writer_);
|
||||||
|
if (!list.AllocateObjectAndArray(num_threads, sizeof(MDRawThread)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
dirent->stream_type = MD_THREAD_LIST_STREAM;
|
||||||
|
dirent->location = list.location();
|
||||||
|
|
||||||
|
*list.get() = num_threads;
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < num_threads; ++i) {
|
||||||
|
MDRawThread thread;
|
||||||
|
my_memset(&thread, 0, sizeof(thread));
|
||||||
|
thread.thread_id = dumper_.threads()[i];
|
||||||
|
// We have a different source of information for the crashing thread. If
|
||||||
|
// we used the actual state of the thread we would find it running in the
|
||||||
|
// signal handler with the alternative stack, which would be deeply
|
||||||
|
// unhelpful.
|
||||||
|
if (thread.thread_id == crashing_tid_) {
|
||||||
|
const void* stack;
|
||||||
|
size_t stack_len;
|
||||||
|
if (!dumper_.GetStackInfo(&stack, &stack_len, GetStackPointer()))
|
||||||
|
return false;
|
||||||
|
UntypedMDRVA memory(&minidump_writer_);
|
||||||
|
if (!memory.Allocate(stack_len))
|
||||||
|
return false;
|
||||||
|
uint8_t* stack_copy = (uint8_t*) dumper_.allocator()->Alloc(stack_len);
|
||||||
|
dumper_.CopyFromProcess(stack_copy, thread.thread_id, stack, stack_len);
|
||||||
|
memory.Copy(stack_copy, stack_len);
|
||||||
|
thread.stack.start_of_memory_range = (uintptr_t) (stack);
|
||||||
|
thread.stack.memory = memory.location();
|
||||||
|
TypedMDRVA<RawContextCPU> cpu(&minidump_writer_);
|
||||||
|
if (!cpu.Allocate())
|
||||||
|
return false;
|
||||||
|
my_memset(cpu.get(), 0, sizeof(RawContextCPU));
|
||||||
|
CPUFillFromUContext(cpu.get(), ucontext_, float_state_);
|
||||||
|
thread.thread_context = cpu.location();
|
||||||
|
crashing_thread_context_ = cpu.location();
|
||||||
|
} else {
|
||||||
|
ThreadInfo info;
|
||||||
|
if (!dumper_.ThreadInfoGet(dumper_.threads()[i], &info))
|
||||||
|
return false;
|
||||||
|
UntypedMDRVA memory(&minidump_writer_);
|
||||||
|
if (!memory.Allocate(info.stack_len))
|
||||||
|
return false;
|
||||||
|
uint8_t* stack_copy =
|
||||||
|
(uint8_t*) dumper_.allocator()->Alloc(info.stack_len);
|
||||||
|
dumper_.CopyFromProcess(stack_copy, thread.thread_id, info.stack,
|
||||||
|
info.stack_len);
|
||||||
|
memory.Copy(stack_copy, info.stack_len);
|
||||||
|
thread.stack.start_of_memory_range = (uintptr_t)(info.stack);
|
||||||
|
thread.stack.memory = memory.location();
|
||||||
|
TypedMDRVA<RawContextCPU> cpu(&minidump_writer_);
|
||||||
|
if (!cpu.Allocate())
|
||||||
|
return false;
|
||||||
|
my_memset(cpu.get(), 0, sizeof(RawContextCPU));
|
||||||
|
CPUFillFromThreadInfo(cpu.get(), info);
|
||||||
|
thread.thread_context = cpu.location();
|
||||||
|
}
|
||||||
|
|
||||||
|
list.CopyIndexAfterObject(i, &thread, sizeof(thread));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ShouldIncludeMapping(const MappingInfo& mapping) {
|
||||||
|
if (mapping.name[0] == 0 || // we only want modules with filenames.
|
||||||
|
mapping.offset || // we only want to include one mapping per shared lib.
|
||||||
|
mapping.size < 4096) { // too small to get a signature for.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write information about the mappings in effect. Because we are using the
|
||||||
|
// minidump format, the information about the mappings is pretty limited.
|
||||||
|
// Because of this, we also include the full, unparsed, /proc/$x/maps file in
|
||||||
|
// another stream in the file.
|
||||||
|
bool WriteMappings(MDRawDirectory* dirent) {
|
||||||
|
const unsigned num_mappings = dumper_.mappings().size();
|
||||||
|
unsigned num_output_mappings = 0;
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < dumper_.mappings().size(); ++i) {
|
||||||
|
const MappingInfo& mapping = *dumper_.mappings()[i];
|
||||||
|
if (ShouldIncludeMapping(mapping))
|
||||||
|
num_output_mappings++;
|
||||||
|
}
|
||||||
|
|
||||||
|
TypedMDRVA<uint32_t> list(&minidump_writer_);
|
||||||
|
if (!list.AllocateObjectAndArray(num_output_mappings, sizeof(MDRawModule)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
dirent->stream_type = MD_MODULE_LIST_STREAM;
|
||||||
|
dirent->location = list.location();
|
||||||
|
*list.get() = num_output_mappings;
|
||||||
|
|
||||||
|
for (unsigned i = 0, j = 0; i < num_mappings; ++i) {
|
||||||
|
const MappingInfo& mapping = *dumper_.mappings()[i];
|
||||||
|
if (!ShouldIncludeMapping(mapping))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
MDRawModule mod;
|
||||||
|
my_memset(&mod, 0, sizeof(mod));
|
||||||
|
mod.base_of_image = mapping.start_addr;
|
||||||
|
mod.size_of_image = mapping.size;
|
||||||
|
const size_t filepath_len = my_strlen(mapping.name);
|
||||||
|
|
||||||
|
// Figure out file name from path
|
||||||
|
const char* filename_ptr = mapping.name + filepath_len - 1;
|
||||||
|
while (filename_ptr >= mapping.name) {
|
||||||
|
if (*filename_ptr == '/')
|
||||||
|
break;
|
||||||
|
filename_ptr--;
|
||||||
|
}
|
||||||
|
filename_ptr++;
|
||||||
|
const size_t filename_len = mapping.name + filepath_len - filename_ptr;
|
||||||
|
|
||||||
|
uint8_t cv_buf[MDCVInfoPDB70_minsize + NAME_MAX];
|
||||||
|
uint8_t* cv_ptr = cv_buf;
|
||||||
|
UntypedMDRVA cv(&minidump_writer_);
|
||||||
|
if (!cv.Allocate(MDCVInfoPDB70_minsize + filename_len + 1))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const uint32_t cv_signature = MD_CVINFOPDB70_SIGNATURE;
|
||||||
|
memcpy(cv_ptr, &cv_signature, sizeof(cv_signature));
|
||||||
|
cv_ptr += sizeof(cv_signature);
|
||||||
|
|
||||||
|
{
|
||||||
|
// We XOR the first page of the file to get a signature for it.
|
||||||
|
uint8_t xor_buf[sizeof(MDGUID)];
|
||||||
|
size_t done = 0;
|
||||||
|
uint8_t* signature = cv_ptr;
|
||||||
|
cv_ptr += sizeof(xor_buf);
|
||||||
|
|
||||||
|
my_memset(signature, 0, sizeof(xor_buf));
|
||||||
|
while (done < 4096) {
|
||||||
|
dumper_.CopyFromProcess(xor_buf, crashing_tid_,
|
||||||
|
(void *) (mod.base_of_image + done),
|
||||||
|
sizeof(xor_buf));
|
||||||
|
for (unsigned i = 0; i < sizeof(xor_buf); ++i)
|
||||||
|
signature[i] ^= xor_buf[i];
|
||||||
|
done += sizeof(xor_buf);
|
||||||
|
}
|
||||||
|
my_memset(cv_ptr, 0, sizeof(uint32_t)); // Set age to 0 on Linux.
|
||||||
|
cv_ptr += sizeof(uint32_t);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write pdb_file_name
|
||||||
|
memcpy(cv_ptr, filename_ptr, filename_len + 1);
|
||||||
|
cv.Copy(cv_buf, MDCVInfoPDB70_minsize + filename_len + 1);
|
||||||
|
|
||||||
|
mod.cv_record = cv.location();
|
||||||
|
|
||||||
|
MDLocationDescriptor ld;
|
||||||
|
if (!minidump_writer_.WriteString(mapping.name, filepath_len, &ld))
|
||||||
|
return false;
|
||||||
|
mod.module_name_rva = ld.rva;
|
||||||
|
|
||||||
|
list.CopyIndexAfterObject(j++, &mod, sizeof(mod));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WriteExceptionStream(MDRawDirectory* dirent) {
|
||||||
|
TypedMDRVA<MDRawExceptionStream> exc(&minidump_writer_);
|
||||||
|
if (!exc.Allocate())
|
||||||
|
return false;
|
||||||
|
my_memset(exc.get(), 0, sizeof(MDRawExceptionStream));
|
||||||
|
|
||||||
|
dirent->stream_type = MD_EXCEPTION_STREAM;
|
||||||
|
dirent->location = exc.location();
|
||||||
|
|
||||||
|
exc.get()->thread_id = crashing_tid_;
|
||||||
|
exc.get()->exception_record.exception_code = siginfo_->si_signo;
|
||||||
|
exc.get()->exception_record.exception_address =
|
||||||
|
(uintptr_t) siginfo_->si_addr;
|
||||||
|
exc.get()->thread_context = crashing_thread_context_;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WriteSystemInfoStream(MDRawDirectory* dirent) {
|
||||||
|
TypedMDRVA<MDRawSystemInfo> si(&minidump_writer_);
|
||||||
|
if (!si.Allocate())
|
||||||
|
return false;
|
||||||
|
my_memset(si.get(), 0, sizeof(MDRawSystemInfo));
|
||||||
|
|
||||||
|
dirent->stream_type = MD_SYSTEM_INFO_STREAM;
|
||||||
|
dirent->location = si.location();
|
||||||
|
|
||||||
|
WriteCPUInformation(si.get());
|
||||||
|
WriteOSInformation(si.get());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
#if defined(__i386)
|
||||||
|
uintptr_t GetStackPointer() {
|
||||||
|
return ucontext_->uc_mcontext.gregs[REG_ESP];
|
||||||
|
}
|
||||||
|
#elif defined(__x86_64)
|
||||||
|
uintptr_t GetStackPointer() {
|
||||||
|
return ucontext_->uc_mcontext.gregs[REG_RSP];
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#error "This code has not been ported to your platform yet."
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void NullifyDirectoryEntry(MDRawDirectory* dirent) {
|
||||||
|
dirent->stream_type = 0;
|
||||||
|
dirent->location.data_size = 0;
|
||||||
|
dirent->location.rva = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WriteCPUInformation(MDRawSystemInfo* sys_info) {
|
||||||
|
char vendor_id[sizeof(sys_info->cpu.x86_cpu_info.vendor_id) + 1] = {0};
|
||||||
|
static const char vendor_id_name[] = "vendor_id";
|
||||||
|
static const size_t vendor_id_name_length = sizeof(vendor_id_name) - 1;
|
||||||
|
|
||||||
|
struct CpuInfoEntry {
|
||||||
|
const char* info_name;
|
||||||
|
int value;
|
||||||
|
bool found;
|
||||||
|
} cpu_info_table[] = {
|
||||||
|
{ "processor", -1, false },
|
||||||
|
{ "model", 0, false },
|
||||||
|
{ "stepping", 0, false },
|
||||||
|
{ "cpuid level", 0, false },
|
||||||
|
};
|
||||||
|
|
||||||
|
// processor_architecture should always be set, do this first
|
||||||
|
sys_info->processor_architecture =
|
||||||
|
#if defined(__i386)
|
||||||
|
MD_CPU_ARCHITECTURE_X86;
|
||||||
|
#elif defined(__x86_64)
|
||||||
|
MD_CPU_ARCHITECTURE_AMD64;
|
||||||
|
#else
|
||||||
|
#error "Unknown CPU arch"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const int fd = sys_open("/proc/cpuinfo", O_RDONLY, 0);
|
||||||
|
if (fd < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
{
|
||||||
|
PageAllocator allocator;
|
||||||
|
LineReader* const line_reader = new(allocator) LineReader(fd);
|
||||||
|
const char* line;
|
||||||
|
unsigned line_len;
|
||||||
|
while (line_reader->GetNextLine(&line, &line_len)) {
|
||||||
|
for (size_t i = 0;
|
||||||
|
i < sizeof(cpu_info_table) / sizeof(cpu_info_table[0]);
|
||||||
|
i++) {
|
||||||
|
CpuInfoEntry* entry = &cpu_info_table[i];
|
||||||
|
if (entry->found)
|
||||||
|
continue;
|
||||||
|
if (!strncmp(line, entry->info_name, strlen(entry->info_name))) {
|
||||||
|
const char* value = strchr(line, ':');
|
||||||
|
if (!value)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// the above strncmp only matches the prefix, it might be the wrong
|
||||||
|
// line. i.e. we matched "model name" instead of "model".
|
||||||
|
// check and make sure there is only spaces between the prefix and
|
||||||
|
// the colon.
|
||||||
|
const char* space_ptr = line + strlen(entry->info_name);
|
||||||
|
for (; space_ptr < value; space_ptr++) {
|
||||||
|
if (!isspace(*space_ptr)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (space_ptr != value)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
sscanf(++value, " %d", &(entry->value));
|
||||||
|
entry->found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// special case for vendor_id
|
||||||
|
if (!strncmp(line, vendor_id_name, vendor_id_name_length)) {
|
||||||
|
const char* value = strchr(line, ':');
|
||||||
|
if (!value)
|
||||||
|
goto popline;
|
||||||
|
|
||||||
|
// skip ':" and all the spaces that follows
|
||||||
|
do {
|
||||||
|
value++;
|
||||||
|
} while (isspace(*value));
|
||||||
|
|
||||||
|
if (*value) {
|
||||||
|
size_t length = strlen(value);
|
||||||
|
if (length == 0)
|
||||||
|
goto popline;
|
||||||
|
// we don't want the trailing newline
|
||||||
|
if (value[length - 1] == '\n')
|
||||||
|
length--;
|
||||||
|
// ensure we have space for the value
|
||||||
|
if (length < sizeof(vendor_id))
|
||||||
|
strncpy(vendor_id, value, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
popline:
|
||||||
|
line_reader->PopLine(line_len);
|
||||||
|
}
|
||||||
|
sys_close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure we got everything we wanted
|
||||||
|
for (size_t i = 0;
|
||||||
|
i < sizeof(cpu_info_table) / sizeof(cpu_info_table[0]);
|
||||||
|
i++) {
|
||||||
|
if (!cpu_info_table[i].found) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// /proc/cpuinfo contains cpu id, change it into number by adding one.
|
||||||
|
cpu_info_table[0].value++;
|
||||||
|
|
||||||
|
sys_info->number_of_processors = cpu_info_table[0].value;
|
||||||
|
sys_info->processor_level = cpu_info_table[3].value;
|
||||||
|
sys_info->processor_revision = cpu_info_table[1].value << 8 |
|
||||||
|
cpu_info_table[2].value;
|
||||||
|
|
||||||
|
if (vendor_id[0] != '\0') {
|
||||||
|
memcpy(sys_info->cpu.x86_cpu_info.vendor_id, vendor_id,
|
||||||
|
sizeof(sys_info->cpu.x86_cpu_info.vendor_id));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WriteFile(MDLocationDescriptor* result, const char* filename) {
|
||||||
|
const int fd = sys_open(filename, O_RDONLY, 0);
|
||||||
|
if (fd < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// We can't stat the files because several of the files that we want to
|
||||||
|
// read are kernel seqfiles, which always have a length of zero. So we have
|
||||||
|
// to read as much as we can into a buffer.
|
||||||
|
static const unsigned kMaxFileSize = 1024;
|
||||||
|
uint8_t* data = (uint8_t*) dumper_.allocator()->Alloc(kMaxFileSize);
|
||||||
|
|
||||||
|
size_t done = 0;
|
||||||
|
while (done < kMaxFileSize) {
|
||||||
|
ssize_t r;
|
||||||
|
do {
|
||||||
|
r = sys_read(fd, data + done, kMaxFileSize - done);
|
||||||
|
} while (r == -1 && errno == EINTR);
|
||||||
|
|
||||||
|
if (r < 1)
|
||||||
|
break;
|
||||||
|
done += r;
|
||||||
|
}
|
||||||
|
sys_close(fd);
|
||||||
|
|
||||||
|
if (!done)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
UntypedMDRVA memory(&minidump_writer_);
|
||||||
|
if (!memory.Allocate(done))
|
||||||
|
return false;
|
||||||
|
memory.Copy(data, done);
|
||||||
|
*result = memory.location();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WriteOSInformation(MDRawSystemInfo* sys_info) {
|
||||||
|
sys_info->platform_id = MD_OS_LINUX;
|
||||||
|
|
||||||
|
struct utsname uts;
|
||||||
|
if (uname(&uts))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
static const size_t buf_len = 512;
|
||||||
|
char buf[buf_len] = {0};
|
||||||
|
size_t space_left = buf_len - 1;
|
||||||
|
const char* info_table[] = {
|
||||||
|
uts.sysname,
|
||||||
|
uts.release,
|
||||||
|
uts.version,
|
||||||
|
uts.machine,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
bool first_item = true;
|
||||||
|
for (const char** cur_info = info_table; *cur_info; cur_info++) {
|
||||||
|
static const char* separator = " ";
|
||||||
|
size_t separator_len = strlen(separator);
|
||||||
|
size_t info_len = strlen(*cur_info);
|
||||||
|
if (info_len == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (space_left < info_len + (first_item ? 0 : separator_len))
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (!first_item) {
|
||||||
|
strcat(buf, separator);
|
||||||
|
space_left -= separator_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
first_item = false;
|
||||||
|
strcat(buf, *cur_info);
|
||||||
|
space_left -= info_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
MDLocationDescriptor location;
|
||||||
|
if (!minidump_writer_.WriteString(buf, 0, &location))
|
||||||
|
return false;
|
||||||
|
sys_info->csd_version_rva = location.rva;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WriteProcFile(MDLocationDescriptor* result, pid_t pid,
|
||||||
|
const char* filename) {
|
||||||
|
char buf[80];
|
||||||
|
memcpy(buf, "/proc/", 6);
|
||||||
|
const unsigned pid_len = my_int_len(pid);
|
||||||
|
my_itos(buf + 6, pid, pid_len);
|
||||||
|
buf[6 + pid_len] = '/';
|
||||||
|
memcpy(buf + 6 + pid_len + 1, filename, my_strlen(filename) + 1);
|
||||||
|
return WriteFile(result, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* const filename_; // output filename
|
||||||
|
const siginfo_t* const siginfo_; // from the signal handler (see sigaction)
|
||||||
|
const struct ucontext* const ucontext_; // also from the signal handler
|
||||||
|
const struct _libc_fpstate* const float_state_; // ditto
|
||||||
|
const pid_t crashing_tid_; // the process which actually crashed
|
||||||
|
LinuxDumper dumper_;
|
||||||
|
MinidumpFileWriter minidump_writer_;
|
||||||
|
MDLocationDescriptor crashing_thread_context_;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool WriteMinidump(const char* filename, pid_t crashing_process,
|
||||||
|
const void* blob, size_t blob_size) {
|
||||||
|
if (blob_size != sizeof(ExceptionHandler::CrashContext))
|
||||||
|
return false;
|
||||||
|
const ExceptionHandler::CrashContext* context =
|
||||||
|
reinterpret_cast<const ExceptionHandler::CrashContext*>(blob);
|
||||||
|
MinidumpWriter writer(filename, crashing_process, context);
|
||||||
|
if (!writer.Init())
|
||||||
|
return false;
|
||||||
|
return writer.Dump();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace google_breakpad
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) 2006, Google Inc.
|
// Copyright (c) 2009, Google Inc.
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
//
|
//
|
||||||
// Author: Li Liu
|
|
||||||
//
|
|
||||||
// Redistribution and use in source and binary forms, with or without
|
// Redistribution and use in source and binary forms, with or without
|
||||||
// modification, are permitted provided that the following conditions are
|
// modification, are permitted provided that the following conditions are
|
||||||
// met:
|
// met:
|
||||||
@ -29,45 +27,27 @@
|
|||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
#ifndef CLIENT_LINUX_HANDLER_MINIDUMP_GENERATOR_H__
|
#ifndef CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_
|
||||||
#define CLIENT_LINUX_HANDLER_MINIDUMP_GENERATOR_H__
|
#define CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <unistd.h>
|
||||||
#include "google_breakpad/common/breakpad_types.h"
|
|
||||||
#include "processor/scoped_ptr.h"
|
|
||||||
|
|
||||||
struct sigcontext;
|
|
||||||
|
|
||||||
namespace google_breakpad {
|
namespace google_breakpad {
|
||||||
|
|
||||||
|
// Write a minidump to the filesystem. This function does not malloc nor use
|
||||||
|
// libc functions which may. Thus, it can be used in contexts where the state
|
||||||
|
// of the heap may be corrupt.
|
||||||
|
// filename: the filename to write to. This is opened O_EXCL and fails if
|
||||||
|
// open fails.
|
||||||
|
// crashing_process: the pid of the crashing process. This must be trusted.
|
||||||
|
// blob: a blob of data from the crashing process. See exception_handler.h
|
||||||
|
// blob_size: the length of |blob|, in bytes
|
||||||
//
|
//
|
||||||
// MinidumpGenerator
|
// Returns true iff successful.
|
||||||
//
|
bool WriteMinidump(const char* filename, pid_t crashing_process,
|
||||||
// Write a minidump to file based on the signo and sig_ctx.
|
const void* blob, size_t blob_size);
|
||||||
// A minidump generator should be created before any exception happen.
|
|
||||||
//
|
|
||||||
class MinidumpGenerator {
|
|
||||||
public:
|
|
||||||
MinidumpGenerator();
|
|
||||||
|
|
||||||
~MinidumpGenerator();
|
|
||||||
|
|
||||||
// Write minidump.
|
|
||||||
bool WriteMinidumpToFile(const char *file_pathname,
|
|
||||||
int signo,
|
|
||||||
uintptr_t sighandler_ebp,
|
|
||||||
struct sigcontext **sig_ctx) const;
|
|
||||||
private:
|
|
||||||
// Allocate memory for stack.
|
|
||||||
void AllocateStack();
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Stack size of the writer thread.
|
|
||||||
static const int kStackSize = 1024 * 1024;
|
|
||||||
scoped_array<char> stack_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace google_breakpad
|
} // namespace google_breakpad
|
||||||
|
|
||||||
#endif // CLIENT_LINUX_HANDLER_MINIDUMP_GENERATOR_H__
|
#endif // CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) 2006, Google Inc.
|
// Copyright (c) 2009, Google Inc.
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
//
|
//
|
||||||
// Author: Li Liu
|
|
||||||
//
|
|
||||||
// Redistribution and use in source and binary forms, with or without
|
// Redistribution and use in source and binary forms, with or without
|
||||||
// modification, are permitted provided that the following conditions are
|
// modification, are permitted provided that the following conditions are
|
||||||
// met:
|
// met:
|
||||||
@ -29,58 +27,53 @@
|
|||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <sys/syscall.h>
|
||||||
|
|
||||||
#include <cassert>
|
#include "client/linux/handler/exception_handler.h"
|
||||||
#include <cstdio>
|
#include "client/linux/minidump_writer/minidump_writer.h"
|
||||||
#include <cstdlib>
|
#include "breakpad_googletest_includes.h"
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
#include "client/linux/handler/minidump_generator.h"
|
|
||||||
|
|
||||||
using namespace google_breakpad;
|
using namespace google_breakpad;
|
||||||
|
|
||||||
// Thread use this to see if it should stop working.
|
// This provides a wrapper around system calls which may be
|
||||||
static bool should_exit = false;
|
// interrupted by a signal and return EINTR. See man 7 signal.
|
||||||
|
#define HANDLE_EINTR(x) ({ \
|
||||||
|
typeof(x) __eintr_result__; \
|
||||||
|
do { \
|
||||||
|
__eintr_result__ = x; \
|
||||||
|
} while (__eintr_result__ == -1 && errno == EINTR); \
|
||||||
|
__eintr_result__;\
|
||||||
|
})
|
||||||
|
|
||||||
static void foo2(int arg) {
|
namespace {
|
||||||
// Stack variable, used for debugging stack dumps.
|
typedef testing::Test MinidumpWriterTest;
|
||||||
int c = arg;
|
|
||||||
c = 0xcccccccc;
|
|
||||||
while (!should_exit)
|
|
||||||
sleep(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void foo(int arg) {
|
TEST(MinidumpWriterTest, Setup) {
|
||||||
// Stack variable, used for debugging stack dumps.
|
int fds[2];
|
||||||
int b = arg;
|
ASSERT_NE(-1, pipe(fds));
|
||||||
b = 0xbbbbbbbb;
|
|
||||||
foo2(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void *thread_main(void *) {
|
const pid_t child = fork();
|
||||||
// Stack variable, used for debugging stack dumps.
|
if (child == 0) {
|
||||||
int a = 0xaaaaaaaa;
|
close(fds[1]);
|
||||||
foo(a);
|
char b;
|
||||||
return NULL;
|
HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
|
||||||
}
|
close(fds[0]);
|
||||||
|
syscall(__NR_exit);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
close(fds[0]);
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
ExceptionHandler::CrashContext context;
|
||||||
CreateThread(10);
|
memset(&context, 0, sizeof(context));
|
||||||
google_breakpad::MinidumpGenerator mg;
|
|
||||||
if (mg.WriteMinidumpToFile("minidump_test.out", -1, 0, NULL))
|
char templ[] = "/tmp/minidump-writer-unittest-XXXXXX";
|
||||||
printf("Succeeded written minidump\n");
|
mktemp(templ);
|
||||||
else
|
ASSERT_TRUE(WriteMinidump(templ, child, &context, sizeof(context)));
|
||||||
printf("Failed to write minidump\n");
|
struct stat st;
|
||||||
should_exit = true;
|
ASSERT_EQ(stat(templ, &st), 0);
|
||||||
return 0;
|
ASSERT_GT(st.st_size, 0u);
|
||||||
|
unlink(templ);
|
||||||
|
|
||||||
|
close(fds[1]);
|
||||||
}
|
}
|
@ -37,6 +37,8 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "common/linux/linux_syscall_support.h"
|
||||||
|
#include "common/linux/linux_libc_support.h"
|
||||||
#include "client/minidump_file_writer-inl.h"
|
#include "client/minidump_file_writer-inl.h"
|
||||||
#include "common/string_conversion.h"
|
#include "common/string_conversion.h"
|
||||||
|
|
||||||
@ -53,7 +55,11 @@ MinidumpFileWriter::~MinidumpFileWriter() {
|
|||||||
|
|
||||||
bool MinidumpFileWriter::Open(const char *path) {
|
bool MinidumpFileWriter::Open(const char *path) {
|
||||||
assert(file_ == -1);
|
assert(file_ == -1);
|
||||||
|
#if __linux__
|
||||||
|
file_ = sys_open(path, O_WRONLY | O_CREAT | O_EXCL, 0600);
|
||||||
|
#else
|
||||||
file_ = open(path, O_WRONLY | O_CREAT | O_EXCL, 0600);
|
file_ = open(path, O_WRONLY | O_CREAT | O_EXCL, 0600);
|
||||||
|
#endif
|
||||||
|
|
||||||
return file_ != -1;
|
return file_ != -1;
|
||||||
}
|
}
|
||||||
@ -63,7 +69,11 @@ bool MinidumpFileWriter::Close() {
|
|||||||
|
|
||||||
if (file_ != -1) {
|
if (file_ != -1) {
|
||||||
ftruncate(file_, position_);
|
ftruncate(file_, position_);
|
||||||
|
#if __linux__
|
||||||
|
result = (sys_close(file_) == 0);
|
||||||
|
#else
|
||||||
result = (close(file_) == 0);
|
result = (close(file_) == 0);
|
||||||
|
#endif
|
||||||
file_ = -1;
|
file_ = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -227,9 +237,16 @@ bool MinidumpFileWriter::Copy(MDRVA position, const void *src, ssize_t size) {
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Seek and write the data
|
// Seek and write the data
|
||||||
if (lseek(file_, position, SEEK_SET) == static_cast<off_t>(position))
|
#if __linux__
|
||||||
if (write(file_, src, size) == size)
|
if (sys_lseek(file_, position, SEEK_SET) == static_cast<off_t>(position)) {
|
||||||
|
if (sys_write(file_, src, size) == size) {
|
||||||
|
#else
|
||||||
|
if (lseek(file_, position, SEEK_SET) == static_cast<off_t>(position)) {
|
||||||
|
if (write(file_, src, size) == size) {
|
||||||
|
#endif
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -27,20 +27,22 @@
|
|||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
#include <cstdarg>
|
#include <assert.h>
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <cxxabi.h>
|
#include <cxxabi.h>
|
||||||
#include <elf.h>
|
#include <elf.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <link.h>
|
#include <link.h>
|
||||||
|
#include <string.h>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdarg>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <list>
|
#include <list>
|
||||||
@ -121,7 +123,7 @@ static const ElfW(Shdr) *FindSectionByName(const char *name,
|
|||||||
|
|
||||||
for (int i = 0; i < nsection; ++i) {
|
for (int i = 0; i < nsection; ++i) {
|
||||||
const char *section_name =
|
const char *section_name =
|
||||||
(char*)(strtab->sh_offset + sections[i].sh_name);
|
reinterpret_cast<char*>(strtab->sh_offset + sections[i].sh_name);
|
||||||
if (!strncmp(name, section_name, name_len))
|
if (!strncmp(name, section_name, name_len))
|
||||||
return sections + i;
|
return sections + i;
|
||||||
}
|
}
|
||||||
|
@ -32,104 +32,74 @@
|
|||||||
// See file_id.h for documentation
|
// See file_id.h for documentation
|
||||||
//
|
//
|
||||||
|
|
||||||
#include <cassert>
|
#include "common/linux/file_id.h"
|
||||||
#include <cstdio>
|
|
||||||
|
#include <arpa/inet.h>
|
||||||
#include <elf.h>
|
#include <elf.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <link.h>
|
#include <link.h>
|
||||||
#include <sys/mman.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "common/linux/file_id.h"
|
#include <cassert>
|
||||||
#include "common/md5.h"
|
#include <cstdio>
|
||||||
|
|
||||||
namespace google_breakpad {
|
namespace google_breakpad {
|
||||||
|
|
||||||
static bool FindElfTextSection(const void *elf_mapped_base,
|
FileID::FileID(const char* path) {
|
||||||
const void **text_start,
|
|
||||||
int *text_size) {
|
|
||||||
assert(elf_mapped_base);
|
|
||||||
assert(text_start);
|
|
||||||
assert(text_size);
|
|
||||||
|
|
||||||
const unsigned char *elf_base =
|
|
||||||
static_cast<const unsigned char *>(elf_mapped_base);
|
|
||||||
const ElfW(Ehdr) *elf_header =
|
|
||||||
reinterpret_cast<const ElfW(Ehdr) *>(elf_base);
|
|
||||||
if (memcmp(elf_header, ELFMAG, SELFMAG) != 0)
|
|
||||||
return false;
|
|
||||||
*text_start = NULL;
|
|
||||||
*text_size = 0;
|
|
||||||
const ElfW(Shdr) *sections =
|
|
||||||
reinterpret_cast<const ElfW(Shdr) *>(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_));
|
strncpy(path_, path, sizeof(path_));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FileID::ElfFileIdentifier(unsigned char identifier[16]) {
|
bool FileID::ElfFileIdentifier(uint8_t identifier[kMDGUIDSize]) {
|
||||||
|
const ssize_t mapped_len = 4096; // Page size (matches WriteMappings())
|
||||||
int fd = open(path_, O_RDONLY);
|
int fd = open(path_, O_RDONLY);
|
||||||
if (fd < 0)
|
if (fd < 0)
|
||||||
return false;
|
return false;
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (fstat(fd, &st) != 0 && st.st_size <= 0) {
|
if (fstat(fd, &st) != 0 || st.st_size <= mapped_len) {
|
||||||
close(fd);
|
close(fd);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
void *base = mmap(NULL, st.st_size,
|
void* base = mmap(NULL, mapped_len,
|
||||||
PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
|
PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
|
||||||
if (base == MAP_FAILED) {
|
close(fd);
|
||||||
close(fd);
|
if (base == MAP_FAILED)
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
bool success = false;
|
memset(identifier, 0, kMDGUIDSize);
|
||||||
const void *text_section = NULL;
|
uint8_t* ptr = reinterpret_cast<uint8_t*>(base);
|
||||||
int text_size = 0;
|
uint8_t* ptr_end = ptr + mapped_len;
|
||||||
if (FindElfTextSection(base, &text_section, &text_size) && (text_size > 0)) {
|
while (ptr < ptr_end) {
|
||||||
struct MD5Context md5;
|
for (unsigned i = 0; i < kMDGUIDSize; i++)
|
||||||
MD5Init(&md5);
|
identifier[i] ^= ptr[i];
|
||||||
MD5Update(&md5,
|
ptr += kMDGUIDSize;
|
||||||
static_cast<const unsigned char*>(text_section),
|
|
||||||
text_size);
|
|
||||||
MD5Final(identifier, &md5);
|
|
||||||
success = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
close(fd);
|
munmap(base, mapped_len);
|
||||||
munmap(base, st.st_size);
|
return true;
|
||||||
return success;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
void FileID::ConvertIdentifierToString(const unsigned char identifier[16],
|
void FileID::ConvertIdentifierToString(const uint8_t identifier[kMDGUIDSize],
|
||||||
char *buffer, int buffer_length) {
|
char* buffer, int buffer_length) {
|
||||||
|
uint8_t identifier_swapped[kMDGUIDSize];
|
||||||
|
|
||||||
|
// Endian-ness swap to match dump processor expectation.
|
||||||
|
memcpy(identifier_swapped, identifier, kMDGUIDSize);
|
||||||
|
uint32_t* data1 = reinterpret_cast<uint32_t*>(identifier_swapped);
|
||||||
|
*data1 = htonl(*data1);
|
||||||
|
uint16_t* data2 = reinterpret_cast<uint16_t*>(identifier_swapped + 4);
|
||||||
|
*data2 = htons(*data2);
|
||||||
|
uint16_t* data3 = reinterpret_cast<uint16_t*>(identifier_swapped + 6);
|
||||||
|
*data3 = htons(*data3);
|
||||||
|
|
||||||
int buffer_idx = 0;
|
int buffer_idx = 0;
|
||||||
for (int idx = 0; (buffer_idx < buffer_length) && (idx < 16); ++idx) {
|
for (unsigned int idx = 0;
|
||||||
int hi = (identifier[idx] >> 4) & 0x0F;
|
(buffer_idx < buffer_length) && (idx < kMDGUIDSize);
|
||||||
int lo = (identifier[idx]) & 0x0F;
|
++idx) {
|
||||||
|
int hi = (identifier_swapped[idx] >> 4) & 0x0F;
|
||||||
|
int lo = (identifier_swapped[idx]) & 0x0F;
|
||||||
|
|
||||||
if (idx == 4 || idx == 6 || idx == 8 || idx == 10)
|
if (idx == 4 || idx == 6 || idx == 8 || idx == 10)
|
||||||
buffer[buffer_idx++] = '-';
|
buffer[buffer_idx++] = '-';
|
||||||
|
@ -35,25 +35,30 @@
|
|||||||
|
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
|
||||||
|
#include "common/linux/guid_creator.h"
|
||||||
|
|
||||||
namespace google_breakpad {
|
namespace google_breakpad {
|
||||||
|
|
||||||
|
static const size_t kMDGUIDSize = sizeof(MDGUID);
|
||||||
|
|
||||||
class FileID {
|
class FileID {
|
||||||
public:
|
public:
|
||||||
FileID(const char *path);
|
explicit FileID(const char* path);
|
||||||
~FileID() {};
|
~FileID() {}
|
||||||
|
|
||||||
// Load the identifier for the elf file path specified in the constructor into
|
// 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
|
// |identifier|. Return false if the identifier could not be created for the
|
||||||
// file.
|
// file.
|
||||||
// The current implementation will return the MD5 hash of the file's bytes.
|
// The current implementation will XOR the first page of data to generate an
|
||||||
bool ElfFileIdentifier(unsigned char identifier[16]);
|
// identifier.
|
||||||
|
bool ElfFileIdentifier(uint8_t identifier[kMDGUIDSize]);
|
||||||
|
|
||||||
// Convert the |identifier| data to a NULL terminated string. The string will
|
// Convert the |identifier| data to a NULL terminated string. The string will
|
||||||
// be formatted as a UUID (e.g., 22F065BB-FC9C-49F7-80FE-26A7CEBD7BCE).
|
// 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
|
// The |buffer| should be at least 37 bytes long to receive all of the data
|
||||||
// and termination. Shorter buffers will contain truncated data.
|
// and termination. Shorter buffers will contain truncated data.
|
||||||
static void ConvertIdentifierToString(const unsigned char identifier[16],
|
static void ConvertIdentifierToString(const uint8_t identifier[kMDGUIDSize],
|
||||||
char *buffer, int buffer_length);
|
char* buffer, int buffer_length);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Storage for the path specified
|
// Storage for the path specified
|
||||||
@ -63,4 +68,3 @@ class FileID {
|
|||||||
} // namespace google_breakpad
|
} // namespace google_breakpad
|
||||||
|
|
||||||
#endif // COMMON_LINUX_FILE_ID_H__
|
#endif // COMMON_LINUX_FILE_ID_H__
|
||||||
|
|
||||||
|
178
src/common/linux/linux_libc_support.h
Normal file
178
src/common/linux/linux_libc_support.h
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
// Copyright (c) 2009, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
// This header provides replacements for libc functions that we need. We if
|
||||||
|
// call the libc functions directly we risk crashing in the dynamic linker as
|
||||||
|
// it tries to resolve uncached PLT entries.
|
||||||
|
|
||||||
|
#ifndef CLIENT_LINUX_LINUX_LIBC_SUPPORT_H_
|
||||||
|
#define CLIENT_LINUX_LINUX_LIBC_SUPPORT_H_
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
static inline size_t
|
||||||
|
my_strlen(const char* s) {
|
||||||
|
size_t len = 0;
|
||||||
|
while (*s++) len++;
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int
|
||||||
|
my_strcmp(const char* a, const char* b) {
|
||||||
|
for (;;) {
|
||||||
|
if (*a < *b)
|
||||||
|
return -1;
|
||||||
|
else if (*a > *b)
|
||||||
|
return 1;
|
||||||
|
else if (*a == 0)
|
||||||
|
return 0;
|
||||||
|
a++;
|
||||||
|
b++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int
|
||||||
|
my_strncmp(const char* a, const char* b, size_t len) {
|
||||||
|
for (size_t i = 0; i < len; ++i) {
|
||||||
|
if (*a < *b)
|
||||||
|
return -1;
|
||||||
|
else if (*a > *b)
|
||||||
|
return 1;
|
||||||
|
else if (*a == 0)
|
||||||
|
return 0;
|
||||||
|
a++;
|
||||||
|
b++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse a non-negative integer.
|
||||||
|
// result: (output) the resulting non-negative integer
|
||||||
|
// s: a NUL terminated string
|
||||||
|
// Return true iff successful.
|
||||||
|
static inline bool
|
||||||
|
my_strtoui(int* result, const char* s) {
|
||||||
|
if (*s == 0)
|
||||||
|
return false;
|
||||||
|
int r = 0;
|
||||||
|
for (;; s++) {
|
||||||
|
if (*s == 0)
|
||||||
|
break;
|
||||||
|
const int old_r = r;
|
||||||
|
r *= 10;
|
||||||
|
if (*s < '0' || *s > '9')
|
||||||
|
return false;
|
||||||
|
r += *s - '0';
|
||||||
|
if (r < old_r)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*result = r;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the length of the given, non-negative integer when expressed in base
|
||||||
|
// 10.
|
||||||
|
static inline unsigned
|
||||||
|
my_int_len(int i) {
|
||||||
|
if (!i)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
int len = 0;
|
||||||
|
while (i) {
|
||||||
|
len++;
|
||||||
|
i /= 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert a non-negative integer to a string
|
||||||
|
// output: (output) the resulting string is written here. This buffer must be
|
||||||
|
// large enough to hold the resulting string. Call |my_int_len| to get the
|
||||||
|
// required length.
|
||||||
|
// i: the non-negative integer to serialise.
|
||||||
|
// i_len: the length of the integer in base 10 (see |my_int_len|).
|
||||||
|
static inline void
|
||||||
|
my_itos(char* output, int i, unsigned i_len) {
|
||||||
|
for (unsigned index = i_len; index; --index, i /= 10)
|
||||||
|
output[index - 1] = '0' + (i % 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline const char*
|
||||||
|
my_strchr(const char* haystack, char needle) {
|
||||||
|
while (*haystack && *haystack != needle)
|
||||||
|
haystack++;
|
||||||
|
if (*haystack == needle)
|
||||||
|
return haystack;
|
||||||
|
return (const char*) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read a hex value
|
||||||
|
// result: (output) the resulting value
|
||||||
|
// s: a string
|
||||||
|
// Returns a pointer to the first invalid charactor.
|
||||||
|
static inline const char*
|
||||||
|
my_read_hex_ptr(uintptr_t* result, const char* s) {
|
||||||
|
uintptr_t r = 0;
|
||||||
|
|
||||||
|
for (;; ++s) {
|
||||||
|
if (*s >= '0' && *s <= '9') {
|
||||||
|
r <<= 4;
|
||||||
|
r += *s - '0';
|
||||||
|
} else if (*s >= 'a' && *s <= 'f') {
|
||||||
|
r <<= 4;
|
||||||
|
r += (*s - 'a') + 10;
|
||||||
|
} else if (*s >= 'A' && *s <= 'F') {
|
||||||
|
r <<= 4;
|
||||||
|
r += (*s - 'A') + 10;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*result = r;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
my_memset(void* ip, char c, size_t len) {
|
||||||
|
char* p = (char *) ip;
|
||||||
|
while (len--)
|
||||||
|
*p++ = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
|
|
||||||
|
#endif // CLIENT_LINUX_LINUX_LIBC_SUPPORT_H_
|
153
src/common/linux/linux_libc_support_unittest.cc
Normal file
153
src/common/linux/linux_libc_support_unittest.cc
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
// Copyright (c) 2009, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
#include "breakpad/linux/linux_libc_support.h"
|
||||||
|
#include "testing/gtest/include/gtest/gtest.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
typedef testing::Test LinuxLibcSupportTest;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LinuxLibcSupportTest, strlen) {
|
||||||
|
static const char* test_data[] = { "", "a", "aa", "aaa", "aabc", NULL };
|
||||||
|
for (unsigned i = 0; ; ++i) {
|
||||||
|
if (!test_data[i])
|
||||||
|
break;
|
||||||
|
ASSERT_EQ(strlen(test_data[i]), my_strlen(test_data[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LinuxLibcSupportTest, strcmp) {
|
||||||
|
static const char* test_data[] = {
|
||||||
|
"", "",
|
||||||
|
"a", "",
|
||||||
|
"", "a",
|
||||||
|
"a", "b",
|
||||||
|
"a", "a",
|
||||||
|
"ab", "aa",
|
||||||
|
"abc", "ab",
|
||||||
|
"abc", "abc",
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (unsigned i = 0; ; ++i) {
|
||||||
|
if (!test_data[i*2])
|
||||||
|
break;
|
||||||
|
ASSERT_EQ(my_strcmp(test_data[i*2], test_data[i*2 + 1]),
|
||||||
|
strcmp(test_data[i*2], test_data[i*2 + 1]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LinuxLibcSupportTest, strtoui) {
|
||||||
|
int result;
|
||||||
|
|
||||||
|
ASSERT_FALSE(my_strtoui(&result, ""));
|
||||||
|
ASSERT_FALSE(my_strtoui(&result, "-1"));
|
||||||
|
ASSERT_FALSE(my_strtoui(&result, "-"));
|
||||||
|
ASSERT_FALSE(my_strtoui(&result, "a"));
|
||||||
|
ASSERT_FALSE(my_strtoui(&result, "23472893472938472987987398472398"));
|
||||||
|
|
||||||
|
ASSERT_TRUE(my_strtoui(&result, "0"));
|
||||||
|
ASSERT_EQ(result, 0);
|
||||||
|
ASSERT_TRUE(my_strtoui(&result, "1"));
|
||||||
|
ASSERT_EQ(result, 1);
|
||||||
|
ASSERT_TRUE(my_strtoui(&result, "12"));
|
||||||
|
ASSERT_EQ(result, 12);
|
||||||
|
ASSERT_TRUE(my_strtoui(&result, "123"));
|
||||||
|
ASSERT_EQ(result, 123);
|
||||||
|
ASSERT_TRUE(my_strtoui(&result, "0123"));
|
||||||
|
ASSERT_EQ(result, 123);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LinuxLibcSupportTest, int_len) {
|
||||||
|
ASSERT_EQ(my_int_len(0), 1);
|
||||||
|
ASSERT_EQ(my_int_len(2), 1);
|
||||||
|
ASSERT_EQ(my_int_len(5), 1);
|
||||||
|
ASSERT_EQ(my_int_len(9), 1);
|
||||||
|
ASSERT_EQ(my_int_len(10), 2);
|
||||||
|
ASSERT_EQ(my_int_len(99), 2);
|
||||||
|
ASSERT_EQ(my_int_len(100), 3);
|
||||||
|
ASSERT_EQ(my_int_len(101), 3);
|
||||||
|
ASSERT_EQ(my_int_len(1000), 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LinuxLibcSupportTest, itos) {
|
||||||
|
char buf[10];
|
||||||
|
|
||||||
|
my_itos(buf, 0, 1);
|
||||||
|
ASSERT_EQ(0, memcmp(buf, "0", 1));
|
||||||
|
|
||||||
|
my_itos(buf, 1, 1);
|
||||||
|
ASSERT_EQ(0, memcmp(buf, "1", 1));
|
||||||
|
|
||||||
|
my_itos(buf, 10, 2);
|
||||||
|
ASSERT_EQ(0, memcmp(buf, "10", 2));
|
||||||
|
|
||||||
|
my_itos(buf, 63, 2);
|
||||||
|
ASSERT_EQ(0, memcmp(buf, "63", 2));
|
||||||
|
|
||||||
|
my_itos(buf, 101, 3);
|
||||||
|
ASSERT_EQ(0, memcmp(buf, "101", 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LinuxLibcSupportTest, strchr) {
|
||||||
|
ASSERT_EQ(NULL, my_strchr("abc", 'd'));
|
||||||
|
ASSERT_EQ(NULL, my_strchr("", 'd'));
|
||||||
|
ASSERT_EQ(NULL, my_strchr("efghi", 'd'));
|
||||||
|
|
||||||
|
ASSERT_TRUE(my_strchr("a", 'a'));
|
||||||
|
ASSERT_TRUE(my_strchr("abc", 'a'));
|
||||||
|
ASSERT_TRUE(my_strchr("bcda", 'a'));
|
||||||
|
ASSERT_TRUE(my_strchr("sdfasdf", 'a'));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LinuxLibcSupportTest, read_hex_ptr) {
|
||||||
|
uintptr_t result;
|
||||||
|
const char* last;
|
||||||
|
|
||||||
|
last = my_read_hex_ptr(&result, "");
|
||||||
|
ASSERT_EQ(result, 0);
|
||||||
|
ASSERT_EQ(*last, 0);
|
||||||
|
|
||||||
|
last = my_read_hex_ptr(&result, "0");
|
||||||
|
ASSERT_EQ(result, 0);
|
||||||
|
ASSERT_EQ(*last, 0);
|
||||||
|
|
||||||
|
last = my_read_hex_ptr(&result, "0123");
|
||||||
|
ASSERT_EQ(result, 0x123);
|
||||||
|
ASSERT_EQ(*last, 0);
|
||||||
|
|
||||||
|
last = my_read_hex_ptr(&result, "0123a");
|
||||||
|
ASSERT_EQ(result, 0x123a);
|
||||||
|
ASSERT_EQ(*last, 0);
|
||||||
|
|
||||||
|
last = my_read_hex_ptr(&result, "0123a-");
|
||||||
|
ASSERT_EQ(result, 0x123a);
|
||||||
|
ASSERT_EQ(*last, '-');
|
||||||
|
}
|
2800
src/common/linux/linux_syscall_support.h
Normal file
2800
src/common/linux/linux_syscall_support.h
Normal file
File diff suppressed because it is too large
Load Diff
176
src/common/linux/memory.h
Normal file
176
src/common/linux/memory.h
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
// Copyright (c) 2009, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
#ifndef CLIENT_LINUX_HANDLER_MEMORY_H_
|
||||||
|
#define CLIENT_LINUX_HANDLER_MEMORY_H_
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
|
||||||
|
#include "common/linux/linux_syscall_support.h"
|
||||||
|
|
||||||
|
namespace google_breakpad {
|
||||||
|
|
||||||
|
// This is very simple allocator which fetches pages from the kernel directly.
|
||||||
|
// Thus, it can be used even when the heap may be corrupted.
|
||||||
|
//
|
||||||
|
// There is no free operation. The pages are only freed when the object is
|
||||||
|
// destroyed.
|
||||||
|
class PageAllocator {
|
||||||
|
public:
|
||||||
|
PageAllocator()
|
||||||
|
: page_size_(getpagesize()),
|
||||||
|
last_(NULL),
|
||||||
|
current_page_(NULL),
|
||||||
|
page_offset_(0) {
|
||||||
|
}
|
||||||
|
|
||||||
|
~PageAllocator() {
|
||||||
|
FreeAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
void *Alloc(unsigned bytes) {
|
||||||
|
if (!bytes)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (current_page_ && page_size_ - page_offset_ >= bytes) {
|
||||||
|
uint8_t *const ret = current_page_ + page_offset_;
|
||||||
|
page_offset_ += bytes;
|
||||||
|
if (page_offset_ == page_size_) {
|
||||||
|
page_offset_ = 0;
|
||||||
|
current_page_ = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
const unsigned pages =
|
||||||
|
(bytes + sizeof(PageHeader) + page_size_ - 1) / page_size_;
|
||||||
|
uint8_t *const ret = GetNPages(pages);
|
||||||
|
if (!ret)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
page_offset_ = (page_size_ - (page_size_ * pages - (bytes + sizeof(PageHeader)))) % page_size_;
|
||||||
|
current_page_ = page_offset_ ? ret + page_size_ * (pages - 1) : NULL;
|
||||||
|
|
||||||
|
return ret + sizeof(PageHeader);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8_t *GetNPages(unsigned num_pages) {
|
||||||
|
void *a = sys_mmap2(NULL, page_size_ * num_pages, PROT_READ | PROT_WRITE,
|
||||||
|
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||||
|
if (a == MAP_FAILED)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
struct PageHeader *header = reinterpret_cast<PageHeader*>(a);
|
||||||
|
header->next = last_;
|
||||||
|
header->num_pages = num_pages;
|
||||||
|
last_ = header;
|
||||||
|
|
||||||
|
return reinterpret_cast<uint8_t*>(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FreeAll() {
|
||||||
|
PageHeader *next;
|
||||||
|
|
||||||
|
for (PageHeader *cur = last_; cur; cur = next) {
|
||||||
|
next = cur->next;
|
||||||
|
sys_munmap(cur, cur->num_pages * page_size_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PageHeader {
|
||||||
|
PageHeader *next; // pointer to the start of the next set of pages.
|
||||||
|
unsigned num_pages; // the number of pages in this set.
|
||||||
|
};
|
||||||
|
|
||||||
|
const unsigned page_size_;
|
||||||
|
PageHeader *last_;
|
||||||
|
uint8_t *current_page_;
|
||||||
|
unsigned page_offset_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A wasteful vector is like a normal std::vector, except that it's very much
|
||||||
|
// simplier and it allocates memory from a PageAllocator. It's wasteful
|
||||||
|
// because, when resizing, it always allocates a whole new array since the
|
||||||
|
// PageAllocator doesn't support realloc.
|
||||||
|
template<class T>
|
||||||
|
class wasteful_vector {
|
||||||
|
public:
|
||||||
|
wasteful_vector(PageAllocator *allocator, unsigned size_hint = 16)
|
||||||
|
: allocator_(allocator),
|
||||||
|
a_((T*) allocator->Alloc(sizeof(T) * size_hint)),
|
||||||
|
allocated_(size_hint),
|
||||||
|
used_(0) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void push_back(const T& new_element) {
|
||||||
|
if (used_ == allocated_)
|
||||||
|
Realloc(allocated_ * 2);
|
||||||
|
a_[used_++] = new_element;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size() const {
|
||||||
|
return used_;
|
||||||
|
}
|
||||||
|
|
||||||
|
T& operator[](size_t index) {
|
||||||
|
return a_[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
const T& operator[](size_t index) const {
|
||||||
|
return a_[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Realloc(unsigned new_size) {
|
||||||
|
T *new_array =
|
||||||
|
reinterpret_cast<T*>(allocator_->Alloc(sizeof(T) * new_size));
|
||||||
|
memcpy(new_array, a_, used_ * sizeof(T));
|
||||||
|
a_ = new_array;
|
||||||
|
allocated_ = new_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
PageAllocator *const allocator_;
|
||||||
|
T *a_; // pointer to an array of |allocated_| elements.
|
||||||
|
unsigned allocated_; // size of |a_|, in elements.
|
||||||
|
unsigned used_; // number of used slots in |a_|.
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace google_breakpad
|
||||||
|
|
||||||
|
inline void* operator new(size_t nbytes,
|
||||||
|
google_breakpad::PageAllocator& allocator) {
|
||||||
|
return allocator.Alloc(nbytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // CLIENT_LINUX_HANDLER_MEMORY_H_
|
84
src/common/linux/memory_unittest.cc
Normal file
84
src/common/linux/memory_unittest.cc
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
// Copyright (c) 2009, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
#include "breakpad/linux/memory.h"
|
||||||
|
#include "testing/gtest/include/gtest/gtest.h"
|
||||||
|
|
||||||
|
using namespace google_breakpad;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
typedef testing::Test PageAllocatorTest;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(PageAllocatorTest, Setup) {
|
||||||
|
PageAllocator allocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(PageAllocatorTest, SmallObjects) {
|
||||||
|
PageAllocator allocator;
|
||||||
|
|
||||||
|
for (unsigned i = 1; i < 1024; ++i) {
|
||||||
|
uint8_t *p = reinterpret_cast<uint8_t*>(allocator.Alloc(i));
|
||||||
|
ASSERT_FALSE(p == NULL);
|
||||||
|
memset(p, 0, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(PageAllocatorTest, LargeObject) {
|
||||||
|
PageAllocator allocator;
|
||||||
|
|
||||||
|
uint8_t *p = reinterpret_cast<uint8_t*>(allocator.Alloc(10000));
|
||||||
|
ASSERT_FALSE(p == NULL);
|
||||||
|
for (unsigned i = 1; i < 10; ++i) {
|
||||||
|
uint8_t *p = reinterpret_cast<uint8_t*>(allocator.Alloc(i));
|
||||||
|
ASSERT_FALSE(p == NULL);
|
||||||
|
memset(p, 0, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
typedef testing::Test WastefulVectorTest;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(WastefulVectorTest, Setup) {
|
||||||
|
PageAllocator allocator_;
|
||||||
|
wasteful_vector<int> v(&allocator_);
|
||||||
|
ASSERT_EQ(v.size(), 0u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(WastefulVectorTest, Simple) {
|
||||||
|
PageAllocator allocator_;
|
||||||
|
wasteful_vector<int> v(&allocator_);
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < 256; ++i)
|
||||||
|
v.push_back(i);
|
||||||
|
ASSERT_EQ(v.size(), 256u);
|
||||||
|
for (unsigned i = 0; i < 256; ++i)
|
||||||
|
ASSERT_EQ(v[i], i);
|
||||||
|
}
|
602
src/tools/linux/md2core/minidump-2-core.cc
Normal file
602
src/tools/linux/md2core/minidump-2-core.cc
Normal file
@ -0,0 +1,602 @@
|
|||||||
|
// Copyright (c) 2009, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
// Converts a minidump file to a core file which gdb can read.
|
||||||
|
// Large parts lifted from the userspace core dumper:
|
||||||
|
// http://code.google.com/p/google-coredumper/
|
||||||
|
//
|
||||||
|
// Usage: minidump-2-core 1234.dmp > core
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <elf.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/user.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
|
||||||
|
#include "google_breakpad/common/minidump_format.h"
|
||||||
|
#include "google_breakpad/common/minidump_cpu_x86.h"
|
||||||
|
#include "breakpad/linux/minidump_format_linux.h"
|
||||||
|
|
||||||
|
#if __WORDSIZE == 64
|
||||||
|
#define ELF_CLASS ELFCLASS64
|
||||||
|
#define Ehdr Elf64_Ehdr
|
||||||
|
#define Phdr Elf64_Phdr
|
||||||
|
#define Shdr Elf64_Shdr
|
||||||
|
#define Nhdr Elf64_Nhdr
|
||||||
|
#define auxv_t Elf64_auxv_t
|
||||||
|
#else
|
||||||
|
#define ELF_CLASS ELFCLASS32
|
||||||
|
#define Ehdr Elf32_Ehdr
|
||||||
|
#define Phdr Elf32_Phdr
|
||||||
|
#define Shdr Elf32_Shdr
|
||||||
|
#define Nhdr Elf32_Nhdr
|
||||||
|
#define auxv_t Elf32_auxv_t
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(__x86_64__)
|
||||||
|
#define ELF_ARCH EM_X86_64
|
||||||
|
#elif defined(__i386__)
|
||||||
|
#define ELF_ARCH EM_386
|
||||||
|
#elif defined(__ARM_ARCH_3__)
|
||||||
|
#define ELF_ARCH EM_ARM
|
||||||
|
#elif defined(__mips__)
|
||||||
|
#define ELF_ARCH EM_MIPS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static int usage(const char* argv0) {
|
||||||
|
fprintf(stderr, "Usage: %s <minidump file>\n", argv0);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write all of the given buffer, handling short writes and EINTR. Return true
|
||||||
|
// iff successful.
|
||||||
|
static bool
|
||||||
|
writea(int fd, const void* idata, size_t length) {
|
||||||
|
const uint8_t* data = (const uint8_t*) idata;
|
||||||
|
|
||||||
|
size_t done = 0;
|
||||||
|
while (done < length) {
|
||||||
|
ssize_t r;
|
||||||
|
do {
|
||||||
|
r = write(fd, data + done, length - done);
|
||||||
|
} while (r == -1 && errno == EINTR);
|
||||||
|
|
||||||
|
if (r < 1)
|
||||||
|
return false;
|
||||||
|
done += r;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A range of a mmaped file.
|
||||||
|
class MMappedRange {
|
||||||
|
public:
|
||||||
|
MMappedRange(const void* data, size_t length)
|
||||||
|
: data_(reinterpret_cast<const uint8_t*>(data)),
|
||||||
|
length_(length) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get an object of |length| bytes at |offset| and return a pointer to it
|
||||||
|
// unless it's out of bounds.
|
||||||
|
const void* GetObject(size_t offset, size_t length) {
|
||||||
|
if (offset + length < offset)
|
||||||
|
return NULL;
|
||||||
|
if (offset + length > length_)
|
||||||
|
return NULL;
|
||||||
|
return data_ + offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get element |index| of an array of objects of length |length| starting at
|
||||||
|
// |offset| bytes. Return NULL if out of bounds.
|
||||||
|
const void* GetArrayElement(size_t offset, size_t length, unsigned index) {
|
||||||
|
const size_t element_offset = offset + index * length;
|
||||||
|
return GetObject(element_offset, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a new range which is a subset of this range.
|
||||||
|
MMappedRange Subrange(const MDLocationDescriptor& location) const {
|
||||||
|
if (location.rva > length_ ||
|
||||||
|
location.rva + location.data_size < location.rva ||
|
||||||
|
location.rva + location.data_size > length_) {
|
||||||
|
return MMappedRange(NULL, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return MMappedRange(data_ + location.rva, location.data_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t* data() const { return data_; }
|
||||||
|
size_t length() const { return length_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
const uint8_t* const data_;
|
||||||
|
const size_t length_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Dynamically determines the byte sex of the system. Returns non-zero
|
||||||
|
* for big-endian machines.
|
||||||
|
*/
|
||||||
|
static inline int sex() {
|
||||||
|
int probe = 1;
|
||||||
|
return !*(char *)&probe;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct elf_timeval { /* Time value with microsecond resolution */
|
||||||
|
long tv_sec; /* Seconds */
|
||||||
|
long tv_usec; /* Microseconds */
|
||||||
|
} elf_timeval;
|
||||||
|
|
||||||
|
typedef struct elf_siginfo { /* Information about signal (unused) */
|
||||||
|
int32_t si_signo; /* Signal number */
|
||||||
|
int32_t si_code; /* Extra code */
|
||||||
|
int32_t si_errno; /* Errno */
|
||||||
|
} elf_siginfo;
|
||||||
|
|
||||||
|
typedef struct prstatus { /* Information about thread; includes CPU reg*/
|
||||||
|
elf_siginfo pr_info; /* Info associated with signal */
|
||||||
|
uint16_t pr_cursig; /* Current signal */
|
||||||
|
unsigned long pr_sigpend; /* Set of pending signals */
|
||||||
|
unsigned long pr_sighold; /* Set of held signals */
|
||||||
|
pid_t pr_pid; /* Process ID */
|
||||||
|
pid_t pr_ppid; /* Parent's process ID */
|
||||||
|
pid_t pr_pgrp; /* Group ID */
|
||||||
|
pid_t pr_sid; /* Session ID */
|
||||||
|
elf_timeval pr_utime; /* User time */
|
||||||
|
elf_timeval pr_stime; /* System time */
|
||||||
|
elf_timeval pr_cutime; /* Cumulative user time */
|
||||||
|
elf_timeval pr_cstime; /* Cumulative system time */
|
||||||
|
user_regs_struct pr_reg; /* CPU registers */
|
||||||
|
uint32_t pr_fpvalid; /* True if math co-processor being used */
|
||||||
|
} prstatus;
|
||||||
|
|
||||||
|
typedef struct prpsinfo { /* Information about process */
|
||||||
|
unsigned char pr_state; /* Numeric process state */
|
||||||
|
char pr_sname; /* Char for pr_state */
|
||||||
|
unsigned char pr_zomb; /* Zombie */
|
||||||
|
signed char pr_nice; /* Nice val */
|
||||||
|
unsigned long pr_flag; /* Flags */
|
||||||
|
#if defined(__x86_64__) || defined(__mips__)
|
||||||
|
uint32_t pr_uid; /* User ID */
|
||||||
|
uint32_t pr_gid; /* Group ID */
|
||||||
|
#else
|
||||||
|
uint16_t pr_uid; /* User ID */
|
||||||
|
uint16_t pr_gid; /* Group ID */
|
||||||
|
#endif
|
||||||
|
pid_t pr_pid; /* Process ID */
|
||||||
|
pid_t pr_ppid; /* Parent's process ID */
|
||||||
|
pid_t pr_pgrp; /* Group ID */
|
||||||
|
pid_t pr_sid; /* Session ID */
|
||||||
|
char pr_fname[16]; /* Filename of executable */
|
||||||
|
char pr_psargs[80]; /* Initial part of arg list */
|
||||||
|
} prpsinfo;
|
||||||
|
|
||||||
|
// We parse the minidump file and keep the parsed information in this structure.
|
||||||
|
struct CrashedProcess {
|
||||||
|
CrashedProcess()
|
||||||
|
: crashing_tid(-1),
|
||||||
|
auxv(NULL),
|
||||||
|
auxv_length(0) {
|
||||||
|
memset(&prps, 0, sizeof(prps));
|
||||||
|
prps.pr_sname = 'R';
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Mapping {
|
||||||
|
uint64_t start_address, end_address;
|
||||||
|
};
|
||||||
|
std::vector<Mapping> mappings;
|
||||||
|
|
||||||
|
pid_t crashing_tid;
|
||||||
|
int fatal_signal;
|
||||||
|
|
||||||
|
struct Thread {
|
||||||
|
pid_t tid;
|
||||||
|
user_regs_struct regs;
|
||||||
|
user_fpregs_struct fpregs;
|
||||||
|
user_fpxregs_struct fpxregs;
|
||||||
|
uintptr_t stack_addr;
|
||||||
|
const uint8_t* stack;
|
||||||
|
size_t stack_length;
|
||||||
|
};
|
||||||
|
std::vector<Thread> threads;
|
||||||
|
|
||||||
|
const uint8_t* auxv;
|
||||||
|
size_t auxv_length;
|
||||||
|
|
||||||
|
prpsinfo prps;
|
||||||
|
};
|
||||||
|
|
||||||
|
static uint32_t
|
||||||
|
U32(const uint8_t* data) {
|
||||||
|
uint32_t v;
|
||||||
|
memcpy(&v, data, sizeof(v));
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint16_t
|
||||||
|
U16(const uint8_t* data) {
|
||||||
|
uint16_t v;
|
||||||
|
memcpy(&v, data, sizeof(v));
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(__i386__)
|
||||||
|
static void
|
||||||
|
ParseThreadRegisters(CrashedProcess::Thread* thread, MMappedRange range) {
|
||||||
|
const MDRawContextX86* rawregs =
|
||||||
|
(const MDRawContextX86*) range.GetObject(0, sizeof(MDRawContextX86));
|
||||||
|
|
||||||
|
thread->regs.ebx = rawregs->ebx;
|
||||||
|
thread->regs.ecx = rawregs->ecx;
|
||||||
|
thread->regs.edx = rawregs->edx;
|
||||||
|
thread->regs.esi = rawregs->esi;
|
||||||
|
thread->regs.edi = rawregs->edi;
|
||||||
|
thread->regs.ebp = rawregs->ebp;
|
||||||
|
thread->regs.eax = rawregs->eax;
|
||||||
|
thread->regs.xds = rawregs->ds;
|
||||||
|
thread->regs.xes = rawregs->es;
|
||||||
|
thread->regs.xfs = rawregs->fs;
|
||||||
|
thread->regs.xgs = rawregs->gs;
|
||||||
|
thread->regs.orig_eax = rawregs->eax;
|
||||||
|
thread->regs.eip = rawregs->eip;
|
||||||
|
thread->regs.xcs = rawregs->cs;
|
||||||
|
thread->regs.eflags = rawregs->eflags;
|
||||||
|
thread->regs.esp = rawregs->esp;
|
||||||
|
thread->regs.xss = rawregs->ss;
|
||||||
|
|
||||||
|
thread->fpregs.cwd = rawregs->float_save.control_word;
|
||||||
|
thread->fpregs.swd = rawregs->float_save.status_word;
|
||||||
|
thread->fpregs.twd = rawregs->float_save.tag_word;
|
||||||
|
thread->fpregs.fip = rawregs->float_save.error_offset;
|
||||||
|
thread->fpregs.fcs = rawregs->float_save.error_selector;
|
||||||
|
thread->fpregs.foo = rawregs->float_save.data_offset;
|
||||||
|
thread->fpregs.fos = rawregs->float_save.data_selector;
|
||||||
|
memcpy(thread->fpregs.st_space, rawregs->float_save.register_area,
|
||||||
|
10 * 8);
|
||||||
|
|
||||||
|
thread->fpxregs.cwd = rawregs->float_save.control_word;
|
||||||
|
thread->fpxregs.swd = rawregs->float_save.status_word;
|
||||||
|
thread->fpxregs.twd = rawregs->float_save.tag_word;
|
||||||
|
thread->fpxregs.fop = U16(rawregs->extended_registers + 6);
|
||||||
|
thread->fpxregs.fip = U16(rawregs->extended_registers + 8);
|
||||||
|
thread->fpxregs.fcs = U16(rawregs->extended_registers + 12);
|
||||||
|
thread->fpxregs.foo = U16(rawregs->extended_registers + 16);
|
||||||
|
thread->fpxregs.fos = U16(rawregs->extended_registers + 20);
|
||||||
|
thread->fpxregs.mxcsr = U32(rawregs->extended_registers + 24);
|
||||||
|
memcpy(thread->fpxregs.st_space, rawregs->extended_registers + 32, 128);
|
||||||
|
memcpy(thread->fpxregs.xmm_space, rawregs->extended_registers + 160, 128);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#error "This code has not been ported to your platform yet"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static void
|
||||||
|
ParseThreadList(CrashedProcess* crashinfo, MMappedRange range,
|
||||||
|
const MMappedRange& full_file) {
|
||||||
|
const uint32_t num_threads =
|
||||||
|
*(const uint32_t*) range.GetObject(0, sizeof(uint32_t));
|
||||||
|
for (unsigned i = 0; i < num_threads; ++i) {
|
||||||
|
CrashedProcess::Thread thread;
|
||||||
|
memset(&thread, 0, sizeof(thread));
|
||||||
|
const MDRawThread* rawthread =
|
||||||
|
(MDRawThread*) range.GetArrayElement(sizeof(uint32_t),
|
||||||
|
sizeof(MDRawThread), i);
|
||||||
|
thread.tid = rawthread->thread_id;
|
||||||
|
thread.stack_addr = rawthread->stack.start_of_memory_range;
|
||||||
|
MMappedRange stack_range = full_file.Subrange(rawthread->stack.memory);
|
||||||
|
thread.stack = stack_range.data();
|
||||||
|
thread.stack_length = rawthread->stack.memory.data_size;
|
||||||
|
|
||||||
|
ParseThreadRegisters(&thread,
|
||||||
|
full_file.Subrange(rawthread->thread_context));
|
||||||
|
|
||||||
|
crashinfo->threads.push_back(thread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
ParseAuxVector(CrashedProcess* crashinfo, MMappedRange range) {
|
||||||
|
crashinfo->auxv = range.data();
|
||||||
|
crashinfo->auxv_length = range.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
ParseCmdLine(CrashedProcess* crashinfo, MMappedRange range) {
|
||||||
|
const char* cmdline = (const char*) range.data();
|
||||||
|
for (size_t i = 0; i < range.length(); ++i) {
|
||||||
|
if (cmdline[i] == 0) {
|
||||||
|
static const size_t fname_len = sizeof(crashinfo->prps.pr_fname) - 1;
|
||||||
|
static const size_t args_len = sizeof(crashinfo->prps.pr_psargs) - 1;
|
||||||
|
memset(crashinfo->prps.pr_fname, 0, fname_len + 1);
|
||||||
|
memset(crashinfo->prps.pr_psargs, 0, args_len + 1);
|
||||||
|
const char* binary_name = strrchr(cmdline, '/');
|
||||||
|
if (binary_name) {
|
||||||
|
binary_name++;
|
||||||
|
const unsigned len = strlen(binary_name);
|
||||||
|
memcpy(crashinfo->prps.pr_fname, binary_name,
|
||||||
|
len > fname_len ? fname_len : len);
|
||||||
|
} else {
|
||||||
|
memcpy(crashinfo->prps.pr_fname, cmdline,
|
||||||
|
i > fname_len ? fname_len : i);
|
||||||
|
}
|
||||||
|
|
||||||
|
const unsigned len = range.length() > args_len ?
|
||||||
|
args_len : range.length();
|
||||||
|
memcpy(crashinfo->prps.pr_psargs, cmdline, len);
|
||||||
|
for (unsigned i = 0; i < len; ++i) {
|
||||||
|
if (crashinfo->prps.pr_psargs[i] == 0)
|
||||||
|
crashinfo->prps.pr_psargs[i] = ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
ParseExceptionStream(CrashedProcess* crashinfo, MMappedRange range) {
|
||||||
|
const MDRawExceptionStream* exp =
|
||||||
|
(MDRawExceptionStream*) range.GetObject(0, sizeof(MDRawExceptionStream));
|
||||||
|
crashinfo->crashing_tid = exp->thread_id;
|
||||||
|
crashinfo->fatal_signal = (int) exp->exception_record.exception_code;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
WriteThread(const CrashedProcess::Thread& thread, int fatal_signal) {
|
||||||
|
struct prstatus pr;
|
||||||
|
memset(&pr, 0, sizeof(pr));
|
||||||
|
|
||||||
|
pr.pr_info.si_signo = fatal_signal;
|
||||||
|
pr.pr_cursig = fatal_signal;
|
||||||
|
pr.pr_pid = thread.tid;
|
||||||
|
memcpy(&pr.pr_reg, &thread.regs, sizeof(user_regs_struct));
|
||||||
|
|
||||||
|
Nhdr nhdr;
|
||||||
|
memset(&nhdr, 0, sizeof(nhdr));
|
||||||
|
nhdr.n_namesz = 5;
|
||||||
|
nhdr.n_descsz = sizeof(struct prstatus);
|
||||||
|
nhdr.n_type = NT_PRSTATUS;
|
||||||
|
if (!writea(1, &nhdr, sizeof(nhdr)) ||
|
||||||
|
!writea(1, "CORE\0\0\0\0", 8) ||
|
||||||
|
!writea(1, &pr, sizeof(struct prstatus))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
nhdr.n_descsz = sizeof(user_fpregs_struct);
|
||||||
|
nhdr.n_type = NT_FPREGSET;
|
||||||
|
if (!writea(1, &nhdr, sizeof(nhdr)) ||
|
||||||
|
!writea(1, "CORE\0\0\0\0", 8) ||
|
||||||
|
!writea(1, &thread.fpregs, sizeof(user_fpregs_struct))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
nhdr.n_descsz = sizeof(user_fpxregs_struct);
|
||||||
|
nhdr.n_type = NT_PRXFPREG;
|
||||||
|
if (!writea(1, &nhdr, sizeof(nhdr)) ||
|
||||||
|
!writea(1, "LINUX\0\0\0", 8) ||
|
||||||
|
!writea(1, &thread.fpxregs, sizeof(user_fpxregs_struct))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
ParseModuleStream(CrashedProcess* crashinfo, MMappedRange range) {
|
||||||
|
const uint32_t num_mappings =
|
||||||
|
*(const uint32_t*) range.GetObject(0, sizeof(uint32_t));
|
||||||
|
for (unsigned i = 0; i < num_mappings; ++i) {
|
||||||
|
CrashedProcess::Mapping mapping;
|
||||||
|
const MDRawModule* rawmodule =
|
||||||
|
(MDRawModule*) range.GetArrayElement(sizeof(uint32_t),
|
||||||
|
sizeof(MDRawModule), i);
|
||||||
|
mapping.start_address = rawmodule->base_of_image;
|
||||||
|
mapping.end_address = rawmodule->size_of_image + rawmodule->base_of_image;
|
||||||
|
|
||||||
|
crashinfo->mappings.push_back(mapping);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char** argv) {
|
||||||
|
if (argc != 2)
|
||||||
|
return usage(argv[0]);
|
||||||
|
|
||||||
|
const int fd = open(argv[1], O_RDONLY);
|
||||||
|
if (fd < 0)
|
||||||
|
return usage(argv[0]);
|
||||||
|
|
||||||
|
struct stat st;
|
||||||
|
fstat(fd, &st);
|
||||||
|
|
||||||
|
const void* bytes = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
|
||||||
|
close(fd);
|
||||||
|
if (bytes == MAP_FAILED) {
|
||||||
|
perror("Failed to mmap dump file");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
MMappedRange dump(bytes, st.st_size);
|
||||||
|
|
||||||
|
const MDRawHeader* header =
|
||||||
|
(const MDRawHeader*) dump.GetObject(0, sizeof(MDRawHeader));
|
||||||
|
|
||||||
|
CrashedProcess crashinfo;
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < header->stream_count; ++i) {
|
||||||
|
const MDRawDirectory* dirent =
|
||||||
|
(const MDRawDirectory*) dump.GetArrayElement(
|
||||||
|
header->stream_directory_rva, sizeof(MDRawDirectory), i);
|
||||||
|
switch (dirent->stream_type) {
|
||||||
|
case MD_THREAD_LIST_STREAM:
|
||||||
|
ParseThreadList(&crashinfo, dump.Subrange(dirent->location), dump);
|
||||||
|
break;
|
||||||
|
case MD_LINUX_AUXV:
|
||||||
|
ParseAuxVector(&crashinfo, dump.Subrange(dirent->location));
|
||||||
|
break;
|
||||||
|
case MD_LINUX_CMD_LINE:
|
||||||
|
ParseCmdLine(&crashinfo, dump.Subrange(dirent->location));
|
||||||
|
break;
|
||||||
|
case MD_EXCEPTION_STREAM:
|
||||||
|
ParseExceptionStream(&crashinfo, dump.Subrange(dirent->location));
|
||||||
|
break;
|
||||||
|
case MD_MODULE_LIST_STREAM:
|
||||||
|
ParseModuleStream(&crashinfo, dump.Subrange(dirent->location));
|
||||||
|
default:
|
||||||
|
fprintf(stderr, "Skipping %x\n", dirent->stream_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the ELF header. The file will look like:
|
||||||
|
// ELF header
|
||||||
|
// Phdr for the PT_NOTE
|
||||||
|
// Phdr for each of the thread stacks
|
||||||
|
// PT_NOTE
|
||||||
|
// each of the thread stacks
|
||||||
|
Ehdr ehdr;
|
||||||
|
memset(&ehdr, 0, sizeof(Ehdr));
|
||||||
|
ehdr.e_ident[0] = ELFMAG0;
|
||||||
|
ehdr.e_ident[1] = ELFMAG1;
|
||||||
|
ehdr.e_ident[2] = ELFMAG2;
|
||||||
|
ehdr.e_ident[3] = ELFMAG3;
|
||||||
|
ehdr.e_ident[4] = ELF_CLASS;
|
||||||
|
ehdr.e_ident[5] = sex() ? ELFDATA2MSB : ELFDATA2LSB;
|
||||||
|
ehdr.e_ident[6] = EV_CURRENT;
|
||||||
|
ehdr.e_type = ET_CORE;
|
||||||
|
ehdr.e_machine = ELF_ARCH;
|
||||||
|
ehdr.e_version = EV_CURRENT;
|
||||||
|
ehdr.e_phoff = sizeof(Ehdr);
|
||||||
|
ehdr.e_ehsize = sizeof(Ehdr);
|
||||||
|
ehdr.e_phentsize= sizeof(Phdr);
|
||||||
|
ehdr.e_phnum = 1 + crashinfo.threads.size() + crashinfo.mappings.size();
|
||||||
|
ehdr.e_shentsize= sizeof(Shdr);
|
||||||
|
if (!writea(1, &ehdr, sizeof(Ehdr)))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
size_t offset = sizeof(Ehdr) +
|
||||||
|
(1 + crashinfo.threads.size() +
|
||||||
|
crashinfo.mappings.size()) * sizeof(Phdr);
|
||||||
|
size_t filesz = sizeof(Nhdr) + 8 + sizeof(prpsinfo) +
|
||||||
|
// sizeof(Nhdr) + 8 + sizeof(user) +
|
||||||
|
sizeof(Nhdr) + 8 + crashinfo.auxv_length +
|
||||||
|
crashinfo.threads.size() * (
|
||||||
|
(sizeof(Nhdr) + 8 + sizeof(prstatus)) +
|
||||||
|
sizeof(Nhdr) + 8 + sizeof(user_fpregs_struct) +
|
||||||
|
sizeof(Nhdr) + 8 + sizeof(user_fpxregs_struct));
|
||||||
|
|
||||||
|
Phdr phdr;
|
||||||
|
memset(&phdr, 0, sizeof(Phdr));
|
||||||
|
phdr.p_type = PT_NOTE;
|
||||||
|
phdr.p_offset = offset;
|
||||||
|
phdr.p_filesz = filesz;
|
||||||
|
if (!writea(1, &phdr, sizeof(phdr)))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
phdr.p_type = PT_LOAD;
|
||||||
|
phdr.p_align = getpagesize();
|
||||||
|
size_t note_align = phdr.p_align - ((offset+filesz) % phdr.p_align);
|
||||||
|
if (note_align == phdr.p_align)
|
||||||
|
note_align = 0;
|
||||||
|
offset += note_align;
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < crashinfo.threads.size(); ++i) {
|
||||||
|
const CrashedProcess::Thread& thread = crashinfo.threads[i];
|
||||||
|
offset += filesz;
|
||||||
|
filesz = thread.stack_length;
|
||||||
|
phdr.p_offset = offset;
|
||||||
|
phdr.p_vaddr = thread.stack_addr;
|
||||||
|
phdr.p_filesz = phdr.p_memsz = filesz;
|
||||||
|
phdr.p_flags = PF_R | PF_W;
|
||||||
|
if (!writea(1, &phdr, sizeof(phdr)))
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < crashinfo.mappings.size(); ++i) {
|
||||||
|
const CrashedProcess::Mapping& mapping = crashinfo.mappings[i];
|
||||||
|
phdr.p_offset = 0;
|
||||||
|
phdr.p_vaddr = mapping.start_address;
|
||||||
|
phdr.p_filesz = 0;
|
||||||
|
phdr.p_flags = PF_R;
|
||||||
|
phdr.p_memsz = mapping.end_address - mapping.start_address;
|
||||||
|
if (!writea(1, &phdr, sizeof(phdr)))
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Nhdr nhdr;
|
||||||
|
memset(&nhdr, 0, sizeof(nhdr));
|
||||||
|
nhdr.n_namesz = 5;
|
||||||
|
nhdr.n_descsz = sizeof(prpsinfo);
|
||||||
|
nhdr.n_type = NT_PRPSINFO;
|
||||||
|
if (!writea(1, &nhdr, sizeof(nhdr)) ||
|
||||||
|
!writea(1, "CORE\0\0\0\0", 8) ||
|
||||||
|
!writea(1, &crashinfo.prps, sizeof(prpsinfo))) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
nhdr.n_descsz = crashinfo.auxv_length;
|
||||||
|
nhdr.n_type = NT_AUXV;
|
||||||
|
if (!writea(1, &nhdr, sizeof(nhdr)) ||
|
||||||
|
!writea(1, "CORE\0\0\0\0", 8) ||
|
||||||
|
!writea(1, &crashinfo.auxv, crashinfo.auxv_length)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < crashinfo.threads.size(); ++i) {
|
||||||
|
if (crashinfo.threads[i].tid == crashinfo.crashing_tid) {
|
||||||
|
WriteThread(crashinfo.threads[i], crashinfo.fatal_signal);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < crashinfo.threads.size(); ++i) {
|
||||||
|
if (crashinfo.threads[i].tid != crashinfo.crashing_tid)
|
||||||
|
WriteThread(crashinfo.threads[i], 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (note_align) {
|
||||||
|
char scratch[note_align];
|
||||||
|
memset(scratch, 0, sizeof(scratch));
|
||||||
|
if (!writea(1, scratch, sizeof(scratch)))
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < crashinfo.threads.size(); ++i) {
|
||||||
|
const CrashedProcess::Thread& thread = crashinfo.threads[i];
|
||||||
|
if (!writea(1, thread.stack, thread.stack_length))
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
munmap(const_cast<void*>(bytes), st.st_size);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user