Compare commits
3 Commits
main
...
linux-rewr
Author | SHA1 | Date | |
---|---|---|---|
|
3e7863fc84 | ||
|
de8df2f34a | ||
|
60e5e9547a |
@ -1,55 +1,45 @@
|
||||
CXX=g++
|
||||
CC=gcc
|
||||
|
||||
CXXFLAGS=-gstabs+ -I../../.. -Wall -D_REENTRANT
|
||||
CXXFLAGS=-gstabs+ -I../../../ -I../../../testing/gtest/include -I../../../testing/include -I../../../testing/gtest -D_REENTRANT -m32
|
||||
CFLAGS=$(CXXFLAGS)
|
||||
LDFLAGS=-lpthread
|
||||
|
||||
OBJ_DIR=.
|
||||
BIN_DIR=.
|
||||
|
||||
THREAD_SRC=linux_thread.cc
|
||||
SHARE_SRC=../../minidump_file_writer.cc\
|
||||
../../../common/string_conversion.cc\
|
||||
../../../common/linux/file_id.cc\
|
||||
minidump_generator.cc
|
||||
HANDLER_SRC=exception_handler.cc\
|
||||
../../../common/linux/guid_creator.cc
|
||||
SHARE_C_SRC=../../../common/convert_UTF.c
|
||||
TEST_CC_SRC=exception_handler_unittest.cc \
|
||||
exception_handler.cc \
|
||||
../../../testing/gtest/src/gtest-all.cc \
|
||||
../../../common/linux/guid_creator.cc \
|
||||
../minidump_writer/minidump_writer.cc \
|
||||
../../minidump_file_writer.cc \
|
||||
../minidump_writer/linux_dumper.cc \
|
||||
../../../testing/gtest/src/gtest_main.cc \
|
||||
../../../common/string_conversion.cc \
|
||||
../minidump_writer/directory_reader_unittest.cc \
|
||||
../minidump_writer/line_reader_unittest.cc \
|
||||
../minidump_writer/linux_dumper_unittest.cc \
|
||||
../minidump_writer/minidump_writer_unittest.cc
|
||||
|
||||
THREAD_TEST_SRC=linux_thread_test.cc
|
||||
MINIDUMP_TEST_SRC=minidump_test.cc
|
||||
EXCEPTION_TEST_SRC=exception_handler_test.cc
|
||||
TEST_C_SRC = ../../../common/convert_UTF.c
|
||||
|
||||
THREAD_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o,$(THREAD_SRC))
|
||||
SHARE_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o,$(SHARE_SRC))
|
||||
HANDLER_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o,$(HANDLER_SRC))
|
||||
SHARE_C_OBJ=$(patsubst %.c,$(OBJ_DIR)/%.o,$(SHARE_C_SRC)) md5.o
|
||||
THREAD_TEST_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o, $(THREAD_TEST_SRC))\
|
||||
$(THREAD_OBJ)
|
||||
MINIDUMP_TEST_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o, $(MINIDUMP_TEST_SRC))\
|
||||
$(THREAD_OBJ) $(SHARE_OBJ) $(SHARE_C_OBJ)
|
||||
EXCEPTION_TEST_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o, $(EXCEPTION_TEST_SRC))\
|
||||
$(THREAD_OBJ) $(SHARE_OBJ) $(SHARE_C_OBJ) $(HANDLER_OBJ)
|
||||
TEST_CC_OBJ=$(patsubst %.cc, $(OBJ_DIR)/%.o,$(TEST_CC_SRC))
|
||||
TEST_C_OBJ=$(patsubst %.c, $(OBJ_DIR)/%.o, $(TEST_C_SRC))
|
||||
|
||||
BIN=$(BIN_DIR)/minidump_test\
|
||||
$(BIN_DIR)/linux_thread_test\
|
||||
$(BIN_DIR)/exception_handler_test
|
||||
LINUX_CLIENT_BIN=$(BIN_DIR)/linux_client_test
|
||||
|
||||
BIN=$(LINUX_CLIENT_BIN)
|
||||
|
||||
.PHONY:all clean
|
||||
|
||||
check:$(BIN)
|
||||
$(LINUX_CLIENT_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 $@
|
||||
|
||||
$(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:
|
||||
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.
|
||||
//
|
||||
// Author: Li Liu
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
@ -29,48 +27,87 @@
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include <signal.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
// The ExceptionHandler object installs signal handlers for a number of
|
||||
// signals. We rely on the signal handler running on the thread which crashed
|
||||
// in order to identify it. This is true of the synchronous signals (SEGV etc),
|
||||
// but not true of ABRT. Thus, if you send ABRT to yourself in a program which
|
||||
// uses ExceptionHandler, you need to use tgkill to direct it to the current
|
||||
// thread.
|
||||
//
|
||||
// The signal flow looks like this:
|
||||
//
|
||||
// SignalHandler (uses a global stack of ExceptionHandler objects to find
|
||||
// | one to handle the signal. If the first rejects it, try
|
||||
// | the second etc...)
|
||||
// V
|
||||
// HandleSignal ----------------------------| (clones a new process which
|
||||
// | | shares an address space with
|
||||
// (wait for cloned | the crashed process. This
|
||||
// process) | allows us to ptrace the crashed
|
||||
// | | process)
|
||||
// V V
|
||||
// (set signal handler to ThreadEntry (static function to bounce
|
||||
// SIG_DFL and rethrow, | back into the object)
|
||||
// killing the crashed |
|
||||
// process) V
|
||||
// DoDump (writes minidump)
|
||||
// |
|
||||
// V
|
||||
// sys_exit
|
||||
//
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
#include <linux/limits.h>
|
||||
// This code is a little fragmented. Different functions of the ExceptionHandler
|
||||
// class run in a number of different contexts. Some of them run in a normal
|
||||
// context and are easy to code, others run in a compromised context and the
|
||||
// restrictions at the top of minidump_writer.cc apply: no libc and use the
|
||||
// alternative malloc. Each function should have comment above it detailing the
|
||||
// context which it runs in.
|
||||
|
||||
#include "client/linux/handler/exception_handler.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/limits.h>
|
||||
#include <sched.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/signal.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/ucontext.h>
|
||||
#include <sys/user.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "common/linux/linux_libc_support.h"
|
||||
#include "common/linux/linux_syscall_support.h"
|
||||
#include "common/linux/memory.h"
|
||||
#include "client/linux/minidump_writer//minidump_writer.h"
|
||||
#include "common/linux/guid_creator.h"
|
||||
#include "google_breakpad/common/minidump_format.h"
|
||||
|
||||
// A wrapper for the tgkill syscall: send a signal to a specific thread.
|
||||
static int tgkill(pid_t tgid, pid_t tid, int sig) {
|
||||
syscall(__NR_tgkill, tgid, tid, sig);
|
||||
}
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
// Signals that we are interested.
|
||||
int SigTable[] = {
|
||||
#if defined(SIGSEGV)
|
||||
SIGSEGV,
|
||||
#endif
|
||||
#ifdef SIGABRT
|
||||
SIGABRT,
|
||||
#endif
|
||||
#ifdef SIGFPE
|
||||
SIGFPE,
|
||||
#endif
|
||||
#ifdef SIGILL
|
||||
SIGILL,
|
||||
#endif
|
||||
#ifdef SIGBUS
|
||||
SIGBUS,
|
||||
#endif
|
||||
// The list of signals which we consider to be crashes. The default action for
|
||||
// all these signals must be Core (see man 7 signal) because we rethrow the
|
||||
// signal after handling it and expect that it'll be fatal.
|
||||
static const int kExceptionSignals[] = {
|
||||
SIGSEGV, SIGABRT, SIGFPE, SIGILL, SIGBUS, -1
|
||||
};
|
||||
|
||||
std::vector<ExceptionHandler*> *ExceptionHandler::handler_stack_ = NULL;
|
||||
int ExceptionHandler::handler_stack_index_ = 0;
|
||||
// We can stack multiple exception handlers. In that case, this is the global
|
||||
// which holds the stack.
|
||||
std::vector<ExceptionHandler*>* ExceptionHandler::handler_stack_ = NULL;
|
||||
unsigned ExceptionHandler::handler_stack_index_ = 0;
|
||||
pthread_mutex_t ExceptionHandler::handler_stack_mutex_ =
|
||||
PTHREAD_MUTEX_INITIALIZER;
|
||||
PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
ExceptionHandler::ExceptionHandler(const string &dump_path,
|
||||
// Runs before crashing: normal context.
|
||||
ExceptionHandler::ExceptionHandler(const std::string &dump_path,
|
||||
FilterCallback filter,
|
||||
MinidumpCallback callback,
|
||||
void *callback_context,
|
||||
@ -79,212 +116,76 @@ ExceptionHandler::ExceptionHandler(const string &dump_path,
|
||||
callback_(callback),
|
||||
callback_context_(callback_context),
|
||||
dump_path_(),
|
||||
installed_handler_(install_handler) {
|
||||
handler_installed_(install_handler),
|
||||
crash_handler_(NULL) {
|
||||
set_dump_path(dump_path);
|
||||
|
||||
act_.sa_handler = HandleException;
|
||||
act_.sa_flags = SA_ONSTACK;
|
||||
sigemptyset(&act_.sa_mask);
|
||||
// now, make sure we're blocking all the signals we are handling
|
||||
// when we're handling any of them
|
||||
for ( size_t i = 0; i < sizeof(SigTable) / sizeof(SigTable[0]); ++i) {
|
||||
sigaddset(&act_.sa_mask, SigTable[i]);
|
||||
}
|
||||
|
||||
if (install_handler) {
|
||||
SetupHandler();
|
||||
InstallHandlers();
|
||||
|
||||
pthread_mutex_lock(&handler_stack_mutex_);
|
||||
if (handler_stack_ == NULL)
|
||||
handler_stack_ = new std::vector<ExceptionHandler *>;
|
||||
handler_stack_->push_back(this);
|
||||
if (handler_stack_ == NULL)
|
||||
handler_stack_ = new std::vector<ExceptionHandler *>;
|
||||
handler_stack_->push_back(this);
|
||||
pthread_mutex_unlock(&handler_stack_mutex_);
|
||||
}
|
||||
}
|
||||
|
||||
// Runs before crashing: normal context.
|
||||
ExceptionHandler::~ExceptionHandler() {
|
||||
TeardownAllHandler();
|
||||
pthread_mutex_lock(&handler_stack_mutex_);
|
||||
if (handler_stack_->back() == this) {
|
||||
handler_stack_->pop_back();
|
||||
} else {
|
||||
fprintf(stderr, "warning: removing Breakpad handler out of order\n");
|
||||
for (std::vector<ExceptionHandler *>::iterator iterator =
|
||||
handler_stack_->begin();
|
||||
iterator != handler_stack_->end();
|
||||
++iterator) {
|
||||
if (*iterator == this) {
|
||||
handler_stack_->erase(iterator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (handler_stack_->empty()) {
|
||||
// When destroying the last ExceptionHandler that installed a handler,
|
||||
// clean up the handler stack.
|
||||
delete handler_stack_;
|
||||
handler_stack_ = NULL;
|
||||
}
|
||||
pthread_mutex_unlock(&handler_stack_mutex_);
|
||||
UninstallHandlers();
|
||||
}
|
||||
|
||||
bool ExceptionHandler::WriteMinidump() {
|
||||
bool success = InternalWriteMinidump(0, 0, NULL);
|
||||
UpdateNextID();
|
||||
return success;
|
||||
}
|
||||
// Runs before crashing: normal context.
|
||||
bool ExceptionHandler::InstallHandlers() {
|
||||
// We run the signal handlers on an alternative stack because we might have
|
||||
// crashed because of a stack overflow.
|
||||
|
||||
// static
|
||||
bool ExceptionHandler::WriteMinidump(const string &dump_path,
|
||||
MinidumpCallback callback,
|
||||
void *callback_context) {
|
||||
ExceptionHandler handler(dump_path, NULL, callback,
|
||||
callback_context, false);
|
||||
return handler.InternalWriteMinidump(0, 0, NULL);
|
||||
}
|
||||
// We use this value rather than SIGSTKSZ because we would end up overrunning
|
||||
// such a small stack.
|
||||
static const unsigned kSigStackSize = 8192;
|
||||
|
||||
void ExceptionHandler::SetupHandler() {
|
||||
// Signal on a different stack to avoid using the stack
|
||||
// of the crashing thread.
|
||||
struct sigaltstack sig_stack;
|
||||
sig_stack.ss_sp = malloc(MINSIGSTKSZ);
|
||||
if (sig_stack.ss_sp == NULL)
|
||||
return;
|
||||
sig_stack.ss_size = MINSIGSTKSZ;
|
||||
sig_stack.ss_flags = 0;
|
||||
signal_stack = malloc(kSigStackSize);
|
||||
stack_t stack;
|
||||
memset(&stack, 0, sizeof(stack));
|
||||
stack.ss_sp = signal_stack;
|
||||
stack.ss_size = kSigStackSize;
|
||||
|
||||
if (sigaltstack(&sig_stack, NULL) < 0)
|
||||
return;
|
||||
for (size_t i = 0; i < sizeof(SigTable) / sizeof(SigTable[0]); ++i)
|
||||
SetupHandler(SigTable[i]);
|
||||
}
|
||||
|
||||
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_))
|
||||
if (sigaltstack(&stack, NULL) == -1)
|
||||
return false;
|
||||
|
||||
bool success = false;
|
||||
// Block all the signals we want to process when writting minidump.
|
||||
// We don't want it to be interrupted.
|
||||
sigset_t sig_blocked, sig_old;
|
||||
bool blocked = true;
|
||||
sigfillset(&sig_blocked);
|
||||
for (size_t i = 0; i < sizeof(SigTable) / sizeof(SigTable[0]); ++i)
|
||||
sigdelset(&sig_blocked, SigTable[i]);
|
||||
if (sigprocmask(SIG_BLOCK, &sig_blocked, &sig_old) != 0) {
|
||||
blocked = false;
|
||||
fprintf(stderr, "google_breakpad::ExceptionHandler::HandleException: "
|
||||
"failed to block signals.\n");
|
||||
struct sigaction sa;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sigemptyset(&sa.sa_mask);
|
||||
|
||||
// mask all exception signals when we're handling one of them.
|
||||
for (unsigned i = 0; kExceptionSignals[i] != -1; ++i)
|
||||
sigaddset(&sa.sa_mask, kExceptionSignals[i]);
|
||||
|
||||
sa.sa_sigaction = SignalHandler;
|
||||
sa.sa_flags = SA_ONSTACK | SA_SIGINFO;
|
||||
|
||||
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() {
|
||||
GUID guid;
|
||||
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
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) 2006, Google Inc.
|
||||
// Copyright (c) 2009, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Author: Li Liu
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
@ -29,26 +27,16 @@
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H__
|
||||
#define CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H__
|
||||
#ifndef 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 <string>
|
||||
|
||||
#include "client/linux/handler/minidump_generator.h"
|
||||
|
||||
// Context information when exception occured.
|
||||
struct sigcontex;
|
||||
#include <signal.h>
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
using std::string;
|
||||
|
||||
//
|
||||
// ExceptionHandler
|
||||
//
|
||||
// ExceptionHandler can write a minidump file when an exception occurs,
|
||||
@ -73,7 +61,6 @@ using std::string;
|
||||
//
|
||||
// Caller should try to make the callbacks as crash-friendly as possible,
|
||||
// it should avoid use heap memory allocation as much as possible.
|
||||
//
|
||||
class ExceptionHandler {
|
||||
public:
|
||||
// A callback function to run before Breakpad performs any substantial
|
||||
@ -108,6 +95,15 @@ class ExceptionHandler {
|
||||
void *context,
|
||||
bool succeeded);
|
||||
|
||||
// In certain cases, a user may wish to handle the generation of the minidump
|
||||
// themselves. In this case, they can install a handler callback which is
|
||||
// called when a crash has occured. If this function returns true, no other
|
||||
// processing of occurs and the process will shortly be crashed. If this
|
||||
// returns false, the normal processing continues.
|
||||
typedef bool (*HandlerCallback)(const void* crash_context,
|
||||
size_t crash_context_size,
|
||||
void* context);
|
||||
|
||||
// Creates a new ExceptionHandler instance to handle writing minidumps.
|
||||
// Before writing a minidump, the optional filter callback will be called.
|
||||
// Its return value determines whether or not Breakpad should write a
|
||||
@ -116,111 +112,87 @@ class ExceptionHandler {
|
||||
// If install_handler is true, then a minidump will be written whenever
|
||||
// an unhandled exception occurs. If it is false, minidumps will only
|
||||
// be written when WriteMinidump is called.
|
||||
ExceptionHandler(const string &dump_path,
|
||||
ExceptionHandler(const std::string &dump_path,
|
||||
FilterCallback filter, MinidumpCallback callback,
|
||||
void *callback_context,
|
||||
bool install_handler);
|
||||
~ExceptionHandler();
|
||||
|
||||
// Get and set the minidump path.
|
||||
string dump_path() const { return dump_path_; }
|
||||
void set_dump_path(const string &dump_path) {
|
||||
std::string dump_path() const { return dump_path_; }
|
||||
void set_dump_path(const std::string &dump_path) {
|
||||
dump_path_ = dump_path;
|
||||
dump_path_c_ = dump_path_.c_str();
|
||||
UpdateNextID();
|
||||
}
|
||||
|
||||
void set_crash_handler(HandlerCallback callback) {
|
||||
crash_handler_ = callback;
|
||||
}
|
||||
|
||||
// Writes a minidump immediately. This can be used to capture the
|
||||
// execution state independently of a crash. Returns true on success.
|
||||
bool WriteMinidump();
|
||||
|
||||
// Convenience form of WriteMinidump which does not require an
|
||||
// ExceptionHandler instance.
|
||||
static bool WriteMinidump(const string &dump_path,
|
||||
static bool WriteMinidump(const std::string &dump_path,
|
||||
MinidumpCallback callback,
|
||||
void *callback_context);
|
||||
|
||||
// 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:
|
||||
// Setup crash handler.
|
||||
void SetupHandler();
|
||||
// Setup signal handler for a signal.
|
||||
void SetupHandler(int signo);
|
||||
// Teardown the handler for a signal.
|
||||
void TeardownHandler(int signo);
|
||||
// Teardown the handler for a signal.
|
||||
void TeardownHandler(int signo, struct sigaction *old);
|
||||
// Teardown all handlers.
|
||||
void TeardownAllHandler();
|
||||
bool InstallHandlers();
|
||||
void UninstallHandlers();
|
||||
void PreresolveSymbols();
|
||||
|
||||
// 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();
|
||||
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:
|
||||
FilterCallback filter_;
|
||||
MinidumpCallback callback_;
|
||||
void *callback_context_;
|
||||
const FilterCallback filter_;
|
||||
const MinidumpCallback callback_;
|
||||
void* const callback_context_;
|
||||
|
||||
// The directory in which a minidump will be written, set by the dump_path
|
||||
// argument to the constructor, or set_dump_path.
|
||||
string dump_path_;
|
||||
|
||||
// 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_;
|
||||
std::string dump_path_;
|
||||
std::string next_minidump_path_;
|
||||
std::string next_minidump_id_;
|
||||
|
||||
// Pointers to C-string representations of the above. These are set
|
||||
// when the above are set so we can avoid calling c_str during
|
||||
// an exception.
|
||||
const char *dump_path_c_;
|
||||
const char *next_minidump_id_c_;
|
||||
const char *next_minidump_path_c_;
|
||||
const char* dump_path_c_;
|
||||
const char* next_minidump_path_c_;
|
||||
const char* next_minidump_id_c_;
|
||||
|
||||
// True if the ExceptionHandler installed an unhandled exception filter
|
||||
// when created (with an install_handler parameter set to true).
|
||||
bool installed_handler_;
|
||||
const bool handler_installed_;
|
||||
void* signal_stack; // the handler stack.
|
||||
HandlerCallback crash_handler_;
|
||||
|
||||
// The global exception handler stack. This is need becuase there may exist
|
||||
// multiple ExceptionHandler instances in a process. Each will have itself
|
||||
// registered in this stack.
|
||||
static std::vector<ExceptionHandler *> *handler_stack_;
|
||||
static std::vector<ExceptionHandler*> *handler_stack_;
|
||||
// The index of the handler that should handle the next exception.
|
||||
static int handler_stack_index_;
|
||||
static unsigned handler_stack_index_;
|
||||
static pthread_mutex_t handler_stack_mutex_;
|
||||
|
||||
// The minidump generator.
|
||||
MinidumpGenerator minidump_generator_;
|
||||
|
||||
// disallow copy ctor and operator=
|
||||
explicit ExceptionHandler(const ExceptionHandler &);
|
||||
void operator=(const ExceptionHandler &);
|
||||
|
||||
// The sigactions structure we use for each signal
|
||||
struct sigaction act_;
|
||||
|
||||
|
||||
// Keep the previous handlers for the signal.
|
||||
// We're wasting a bit of memory here since we only change
|
||||
// the handler for some signals but i want to avoid allocating
|
||||
// memory in the signal handler
|
||||
struct sigaction old_actions_[NSIG];
|
||||
// A vector of the old signal handlers. The void* is a pointer to a newly
|
||||
// allocated sigaction structure to avoid pulling in too many includes.
|
||||
std::vector<std::pair<int, void *> > old_handlers_;
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H__
|
||||
#endif // CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_
|
||||
|
@ -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.
|
||||
//
|
||||
// Author: Li Liu
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
@ -29,45 +27,27 @@
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef CLIENT_LINUX_HANDLER_MINIDUMP_GENERATOR_H__
|
||||
#define CLIENT_LINUX_HANDLER_MINIDUMP_GENERATOR_H__
|
||||
#ifndef CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_
|
||||
#define CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "google_breakpad/common/breakpad_types.h"
|
||||
#include "processor/scoped_ptr.h"
|
||||
|
||||
struct sigcontext;
|
||||
#include <unistd.h>
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
// Write a minidump to the filesystem. This function does not malloc nor use
|
||||
// libc functions which may. Thus, it can be used in contexts where the state
|
||||
// of the heap may be corrupt.
|
||||
// filename: the filename to write to. This is opened O_EXCL and fails if
|
||||
// open fails.
|
||||
// crashing_process: the pid of the crashing process. This must be trusted.
|
||||
// blob: a blob of data from the crashing process. See exception_handler.h
|
||||
// blob_size: the length of |blob|, in bytes
|
||||
//
|
||||
// MinidumpGenerator
|
||||
//
|
||||
// Write a minidump to file based on the signo and sig_ctx.
|
||||
// A minidump generator should be created before any exception happen.
|
||||
//
|
||||
class MinidumpGenerator {
|
||||
public:
|
||||
MinidumpGenerator();
|
||||
|
||||
~MinidumpGenerator();
|
||||
|
||||
// Write minidump.
|
||||
bool WriteMinidumpToFile(const char *file_pathname,
|
||||
int signo,
|
||||
uintptr_t sighandler_ebp,
|
||||
struct sigcontext **sig_ctx) const;
|
||||
private:
|
||||
// Allocate memory for stack.
|
||||
void AllocateStack();
|
||||
|
||||
private:
|
||||
// Stack size of the writer thread.
|
||||
static const int kStackSize = 1024 * 1024;
|
||||
scoped_array<char> stack_;
|
||||
};
|
||||
// Returns true iff successful.
|
||||
bool WriteMinidump(const char* filename, pid_t crashing_process,
|
||||
const void* blob, size_t blob_size);
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_LINUX_HANDLER_MINIDUMP_GENERATOR_H__
|
||||
#endif // CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) 2006, Google Inc.
|
||||
// Copyright (c) 2009, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Author: Li Liu
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
@ -29,58 +27,53 @@
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/syscall.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include "client/linux/handler/minidump_generator.h"
|
||||
#include "client/linux/handler/exception_handler.h"
|
||||
#include "client/linux/minidump_writer/minidump_writer.h"
|
||||
#include "breakpad_googletest_includes.h"
|
||||
|
||||
using namespace google_breakpad;
|
||||
|
||||
// Thread use this to see if it should stop working.
|
||||
static bool should_exit = false;
|
||||
// This provides a wrapper around system calls which may be
|
||||
// interrupted by a signal and return EINTR. See man 7 signal.
|
||||
#define HANDLE_EINTR(x) ({ \
|
||||
typeof(x) __eintr_result__; \
|
||||
do { \
|
||||
__eintr_result__ = x; \
|
||||
} while (__eintr_result__ == -1 && errno == EINTR); \
|
||||
__eintr_result__;\
|
||||
})
|
||||
|
||||
static void foo2(int arg) {
|
||||
// Stack variable, used for debugging stack dumps.
|
||||
int c = arg;
|
||||
c = 0xcccccccc;
|
||||
while (!should_exit)
|
||||
sleep(1);
|
||||
namespace {
|
||||
typedef testing::Test MinidumpWriterTest;
|
||||
}
|
||||
|
||||
static void foo(int arg) {
|
||||
// Stack variable, used for debugging stack dumps.
|
||||
int b = arg;
|
||||
b = 0xbbbbbbbb;
|
||||
foo2(b);
|
||||
}
|
||||
TEST(MinidumpWriterTest, Setup) {
|
||||
int fds[2];
|
||||
ASSERT_NE(-1, pipe(fds));
|
||||
|
||||
static void *thread_main(void *) {
|
||||
// Stack variable, used for debugging stack dumps.
|
||||
int a = 0xaaaaaaaa;
|
||||
foo(a);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void CreateThread(int num) {
|
||||
pthread_t h;
|
||||
for (int i = 0; i < num; ++i) {
|
||||
pthread_create(&h, NULL, thread_main, NULL);
|
||||
pthread_detach(h);
|
||||
const pid_t child = fork();
|
||||
if (child == 0) {
|
||||
close(fds[1]);
|
||||
char b;
|
||||
HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
|
||||
close(fds[0]);
|
||||
syscall(__NR_exit);
|
||||
}
|
||||
}
|
||||
close(fds[0]);
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
CreateThread(10);
|
||||
google_breakpad::MinidumpGenerator mg;
|
||||
if (mg.WriteMinidumpToFile("minidump_test.out", -1, 0, NULL))
|
||||
printf("Succeeded written minidump\n");
|
||||
else
|
||||
printf("Failed to write minidump\n");
|
||||
should_exit = true;
|
||||
return 0;
|
||||
ExceptionHandler::CrashContext context;
|
||||
memset(&context, 0, sizeof(context));
|
||||
|
||||
char templ[] = "/tmp/minidump-writer-unittest-XXXXXX";
|
||||
mktemp(templ);
|
||||
ASSERT_TRUE(WriteMinidump(templ, child, &context, sizeof(context)));
|
||||
struct stat st;
|
||||
ASSERT_EQ(stat(templ, &st), 0);
|
||||
ASSERT_GT(st.st_size, 0u);
|
||||
unlink(templ);
|
||||
|
||||
close(fds[1]);
|
||||
}
|
@ -37,6 +37,8 @@
|
||||
#include <string.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 "common/string_conversion.h"
|
||||
|
||||
@ -53,7 +55,11 @@ MinidumpFileWriter::~MinidumpFileWriter() {
|
||||
|
||||
bool MinidumpFileWriter::Open(const char *path) {
|
||||
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);
|
||||
#endif
|
||||
|
||||
return file_ != -1;
|
||||
}
|
||||
@ -63,7 +69,11 @@ bool MinidumpFileWriter::Close() {
|
||||
|
||||
if (file_ != -1) {
|
||||
ftruncate(file_, position_);
|
||||
#if __linux__
|
||||
result = (sys_close(file_) == 0);
|
||||
#else
|
||||
result = (close(file_) == 0);
|
||||
#endif
|
||||
file_ = -1;
|
||||
}
|
||||
|
||||
@ -227,9 +237,16 @@ bool MinidumpFileWriter::Copy(MDRVA position, const void *src, ssize_t size) {
|
||||
return false;
|
||||
|
||||
// Seek and write the data
|
||||
if (lseek(file_, position, SEEK_SET) == static_cast<off_t>(position))
|
||||
if (write(file_, src, size) == size)
|
||||
#if __linux__
|
||||
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 false;
|
||||
}
|
||||
|
@ -28,146 +28,51 @@
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include <a.out.h>
|
||||
#include <cstdarg>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <assert.h>
|
||||
#include <cxxabi.h>
|
||||
#include <elf.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <link.h>
|
||||
#include <sys/mman.h>
|
||||
#include <stab.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <algorithm>
|
||||
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "common/linux/dump_symbols.h"
|
||||
#include "common/linux/file_id.h"
|
||||
#include "common/linux/guid_creator.h"
|
||||
#include "common/linux/module.h"
|
||||
#include "common/linux/stabs_reader.h"
|
||||
#include "processor/scoped_ptr.h"
|
||||
|
||||
// This namespace contains helper functions.
|
||||
namespace {
|
||||
|
||||
struct SourceFileInfo;
|
||||
|
||||
// Infomation of a line.
|
||||
struct LineInfo {
|
||||
// Offset from start of the function.
|
||||
// Load from stab symbol.
|
||||
ElfW(Off) rva_to_func;
|
||||
// Offset from base of the loading binary.
|
||||
ElfW(Off) rva_to_base;
|
||||
// Size of the line.
|
||||
// It is the difference of the starting address of the line and starting
|
||||
// address of the next N_SLINE, N_FUN or N_SO.
|
||||
uint32_t size;
|
||||
// Line number.
|
||||
uint32_t line_num;
|
||||
// The source file this line belongs to.
|
||||
SourceFileInfo *file;
|
||||
};
|
||||
|
||||
typedef std::list<struct LineInfo> LineInfoList;
|
||||
|
||||
// Information of a function.
|
||||
struct FuncInfo {
|
||||
// Name of the function.
|
||||
std::string name;
|
||||
// Offset from the base of the loading address.
|
||||
ElfW(Off) rva_to_base;
|
||||
// Virtual address of the function.
|
||||
// Load from stab symbol.
|
||||
ElfW(Addr) addr;
|
||||
// Size of the function.
|
||||
// It is the difference of the starting address of the function and starting
|
||||
// address of the next N_FUN or N_SO.
|
||||
uint32_t size;
|
||||
// Total size of stack parameters.
|
||||
uint32_t stack_param_size;
|
||||
// Line information array.
|
||||
LineInfoList line_info;
|
||||
};
|
||||
|
||||
typedef std::list<struct FuncInfo> FuncInfoList;
|
||||
|
||||
// Information of a source file.
|
||||
struct SourceFileInfo {
|
||||
// Name of the source file.
|
||||
const char *name;
|
||||
// Starting address of the source file.
|
||||
ElfW(Addr) addr;
|
||||
// Id of the source file.
|
||||
int source_id;
|
||||
// Functions information.
|
||||
FuncInfoList func_info;
|
||||
};
|
||||
|
||||
// A simple std::list of pointers to SourceFileInfo structures, that
|
||||
// owns the structures pointed to: destroying the list destroys them,
|
||||
// as well.
|
||||
class SourceFileInfoList : public std::list<SourceFileInfo *> {
|
||||
public:
|
||||
~SourceFileInfoList() {
|
||||
for (iterator it = this->begin(); it != this->end(); it++)
|
||||
delete *it;
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::map<const char *, SourceFileInfo *> NameToFileMap;
|
||||
|
||||
// Information of a symbol table.
|
||||
// This is the root of all types of symbol.
|
||||
struct SymbolInfo {
|
||||
// The main files used in this module. This does not include header
|
||||
// files; it includes only files that were provided as the primary
|
||||
// source file for the compilation unit. In STABS, these are files
|
||||
// named in 'N_SO' entries.
|
||||
SourceFileInfoList main_files;
|
||||
|
||||
// Map from file names to source file structures. Note that this
|
||||
// map's keys are compared as pointers, not strings, so if the same
|
||||
// name appears at two different addresses in stabstr, the map will
|
||||
// treat that as two different names. If the linker didn't unify
|
||||
// names in .stabstr (which it does), this would result in duplicate
|
||||
// FILE lines, which is benign.
|
||||
NameToFileMap name_to_file;
|
||||
|
||||
// An array of some addresses at which a file boundary occurs.
|
||||
//
|
||||
// The STABS information describing a compilation unit gives the
|
||||
// unit's start address, but not its ending address or size. Those
|
||||
// must be inferred by finding the start address of the next file.
|
||||
// For the last compilation unit, or when one compilation unit ends
|
||||
// before the next one starts, STABS includes an N_SO entry whose
|
||||
// filename is the empty string; such an entry's address serves
|
||||
// simply to mark the end of the preceding compilation unit. Rather
|
||||
// than create FuncInfoList for such entries, we record their
|
||||
// addresses here. These are not necessarily sorted.
|
||||
std::vector<ElfW(Addr)> file_boundaries;
|
||||
|
||||
// The current source file, for line number information. This is
|
||||
// persistent across functions.
|
||||
SourceFileInfo *current_source_file;
|
||||
};
|
||||
using google_breakpad::Module;
|
||||
using std::vector;
|
||||
|
||||
// Stab section name.
|
||||
static const char *kStabName = ".stab";
|
||||
|
||||
// Demangle using abi call.
|
||||
// Older GCC may not support it.
|
||||
static std::string Demangle(const char *mangled) {
|
||||
static std::string Demangle(const std::string &mangled) {
|
||||
int status = 0;
|
||||
char *demangled = abi::__cxa_demangle(mangled, NULL, NULL, &status);
|
||||
char *demangled = abi::__cxa_demangle(mangled.c_str(), NULL, NULL, &status);
|
||||
if (status == 0 && demangled != NULL) {
|
||||
std::string str(demangled);
|
||||
free(demangled);
|
||||
@ -220,285 +125,219 @@ static const ElfW(Shdr) *FindSectionByName(const char *name,
|
||||
|
||||
for (int i = 0; i < nsection; ++i) {
|
||||
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))
|
||||
return sections + i;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Return the SourceFileInfo for the file named NAME in SYMBOLS, as
|
||||
// recorden in the name_to_file map. If none exists, create a new
|
||||
// one.
|
||||
//
|
||||
// If the file is a main file, it is the caller's responsibility to
|
||||
// set its address and add it to the list of main files.
|
||||
//
|
||||
// When creating a new file, this function does not make a copy of
|
||||
// NAME; NAME must stay alive for as long as the symbol table does.
|
||||
static SourceFileInfo *FindSourceFileInfo(SymbolInfo *symbols,
|
||||
const char *name) {
|
||||
SourceFileInfo **map_entry = &symbols->name_to_file[name];
|
||||
SourceFileInfo *file;
|
||||
if (*map_entry)
|
||||
file = *map_entry;
|
||||
else {
|
||||
file = new SourceFileInfo;
|
||||
file->name = name;
|
||||
file->source_id = -1;
|
||||
file->addr = 0;
|
||||
*map_entry = file;
|
||||
}
|
||||
return file;
|
||||
}
|
||||
// Our handler class for STABS data.
|
||||
class DumpStabsHandler: public google_breakpad::StabsHandler {
|
||||
public:
|
||||
DumpStabsHandler(Module *module) :
|
||||
module_(module),
|
||||
comp_unit_base_address_(0),
|
||||
current_function_(NULL),
|
||||
current_source_file_(NULL),
|
||||
current_source_file_name_(NULL) { }
|
||||
|
||||
static int LoadLineInfo(struct nlist *list,
|
||||
struct nlist *list_end,
|
||||
SymbolInfo *symbols,
|
||||
struct SourceFileInfo *source_file_info,
|
||||
struct FuncInfo *func_info,
|
||||
const ElfW(Shdr) *stabstr_section) {
|
||||
struct nlist *cur_list = list;
|
||||
// The name of the file any subsequent lines would belong to.
|
||||
const char *last_source_name = symbols->current_source_file->name;
|
||||
do {
|
||||
// Skip non line information.
|
||||
while (cur_list < list_end && cur_list->n_type != N_SLINE) {
|
||||
// Only exit when got another function, or source file.
|
||||
if (cur_list->n_type == N_FUN || cur_list->n_type == N_SO)
|
||||
return cur_list - list;
|
||||
// N_SOL means source lines following it will be from another
|
||||
// source file. But don't actually create a file entry yet;
|
||||
// wait until we see executable code attributed to the file.
|
||||
if (cur_list->n_type == N_SOL
|
||||
&& cur_list->n_un.n_strx > 0)
|
||||
last_source_name = reinterpret_cast<char *>(cur_list->n_un.n_strx
|
||||
+ stabstr_section->sh_offset);
|
||||
++cur_list;
|
||||
}
|
||||
struct LineInfo line;
|
||||
while (cur_list < list_end && cur_list->n_type == N_SLINE) {
|
||||
// If this line is attributed to a new file, create its entry now.
|
||||
if (last_source_name != symbols->current_source_file->name) {
|
||||
symbols->current_source_file
|
||||
= FindSourceFileInfo(symbols, last_source_name);
|
||||
}
|
||||
line.file = symbols->current_source_file;
|
||||
line.rva_to_func = cur_list->n_value;
|
||||
// n_desc is a signed short
|
||||
line.line_num = (unsigned short)cur_list->n_desc;
|
||||
// We will compute these later. For now, pacify compiler warnings.
|
||||
line.size = 0;
|
||||
line.rva_to_base = 0;
|
||||
func_info->line_info.push_back(line);
|
||||
++cur_list;
|
||||
}
|
||||
} while (list < list_end);
|
||||
bool StartCompilationUnit(const char *name, uint64_t address,
|
||||
const char *build_directory);
|
||||
bool EndCompilationUnit(uint64_t address);
|
||||
bool StartFunction(const std::string &name, uint64_t address);
|
||||
bool EndFunction(uint64_t address);
|
||||
bool Line(uint64_t address, const char *name, int number);
|
||||
|
||||
return cur_list - list;
|
||||
}
|
||||
// Do any final processing necessary to make module_ contain all the
|
||||
// data provided by the STABS reader.
|
||||
//
|
||||
// Because STABS does not provide reliable size information for
|
||||
// functions and lines, we need to make a pass over the data after
|
||||
// processing all the STABS to compute those sizes. We take care of
|
||||
// that here.
|
||||
void Finalize();
|
||||
|
||||
static int LoadFuncSymbols(struct nlist *list,
|
||||
struct nlist *list_end,
|
||||
SymbolInfo *symbols,
|
||||
struct SourceFileInfo *source_file_info,
|
||||
const ElfW(Shdr) *stabstr_section) {
|
||||
struct nlist *cur_list = list;
|
||||
assert(cur_list->n_type == N_SO);
|
||||
++cur_list;
|
||||
source_file_info->func_info.clear();
|
||||
while (cur_list < list_end) {
|
||||
// Go until the function symbol.
|
||||
while (cur_list < list_end && cur_list->n_type != N_FUN) {
|
||||
if (cur_list->n_type == N_SO) {
|
||||
return cur_list - list;
|
||||
}
|
||||
++cur_list;
|
||||
continue;
|
||||
}
|
||||
if (cur_list->n_type == N_FUN) {
|
||||
struct FuncInfo func_info;
|
||||
// The STABS data for an N_FUN entry is the function's (mangled)
|
||||
// name, followed by a colon, followed by type information. We
|
||||
// want to retain the name only.
|
||||
const char *stabs_name
|
||||
= reinterpret_cast<char *>(cur_list->n_un.n_strx +
|
||||
stabstr_section->sh_offset);
|
||||
const char *name_end = strchr(stabs_name, ':');
|
||||
if (! name_end)
|
||||
name_end = stabs_name + strlen(stabs_name);
|
||||
func_info.name = std::string(stabs_name, name_end - stabs_name);
|
||||
func_info.addr = cur_list->n_value;
|
||||
func_info.rva_to_base = 0;
|
||||
func_info.size = 0;
|
||||
func_info.stack_param_size = 0;
|
||||
cur_list++;
|
||||
private:
|
||||
|
||||
// Line info.
|
||||
cur_list += LoadLineInfo(cur_list,
|
||||
list_end,
|
||||
symbols,
|
||||
source_file_info,
|
||||
&func_info,
|
||||
stabstr_section);
|
||||
// An arbitrary, but very large, size to use for functions whose
|
||||
// size we can't compute properly.
|
||||
static const uint64_t kFallbackSize = 0x10000000;
|
||||
|
||||
// Functions in this module should have address bigger than the module
|
||||
// startring address.
|
||||
// There maybe a lot of duplicated entry for a function in the symbol,
|
||||
// only one of them can met this.
|
||||
if (func_info.addr >= source_file_info->addr) {
|
||||
source_file_info->func_info.push_back(func_info);
|
||||
}
|
||||
}
|
||||
}
|
||||
return cur_list - list;
|
||||
}
|
||||
// The module we're contributing debugging info to.
|
||||
Module *module_;
|
||||
|
||||
// Compute size and rva information based on symbols loaded from stab section.
|
||||
static bool ComputeSizeAndRVA(ElfW(Addr) loading_addr,
|
||||
struct SymbolInfo *symbols) {
|
||||
SourceFileInfoList::iterator file_it;
|
||||
FuncInfoList::iterator func_it;
|
||||
LineInfoList::iterator line_it;
|
||||
// The functions we've generated so far. We don't add these to
|
||||
// module_ as we parse them. Instead, we wait until we've computed
|
||||
// their ending address, and their lines' ending addresses.
|
||||
//
|
||||
// We could just stick them in module_ from the outset, but if
|
||||
// module_ already contains data gathered from other debugging
|
||||
// formats, that would complicate the size computation.
|
||||
vector<Module::Function *> functions_;
|
||||
|
||||
// A table of all the addresses at which files and functions start
|
||||
// or end. We build this from the file boundary list and our lists
|
||||
// of files and functions, sort it, and then use it to find the ends
|
||||
// of functions and source lines for which we have no size
|
||||
// information.
|
||||
std::vector<ElfW(Addr)> boundaries = symbols->file_boundaries;
|
||||
for (file_it = symbols->main_files.begin();
|
||||
file_it != symbols->main_files.end(); file_it++) {
|
||||
boundaries.push_back((*file_it)->addr);
|
||||
for (func_it = (*file_it)->func_info.begin();
|
||||
func_it != (*file_it)->func_info.end(); func_it++)
|
||||
boundaries.push_back(func_it->addr);
|
||||
}
|
||||
std::sort(boundaries.begin(), boundaries.end());
|
||||
// Boundary addresses. STABS doesn't necessarily supply sizes for
|
||||
// functions and lines, so we need to compute them ourselves by
|
||||
// finding the next object.
|
||||
vector<Module::Address> boundaries_;
|
||||
|
||||
int no_next_addr_count = 0;
|
||||
for (file_it = symbols->main_files.begin();
|
||||
file_it != symbols->main_files.end(); file_it++) {
|
||||
for (func_it = (*file_it)->func_info.begin();
|
||||
func_it != (*file_it)->func_info.end(); func_it++) {
|
||||
struct FuncInfo &func_info = *func_it;
|
||||
assert(func_info.addr >= loading_addr);
|
||||
func_info.rva_to_base = func_info.addr - loading_addr;
|
||||
func_info.size = 0;
|
||||
std::vector<ElfW(Addr)>::iterator boundary
|
||||
= std::upper_bound(boundaries.begin(), boundaries.end(),
|
||||
func_info.addr);
|
||||
ElfW(Addr) next_addr = (boundary == boundaries.end()) ? 0 : *boundary;
|
||||
// I've noticed functions with an address bigger than any other functions
|
||||
// and source files modules, this is probably the last function in the
|
||||
// module, due to limitions of Linux stab symbol, it is impossible to get
|
||||
// the exact size of this kind of function, thus we give it a default
|
||||
// very big value. This should be safe since this is the last function.
|
||||
// But it is a ugly hack.....
|
||||
// The following code can reproduce the case:
|
||||
// template<class T>
|
||||
// void Foo(T value) {
|
||||
// }
|
||||
//
|
||||
// int main(void) {
|
||||
// Foo(10);
|
||||
// Foo(std::string("hello"));
|
||||
// return 0;
|
||||
// }
|
||||
// TODO(liuli): Find a better solution.
|
||||
static const int kDefaultSize = 0x10000000;
|
||||
if (next_addr != 0) {
|
||||
func_info.size = next_addr - func_info.addr;
|
||||
} else {
|
||||
if (no_next_addr_count > 1) {
|
||||
fprintf(stderr, "Got more than one funtion without the \
|
||||
following symbol. Igore this function.\n");
|
||||
fprintf(stderr, "The dumped symbol may not correct.\n");
|
||||
assert(!"This should not happen!\n");
|
||||
func_info.size = 0;
|
||||
continue;
|
||||
}
|
||||
// The base address of the current compilation unit. We use this to
|
||||
// recognize functions we should omit from the symbol file. (If you
|
||||
// know the details of why we omit these, please patch this
|
||||
// comment.)
|
||||
Module::Address comp_unit_base_address_;
|
||||
|
||||
no_next_addr_count++;
|
||||
func_info.size = kDefaultSize;
|
||||
}
|
||||
// Compute line size.
|
||||
for (line_it = func_info.line_info.begin();
|
||||
line_it != func_info.line_info.end(); line_it++) {
|
||||
struct LineInfo &line_info = *line_it;
|
||||
LineInfoList::iterator next_line_it = line_it;
|
||||
next_line_it++;
|
||||
line_info.size = 0;
|
||||
if (next_line_it != func_info.line_info.end()) {
|
||||
line_info.size =
|
||||
next_line_it->rva_to_func - line_info.rva_to_func;
|
||||
} else {
|
||||
// The last line in the function.
|
||||
// If we can find a function or source file symbol immediately
|
||||
// following the line, we can get the size of the line by computing
|
||||
// the difference of the next address to the starting address of this
|
||||
// line.
|
||||
// Otherwise, we need to set a default big enough value. This occurs
|
||||
// mostly because the this function is the last one in the module.
|
||||
if (next_addr != 0) {
|
||||
ElfW(Off) next_addr_offset = next_addr - func_info.addr;
|
||||
line_info.size = next_addr_offset - line_info.rva_to_func;
|
||||
} else {
|
||||
line_info.size = kDefaultSize;
|
||||
}
|
||||
}
|
||||
line_info.rva_to_base = line_info.rva_to_func + func_info.rva_to_base;
|
||||
} // for each line.
|
||||
} // for each function.
|
||||
} // for each source file.
|
||||
// The function we're currently contributing lines to.
|
||||
Module::Function *current_function_;
|
||||
|
||||
// The last Module::File we got a line number in.
|
||||
Module::File *current_source_file_;
|
||||
|
||||
// The pointer in the .stabstr section of the name that
|
||||
// current_source_file_ is built from. This allows us to quickly
|
||||
// recognize when the current line is in the same file as the
|
||||
// previous one (which it usually is).
|
||||
const char *current_source_file_name_;
|
||||
};
|
||||
|
||||
bool DumpStabsHandler::StartCompilationUnit(const char *name, uint64_t address,
|
||||
const char *build_directory) {
|
||||
assert(! comp_unit_base_address_);
|
||||
current_source_file_name_ = name;
|
||||
current_source_file_ = module_->FindFile(name);
|
||||
comp_unit_base_address_ = address;
|
||||
boundaries_.push_back(static_cast<Module::Address>(address));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DumpStabsHandler::EndCompilationUnit(uint64_t address) {
|
||||
assert(comp_unit_base_address_);
|
||||
comp_unit_base_address_ = 0;
|
||||
current_source_file_ = NULL;
|
||||
current_source_file_name_ = NULL;
|
||||
if (address)
|
||||
boundaries_.push_back(static_cast<Module::Address>(address));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DumpStabsHandler::StartFunction(const std::string &name,
|
||||
uint64_t address) {
|
||||
assert(! current_function_);
|
||||
Module::Function *f = new Module::Function;
|
||||
f->name_ = Demangle(name);
|
||||
f->address_ = address;
|
||||
f->size_ = 0; // We compute this in DumpStabsHandler::Finalize().
|
||||
f->parameter_size_ = 0; // We don't provide this information.
|
||||
current_function_ = f;
|
||||
boundaries_.push_back(static_cast<Module::Address>(address));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DumpStabsHandler::EndFunction(uint64_t address) {
|
||||
assert(current_function_);
|
||||
// Functions in this compilation unit should have address bigger
|
||||
// than the compilation unit's starting address. There may be a lot
|
||||
// of duplicated entries for functions in the STABS data; only one
|
||||
// entry can meet this requirement.
|
||||
//
|
||||
// (I don't really understand the above comment; just bringing it
|
||||
// along from the previous code, and leaving the behaivor unchanged.
|
||||
// If you know the whole story, please patch this comment. --jimb)
|
||||
if (current_function_->address_ >= comp_unit_base_address_)
|
||||
functions_.push_back(current_function_);
|
||||
else
|
||||
delete current_function_;
|
||||
current_function_ = NULL;
|
||||
if (address)
|
||||
boundaries_.push_back(static_cast<Module::Address>(address));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DumpStabsHandler::Line(uint64_t address, const char *name, int number) {
|
||||
assert(current_function_);
|
||||
assert(current_source_file_);
|
||||
if (name != current_source_file_name_) {
|
||||
current_source_file_ = module_->FindFile(name);
|
||||
current_source_file_name_ = name;
|
||||
}
|
||||
Module::Line line;
|
||||
line.address_ = address;
|
||||
line.size_ = 0; // We compute this in DumpStabsHandler::Finalize().
|
||||
line.file_ = current_source_file_;
|
||||
line.number_ = number;
|
||||
current_function_->lines_.push_back(line);
|
||||
return true;
|
||||
}
|
||||
|
||||
void DumpStabsHandler::Finalize() {
|
||||
// Sort our boundary list, so we can search it quickly.
|
||||
sort(boundaries_.begin(), boundaries_.end());
|
||||
// Sort all functions by address, just for neatness.
|
||||
sort(functions_.begin(), functions_.end(),
|
||||
Module::Function::CompareByAddress);
|
||||
for (vector<Module::Function *>::iterator func_it = functions_.begin();
|
||||
func_it != functions_.end();
|
||||
func_it++) {
|
||||
Module::Function *f = *func_it;
|
||||
// Compute the function f's size.
|
||||
vector<Module::Address>::iterator boundary
|
||||
= std::upper_bound(boundaries_.begin(), boundaries_.end(), f->address_);
|
||||
if (boundary != boundaries_.end())
|
||||
f->size_ = *boundary - f->address_;
|
||||
else
|
||||
// If this is the last function in the module, and the STABS
|
||||
// reader was unable to give us its ending address, then assign
|
||||
// it a bogus, very large value. This will happen at most once
|
||||
// per module: since we've added all functions' addresses to the
|
||||
// boundary table, only one can be the last.
|
||||
f->size_ = kFallbackSize;
|
||||
|
||||
// Compute sizes for each of the function f's lines --- if it has any.
|
||||
if (! f->lines_.empty()) {
|
||||
stable_sort(f->lines_.begin(), f->lines_.end(),
|
||||
Module::Line::CompareByAddress);
|
||||
vector<Module::Line>::iterator last_line = f->lines_.end() - 1;
|
||||
for (vector<Module::Line>::iterator line_it = f->lines_.begin();
|
||||
line_it != last_line; line_it++)
|
||||
line_it[0].size_ = line_it[1].address_ - line_it[0].address_;
|
||||
// Compute the size of the last line from f's end address.
|
||||
last_line->size_ = (f->address_ + f->size_) - last_line->address_;
|
||||
}
|
||||
}
|
||||
// Now that everything has a size, add our functions to the module, and
|
||||
// dispose of our private list.
|
||||
module_->AddFunctions(functions_.begin(), functions_.end());
|
||||
functions_.clear();
|
||||
}
|
||||
|
||||
static bool LoadSymbols(const ElfW(Shdr) *stab_section,
|
||||
const ElfW(Shdr) *stabstr_section,
|
||||
ElfW(Addr) loading_addr,
|
||||
struct SymbolInfo *symbols) {
|
||||
Module *module) {
|
||||
if (stab_section == NULL || stabstr_section == NULL)
|
||||
return false;
|
||||
|
||||
struct nlist *lists =
|
||||
reinterpret_cast<struct nlist *>(stab_section->sh_offset);
|
||||
int nstab = stab_section->sh_size / sizeof(struct nlist);
|
||||
// First pass, load all symbols from the object file.
|
||||
for (int i = 0; i < nstab; ) {
|
||||
int step = 1;
|
||||
struct nlist *cur_list = lists + i;
|
||||
if (cur_list->n_type == N_SO) {
|
||||
if (cur_list->n_un.n_strx) {
|
||||
const char *name = reinterpret_cast<char *>(cur_list->n_un.n_strx
|
||||
+ stabstr_section->sh_offset);
|
||||
struct SourceFileInfo *source_file_info
|
||||
= FindSourceFileInfo(symbols, name);
|
||||
// Add it to the list; use ADDR to tell whether we've already done so.
|
||||
if (! source_file_info->addr)
|
||||
symbols->main_files.push_back(source_file_info);
|
||||
source_file_info->addr = cur_list->n_value;
|
||||
symbols->current_source_file = source_file_info;
|
||||
step = LoadFuncSymbols(cur_list, lists + nstab, symbols,
|
||||
source_file_info, stabstr_section);
|
||||
} else {
|
||||
// N_SO entries with no name mark file boundary addresses.
|
||||
symbols->file_boundaries.push_back(cur_list->n_value);
|
||||
}
|
||||
}
|
||||
i += step;
|
||||
}
|
||||
|
||||
// Second pass, compute the size of functions and lines.
|
||||
return ComputeSizeAndRVA(loading_addr, symbols);
|
||||
// A callback object to handle data from the STABS reader.
|
||||
DumpStabsHandler handler(module);
|
||||
// Find the addresses of the STABS data, and create a STABS reader object.
|
||||
uint8_t *stabs = reinterpret_cast<uint8_t *>(stab_section->sh_offset);
|
||||
uint8_t *stabstr = reinterpret_cast<uint8_t *>(stabstr_section->sh_offset);
|
||||
google_breakpad::StabsReader reader(stabs, stab_section->sh_size,
|
||||
stabstr, stabstr_section->sh_size,
|
||||
&handler);
|
||||
// Read the STABS data, and do post-processing.
|
||||
if (! reader.Process())
|
||||
return false;
|
||||
handler.Finalize();
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool LoadSymbols(ElfW(Ehdr) *elf_header, struct SymbolInfo *symbols) {
|
||||
static bool LoadSymbols(ElfW(Ehdr) *elf_header, Module *module) {
|
||||
// Translate all offsets in section headers into address.
|
||||
FixAddress(elf_header);
|
||||
ElfW(Addr) loading_addr = GetLoadingAddress(
|
||||
reinterpret_cast<ElfW(Phdr) *>(elf_header->e_phoff),
|
||||
elf_header->e_phnum);
|
||||
module->SetLoadAddress(loading_addr);
|
||||
|
||||
const ElfW(Shdr) *sections =
|
||||
reinterpret_cast<ElfW(Shdr) *>(elf_header->e_shoff);
|
||||
@ -512,153 +351,7 @@ static bool LoadSymbols(ElfW(Ehdr) *elf_header, struct SymbolInfo *symbols) {
|
||||
const ElfW(Shdr) *stabstr_section = stab_section->sh_link + sections;
|
||||
|
||||
// Load symbols.
|
||||
return LoadSymbols(stab_section, stabstr_section, loading_addr, symbols);
|
||||
}
|
||||
|
||||
static bool WriteModuleInfo(FILE *file,
|
||||
ElfW(Half) arch,
|
||||
const std::string &obj_file) {
|
||||
const char *arch_name = NULL;
|
||||
if (arch == EM_386)
|
||||
arch_name = "x86";
|
||||
else if (arch == EM_X86_64)
|
||||
arch_name = "x86_64";
|
||||
else
|
||||
return false;
|
||||
|
||||
unsigned char identifier[16];
|
||||
google_breakpad::FileID file_id(obj_file.c_str());
|
||||
if (file_id.ElfFileIdentifier(identifier)) {
|
||||
char identifier_str[40];
|
||||
file_id.ConvertIdentifierToString(identifier,
|
||||
identifier_str, sizeof(identifier_str));
|
||||
char id_no_dash[40];
|
||||
int id_no_dash_len = 0;
|
||||
memset(id_no_dash, 0, sizeof(id_no_dash));
|
||||
for (int i = 0; identifier_str[i] != '\0'; ++i)
|
||||
if (identifier_str[i] != '-')
|
||||
id_no_dash[id_no_dash_len++] = identifier_str[i];
|
||||
// Add an extra "0" by the end.
|
||||
id_no_dash[id_no_dash_len++] = '0';
|
||||
std::string filename = obj_file;
|
||||
size_t slash_pos = obj_file.find_last_of("/");
|
||||
if (slash_pos != std::string::npos)
|
||||
filename = obj_file.substr(slash_pos + 1);
|
||||
return 0 <= fprintf(file, "MODULE Linux %s %s %s\n", arch_name,
|
||||
id_no_dash, filename.c_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set *INCLUDED_FILES to the list of included files in SYMBOLS,
|
||||
// ordered appropriately for output. Included files should appear in
|
||||
// the order in which they are first referenced by source line info.
|
||||
// Assign these files source id numbers starting with NEXT_SOURCE_ID.
|
||||
//
|
||||
// Note that the name_to_file map may contain #included files that are
|
||||
// unreferenced; these are the result of LoadFuncSymbols omitting
|
||||
// functions from the list whose addresses fall outside the address
|
||||
// range of the file that contains them.
|
||||
static void CollectIncludedFiles(const struct SymbolInfo &symbols,
|
||||
std::vector<SourceFileInfo *> *included_files,
|
||||
int next_source_id) {
|
||||
for (SourceFileInfoList::const_iterator file_it = symbols.main_files.begin();
|
||||
file_it != symbols.main_files.end(); file_it++) {
|
||||
for (FuncInfoList::const_iterator func_it = (*file_it)->func_info.begin();
|
||||
func_it != (*file_it)->func_info.end(); func_it++) {
|
||||
for (LineInfoList::const_iterator line_it = func_it->line_info.begin();
|
||||
line_it != func_it->line_info.end(); line_it++) {
|
||||
SourceFileInfo *file = line_it->file;
|
||||
if (file->source_id == -1) {
|
||||
file->source_id = next_source_id++;
|
||||
// Here we use the source id as a mark, ensuring that each
|
||||
// file appears in the list only once.
|
||||
included_files->push_back(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write 'FILE' lines for all source files in SYMBOLS to FILE. We
|
||||
// assign source id numbers to files here.
|
||||
static bool WriteSourceFileInfo(FILE *file, struct SymbolInfo &symbols) {
|
||||
int next_source_id = 0;
|
||||
// Assign source id numbers to main files, and write them out to the file.
|
||||
for (SourceFileInfoList::iterator file_it = symbols.main_files.begin();
|
||||
file_it != symbols.main_files.end(); file_it++) {
|
||||
SourceFileInfo *file_info = *file_it;
|
||||
assert(file_info->addr);
|
||||
// We only output 'FILE' lines for main files if their names
|
||||
// contain '.'. The extensionless C++ header files are #included,
|
||||
// not main files, so it wouldn't affect them. If you know the
|
||||
// story, please patch this comment.
|
||||
if (strchr(file_info->name, '.')) {
|
||||
file_info->source_id = next_source_id++;
|
||||
if (0 > fprintf(file, "FILE %d %s\n",
|
||||
file_info->source_id, file_info->name))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Compute the list of included files, and write them out.
|
||||
// Can't use SourceFileInfoList here, because that owns the files it
|
||||
// points to.
|
||||
std::vector<SourceFileInfo *> included_files;
|
||||
std::vector<SourceFileInfo *>::const_iterator file_it;
|
||||
CollectIncludedFiles(symbols, &included_files, next_source_id);
|
||||
for (file_it = included_files.begin(); file_it != included_files.end();
|
||||
file_it++) {
|
||||
if (0 > fprintf(file, "FILE %d %s\n",
|
||||
(*file_it)->source_id, (*file_it)->name))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool WriteOneFunction(FILE *file,
|
||||
const struct FuncInfo &func_info){
|
||||
std::string func_name = Demangle(func_info.name.c_str());
|
||||
|
||||
if (func_info.size <= 0)
|
||||
return true;
|
||||
|
||||
if (0 <= fprintf(file, "FUNC %lx %lx %d %s\n",
|
||||
(unsigned long) func_info.rva_to_base,
|
||||
(unsigned long) func_info.size,
|
||||
func_info.stack_param_size,
|
||||
func_name.c_str())) {
|
||||
for (LineInfoList::const_iterator it = func_info.line_info.begin();
|
||||
it != func_info.line_info.end(); it++) {
|
||||
const struct LineInfo &line_info = *it;
|
||||
if (0 > fprintf(file, "%lx %lx %d %d\n",
|
||||
(unsigned long) line_info.rva_to_base,
|
||||
(unsigned long) line_info.size,
|
||||
line_info.line_num,
|
||||
line_info.file->source_id))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool WriteFunctionInfo(FILE *file, const struct SymbolInfo &symbols) {
|
||||
for (SourceFileInfoList::const_iterator it = symbols.main_files.begin();
|
||||
it != symbols.main_files.end(); it++) {
|
||||
const struct SourceFileInfo &file_info = **it;
|
||||
for (FuncInfoList::const_iterator fiIt = file_info.func_info.begin();
|
||||
fiIt != file_info.func_info.end(); fiIt++) {
|
||||
const struct FuncInfo &func_info = *fiIt;
|
||||
if (!WriteOneFunction(file, func_info))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool DumpStabSymbols(FILE *file, struct SymbolInfo &symbols) {
|
||||
return WriteSourceFileInfo(file, symbols) &&
|
||||
WriteFunctionInfo(file, symbols);
|
||||
return LoadSymbols(stab_section, stabstr_section, module);
|
||||
}
|
||||
|
||||
//
|
||||
@ -713,6 +406,48 @@ class MmapWrapper {
|
||||
size_t size_;
|
||||
};
|
||||
|
||||
// Return the breakpad symbol file identifier for the architecture of
|
||||
// ELF_HEADER.
|
||||
const char *ElfArchitecture(const ElfW(Ehdr) *elf_header) {
|
||||
ElfW(Half) arch = elf_header->e_machine;
|
||||
if (arch == EM_386)
|
||||
return "x86";
|
||||
else if (arch == EM_X86_64)
|
||||
return "x86_64";
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Format the Elf file identifier in IDENTIFIER as a UUID with the
|
||||
// dashes removed.
|
||||
std::string FormatIdentifier(unsigned char identifier[16]) {
|
||||
char identifier_str[40];
|
||||
google_breakpad::FileID::ConvertIdentifierToString(
|
||||
identifier,
|
||||
identifier_str,
|
||||
sizeof(identifier_str));
|
||||
std::string id_no_dash;
|
||||
for (int i = 0; identifier_str[i] != '\0'; ++i)
|
||||
if (identifier_str[i] != '-')
|
||||
id_no_dash += identifier_str[i];
|
||||
// Add an extra "0" by the end. PDB files on Windows have an 'age'
|
||||
// number appended to the end of the file identifier; this isn't
|
||||
// really used or necessary on other platforms, but let's preserve
|
||||
// the pattern.
|
||||
id_no_dash += '0';
|
||||
return id_no_dash;
|
||||
}
|
||||
|
||||
// Return the non-directory portion of FILENAME: the portion after the
|
||||
// last slash, or the whole filename if there are no slashes.
|
||||
std::string BaseFileName(const std::string &filename) {
|
||||
// Lots of copies! basename's behavior is less than ideal.
|
||||
char *c_filename = strdup(filename.c_str());
|
||||
std::string base = basename(c_filename);
|
||||
free(c_filename);
|
||||
return base;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace google_breakpad {
|
||||
@ -734,16 +469,27 @@ bool DumpSymbols::WriteSymbolFile(const std::string &obj_file,
|
||||
ElfW(Ehdr) *elf_header = reinterpret_cast<ElfW(Ehdr) *>(obj_base);
|
||||
if (!IsValidElf(elf_header))
|
||||
return false;
|
||||
struct SymbolInfo symbols;
|
||||
|
||||
if (!LoadSymbols(elf_header, &symbols))
|
||||
return false;
|
||||
// Write to symbol file.
|
||||
if (WriteModuleInfo(sym_file, elf_header->e_machine, obj_file) &&
|
||||
DumpStabSymbols(sym_file, symbols))
|
||||
return true;
|
||||
unsigned char identifier[16];
|
||||
google_breakpad::FileID file_id(obj_file.c_str());
|
||||
if (! file_id.ElfFileIdentifier(identifier))
|
||||
return false;
|
||||
|
||||
return false;
|
||||
const char *architecture = ElfArchitecture(elf_header);
|
||||
if (! architecture)
|
||||
return false;
|
||||
|
||||
std::string name = BaseFileName(obj_file);
|
||||
std::string os = "Linux";
|
||||
std::string id = FormatIdentifier(identifier);
|
||||
|
||||
Module module(name, os, architecture, id);
|
||||
if (!LoadSymbols(elf_header, &module))
|
||||
return false;
|
||||
if (!module.Write(sym_file))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
@ -32,104 +32,74 @@
|
||||
// See file_id.h for documentation
|
||||
//
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include "common/linux/file_id.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <elf.h>
|
||||
#include <fcntl.h>
|
||||
#include <link.h>
|
||||
#include <sys/mman.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "common/linux/file_id.h"
|
||||
#include "common/md5.h"
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
static bool FindElfTextSection(const void *elf_mapped_base,
|
||||
const void **text_start,
|
||||
int *text_size) {
|
||||
assert(elf_mapped_base);
|
||||
assert(text_start);
|
||||
assert(text_size);
|
||||
|
||||
const unsigned char *elf_base =
|
||||
static_cast<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) {
|
||||
FileID::FileID(const char* 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);
|
||||
if (fd < 0)
|
||||
return false;
|
||||
struct stat st;
|
||||
if (fstat(fd, &st) != 0 && st.st_size <= 0) {
|
||||
if (fstat(fd, &st) != 0 || st.st_size <= mapped_len) {
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
void *base = mmap(NULL, st.st_size,
|
||||
void* base = mmap(NULL, mapped_len,
|
||||
PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
|
||||
if (base == MAP_FAILED) {
|
||||
close(fd);
|
||||
close(fd);
|
||||
if (base == MAP_FAILED)
|
||||
return false;
|
||||
}
|
||||
bool success = false;
|
||||
const void *text_section = NULL;
|
||||
int text_size = 0;
|
||||
if (FindElfTextSection(base, &text_section, &text_size) && (text_size > 0)) {
|
||||
struct MD5Context md5;
|
||||
MD5Init(&md5);
|
||||
MD5Update(&md5,
|
||||
static_cast<const unsigned char*>(text_section),
|
||||
text_size);
|
||||
MD5Final(identifier, &md5);
|
||||
success = true;
|
||||
|
||||
memset(identifier, 0, kMDGUIDSize);
|
||||
uint8_t* ptr = reinterpret_cast<uint8_t*>(base);
|
||||
uint8_t* ptr_end = ptr + mapped_len;
|
||||
while (ptr < ptr_end) {
|
||||
for (unsigned i = 0; i < kMDGUIDSize; i++)
|
||||
identifier[i] ^= ptr[i];
|
||||
ptr += kMDGUIDSize;
|
||||
}
|
||||
|
||||
close(fd);
|
||||
munmap(base, st.st_size);
|
||||
return success;
|
||||
munmap(base, mapped_len);
|
||||
return true;
|
||||
}
|
||||
|
||||
// static
|
||||
void FileID::ConvertIdentifierToString(const unsigned char identifier[16],
|
||||
char *buffer, int buffer_length) {
|
||||
void FileID::ConvertIdentifierToString(const uint8_t identifier[kMDGUIDSize],
|
||||
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;
|
||||
for (int idx = 0; (buffer_idx < buffer_length) && (idx < 16); ++idx) {
|
||||
int hi = (identifier[idx] >> 4) & 0x0F;
|
||||
int lo = (identifier[idx]) & 0x0F;
|
||||
for (unsigned int idx = 0;
|
||||
(buffer_idx < buffer_length) && (idx < kMDGUIDSize);
|
||||
++idx) {
|
||||
int hi = (identifier_swapped[idx] >> 4) & 0x0F;
|
||||
int lo = (identifier_swapped[idx]) & 0x0F;
|
||||
|
||||
if (idx == 4 || idx == 6 || idx == 8 || idx == 10)
|
||||
buffer[buffer_idx++] = '-';
|
||||
|
@ -35,25 +35,30 @@
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#include "common/linux/guid_creator.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
static const size_t kMDGUIDSize = sizeof(MDGUID);
|
||||
|
||||
class FileID {
|
||||
public:
|
||||
FileID(const char *path);
|
||||
~FileID() {};
|
||||
explicit FileID(const char* path);
|
||||
~FileID() {}
|
||||
|
||||
// Load the identifier for the elf file path specified in the constructor into
|
||||
// |identifier|. Return false if the identifier could not be created for the
|
||||
// file.
|
||||
// The current implementation will return the MD5 hash of the file's bytes.
|
||||
bool ElfFileIdentifier(unsigned char identifier[16]);
|
||||
// The current implementation will XOR the first page of data to generate an
|
||||
// identifier.
|
||||
bool ElfFileIdentifier(uint8_t identifier[kMDGUIDSize]);
|
||||
|
||||
// Convert the |identifier| data to a NULL terminated string. The string will
|
||||
// be formatted as a UUID (e.g., 22F065BB-FC9C-49F7-80FE-26A7CEBD7BCE).
|
||||
// The |buffer| should be at least 37 bytes long to receive all of the data
|
||||
// and termination. Shorter buffers will contain truncated data.
|
||||
static void ConvertIdentifierToString(const unsigned char identifier[16],
|
||||
char *buffer, int buffer_length);
|
||||
static void ConvertIdentifierToString(const uint8_t identifier[kMDGUIDSize],
|
||||
char* buffer, int buffer_length);
|
||||
|
||||
private:
|
||||
// Storage for the path specified
|
||||
@ -63,4 +68,3 @@ class FileID {
|
||||
} // namespace google_breakpad
|
||||
|
||||
#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);
|
||||
}
|
167
src/common/linux/module.cc
Normal file
167
src/common/linux/module.cc
Normal file
@ -0,0 +1,167 @@
|
||||
// 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 <cerrno>
|
||||
#include <cstring>
|
||||
#include "common/linux/module.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
Module::Module(const string &name, const string &os,
|
||||
const string &architecture, const string &id) :
|
||||
name_(name),
|
||||
os_(os),
|
||||
architecture_(architecture),
|
||||
id_(id),
|
||||
load_address_(0) { }
|
||||
|
||||
Module::~Module() {
|
||||
for (FileByNameMap::iterator it = files_.begin(); it != files_.end(); it++)
|
||||
delete it->second;
|
||||
for (vector<Function *>::iterator it = functions_.begin();
|
||||
it != functions_.end(); it++)
|
||||
delete *it;
|
||||
}
|
||||
|
||||
void Module::SetLoadAddress(Address address) {
|
||||
load_address_ = address;
|
||||
}
|
||||
|
||||
void Module::AddFunction(Function *function) {
|
||||
functions_.push_back(function);
|
||||
}
|
||||
|
||||
void Module::AddFunctions(vector<Function *>::iterator begin,
|
||||
vector<Function *>::iterator end) {
|
||||
functions_.insert(functions_.end(), begin, end);
|
||||
}
|
||||
|
||||
Module::File *Module::FindFile(const string &name) {
|
||||
// A tricky bit here. The key of each map entry needs to be a
|
||||
// pointer to the entry's File's name string. This means that we
|
||||
// can't do the initial lookup with any operation that would create
|
||||
// an empty entry for us if the name isn't found (like, say,
|
||||
// operator[] or insert do), because such a created entry's key will
|
||||
// be a pointer the string passed as our argument. Since the key of
|
||||
// a map's value type is const, we can't fix it up once we've
|
||||
// created our file. lower_bound does the lookup without doing an
|
||||
// insertion, and returns a good hint iterator to pass to insert.
|
||||
// Our "destiny" is where we belong, whether we're there or not now.
|
||||
FileByNameMap::iterator destiny = files_.lower_bound(&name);
|
||||
if (destiny == files_.end()
|
||||
|| *destiny->first != name) { // Repeated string comparison, boo hoo.
|
||||
File *file = new File;
|
||||
file->name_ = name;
|
||||
file->source_id_ = -1;
|
||||
destiny = files_.insert(destiny,
|
||||
FileByNameMap::value_type(&file->name_, file));
|
||||
}
|
||||
return destiny->second;
|
||||
}
|
||||
|
||||
Module::File *Module::FindFile(const char *name) {
|
||||
string name_string = name;
|
||||
return FindFile(name_string);
|
||||
}
|
||||
|
||||
void Module::AssignSourceIds() {
|
||||
// First, give every source file an id of -1.
|
||||
for (FileByNameMap::iterator file_it = files_.begin();
|
||||
file_it != files_.end(); file_it++)
|
||||
file_it->second->source_id_ = -1;
|
||||
|
||||
// Next, mark all files actually cited by our functions' line number
|
||||
// info, by setting each one's source id to zero.
|
||||
for (vector<Function *>::const_iterator func_it = functions_.begin();
|
||||
func_it != functions_.end(); func_it++) {
|
||||
Function *func = *func_it;
|
||||
for (vector<Line>::iterator line_it = func->lines_.begin();
|
||||
line_it != func->lines_.end(); line_it++)
|
||||
line_it->file_->source_id_ = 0;
|
||||
}
|
||||
|
||||
// Finally, assign source ids to those files that have been marked.
|
||||
// We could have just assigned source id numbers while traversing
|
||||
// the line numbers, but doing it this way numbers the files in
|
||||
// lexicographical order by name, which is neat.
|
||||
int next_source_id = 0;
|
||||
for (FileByNameMap::iterator file_it = files_.begin();
|
||||
file_it != files_.end(); file_it++)
|
||||
if (! file_it->second->source_id_)
|
||||
file_it->second->source_id_ = next_source_id++;
|
||||
}
|
||||
|
||||
bool Module::ReportError() {
|
||||
fprintf(stderr, "error writing symbol file: %s\n",
|
||||
strerror (errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Module::Write(FILE *stream) {
|
||||
if (0 > fprintf(stream, "MODULE %s %s %s %s\n",
|
||||
os_.c_str(), architecture_.c_str(), id_.c_str(),
|
||||
name_.c_str()))
|
||||
return ReportError();
|
||||
|
||||
// Write out files.
|
||||
AssignSourceIds();
|
||||
for (FileByNameMap::iterator file_it = files_.begin();
|
||||
file_it != files_.end(); file_it++) {
|
||||
File *file = file_it->second;
|
||||
if (file->source_id_ >= 0) {
|
||||
if (0 > fprintf(stream, "FILE %d %s\n",
|
||||
file->source_id_, file->name_.c_str()))
|
||||
return ReportError();
|
||||
}
|
||||
}
|
||||
|
||||
// Write out functions and their lines.
|
||||
for (vector<Function *>::const_iterator func_it = functions_.begin();
|
||||
func_it != functions_.end(); func_it++) {
|
||||
Function *func = *func_it;
|
||||
if (0 > fprintf(stream, "FUNC %lx %lx %lu %s\n",
|
||||
(unsigned long) (func->address_ - load_address_),
|
||||
(unsigned long) func->size_,
|
||||
(unsigned long) func->parameter_size_,
|
||||
func->name_.c_str()))
|
||||
return ReportError();
|
||||
for (vector<Line>::iterator line_it = func->lines_.begin();
|
||||
line_it != func->lines_.end(); line_it++)
|
||||
if (0 > fprintf(stream, "%lx %lx %d %d\n",
|
||||
(unsigned long) (line_it->address_ - load_address_),
|
||||
(unsigned long) line_it->size_,
|
||||
line_it->number_,
|
||||
line_it->file_->source_id_))
|
||||
return ReportError();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
193
src/common/linux/module.h
Normal file
193
src/common/linux/module.h
Normal file
@ -0,0 +1,193 @@
|
||||
// Copyright (c) 2009, Google Inc. -*- mode: c++ -*-
|
||||
// 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.
|
||||
|
||||
// module.h: defines google_breakpad::Module, for writing breakpad symbol files
|
||||
|
||||
#ifndef COMMON_LINUX_MODULE_H__
|
||||
#define COMMON_LINUX_MODULE_H__
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdio>
|
||||
|
||||
#include "google_breakpad/common/breakpad_types.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
using std::map;
|
||||
|
||||
// A Module represents the contents of a module, and supports methods
|
||||
// for adding information produced by parsing STABS or DWARF data
|
||||
// --- possibly both from the same file --- and then writing out the
|
||||
// unified contents as a Breakpad-format symbol file.
|
||||
class Module {
|
||||
public:
|
||||
// The type of addresses and sizes in a symbol table.
|
||||
typedef u_int64_t Address;
|
||||
struct File;
|
||||
struct Function;
|
||||
struct Line;
|
||||
|
||||
// Addresses appearing in File, Function, and Line structures are
|
||||
// absolute, not relative to the the module's load address. That
|
||||
// is, if the module were loaded at its nominal load address, the
|
||||
// addresses would be correct.
|
||||
|
||||
// A source file.
|
||||
struct File {
|
||||
// The name of the source file.
|
||||
string name_;
|
||||
|
||||
// The file's source id. The Write member function clears this
|
||||
// field and assigns source ids a fresh, so any value placed here
|
||||
// before calling Write will be lost.
|
||||
int source_id_;
|
||||
};
|
||||
|
||||
// A function.
|
||||
struct Function {
|
||||
// For sorting by address. (Not style-guide compliant, but it's
|
||||
// stupid not to put this in the struct.)
|
||||
static bool CompareByAddress(const Function *x, const Function *y) {
|
||||
return x->address_ < y->address_;
|
||||
}
|
||||
|
||||
// The function's name.
|
||||
string name_;
|
||||
|
||||
// The start address and length of the function's code.
|
||||
Address address_, size_;
|
||||
|
||||
// The function's parameter size.
|
||||
Address parameter_size_;
|
||||
|
||||
// Source lines belonging to this function, sorted by increasing
|
||||
// address.
|
||||
vector<Line> lines_;
|
||||
};
|
||||
|
||||
// A source line.
|
||||
struct Line {
|
||||
// For sorting by address. (Not style-guide compliant, but it's
|
||||
// stupid not to put this in the struct.)
|
||||
static bool CompareByAddress(const Module::Line &x, const Module::Line &y) {
|
||||
return x.address_ < y.address_;
|
||||
}
|
||||
|
||||
Address address_, size_; // The address and size of the line's code.
|
||||
File *file_; // The source file.
|
||||
int number_; // The source line number.
|
||||
};
|
||||
|
||||
// Create a new module with the given name, operating system,
|
||||
// architecture, and ID string.
|
||||
Module(const string &name, const string &os, const string &architecture,
|
||||
const string &id);
|
||||
~Module();
|
||||
|
||||
// Set the module's load address to LOAD_ADDRESS; addresses given
|
||||
// for functions and lines will be written to the Breakpad symbol
|
||||
// file as offsets from this address. Construction initializes this
|
||||
// module's load address to zero: addresses written to the symbol
|
||||
// file will be the same as they appear in the File and Line
|
||||
// structures.
|
||||
void SetLoadAddress(Address load_address);
|
||||
|
||||
// Add FUNCTION to the module.
|
||||
// Destroying this module frees all Function objects that have been
|
||||
// added with this function.
|
||||
void AddFunction(Function *function);
|
||||
|
||||
// Add all the functions in [BEGIN,END) to the module.
|
||||
// Destroying this module frees all Function objects that have been
|
||||
// added with this function.
|
||||
void AddFunctions(vector<Function *>::iterator begin,
|
||||
vector<Function *>::iterator end);
|
||||
|
||||
// If this module has a file named NAME, return a pointer to it. If
|
||||
// it has none, then create one and return a pointer to the new
|
||||
// file. Destroying this module frees all File objects that have
|
||||
// been created using this function, or with Insert.
|
||||
File *FindFile(const string &name);
|
||||
File *FindFile(const char *name);
|
||||
|
||||
// Write this module to STREAM in the breakpad symbol format.
|
||||
// Return true if all goes well, or false if an error occurs. This
|
||||
// method writes out:
|
||||
// - a header based on the values given to the constructor,
|
||||
// - the source files added via FindFile, and finally
|
||||
// - the functions added via AddFunctions, each with its lines.
|
||||
// Addresses in the output are all relative to the load address
|
||||
// established by SetLoadAddress.
|
||||
bool Write(FILE *stream);
|
||||
|
||||
private:
|
||||
|
||||
// Find those files in this module that are actually referred to by
|
||||
// functions' line number data, and assign them source id numbers.
|
||||
// Set the source id numbers for all other files --- unused by the
|
||||
// source line data --- to -1. We do this before writing out the
|
||||
// symbol file, at which point we omit any unused files.
|
||||
void AssignSourceIds();
|
||||
|
||||
// Report an error that has occurred writing the symbol file, using
|
||||
// errno to find the appropriate cause. Return false.
|
||||
static bool ReportError();
|
||||
|
||||
// Module header entries.
|
||||
string name_, os_, architecture_, id_;
|
||||
|
||||
// The module's nominal load address. Addresses for functions and
|
||||
// lines are absolute, assuming the module is loaded at this
|
||||
// address.
|
||||
Address load_address_;
|
||||
|
||||
// Relation for maps whose keys are strings shared with some other
|
||||
// structure.
|
||||
struct CompareStringPtrs {
|
||||
bool operator()(const string *x, const string *y) { return *x < *y; };
|
||||
};
|
||||
|
||||
// A map from filenames to File structures. The map's keys are
|
||||
// pointers to the Files' names.
|
||||
typedef map<const string *, File *, CompareStringPtrs> FileByNameMap;
|
||||
|
||||
// The module owns all the files and functions that have been added
|
||||
// to it; destroying the module frees the Files and Functions these
|
||||
// point to.
|
||||
FileByNameMap files_; // This module's source files.
|
||||
vector<Function *> functions_; // This module's functions.
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // COMMON_LINUX_MODULE_H__
|
195
src/common/linux/stabs_reader.cc
Normal file
195
src/common/linux/stabs_reader.cc
Normal file
@ -0,0 +1,195 @@
|
||||
// Copyright 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 file implements the google_breakpad::StabsReader class.
|
||||
|
||||
#include <a.out.h>
|
||||
#include <stab.h>
|
||||
#include <cstring>
|
||||
#include <cassert>
|
||||
|
||||
#include "common/linux/stabs_reader.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
StabsReader::StabsReader(const uint8_t *stab, size_t stab_size,
|
||||
const uint8_t *stabstr, size_t stabstr_size,
|
||||
StabsHandler *handler) :
|
||||
stabstr_(stabstr),
|
||||
stabstr_size_(stabstr_size),
|
||||
handler_(handler),
|
||||
symbol_(NULL),
|
||||
current_source_file_(NULL) {
|
||||
symbols_ = reinterpret_cast<const struct nlist *>(stab);
|
||||
symbols_end_ = symbols_ + (stab_size / sizeof (*symbols_));
|
||||
}
|
||||
|
||||
const char *StabsReader::SymbolString() {
|
||||
ptrdiff_t offset = symbol_->n_un.n_strx;
|
||||
if (offset < 0 || (size_t) offset >= stabstr_size_) {
|
||||
handler_->Warning("symbol %d: name offset outside the string section",
|
||||
symbol_ - symbols_);
|
||||
// Return our null string, to keep our promise about all names being
|
||||
// taken from the string section.
|
||||
offset = 0;
|
||||
}
|
||||
return reinterpret_cast<const char *>(stabstr_ + offset);
|
||||
}
|
||||
|
||||
bool StabsReader::Process() {
|
||||
symbol_ = symbols_;
|
||||
while (symbol_ < symbols_end_) {
|
||||
if (symbol_->n_type == N_SO) {
|
||||
if (! ProcessCompilationUnit())
|
||||
return false;
|
||||
} else
|
||||
symbol_++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StabsReader::ProcessCompilationUnit() {
|
||||
assert(symbol_ < symbols_end_ && symbol_->n_type == N_SO);
|
||||
|
||||
// There may be an N_SO entry whose name ends with a slash,
|
||||
// indicating the directory in which the compilation occurred.
|
||||
// The build directory defaults to NULL.
|
||||
const char *build_directory = NULL;
|
||||
{
|
||||
const char *name = SymbolString();
|
||||
if (name[0] && name[strlen(name) - 1] == '/') {
|
||||
build_directory = name;
|
||||
symbol_++;
|
||||
}
|
||||
}
|
||||
|
||||
// We expect to see an N_SO entry with a filename next, indicating
|
||||
// the start of the compilation unit.
|
||||
{
|
||||
if (symbol_ >= symbols_end_ || symbol_->n_type != N_SO)
|
||||
return true;
|
||||
const char *name = SymbolString();
|
||||
if (name[0] == '\0')
|
||||
return true;
|
||||
current_source_file_ = name;
|
||||
}
|
||||
|
||||
if (! handler_->StartCompilationUnit(current_source_file_,
|
||||
SymbolValue(),
|
||||
build_directory))
|
||||
return false;
|
||||
|
||||
symbol_++;
|
||||
|
||||
// The STABS documentation says that some compilers may emit
|
||||
// additional N_SO units with names immediately following the first,
|
||||
// and that they should be ignored. However, the original Breakpad
|
||||
// STABS reader doesn't ignore them, so we won't either.
|
||||
|
||||
// Process the body of the compilation unit, up to the next N_SO.
|
||||
while (symbol_ < symbols_end_ && symbol_->n_type != N_SO) {
|
||||
if (symbol_->n_type == N_FUN) {
|
||||
if (! ProcessFunction())
|
||||
return false;
|
||||
} else
|
||||
// Ignore anything else.
|
||||
symbol_++;
|
||||
}
|
||||
|
||||
// An N_SO with an empty name indicates the end of the compilation
|
||||
// unit. Default to zero.
|
||||
uint64_t ending_address = 0;
|
||||
if (symbol_ < symbols_end_) {
|
||||
assert(symbol_->n_type == N_SO);
|
||||
const char *name = SymbolString();
|
||||
if (name[0] == '\0') {
|
||||
ending_address = SymbolValue();
|
||||
symbol_++;
|
||||
}
|
||||
}
|
||||
|
||||
if (! handler_->EndCompilationUnit(ending_address))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StabsReader::ProcessFunction() {
|
||||
assert(symbol_ < symbols_end_ && symbol_->n_type == N_FUN);
|
||||
|
||||
uint64_t function_address = SymbolValue();
|
||||
// The STABS string for an N_FUN entry is the name of the function,
|
||||
// followed by a colon, followed by type information for the
|
||||
// function. We want to pass the name alone to StartFunction.
|
||||
const char *stab_string = SymbolString();
|
||||
const char *name_end = strchr(stab_string, ':');
|
||||
if (! name_end)
|
||||
name_end = stab_string + strlen(stab_string);
|
||||
std::string name(stab_string, name_end - stab_string);
|
||||
if (! handler_->StartFunction(name, function_address))
|
||||
return false;
|
||||
symbol_++;
|
||||
|
||||
while (symbol_ < symbols_end_) {
|
||||
if (symbol_->n_type == N_SO || symbol_->n_type == N_FUN)
|
||||
break;
|
||||
else if (symbol_->n_type == N_SLINE) {
|
||||
// The value of an N_SLINE entry is the offset of the line from
|
||||
// the function's start address.
|
||||
uint64_t line_address = function_address + SymbolValue();
|
||||
// The n_desc of a N_SLINE entry is the line number. It's a
|
||||
// signed 16-bit field; line numbers from 32768 to 65535 are
|
||||
// stored as n-65536.
|
||||
uint16_t line_number = symbol_->n_desc;
|
||||
if (! handler_->Line(line_address, current_source_file_, line_number))
|
||||
return false;
|
||||
symbol_++;
|
||||
} else if (symbol_->n_type == N_SOL) {
|
||||
current_source_file_ = SymbolString();
|
||||
symbol_++;
|
||||
} else
|
||||
// Ignore anything else.
|
||||
symbol_++;
|
||||
}
|
||||
|
||||
// If there is a subsequent N_SO or N_FUN entry, its address is our
|
||||
// end address.
|
||||
uint64_t ending_address = 0;
|
||||
if (symbol_ < symbols_end_) {
|
||||
assert(symbol_->n_type == N_SO || symbol_->n_type == N_FUN);
|
||||
ending_address = SymbolValue();
|
||||
// Note: we do not increment symbol_ here, since we haven't consumed it.
|
||||
}
|
||||
|
||||
if (! handler_->EndFunction(ending_address))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
188
src/common/linux/stabs_reader.h
Normal file
188
src/common/linux/stabs_reader.h
Normal file
@ -0,0 +1,188 @@
|
||||
// Copyright 2009 Google Inc. All Rights Reserved. -*- mode: c++ -*-
|
||||
//
|
||||
// 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 file contains definitions related to the STABS reader and
|
||||
// its handler interfaces.
|
||||
// A description of the STABS debugging format can be found at
|
||||
// http://sourceware.org/gdb/current/onlinedocs/stabs_toc.html
|
||||
// The comments here assume you understand the format.
|
||||
//
|
||||
// This reader assumes that the system's <a.out.h> and <stab.h>
|
||||
// headers accurately describe the layout of the STABS data; this code
|
||||
// is not cross-platform safe.
|
||||
|
||||
#ifndef COMMON_LINUX_STABS_READER_H__
|
||||
#define COMMON_LINUX_STABS_READER_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <cstddef>
|
||||
#include <a.out.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
class StabsHandler;
|
||||
|
||||
class StabsReader {
|
||||
public:
|
||||
// Create a reader for the STABS debug information whose .stab
|
||||
// section is the STAB_SIZE bytes at STAB, and whose .stabstr
|
||||
// section is the STABSTR_SIZE bytes at STABSTR. The reader will
|
||||
// call the methods of HANDLER to report the information it finds,
|
||||
// when the reader's 'process' method is called.
|
||||
//
|
||||
// Note that, in ELF, the .stabstr section should be found using the
|
||||
// 'sh_link' field of the .stab section header, not by name.
|
||||
StabsReader(const uint8_t *stab, size_t stab_size,
|
||||
const uint8_t *stabstr, size_t stabstr_size,
|
||||
StabsHandler *handler);
|
||||
|
||||
// Process the STAB data, calling the handler's methods to report
|
||||
// what we find. While the handler functions return true, continue
|
||||
// to process until we reach the end of the section. If we
|
||||
// processed the entire section and all handlers returned true,
|
||||
// return true. If any handler returned false, return false.
|
||||
bool Process();
|
||||
|
||||
private:
|
||||
// Return the name of the current symbol.
|
||||
const char *SymbolString();
|
||||
|
||||
// Return the value of the current symbol.
|
||||
const uint64_t SymbolValue() {
|
||||
return symbol_->n_value;
|
||||
}
|
||||
|
||||
// Process a compilation unit starting at symbol_. Return true
|
||||
// to continue processing, or false to abort.
|
||||
bool ProcessCompilationUnit();
|
||||
|
||||
// Process a function in current_source_file_ starting at symbol_.
|
||||
// Return true to continue processing, or false to abort.
|
||||
bool ProcessFunction();
|
||||
|
||||
// The debugging information we're reading.
|
||||
const struct nlist *symbols_, *symbols_end_;
|
||||
const uint8_t *stabstr_;
|
||||
size_t stabstr_size_;
|
||||
|
||||
StabsHandler *handler_;
|
||||
|
||||
// The current symbol we're processing.
|
||||
const struct nlist *symbol_;
|
||||
|
||||
// The current source file name.
|
||||
const char *current_source_file_;
|
||||
};
|
||||
|
||||
// Consumer-provided callback structure for the STABS reader.
|
||||
// Clients of the STABS reader provide an instance of this structure.
|
||||
// The reader then invokes the methods of that instance to report the
|
||||
// information it finds.
|
||||
//
|
||||
// The default definitions of the methods do nothing.
|
||||
class StabsHandler {
|
||||
public:
|
||||
StabsHandler() { }
|
||||
virtual ~StabsHandler() { }
|
||||
|
||||
// Some general notes about the handler callback functions:
|
||||
|
||||
// Processing proceeds until the end of the .stabs section, or until
|
||||
// one of these functions returns false.
|
||||
|
||||
// The addresses given are as reported in the STABS info, without
|
||||
// regard for whether the module may be loaded at different
|
||||
// addresses at different times (a shared library, say). When
|
||||
// processing STABS from an ELF shared library, the addresses given
|
||||
// all assume the library is loaded at its nominal load address.
|
||||
// They are *not* offsets from the nominal load address. If you
|
||||
// want offsets, you must subtract off the library's nominal load
|
||||
// address.
|
||||
|
||||
// The arguments to these functions named FILENAME are all
|
||||
// references to strings stored in the .stabstr section. Because
|
||||
// both the Linux and Solaris linkers factor out duplicate strings
|
||||
// from the .stabstr section, the consumer can assume that if two
|
||||
// FILENAME values are different addresses, they represent different
|
||||
// file names.
|
||||
//
|
||||
// Thus, it's safe to use (say) std::map<char *, ...>, which does
|
||||
// address comparisons. Since all the pointers are into the array
|
||||
// holding the .stabstr section's contents, comparing them produces
|
||||
// predictable results.
|
||||
|
||||
// Begin processing a compilation unit whose main source file is
|
||||
// named FILENAME, and whose base address is ADDRESS. If
|
||||
// BUILD_DIRECTORY is non-NULL, it is the name of the build
|
||||
// directory in which the compilation occurred.
|
||||
virtual bool StartCompilationUnit(const char *filename, uint64_t address,
|
||||
const char *build_directory) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Finish processing the compilation unit. If END_ADDRESS is
|
||||
// non-zero, it is the ending address of the compilation unit. This
|
||||
// information may not be available, in which case the consumer must
|
||||
// infer it by other means.
|
||||
virtual bool EndCompilationUnit(uint64_t address) { return true; }
|
||||
|
||||
// Begin processing a function named NAME, whose starting address is
|
||||
// ADDRESS. This function belongs to the compilation unit that was
|
||||
// most recently started but not ended.
|
||||
//
|
||||
// Note that, unlike filenames, NAME is not a pointer into the
|
||||
// .stabstr section; this is because the name as it appears in the
|
||||
// STABS data is followed by type information. The value passed to
|
||||
// StartFunction is the function name alone.
|
||||
virtual bool StartFunction(const std::string &name, uint64_t address) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Finishing processing the function. If END_ADDRESS is non-zero,
|
||||
// it is the ending address for the function. This information may
|
||||
// not be available, in which case the consumer must infer it by
|
||||
// other means.
|
||||
virtual bool EndFunction(uint64_t address) { return true; }
|
||||
|
||||
// Report that the code at ADDRESS is attributable to line NUMBER of
|
||||
// the source file named FILENAME. The caller must infer the ending
|
||||
// address of the line.
|
||||
virtual bool Line(uint64_t address, const char *filename, int number) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Report a warning. FORMAT is a printf-like format string,
|
||||
// specifying how to format the subsequent arguments.
|
||||
virtual void Warning(const char *format, ...) { }
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // COMMON_LINUX_STABS_READER_H__
|
@ -16,7 +16,8 @@ BIN=dump_syms
|
||||
|
||||
all:$(BIN)
|
||||
|
||||
DUMP_OBJ=dump_symbols.o guid_creator.o dump_syms.o file_id.o md5.o
|
||||
DUMP_OBJ=dump_symbols.o guid_creator.o dump_syms.o file_id.o md5.o \
|
||||
stabs_reader.o module.o
|
||||
|
||||
dump_syms:$(DUMP_OBJ)
|
||||
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -o $@ $^
|
||||
@ -24,6 +25,12 @@ dump_syms:$(DUMP_OBJ)
|
||||
dump_symbols.o:../../../common/linux/dump_symbols.cc
|
||||
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $^
|
||||
|
||||
stabs_reader.o:../../../common/linux/stabs_reader.cc
|
||||
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $^
|
||||
|
||||
module.o:../../../common/linux/module.cc
|
||||
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $^
|
||||
|
||||
guid_creator.o:../../../common/linux/guid_creator.cc
|
||||
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $^
|
||||
|
||||
|
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