зеркало из https://github.com/mozilla/gecko-dev.git
bug 514188 - sync to breakpad revision 437 to pick up Linux client rewrite. r=bsmedberg
This commit is contained in:
Родитель
1bc4764e56
Коммит
aba2a6854a
|
@ -0,0 +1,3 @@
|
||||||
|
MODULE Linux x86 B8CFDE93002D54DA1900A40AA1BD67690 linux-gate.so
|
||||||
|
PUBLIC 400 0 __kernel_vsyscall
|
||||||
|
STACK WIN 4 400 100 1 1 0 0 0 0 0 1
|
|
@ -0,0 +1,3 @@
|
||||||
|
MODULE Linux x86 4FBDA58B5A1DF5A379E3CF19A235EA090 linux-gate.so
|
||||||
|
PUBLIC 400 0 __kernel_vsyscall
|
||||||
|
STACK WIN 4 400 200 3 3 0 0 0 0 0 1
|
|
@ -1,8 +1,6 @@
|
||||||
// Copyright (c) 2006, Google Inc.
|
// Copyright (c) 2009, Google Inc.
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
//
|
//
|
||||||
// Author: Li Liu
|
|
||||||
//
|
|
||||||
// Redistribution and use in source and binary forms, with or without
|
// Redistribution and use in source and binary forms, with or without
|
||||||
// modification, are permitted provided that the following conditions are
|
// modification, are permitted provided that the following conditions are
|
||||||
// met:
|
// met:
|
||||||
|
@ -29,49 +27,88 @@
|
||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
#include <signal.h>
|
// The ExceptionHandler object installs signal handlers for a number of
|
||||||
#include <sys/stat.h>
|
// signals. We rely on the signal handler running on the thread which crashed
|
||||||
#include <sys/types.h>
|
// in order to identify it. This is true of the synchronous signals (SEGV etc),
|
||||||
#include <string.h>
|
// but not true of ABRT. Thus, if you send ABRT to yourself in a program which
|
||||||
#include <unistd.h>
|
// uses ExceptionHandler, you need to use tgkill to direct it to the current
|
||||||
|
// thread.
|
||||||
|
//
|
||||||
|
// The signal flow looks like this:
|
||||||
|
//
|
||||||
|
// SignalHandler (uses a global stack of ExceptionHandler objects to find
|
||||||
|
// | one to handle the signal. If the first rejects it, try
|
||||||
|
// | the second etc...)
|
||||||
|
// V
|
||||||
|
// HandleSignal ----------------------------| (clones a new process which
|
||||||
|
// | | shares an address space with
|
||||||
|
// (wait for cloned | the crashed process. This
|
||||||
|
// process) | allows us to ptrace the crashed
|
||||||
|
// | | process)
|
||||||
|
// V V
|
||||||
|
// (set signal handler to ThreadEntry (static function to bounce
|
||||||
|
// SIG_DFL and rethrow, | back into the object)
|
||||||
|
// killing the crashed |
|
||||||
|
// process) V
|
||||||
|
// DoDump (writes minidump)
|
||||||
|
// |
|
||||||
|
// V
|
||||||
|
// sys_exit
|
||||||
|
//
|
||||||
|
|
||||||
#include <cassert>
|
// This code is a little fragmented. Different functions of the ExceptionHandler
|
||||||
#include <cstdlib>
|
// class run in a number of different contexts. Some of them run in a normal
|
||||||
#include <cstdio>
|
// context and are easy to code, others run in a compromised context and the
|
||||||
#include <ctime>
|
// restrictions at the top of minidump_writer.cc apply: no libc and use the
|
||||||
#include <linux/limits.h>
|
// alternative malloc. Each function should have comment above it detailing the
|
||||||
|
// context which it runs in.
|
||||||
|
|
||||||
#include "client/linux/handler/exception_handler.h"
|
#include "client/linux/handler/exception_handler.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <linux/limits.h>
|
||||||
|
#include <sched.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <sys/signal.h>
|
||||||
|
#include <sys/syscall.h>
|
||||||
|
#include <sys/ucontext.h>
|
||||||
|
#include <sys/user.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "common/linux/linux_libc_support.h"
|
||||||
|
#include "common/linux/linux_syscall_support.h"
|
||||||
|
#include "common/linux/memory.h"
|
||||||
|
#include "client/linux/minidump_writer//minidump_writer.h"
|
||||||
#include "common/linux/guid_creator.h"
|
#include "common/linux/guid_creator.h"
|
||||||
#include "google_breakpad/common/minidump_format.h"
|
|
||||||
|
// A wrapper for the tgkill syscall: send a signal to a specific thread.
|
||||||
|
static int tgkill(pid_t tgid, pid_t tid, int sig) {
|
||||||
|
syscall(__NR_tgkill, tgid, tid, sig);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
namespace google_breakpad {
|
namespace google_breakpad {
|
||||||
|
|
||||||
// Signals that we are interested.
|
// The list of signals which we consider to be crashes. The default action for
|
||||||
int SigTable[] = {
|
// all these signals must be Core (see man 7 signal) because we rethrow the
|
||||||
#if defined(SIGSEGV)
|
// signal after handling it and expect that it'll be fatal.
|
||||||
SIGSEGV,
|
static const int kExceptionSignals[] = {
|
||||||
#endif
|
SIGSEGV, SIGABRT, SIGFPE, SIGILL, SIGBUS, -1
|
||||||
#ifdef SIGABRT
|
|
||||||
SIGABRT,
|
|
||||||
#endif
|
|
||||||
#ifdef SIGFPE
|
|
||||||
SIGFPE,
|
|
||||||
#endif
|
|
||||||
#ifdef SIGILL
|
|
||||||
SIGILL,
|
|
||||||
#endif
|
|
||||||
#ifdef SIGBUS
|
|
||||||
SIGBUS,
|
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<ExceptionHandler*> *ExceptionHandler::handler_stack_ = NULL;
|
// We can stack multiple exception handlers. In that case, this is the global
|
||||||
int ExceptionHandler::handler_stack_index_ = 0;
|
// which holds the stack.
|
||||||
|
std::vector<ExceptionHandler*>* ExceptionHandler::handler_stack_ = NULL;
|
||||||
|
unsigned ExceptionHandler::handler_stack_index_ = 0;
|
||||||
pthread_mutex_t ExceptionHandler::handler_stack_mutex_ =
|
pthread_mutex_t ExceptionHandler::handler_stack_mutex_ =
|
||||||
PTHREAD_MUTEX_INITIALIZER;
|
PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
|
||||||
ExceptionHandler::ExceptionHandler(const string &dump_path,
|
// Runs before crashing: normal context.
|
||||||
|
ExceptionHandler::ExceptionHandler(const std::string &dump_path,
|
||||||
FilterCallback filter,
|
FilterCallback filter,
|
||||||
MinidumpCallback callback,
|
MinidumpCallback callback,
|
||||||
void *callback_context,
|
void *callback_context,
|
||||||
|
@ -80,212 +117,77 @@ ExceptionHandler::ExceptionHandler(const string &dump_path,
|
||||||
callback_(callback),
|
callback_(callback),
|
||||||
callback_context_(callback_context),
|
callback_context_(callback_context),
|
||||||
dump_path_(),
|
dump_path_(),
|
||||||
installed_handler_(install_handler) {
|
handler_installed_(install_handler),
|
||||||
|
crash_handler_(NULL) {
|
||||||
set_dump_path(dump_path);
|
set_dump_path(dump_path);
|
||||||
|
|
||||||
act_.sa_handler = HandleException;
|
|
||||||
act_.sa_flags = SA_ONSTACK;
|
|
||||||
sigemptyset(&act_.sa_mask);
|
|
||||||
// now, make sure we're blocking all the signals we are handling
|
|
||||||
// when we're handling any of them
|
|
||||||
for ( size_t i = 0; i < sizeof(SigTable) / sizeof(SigTable[0]); ++i) {
|
|
||||||
sigaddset(&act_.sa_mask, SigTable[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (install_handler) {
|
if (install_handler) {
|
||||||
SetupHandler();
|
InstallHandlers();
|
||||||
|
|
||||||
pthread_mutex_lock(&handler_stack_mutex_);
|
pthread_mutex_lock(&handler_stack_mutex_);
|
||||||
if (handler_stack_ == NULL)
|
if (handler_stack_ == NULL)
|
||||||
handler_stack_ = new std::vector<ExceptionHandler *>;
|
handler_stack_ = new std::vector<ExceptionHandler *>;
|
||||||
handler_stack_->push_back(this);
|
handler_stack_->push_back(this);
|
||||||
pthread_mutex_unlock(&handler_stack_mutex_);
|
pthread_mutex_unlock(&handler_stack_mutex_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Runs before crashing: normal context.
|
||||||
ExceptionHandler::~ExceptionHandler() {
|
ExceptionHandler::~ExceptionHandler() {
|
||||||
TeardownAllHandler();
|
UninstallHandlers();
|
||||||
pthread_mutex_lock(&handler_stack_mutex_);
|
|
||||||
if (handler_stack_->back() == this) {
|
|
||||||
handler_stack_->pop_back();
|
|
||||||
} else {
|
|
||||||
fprintf(stderr, "warning: removing Breakpad handler out of order\n");
|
|
||||||
for (std::vector<ExceptionHandler *>::iterator iterator =
|
|
||||||
handler_stack_->begin();
|
|
||||||
iterator != handler_stack_->end();
|
|
||||||
++iterator) {
|
|
||||||
if (*iterator == this) {
|
|
||||||
handler_stack_->erase(iterator);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (handler_stack_->empty()) {
|
|
||||||
// When destroying the last ExceptionHandler that installed a handler,
|
|
||||||
// clean up the handler stack.
|
|
||||||
delete handler_stack_;
|
|
||||||
handler_stack_ = NULL;
|
|
||||||
}
|
|
||||||
pthread_mutex_unlock(&handler_stack_mutex_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ExceptionHandler::WriteMinidump() {
|
// Runs before crashing: normal context.
|
||||||
bool success = InternalWriteMinidump(0, 0, NULL);
|
bool ExceptionHandler::InstallHandlers() {
|
||||||
UpdateNextID();
|
// We run the signal handlers on an alternative stack because we might have
|
||||||
return success;
|
// crashed because of a stack overflow.
|
||||||
}
|
|
||||||
|
|
||||||
// static
|
// We use this value rather than SIGSTKSZ because we would end up overrunning
|
||||||
bool ExceptionHandler::WriteMinidump(const string &dump_path,
|
// such a small stack.
|
||||||
MinidumpCallback callback,
|
static const unsigned kSigStackSize = 8192;
|
||||||
void *callback_context) {
|
|
||||||
ExceptionHandler handler(dump_path, NULL, callback,
|
|
||||||
callback_context, false);
|
|
||||||
return handler.InternalWriteMinidump(0, 0, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ExceptionHandler::SetupHandler() {
|
signal_stack = malloc(kSigStackSize);
|
||||||
// Signal on a different stack to avoid using the stack
|
stack_t stack;
|
||||||
// of the crashing thread.
|
memset(&stack, 0, sizeof(stack));
|
||||||
struct sigaltstack sig_stack;
|
stack.ss_sp = signal_stack;
|
||||||
sig_stack.ss_sp = malloc(MINSIGSTKSZ);
|
stack.ss_size = kSigStackSize;
|
||||||
if (sig_stack.ss_sp == NULL)
|
|
||||||
return;
|
|
||||||
sig_stack.ss_size = MINSIGSTKSZ;
|
|
||||||
sig_stack.ss_flags = 0;
|
|
||||||
|
|
||||||
if (sigaltstack(&sig_stack, NULL) < 0)
|
if (sigaltstack(&stack, NULL) == -1)
|
||||||
return;
|
|
||||||
for (size_t i = 0; i < sizeof(SigTable) / sizeof(SigTable[0]); ++i)
|
|
||||||
SetupHandler(SigTable[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ExceptionHandler::SetupHandler(int signo) {
|
|
||||||
|
|
||||||
// We're storing pointers to the old signal action
|
|
||||||
// structure, rather than copying the structure
|
|
||||||
// because we can't count on the sa_mask field to
|
|
||||||
// be scalar.
|
|
||||||
struct sigaction *old_act = &old_actions_[signo];
|
|
||||||
|
|
||||||
if (sigaction(signo, &act_, old_act) < 0)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ExceptionHandler::TeardownHandler(int signo) {
|
|
||||||
TeardownHandler(signo, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ExceptionHandler::TeardownHandler(int signo, struct sigaction *final_handler) {
|
|
||||||
if (old_actions_[signo].sa_handler) {
|
|
||||||
struct sigaction *act = &old_actions_[signo];
|
|
||||||
sigaction(signo, act, final_handler);
|
|
||||||
memset(&old_actions_[signo], 0x0, sizeof(struct sigaction));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ExceptionHandler::TeardownAllHandler() {
|
|
||||||
for (size_t i = 0; i < sizeof(SigTable) / sizeof(SigTable[0]); ++i) {
|
|
||||||
TeardownHandler(SigTable[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// static
|
|
||||||
void ExceptionHandler::HandleException(int signo) {
|
|
||||||
// In Linux, the context information about the signal is put on the stack of
|
|
||||||
// the signal handler frame as value parameter. For some reasons, the
|
|
||||||
// prototype of the handler doesn't declare this information as parameter, we
|
|
||||||
// will do it by hand. It is the second parameter above the signal number.
|
|
||||||
// However, if we are being called by another signal handler passing the
|
|
||||||
// signal up the chain, then we may not have this random extra parameter,
|
|
||||||
// so we may have to walk the stack to find it. We do the actual work
|
|
||||||
// on another thread, where it's a little safer, but we want the ebp
|
|
||||||
// from this frame to find it.
|
|
||||||
uintptr_t current_ebp = 0;
|
|
||||||
asm volatile ("movl %%ebp, %0"
|
|
||||||
:"=m"(current_ebp));
|
|
||||||
|
|
||||||
pthread_mutex_lock(&handler_stack_mutex_);
|
|
||||||
ExceptionHandler *current_handler =
|
|
||||||
handler_stack_->at(handler_stack_->size() - ++handler_stack_index_);
|
|
||||||
pthread_mutex_unlock(&handler_stack_mutex_);
|
|
||||||
|
|
||||||
// Restore original handler.
|
|
||||||
struct sigaction old_action;
|
|
||||||
current_handler->TeardownHandler(signo, &old_action);
|
|
||||||
|
|
||||||
struct sigcontext *sig_ctx = NULL;
|
|
||||||
if (current_handler->InternalWriteMinidump(signo, current_ebp, &sig_ctx)) {
|
|
||||||
// Fully handled this exception, safe to exit.
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
} else {
|
|
||||||
// Exception not fully handled, will call the next handler in stack to
|
|
||||||
// process it.
|
|
||||||
if (old_action.sa_handler != NULL && sig_ctx != NULL) {
|
|
||||||
|
|
||||||
// Have our own typedef, because of the comment above w.r.t signal
|
|
||||||
// context on the stack
|
|
||||||
typedef void (*SignalHandler)(int signo, struct sigcontext);
|
|
||||||
|
|
||||||
SignalHandler old_handler =
|
|
||||||
reinterpret_cast<SignalHandler>(old_action.sa_handler);
|
|
||||||
|
|
||||||
sigset_t old_set;
|
|
||||||
// Use SIG_BLOCK here because we don't want to unblock a signal
|
|
||||||
// that the signal handler we're currently in needs to block
|
|
||||||
sigprocmask(SIG_BLOCK, &old_action.sa_mask, &old_set);
|
|
||||||
old_handler(signo, *sig_ctx);
|
|
||||||
sigprocmask(SIG_SETMASK, &old_set, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
pthread_mutex_lock(&handler_stack_mutex_);
|
|
||||||
current_handler->SetupHandler(signo);
|
|
||||||
--handler_stack_index_;
|
|
||||||
// All the handlers in stack have been invoked to handle the exception,
|
|
||||||
// normally the process should be terminated and should not reach here.
|
|
||||||
// In case we got here, ask the OS to handle it to avoid endless loop,
|
|
||||||
// normally the OS will generate a core and termiate the process. This
|
|
||||||
// may be desired to debug the program.
|
|
||||||
if (handler_stack_index_ == 0)
|
|
||||||
signal(signo, SIG_DFL);
|
|
||||||
pthread_mutex_unlock(&handler_stack_mutex_);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ExceptionHandler::InternalWriteMinidump(int signo,
|
|
||||||
uintptr_t sighandler_ebp,
|
|
||||||
struct sigcontext **sig_ctx) {
|
|
||||||
if (filter_ && !filter_(callback_context_))
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
bool success = false;
|
struct sigaction sa;
|
||||||
// Block all the signals we want to process when writting minidump.
|
memset(&sa, 0, sizeof(sa));
|
||||||
// We don't want it to be interrupted.
|
sigemptyset(&sa.sa_mask);
|
||||||
sigset_t sig_blocked, sig_old;
|
|
||||||
bool blocked = true;
|
// mask all exception signals when we're handling one of them.
|
||||||
sigfillset(&sig_blocked);
|
for (unsigned i = 0; kExceptionSignals[i] != -1; ++i)
|
||||||
for (size_t i = 0; i < sizeof(SigTable) / sizeof(SigTable[0]); ++i)
|
sigaddset(&sa.sa_mask, kExceptionSignals[i]);
|
||||||
sigdelset(&sig_blocked, SigTable[i]);
|
|
||||||
if (sigprocmask(SIG_BLOCK, &sig_blocked, &sig_old) != 0) {
|
sa.sa_sigaction = SignalHandler;
|
||||||
blocked = false;
|
sa.sa_flags = SA_ONSTACK | SA_SIGINFO;
|
||||||
fprintf(stderr, "google_breakpad::ExceptionHandler::HandleException: "
|
|
||||||
"failed to block signals.\n");
|
for (unsigned i = 0; kExceptionSignals[i] != -1; ++i) {
|
||||||
|
struct sigaction* old = new struct sigaction;
|
||||||
|
if (sigaction(kExceptionSignals[i], &sa, old) == -1)
|
||||||
|
return false;
|
||||||
|
old_handlers_.push_back(std::make_pair(kExceptionSignals[i], old));
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
success = minidump_generator_.WriteMinidumpToFile(
|
|
||||||
next_minidump_path_c_, signo, sighandler_ebp, sig_ctx);
|
|
||||||
|
|
||||||
// Unblock the signals.
|
|
||||||
if (blocked) {
|
|
||||||
sigprocmask(SIG_SETMASK, &sig_old, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (callback_)
|
|
||||||
success = callback_(dump_path_c_, next_minidump_id_c_,
|
|
||||||
callback_context_, success);
|
|
||||||
return success;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Runs before crashing: normal context.
|
||||||
|
void ExceptionHandler::UninstallHandlers() {
|
||||||
|
for (unsigned i = 0; i < old_handlers_.size(); ++i) {
|
||||||
|
struct sigaction *action =
|
||||||
|
reinterpret_cast<struct sigaction*>(old_handlers_[i].second);
|
||||||
|
sigaction(old_handlers_[i].first, action, NULL);
|
||||||
|
delete action;
|
||||||
|
}
|
||||||
|
|
||||||
|
old_handlers_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Runs before crashing: normal context.
|
||||||
void ExceptionHandler::UpdateNextID() {
|
void ExceptionHandler::UpdateNextID() {
|
||||||
GUID guid;
|
GUID guid;
|
||||||
char guid_str[kGUIDStringLength + 1];
|
char guid_str[kGUIDStringLength + 1];
|
||||||
|
@ -303,4 +205,120 @@ void ExceptionHandler::UpdateNextID() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This function runs in a compromised context: see the top of the file.
|
||||||
|
// Runs on the crashing thread.
|
||||||
|
// static
|
||||||
|
void ExceptionHandler::SignalHandler(int sig, siginfo_t* info, void* uc) {
|
||||||
|
// All the exception signals are blocked at this point.
|
||||||
|
|
||||||
|
pthread_mutex_lock(&handler_stack_mutex_);
|
||||||
|
|
||||||
|
if (!handler_stack_->size()) {
|
||||||
|
pthread_mutex_unlock(&handler_stack_mutex_);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = handler_stack_->size() - 1; i >= 0; --i) {
|
||||||
|
if ((*handler_stack_)[i]->HandleSignal(sig, info, uc)) {
|
||||||
|
// successfully handled: We are in an invalid state since an exception
|
||||||
|
// signal has been delivered. We don't call the exit handlers because
|
||||||
|
// they could end up corrupting on-disk state.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&handler_stack_mutex_);
|
||||||
|
|
||||||
|
// Terminate ourselves with the same signal so that our parent knows that we
|
||||||
|
// crashed. The default action for all the signals which we catch is Core, so
|
||||||
|
// this is the end of us.
|
||||||
|
signal(sig, SIG_DFL);
|
||||||
|
tgkill(getpid(), sys_gettid(), sig);
|
||||||
|
|
||||||
|
// not reached.
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ThreadArgument {
|
||||||
|
pid_t pid; // the crashing process
|
||||||
|
ExceptionHandler* handler;
|
||||||
|
const void* context; // a CrashContext structure
|
||||||
|
size_t context_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is the entry function for the cloned process. We are in a compromised
|
||||||
|
// context here: see the top of the file.
|
||||||
|
// static
|
||||||
|
int ExceptionHandler::ThreadEntry(void *arg) {
|
||||||
|
const ThreadArgument *thread_arg = reinterpret_cast<ThreadArgument*>(arg);
|
||||||
|
return thread_arg->handler->DoDump(thread_arg->pid, thread_arg->context,
|
||||||
|
thread_arg->context_size) == false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function runs in a compromised context: see the top of the file.
|
||||||
|
// Runs on the crashing thread.
|
||||||
|
bool ExceptionHandler::HandleSignal(int sig, siginfo_t* info, void* uc) {
|
||||||
|
if (filter_ && !filter_(callback_context_))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Allow ourselves to be dumped.
|
||||||
|
sys_prctl(PR_SET_DUMPABLE, 1);
|
||||||
|
|
||||||
|
CrashContext context;
|
||||||
|
memcpy(&context.siginfo, info, sizeof(siginfo_t));
|
||||||
|
memcpy(&context.context, uc, sizeof(struct ucontext));
|
||||||
|
memcpy(&context.float_state, ((struct ucontext *)uc)->uc_mcontext.fpregs,
|
||||||
|
sizeof(context.float_state));
|
||||||
|
context.tid = sys_gettid();
|
||||||
|
|
||||||
|
if (crash_handler_ && crash_handler_(&context, sizeof(context),
|
||||||
|
callback_context_))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
static const unsigned kChildStackSize = 8000;
|
||||||
|
PageAllocator allocator;
|
||||||
|
uint8_t* stack = (uint8_t*) allocator.Alloc(kChildStackSize);
|
||||||
|
if (!stack)
|
||||||
|
return false;
|
||||||
|
// clone() needs the top-most address. (scrub just to be safe)
|
||||||
|
stack += kChildStackSize;
|
||||||
|
my_memset(stack - 16, 0, 16);
|
||||||
|
|
||||||
|
ThreadArgument thread_arg;
|
||||||
|
thread_arg.handler = this;
|
||||||
|
thread_arg.pid = getpid();
|
||||||
|
thread_arg.context = &context;
|
||||||
|
thread_arg.context_size = sizeof(context);
|
||||||
|
|
||||||
|
const pid_t child = sys_clone(
|
||||||
|
ThreadEntry, stack, CLONE_FILES | CLONE_FS | CLONE_UNTRACED,
|
||||||
|
&thread_arg, NULL, NULL, NULL);
|
||||||
|
int r, status;
|
||||||
|
do {
|
||||||
|
r = sys_waitpid(child, &status, __WALL);
|
||||||
|
} while (r == -1 && errno == EINTR);
|
||||||
|
|
||||||
|
if (r == -1) {
|
||||||
|
static const char msg[] = "ExceptionHandler::HandleSignal: waitpid failed:";
|
||||||
|
sys_write(2, msg, sizeof(msg) - 1);
|
||||||
|
sys_write(2, strerror(errno), strlen(strerror(errno)));
|
||||||
|
sys_write(2, "\n", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool success = r != -1 && WIFEXITED(status) && WEXITSTATUS(status) == 0;
|
||||||
|
|
||||||
|
if (callback_)
|
||||||
|
success = callback_(dump_path_c_, next_minidump_id_c_,
|
||||||
|
callback_context_, success);
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function runs in a compromised context: see the top of the file.
|
||||||
|
// Runs on the cloned process.
|
||||||
|
bool ExceptionHandler::DoDump(pid_t crashing_process, const void* context,
|
||||||
|
size_t context_size) {
|
||||||
|
return google_breakpad::WriteMinidump(
|
||||||
|
next_minidump_path_c_, crashing_process, context, context_size);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace google_breakpad
|
} // namespace google_breakpad
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
// Copyright (c) 2006, Google Inc.
|
// Copyright (c) 2009, Google Inc.
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
//
|
//
|
||||||
// Author: Li Liu
|
|
||||||
//
|
|
||||||
// Redistribution and use in source and binary forms, with or without
|
// Redistribution and use in source and binary forms, with or without
|
||||||
// modification, are permitted provided that the following conditions are
|
// modification, are permitted provided that the following conditions are
|
||||||
// met:
|
// met:
|
||||||
|
@ -29,26 +27,16 @@
|
||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
#ifndef CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H__
|
#ifndef CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_
|
||||||
#define CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H__
|
#define CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_
|
||||||
|
|
||||||
#include <pthread.h>
|
|
||||||
|
|
||||||
#include <map>
|
|
||||||
#include <string>
|
|
||||||
#include <signal.h>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include "client/linux/handler/minidump_generator.h"
|
#include <signal.h>
|
||||||
|
|
||||||
// Context information when exception occured.
|
|
||||||
struct sigcontex;
|
|
||||||
|
|
||||||
namespace google_breakpad {
|
namespace google_breakpad {
|
||||||
|
|
||||||
using std::string;
|
|
||||||
|
|
||||||
//
|
|
||||||
// ExceptionHandler
|
// ExceptionHandler
|
||||||
//
|
//
|
||||||
// ExceptionHandler can write a minidump file when an exception occurs,
|
// ExceptionHandler can write a minidump file when an exception occurs,
|
||||||
|
@ -73,7 +61,6 @@ using std::string;
|
||||||
//
|
//
|
||||||
// Caller should try to make the callbacks as crash-friendly as possible,
|
// Caller should try to make the callbacks as crash-friendly as possible,
|
||||||
// it should avoid use heap memory allocation as much as possible.
|
// it should avoid use heap memory allocation as much as possible.
|
||||||
//
|
|
||||||
class ExceptionHandler {
|
class ExceptionHandler {
|
||||||
public:
|
public:
|
||||||
// A callback function to run before Breakpad performs any substantial
|
// A callback function to run before Breakpad performs any substantial
|
||||||
|
@ -108,6 +95,15 @@ class ExceptionHandler {
|
||||||
void *context,
|
void *context,
|
||||||
bool succeeded);
|
bool succeeded);
|
||||||
|
|
||||||
|
// In certain cases, a user may wish to handle the generation of the minidump
|
||||||
|
// themselves. In this case, they can install a handler callback which is
|
||||||
|
// called when a crash has occured. If this function returns true, no other
|
||||||
|
// processing of occurs and the process will shortly be crashed. If this
|
||||||
|
// returns false, the normal processing continues.
|
||||||
|
typedef bool (*HandlerCallback)(const void* crash_context,
|
||||||
|
size_t crash_context_size,
|
||||||
|
void* context);
|
||||||
|
|
||||||
// Creates a new ExceptionHandler instance to handle writing minidumps.
|
// Creates a new ExceptionHandler instance to handle writing minidumps.
|
||||||
// Before writing a minidump, the optional filter callback will be called.
|
// Before writing a minidump, the optional filter callback will be called.
|
||||||
// Its return value determines whether or not Breakpad should write a
|
// Its return value determines whether or not Breakpad should write a
|
||||||
|
@ -116,111 +112,87 @@ class ExceptionHandler {
|
||||||
// If install_handler is true, then a minidump will be written whenever
|
// If install_handler is true, then a minidump will be written whenever
|
||||||
// an unhandled exception occurs. If it is false, minidumps will only
|
// an unhandled exception occurs. If it is false, minidumps will only
|
||||||
// be written when WriteMinidump is called.
|
// be written when WriteMinidump is called.
|
||||||
ExceptionHandler(const string &dump_path,
|
ExceptionHandler(const std::string &dump_path,
|
||||||
FilterCallback filter, MinidumpCallback callback,
|
FilterCallback filter, MinidumpCallback callback,
|
||||||
void *callback_context,
|
void *callback_context,
|
||||||
bool install_handler);
|
bool install_handler);
|
||||||
~ExceptionHandler();
|
~ExceptionHandler();
|
||||||
|
|
||||||
// Get and set the minidump path.
|
// Get and set the minidump path.
|
||||||
string dump_path() const { return dump_path_; }
|
std::string dump_path() const { return dump_path_; }
|
||||||
void set_dump_path(const string &dump_path) {
|
void set_dump_path(const std::string &dump_path) {
|
||||||
dump_path_ = dump_path;
|
dump_path_ = dump_path;
|
||||||
dump_path_c_ = dump_path_.c_str();
|
dump_path_c_ = dump_path_.c_str();
|
||||||
UpdateNextID();
|
UpdateNextID();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void set_crash_handler(HandlerCallback callback) {
|
||||||
|
crash_handler_ = callback;
|
||||||
|
}
|
||||||
|
|
||||||
// Writes a minidump immediately. This can be used to capture the
|
// Writes a minidump immediately. This can be used to capture the
|
||||||
// execution state independently of a crash. Returns true on success.
|
// execution state independently of a crash. Returns true on success.
|
||||||
bool WriteMinidump();
|
bool WriteMinidump();
|
||||||
|
|
||||||
// Convenience form of WriteMinidump which does not require an
|
// Convenience form of WriteMinidump which does not require an
|
||||||
// ExceptionHandler instance.
|
// ExceptionHandler instance.
|
||||||
static bool WriteMinidump(const string &dump_path,
|
static bool WriteMinidump(const std::string &dump_path,
|
||||||
MinidumpCallback callback,
|
MinidumpCallback callback,
|
||||||
void *callback_context);
|
void *callback_context);
|
||||||
|
|
||||||
|
// This structure is passed to minidump_writer.h:WriteMinidump via an opaque
|
||||||
|
// blob. It shouldn't be needed in any user code.
|
||||||
|
struct CrashContext {
|
||||||
|
siginfo_t siginfo;
|
||||||
|
pid_t tid; // the crashing thread.
|
||||||
|
struct ucontext context;
|
||||||
|
struct _libc_fpstate float_state;
|
||||||
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Setup crash handler.
|
bool InstallHandlers();
|
||||||
void SetupHandler();
|
void UninstallHandlers();
|
||||||
// Setup signal handler for a signal.
|
void PreresolveSymbols();
|
||||||
void SetupHandler(int signo);
|
|
||||||
// Teardown the handler for a signal.
|
|
||||||
void TeardownHandler(int signo);
|
|
||||||
// Teardown the handler for a signal.
|
|
||||||
void TeardownHandler(int signo, struct sigaction *old);
|
|
||||||
// Teardown all handlers.
|
|
||||||
void TeardownAllHandler();
|
|
||||||
|
|
||||||
// Signal handler.
|
|
||||||
static void HandleException(int signo);
|
|
||||||
|
|
||||||
// If called from a signal handler, sighandler_ebp is the ebp of
|
|
||||||
// that signal handler's frame, and sig_ctx is an out parameter
|
|
||||||
// that will be set to point at the sigcontext that was placed
|
|
||||||
// on the stack by the kernel. You can pass zero and NULL
|
|
||||||
// for the second and third parameters if you are not calling
|
|
||||||
// this from a signal handler.
|
|
||||||
bool InternalWriteMinidump(int signo, uintptr_t sighandler_ebp,
|
|
||||||
struct sigcontext **sig_ctx);
|
|
||||||
|
|
||||||
// Generates a new ID and stores it in next_minidump_id, and stores the
|
|
||||||
// path of the next minidump to be written in next_minidump_path_.
|
|
||||||
void UpdateNextID();
|
void UpdateNextID();
|
||||||
|
static void SignalHandler(int sig, siginfo_t* info, void* uc);
|
||||||
|
bool HandleSignal(int sig, siginfo_t* info, void* uc);
|
||||||
|
static int ThreadEntry(void* arg);
|
||||||
|
bool DoDump(pid_t crashing_process, const void* context,
|
||||||
|
size_t context_size);
|
||||||
|
|
||||||
private:
|
const FilterCallback filter_;
|
||||||
FilterCallback filter_;
|
const MinidumpCallback callback_;
|
||||||
MinidumpCallback callback_;
|
void* const callback_context_;
|
||||||
void *callback_context_;
|
|
||||||
|
|
||||||
// The directory in which a minidump will be written, set by the dump_path
|
std::string dump_path_;
|
||||||
// argument to the constructor, or set_dump_path.
|
std::string next_minidump_path_;
|
||||||
string dump_path_;
|
std::string next_minidump_id_;
|
||||||
|
|
||||||
// The basename of the next minidump to be written, without the extension
|
|
||||||
string next_minidump_id_;
|
|
||||||
|
|
||||||
// The full pathname of the next minidump to be written, including the file
|
|
||||||
// extension
|
|
||||||
string next_minidump_path_;
|
|
||||||
|
|
||||||
// Pointers to C-string representations of the above. These are set
|
// Pointers to C-string representations of the above. These are set
|
||||||
// when the above are set so we can avoid calling c_str during
|
// when the above are set so we can avoid calling c_str during
|
||||||
// an exception.
|
// an exception.
|
||||||
const char *dump_path_c_;
|
const char* dump_path_c_;
|
||||||
const char *next_minidump_id_c_;
|
const char* next_minidump_path_c_;
|
||||||
const char *next_minidump_path_c_;
|
const char* next_minidump_id_c_;
|
||||||
|
|
||||||
// True if the ExceptionHandler installed an unhandled exception filter
|
const bool handler_installed_;
|
||||||
// when created (with an install_handler parameter set to true).
|
void* signal_stack; // the handler stack.
|
||||||
bool installed_handler_;
|
HandlerCallback crash_handler_;
|
||||||
|
|
||||||
// The global exception handler stack. This is need becuase there may exist
|
// The global exception handler stack. This is need becuase there may exist
|
||||||
// multiple ExceptionHandler instances in a process. Each will have itself
|
// multiple ExceptionHandler instances in a process. Each will have itself
|
||||||
// registered in this stack.
|
// registered in this stack.
|
||||||
static std::vector<ExceptionHandler *> *handler_stack_;
|
static std::vector<ExceptionHandler*> *handler_stack_;
|
||||||
// The index of the handler that should handle the next exception.
|
// The index of the handler that should handle the next exception.
|
||||||
static int handler_stack_index_;
|
static unsigned handler_stack_index_;
|
||||||
static pthread_mutex_t handler_stack_mutex_;
|
static pthread_mutex_t handler_stack_mutex_;
|
||||||
|
|
||||||
// The minidump generator.
|
// A vector of the old signal handlers. The void* is a pointer to a newly
|
||||||
MinidumpGenerator minidump_generator_;
|
// allocated sigaction structure to avoid pulling in too many includes.
|
||||||
|
std::vector<std::pair<int, void *> > old_handlers_;
|
||||||
// disallow copy ctor and operator=
|
|
||||||
explicit ExceptionHandler(const ExceptionHandler &);
|
|
||||||
void operator=(const ExceptionHandler &);
|
|
||||||
|
|
||||||
// The sigactions structure we use for each signal
|
|
||||||
struct sigaction act_;
|
|
||||||
|
|
||||||
|
|
||||||
// Keep the previous handlers for the signal.
|
|
||||||
// We're wasting a bit of memory here since we only change
|
|
||||||
// the handler for some signals but i want to avoid allocating
|
|
||||||
// memory in the signal handler
|
|
||||||
struct sigaction old_actions_[NSIG];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace google_breakpad
|
} // namespace google_breakpad
|
||||||
|
|
||||||
#endif // CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H__
|
#endif // CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_
|
||||||
|
|
|
@ -1,124 +0,0 @@
|
||||||
// Copyright (c) 2006, Google Inc.
|
|
||||||
// All rights reserved.
|
|
||||||
//
|
|
||||||
// Author: Li Liu
|
|
||||||
//
|
|
||||||
// Redistribution and use in source and binary forms, with or without
|
|
||||||
// modification, are permitted provided that the following conditions are
|
|
||||||
// met:
|
|
||||||
//
|
|
||||||
// * Redistributions of source code must retain the above copyright
|
|
||||||
// notice, this list of conditions and the following disclaimer.
|
|
||||||
// * Redistributions in binary form must reproduce the above
|
|
||||||
// copyright notice, this list of conditions and the following disclaimer
|
|
||||||
// in the documentation and/or other materials provided with the
|
|
||||||
// distribution.
|
|
||||||
// * Neither the name of Google Inc. nor the names of its
|
|
||||||
// contributors may be used to endorse or promote products derived from
|
|
||||||
// this software without specific prior written permission.
|
|
||||||
//
|
|
||||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
#include "client/linux/handler/exception_handler.h"
|
|
||||||
#include "client/linux/handler/linux_thread.h"
|
|
||||||
|
|
||||||
using namespace google_breakpad;
|
|
||||||
|
|
||||||
// Thread use this to see if it should stop working.
|
|
||||||
static bool should_exit = false;
|
|
||||||
|
|
||||||
static int foo2(int arg) {
|
|
||||||
// Stack variable, used for debugging stack dumps.
|
|
||||||
/*DDDebug*/printf("%s:%d\n", __FUNCTION__, __LINE__);
|
|
||||||
int c = 0xcccccccc;
|
|
||||||
fprintf(stderr, "Thread trying to crash: %x\n", getpid());
|
|
||||||
c = *reinterpret_cast<int *>(0x5);
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int foo(int arg) {
|
|
||||||
// Stack variable, used for debugging stack dumps.
|
|
||||||
int b = 0xbbbbbbbb;
|
|
||||||
b = foo2(b);
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void *thread_crash(void *) {
|
|
||||||
// Stack variable, used for debugging stack dumps.
|
|
||||||
int a = 0xaaaaaaaa;
|
|
||||||
sleep(1);
|
|
||||||
a = foo(a);
|
|
||||||
printf("%x\n", a);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void *thread_main(void *) {
|
|
||||||
while (!should_exit)
|
|
||||||
sleep(1);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void CreateCrashThread() {
|
|
||||||
pthread_t h;
|
|
||||||
pthread_create(&h, NULL, thread_crash, NULL);
|
|
||||||
pthread_detach(h);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create working threads.
|
|
||||||
static void CreateThread(int num) {
|
|
||||||
pthread_t h;
|
|
||||||
for (int i = 0; i < num; ++i) {
|
|
||||||
pthread_create(&h, NULL, thread_main, NULL);
|
|
||||||
pthread_detach(h);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Callback when minidump written.
|
|
||||||
static bool MinidumpCallback(const char *dump_path,
|
|
||||||
const char *minidump_id,
|
|
||||||
void *context,
|
|
||||||
bool succeeded) {
|
|
||||||
int index = reinterpret_cast<int>(context);
|
|
||||||
printf("%d %s: %s is dumped\n", index, __FUNCTION__, minidump_id);
|
|
||||||
if (index == 0) {
|
|
||||||
should_exit = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Don't process it.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
|
||||||
int handler_index = 0;
|
|
||||||
ExceptionHandler handler_ignore(".", NULL, MinidumpCallback,
|
|
||||||
(void*)handler_index, true);
|
|
||||||
++handler_index;
|
|
||||||
ExceptionHandler handler_process(".", NULL, MinidumpCallback,
|
|
||||||
(void*)handler_index, true);
|
|
||||||
CreateCrashThread();
|
|
||||||
CreateThread(10);
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
sleep(1);
|
|
||||||
should_exit = true;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
|
@ -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 = (intptr_t) 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 = (intptr_t) 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
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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_
|
|
@ -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);
|
||||||
|
}
|
|
@ -0,0 +1,421 @@
|
||||||
|
// 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)
|
||||||
|
if (sys_ptrace(PTRACE_GETFPXREGS, tid, NULL, &info->fpxregs) == -1)
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__i386) || defined(__x86_64)
|
||||||
|
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
|
|
@ -0,0 +1,146 @@
|
||||||
|
// 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 <linux/limits.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/user.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)
|
||||||
|
user_fpxregs_struct fpxregs;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__i386) || defined(__x86_64)
|
||||||
|
|
||||||
|
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_
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.ftw;
|
||||||
|
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.mxcr_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->uc_mcontext.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->mxcr_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, MD_MODULE_SIZE))
|
||||||
|
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, MD_MODULE_SIZE);
|
||||||
|
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, MD_MODULE_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 },
|
||||||
|
{ "cpu family", 0, false },
|
||||||
|
};
|
||||||
|
|
||||||
|
// processor_architecture should always be set, do this first
|
||||||
|
sys_info->processor_architecture =
|
||||||
|
#if defined(__i386)
|
||||||
|
MD_CPU_ARCHITECTURE_X86;
|
||||||
|
#elif defined(__x86_64)
|
||||||
|
MD_CPU_ARCHITECTURE_AMD64;
|
||||||
|
#else
|
||||||
|
#error "Unknown CPU arch"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const int fd = sys_open("/proc/cpuinfo", O_RDONLY, 0);
|
||||||
|
if (fd < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
{
|
||||||
|
PageAllocator allocator;
|
||||||
|
LineReader* const line_reader = new(allocator) LineReader(fd);
|
||||||
|
const char* line;
|
||||||
|
unsigned line_len;
|
||||||
|
while (line_reader->GetNextLine(&line, &line_len)) {
|
||||||
|
for (size_t i = 0;
|
||||||
|
i < sizeof(cpu_info_table) / sizeof(cpu_info_table[0]);
|
||||||
|
i++) {
|
||||||
|
CpuInfoEntry* entry = &cpu_info_table[i];
|
||||||
|
if (entry->found)
|
||||||
|
continue;
|
||||||
|
if (!strncmp(line, entry->info_name, strlen(entry->info_name))) {
|
||||||
|
const char* value = strchr(line, ':');
|
||||||
|
if (!value)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// the above strncmp only matches the prefix, it might be the wrong
|
||||||
|
// line. i.e. we matched "model name" instead of "model".
|
||||||
|
// check and make sure there is only spaces between the prefix and
|
||||||
|
// the colon.
|
||||||
|
const char* space_ptr = line + strlen(entry->info_name);
|
||||||
|
for (; space_ptr < value; space_ptr++) {
|
||||||
|
if (!isspace(*space_ptr)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (space_ptr != value)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
sscanf(++value, " %d", &(entry->value));
|
||||||
|
entry->found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// special case for vendor_id
|
||||||
|
if (!strncmp(line, vendor_id_name, vendor_id_name_length)) {
|
||||||
|
const char* value = strchr(line, ':');
|
||||||
|
if (!value)
|
||||||
|
goto popline;
|
||||||
|
|
||||||
|
// skip ':" and all the spaces that follows
|
||||||
|
do {
|
||||||
|
value++;
|
||||||
|
} while (isspace(*value));
|
||||||
|
|
||||||
|
if (*value) {
|
||||||
|
size_t length = strlen(value);
|
||||||
|
if (length == 0)
|
||||||
|
goto popline;
|
||||||
|
// we don't want the trailing newline
|
||||||
|
if (value[length - 1] == '\n')
|
||||||
|
length--;
|
||||||
|
// ensure we have space for the value
|
||||||
|
if (length < sizeof(vendor_id))
|
||||||
|
strncpy(vendor_id, value, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
popline:
|
||||||
|
line_reader->PopLine(line_len);
|
||||||
|
}
|
||||||
|
sys_close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure we got everything we wanted
|
||||||
|
for (size_t i = 0;
|
||||||
|
i < sizeof(cpu_info_table) / sizeof(cpu_info_table[0]);
|
||||||
|
i++) {
|
||||||
|
if (!cpu_info_table[i].found) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// /proc/cpuinfo contains cpu id, change it into number by adding one.
|
||||||
|
cpu_info_table[0].value++;
|
||||||
|
|
||||||
|
sys_info->number_of_processors = cpu_info_table[0].value;
|
||||||
|
sys_info->processor_level = cpu_info_table[3].value;
|
||||||
|
sys_info->processor_revision = cpu_info_table[1].value << 8 |
|
||||||
|
cpu_info_table[2].value;
|
||||||
|
|
||||||
|
if (vendor_id[0] != '\0') {
|
||||||
|
memcpy(sys_info->cpu.x86_cpu_info.vendor_id, vendor_id,
|
||||||
|
sizeof(sys_info->cpu.x86_cpu_info.vendor_id));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WriteFile(MDLocationDescriptor* result, const char* filename) {
|
||||||
|
const int fd = sys_open(filename, O_RDONLY, 0);
|
||||||
|
if (fd < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// We can't stat the files because several of the files that we want to
|
||||||
|
// read are kernel seqfiles, which always have a length of zero. So we have
|
||||||
|
// to read as much as we can into a buffer.
|
||||||
|
static const unsigned kMaxFileSize = 1024;
|
||||||
|
uint8_t* data = (uint8_t*) dumper_.allocator()->Alloc(kMaxFileSize);
|
||||||
|
|
||||||
|
size_t done = 0;
|
||||||
|
while (done < kMaxFileSize) {
|
||||||
|
ssize_t r;
|
||||||
|
do {
|
||||||
|
r = sys_read(fd, data + done, kMaxFileSize - done);
|
||||||
|
} while (r == -1 && errno == EINTR);
|
||||||
|
|
||||||
|
if (r < 1)
|
||||||
|
break;
|
||||||
|
done += r;
|
||||||
|
}
|
||||||
|
sys_close(fd);
|
||||||
|
|
||||||
|
if (!done)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
UntypedMDRVA memory(&minidump_writer_);
|
||||||
|
if (!memory.Allocate(done))
|
||||||
|
return false;
|
||||||
|
memory.Copy(data, done);
|
||||||
|
*result = memory.location();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WriteOSInformation(MDRawSystemInfo* sys_info) {
|
||||||
|
sys_info->platform_id = MD_OS_LINUX;
|
||||||
|
|
||||||
|
struct utsname uts;
|
||||||
|
if (uname(&uts))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
static const size_t buf_len = 512;
|
||||||
|
char buf[buf_len] = {0};
|
||||||
|
size_t space_left = buf_len - 1;
|
||||||
|
const char* info_table[] = {
|
||||||
|
uts.sysname,
|
||||||
|
uts.release,
|
||||||
|
uts.version,
|
||||||
|
uts.machine,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
bool first_item = true;
|
||||||
|
for (const char** cur_info = info_table; *cur_info; cur_info++) {
|
||||||
|
static const char* separator = " ";
|
||||||
|
size_t separator_len = strlen(separator);
|
||||||
|
size_t info_len = strlen(*cur_info);
|
||||||
|
if (info_len == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (space_left < info_len + (first_item ? 0 : separator_len))
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (!first_item) {
|
||||||
|
strcat(buf, separator);
|
||||||
|
space_left -= separator_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
first_item = false;
|
||||||
|
strcat(buf, *cur_info);
|
||||||
|
space_left -= info_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
MDLocationDescriptor location;
|
||||||
|
if (!minidump_writer_.WriteString(buf, 0, &location))
|
||||||
|
return false;
|
||||||
|
sys_info->csd_version_rva = location.rva;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WriteProcFile(MDLocationDescriptor* result, pid_t pid,
|
||||||
|
const char* filename) {
|
||||||
|
char buf[80];
|
||||||
|
memcpy(buf, "/proc/", 6);
|
||||||
|
const unsigned pid_len = my_int_len(pid);
|
||||||
|
my_itos(buf + 6, pid, pid_len);
|
||||||
|
buf[6 + pid_len] = '/';
|
||||||
|
memcpy(buf + 6 + pid_len + 1, filename, my_strlen(filename) + 1);
|
||||||
|
return WriteFile(result, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* const filename_; // output filename
|
||||||
|
const siginfo_t* const siginfo_; // from the signal handler (see sigaction)
|
||||||
|
const struct ucontext* const ucontext_; // also from the signal handler
|
||||||
|
const struct _libc_fpstate* const float_state_; // ditto
|
||||||
|
const pid_t crashing_tid_; // the process which actually crashed
|
||||||
|
LinuxDumper dumper_;
|
||||||
|
MinidumpFileWriter minidump_writer_;
|
||||||
|
MDLocationDescriptor crashing_thread_context_;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool WriteMinidump(const char* filename, pid_t crashing_process,
|
||||||
|
const void* blob, size_t blob_size) {
|
||||||
|
if (blob_size != sizeof(ExceptionHandler::CrashContext))
|
||||||
|
return false;
|
||||||
|
const ExceptionHandler::CrashContext* context =
|
||||||
|
reinterpret_cast<const ExceptionHandler::CrashContext*>(blob);
|
||||||
|
MinidumpWriter writer(filename, crashing_process, context);
|
||||||
|
if (!writer.Init())
|
||||||
|
return false;
|
||||||
|
return writer.Dump();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace google_breakpad
|
|
@ -1,8 +1,6 @@
|
||||||
// Copyright (c) 2006, Google Inc.
|
// Copyright (c) 2009, Google Inc.
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
//
|
//
|
||||||
// Author: Li Liu
|
|
||||||
//
|
|
||||||
// Redistribution and use in source and binary forms, with or without
|
// Redistribution and use in source and binary forms, with or without
|
||||||
// modification, are permitted provided that the following conditions are
|
// modification, are permitted provided that the following conditions are
|
||||||
// met:
|
// met:
|
||||||
|
@ -29,45 +27,27 @@
|
||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
#ifndef CLIENT_LINUX_HANDLER_MINIDUMP_GENERATOR_H__
|
#ifndef CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_
|
||||||
#define CLIENT_LINUX_HANDLER_MINIDUMP_GENERATOR_H__
|
#define CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <unistd.h>
|
||||||
#include "google_breakpad/common/breakpad_types.h"
|
|
||||||
#include "processor/scoped_ptr.h"
|
|
||||||
|
|
||||||
struct sigcontext;
|
|
||||||
|
|
||||||
namespace google_breakpad {
|
namespace google_breakpad {
|
||||||
|
|
||||||
|
// Write a minidump to the filesystem. This function does not malloc nor use
|
||||||
|
// libc functions which may. Thus, it can be used in contexts where the state
|
||||||
|
// of the heap may be corrupt.
|
||||||
|
// filename: the filename to write to. This is opened O_EXCL and fails if
|
||||||
|
// open fails.
|
||||||
|
// crashing_process: the pid of the crashing process. This must be trusted.
|
||||||
|
// blob: a blob of data from the crashing process. See exception_handler.h
|
||||||
|
// blob_size: the length of |blob|, in bytes
|
||||||
//
|
//
|
||||||
// MinidumpGenerator
|
// Returns true iff successful.
|
||||||
//
|
bool WriteMinidump(const char* filename, pid_t crashing_process,
|
||||||
// Write a minidump to file based on the signo and sig_ctx.
|
const void* blob, size_t blob_size);
|
||||||
// A minidump generator should be created before any exception happen.
|
|
||||||
//
|
|
||||||
class MinidumpGenerator {
|
|
||||||
public:
|
|
||||||
MinidumpGenerator();
|
|
||||||
|
|
||||||
~MinidumpGenerator();
|
|
||||||
|
|
||||||
// Write minidump.
|
|
||||||
bool WriteMinidumpToFile(const char *file_pathname,
|
|
||||||
int signo,
|
|
||||||
uintptr_t sighandler_ebp,
|
|
||||||
struct sigcontext **sig_ctx) const;
|
|
||||||
private:
|
|
||||||
// Allocate memory for stack.
|
|
||||||
void AllocateStack();
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Stack size of the writer thread.
|
|
||||||
static const int kStackSize = 1024 * 1024;
|
|
||||||
scoped_array<char> stack_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace google_breakpad
|
} // namespace google_breakpad
|
||||||
|
|
||||||
#endif // CLIENT_LINUX_HANDLER_MINIDUMP_GENERATOR_H__
|
#endif // CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_
|
|
@ -1,8 +1,6 @@
|
||||||
// Copyright (c) 2006, Google Inc.
|
// Copyright (c) 2009, Google Inc.
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
//
|
//
|
||||||
// Author: Li Liu
|
|
||||||
//
|
|
||||||
// Redistribution and use in source and binary forms, with or without
|
// Redistribution and use in source and binary forms, with or without
|
||||||
// modification, are permitted provided that the following conditions are
|
// modification, are permitted provided that the following conditions are
|
||||||
// met:
|
// met:
|
||||||
|
@ -29,58 +27,53 @@
|
||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <sys/syscall.h>
|
||||||
|
|
||||||
#include <cassert>
|
#include "client/linux/handler/exception_handler.h"
|
||||||
#include <cstdio>
|
#include "client/linux/minidump_writer/minidump_writer.h"
|
||||||
#include <cstdlib>
|
#include "breakpad_googletest_includes.h"
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
#include "client/linux/handler/minidump_generator.h"
|
|
||||||
|
|
||||||
using namespace google_breakpad;
|
using namespace google_breakpad;
|
||||||
|
|
||||||
// Thread use this to see if it should stop working.
|
// This provides a wrapper around system calls which may be
|
||||||
static bool should_exit = false;
|
// interrupted by a signal and return EINTR. See man 7 signal.
|
||||||
|
#define HANDLE_EINTR(x) ({ \
|
||||||
|
typeof(x) __eintr_result__; \
|
||||||
|
do { \
|
||||||
|
__eintr_result__ = x; \
|
||||||
|
} while (__eintr_result__ == -1 && errno == EINTR); \
|
||||||
|
__eintr_result__;\
|
||||||
|
})
|
||||||
|
|
||||||
static void foo2(int arg) {
|
namespace {
|
||||||
// Stack variable, used for debugging stack dumps.
|
typedef testing::Test MinidumpWriterTest;
|
||||||
int c = arg;
|
|
||||||
c = 0xcccccccc;
|
|
||||||
while (!should_exit)
|
|
||||||
sleep(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void foo(int arg) {
|
TEST(MinidumpWriterTest, Setup) {
|
||||||
// Stack variable, used for debugging stack dumps.
|
int fds[2];
|
||||||
int b = arg;
|
ASSERT_NE(-1, pipe(fds));
|
||||||
b = 0xbbbbbbbb;
|
|
||||||
foo2(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void *thread_main(void *) {
|
const pid_t child = fork();
|
||||||
// Stack variable, used for debugging stack dumps.
|
if (child == 0) {
|
||||||
int a = 0xaaaaaaaa;
|
close(fds[1]);
|
||||||
foo(a);
|
char b;
|
||||||
return NULL;
|
HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
|
||||||
}
|
close(fds[0]);
|
||||||
|
syscall(__NR_exit);
|
||||||
static void CreateThread(int num) {
|
|
||||||
pthread_t h;
|
|
||||||
for (int i = 0; i < num; ++i) {
|
|
||||||
pthread_create(&h, NULL, thread_main, NULL);
|
|
||||||
pthread_detach(h);
|
|
||||||
}
|
}
|
||||||
}
|
close(fds[0]);
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
ExceptionHandler::CrashContext context;
|
||||||
CreateThread(10);
|
memset(&context, 0, sizeof(context));
|
||||||
google_breakpad::MinidumpGenerator mg;
|
|
||||||
if (mg.WriteMinidumpToFile("minidump_test.out", -1, 0, NULL))
|
char templ[] = "/tmp/minidump-writer-unittest-XXXXXX";
|
||||||
printf("Succeeded written minidump\n");
|
mktemp(templ);
|
||||||
else
|
ASSERT_TRUE(WriteMinidump(templ, child, &context, sizeof(context)));
|
||||||
printf("Failed to write minidump\n");
|
struct stat st;
|
||||||
should_exit = true;
|
ASSERT_EQ(stat(templ, &st), 0);
|
||||||
return 0;
|
ASSERT_GT(st.st_size, 0u);
|
||||||
|
unlink(templ);
|
||||||
|
|
||||||
|
close(fds[1]);
|
||||||
}
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
// 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 "common/linux/google_crashdump_uploader.h"
|
||||||
|
#include "third_party/linux/include/glog/logging.h"
|
||||||
|
#include "third_party/linux/include/gflags/gflags.h"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
DEFINE_string(crash_server, "http://clients2.google.com/cr",
|
||||||
|
"The crash server to upload minidumps to.");
|
||||||
|
DEFINE_string(product_name, "",
|
||||||
|
"The product name that the minidump corresponds to.");
|
||||||
|
DEFINE_string(product_version, "",
|
||||||
|
"The version of the product that produced the minidump.");
|
||||||
|
DEFINE_string(client_id, "",
|
||||||
|
"The client GUID");
|
||||||
|
DEFINE_string(minidump_path, "",
|
||||||
|
"The path of the minidump file.");
|
||||||
|
DEFINE_string(ptime, "",
|
||||||
|
"The process uptime in milliseconds.");
|
||||||
|
DEFINE_string(ctime, "",
|
||||||
|
"The cumulative process uptime in milliseconds.");
|
||||||
|
DEFINE_string(email, "",
|
||||||
|
"The user's email address.");
|
||||||
|
DEFINE_string(comments, "",
|
||||||
|
"Extra user comments");
|
||||||
|
DEFINE_string(proxy_host, "",
|
||||||
|
"Proxy host");
|
||||||
|
DEFINE_string(proxy_userpasswd, "",
|
||||||
|
"Proxy username/password in user:pass format.");
|
||||||
|
|
||||||
|
|
||||||
|
bool CheckForRequiredFlagsOrDie() {
|
||||||
|
std::string error_text = "";
|
||||||
|
if (FLAGS_product_name.empty()) {
|
||||||
|
error_text.append("\nProduct name must be specified.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FLAGS_product_version.empty()) {
|
||||||
|
error_text.append("\nProduct version must be specified.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FLAGS_client_id.empty()) {
|
||||||
|
error_text.append("\nClient ID must be specified.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FLAGS_minidump_path.empty()) {
|
||||||
|
error_text.append("\nMinidump pathname must be specified.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!error_text.empty()) {
|
||||||
|
LOG(ERROR) << error_text;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
google::InitGoogleLogging(argv[0]);
|
||||||
|
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||||
|
if (!CheckForRequiredFlagsOrDie()) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
google_breakpad::GoogleCrashdumpUploader g(FLAGS_product_name,
|
||||||
|
FLAGS_product_version,
|
||||||
|
FLAGS_client_id,
|
||||||
|
FLAGS_ptime,
|
||||||
|
FLAGS_ctime,
|
||||||
|
FLAGS_email,
|
||||||
|
FLAGS_comments,
|
||||||
|
FLAGS_minidump_path,
|
||||||
|
FLAGS_crash_server,
|
||||||
|
FLAGS_proxy_host,
|
||||||
|
FLAGS_proxy_userpasswd);
|
||||||
|
g.Upload();
|
||||||
|
}
|
|
@ -19,6 +19,9 @@
|
||||||
F945858E0F782333009A47BF /* PBXTargetDependency */,
|
F945858E0F782333009A47BF /* PBXTargetDependency */,
|
||||||
F94585900F782336009A47BF /* PBXTargetDependency */,
|
F94585900F782336009A47BF /* PBXTargetDependency */,
|
||||||
F93DE3A70F830D1D00608B94 /* PBXTargetDependency */,
|
F93DE3A70F830D1D00608B94 /* PBXTargetDependency */,
|
||||||
|
F95BB8B3101F94D300AA053B /* PBXTargetDependency */,
|
||||||
|
F95BB8B5101F94D300AA053B /* PBXTargetDependency */,
|
||||||
|
F95BB8B7101F94D300AA053B /* PBXTargetDependency */,
|
||||||
);
|
);
|
||||||
name = All;
|
name = All;
|
||||||
productName = All;
|
productName = All;
|
||||||
|
@ -88,6 +91,7 @@
|
||||||
F93DE33F0F82C66B00608B94 /* string_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53820ECCE635009BE4BA /* string_utilities.cc */; };
|
F93DE33F0F82C66B00608B94 /* string_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53820ECCE635009BE4BA /* string_utilities.cc */; };
|
||||||
F93DE3410F82C68300608B94 /* exception_handler_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F93DE3400F82C68300608B94 /* exception_handler_test.cc */; };
|
F93DE3410F82C68300608B94 /* exception_handler_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F93DE3400F82C68300608B94 /* exception_handler_test.cc */; };
|
||||||
F945849E0F280E3C009A47BF /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = F945849C0F280E3C009A47BF /* Localizable.strings */; };
|
F945849E0F280E3C009A47BF /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = F945849C0F280E3C009A47BF /* Localizable.strings */; };
|
||||||
|
F9B630A0100FF96B00D0F4AC /* goArrow.png in Resources */ = {isa = PBXBuildFile; fileRef = F9B6309F100FF96B00D0F4AC /* goArrow.png */; };
|
||||||
F9C44DB20EF07288003AEBAA /* Controller.m in Sources */ = {isa = PBXBuildFile; fileRef = F9C44DAC0EF07288003AEBAA /* Controller.m */; };
|
F9C44DB20EF07288003AEBAA /* Controller.m in Sources */ = {isa = PBXBuildFile; fileRef = F9C44DAC0EF07288003AEBAA /* Controller.m */; };
|
||||||
F9C44DB30EF07288003AEBAA /* crashduringload in Resources */ = {isa = PBXBuildFile; fileRef = F9C44DAD0EF07288003AEBAA /* crashduringload */; };
|
F9C44DB30EF07288003AEBAA /* crashduringload in Resources */ = {isa = PBXBuildFile; fileRef = F9C44DAD0EF07288003AEBAA /* crashduringload */; };
|
||||||
F9C44DB40EF07288003AEBAA /* crashInMain in Resources */ = {isa = PBXBuildFile; fileRef = F9C44DAE0EF07288003AEBAA /* crashInMain */; };
|
F9C44DB40EF07288003AEBAA /* crashInMain in Resources */ = {isa = PBXBuildFile; fileRef = F9C44DAE0EF07288003AEBAA /* crashInMain */; };
|
||||||
|
@ -191,6 +195,55 @@
|
||||||
remoteGlobalIDString = F9C44DA40EF060A8003AEBAA;
|
remoteGlobalIDString = F9C44DA40EF060A8003AEBAA;
|
||||||
remoteInfo = BreakpadTest;
|
remoteInfo = BreakpadTest;
|
||||||
};
|
};
|
||||||
|
F95BB884101F949F00AA053B /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = F95BB87C101F949F00AA053B /* crash_report.xcodeproj */;
|
||||||
|
proxyType = 2;
|
||||||
|
remoteGlobalIDString = 8DD76FA10486AA7600D96B5E /* crash_report */;
|
||||||
|
remoteInfo = crash_report;
|
||||||
|
};
|
||||||
|
F95BB891101F94AC00AA053B /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = F95BB889101F94AC00AA053B /* dump_syms.xcodeproj */;
|
||||||
|
proxyType = 2;
|
||||||
|
remoteGlobalIDString = 8DD76FA10486AA7600D96B5E /* dump_syms */;
|
||||||
|
remoteInfo = dump_syms;
|
||||||
|
};
|
||||||
|
F95BB89E101F94C000AA053B /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = F95BB894101F94C000AA053B /* symupload.xcodeproj */;
|
||||||
|
proxyType = 2;
|
||||||
|
remoteGlobalIDString = 8DD76FA10486AA7600D96B5E /* symupload */;
|
||||||
|
remoteInfo = symupload;
|
||||||
|
};
|
||||||
|
F95BB8A0101F94C000AA053B /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = F95BB894101F94C000AA053B /* symupload.xcodeproj */;
|
||||||
|
proxyType = 2;
|
||||||
|
remoteGlobalIDString = 9BD835FB0B0544950055103E /* minidump_upload */;
|
||||||
|
remoteInfo = minidump_upload;
|
||||||
|
};
|
||||||
|
F95BB8B2101F94D300AA053B /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = F95BB889101F94AC00AA053B /* dump_syms.xcodeproj */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 8DD76F960486AA7600D96B5E /* dump_syms */;
|
||||||
|
remoteInfo = dump_syms;
|
||||||
|
};
|
||||||
|
F95BB8B4101F94D300AA053B /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = F95BB894101F94C000AA053B /* symupload.xcodeproj */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 8DD76F960486AA7600D96B5E /* symupload */;
|
||||||
|
remoteInfo = symupload;
|
||||||
|
};
|
||||||
|
F95BB8B6101F94D300AA053B /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = F95BB87C101F949F00AA053B /* crash_report.xcodeproj */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 8DD76F960486AA7600D96B5E /* crash_report */;
|
||||||
|
remoteInfo = crash_report;
|
||||||
|
};
|
||||||
F9C44E190EF0790F003AEBAA /* PBXContainerItemProxy */ = {
|
F9C44E190EF0790F003AEBAA /* PBXContainerItemProxy */ = {
|
||||||
isa = PBXContainerItemProxy;
|
isa = PBXContainerItemProxy;
|
||||||
containerPortal = 0867D690FE84028FC02AAC07 /* Project object */;
|
containerPortal = 0867D690FE84028FC02AAC07 /* Project object */;
|
||||||
|
@ -288,6 +341,10 @@
|
||||||
F93DE3400F82C68300608B94 /* exception_handler_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = exception_handler_test.cc; path = handler/exception_handler_test.cc; sourceTree = "<group>"; };
|
F93DE3400F82C68300608B94 /* exception_handler_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = exception_handler_test.cc; path = handler/exception_handler_test.cc; sourceTree = "<group>"; };
|
||||||
F945849D0F280E3C009A47BF /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = sender/English.lproj/Localizable.strings; sourceTree = "<group>"; };
|
F945849D0F280E3C009A47BF /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = sender/English.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
F945859D0F78241E009A47BF /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Framework/Info.plist; sourceTree = "<group>"; };
|
F945859D0F78241E009A47BF /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Framework/Info.plist; sourceTree = "<group>"; };
|
||||||
|
F95BB87C101F949F00AA053B /* crash_report.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = crash_report.xcodeproj; path = ../../tools/mac/crash_report/crash_report.xcodeproj; sourceTree = SOURCE_ROOT; };
|
||||||
|
F95BB889101F94AC00AA053B /* dump_syms.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = dump_syms.xcodeproj; path = ../../tools/mac/dump_syms/dump_syms.xcodeproj; sourceTree = SOURCE_ROOT; };
|
||||||
|
F95BB894101F94C000AA053B /* symupload.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = symupload.xcodeproj; path = ../../tools/mac/symupload/symupload.xcodeproj; sourceTree = SOURCE_ROOT; };
|
||||||
|
F9B6309F100FF96B00D0F4AC /* goArrow.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = goArrow.png; path = sender/goArrow.png; sourceTree = "<group>"; };
|
||||||
F9C44DA50EF060A8003AEBAA /* BreakpadTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BreakpadTest.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
F9C44DA50EF060A8003AEBAA /* BreakpadTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BreakpadTest.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
F9C44DAC0EF07288003AEBAA /* Controller.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Controller.m; path = testapp/Controller.m; sourceTree = "<group>"; };
|
F9C44DAC0EF07288003AEBAA /* Controller.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Controller.m; path = testapp/Controller.m; sourceTree = "<group>"; };
|
||||||
F9C44DAD0EF07288003AEBAA /* crashduringload */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; name = crashduringload; path = testapp/crashduringload; sourceTree = "<group>"; };
|
F9C44DAD0EF07288003AEBAA /* crashduringload */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; name = crashduringload; path = testapp/crashduringload; sourceTree = "<group>"; };
|
||||||
|
@ -404,6 +461,7 @@
|
||||||
0867D691FE84028FC02AAC07 /* Breakpad */ = {
|
0867D691FE84028FC02AAC07 /* Breakpad */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
F95BB8A3101F94C300AA053B /* Tools */,
|
||||||
32DBCF5E0370ADEE00C91783 /* Breakpad_Prefix.pch */,
|
32DBCF5E0370ADEE00C91783 /* Breakpad_Prefix.pch */,
|
||||||
F92C538D0ECCE6F2009BE4BA /* client */,
|
F92C538D0ECCE6F2009BE4BA /* client */,
|
||||||
F92C53600ECCE3D6009BE4BA /* common */,
|
F92C53600ECCE3D6009BE4BA /* common */,
|
||||||
|
@ -535,6 +593,7 @@
|
||||||
F92C56A60ECE04B6009BE4BA /* sender */ = {
|
F92C56A60ECE04B6009BE4BA /* sender */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
F9B6309F100FF96B00D0F4AC /* goArrow.png */,
|
||||||
F92C56A70ECE04C5009BE4BA /* crash_report_sender.h */,
|
F92C56A70ECE04C5009BE4BA /* crash_report_sender.h */,
|
||||||
F92C56A80ECE04C5009BE4BA /* crash_report_sender.m */,
|
F92C56A80ECE04C5009BE4BA /* crash_report_sender.m */,
|
||||||
F945849C0F280E3C009A47BF /* Localizable.strings */,
|
F945849C0F280E3C009A47BF /* Localizable.strings */,
|
||||||
|
@ -546,6 +605,41 @@
|
||||||
name = sender;
|
name = sender;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
F95BB87D101F949F00AA053B /* Products */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
F95BB885101F949F00AA053B /* crash_report */,
|
||||||
|
);
|
||||||
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
F95BB88A101F94AC00AA053B /* Products */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
F95BB892101F94AC00AA053B /* dump_syms */,
|
||||||
|
);
|
||||||
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
F95BB895101F94C000AA053B /* Products */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
F95BB89F101F94C000AA053B /* symupload */,
|
||||||
|
F95BB8A1101F94C000AA053B /* minidump_upload */,
|
||||||
|
);
|
||||||
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
F95BB8A3101F94C300AA053B /* Tools */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
F95BB894101F94C000AA053B /* symupload.xcodeproj */,
|
||||||
|
F95BB889101F94AC00AA053B /* dump_syms.xcodeproj */,
|
||||||
|
F95BB87C101F949F00AA053B /* crash_report.xcodeproj */,
|
||||||
|
);
|
||||||
|
name = Tools;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
F9C44DAB0EF0726F003AEBAA /* testapp */ = {
|
F9C44DAB0EF0726F003AEBAA /* testapp */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -778,6 +872,20 @@
|
||||||
mainGroup = 0867D691FE84028FC02AAC07 /* Breakpad */;
|
mainGroup = 0867D691FE84028FC02AAC07 /* Breakpad */;
|
||||||
productRefGroup = 034768DFFF38A50411DB9C8B /* Products */;
|
productRefGroup = 034768DFFF38A50411DB9C8B /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
|
projectReferences = (
|
||||||
|
{
|
||||||
|
ProductGroup = F95BB87D101F949F00AA053B /* Products */;
|
||||||
|
ProjectRef = F95BB87C101F949F00AA053B /* crash_report.xcodeproj */;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ProductGroup = F95BB88A101F94AC00AA053B /* Products */;
|
||||||
|
ProjectRef = F95BB889101F94AC00AA053B /* dump_syms.xcodeproj */;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ProductGroup = F95BB895101F94C000AA053B /* Products */;
|
||||||
|
ProjectRef = F95BB894101F94C000AA053B /* symupload.xcodeproj */;
|
||||||
|
},
|
||||||
|
);
|
||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
targets = (
|
targets = (
|
||||||
8DC2EF4F0486A6940098B216 /* Breakpad */,
|
8DC2EF4F0486A6940098B216 /* Breakpad */,
|
||||||
|
@ -794,6 +902,37 @@
|
||||||
};
|
};
|
||||||
/* End PBXProject section */
|
/* End PBXProject section */
|
||||||
|
|
||||||
|
/* Begin PBXReferenceProxy section */
|
||||||
|
F95BB885101F949F00AA053B /* crash_report */ = {
|
||||||
|
isa = PBXReferenceProxy;
|
||||||
|
fileType = "compiled.mach-o.executable";
|
||||||
|
path = crash_report;
|
||||||
|
remoteRef = F95BB884101F949F00AA053B /* PBXContainerItemProxy */;
|
||||||
|
sourceTree = BUILT_PRODUCTS_DIR;
|
||||||
|
};
|
||||||
|
F95BB892101F94AC00AA053B /* dump_syms */ = {
|
||||||
|
isa = PBXReferenceProxy;
|
||||||
|
fileType = "compiled.mach-o.executable";
|
||||||
|
path = dump_syms;
|
||||||
|
remoteRef = F95BB891101F94AC00AA053B /* PBXContainerItemProxy */;
|
||||||
|
sourceTree = BUILT_PRODUCTS_DIR;
|
||||||
|
};
|
||||||
|
F95BB89F101F94C000AA053B /* symupload */ = {
|
||||||
|
isa = PBXReferenceProxy;
|
||||||
|
fileType = "compiled.mach-o.executable";
|
||||||
|
path = symupload;
|
||||||
|
remoteRef = F95BB89E101F94C000AA053B /* PBXContainerItemProxy */;
|
||||||
|
sourceTree = BUILT_PRODUCTS_DIR;
|
||||||
|
};
|
||||||
|
F95BB8A1101F94C000AA053B /* minidump_upload */ = {
|
||||||
|
isa = PBXReferenceProxy;
|
||||||
|
fileType = "compiled.mach-o.executable";
|
||||||
|
path = minidump_upload;
|
||||||
|
remoteRef = F95BB8A0101F94C000AA053B /* PBXContainerItemProxy */;
|
||||||
|
sourceTree = BUILT_PRODUCTS_DIR;
|
||||||
|
};
|
||||||
|
/* End PBXReferenceProxy section */
|
||||||
|
|
||||||
/* Begin PBXResourcesBuildPhase section */
|
/* Begin PBXResourcesBuildPhase section */
|
||||||
8DC2EF520486A6940098B216 /* Resources */ = {
|
8DC2EF520486A6940098B216 /* Resources */ = {
|
||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
|
@ -813,6 +952,7 @@
|
||||||
4084699D0F5D9CF900FDCA37 /* crash_report_sender.icns in Resources */,
|
4084699D0F5D9CF900FDCA37 /* crash_report_sender.icns in Resources */,
|
||||||
33880C800F9E097100817F82 /* InfoPlist.strings in Resources */,
|
33880C800F9E097100817F82 /* InfoPlist.strings in Resources */,
|
||||||
3329D4ED0FA16D820007BBC5 /* Breakpad.nib in Resources */,
|
3329D4ED0FA16D820007BBC5 /* Breakpad.nib in Resources */,
|
||||||
|
F9B630A0100FF96B00D0F4AC /* goArrow.png in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -1063,6 +1203,21 @@
|
||||||
target = F9C44DA40EF060A8003AEBAA /* BreakpadTest */;
|
target = F9C44DA40EF060A8003AEBAA /* BreakpadTest */;
|
||||||
targetProxy = F945858F0F782336009A47BF /* PBXContainerItemProxy */;
|
targetProxy = F945858F0F782336009A47BF /* PBXContainerItemProxy */;
|
||||||
};
|
};
|
||||||
|
F95BB8B3101F94D300AA053B /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
name = dump_syms;
|
||||||
|
targetProxy = F95BB8B2101F94D300AA053B /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
F95BB8B5101F94D300AA053B /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
name = symupload;
|
||||||
|
targetProxy = F95BB8B4101F94D300AA053B /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
F95BB8B7101F94D300AA053B /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
name = crash_report;
|
||||||
|
targetProxy = F95BB8B6101F94D300AA053B /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
F9C44E1A0EF0790F003AEBAA /* PBXTargetDependency */ = {
|
F9C44E1A0EF0790F003AEBAA /* PBXTargetDependency */ = {
|
||||||
isa = PBXTargetDependency;
|
isa = PBXTargetDependency;
|
||||||
target = 8DC2EF4F0486A6940098B216 /* Breakpad */;
|
target = 8DC2EF4F0486A6940098B216 /* Breakpad */;
|
||||||
|
|
|
@ -89,6 +89,7 @@ extern "C" {
|
||||||
#define BREAKPAD_PROCESS_CRASH_TIME "BreakpadProcessCrashTime"
|
#define BREAKPAD_PROCESS_CRASH_TIME "BreakpadProcessCrashTime"
|
||||||
#define BREAKPAD_LOGFILE_KEY_PREFIX "BreakpadAppLogFile"
|
#define BREAKPAD_LOGFILE_KEY_PREFIX "BreakpadAppLogFile"
|
||||||
#define BREAKPAD_SERVER_PARAMETER_PREFIX "BreakpadServerParameterPrefix_"
|
#define BREAKPAD_SERVER_PARAMETER_PREFIX "BreakpadServerParameterPrefix_"
|
||||||
|
#define BREAKPAD_ON_DEMAND "BreakpadOnDemand"
|
||||||
|
|
||||||
// Optional user-defined function to dec to decide if we should handle
|
// Optional user-defined function to dec to decide if we should handle
|
||||||
// this crash or forward it along.
|
// this crash or forward it along.
|
||||||
|
@ -215,7 +216,7 @@ typedef bool (*BreakpadFilterCallback)(int exception_type,
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
// The following are NOT user-supplied but are documented here for
|
// The following are NOT user-supplied but are documented here for
|
||||||
// completeness. They are calculated by Breakpad during initialization &
|
// completeness. They are calculated by Breakpad during initialization &
|
||||||
// crash-dump generation.
|
// crash-dump generation, or entered in by the user.
|
||||||
//
|
//
|
||||||
// BREAKPAD_PROCESS_START_TIME The time the process started.
|
// BREAKPAD_PROCESS_START_TIME The time the process started.
|
||||||
//
|
//
|
||||||
|
@ -242,6 +243,12 @@ typedef bool (*BreakpadFilterCallback)(int exception_type,
|
||||||
// server without leaking Breakpad's
|
// server without leaking Breakpad's
|
||||||
// internal values.
|
// internal values.
|
||||||
//
|
//
|
||||||
|
// BREAKPAD_ON_DEMAND Used internally to indicate to the
|
||||||
|
// Reporter that we're sending on-demand,
|
||||||
|
// not as result of a crash.
|
||||||
|
//
|
||||||
|
// BREAKPAD_COMMENTS The text the user provided as comments.
|
||||||
|
// Only used in crash_report_sender.
|
||||||
|
|
||||||
// Returns a new BreakpadRef object on success, NULL otherwise.
|
// Returns a new BreakpadRef object on success, NULL otherwise.
|
||||||
BreakpadRef BreakpadCreate(NSDictionary *parameters);
|
BreakpadRef BreakpadCreate(NSDictionary *parameters);
|
||||||
|
@ -286,7 +293,7 @@ void BreakpadRemoveKeyValue(BreakpadRef ref, NSString *key);
|
||||||
// necessary. Note that as mentioned above there are limits on both
|
// necessary. Note that as mentioned above there are limits on both
|
||||||
// the number of keys and their length.
|
// the number of keys and their length.
|
||||||
void BreakpadAddUploadParameter(BreakpadRef ref, NSString *key,
|
void BreakpadAddUploadParameter(BreakpadRef ref, NSString *key,
|
||||||
NSString *value);
|
NSString *value);
|
||||||
|
|
||||||
// This method will remove a previously-added parameter from the
|
// This method will remove a previously-added parameter from the
|
||||||
// upload parameter set.
|
// upload parameter set.
|
||||||
|
|
|
@ -409,7 +409,6 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) {
|
||||||
NSString *timeout = [parameters objectForKey:@BREAKPAD_CONFIRM_TIMEOUT];
|
NSString *timeout = [parameters objectForKey:@BREAKPAD_CONFIRM_TIMEOUT];
|
||||||
NSArray *logFilePaths = [parameters objectForKey:@BREAKPAD_LOGFILES];
|
NSArray *logFilePaths = [parameters objectForKey:@BREAKPAD_LOGFILES];
|
||||||
NSString *logFileTailSize = [parameters objectForKey:@BREAKPAD_LOGFILE_UPLOAD_SIZE];
|
NSString *logFileTailSize = [parameters objectForKey:@BREAKPAD_LOGFILE_UPLOAD_SIZE];
|
||||||
NSString *reportEmail = [parameters objectForKey:@BREAKPAD_EMAIL];
|
|
||||||
NSString *requestUserText =
|
NSString *requestUserText =
|
||||||
[parameters objectForKey:@BREAKPAD_REQUEST_COMMENTS];
|
[parameters objectForKey:@BREAKPAD_REQUEST_COMMENTS];
|
||||||
NSString *requestEmail = [parameters objectForKey:@BREAKPAD_REQUEST_EMAIL];
|
NSString *requestEmail = [parameters objectForKey:@BREAKPAD_REQUEST_EMAIL];
|
||||||
|
@ -451,7 +450,7 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) {
|
||||||
vendor = @"Vendor not specified";
|
vendor = @"Vendor not specified";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize the values
|
// Normalize the values.
|
||||||
if (skipConfirm) {
|
if (skipConfirm) {
|
||||||
skipConfirm = [skipConfirm uppercaseString];
|
skipConfirm = [skipConfirm uppercaseString];
|
||||||
|
|
||||||
|
@ -504,7 +503,7 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) {
|
||||||
[resourcePath stringByAppendingPathComponent:@"Inspector"];
|
[resourcePath stringByAppendingPathComponent:@"Inspector"];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that there is an Inspector tool
|
// Verify that there is an Inspector tool.
|
||||||
if (![[NSFileManager defaultManager] fileExistsAtPath:inspectorPathString]) {
|
if (![[NSFileManager defaultManager] fileExistsAtPath:inspectorPathString]) {
|
||||||
DEBUGLOG(stderr, "Cannot find Inspector tool\n");
|
DEBUGLOG(stderr, "Cannot find Inspector tool\n");
|
||||||
return false;
|
return false;
|
||||||
|
@ -517,7 +516,7 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) {
|
||||||
reporterPathString = [[NSBundle bundleWithPath:reporterPathString] executablePath];
|
reporterPathString = [[NSBundle bundleWithPath:reporterPathString] executablePath];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that there is a Reporter application
|
// Verify that there is a Reporter application.
|
||||||
if (![[NSFileManager defaultManager]
|
if (![[NSFileManager defaultManager]
|
||||||
fileExistsAtPath:reporterPathString]) {
|
fileExistsAtPath:reporterPathString]) {
|
||||||
DEBUGLOG(stderr, "Cannot find Reporter tool\n");
|
DEBUGLOG(stderr, "Cannot find Reporter tool\n");
|
||||||
|
@ -588,11 +587,6 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reportEmail) {
|
|
||||||
dictionary.SetKeyValue(BREAKPAD_EMAIL,
|
|
||||||
[reportEmail UTF8String]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (serverParameters) {
|
if (serverParameters) {
|
||||||
// For each key-value pair, call BreakpadAddUploadParameter()
|
// For each key-value pair, call BreakpadAddUploadParameter()
|
||||||
NSEnumerator *keyEnumerator = [serverParameters keyEnumerator];
|
NSEnumerator *keyEnumerator = [serverParameters keyEnumerator];
|
||||||
|
@ -633,7 +627,9 @@ void Breakpad::RemoveKeyValue(NSString *key) {
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
void Breakpad::GenerateAndSendReport() {
|
void Breakpad::GenerateAndSendReport() {
|
||||||
|
config_params_->SetKeyValue(BREAKPAD_ON_DEMAND, "YES");
|
||||||
HandleException(0, 0, 0, mach_thread_self());
|
HandleException(0, 0, 0, mach_thread_self());
|
||||||
|
config_params_->SetKeyValue(BREAKPAD_ON_DEMAND, "NO");
|
||||||
}
|
}
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
|
@ -751,65 +747,59 @@ BreakpadRef BreakpadCreate(NSDictionary *parameters) {
|
||||||
|
|
||||||
// Create a mutex for use in accessing the SimpleStringDictionary
|
// Create a mutex for use in accessing the SimpleStringDictionary
|
||||||
int mutexResult = pthread_mutex_init(&gDictionaryMutex, NULL);
|
int mutexResult = pthread_mutex_init(&gDictionaryMutex, NULL);
|
||||||
if (mutexResult != 0) {
|
if (mutexResult == 0) {
|
||||||
throw mutexResult; // caught down below
|
|
||||||
}
|
|
||||||
|
|
||||||
// With the current compiler, gBreakpadAllocator is allocating 1444 bytes.
|
// With the current compiler, gBreakpadAllocator is allocating 1444 bytes.
|
||||||
// Let's round up to the nearest page size.
|
// Let's round up to the nearest page size.
|
||||||
//
|
//
|
||||||
int breakpad_pool_size = 4096;
|
int breakpad_pool_size = 4096;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
sizeof(Breakpad)
|
sizeof(Breakpad)
|
||||||
+ sizeof(google_breakpad::ExceptionHandler)
|
+ sizeof(google_breakpad::ExceptionHandler)
|
||||||
+ sizeof( STUFF ALLOCATED INSIDE ExceptionHandler )
|
+ sizeof( STUFF ALLOCATED INSIDE ExceptionHandler )
|
||||||
*/
|
*/
|
||||||
|
|
||||||
gBreakpadAllocator =
|
gBreakpadAllocator =
|
||||||
new (gMasterAllocator->Allocate(sizeof(ProtectedMemoryAllocator)))
|
new (gMasterAllocator->Allocate(sizeof(ProtectedMemoryAllocator)))
|
||||||
ProtectedMemoryAllocator(breakpad_pool_size);
|
ProtectedMemoryAllocator(breakpad_pool_size);
|
||||||
|
|
||||||
// Stack-based autorelease pool for Breakpad::Create() obj-c code.
|
// Stack-based autorelease pool for Breakpad::Create() obj-c code.
|
||||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||||
Breakpad *breakpad = Breakpad::Create(parameters);
|
Breakpad *breakpad = Breakpad::Create(parameters);
|
||||||
|
|
||||||
|
if (breakpad) {
|
||||||
|
// Make read-only to protect against memory smashers
|
||||||
|
gMasterAllocator->Protect();
|
||||||
|
gKeyValueAllocator->Protect();
|
||||||
|
gBreakpadAllocator->Protect();
|
||||||
|
// Can uncomment this line to figure out how much space was actually
|
||||||
|
// allocated using this allocator
|
||||||
|
// printf("gBreakpadAllocator allocated size = %d\n",
|
||||||
|
// gBreakpadAllocator->GetAllocatedSize() );
|
||||||
|
[pool release];
|
||||||
|
return (BreakpadRef)breakpad;
|
||||||
|
}
|
||||||
|
|
||||||
if (breakpad) {
|
|
||||||
// Make read-only to protect against memory smashers
|
|
||||||
gMasterAllocator->Protect();
|
|
||||||
gKeyValueAllocator->Protect();
|
|
||||||
gBreakpadAllocator->Protect();
|
|
||||||
} else {
|
|
||||||
[pool release];
|
[pool release];
|
||||||
#ifdef __EXCEPTIONS
|
|
||||||
throw(-1);
|
|
||||||
#else
|
|
||||||
return NULL;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Can uncomment this line to figure out how much space was actually
|
|
||||||
// allocated using this allocator
|
|
||||||
// printf("gBreakpadAllocator allocated size = %d\n",
|
|
||||||
// gBreakpadAllocator->GetAllocatedSize() );
|
|
||||||
|
|
||||||
[pool release];
|
|
||||||
return (BreakpadRef)breakpad;
|
|
||||||
} catch(...) { // don't let exceptions leave this C API
|
} catch(...) { // don't let exceptions leave this C API
|
||||||
if (gKeyValueAllocator) {
|
fprintf(stderr, "BreakpadCreate() : error\n");
|
||||||
gKeyValueAllocator->~ProtectedMemoryAllocator();
|
|
||||||
gKeyValueAllocator = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gBreakpadAllocator) {
|
|
||||||
gBreakpadAllocator->~ProtectedMemoryAllocator();
|
|
||||||
gBreakpadAllocator = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
delete gMasterAllocator;
|
|
||||||
gMasterAllocator = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (gKeyValueAllocator) {
|
||||||
|
gKeyValueAllocator->~ProtectedMemoryAllocator();
|
||||||
|
gKeyValueAllocator = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gBreakpadAllocator) {
|
||||||
|
gBreakpadAllocator->~ProtectedMemoryAllocator();
|
||||||
|
gBreakpadAllocator = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete gMasterAllocator;
|
||||||
|
gMasterAllocator = NULL;
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -319,6 +319,12 @@ kern_return_t Inspector::ReadMessages() {
|
||||||
printf("parameter count = %d\n", info.parameter_count);
|
printf("parameter count = %d\n", info.parameter_count);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// In certain situations where multiple crash requests come
|
||||||
|
// through quickly, we can end up with the mach IPC messages not
|
||||||
|
// coming through correctly. Since we don't know what parameters
|
||||||
|
// we've missed, we can't do much besides abort the crash dump
|
||||||
|
// situation in this case.
|
||||||
|
unsigned int parameters_read = 0;
|
||||||
// The initial message contains the number of key value pairs that
|
// The initial message contains the number of key value pairs that
|
||||||
// we are expected to read.
|
// we are expected to read.
|
||||||
// Read each key/value pair, one mach message per key/value pair.
|
// Read each key/value pair, one mach message per key/value pair.
|
||||||
|
@ -329,6 +335,14 @@ kern_return_t Inspector::ReadMessages() {
|
||||||
if(result == KERN_SUCCESS) {
|
if(result == KERN_SUCCESS) {
|
||||||
KeyValueMessageData &key_value_data =
|
KeyValueMessageData &key_value_data =
|
||||||
(KeyValueMessageData&)*message.GetData();
|
(KeyValueMessageData&)*message.GetData();
|
||||||
|
// If we get a blank key, make sure we don't increment the
|
||||||
|
// parameter count; in some cases (notably on-demand generation
|
||||||
|
// many times in a short period of time) caused the Mach IPC
|
||||||
|
// messages to not come through correctly.
|
||||||
|
if (strlen(key_value_data.key) == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
parameters_read++;
|
||||||
|
|
||||||
config_params_.SetKeyValue(key_value_data.key, key_value_data.value);
|
config_params_.SetKeyValue(key_value_data.key, key_value_data.value);
|
||||||
} else {
|
} else {
|
||||||
|
@ -336,6 +350,11 @@ kern_return_t Inspector::ReadMessages() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (parameters_read != info.parameter_count) {
|
||||||
|
DEBUGLOG(stderr, "Only read %d parameters instead of %d, aborting crash "
|
||||||
|
"dump generation.", parameters_read, info.parameter_count);
|
||||||
|
return KERN_FAILURE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -379,7 +398,7 @@ bool Inspector::InspectTask() {
|
||||||
SetCrashTimeParameters();
|
SetCrashTimeParameters();
|
||||||
// If the client app has not specified a minidump directory,
|
// If the client app has not specified a minidump directory,
|
||||||
// use a default of Library/<kDefaultLibrarySubdirectory>/<Product Name>
|
// use a default of Library/<kDefaultLibrarySubdirectory>/<Product Name>
|
||||||
if (0 == strlen(minidumpDirectory)) {
|
if (!minidumpDirectory || 0 == strlen(minidumpDirectory)) {
|
||||||
NSArray *libraryDirectories =
|
NSArray *libraryDirectories =
|
||||||
NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
|
NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
|
||||||
NSUserDomainMask,
|
NSUserDomainMask,
|
||||||
|
|
|
@ -92,12 +92,15 @@ extern "C"
|
||||||
boolean_t exc_server(mach_msg_header_t *request,
|
boolean_t exc_server(mach_msg_header_t *request,
|
||||||
mach_msg_header_t *reply);
|
mach_msg_header_t *reply);
|
||||||
|
|
||||||
|
// This symbol must be visible to dlsym() - see
|
||||||
|
// http://code.google.com/p/google-breakpad/issues/detail?id=345 for details.
|
||||||
kern_return_t catch_exception_raise(mach_port_t target_port,
|
kern_return_t catch_exception_raise(mach_port_t target_port,
|
||||||
mach_port_t failed_thread,
|
mach_port_t failed_thread,
|
||||||
mach_port_t task,
|
mach_port_t task,
|
||||||
exception_type_t exception,
|
exception_type_t exception,
|
||||||
exception_data_t code,
|
exception_data_t code,
|
||||||
mach_msg_type_number_t code_count);
|
mach_msg_type_number_t code_count)
|
||||||
|
__attribute__((visibility("default")));
|
||||||
|
|
||||||
kern_return_t ForwardException(mach_port_t task,
|
kern_return_t ForwardException(mach_port_t task,
|
||||||
mach_port_t failed_thread,
|
mach_port_t failed_thread,
|
||||||
|
@ -435,6 +438,9 @@ kern_return_t catch_exception_raise(mach_port_t port, mach_port_t failed_thread,
|
||||||
exception_type_t exception,
|
exception_type_t exception,
|
||||||
exception_data_t code,
|
exception_data_t code,
|
||||||
mach_msg_type_number_t code_count) {
|
mach_msg_type_number_t code_count) {
|
||||||
|
if (task != mach_task_self()) {
|
||||||
|
return KERN_FAILURE;
|
||||||
|
}
|
||||||
return ForwardException(task, failed_thread, exception, code, code_count);
|
return ForwardException(task, failed_thread, exception, code, code_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -637,6 +637,7 @@ bool MinidumpGenerator::WriteSystemInfoStream(
|
||||||
// get version and feature info
|
// get version and feature info
|
||||||
cpuid(1, info_ptr->cpu.x86_cpu_info.version_information, unused, unused2,
|
cpuid(1, info_ptr->cpu.x86_cpu_info.version_information, unused, unused2,
|
||||||
info_ptr->cpu.x86_cpu_info.feature_information);
|
info_ptr->cpu.x86_cpu_info.feature_information);
|
||||||
|
|
||||||
// family
|
// family
|
||||||
info_ptr->processor_level =
|
info_ptr->processor_level =
|
||||||
(info_ptr->cpu.x86_cpu_info.version_information & 0xF00) >> 8;
|
(info_ptr->cpu.x86_cpu_info.version_information & 0xF00) >> 8;
|
||||||
|
@ -644,6 +645,20 @@ bool MinidumpGenerator::WriteSystemInfoStream(
|
||||||
info_ptr->processor_revision =
|
info_ptr->processor_revision =
|
||||||
(info_ptr->cpu.x86_cpu_info.version_information & 0xF) |
|
(info_ptr->cpu.x86_cpu_info.version_information & 0xF) |
|
||||||
((info_ptr->cpu.x86_cpu_info.version_information & 0xF0) << 4);
|
((info_ptr->cpu.x86_cpu_info.version_information & 0xF0) << 4);
|
||||||
|
|
||||||
|
// decode extended model info
|
||||||
|
if (info_ptr->processor_level == 0xF ||
|
||||||
|
info_ptr->processor_level == 0x6) {
|
||||||
|
info_ptr->processor_revision |=
|
||||||
|
((info_ptr->cpu.x86_cpu_info.version_information & 0xF0000) >> 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode extended family info
|
||||||
|
if (info_ptr->processor_level == 0xF) {
|
||||||
|
info_ptr->processor_level +=
|
||||||
|
((info_ptr->cpu.x86_cpu_info.version_information & 0xFF00000) >> 20);
|
||||||
|
}
|
||||||
|
|
||||||
#endif // __i386__
|
#endif // __i386__
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -68,7 +68,7 @@ typedef MDRawContextPPC MinidumpContext;
|
||||||
|
|
||||||
// Use the REGISTER_FROM_THREADSTATE to access a register name from the
|
// Use the REGISTER_FROM_THREADSTATE to access a register name from the
|
||||||
// breakpad_thread_state_t structure.
|
// breakpad_thread_state_t structure.
|
||||||
#if __DARWIN_UNIX03 || !TARGET_CPU_X86 || TARGET_CPU_X86_64
|
#if __DARWIN_UNIX03 || TARGET_CPU_X86_64 || TARGET_CPU_PPC64
|
||||||
// In The 10.5 SDK Headers Apple prepended __ to the variable names in the
|
// In The 10.5 SDK Headers Apple prepended __ to the variable names in the
|
||||||
// i386_thread_state_t structure. There's no good way to tell what version of
|
// i386_thread_state_t structure. There's no good way to tell what version of
|
||||||
// the SDK we're compiling against so we just toggle on the same preprocessor
|
// the SDK we're compiling against so we just toggle on the same preprocessor
|
||||||
|
|
|
@ -4,6 +4,14 @@
|
||||||
<dict>
|
<dict>
|
||||||
<key>IBClasses</key>
|
<key>IBClasses</key>
|
||||||
<array>
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CLASS</key>
|
||||||
|
<string>LengthLimitingTextField</string>
|
||||||
|
<key>LANGUAGE</key>
|
||||||
|
<string>ObjC</string>
|
||||||
|
<key>SUPERCLASS</key>
|
||||||
|
<string>NSTextField</string>
|
||||||
|
</dict>
|
||||||
<dict>
|
<dict>
|
||||||
<key>ACTIONS</key>
|
<key>ACTIONS</key>
|
||||||
<dict>
|
<dict>
|
||||||
|
@ -26,10 +34,14 @@
|
||||||
<string>NSButton</string>
|
<string>NSButton</string>
|
||||||
<key>commentMessage_</key>
|
<key>commentMessage_</key>
|
||||||
<string>NSTextField</string>
|
<string>NSTextField</string>
|
||||||
|
<key>commentsEntryField_</key>
|
||||||
|
<string>LengthLimitingTextField</string>
|
||||||
|
<key>countdownLabel_</key>
|
||||||
|
<string>NSTextField</string>
|
||||||
<key>dialogTitle_</key>
|
<key>dialogTitle_</key>
|
||||||
<string>NSTextField</string>
|
<string>NSTextField</string>
|
||||||
<key>emailEntryField_</key>
|
<key>emailEntryField_</key>
|
||||||
<string>NSView</string>
|
<string>LengthLimitingTextField</string>
|
||||||
<key>emailLabel_</key>
|
<key>emailLabel_</key>
|
||||||
<string>NSTextField</string>
|
<string>NSTextField</string>
|
||||||
<key>emailMessage_</key>
|
<key>emailMessage_</key>
|
||||||
|
|
|
@ -3,17 +3,17 @@
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>IBFramework Version</key>
|
<key>IBFramework Version</key>
|
||||||
<string>672</string>
|
<string>676</string>
|
||||||
<key>IBLastKnownRelativeProjectPath</key>
|
<key>IBLastKnownRelativeProjectPath</key>
|
||||||
<string>../Breakpad.xcodeproj</string>
|
<string>../Breakpad.xcodeproj</string>
|
||||||
<key>IBOldestOS</key>
|
<key>IBOldestOS</key>
|
||||||
<integer>5</integer>
|
<integer>5</integer>
|
||||||
<key>IBOpenObjects</key>
|
<key>IBOpenObjects</key>
|
||||||
<array>
|
<array>
|
||||||
<integer>2</integer>
|
<integer>132</integer>
|
||||||
</array>
|
</array>
|
||||||
<key>IBSystem Version</key>
|
<key>IBSystem Version</key>
|
||||||
<string>9G55</string>
|
<string>9J61</string>
|
||||||
<key>targetFramework</key>
|
<key>targetFramework</key>
|
||||||
<string>IBCocoaFramework</string>
|
<string>IBCocoaFramework</string>
|
||||||
</dict>
|
</dict>
|
||||||
|
|
Двоичные данные
toolkit/crashreporter/google-breakpad/src/client/mac/sender/Breakpad.nib/keyedobjects.nib
сгенерированный
Двоичные данные
toolkit/crashreporter/google-breakpad/src/client/mac/sender/Breakpad.nib/keyedobjects.nib
сгенерированный
Двоичный файл не отображается.
Двоичный файл не отображается.
|
@ -41,6 +41,24 @@
|
||||||
extern NSString *const kGoogleServerType;
|
extern NSString *const kGoogleServerType;
|
||||||
extern NSString *const kSocorroServerType;
|
extern NSString *const kSocorroServerType;
|
||||||
extern NSString *const kDefaultServerType;
|
extern NSString *const kDefaultServerType;
|
||||||
|
|
||||||
|
// We're sublcassing NSTextField in order to override a particular
|
||||||
|
// method (see the implementation) that lets us reject changes if they
|
||||||
|
// are longer than a particular length. Bindings would normally solve
|
||||||
|
// this problem, but when we implemented a validation method, and
|
||||||
|
// returned NO for strings that were too long, the UI was not updated
|
||||||
|
// right away, which was a poor user experience. The UI would be
|
||||||
|
// updated as soon as the text field lost first responder status,
|
||||||
|
// which isn't soon enough. It is a known bug that the UI KVO didn't
|
||||||
|
// work in the middle of a validation.
|
||||||
|
@interface LengthLimitingTextField : NSTextField {
|
||||||
|
@private
|
||||||
|
unsigned int maximumLength_;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) setMaximumLength:(unsigned int)maxLength;
|
||||||
|
@end
|
||||||
|
|
||||||
@interface Reporter : NSObject {
|
@interface Reporter : NSObject {
|
||||||
@public
|
@public
|
||||||
IBOutlet NSWindow *alertWindow_; // The alert window
|
IBOutlet NSWindow *alertWindow_; // The alert window
|
||||||
|
@ -50,26 +68,34 @@ extern NSString *const kDefaultServerType;
|
||||||
IBOutlet NSBox *preEmailBox_;
|
IBOutlet NSBox *preEmailBox_;
|
||||||
IBOutlet NSBox *emailSectionBox_;
|
IBOutlet NSBox *emailSectionBox_;
|
||||||
// Localized elements (or things that need to be moved during localization).
|
// Localized elements (or things that need to be moved during localization).
|
||||||
IBOutlet NSTextField *dialogTitle_;
|
IBOutlet NSTextField *dialogTitle_;
|
||||||
IBOutlet NSTextField *commentMessage_;
|
IBOutlet NSTextField *commentMessage_;
|
||||||
IBOutlet NSTextField *emailMessage_;
|
IBOutlet NSTextField *emailMessage_;
|
||||||
IBOutlet NSTextField *emailLabel_;
|
IBOutlet NSTextField *emailLabel_;
|
||||||
IBOutlet NSTextField *privacyLinkLabel_;
|
IBOutlet NSTextField *privacyLinkLabel_;
|
||||||
IBOutlet NSButton *sendButton_;
|
IBOutlet NSButton *sendButton_;
|
||||||
IBOutlet NSButton *cancelButton_;
|
IBOutlet NSButton *cancelButton_;
|
||||||
IBOutlet NSView *emailEntryField_;
|
IBOutlet LengthLimitingTextField *emailEntryField_;
|
||||||
IBOutlet NSView *privacyLinkArrow_;
|
IBOutlet LengthLimitingTextField *commentsEntryField_;
|
||||||
|
IBOutlet NSTextField *countdownLabel_;
|
||||||
|
IBOutlet NSView *privacyLinkArrow_;
|
||||||
|
|
||||||
// Text field bindings, for user input.
|
// Text field bindings, for user input.
|
||||||
NSString *commentsValue_; // Comments from the user
|
NSString *commentsValue_; // Comments from the user
|
||||||
NSString *emailValue_; // Email from the user
|
NSString *emailValue_; // Email from the user
|
||||||
|
NSString *countdownMessage_; // Message indicating time
|
||||||
|
// left for input.
|
||||||
@private
|
@private
|
||||||
int configFile_; // File descriptor for config file
|
int configFile_; // File descriptor for config file
|
||||||
NSMutableDictionary *parameters_; // Key value pairs of data (STRONG)
|
NSMutableDictionary *parameters_; // Key value pairs of data (STRONG)
|
||||||
NSData *minidumpContents_; // The data in the minidump (STRONG)
|
NSData *minidumpContents_; // The data in the minidump (STRONG)
|
||||||
NSData *logFileData_; // An NSdata for the tar,
|
NSData *logFileData_; // An NSdata for the tar,
|
||||||
// bz2'd log file.
|
// bz2'd log file.
|
||||||
|
NSTimeInterval remainingDialogTime_; // Keeps track of how long
|
||||||
|
// we have until we cancel
|
||||||
|
// the dialog
|
||||||
|
NSTimer *messageTimer_; // Timer we use to update
|
||||||
|
// the dialog
|
||||||
NSMutableDictionary *serverDictionary_; // The dictionary mapping a
|
NSMutableDictionary *serverDictionary_; // The dictionary mapping a
|
||||||
// server type name to a
|
// server type name to a
|
||||||
// dictionary of server
|
// dictionary of server
|
||||||
|
@ -107,4 +133,7 @@ extern NSString *const kDefaultServerType;
|
||||||
- (NSString *)emailValue;
|
- (NSString *)emailValue;
|
||||||
- (void)setEmailValue:(NSString *)value;
|
- (void)setEmailValue:(NSString *)value;
|
||||||
|
|
||||||
|
- (NSString *)countdownMessage;
|
||||||
|
- (void)setCountdownMessage:(NSString *)value;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -42,8 +42,11 @@
|
||||||
|
|
||||||
#define kLastSubmission @"LastSubmission"
|
#define kLastSubmission @"LastSubmission"
|
||||||
const int kMinidumpFileLengthLimit = 800000;
|
const int kMinidumpFileLengthLimit = 800000;
|
||||||
|
const int kUserCommentsMaxLength = 1500;
|
||||||
|
const int kEmailMaxLength = 64;
|
||||||
|
|
||||||
#define kApplePrefsSyncExcludeAllKey @"com.apple.PreferenceSync.ExcludeAllSyncKeys"
|
#define kApplePrefsSyncExcludeAllKey \
|
||||||
|
@"com.apple.PreferenceSync.ExcludeAllSyncKeys"
|
||||||
|
|
||||||
NSString *const kGoogleServerType = @"google";
|
NSString *const kGoogleServerType = @"google";
|
||||||
NSString *const kSocorroServerType = @"socorro";
|
NSString *const kSocorroServerType = @"socorro";
|
||||||
|
@ -109,9 +112,12 @@ NSString *const kDefaultServerType = @"google";
|
||||||
@implementation NSTextField (ResizabilityExtentions)
|
@implementation NSTextField (ResizabilityExtentions)
|
||||||
- (float)breakpad_adjustHeightToFit {
|
- (float)breakpad_adjustHeightToFit {
|
||||||
NSRect oldFrame = [self frame];
|
NSRect oldFrame = [self frame];
|
||||||
|
// Starting with the 10.5 SDK, height won't grow, so make it huge to start.
|
||||||
|
NSRect presizeFrame = oldFrame;
|
||||||
|
presizeFrame.size.height = MAXFLOAT;
|
||||||
// sizeToFit will blow out the width rather than making the field taller, so
|
// sizeToFit will blow out the width rather than making the field taller, so
|
||||||
// we do it manually.
|
// we do it manually.
|
||||||
NSSize newSize = [[self cell] cellSizeForBounds:oldFrame];
|
NSSize newSize = [[self cell] cellSizeForBounds:presizeFrame];
|
||||||
NSRect newFrame = NSMakeRect(oldFrame.origin.x, oldFrame.origin.y,
|
NSRect newFrame = NSMakeRect(oldFrame.origin.x, oldFrame.origin.y,
|
||||||
NSWidth(oldFrame), newSize.height);
|
NSWidth(oldFrame), newSize.height);
|
||||||
[self setFrame:newFrame];
|
[self setFrame:newFrame];
|
||||||
|
@ -174,6 +180,9 @@ NSString *const kDefaultServerType = @"google";
|
||||||
// Returns YES if we should send the report without asking the user first.
|
// Returns YES if we should send the report without asking the user first.
|
||||||
- (BOOL)shouldSubmitSilently;
|
- (BOOL)shouldSubmitSilently;
|
||||||
|
|
||||||
|
// Returns YES if the minidump was generated on demand.
|
||||||
|
- (BOOL)isOnDemand;
|
||||||
|
|
||||||
// Returns YES if we should ask the user to provide comments.
|
// Returns YES if we should ask the user to provide comments.
|
||||||
- (BOOL)shouldRequestComments;
|
- (BOOL)shouldRequestComments;
|
||||||
|
|
||||||
|
@ -187,11 +196,11 @@ NSString *const kDefaultServerType = @"google";
|
||||||
|
|
||||||
// Returns the short description of the crash, suitable for use as a dialog
|
// Returns the short description of the crash, suitable for use as a dialog
|
||||||
// title (e.g., "The application Foo has quit unexpectedly").
|
// title (e.g., "The application Foo has quit unexpectedly").
|
||||||
- (NSString*)shortCrashDialogMessage;
|
- (NSString*)shortDialogMessage;
|
||||||
|
|
||||||
// Return explanatory text about the crash and the reporter, suitable for the
|
// Return explanatory text about the crash and the reporter, suitable for the
|
||||||
// body text of a dialog.
|
// body text of a dialog.
|
||||||
- (NSString*)explanatoryCrashDialogText;
|
- (NSString*)explanatoryDialogText;
|
||||||
|
|
||||||
// Returns the amount of time the UI should be shown before timing out.
|
// Returns the amount of time the UI should be shown before timing out.
|
||||||
- (NSTimeInterval)messageTimeout;
|
- (NSTimeInterval)messageTimeout;
|
||||||
|
@ -207,7 +216,7 @@ NSString *const kDefaultServerType = @"google";
|
||||||
- (void)removeEmailPrompt;
|
- (void)removeEmailPrompt;
|
||||||
|
|
||||||
// Run an alert window with the given timeout. Returns
|
// Run an alert window with the given timeout. Returns
|
||||||
// NSAlertButtonDefault if the timeout is exceeded. A timeout of 0
|
// NSRunStoppedResponse if the timeout is exceeded. A timeout of 0
|
||||||
// queues the message immediately in the modal run loop.
|
// queues the message immediately in the modal run loop.
|
||||||
- (int)runModalWindow:(NSWindow*)window withTimeout:(NSTimeInterval)timeout;
|
- (int)runModalWindow:(NSWindow*)window withTimeout:(NSTimeInterval)timeout;
|
||||||
|
|
||||||
|
@ -235,6 +244,16 @@ NSString *const kDefaultServerType = @"google";
|
||||||
// will be uploaded to the crash server.
|
// will be uploaded to the crash server.
|
||||||
- (void)addServerParameter:(id)value forKey:(NSString *)key;
|
- (void)addServerParameter:(id)value forKey:(NSString *)key;
|
||||||
|
|
||||||
|
// This method is used to periodically update the UI with how many
|
||||||
|
// seconds are left in the dialog display.
|
||||||
|
- (void)updateSecondsLeftInDialogDisplay:(NSTimer*)theTimer;
|
||||||
|
|
||||||
|
// When we receive this notification, it means that the user has
|
||||||
|
// begun editing the email address or comments field, and we disable
|
||||||
|
// the timers so that the user has as long as they want to type
|
||||||
|
// in their comments/email.
|
||||||
|
- (void)controlTextDidBeginEditing:(NSNotification *)aNotification;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation Reporter
|
@implementation Reporter
|
||||||
|
@ -261,6 +280,7 @@ NSString *const kDefaultServerType = @"google";
|
||||||
- (id)initWithConfigurationFD:(int)fd {
|
- (id)initWithConfigurationFD:(int)fd {
|
||||||
if ((self = [super init])) {
|
if ((self = [super init])) {
|
||||||
configFile_ = fd;
|
configFile_ = fd;
|
||||||
|
remainingDialogTime_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Because the reporter is embedded in the framework (and many copies
|
// Because the reporter is embedded in the framework (and many copies
|
||||||
|
@ -545,8 +565,8 @@ NSString *const kDefaultServerType = @"google";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Create an alert panel to tell the user something happened
|
// Create an alert panel to tell the user something happened
|
||||||
NSPanel* alert = NSGetAlertPanel([self shortCrashDialogMessage],
|
NSPanel* alert = NSGetAlertPanel([self shortDialogMessage],
|
||||||
[self explanatoryCrashDialogText],
|
[self explanatoryDialogText],
|
||||||
NSLocalizedString(@"sendReportButton", @""),
|
NSLocalizedString(@"sendReportButton", @""),
|
||||||
NSLocalizedString(@"cancelButton", @""),
|
NSLocalizedString(@"cancelButton", @""),
|
||||||
nil);
|
nil);
|
||||||
|
@ -566,11 +586,11 @@ NSString *const kDefaultServerType = @"google";
|
||||||
// "fall" as text areas are shrunk from their overly-large IB sizes.
|
// "fall" as text areas are shrunk from their overly-large IB sizes.
|
||||||
|
|
||||||
// Localize the header. No resizing needed, as it has plenty of room.
|
// Localize the header. No resizing needed, as it has plenty of room.
|
||||||
[dialogTitle_ setStringValue:[self shortCrashDialogMessage]];
|
[dialogTitle_ setStringValue:[self shortDialogMessage]];
|
||||||
|
|
||||||
// Localize the explanatory text field.
|
// Localize the explanatory text field.
|
||||||
[commentMessage_ setStringValue:[NSString stringWithFormat:@"%@\n\n%@",
|
[commentMessage_ setStringValue:[NSString stringWithFormat:@"%@\n\n%@",
|
||||||
[self explanatoryCrashDialogText],
|
[self explanatoryDialogText],
|
||||||
NSLocalizedString(@"commentsMsg", @"")]];
|
NSLocalizedString(@"commentsMsg", @"")]];
|
||||||
float commentHeightDelta = [commentMessage_ breakpad_adjustHeightToFit];
|
float commentHeightDelta = [commentMessage_ breakpad_adjustHeightToFit];
|
||||||
[headerBox_ breakpad_shiftVertically:commentHeightDelta];
|
[headerBox_ breakpad_shiftVertically:commentHeightDelta];
|
||||||
|
@ -615,9 +635,13 @@ NSString *const kDefaultServerType = @"google";
|
||||||
- (int)runModalWindow:(NSWindow*)window withTimeout:(NSTimeInterval)timeout {
|
- (int)runModalWindow:(NSWindow*)window withTimeout:(NSTimeInterval)timeout {
|
||||||
// Queue a |stopModal| message to be performed in |timeout| seconds.
|
// Queue a |stopModal| message to be performed in |timeout| seconds.
|
||||||
if (timeout > 0.001) {
|
if (timeout > 0.001) {
|
||||||
[NSApp performSelector:@selector(stopModal)
|
remainingDialogTime_ = timeout;
|
||||||
withObject:nil
|
SEL updateSelector = @selector(updateSecondsLeftInDialogDisplay:);
|
||||||
afterDelay:timeout];
|
messageTimer_ = [NSTimer scheduledTimerWithTimeInterval:1.0
|
||||||
|
target:self
|
||||||
|
selector:updateSelector
|
||||||
|
userInfo:nil
|
||||||
|
repeats:YES];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the window modally and wait for either a |stopModal| message or a
|
// Run the window modally and wait for either a |stopModal| message or a
|
||||||
|
@ -625,12 +649,6 @@ NSString *const kDefaultServerType = @"google";
|
||||||
[NSApp activateIgnoringOtherApps:YES];
|
[NSApp activateIgnoringOtherApps:YES];
|
||||||
int returnMethod = [NSApp runModalForWindow:window];
|
int returnMethod = [NSApp runModalForWindow:window];
|
||||||
|
|
||||||
// Cancel the pending |stopModal| message.
|
|
||||||
if (returnMethod != NSRunStoppedResponse) {
|
|
||||||
[NSObject cancelPreviousPerformRequestsWithTarget:NSApp
|
|
||||||
selector:@selector(stopModal)
|
|
||||||
object:nil];
|
|
||||||
}
|
|
||||||
return returnMethod;
|
return returnMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -667,8 +685,10 @@ NSString *const kDefaultServerType = @"google";
|
||||||
textView:(NSTextView*)textView
|
textView:(NSTextView*)textView
|
||||||
doCommandBySelector:(SEL)commandSelector {
|
doCommandBySelector:(SEL)commandSelector {
|
||||||
BOOL result = NO;
|
BOOL result = NO;
|
||||||
// If the user has entered text, don't end editing on "return"
|
// If the user has entered text on the comment field, don't end
|
||||||
if (commandSelector == @selector(insertNewline:)
|
// editing on "return".
|
||||||
|
if (control == commentsEntryField_ &&
|
||||||
|
commandSelector == @selector(insertNewline:)
|
||||||
&& [[textView string] length] > 0) {
|
&& [[textView string] length] > 0) {
|
||||||
[textView insertNewlineIgnoringFieldEditor:self];
|
[textView insertNewlineIgnoringFieldEditor:self];
|
||||||
result = YES;
|
result = YES;
|
||||||
|
@ -676,6 +696,50 @@ doCommandBySelector:(SEL)commandSelector {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)controlTextDidBeginEditing:(NSNotification *)aNotification {
|
||||||
|
[messageTimer_ invalidate];
|
||||||
|
[self setCountdownMessage:@""];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)updateSecondsLeftInDialogDisplay:(NSTimer*)theTimer {
|
||||||
|
remainingDialogTime_ -= 1;
|
||||||
|
|
||||||
|
NSString *countdownMessage;
|
||||||
|
NSString *formatString;
|
||||||
|
|
||||||
|
int displayedTimeLeft; // This can be either minutes or seconds.
|
||||||
|
|
||||||
|
if (remainingDialogTime_ > 59) {
|
||||||
|
// calculate minutes remaining for UI purposes
|
||||||
|
displayedTimeLeft = (remainingDialogTime_ / 60);
|
||||||
|
|
||||||
|
if (displayedTimeLeft == 1) {
|
||||||
|
formatString = NSLocalizedString(@"countdownMsgMinuteSingular", @"");
|
||||||
|
} else {
|
||||||
|
formatString = NSLocalizedString(@"countdownMsgMinutesPlural", @"");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
displayedTimeLeft = remainingDialogTime_;
|
||||||
|
if (remainingDialogTime_ == 1) {
|
||||||
|
formatString = NSLocalizedString(@"countdownMsgSecondSingular", @"");
|
||||||
|
} else {
|
||||||
|
formatString = NSLocalizedString(@"countdownMsgSecondsPlural", @"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
countdownMessage = [NSString stringWithFormat:formatString,
|
||||||
|
displayedTimeLeft];
|
||||||
|
if (remainingDialogTime_ <= 30) {
|
||||||
|
[countdownLabel_ setTextColor:[NSColor redColor]];
|
||||||
|
}
|
||||||
|
[self setCountdownMessage:countdownMessage];
|
||||||
|
if (remainingDialogTime_ <= 0) {
|
||||||
|
[messageTimer_ invalidate];
|
||||||
|
[NSApp stopModal];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#pragma mark Accessors
|
#pragma mark Accessors
|
||||||
#pragma mark -
|
#pragma mark -
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
|
@ -702,6 +766,17 @@ doCommandBySelector:(SEL)commandSelector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (NSString *)countdownMessage {
|
||||||
|
return [[countdownMessage_ retain] autorelease];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setCountdownMessage:(NSString *)value {
|
||||||
|
if (countdownMessage_ != value) {
|
||||||
|
[countdownMessage_ release];
|
||||||
|
countdownMessage_ = [value copy];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark -
|
#pragma mark -
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
- (BOOL)reportIntervalElapsed {
|
- (BOOL)reportIntervalElapsed {
|
||||||
|
@ -730,6 +805,11 @@ doCommandBySelector:(SEL)commandSelector {
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (BOOL)isOnDemand {
|
||||||
|
return [[parameters_ objectForKey:@BREAKPAD_ON_DEMAND]
|
||||||
|
isEqualToString:@"YES"];
|
||||||
|
}
|
||||||
|
|
||||||
- (BOOL)shouldSubmitSilently {
|
- (BOOL)shouldSubmitSilently {
|
||||||
return [[parameters_ objectForKey:@BREAKPAD_SKIP_CONFIRM]
|
return [[parameters_ objectForKey:@BREAKPAD_SKIP_CONFIRM]
|
||||||
isEqualToString:@"YES"];
|
isEqualToString:@"YES"];
|
||||||
|
@ -745,21 +825,40 @@ doCommandBySelector:(SEL)commandSelector {
|
||||||
isEqualToString:@"YES"];
|
isEqualToString:@"YES"];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString*)shortCrashDialogMessage {
|
- (NSString*)shortDialogMessage {
|
||||||
NSString *displayName = [parameters_ objectForKey:@BREAKPAD_PRODUCT_DISPLAY];
|
NSString *displayName = [parameters_ objectForKey:@BREAKPAD_PRODUCT_DISPLAY];
|
||||||
if (![displayName length])
|
if (![displayName length])
|
||||||
displayName = [parameters_ objectForKey:@BREAKPAD_PRODUCT];
|
displayName = [parameters_ objectForKey:@BREAKPAD_PRODUCT];
|
||||||
|
|
||||||
return [NSString stringWithFormat:NSLocalizedString(@"headerFmt", @""),
|
if ([self isOnDemand]) {
|
||||||
displayName];
|
return [NSString
|
||||||
|
stringWithFormat:NSLocalizedString(@"noCrashDialogHeader", @""),
|
||||||
|
displayName];
|
||||||
|
} else {
|
||||||
|
return [NSString
|
||||||
|
stringWithFormat:NSLocalizedString(@"crashDialogHeader", @""),
|
||||||
|
displayName];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString*)explanatoryCrashDialogText {
|
- (NSString*)explanatoryDialogText {
|
||||||
|
NSString *displayName = [parameters_ objectForKey:@BREAKPAD_PRODUCT_DISPLAY];
|
||||||
|
if (![displayName length])
|
||||||
|
displayName = [parameters_ objectForKey:@BREAKPAD_PRODUCT];
|
||||||
|
|
||||||
NSString *vendor = [parameters_ objectForKey:@BREAKPAD_VENDOR];
|
NSString *vendor = [parameters_ objectForKey:@BREAKPAD_VENDOR];
|
||||||
if (![vendor length])
|
if (![vendor length])
|
||||||
vendor = @"unknown vendor";
|
vendor = @"unknown vendor";
|
||||||
|
|
||||||
return [NSString stringWithFormat:NSLocalizedString(@"msgFmt", @""), vendor];
|
if ([self isOnDemand]) {
|
||||||
|
return [NSString
|
||||||
|
stringWithFormat:NSLocalizedString(@"noCrashDialogMsg", @""),
|
||||||
|
vendor, displayName];
|
||||||
|
} else {
|
||||||
|
return [NSString
|
||||||
|
stringWithFormat:NSLocalizedString(@"crashDialogMsg", @""),
|
||||||
|
vendor];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSTimeInterval)messageTimeout {
|
- (NSTimeInterval)messageTimeout {
|
||||||
|
@ -940,6 +1039,77 @@ doCommandBySelector:(SEL)commandSelector {
|
||||||
[super dealloc];
|
[super dealloc];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)awakeFromNib {
|
||||||
|
[emailEntryField_ setMaximumLength:kEmailMaxLength];
|
||||||
|
[commentsEntryField_ setMaximumLength:kUserCommentsMaxLength];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
@implementation LengthLimitingTextField
|
||||||
|
|
||||||
|
- (void) setMaximumLength:(unsigned int)maxLength {
|
||||||
|
maximumLength_ = maxLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the method we're overriding in NSTextField, which lets us
|
||||||
|
// limit the user's input if it makes the string too long.
|
||||||
|
- (BOOL) textView:(NSTextView *)textView
|
||||||
|
shouldChangeTextInRange:(NSRange)affectedCharRange
|
||||||
|
replacementString:(NSString *)replacementString {
|
||||||
|
|
||||||
|
// Sometimes the range comes in invalid, so reject if we can't
|
||||||
|
// figure out if the replacement text is too long.
|
||||||
|
if (affectedCharRange.location == NSNotFound) {
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
// Figure out what the new string length would be, taking into
|
||||||
|
// account user selections.
|
||||||
|
int newStringLength =
|
||||||
|
[[textView string] length] - affectedCharRange.length +
|
||||||
|
[replacementString length];
|
||||||
|
if (newStringLength > maximumLength_) {
|
||||||
|
return NO;
|
||||||
|
} else {
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cut, copy, and paste have to be caught specifically since there is no menu.
|
||||||
|
- (BOOL)performKeyEquivalent:(NSEvent*)event {
|
||||||
|
// Only handle the key equivalent if |self| is the text field with focus.
|
||||||
|
NSText* fieldEditor = [self currentEditor];
|
||||||
|
if (fieldEditor != nil) {
|
||||||
|
// Check for a single "Command" modifier
|
||||||
|
unsigned int modifiers = [event modifierFlags];
|
||||||
|
modifiers &= NSDeviceIndependentModifierFlagsMask;
|
||||||
|
if (modifiers == NSCommandKeyMask) {
|
||||||
|
// Now, check for Select All, Cut, Copy, or Paste key equivalents.
|
||||||
|
NSString* characters = [event characters];
|
||||||
|
// Select All is Command-A.
|
||||||
|
if ([characters isEqualToString:@"a"]) {
|
||||||
|
[fieldEditor selectAll:self];
|
||||||
|
return YES;
|
||||||
|
// Cut is Command-X.
|
||||||
|
} else if ([characters isEqualToString:@"x"]) {
|
||||||
|
[fieldEditor cut:self];
|
||||||
|
return YES;
|
||||||
|
// Copy is Command-C.
|
||||||
|
} else if ([characters isEqualToString:@"c"]) {
|
||||||
|
[fieldEditor copy:self];
|
||||||
|
return YES;
|
||||||
|
// Paste is Command-V.
|
||||||
|
} else if ([characters isEqualToString:@"v"]) {
|
||||||
|
[fieldEditor paste:self];
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Let the super class handle the rest (e.g. Command-Period will cancel).
|
||||||
|
return [super performKeyEquivalent:event];
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
|
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 3.5 KiB |
|
@ -209,8 +209,9 @@ const mach_port_t kNoLastExceptionThread = MACH_PORT_NULL;
|
||||||
@"Last exception type is not 0 for on demand");
|
@"Last exception type is not 0 for on demand");
|
||||||
STAssertEquals(last_exception_code_, 0,
|
STAssertEquals(last_exception_code_, 0,
|
||||||
@"Last exception code is not 0 for on demand");
|
@"Last exception code is not 0 for on demand");
|
||||||
STAssertEquals(last_exception_thread_, (mach_port_t)0,
|
STAssertEquals(last_exception_thread_, mach_thread_self(),
|
||||||
@"Last exception thread is not 0 for on demand");
|
@"Last exception thread is not mach_thread_self() "
|
||||||
|
"for on demand");
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -37,6 +37,8 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "common/linux/linux_syscall_support.h"
|
||||||
|
#include "common/linux/linux_libc_support.h"
|
||||||
#include "client/minidump_file_writer-inl.h"
|
#include "client/minidump_file_writer-inl.h"
|
||||||
#include "common/string_conversion.h"
|
#include "common/string_conversion.h"
|
||||||
|
|
||||||
|
@ -53,7 +55,11 @@ MinidumpFileWriter::~MinidumpFileWriter() {
|
||||||
|
|
||||||
bool MinidumpFileWriter::Open(const char *path) {
|
bool MinidumpFileWriter::Open(const char *path) {
|
||||||
assert(file_ == -1);
|
assert(file_ == -1);
|
||||||
|
#if __linux__
|
||||||
|
file_ = sys_open(path, O_WRONLY | O_CREAT | O_EXCL, 0600);
|
||||||
|
#else
|
||||||
file_ = open(path, O_WRONLY | O_CREAT | O_EXCL, 0600);
|
file_ = open(path, O_WRONLY | O_CREAT | O_EXCL, 0600);
|
||||||
|
#endif
|
||||||
|
|
||||||
return file_ != -1;
|
return file_ != -1;
|
||||||
}
|
}
|
||||||
|
@ -63,7 +69,11 @@ bool MinidumpFileWriter::Close() {
|
||||||
|
|
||||||
if (file_ != -1) {
|
if (file_ != -1) {
|
||||||
ftruncate(file_, position_);
|
ftruncate(file_, position_);
|
||||||
|
#if __linux__
|
||||||
|
result = (sys_close(file_) == 0);
|
||||||
|
#else
|
||||||
result = (close(file_) == 0);
|
result = (close(file_) == 0);
|
||||||
|
#endif
|
||||||
file_ = -1;
|
file_ = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,9 +237,16 @@ bool MinidumpFileWriter::Copy(MDRVA position, const void *src, ssize_t size) {
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Seek and write the data
|
// Seek and write the data
|
||||||
if (lseek(file_, position, SEEK_SET) == static_cast<off_t>(position))
|
#if __linux__
|
||||||
if (write(file_, src, size) == size)
|
if (sys_lseek(file_, position, SEEK_SET) == static_cast<off_t>(position)) {
|
||||||
|
if (sys_write(file_, src, size) == size) {
|
||||||
|
#else
|
||||||
|
if (lseek(file_, position, SEEK_SET) == static_cast<off_t>(position)) {
|
||||||
|
if (write(file_, src, size) == size) {
|
||||||
|
#endif
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "crash_report_sender", "send
|
||||||
EndProject
|
EndProject
|
||||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "crash_generation", "crash_generation\crash_generation.vcproj", "{A820AF62-6239-4693-8430-4F516C1838F4}"
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "crash_generation", "crash_generation\crash_generation.vcproj", "{A820AF62-6239-4693-8430-4F516C1838F4}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "exception_handler_test", "handler\exception_handler_test\exception_handler_test.vcproj", "{89094A11-CF25-4037-AF43-EACFA751405E}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Win32 = Debug|Win32
|
Debug|Win32 = Debug|Win32
|
||||||
|
@ -42,6 +44,14 @@ Global
|
||||||
{A820AF62-6239-4693-8430-4F516C1838F4}.Release|Win32.Build.0 = Release|Win32
|
{A820AF62-6239-4693-8430-4F516C1838F4}.Release|Win32.Build.0 = Release|Win32
|
||||||
{A820AF62-6239-4693-8430-4F516C1838F4}.ReleaseStaticCRT|Win32.ActiveCfg = ReleaseStaticCRT|Win32
|
{A820AF62-6239-4693-8430-4F516C1838F4}.ReleaseStaticCRT|Win32.ActiveCfg = ReleaseStaticCRT|Win32
|
||||||
{A820AF62-6239-4693-8430-4F516C1838F4}.ReleaseStaticCRT|Win32.Build.0 = ReleaseStaticCRT|Win32
|
{A820AF62-6239-4693-8430-4F516C1838F4}.ReleaseStaticCRT|Win32.Build.0 = ReleaseStaticCRT|Win32
|
||||||
|
{89094A11-CF25-4037-AF43-EACFA751405E}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||||
|
{89094A11-CF25-4037-AF43-EACFA751405E}.Debug|Win32.Build.0 = Debug|Win32
|
||||||
|
{89094A11-CF25-4037-AF43-EACFA751405E}.DebugStaticCRT|Win32.ActiveCfg = Debug|Win32
|
||||||
|
{89094A11-CF25-4037-AF43-EACFA751405E}.DebugStaticCRT|Win32.Build.0 = Debug|Win32
|
||||||
|
{89094A11-CF25-4037-AF43-EACFA751405E}.Release|Win32.ActiveCfg = Release|Win32
|
||||||
|
{89094A11-CF25-4037-AF43-EACFA751405E}.Release|Win32.Build.0 = Release|Win32
|
||||||
|
{89094A11-CF25-4037-AF43-EACFA751405E}.ReleaseStaticCRT|Win32.ActiveCfg = Release|Win32
|
||||||
|
{89094A11-CF25-4037-AF43-EACFA751405E}.ReleaseStaticCRT|Win32.Build.0 = Release|Win32
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
|
@ -218,12 +218,14 @@ bool CrashGenerationServer::Start() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register a callback with the thread pool for the client connection.
|
// Register a callback with the thread pool for the client connection.
|
||||||
RegisterWaitForSingleObject(&pipe_wait_handle_,
|
if (!RegisterWaitForSingleObject(&pipe_wait_handle_,
|
||||||
overlapped_.hEvent,
|
overlapped_.hEvent,
|
||||||
OnPipeConnected,
|
OnPipeConnected,
|
||||||
this,
|
this,
|
||||||
INFINITE,
|
INFINITE,
|
||||||
kPipeIOThreadFlags);
|
kPipeIOThreadFlags)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
pipe_ = CreateNamedPipe(pipe_name_.c_str(),
|
pipe_ = CreateNamedPipe(pipe_name_.c_str(),
|
||||||
kPipeAttr,
|
kPipeAttr,
|
||||||
|
|
|
@ -249,12 +249,12 @@ ExceptionHandler::~ExceptionHandler() {
|
||||||
// TODO(mmentovai): use advapi32!ReportEvent to log the warning to the
|
// TODO(mmentovai): use advapi32!ReportEvent to log the warning to the
|
||||||
// system's application event log.
|
// system's application event log.
|
||||||
fprintf(stderr, "warning: removing Breakpad handler out of order\n");
|
fprintf(stderr, "warning: removing Breakpad handler out of order\n");
|
||||||
for (vector<ExceptionHandler*>::iterator iterator =
|
vector<ExceptionHandler*>::iterator iterator = handler_stack_->begin();
|
||||||
handler_stack_->begin();
|
while (iterator != handler_stack_->end()) {
|
||||||
iterator != handler_stack_->end();
|
|
||||||
++iterator) {
|
|
||||||
if (*iterator == this) {
|
if (*iterator == this) {
|
||||||
handler_stack_->erase(iterator);
|
iterator = handler_stack_->erase(iterator);
|
||||||
|
} else {
|
||||||
|
++iterator;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,164 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include "breakpad_googletest_includes.h"
|
||||||
|
#include "client/windows/crash_generation/crash_generation_server.h"
|
||||||
|
#include "client/windows/handler/exception_handler.h"
|
||||||
|
#include <windows.h>
|
||||||
|
#include <dbghelp.h>
|
||||||
|
#include <strsafe.h>
|
||||||
|
#include <objbase.h>
|
||||||
|
#include <shellapi.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
const wchar_t kPipeName[] = L"\\\\.\\pipe\\BreakpadCrashTest\\TestCaseServer";
|
||||||
|
const char kSuccessIndicator[] = "success";
|
||||||
|
const char kFailureIndicator[] = "failure";
|
||||||
|
|
||||||
|
// Utility function to test for a path's existence.
|
||||||
|
BOOL DoesPathExist(const TCHAR *path_name);
|
||||||
|
|
||||||
|
class ExceptionHandlerDeathTest : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
// Member variable for each test that they can use
|
||||||
|
// for temporary storage.
|
||||||
|
TCHAR temp_path_[MAX_PATH];
|
||||||
|
// Actually constructs a temp path name.
|
||||||
|
virtual void SetUp();
|
||||||
|
// A helper method that tests can use to crash.
|
||||||
|
void DoCrash();
|
||||||
|
};
|
||||||
|
|
||||||
|
void ExceptionHandlerDeathTest::SetUp() {
|
||||||
|
const ::testing::TestInfo* const test_info =
|
||||||
|
::testing::UnitTest::GetInstance()->current_test_info();
|
||||||
|
TCHAR temp_path[MAX_PATH] = { '\0' };
|
||||||
|
TCHAR test_name_wide[MAX_PATH] = { '\0' };
|
||||||
|
// We want the temporary directory to be what the OS returns
|
||||||
|
// to us, + the test case name.
|
||||||
|
GetTempPath(MAX_PATH, temp_path);
|
||||||
|
// THe test case name is exposed to use as a c-style string,
|
||||||
|
// But we might be working in UNICODE here on Windows.
|
||||||
|
int dwRet = MultiByteToWideChar(CP_ACP, 0, test_info->name(),
|
||||||
|
(int)strlen(test_info->name()), test_name_wide, MAX_PATH);
|
||||||
|
if (!dwRet) {
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
StringCchPrintfW(temp_path_, MAX_PATH, L"%s%s", temp_path, test_name_wide);
|
||||||
|
CreateDirectory(temp_path_, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL DoesPathExist(const TCHAR *path_name) {
|
||||||
|
DWORD flags = GetFileAttributes(path_name);
|
||||||
|
if (flags == INVALID_FILE_ATTRIBUTES) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MinidumpWrittenCallback(const wchar_t* dump_path,
|
||||||
|
const wchar_t* minidump_id,
|
||||||
|
void* context,
|
||||||
|
EXCEPTION_POINTERS* exinfo,
|
||||||
|
MDRawAssertionInfo* assertion,
|
||||||
|
bool succeeded) {
|
||||||
|
if (succeeded && DoesPathExist(dump_path)) {
|
||||||
|
fprintf(stderr, kSuccessIndicator);
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, kFailureIndicator);
|
||||||
|
}
|
||||||
|
// If we don't flush, the output doesn't get sent before
|
||||||
|
// this process dies.
|
||||||
|
fflush(stderr);
|
||||||
|
return succeeded;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ExceptionHandlerDeathTest, InProcTest) {
|
||||||
|
// For the in-proc test, we just need to instantiate an exception
|
||||||
|
// handler in in-proc mode, and crash. Since the entire test is
|
||||||
|
// reexecuted in the child process, we don't have to worry about
|
||||||
|
// the semantics of the exception handler being inherited/not
|
||||||
|
// inherited across CreateProcess().
|
||||||
|
ASSERT_TRUE(DoesPathExist(temp_path_));
|
||||||
|
google_breakpad::ExceptionHandler *exc =
|
||||||
|
new google_breakpad::ExceptionHandler(
|
||||||
|
temp_path_, NULL, &MinidumpWrittenCallback, NULL,
|
||||||
|
google_breakpad::ExceptionHandler::HANDLER_ALL);
|
||||||
|
int *i = NULL;
|
||||||
|
ASSERT_DEATH((*i)++, kSuccessIndicator);
|
||||||
|
delete exc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool gDumpCallbackCalled = false;
|
||||||
|
|
||||||
|
void clientDumpCallback(void *dump_context,
|
||||||
|
const google_breakpad::ClientInfo *client_info,
|
||||||
|
const std::wstring *dump_path){
|
||||||
|
|
||||||
|
gDumpCallbackCalled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExceptionHandlerDeathTest::DoCrash() {
|
||||||
|
google_breakpad::ExceptionHandler *exc =
|
||||||
|
new google_breakpad::ExceptionHandler(
|
||||||
|
temp_path_, NULL, NULL, NULL,
|
||||||
|
google_breakpad::ExceptionHandler::HANDLER_ALL, MiniDumpNormal, kPipeName,
|
||||||
|
NULL);
|
||||||
|
// Although this is executing in the child process of the death test,
|
||||||
|
// if it's not true we'll still get an error rather than the crash
|
||||||
|
// being expected.
|
||||||
|
ASSERT_TRUE(exc->IsOutOfProcess());
|
||||||
|
int *i = NULL;
|
||||||
|
printf("%d\n", (*i)++);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ExceptionHandlerDeathTest, OutOfProcTest) {
|
||||||
|
// We can take advantage of a detail of google test here to save some
|
||||||
|
// complexity in testing: when you do a death test, it actually forks.
|
||||||
|
// So we can make the main test harness the crash generation server,
|
||||||
|
// and call ASSERT_DEATH on a NULL dereference, it to expecting test
|
||||||
|
// the out of process scenario, since it's happening in a different
|
||||||
|
// process! This is different from the above because, above, we pass
|
||||||
|
// a NULL pipe name, and we also don't start a crash generation server.
|
||||||
|
|
||||||
|
ASSERT_TRUE(DoesPathExist(temp_path_));
|
||||||
|
std::wstring dump_path(temp_path_);
|
||||||
|
google_breakpad::CrashGenerationServer server(
|
||||||
|
kPipeName, NULL, NULL, NULL, &clientDumpCallback, NULL, NULL, NULL, true,
|
||||||
|
&dump_path);
|
||||||
|
|
||||||
|
// This HAS to be EXPECT_, because when this test case is executed in the
|
||||||
|
// child process, the server registration will fail due to the named pipe
|
||||||
|
// being the same.
|
||||||
|
EXPECT_TRUE(server.Start());
|
||||||
|
EXPECT_FALSE(gDumpCallbackCalled);
|
||||||
|
ASSERT_DEATH(this->DoCrash(), "");
|
||||||
|
EXPECT_TRUE(gDumpCallbackCalled);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,266 @@
|
||||||
|
<?xml version="1.0" encoding="Windows-1252"?>
|
||||||
|
<VisualStudioProject
|
||||||
|
ProjectType="Visual C++"
|
||||||
|
Version="8.00"
|
||||||
|
Name="exception_handler_test"
|
||||||
|
ProjectGUID="{89094A11-CF25-4037-AF43-EACFA751405E}"
|
||||||
|
RootNamespace="exception_handler_test"
|
||||||
|
Keyword="Win32Proj"
|
||||||
|
>
|
||||||
|
<Platforms>
|
||||||
|
<Platform
|
||||||
|
Name="Win32"
|
||||||
|
/>
|
||||||
|
</Platforms>
|
||||||
|
<ToolFiles>
|
||||||
|
</ToolFiles>
|
||||||
|
<Configurations>
|
||||||
|
<Configuration
|
||||||
|
Name="Debug|Win32"
|
||||||
|
OutputDirectory="$(SolutionDir)$(ConfigurationName)"
|
||||||
|
IntermediateDirectory="$(ConfigurationName)"
|
||||||
|
ConfigurationType="1"
|
||||||
|
CharacterSet="1"
|
||||||
|
>
|
||||||
|
<Tool
|
||||||
|
Name="VCPreBuildEventTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCCustomBuildTool"
|
||||||
|
Description=""
|
||||||
|
CommandLine=""
|
||||||
|
Outputs=""
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCXMLDataGeneratorTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCWebServiceProxyGeneratorTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCMIDLTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCCLCompilerTool"
|
||||||
|
Optimization="0"
|
||||||
|
AdditionalIncludeDirectories="..\..\..\..\testing;..\..\..\..\testing\include;..\..\..\..\testing\gtest;..\..\..\..\testing\gtest\include;..\..\..\..\"
|
||||||
|
PreprocessorDefinitions="WIN32;_DEBUG;_LIB;WIN32_LEAN_AND_MEAN;_WIN32_WINNT=0x0500"
|
||||||
|
MinimalRebuild="true"
|
||||||
|
BasicRuntimeChecks="3"
|
||||||
|
RuntimeLibrary="3"
|
||||||
|
UsePrecompiledHeader="0"
|
||||||
|
WarningLevel="3"
|
||||||
|
Detect64BitPortabilityProblems="true"
|
||||||
|
DebugInformationFormat="4"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCManagedResourceCompilerTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCResourceCompilerTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCPreLinkEventTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCLinkerTool"
|
||||||
|
LinkIncremental="2"
|
||||||
|
GenerateDebugInformation="true"
|
||||||
|
SubSystem="1"
|
||||||
|
TargetMachine="1"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCALinkTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCManifestTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCXDCMakeTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCBscMakeTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCFxCopTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCAppVerifierTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCWebDeploymentTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCPostBuildEventTool"
|
||||||
|
Description="Running tests"
|
||||||
|
CommandLine="$(OutDir)\$(ProjectName).exe"
|
||||||
|
/>
|
||||||
|
</Configuration>
|
||||||
|
<Configuration
|
||||||
|
Name="Release|Win32"
|
||||||
|
OutputDirectory="$(SolutionDir)$(ConfigurationName)"
|
||||||
|
IntermediateDirectory="$(ConfigurationName)"
|
||||||
|
ConfigurationType="1"
|
||||||
|
CharacterSet="1"
|
||||||
|
WholeProgramOptimization="1"
|
||||||
|
>
|
||||||
|
<Tool
|
||||||
|
Name="VCPreBuildEventTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCCustomBuildTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCXMLDataGeneratorTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCWebServiceProxyGeneratorTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCMIDLTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCCLCompilerTool"
|
||||||
|
AdditionalIncludeDirectories="..\..\..\..\testing;..\..\..\..\testing\include;..\..\..\..\testing\gtest;..\..\..\..\testing\gtest\include;..\..\..\..\"
|
||||||
|
PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;WIN32_LEAN_AND_MEAN;_WIN32_WINNT=0x0500"
|
||||||
|
RuntimeLibrary="2"
|
||||||
|
UsePrecompiledHeader="0"
|
||||||
|
WarningLevel="3"
|
||||||
|
Detect64BitPortabilityProblems="true"
|
||||||
|
DebugInformationFormat="3"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCManagedResourceCompilerTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCResourceCompilerTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCPreLinkEventTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCLinkerTool"
|
||||||
|
LinkIncremental="1"
|
||||||
|
GenerateDebugInformation="true"
|
||||||
|
SubSystem="1"
|
||||||
|
OptimizeReferences="2"
|
||||||
|
EnableCOMDATFolding="2"
|
||||||
|
TargetMachine="1"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCALinkTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCManifestTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCXDCMakeTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCBscMakeTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCFxCopTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCAppVerifierTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCWebDeploymentTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCPostBuildEventTool"
|
||||||
|
Description="Running tests"
|
||||||
|
CommandLine="$(OutDir)\$(ProjectName).exe"
|
||||||
|
/>
|
||||||
|
</Configuration>
|
||||||
|
</Configurations>
|
||||||
|
<References>
|
||||||
|
</References>
|
||||||
|
<Files>
|
||||||
|
<Filter
|
||||||
|
Name="Source Files"
|
||||||
|
Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
|
||||||
|
UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
|
||||||
|
>
|
||||||
|
<File
|
||||||
|
RelativePath="..\..\crash_generation\client_info.cc"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath="..\..\crash_generation\crash_generation_client.cc"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath="..\..\crash_generation\crash_generation_server.cc"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath="..\exception_handler.cc"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath=".\exception_handler_test.cc"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath="..\..\..\..\testing\gtest\src\gtest-all.cc"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath="..\..\..\..\testing\gtest\src\gtest_main.cc"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath="..\..\..\..\common\windows\guid_string.cc"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath="..\..\crash_generation\minidump_generator.cc"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
|
</Filter>
|
||||||
|
<Filter
|
||||||
|
Name="Header Files"
|
||||||
|
Filter="h;hpp;hxx;hm;inl;inc;xsd"
|
||||||
|
UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
|
||||||
|
>
|
||||||
|
<File
|
||||||
|
RelativePath="..\..\crash_generation\client_info.h"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath="..\..\crash_generation\crash_generation_client.h"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath="..\..\crash_generation\crash_generation_server.h"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath="..\exception_handler.h"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath="..\..\..\..\common\windows\guid_string.h"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath="..\..\crash_generation\minidump_generator.h"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
|
</Filter>
|
||||||
|
<Filter
|
||||||
|
Name="Resource Files"
|
||||||
|
Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav"
|
||||||
|
UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"
|
||||||
|
>
|
||||||
|
</Filter>
|
||||||
|
<File
|
||||||
|
RelativePath=".\ReadMe.txt"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
|
</Files>
|
||||||
|
<Globals>
|
||||||
|
</Globals>
|
||||||
|
</VisualStudioProject>
|
|
@ -183,7 +183,7 @@ bool ShowDumpResults(const wchar_t* dump_path,
|
||||||
delete [] text;
|
delete [] text;
|
||||||
}
|
}
|
||||||
|
|
||||||
AppendTextWorker(text);
|
QueueUserWorkItem(AppendTextWorker, text, WT_EXECUTEDEFAULT);
|
||||||
return succeeded;
|
return succeeded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -467,6 +467,7 @@ int APIENTRY _tWinMain(HINSTANCE instance,
|
||||||
|
|
||||||
CustomClientInfo custom_info = {kCustomInfoEntries, kCustomInfoCount};
|
CustomClientInfo custom_info = {kCustomInfoEntries, kCustomInfoCount};
|
||||||
|
|
||||||
|
CrashServerStart();
|
||||||
// This is needed for CRT to not show dialog for invalid param
|
// This is needed for CRT to not show dialog for invalid param
|
||||||
// failures and instead let the code handle it.
|
// failures and instead let the code handle it.
|
||||||
_CrtSetReportMode(_CRT_ASSERT, 0);
|
_CrtSetReportMode(_CRT_ASSERT, 0);
|
||||||
|
|
|
@ -27,114 +27,48 @@
|
||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
#include <a.out.h>
|
#include <assert.h>
|
||||||
#include <cstdarg>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <cxxabi.h>
|
#include <cxxabi.h>
|
||||||
#include <elf.h>
|
#include <elf.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <link.h>
|
#include <link.h>
|
||||||
|
#include <string.h>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#include <stab.h>
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdarg>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <list>
|
#include <list>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "common/linux/dump_symbols.h"
|
#include "common/linux/dump_symbols.h"
|
||||||
#include "common/linux/file_id.h"
|
#include "common/linux/file_id.h"
|
||||||
#include "common/linux/guid_creator.h"
|
#include "common/linux/module.h"
|
||||||
#include "processor/scoped_ptr.h"
|
#include "common/linux/stabs_reader.h"
|
||||||
|
|
||||||
// This namespace contains helper functions.
|
// This namespace contains helper functions.
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// Infomation of a line.
|
using google_breakpad::Module;
|
||||||
struct LineInfo {
|
using std::vector;
|
||||||
// The index into string table for the name of the source file which
|
|
||||||
// this line belongs to.
|
|
||||||
// Load from stab symbol.
|
|
||||||
uint32_t source_name_index;
|
|
||||||
// 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;
|
|
||||||
// Id of the source file for this line.
|
|
||||||
int source_id;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef std::list<struct LineInfo> LineInfoList;
|
|
||||||
|
|
||||||
// Information of a function.
|
|
||||||
struct FuncInfo {
|
|
||||||
// Name of the function.
|
|
||||||
const char *name;
|
|
||||||
// Offset from the base of the loading address.
|
|
||||||
ElfW(Off) rva_to_base;
|
|
||||||
// Virtual address of the function.
|
|
||||||
// Load from stab symbol.
|
|
||||||
ElfW(Addr) addr;
|
|
||||||
// Size of the function.
|
|
||||||
// It is the difference of the starting address of the function and starting
|
|
||||||
// address of the next N_FUN or N_SO.
|
|
||||||
uint32_t size;
|
|
||||||
// Total size of stack parameters.
|
|
||||||
uint32_t stack_param_size;
|
|
||||||
// Is there any lines included from other files?
|
|
||||||
bool has_sol;
|
|
||||||
// Line information array.
|
|
||||||
LineInfoList line_info;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef std::list<struct FuncInfo> FuncInfoList;
|
|
||||||
|
|
||||||
// Information of a source file.
|
|
||||||
struct SourceFileInfo {
|
|
||||||
// Name string index into the string table.
|
|
||||||
uint32_t name_index;
|
|
||||||
// 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;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef std::list<struct SourceFileInfo> SourceFileInfoList;
|
|
||||||
|
|
||||||
// Information of a symbol table.
|
|
||||||
// This is the root of all types of symbol.
|
|
||||||
struct SymbolInfo {
|
|
||||||
SourceFileInfoList source_file_info;
|
|
||||||
|
|
||||||
// The next source id for newly found source file.
|
|
||||||
int next_source_id;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Stab section name.
|
// Stab section name.
|
||||||
static const char *kStabName = ".stab";
|
static const char *kStabName = ".stab";
|
||||||
|
|
||||||
// Demangle using abi call.
|
// Demangle using abi call.
|
||||||
// Older GCC may not support it.
|
// Older GCC may not support it.
|
||||||
static std::string Demangle(const char *mangled) {
|
static std::string Demangle(const std::string &mangled) {
|
||||||
int status = 0;
|
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) {
|
if (status == 0 && demangled != NULL) {
|
||||||
std::string str(demangled);
|
std::string str(demangled);
|
||||||
free(demangled);
|
free(demangled);
|
||||||
|
@ -146,7 +80,7 @@ static std::string Demangle(const char *mangled) {
|
||||||
// Fix offset into virtual address by adding the mapped base into offsets.
|
// Fix offset into virtual address by adding the mapped base into offsets.
|
||||||
// Make life easier when want to find something by offset.
|
// Make life easier when want to find something by offset.
|
||||||
static void FixAddress(void *obj_base) {
|
static void FixAddress(void *obj_base) {
|
||||||
ElfW(Word) base = reinterpret_cast<ElfW(Word)>(obj_base);
|
ElfW(Addr) base = reinterpret_cast<ElfW(Addr)>(obj_base);
|
||||||
ElfW(Ehdr) *elf_header = static_cast<ElfW(Ehdr) *>(obj_base);
|
ElfW(Ehdr) *elf_header = static_cast<ElfW(Ehdr) *>(obj_base);
|
||||||
elf_header->e_phoff += base;
|
elf_header->e_phoff += base;
|
||||||
elf_header->e_shoff += base;
|
elf_header->e_shoff += base;
|
||||||
|
@ -187,384 +121,219 @@ static const ElfW(Shdr) *FindSectionByName(const char *name,
|
||||||
|
|
||||||
for (int i = 0; i < nsection; ++i) {
|
for (int i = 0; i < nsection; ++i) {
|
||||||
const char *section_name =
|
const char *section_name =
|
||||||
(char*)(strtab->sh_offset + sections[i].sh_name);
|
reinterpret_cast<char*>(strtab->sh_offset + sections[i].sh_name);
|
||||||
if (!strncmp(name, section_name, name_len))
|
if (!strncmp(name, section_name, name_len))
|
||||||
return sections + i;
|
return sections + i;
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(liuli): Computer the stack parameter size.
|
// Our handler class for STABS data.
|
||||||
// Expect parameter variables are immediately following the N_FUN symbol.
|
class DumpStabsHandler: public google_breakpad::StabsHandler {
|
||||||
// Will need to parse the type information to get a correct size.
|
public:
|
||||||
static int LoadStackParamSize(struct nlist *list,
|
DumpStabsHandler(Module *module) :
|
||||||
struct nlist *list_end,
|
module_(module),
|
||||||
struct FuncInfo *func_info) {
|
comp_unit_base_address_(0),
|
||||||
struct nlist *cur_list = list;
|
current_function_(NULL),
|
||||||
assert(cur_list->n_type == N_FUN);
|
current_source_file_(NULL),
|
||||||
++cur_list;
|
current_source_file_name_(NULL) { }
|
||||||
int step = 1;
|
|
||||||
while (cur_list < list_end && cur_list->n_type == N_PSYM) {
|
|
||||||
++cur_list;
|
|
||||||
++step;
|
|
||||||
}
|
|
||||||
func_info->stack_param_size = 0;
|
|
||||||
return step;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int LoadLineInfo(struct nlist *list,
|
bool StartCompilationUnit(const char *name, uint64_t address,
|
||||||
struct nlist *list_end,
|
const char *build_directory);
|
||||||
const struct SourceFileInfo &source_file_info,
|
bool EndCompilationUnit(uint64_t address);
|
||||||
struct FuncInfo *func_info) {
|
bool StartFunction(const std::string &name, uint64_t address);
|
||||||
struct nlist *cur_list = list;
|
bool EndFunction(uint64_t address);
|
||||||
func_info->has_sol = false;
|
bool Line(uint64_t address, const char *name, int number);
|
||||||
// Records which source file the following lines belongs. Default
|
|
||||||
// to the file we are handling. This helps us handling inlined source.
|
|
||||||
// When encountering N_SOL, we will change this to the source file
|
|
||||||
// specified by N_SOL.
|
|
||||||
int current_source_name_index = source_file_info.name_index;
|
|
||||||
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.
|
|
||||||
if (cur_list->n_type == N_SOL) {
|
|
||||||
func_info->has_sol = true;
|
|
||||||
|
|
||||||
if (cur_list->n_un.n_strx > 0 &&
|
// Do any final processing necessary to make module_ contain all the
|
||||||
cur_list->n_un.n_strx != current_source_name_index) {
|
// data provided by the STABS reader.
|
||||||
// The following lines will be from this source file.
|
//
|
||||||
current_source_name_index = cur_list->n_un.n_strx;
|
// 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
|
||||||
++cur_list;
|
// that here.
|
||||||
}
|
void Finalize();
|
||||||
struct LineInfo line;
|
|
||||||
while (cur_list < list_end && cur_list->n_type == N_SLINE) {
|
|
||||||
line.source_name_index = current_source_name_index;
|
|
||||||
line.rva_to_func = cur_list->n_value;
|
|
||||||
// n_desc is a signed short
|
|
||||||
line.line_num = (unsigned short)cur_list->n_desc;
|
|
||||||
// Don't set it here.
|
|
||||||
// Will be processed in later pass.
|
|
||||||
line.source_id = -1;
|
|
||||||
func_info->line_info.push_back(line);
|
|
||||||
++cur_list;
|
|
||||||
}
|
|
||||||
} while (list < list_end);
|
|
||||||
|
|
||||||
return cur_list - list;
|
private:
|
||||||
}
|
|
||||||
|
|
||||||
static int LoadFuncSymbols(struct nlist *list,
|
// An arbitrary, but very large, size to use for functions whose
|
||||||
struct nlist *list_end,
|
// size we can't compute properly.
|
||||||
const ElfW(Shdr) *stabstr_section,
|
static const uint64_t kFallbackSize = 0x10000000;
|
||||||
struct SourceFileInfo *source_file_info) {
|
|
||||||
struct nlist *cur_list = list;
|
|
||||||
assert(cur_list->n_type == N_SO);
|
|
||||||
++cur_list;
|
|
||||||
source_file_info->func_info.clear();
|
|
||||||
while (cur_list < list_end) {
|
|
||||||
// Go until the function symbol.
|
|
||||||
while (cur_list < list_end && cur_list->n_type != N_FUN) {
|
|
||||||
if (cur_list->n_type == N_SO) {
|
|
||||||
return cur_list - list;
|
|
||||||
}
|
|
||||||
++cur_list;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cur_list->n_type == N_FUN) {
|
|
||||||
struct FuncInfo func_info;
|
|
||||||
func_info.name =
|
|
||||||
reinterpret_cast<char *>(cur_list->n_un.n_strx +
|
|
||||||
stabstr_section->sh_offset);
|
|
||||||
func_info.addr = cur_list->n_value;
|
|
||||||
func_info.rva_to_base = 0;
|
|
||||||
func_info.size = 0;
|
|
||||||
func_info.stack_param_size = 0;
|
|
||||||
func_info.has_sol = 0;
|
|
||||||
|
|
||||||
// Stack parameter size.
|
// The module we're contributing debugging info to.
|
||||||
cur_list += LoadStackParamSize(cur_list, list_end, &func_info);
|
Module *module_;
|
||||||
// Line info.
|
|
||||||
cur_list += LoadLineInfo(cur_list,
|
|
||||||
list_end,
|
|
||||||
*source_file_info,
|
|
||||||
&func_info);
|
|
||||||
|
|
||||||
// Functions in this module should have address bigger than the module
|
// The functions we've generated so far. We don't add these to
|
||||||
// startring address.
|
// module_ as we parse them. Instead, we wait until we've computed
|
||||||
// There maybe a lot of duplicated entry for a function in the symbol,
|
// their ending address, and their lines' ending addresses.
|
||||||
// only one of them can met this.
|
//
|
||||||
if (func_info.addr >= source_file_info->addr) {
|
// We could just stick them in module_ from the outset, but if
|
||||||
source_file_info->func_info.push_back(func_info);
|
// module_ already contains data gathered from other debugging
|
||||||
}
|
// formats, that would complicate the size computation.
|
||||||
}
|
vector<Module::Function *> functions_;
|
||||||
}
|
|
||||||
return cur_list - list;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Comapre the address.
|
// Boundary addresses. STABS doesn't necessarily supply sizes for
|
||||||
// The argument should have a memeber named "addr"
|
// functions and lines, so we need to compute them ourselves by
|
||||||
template<class T1, class T2>
|
// finding the next object.
|
||||||
static bool CompareAddress(T1 *a, T2 *b) {
|
vector<Module::Address> boundaries_;
|
||||||
return a->addr < b->addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort the array into increasing ordered array based on the virtual address.
|
// The base address of the current compilation unit. We use this to
|
||||||
// Return vector of pointers to the elements in the incoming array. So caller
|
// recognize functions we should omit from the symbol file. (If you
|
||||||
// should make sure the returned vector lives longer than the incoming vector.
|
// know the details of why we omit these, please patch this
|
||||||
template<class Container>
|
// comment.)
|
||||||
static std::vector<typename Container::value_type *> SortByAddress(
|
Module::Address comp_unit_base_address_;
|
||||||
Container *container) {
|
|
||||||
typedef typename Container::iterator It;
|
|
||||||
typedef typename Container::value_type T;
|
|
||||||
std::vector<T *> sorted_array_ptr;
|
|
||||||
sorted_array_ptr.reserve(container->size());
|
|
||||||
for (It it = container->begin(); it != container->end(); it++)
|
|
||||||
sorted_array_ptr.push_back(&(*it));
|
|
||||||
std::sort(sorted_array_ptr.begin(),
|
|
||||||
sorted_array_ptr.end(),
|
|
||||||
std::ptr_fun(CompareAddress<T, T>));
|
|
||||||
|
|
||||||
return sorted_array_ptr;
|
// The function we're currently contributing lines to.
|
||||||
}
|
Module::Function *current_function_;
|
||||||
|
|
||||||
// Find the address of the next function or source file symbol in the symbol
|
// The last Module::File we got a line number in.
|
||||||
// table. The address should be bigger than the current function's address.
|
Module::File *current_source_file_;
|
||||||
static ElfW(Addr) NextAddress(
|
|
||||||
std::vector<struct FuncInfo *> *sorted_functions,
|
|
||||||
std::vector<struct SourceFileInfo *> *sorted_files,
|
|
||||||
const struct FuncInfo &func_info) {
|
|
||||||
std::vector<struct FuncInfo *>::iterator next_func_iter =
|
|
||||||
std::find_if(sorted_functions->begin(),
|
|
||||||
sorted_functions->end(),
|
|
||||||
std::bind1st(
|
|
||||||
std::ptr_fun(
|
|
||||||
CompareAddress<struct FuncInfo,
|
|
||||||
struct FuncInfo>
|
|
||||||
),
|
|
||||||
&func_info)
|
|
||||||
);
|
|
||||||
if (next_func_iter != sorted_functions->end())
|
|
||||||
return (*next_func_iter)->addr;
|
|
||||||
|
|
||||||
std::vector<struct SourceFileInfo *>::iterator next_file_iter =
|
// The pointer in the .stabstr section of the name that
|
||||||
std::find_if(sorted_files->begin(),
|
// current_source_file_ is built from. This allows us to quickly
|
||||||
sorted_files->end(),
|
// recognize when the current line is in the same file as the
|
||||||
std::bind1st(
|
// previous one (which it usually is).
|
||||||
std::ptr_fun(
|
const char *current_source_file_name_;
|
||||||
CompareAddress<struct FuncInfo,
|
};
|
||||||
struct SourceFileInfo>
|
|
||||||
),
|
bool DumpStabsHandler::StartCompilationUnit(const char *name, uint64_t address,
|
||||||
&func_info)
|
const char *build_directory) {
|
||||||
);
|
assert(! comp_unit_base_address_);
|
||||||
if (next_file_iter != sorted_files->end()) {
|
current_source_file_name_ = name;
|
||||||
return (*next_file_iter)->addr;
|
current_source_file_ = module_->FindFile(name);
|
||||||
}
|
comp_unit_base_address_ = address;
|
||||||
return 0;
|
boundaries_.push_back(static_cast<Module::Address>(address));
|
||||||
}
|
|
||||||
|
|
||||||
static int FindFileByNameIdx(uint32_t name_index,
|
|
||||||
SourceFileInfoList &files) {
|
|
||||||
for (SourceFileInfoList::iterator it = files.begin();
|
|
||||||
it != files.end(); it++) {
|
|
||||||
if (it->name_index == name_index)
|
|
||||||
return it->source_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add included file information.
|
|
||||||
// Also fix the source id for the line info.
|
|
||||||
static void AddIncludedFiles(struct SymbolInfo *symbols,
|
|
||||||
const ElfW(Shdr) *stabstr_section) {
|
|
||||||
for (SourceFileInfoList::iterator source_file_it =
|
|
||||||
symbols->source_file_info.begin();
|
|
||||||
source_file_it != symbols->source_file_info.end();
|
|
||||||
++source_file_it) {
|
|
||||||
struct SourceFileInfo &source_file = *source_file_it;
|
|
||||||
|
|
||||||
for (FuncInfoList::iterator func_info_it = source_file.func_info.begin();
|
|
||||||
func_info_it != source_file.func_info.end();
|
|
||||||
++func_info_it) {
|
|
||||||
struct FuncInfo &func_info = *func_info_it;
|
|
||||||
|
|
||||||
for (LineInfoList::iterator line_info_it = func_info.line_info.begin();
|
|
||||||
line_info_it != func_info.line_info.end(); ++line_info_it) {
|
|
||||||
struct LineInfo &line_info = *line_info_it;
|
|
||||||
|
|
||||||
assert(line_info.source_name_index > 0);
|
|
||||||
assert(source_file.name_index > 0);
|
|
||||||
|
|
||||||
// Check if the line belongs to the source file by comparing the
|
|
||||||
// name index into string table.
|
|
||||||
if (line_info.source_name_index != source_file.name_index) {
|
|
||||||
// This line is not from the current source file, check if this
|
|
||||||
// source file has been added before.
|
|
||||||
int found_source_id = FindFileByNameIdx(line_info.source_name_index,
|
|
||||||
symbols->source_file_info);
|
|
||||||
if (found_source_id < 0) {
|
|
||||||
// Got a new included file.
|
|
||||||
// Those included files don't have address or line information.
|
|
||||||
SourceFileInfo new_file;
|
|
||||||
new_file.name_index = line_info.source_name_index;
|
|
||||||
new_file.name = reinterpret_cast<char *>(new_file.name_index +
|
|
||||||
stabstr_section->sh_offset);
|
|
||||||
new_file.addr = 0;
|
|
||||||
new_file.source_id = symbols->next_source_id++;
|
|
||||||
line_info.source_id = new_file.source_id;
|
|
||||||
symbols->source_file_info.push_back(new_file);
|
|
||||||
} else {
|
|
||||||
// The file has been added.
|
|
||||||
line_info.source_id = found_source_id;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// The line belongs to the file.
|
|
||||||
line_info.source_id = source_file.source_id;
|
|
||||||
}
|
|
||||||
} // for each line.
|
|
||||||
} // for each function.
|
|
||||||
} // for each source file.
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute size and rva information based on symbols loaded from stab section.
|
|
||||||
static bool ComputeSizeAndRVA(ElfW(Addr) loading_addr,
|
|
||||||
struct SymbolInfo *symbols) {
|
|
||||||
std::vector<struct SourceFileInfo *> sorted_files =
|
|
||||||
SortByAddress(&(symbols->source_file_info));
|
|
||||||
for (size_t i = 0; i < sorted_files.size(); ++i) {
|
|
||||||
struct SourceFileInfo &source_file = *sorted_files[i];
|
|
||||||
std::vector<struct FuncInfo *> sorted_functions =
|
|
||||||
SortByAddress(&(source_file.func_info));
|
|
||||||
for (size_t j = 0; j < sorted_functions.size(); ++j) {
|
|
||||||
struct FuncInfo &func_info = *sorted_functions[j];
|
|
||||||
assert(func_info.addr >= loading_addr);
|
|
||||||
func_info.rva_to_base = func_info.addr - loading_addr;
|
|
||||||
func_info.size = 0;
|
|
||||||
ElfW(Addr) next_addr = NextAddress(&sorted_functions,
|
|
||||||
&sorted_files,
|
|
||||||
func_info);
|
|
||||||
// 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;
|
|
||||||
static int no_next_addr_count = 0;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
no_next_addr_count++;
|
|
||||||
func_info.size = kDefaultSize;
|
|
||||||
}
|
|
||||||
// Compute line size.
|
|
||||||
for (LineInfoList::iterator line_info_it = func_info.line_info.begin();
|
|
||||||
line_info_it != func_info.line_info.end(); line_info_it++) {
|
|
||||||
struct LineInfo &line_info = *line_info_it;
|
|
||||||
LineInfoList::iterator next_line_info_it = line_info_it;
|
|
||||||
next_line_info_it++;
|
|
||||||
line_info.size = 0;
|
|
||||||
if (next_line_info_it != func_info.line_info.end()) {
|
|
||||||
line_info.size =
|
|
||||||
next_line_info_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.
|
|
||||||
return true;
|
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,
|
static bool LoadSymbols(const ElfW(Shdr) *stab_section,
|
||||||
const ElfW(Shdr) *stabstr_section,
|
const ElfW(Shdr) *stabstr_section,
|
||||||
ElfW(Addr) loading_addr,
|
Module *module) {
|
||||||
struct SymbolInfo *symbols) {
|
|
||||||
if (stab_section == NULL || stabstr_section == NULL)
|
if (stab_section == NULL || stabstr_section == NULL)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
struct nlist *lists =
|
// A callback object to handle data from the STABS reader.
|
||||||
reinterpret_cast<struct nlist *>(stab_section->sh_offset);
|
DumpStabsHandler handler(module);
|
||||||
int nstab = stab_section->sh_size / sizeof(struct nlist);
|
// Find the addresses of the STABS data, and create a STABS reader object.
|
||||||
// First pass, load all symbols from the object file.
|
uint8_t *stabs = reinterpret_cast<uint8_t *>(stab_section->sh_offset);
|
||||||
for (int i = 0; i < nstab; ) {
|
uint8_t *stabstr = reinterpret_cast<uint8_t *>(stabstr_section->sh_offset);
|
||||||
int step = 1;
|
google_breakpad::StabsReader reader(stabs, stab_section->sh_size,
|
||||||
struct nlist *cur_list = lists + i;
|
stabstr, stabstr_section->sh_size,
|
||||||
if (cur_list->n_type == N_SO) {
|
&handler);
|
||||||
// FUNC <address> <length> <param_stack_size> <function>
|
// Read the STABS data, and do post-processing.
|
||||||
struct SourceFileInfo source_file_info;
|
if (! reader.Process())
|
||||||
source_file_info.name_index = cur_list->n_un.n_strx;
|
return false;
|
||||||
source_file_info.name = reinterpret_cast<char *>(cur_list->n_un.n_strx +
|
handler.Finalize();
|
||||||
stabstr_section->sh_offset);
|
return true;
|
||||||
source_file_info.addr = cur_list->n_value;
|
|
||||||
if (strchr(source_file_info.name, '.'))
|
|
||||||
source_file_info.source_id = symbols->next_source_id++;
|
|
||||||
else
|
|
||||||
source_file_info.source_id = -1;
|
|
||||||
step = LoadFuncSymbols(cur_list, lists + nstab,
|
|
||||||
stabstr_section, &source_file_info);
|
|
||||||
symbols->source_file_info.push_back(source_file_info);
|
|
||||||
}
|
|
||||||
i += step;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Second pass, compute the size of functions and lines.
|
|
||||||
if (ComputeSizeAndRVA(loading_addr, symbols)) {
|
|
||||||
// Third pass, check for included source code, especially for header files.
|
|
||||||
// Until now, we only have compiling unit information, but they can
|
|
||||||
// have code from include files, add them here.
|
|
||||||
AddIncludedFiles(symbols, stabstr_section);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
// Translate all offsets in section headers into address.
|
||||||
FixAddress(elf_header);
|
FixAddress(elf_header);
|
||||||
ElfW(Addr) loading_addr = GetLoadingAddress(
|
ElfW(Addr) loading_addr = GetLoadingAddress(
|
||||||
reinterpret_cast<ElfW(Phdr) *>(elf_header->e_phoff),
|
reinterpret_cast<ElfW(Phdr) *>(elf_header->e_phoff),
|
||||||
elf_header->e_phnum);
|
elf_header->e_phnum);
|
||||||
|
module->SetLoadAddress(loading_addr);
|
||||||
|
|
||||||
const ElfW(Shdr) *sections =
|
const ElfW(Shdr) *sections =
|
||||||
reinterpret_cast<ElfW(Shdr) *>(elf_header->e_shoff);
|
reinterpret_cast<ElfW(Shdr) *>(elf_header->e_shoff);
|
||||||
|
@ -578,107 +347,7 @@ static bool LoadSymbols(ElfW(Ehdr) *elf_header, struct SymbolInfo *symbols) {
|
||||||
const ElfW(Shdr) *stabstr_section = stab_section->sh_link + sections;
|
const ElfW(Shdr) *stabstr_section = stab_section->sh_link + sections;
|
||||||
|
|
||||||
// Load symbols.
|
// Load symbols.
|
||||||
return LoadSymbols(stab_section, stabstr_section, loading_addr, symbols);
|
return LoadSymbols(stab_section, stabstr_section, module);
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool WriteSourceFileInfo(FILE *file, const struct SymbolInfo &symbols) {
|
|
||||||
for (SourceFileInfoList::const_iterator it =
|
|
||||||
symbols.source_file_info.begin();
|
|
||||||
it != symbols.source_file_info.end(); it++) {
|
|
||||||
if (it->source_id != -1) {
|
|
||||||
const char *name = it->name;
|
|
||||||
if (0 > fprintf(file, "FILE %d %s\n", it->source_id, name))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool WriteOneFunction(FILE *file,
|
|
||||||
const struct FuncInfo &func_info){
|
|
||||||
// Discard the ending part of the name.
|
|
||||||
std::string func_name(func_info.name);
|
|
||||||
std::string::size_type last_colon = func_name.find_last_of(':');
|
|
||||||
if (last_colon != std::string::npos)
|
|
||||||
func_name = func_name.substr(0, last_colon);
|
|
||||||
func_name = Demangle(func_name.c_str());
|
|
||||||
|
|
||||||
if (func_info.size <= 0)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (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.source_id))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool WriteFunctionInfo(FILE *file, const struct SymbolInfo &symbols) {
|
|
||||||
for (SourceFileInfoList::const_iterator it =
|
|
||||||
symbols.source_file_info.begin();
|
|
||||||
it != symbols.source_file_info.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, const struct SymbolInfo &symbols) {
|
|
||||||
return WriteSourceFileInfo(file, symbols) &&
|
|
||||||
WriteFunctionInfo(file, symbols);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -733,6 +402,48 @@ class MmapWrapper {
|
||||||
size_t size_;
|
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
|
||||||
|
|
||||||
namespace google_breakpad {
|
namespace google_breakpad {
|
||||||
|
@ -754,17 +465,27 @@ bool DumpSymbols::WriteSymbolFile(const std::string &obj_file,
|
||||||
ElfW(Ehdr) *elf_header = reinterpret_cast<ElfW(Ehdr) *>(obj_base);
|
ElfW(Ehdr) *elf_header = reinterpret_cast<ElfW(Ehdr) *>(obj_base);
|
||||||
if (!IsValidElf(elf_header))
|
if (!IsValidElf(elf_header))
|
||||||
return false;
|
return false;
|
||||||
struct SymbolInfo symbols;
|
|
||||||
symbols.next_source_id = 0;
|
|
||||||
|
|
||||||
if (!LoadSymbols(elf_header, &symbols))
|
unsigned char identifier[16];
|
||||||
return false;
|
google_breakpad::FileID file_id(obj_file.c_str());
|
||||||
// Write to symbol file.
|
if (! file_id.ElfFileIdentifier(identifier))
|
||||||
if (WriteModuleInfo(sym_file, elf_header->e_machine, obj_file) &&
|
return false;
|
||||||
DumpStabSymbols(sym_file, symbols))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
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
|
} // namespace google_breakpad
|
||||||
|
|
|
@ -32,104 +32,74 @@
|
||||||
// See file_id.h for documentation
|
// See file_id.h for documentation
|
||||||
//
|
//
|
||||||
|
|
||||||
#include <cassert>
|
#include "common/linux/file_id.h"
|
||||||
#include <cstdio>
|
|
||||||
|
#include <arpa/inet.h>
|
||||||
#include <elf.h>
|
#include <elf.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <link.h>
|
#include <link.h>
|
||||||
#include <sys/mman.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "common/linux/file_id.h"
|
#include <cassert>
|
||||||
#include "common/md5.h"
|
#include <cstdio>
|
||||||
|
|
||||||
namespace google_breakpad {
|
namespace google_breakpad {
|
||||||
|
|
||||||
static bool FindElfTextSection(const void *elf_mapped_base,
|
FileID::FileID(const char* path) {
|
||||||
const void **text_start,
|
|
||||||
int *text_size) {
|
|
||||||
assert(elf_mapped_base);
|
|
||||||
assert(text_start);
|
|
||||||
assert(text_size);
|
|
||||||
|
|
||||||
const unsigned char *elf_base =
|
|
||||||
static_cast<const unsigned char *>(elf_mapped_base);
|
|
||||||
const ElfW(Ehdr) *elf_header =
|
|
||||||
reinterpret_cast<const ElfW(Ehdr) *>(elf_base);
|
|
||||||
if (memcmp(elf_header, ELFMAG, SELFMAG) != 0)
|
|
||||||
return false;
|
|
||||||
*text_start = NULL;
|
|
||||||
*text_size = 0;
|
|
||||||
const ElfW(Shdr) *sections =
|
|
||||||
reinterpret_cast<const ElfW(Shdr) *>(elf_base + elf_header->e_shoff);
|
|
||||||
const char *text_section_name = ".text";
|
|
||||||
int name_len = strlen(text_section_name);
|
|
||||||
const ElfW(Shdr) *string_section = sections + elf_header->e_shstrndx;
|
|
||||||
const ElfW(Shdr) *text_section = NULL;
|
|
||||||
for (int i = 0; i < elf_header->e_shnum; ++i) {
|
|
||||||
if (sections[i].sh_type == SHT_PROGBITS) {
|
|
||||||
const char *section_name = (char*)(elf_base +
|
|
||||||
string_section->sh_offset +
|
|
||||||
sections[i].sh_name);
|
|
||||||
if (!strncmp(section_name, text_section_name, name_len)) {
|
|
||||||
text_section = §ions[i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (text_section != NULL && text_section->sh_size > 0) {
|
|
||||||
int text_section_size = text_section->sh_size;
|
|
||||||
*text_start = elf_base + text_section->sh_offset;
|
|
||||||
*text_size = text_section_size;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
FileID::FileID(const char *path) {
|
|
||||||
strncpy(path_, path, sizeof(path_));
|
strncpy(path_, path, sizeof(path_));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FileID::ElfFileIdentifier(unsigned char identifier[16]) {
|
bool FileID::ElfFileIdentifier(uint8_t identifier[kMDGUIDSize]) {
|
||||||
|
const ssize_t mapped_len = 4096; // Page size (matches WriteMappings())
|
||||||
int fd = open(path_, O_RDONLY);
|
int fd = open(path_, O_RDONLY);
|
||||||
if (fd < 0)
|
if (fd < 0)
|
||||||
return false;
|
return false;
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (fstat(fd, &st) != 0 && st.st_size <= 0) {
|
if (fstat(fd, &st) != 0 || st.st_size <= mapped_len) {
|
||||||
close(fd);
|
close(fd);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
void *base = mmap(NULL, st.st_size,
|
void* base = mmap(NULL, mapped_len,
|
||||||
PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
|
PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
|
||||||
if (base == MAP_FAILED) {
|
close(fd);
|
||||||
close(fd);
|
if (base == MAP_FAILED)
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
bool success = false;
|
memset(identifier, 0, kMDGUIDSize);
|
||||||
const void *text_section = NULL;
|
uint8_t* ptr = reinterpret_cast<uint8_t*>(base);
|
||||||
int text_size = 0;
|
uint8_t* ptr_end = ptr + mapped_len;
|
||||||
if (FindElfTextSection(base, &text_section, &text_size) && (text_size > 0)) {
|
while (ptr < ptr_end) {
|
||||||
struct MD5Context md5;
|
for (unsigned i = 0; i < kMDGUIDSize; i++)
|
||||||
MD5Init(&md5);
|
identifier[i] ^= ptr[i];
|
||||||
MD5Update(&md5,
|
ptr += kMDGUIDSize;
|
||||||
static_cast<const unsigned char*>(text_section),
|
|
||||||
text_size);
|
|
||||||
MD5Final(identifier, &md5);
|
|
||||||
success = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
close(fd);
|
munmap(base, mapped_len);
|
||||||
munmap(base, st.st_size);
|
return true;
|
||||||
return success;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
void FileID::ConvertIdentifierToString(const unsigned char identifier[16],
|
void FileID::ConvertIdentifierToString(const uint8_t identifier[kMDGUIDSize],
|
||||||
char *buffer, int buffer_length) {
|
char* buffer, int buffer_length) {
|
||||||
|
uint8_t identifier_swapped[kMDGUIDSize];
|
||||||
|
|
||||||
|
// Endian-ness swap to match dump processor expectation.
|
||||||
|
memcpy(identifier_swapped, identifier, kMDGUIDSize);
|
||||||
|
uint32_t* data1 = reinterpret_cast<uint32_t*>(identifier_swapped);
|
||||||
|
*data1 = htonl(*data1);
|
||||||
|
uint16_t* data2 = reinterpret_cast<uint16_t*>(identifier_swapped + 4);
|
||||||
|
*data2 = htons(*data2);
|
||||||
|
uint16_t* data3 = reinterpret_cast<uint16_t*>(identifier_swapped + 6);
|
||||||
|
*data3 = htons(*data3);
|
||||||
|
|
||||||
int buffer_idx = 0;
|
int buffer_idx = 0;
|
||||||
for (int idx = 0; (buffer_idx < buffer_length) && (idx < 16); ++idx) {
|
for (unsigned int idx = 0;
|
||||||
int hi = (identifier[idx] >> 4) & 0x0F;
|
(buffer_idx < buffer_length) && (idx < kMDGUIDSize);
|
||||||
int lo = (identifier[idx]) & 0x0F;
|
++idx) {
|
||||||
|
int hi = (identifier_swapped[idx] >> 4) & 0x0F;
|
||||||
|
int lo = (identifier_swapped[idx]) & 0x0F;
|
||||||
|
|
||||||
if (idx == 4 || idx == 6 || idx == 8 || idx == 10)
|
if (idx == 4 || idx == 6 || idx == 8 || idx == 10)
|
||||||
buffer[buffer_idx++] = '-';
|
buffer[buffer_idx++] = '-';
|
||||||
|
|
|
@ -35,25 +35,30 @@
|
||||||
|
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
|
||||||
|
#include "common/linux/guid_creator.h"
|
||||||
|
|
||||||
namespace google_breakpad {
|
namespace google_breakpad {
|
||||||
|
|
||||||
|
static const size_t kMDGUIDSize = sizeof(MDGUID);
|
||||||
|
|
||||||
class FileID {
|
class FileID {
|
||||||
public:
|
public:
|
||||||
FileID(const char *path);
|
explicit FileID(const char* path);
|
||||||
~FileID() {};
|
~FileID() {}
|
||||||
|
|
||||||
// Load the identifier for the elf file path specified in the constructor into
|
// Load the identifier for the elf file path specified in the constructor into
|
||||||
// |identifier|. Return false if the identifier could not be created for the
|
// |identifier|. Return false if the identifier could not be created for the
|
||||||
// file.
|
// file.
|
||||||
// The current implementation will return the MD5 hash of the file's bytes.
|
// The current implementation will XOR the first page of data to generate an
|
||||||
bool ElfFileIdentifier(unsigned char identifier[16]);
|
// identifier.
|
||||||
|
bool ElfFileIdentifier(uint8_t identifier[kMDGUIDSize]);
|
||||||
|
|
||||||
// Convert the |identifier| data to a NULL terminated string. The string will
|
// Convert the |identifier| data to a NULL terminated string. The string will
|
||||||
// be formatted as a UUID (e.g., 22F065BB-FC9C-49F7-80FE-26A7CEBD7BCE).
|
// be formatted as a UUID (e.g., 22F065BB-FC9C-49F7-80FE-26A7CEBD7BCE).
|
||||||
// The |buffer| should be at least 37 bytes long to receive all of the data
|
// The |buffer| should be at least 37 bytes long to receive all of the data
|
||||||
// and termination. Shorter buffers will contain truncated data.
|
// and termination. Shorter buffers will contain truncated data.
|
||||||
static void ConvertIdentifierToString(const unsigned char identifier[16],
|
static void ConvertIdentifierToString(const uint8_t identifier[kMDGUIDSize],
|
||||||
char *buffer, int buffer_length);
|
char* buffer, int buffer_length);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Storage for the path specified
|
// Storage for the path specified
|
||||||
|
@ -63,4 +68,3 @@ class FileID {
|
||||||
} // namespace google_breakpad
|
} // namespace google_breakpad
|
||||||
|
|
||||||
#endif // COMMON_LINUX_FILE_ID_H__
|
#endif // COMMON_LINUX_FILE_ID_H__
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,196 @@
|
||||||
|
// 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 "common/linux/google_crashdump_uploader.h"
|
||||||
|
#include "common/linux/libcurl_wrapper.h"
|
||||||
|
#include "third_party/linux/include/glog/logging.h"
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
namespace google_breakpad {
|
||||||
|
|
||||||
|
GoogleCrashdumpUploader::GoogleCrashdumpUploader(const std::string& product,
|
||||||
|
const std::string& version,
|
||||||
|
const std::string& guid,
|
||||||
|
const std::string& ptime,
|
||||||
|
const std::string& ctime,
|
||||||
|
const std::string& email,
|
||||||
|
const std::string& comments,
|
||||||
|
const std::string& minidump_pathname,
|
||||||
|
const std::string& crash_server,
|
||||||
|
const std::string& proxy_host,
|
||||||
|
const std::string& proxy_userpassword) {
|
||||||
|
LibcurlWrapper* http_layer = new LibcurlWrapper();
|
||||||
|
Init(product,
|
||||||
|
version,
|
||||||
|
guid,
|
||||||
|
ptime,
|
||||||
|
ctime,
|
||||||
|
email,
|
||||||
|
comments,
|
||||||
|
minidump_pathname,
|
||||||
|
crash_server,
|
||||||
|
proxy_host,
|
||||||
|
proxy_userpassword,
|
||||||
|
http_layer);
|
||||||
|
}
|
||||||
|
|
||||||
|
GoogleCrashdumpUploader::GoogleCrashdumpUploader(const std::string& product,
|
||||||
|
const std::string& version,
|
||||||
|
const std::string& guid,
|
||||||
|
const std::string& ptime,
|
||||||
|
const std::string& ctime,
|
||||||
|
const std::string& email,
|
||||||
|
const std::string& comments,
|
||||||
|
const std::string& minidump_pathname,
|
||||||
|
const std::string& crash_server,
|
||||||
|
const std::string& proxy_host,
|
||||||
|
const std::string& proxy_userpassword,
|
||||||
|
LibcurlWrapper* http_layer) {
|
||||||
|
Init(product,
|
||||||
|
version,
|
||||||
|
guid,
|
||||||
|
ptime,
|
||||||
|
ctime,
|
||||||
|
email,
|
||||||
|
comments,
|
||||||
|
minidump_pathname,
|
||||||
|
crash_server,
|
||||||
|
proxy_host,
|
||||||
|
proxy_userpassword,
|
||||||
|
http_layer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GoogleCrashdumpUploader::Init(const std::string& product,
|
||||||
|
const std::string& version,
|
||||||
|
const std::string& guid,
|
||||||
|
const std::string& ptime,
|
||||||
|
const std::string& ctime,
|
||||||
|
const std::string& email,
|
||||||
|
const std::string& comments,
|
||||||
|
const std::string& minidump_pathname,
|
||||||
|
const std::string& crash_server,
|
||||||
|
const std::string& proxy_host,
|
||||||
|
const std::string& proxy_userpassword,
|
||||||
|
LibcurlWrapper* http_layer) {
|
||||||
|
product_ = product;
|
||||||
|
version_ = version;
|
||||||
|
guid_ = guid;
|
||||||
|
ptime_ = ptime;
|
||||||
|
ctime_ = ctime;
|
||||||
|
email_ = email;
|
||||||
|
comments_ = comments;
|
||||||
|
http_layer_ = http_layer;
|
||||||
|
|
||||||
|
crash_server_ = crash_server;
|
||||||
|
proxy_host_ = proxy_host;
|
||||||
|
proxy_userpassword_ = proxy_userpassword;
|
||||||
|
minidump_pathname_ = minidump_pathname;
|
||||||
|
LOG(INFO) << "Uploader initializing";
|
||||||
|
LOG(INFO) << "\tProduct: " << product_;
|
||||||
|
LOG(INFO) << "\tVersion: " << version_;
|
||||||
|
LOG(INFO) << "\tGUID: " << guid_;
|
||||||
|
if (!ptime_.empty()) {
|
||||||
|
LOG(INFO) << "\tProcess uptime: " << ptime_;
|
||||||
|
}
|
||||||
|
if (!ctime_.empty()) {
|
||||||
|
LOG(INFO) << "\tCumulative Process uptime: " << ctime_;
|
||||||
|
}
|
||||||
|
if (!email_.empty()) {
|
||||||
|
LOG(INFO) << "\tEmail: " << email_;
|
||||||
|
}
|
||||||
|
if (!comments_.empty()) {
|
||||||
|
LOG(INFO) << "\tComments: " << comments_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GoogleCrashdumpUploader::CheckRequiredParametersArePresent() {
|
||||||
|
std::string error_text;
|
||||||
|
if (product_.empty()) {
|
||||||
|
error_text.append("\nProduct name must be specified.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version_.empty()) {
|
||||||
|
error_text.append("\nProduct version must be specified.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (guid_.empty()) {
|
||||||
|
error_text.append("\nClient ID must be specified.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minidump_pathname_.empty()) {
|
||||||
|
error_text.append("\nMinidump pathname must be specified.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!error_text.empty()) {
|
||||||
|
LOG(ERROR) << error_text;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GoogleCrashdumpUploader::Upload() {
|
||||||
|
bool ok = http_layer_->Init();
|
||||||
|
if (!ok) {
|
||||||
|
LOG(WARNING) << "http layer init failed";
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CheckRequiredParametersArePresent()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct stat st;
|
||||||
|
int err = stat(minidump_pathname_.c_str(), &st);
|
||||||
|
if (err) {
|
||||||
|
LOG(WARNING) << minidump_pathname_ << " could not be found: " << errno;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
parameters_["prod"] = product_;
|
||||||
|
parameters_["ver"] = version_;
|
||||||
|
parameters_["guid"] = guid_;
|
||||||
|
parameters_["ptime"] = ptime_;
|
||||||
|
parameters_["ctime"] = ctime_;
|
||||||
|
parameters_["email"] = email_;
|
||||||
|
parameters_["comments_"] = comments_;
|
||||||
|
if (!http_layer_->AddFile(minidump_pathname_,
|
||||||
|
"upload_file_minidump")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
LOG(INFO) << "Sending request to " << crash_server_;
|
||||||
|
return http_layer_->SendRequest(crash_server_,
|
||||||
|
parameters_,
|
||||||
|
NULL);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
// 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 <map>
|
||||||
|
|
||||||
|
namespace google_breakpad {
|
||||||
|
|
||||||
|
class LibcurlWrapper;
|
||||||
|
|
||||||
|
class GoogleCrashdumpUploader {
|
||||||
|
public:
|
||||||
|
GoogleCrashdumpUploader(const std::string& product,
|
||||||
|
const std::string& version,
|
||||||
|
const std::string& guid,
|
||||||
|
const std::string& ptime,
|
||||||
|
const std::string& ctime,
|
||||||
|
const std::string& email,
|
||||||
|
const std::string& comments,
|
||||||
|
const std::string& minidump_pathname,
|
||||||
|
const std::string& crash_server,
|
||||||
|
const std::string& proxy_host,
|
||||||
|
const std::string& proxy_userpassword);
|
||||||
|
|
||||||
|
GoogleCrashdumpUploader(const std::string& product,
|
||||||
|
const std::string& version,
|
||||||
|
const std::string& guid,
|
||||||
|
const std::string& ptime,
|
||||||
|
const std::string& ctime,
|
||||||
|
const std::string& email,
|
||||||
|
const std::string& comments,
|
||||||
|
const std::string& minidump_pathname,
|
||||||
|
const std::string& crash_server,
|
||||||
|
const std::string& proxy_host,
|
||||||
|
const std::string& proxy_userpassword,
|
||||||
|
LibcurlWrapper* http_layer);
|
||||||
|
|
||||||
|
void Init(const std::string& product,
|
||||||
|
const std::string& version,
|
||||||
|
const std::string& guid,
|
||||||
|
const std::string& ptime,
|
||||||
|
const std::string& ctime,
|
||||||
|
const std::string& email,
|
||||||
|
const std::string& comments,
|
||||||
|
const std::string& minidump_pathname,
|
||||||
|
const std::string& crash_server,
|
||||||
|
const std::string& proxy_host,
|
||||||
|
const std::string& proxy_userpassword,
|
||||||
|
LibcurlWrapper* http_layer);
|
||||||
|
bool Upload();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool CheckRequiredParametersArePresent();
|
||||||
|
|
||||||
|
LibcurlWrapper* http_layer_;
|
||||||
|
std::string product_;
|
||||||
|
std::string version_;
|
||||||
|
std::string guid_;
|
||||||
|
std::string ptime_;
|
||||||
|
std::string ctime_;
|
||||||
|
std::string email_;
|
||||||
|
std::string comments_;
|
||||||
|
std::string minidump_pathname_;
|
||||||
|
|
||||||
|
std::string crash_server_;
|
||||||
|
std::string proxy_host_;
|
||||||
|
std::string proxy_userpassword_;
|
||||||
|
|
||||||
|
std::map<std::string, std::string> parameters_;
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,166 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// Unit test for crash dump uploader.
|
||||||
|
|
||||||
|
#include "common/linux/google_crashdump_uploader.h"
|
||||||
|
#include "common/linux/libcurl_wrapper.h"
|
||||||
|
#include "breakpad_googletest_includes.h"
|
||||||
|
|
||||||
|
namespace google_breakpad {
|
||||||
|
|
||||||
|
using ::testing::Return;
|
||||||
|
using ::testing::_;
|
||||||
|
|
||||||
|
class MockLibcurlWrapper : public LibcurlWrapper {
|
||||||
|
public:
|
||||||
|
MOCK_METHOD0(Init, bool());
|
||||||
|
MOCK_METHOD2(SetProxy, bool(const std::string& proxy_host,
|
||||||
|
const std::string& proxy_userpwd));
|
||||||
|
MOCK_METHOD2(AddFile, bool(const std::string& upload_file_path,
|
||||||
|
const std::string& basename));
|
||||||
|
MOCK_METHOD3(SendRequest,
|
||||||
|
bool(const std::string& url,
|
||||||
|
const std::map<std::string, std::string>& parameters,
|
||||||
|
std::string* server_response));
|
||||||
|
};
|
||||||
|
|
||||||
|
class GoogleCrashdumpUploaderTest : public ::testing::Test {
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(GoogleCrashdumpUploaderTest, InitFailsCausesUploadFailure) {
|
||||||
|
MockLibcurlWrapper m;
|
||||||
|
EXPECT_CALL(m, Init()).Times(1).WillOnce(Return(false));
|
||||||
|
GoogleCrashdumpUploader *uploader = new GoogleCrashdumpUploader("foobar",
|
||||||
|
"1.0",
|
||||||
|
"AAA-BBB",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"test@test.com",
|
||||||
|
"none",
|
||||||
|
"/tmp/foo.dmp",
|
||||||
|
"http://foo.com",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
&m);
|
||||||
|
ASSERT_FALSE(uploader->Upload());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(GoogleCrashdumpUploaderTest, TestSendRequestHappensWithValidParameters) {
|
||||||
|
// Create a temp file
|
||||||
|
char tempfn[80] = "/tmp/googletest-upload-XXXXXX";
|
||||||
|
int fd = mkstemp(tempfn);
|
||||||
|
ASSERT_NE(fd, -1);
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
MockLibcurlWrapper m;
|
||||||
|
EXPECT_CALL(m, Init()).Times(1).WillOnce(Return(true));
|
||||||
|
EXPECT_CALL(m, AddFile(tempfn, _)).WillOnce(Return(true));
|
||||||
|
EXPECT_CALL(m,
|
||||||
|
SendRequest("http://foo.com",_,_)).Times(1).WillOnce(Return(true));
|
||||||
|
GoogleCrashdumpUploader *uploader = new GoogleCrashdumpUploader("foobar",
|
||||||
|
"1.0",
|
||||||
|
"AAA-BBB",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"test@test.com",
|
||||||
|
"none",
|
||||||
|
tempfn,
|
||||||
|
"http://foo.com",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
&m);
|
||||||
|
ASSERT_TRUE(uploader->Upload());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST_F(GoogleCrashdumpUploaderTest, InvalidPathname) {
|
||||||
|
MockLibcurlWrapper m;
|
||||||
|
EXPECT_CALL(m, Init()).Times(1).WillOnce(Return(true));
|
||||||
|
EXPECT_CALL(m, SendRequest(_,_,_)).Times(0);
|
||||||
|
GoogleCrashdumpUploader *uploader = new GoogleCrashdumpUploader("foobar",
|
||||||
|
"1.0",
|
||||||
|
"AAA-BBB",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"test@test.com",
|
||||||
|
"none",
|
||||||
|
"/tmp/foo.dmp",
|
||||||
|
"http://foo.com",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
&m);
|
||||||
|
ASSERT_FALSE(uploader->Upload());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(GoogleCrashdumpUploaderTest, TestRequiredParametersMustBePresent) {
|
||||||
|
// Test with empty product name.
|
||||||
|
GoogleCrashdumpUploader uploader("",
|
||||||
|
"1.0",
|
||||||
|
"AAA-BBB",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"test@test.com",
|
||||||
|
"none",
|
||||||
|
"/tmp/foo.dmp",
|
||||||
|
"http://foo.com",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
ASSERT_FALSE(uploader.Upload());
|
||||||
|
|
||||||
|
// Test with empty product version.
|
||||||
|
GoogleCrashdumpUploader uploader1("product",
|
||||||
|
"",
|
||||||
|
"AAA-BBB",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"/tmp/foo.dmp",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
|
||||||
|
ASSERT_FALSE(uploader1.Upload());
|
||||||
|
|
||||||
|
// Test with empty client GUID.
|
||||||
|
GoogleCrashdumpUploader uploader2("product",
|
||||||
|
"1.0",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"/tmp/foo.dmp",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
ASSERT_FALSE(uploader2.Upload());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,209 @@
|
||||||
|
// 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 <curl/curl.h>
|
||||||
|
#include <curl/easy.h>
|
||||||
|
#include <curl/types.h>
|
||||||
|
#include <dlfcn.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "common/linux/libcurl_wrapper.h"
|
||||||
|
#include "third_party/linux/include/glog/logging.h"
|
||||||
|
|
||||||
|
namespace google_breakpad {
|
||||||
|
LibcurlWrapper::LibcurlWrapper()
|
||||||
|
: init_ok_(false),
|
||||||
|
formpost_(NULL),
|
||||||
|
lastptr_(NULL),
|
||||||
|
headerlist_(NULL) {
|
||||||
|
curl_lib_ = dlopen("libcurl.so", RTLD_NOW);
|
||||||
|
if (!curl_lib_) {
|
||||||
|
curl_lib_ = dlopen("libcurl.so.4", RTLD_NOW);
|
||||||
|
}
|
||||||
|
if (!curl_lib_) {
|
||||||
|
curl_lib_ = dlopen("libcurl.so.3", RTLD_NOW);
|
||||||
|
}
|
||||||
|
if (!curl_lib_) {
|
||||||
|
LOG(WARNING) << "Could not find libcurl via dlopen";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LOG(INFO) << "LibcurlWrapper init succeeded";
|
||||||
|
init_ok_ = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LibcurlWrapper::SetProxy(const std::string& proxy_host,
|
||||||
|
const std::string& proxy_userpwd) {
|
||||||
|
if (!init_ok_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Set proxy information if necessary.
|
||||||
|
if (!proxy_host.empty()) {
|
||||||
|
(*easy_setopt_)(curl_, CURLOPT_PROXY, proxy_host.c_str());
|
||||||
|
} else {
|
||||||
|
LOG(WARNING) << "SetProxy called with empty proxy host.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!proxy_userpwd.empty()) {
|
||||||
|
(*easy_setopt_)(curl_, CURLOPT_PROXYUSERPWD, proxy_userpwd.c_str());
|
||||||
|
} else {
|
||||||
|
LOG(WARNING) << "SetProxy called with empty proxy username/password.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
LOG(INFO) << "Set proxy host to " << proxy_host;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LibcurlWrapper::AddFile(const std::string& upload_file_path,
|
||||||
|
const std::string& basename) {
|
||||||
|
if (!init_ok_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
LOG(INFO) << "Adding " << upload_file_path << " to form upload.";
|
||||||
|
// Add form file.
|
||||||
|
(*formadd_)(&formpost_, &lastptr_,
|
||||||
|
CURLFORM_COPYNAME, basename.c_str(),
|
||||||
|
CURLFORM_FILE, upload_file_path.c_str(),
|
||||||
|
CURLFORM_END);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback to get the response data from server.
|
||||||
|
static size_t WriteCallback(void *ptr, size_t size,
|
||||||
|
size_t nmemb, void *userp) {
|
||||||
|
if (!userp)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
std::string *response = reinterpret_cast<std::string *>(userp);
|
||||||
|
size_t real_size = size * nmemb;
|
||||||
|
response->append(reinterpret_cast<char *>(ptr), real_size);
|
||||||
|
return real_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LibcurlWrapper::SendRequest(const std::string& url,
|
||||||
|
const std::map<std::string, std::string>& parameters,
|
||||||
|
std::string* server_response) {
|
||||||
|
(*easy_setopt_)(curl_, CURLOPT_URL, url.c_str());
|
||||||
|
std::map<std::string, std::string>::const_iterator iter = parameters.begin();
|
||||||
|
for (; iter != parameters.end(); ++iter)
|
||||||
|
(*formadd_)(&formpost_, &lastptr_,
|
||||||
|
CURLFORM_COPYNAME, iter->first.c_str(),
|
||||||
|
CURLFORM_COPYCONTENTS, iter->second.c_str(),
|
||||||
|
CURLFORM_END);
|
||||||
|
|
||||||
|
(*easy_setopt_)(curl_, CURLOPT_HTTPPOST, formpost_);
|
||||||
|
if (server_response != NULL) {
|
||||||
|
(*easy_setopt_)(curl_, CURLOPT_WRITEFUNCTION, WriteCallback);
|
||||||
|
(*easy_setopt_)(curl_, CURLOPT_WRITEDATA,
|
||||||
|
reinterpret_cast<void *>(server_response));
|
||||||
|
}
|
||||||
|
|
||||||
|
CURLcode err_code = CURLE_OK;
|
||||||
|
err_code = (*easy_perform_)(curl_);
|
||||||
|
*(void**) (&easy_strerror_) = dlsym(curl_lib_, "curl_easy_strerror");
|
||||||
|
#ifndef NDEBUG
|
||||||
|
if (err_code != CURLE_OK)
|
||||||
|
fprintf(stderr, "Failed to send http request to %s, error: %s\n",
|
||||||
|
url.c_str(),
|
||||||
|
(*easy_strerror_)(err_code));
|
||||||
|
#endif
|
||||||
|
if (headerlist_ != NULL) {
|
||||||
|
(*slist_free_all_)(headerlist_);
|
||||||
|
}
|
||||||
|
|
||||||
|
(*easy_cleanup_)(curl_);
|
||||||
|
if (formpost_ != NULL) {
|
||||||
|
(*formfree_)(formpost_);
|
||||||
|
}
|
||||||
|
|
||||||
|
return err_code == CURLE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LibcurlWrapper::Init() {
|
||||||
|
if (!init_ok_) {
|
||||||
|
LOG(WARNING) << "Init_OK was not true in LibcurlWrapper::Init(), check earlier log messages";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SetFunctionPointers()) {
|
||||||
|
LOG(WARNING) << "Could not find function pointers";
|
||||||
|
init_ok_ = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_ = (*easy_init_)();
|
||||||
|
|
||||||
|
last_curl_error_ = "No Error";
|
||||||
|
|
||||||
|
if (!curl_) {
|
||||||
|
dlclose(curl_lib_);
|
||||||
|
LOG(WARNING) << "Curl initialization failed";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable 100-continue header.
|
||||||
|
char buf[] = "Expect:";
|
||||||
|
|
||||||
|
headerlist_ = (*slist_append_)(headerlist_, buf);
|
||||||
|
(*easy_setopt_)(curl_, CURLOPT_HTTPHEADER, headerlist_);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define SET_AND_CHECK_FUNCTION_POINTER(var, function_name) \
|
||||||
|
*(void**) (&var) = dlsym(curl_lib_, function_name); \
|
||||||
|
if (!var) { \
|
||||||
|
LOG(WARNING) << "Could not find libcurl function " << function_name; \
|
||||||
|
init_ok_ = false; \
|
||||||
|
return false; \
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LibcurlWrapper::SetFunctionPointers() {
|
||||||
|
|
||||||
|
SET_AND_CHECK_FUNCTION_POINTER(easy_init_,
|
||||||
|
"curl_easy_init");
|
||||||
|
SET_AND_CHECK_FUNCTION_POINTER(easy_setopt_,
|
||||||
|
"curl_easy_setopt");
|
||||||
|
SET_AND_CHECK_FUNCTION_POINTER(formadd_,
|
||||||
|
"curl_formadd");
|
||||||
|
SET_AND_CHECK_FUNCTION_POINTER(slist_append_,
|
||||||
|
"curl_slist_append");
|
||||||
|
SET_AND_CHECK_FUNCTION_POINTER(easy_perform_,
|
||||||
|
"curl_easy_perform");
|
||||||
|
SET_AND_CHECK_FUNCTION_POINTER(easy_cleanup_,
|
||||||
|
"curl_easy_cleanup");
|
||||||
|
SET_AND_CHECK_FUNCTION_POINTER(slist_free_all_,
|
||||||
|
"curl_slist_free_all");
|
||||||
|
SET_AND_CHECK_FUNCTION_POINTER(formfree_,
|
||||||
|
"curl_formfree");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// A wrapper for libcurl to do HTTP Uploads, to support easy mocking
|
||||||
|
// and unit testing of the HTTPUpload class.
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <map>
|
||||||
|
#include <curl/curl.h>
|
||||||
|
|
||||||
|
namespace google_breakpad {
|
||||||
|
class LibcurlWrapper {
|
||||||
|
public:
|
||||||
|
LibcurlWrapper();
|
||||||
|
virtual bool Init();
|
||||||
|
virtual bool SetProxy(const std::string& proxy_host,
|
||||||
|
const std::string& proxy_userpwd);
|
||||||
|
virtual bool AddFile(const std::string& upload_file_path,
|
||||||
|
const std::string& basename);
|
||||||
|
virtual bool SendRequest(const std::string& url,
|
||||||
|
const std::map<std::string, std::string>& parameters,
|
||||||
|
std::string* server_response);
|
||||||
|
private:
|
||||||
|
// This function initializes class state corresponding to function
|
||||||
|
// pointers into the CURL library.
|
||||||
|
bool SetFunctionPointers();
|
||||||
|
|
||||||
|
bool init_ok_; // Whether init succeeded
|
||||||
|
void* curl_lib_; // Pointer to result of dlopen() on
|
||||||
|
// curl library
|
||||||
|
std::string last_curl_error_; // The text of the last error when
|
||||||
|
// dealing
|
||||||
|
// with CURL.
|
||||||
|
|
||||||
|
CURL *curl_; // Pointer for handle for CURL calls.
|
||||||
|
|
||||||
|
CURL* (*easy_init_)(void);
|
||||||
|
|
||||||
|
// Stateful pointers for calling into curl_formadd()
|
||||||
|
struct curl_httppost *formpost_;
|
||||||
|
struct curl_httppost *lastptr_;
|
||||||
|
struct curl_slist *headerlist_;
|
||||||
|
|
||||||
|
// Function pointers into CURL library
|
||||||
|
CURLcode (*easy_setopt_)(CURL *, CURLoption, ...);
|
||||||
|
CURLFORMcode (*formadd_)(struct curl_httppost **,
|
||||||
|
struct curl_httppost **, ...);
|
||||||
|
struct curl_slist* (*slist_append_)(struct curl_slist *, const char *);
|
||||||
|
void (*slist_free_all_)(struct curl_slist *);
|
||||||
|
CURLcode (*easy_perform_)(CURL *);
|
||||||
|
const char* (*easy_strerror_)(CURLcode);
|
||||||
|
void (*easy_cleanup_)(CURL *);
|
||||||
|
void (*formfree_)(struct curl_httppost *);
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
|
@ -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_
|
|
@ -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 "common/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, '-');
|
||||||
|
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,181 @@
|
||||||
|
// 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) {
|
||||||
|
#ifdef __x86_64
|
||||||
|
void *a = sys_mmap(NULL, page_size_ * num_pages, PROT_READ | PROT_WRITE,
|
||||||
|
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||||
|
#else
|
||||||
|
void *a = sys_mmap2(NULL, page_size_ * num_pages, PROT_READ | PROT_WRITE,
|
||||||
|
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||||
|
#endif
|
||||||
|
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_
|
|
@ -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 "common/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);
|
||||||
|
}
|
|
@ -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
|
|
@ -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__
|
|
@ -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
|
|
@ -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__
|
|
@ -31,6 +31,8 @@
|
||||||
// Simple string dictionary that does not allocate memory
|
// Simple string dictionary that does not allocate memory
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
#import "SimpleStringDictionary.h"
|
#import "SimpleStringDictionary.h"
|
||||||
|
|
||||||
namespace google_breakpad {
|
namespace google_breakpad {
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
|
|
||||||
// This will map from an architecture string to a SectionMap, which
|
// This will map from an architecture string to a SectionMap, which
|
||||||
// will contain the offsets for all the sections in the dictionary
|
// will contain the offsets for all the sections in the dictionary
|
||||||
typedef hash_map<string, dwarf2reader::SectionMap *> ArchSectionMap;
|
typedef map<string, dwarf2reader::SectionMap *> ArchSectionMap;
|
||||||
|
|
||||||
@interface DumpSymbols : NSObject {
|
@interface DumpSymbols : NSObject {
|
||||||
@protected
|
@protected
|
||||||
|
|
|
@ -68,15 +68,6 @@ static NSString *kHeaderCPUTypeKey = @"cpuType";
|
||||||
// for pruning out extraneous non-function symbols.
|
// for pruning out extraneous non-function symbols.
|
||||||
static const int kTextSection = 1;
|
static const int kTextSection = 1;
|
||||||
|
|
||||||
namespace __gnu_cxx {
|
|
||||||
template<>
|
|
||||||
struct hash<std::string> {
|
|
||||||
size_t operator()(const std::string& k) const {
|
|
||||||
return hash< const char* >()( k.c_str() );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dump FunctionMap to stdout. Print address, function name, file
|
// Dump FunctionMap to stdout. Print address, function name, file
|
||||||
// name, line number, lowpc, and highpc if available.
|
// name, line number, lowpc, and highpc if available.
|
||||||
void DumpFunctionMap(const dwarf2reader::FunctionMap function_map) {
|
void DumpFunctionMap(const dwarf2reader::FunctionMap function_map) {
|
||||||
|
@ -321,13 +312,16 @@ void DumpFunctionMap(const dwarf2reader::FunctionMap function_map) {
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
- (BOOL)loadSymbolInfo:(void *)base offset:(uint32_t)offset {
|
- (BOOL)loadSymbolInfo:(void *)base offset:(uint32_t)offset {
|
||||||
|
BOOL loadedStabs = [self loadSTABSSymbolInfo:base offset:offset];
|
||||||
|
|
||||||
NSMutableDictionary *archSections = [sectionData_ objectForKey:architecture_];
|
NSMutableDictionary *archSections = [sectionData_ objectForKey:architecture_];
|
||||||
|
BOOL loadedDWARF = NO;
|
||||||
if ([archSections objectForKey:@"__DWARF__debug_info"]) {
|
if ([archSections objectForKey:@"__DWARF__debug_info"]) {
|
||||||
// Treat this this as debug information
|
// Treat this this as debug information
|
||||||
return [self loadDWARFSymbolInfo:base offset:offset];
|
loadedDWARF = [self loadDWARFSymbolInfo:base offset:offset];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [self loadSTABSSymbolInfo:base offset:offset];
|
return loadedDWARF || loadedStabs;
|
||||||
}
|
}
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
|
@ -342,11 +336,15 @@ void DumpFunctionMap(const dwarf2reader::FunctionMap function_map) {
|
||||||
section *dbgInfoSection = [[archSections objectForKey:@"__DWARF__debug_info"] sectionPointer];
|
section *dbgInfoSection = [[archSections objectForKey:@"__DWARF__debug_info"] sectionPointer];
|
||||||
uint32_t debugInfoSize = SwapLongIfNeeded(dbgInfoSection->size);
|
uint32_t debugInfoSize = SwapLongIfNeeded(dbgInfoSection->size);
|
||||||
|
|
||||||
// i think this will break if run on a big-endian machine
|
#if __BIG_ENDIAN__
|
||||||
|
dwarf2reader::ByteReader byte_reader(swap ?
|
||||||
|
dwarf2reader::ENDIANNESS_LITTLE :
|
||||||
|
dwarf2reader::ENDIANNESS_BIG);
|
||||||
|
#elif __LITTLE_ENDIAN__
|
||||||
dwarf2reader::ByteReader byte_reader(swap ?
|
dwarf2reader::ByteReader byte_reader(swap ?
|
||||||
dwarf2reader::ENDIANNESS_BIG :
|
dwarf2reader::ENDIANNESS_BIG :
|
||||||
dwarf2reader::ENDIANNESS_LITTLE);
|
dwarf2reader::ENDIANNESS_LITTLE);
|
||||||
|
#endif
|
||||||
uint64_t dbgOffset = 0;
|
uint64_t dbgOffset = 0;
|
||||||
|
|
||||||
dwarf2reader::SectionMap* oneArchitectureSectionMap = [self getSectionMapForArchitecture:architecture_];
|
dwarf2reader::SectionMap* oneArchitectureSectionMap = [self getSectionMapForArchitecture:architecture_];
|
||||||
|
|
|
@ -37,9 +37,11 @@ inline uint8 ByteReader::ReadOneByte(const char* buffer) const {
|
||||||
return buffer[0];
|
return buffer[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
inline uint16 ByteReader::ReadTwoBytes(const char* buffer) const {
|
inline uint16 ByteReader::ReadTwoBytes(const char* signed_buffer) const {
|
||||||
const uint16 buffer0 = static_cast<uint16>(buffer[0]);
|
const unsigned char *buffer
|
||||||
const uint16 buffer1 = static_cast<uint16>(buffer[1]);
|
= reinterpret_cast<const unsigned char *>(signed_buffer);
|
||||||
|
const uint16 buffer0 = buffer[0];
|
||||||
|
const uint16 buffer1 = buffer[1];
|
||||||
if (endian_ == ENDIANNESS_LITTLE) {
|
if (endian_ == ENDIANNESS_LITTLE) {
|
||||||
return buffer0 | buffer1 << 8;
|
return buffer0 | buffer1 << 8;
|
||||||
} else {
|
} else {
|
||||||
|
@ -47,11 +49,13 @@ inline uint16 ByteReader::ReadTwoBytes(const char* buffer) const {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline uint64 ByteReader::ReadFourBytes(const char* buffer) const {
|
inline uint64 ByteReader::ReadFourBytes(const char* signed_buffer) const {
|
||||||
const uint32 buffer0 = static_cast<uint32>(buffer[0]);
|
const unsigned char *buffer
|
||||||
const uint32 buffer1 = static_cast<uint32>(buffer[1]);
|
= reinterpret_cast<const unsigned char *>(signed_buffer);
|
||||||
const uint32 buffer2 = static_cast<uint32>(buffer[2]);
|
const uint32 buffer0 = buffer[0];
|
||||||
const uint32 buffer3 = static_cast<uint32>(buffer[3]);
|
const uint32 buffer1 = buffer[1];
|
||||||
|
const uint32 buffer2 = buffer[2];
|
||||||
|
const uint32 buffer3 = buffer[3];
|
||||||
if (endian_ == ENDIANNESS_LITTLE) {
|
if (endian_ == ENDIANNESS_LITTLE) {
|
||||||
return buffer0 | buffer1 << 8 | buffer2 << 16 | buffer3 << 24;
|
return buffer0 | buffer1 << 8 | buffer2 << 16 | buffer3 << 24;
|
||||||
} else {
|
} else {
|
||||||
|
@ -59,15 +63,17 @@ inline uint64 ByteReader::ReadFourBytes(const char* buffer) const {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline uint64 ByteReader::ReadEightBytes(const char* buffer) const {
|
inline uint64 ByteReader::ReadEightBytes(const char* signed_buffer) const {
|
||||||
const uint64 buffer0 = static_cast<uint64>(buffer[0]);
|
const unsigned char *buffer
|
||||||
const uint64 buffer1 = static_cast<uint64>(buffer[1]);
|
= reinterpret_cast<const unsigned char *>(signed_buffer);
|
||||||
const uint64 buffer2 = static_cast<uint64>(buffer[2]);
|
const uint64 buffer0 = buffer[0];
|
||||||
const uint64 buffer3 = static_cast<uint64>(buffer[3]);
|
const uint64 buffer1 = buffer[1];
|
||||||
const uint64 buffer4 = static_cast<uint64>(buffer[4]);
|
const uint64 buffer2 = buffer[2];
|
||||||
const uint64 buffer5 = static_cast<uint64>(buffer[5]);
|
const uint64 buffer3 = buffer[3];
|
||||||
const uint64 buffer6 = static_cast<uint64>(buffer[6]);
|
const uint64 buffer4 = buffer[4];
|
||||||
const uint64 buffer7 = static_cast<uint64>(buffer[7]);
|
const uint64 buffer5 = buffer[5];
|
||||||
|
const uint64 buffer6 = buffer[6];
|
||||||
|
const uint64 buffer7 = buffer[7];
|
||||||
if (endian_ == ENDIANNESS_LITTLE) {
|
if (endian_ == ENDIANNESS_LITTLE) {
|
||||||
return buffer0 | buffer1 << 8 | buffer2 << 16 | buffer3 << 24 |
|
return buffer0 | buffer1 << 8 | buffer2 << 16 | buffer3 << 24 |
|
||||||
buffer4 << 32 | buffer5 << 40 | buffer6 << 48 | buffer7 << 56;
|
buffer4 << 32 | buffer5 << 40 | buffer6 << 48 | buffer7 << 56;
|
||||||
|
|
|
@ -26,8 +26,9 @@
|
||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
#include "common/mac/dwarf/bytereader-inl.h"
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "common/mac/dwarf/bytereader-inl.h"
|
||||||
#include "common/mac/dwarf/bytereader.h"
|
#include "common/mac/dwarf/bytereader.h"
|
||||||
|
|
||||||
namespace dwarf2reader {
|
namespace dwarf2reader {
|
||||||
|
|
|
@ -26,7 +26,8 @@
|
||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
#include <ext/hash_map>
|
#include <assert.h>
|
||||||
|
|
||||||
#include <stack>
|
#include <stack>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
@ -35,17 +36,6 @@
|
||||||
#include "common/mac/dwarf/bytereader.h"
|
#include "common/mac/dwarf/bytereader.h"
|
||||||
#include "common/mac/dwarf/line_state_machine.h"
|
#include "common/mac/dwarf/line_state_machine.h"
|
||||||
|
|
||||||
namespace __gnu_cxx
|
|
||||||
{
|
|
||||||
template<> struct hash< std::string >
|
|
||||||
{
|
|
||||||
size_t operator()( const std::string& x ) const
|
|
||||||
{
|
|
||||||
return hash< const char* >()( x.c_str() );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace dwarf2reader {
|
namespace dwarf2reader {
|
||||||
|
|
||||||
// Read a DWARF2/3 initial length field from START, using READER, and
|
// Read a DWARF2/3 initial length field from START, using READER, and
|
||||||
|
|
|
@ -36,8 +36,8 @@
|
||||||
#ifndef COMMON_MAC_DWARF_DWARF2READER_H__
|
#ifndef COMMON_MAC_DWARF_DWARF2READER_H__
|
||||||
#define COMMON_MAC_DWARF_DWARF2READER_H__
|
#define COMMON_MAC_DWARF_DWARF2READER_H__
|
||||||
|
|
||||||
#include <ext/hash_map>
|
|
||||||
#include <list>
|
#include <list>
|
||||||
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
@ -46,7 +46,6 @@
|
||||||
#include "common/mac/dwarf/types.h"
|
#include "common/mac/dwarf/types.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace __gnu_cxx;
|
|
||||||
|
|
||||||
namespace dwarf2reader {
|
namespace dwarf2reader {
|
||||||
struct LineStateMachine;
|
struct LineStateMachine;
|
||||||
|
@ -56,7 +55,7 @@ class LineInfoHandler;
|
||||||
|
|
||||||
// This maps from a string naming a section to a pair containing a
|
// This maps from a string naming a section to a pair containing a
|
||||||
// the data for the section, and the size of the section.
|
// the data for the section, and the size of the section.
|
||||||
typedef hash_map<string, pair<const char*, uint64> > SectionMap;
|
typedef map<string, pair<const char*, uint64> > SectionMap;
|
||||||
typedef list<pair<enum DwarfAttribute, enum DwarfForm> > AttributeList;
|
typedef list<pair<enum DwarfAttribute, enum DwarfForm> > AttributeList;
|
||||||
typedef AttributeList::iterator AttributeIterator;
|
typedef AttributeList::iterator AttributeIterator;
|
||||||
typedef AttributeList::const_iterator ConstAttributeIterator;
|
typedef AttributeList::const_iterator ConstAttributeIterator;
|
||||||
|
|
|
@ -29,26 +29,17 @@
|
||||||
// This is a client for the dwarf2reader to extract function and line
|
// This is a client for the dwarf2reader to extract function and line
|
||||||
// information from the debug info.
|
// information from the debug info.
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
|
||||||
#include "common/mac/dwarf/functioninfo.h"
|
#include "common/mac/dwarf/functioninfo.h"
|
||||||
|
|
||||||
#include "common/mac/dwarf/bytereader.h"
|
#include "common/mac/dwarf/bytereader.h"
|
||||||
|
|
||||||
|
|
||||||
namespace __gnu_cxx
|
|
||||||
{
|
|
||||||
template<>
|
|
||||||
struct hash<std::string>
|
|
||||||
{
|
|
||||||
size_t operator()(const std::string& k) const;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
namespace dwarf2reader {
|
namespace dwarf2reader {
|
||||||
|
|
||||||
// Given an offset value, its form, and the base offset of the
|
// Given an offset value, its form, and the base offset of the
|
||||||
|
|
|
@ -118,11 +118,13 @@ bool PDBSourceLineWriter::PrintLines(IDiaEnumLineNumbers *lines) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
DWORD source_id;
|
DWORD dia_source_id;
|
||||||
if (FAILED(line->get_sourceFileId(&source_id))) {
|
if (FAILED(line->get_sourceFileId(&dia_source_id))) {
|
||||||
fprintf(stderr, "failed to get line source file id\n");
|
fprintf(stderr, "failed to get line source file id\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// duplicate file names are coalesced to share one ID
|
||||||
|
DWORD source_id = GetRealFileID(dia_source_id);
|
||||||
|
|
||||||
DWORD line_num;
|
DWORD line_num;
|
||||||
if (FAILED(line->get_lineNumber(&line_num))) {
|
if (FAILED(line->get_lineNumber(&line_num))) {
|
||||||
|
@ -136,17 +138,18 @@ bool PDBSourceLineWriter::PrintLines(IDiaEnumLineNumbers *lines) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PDBSourceLineWriter::PrintFunction(IDiaSymbol *function) {
|
bool PDBSourceLineWriter::PrintFunction(IDiaSymbol *function,
|
||||||
|
IDiaSymbol *block) {
|
||||||
// The function format is:
|
// The function format is:
|
||||||
// FUNC <address> <length> <param_stack_size> <function>
|
// FUNC <address> <length> <param_stack_size> <function>
|
||||||
DWORD rva;
|
DWORD rva;
|
||||||
if (FAILED(function->get_relativeVirtualAddress(&rva))) {
|
if (FAILED(block->get_relativeVirtualAddress(&rva))) {
|
||||||
fprintf(stderr, "couldn't get rva\n");
|
fprintf(stderr, "couldn't get rva\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ULONGLONG length;
|
ULONGLONG length;
|
||||||
if (FAILED(function->get_length(&length))) {
|
if (FAILED(block->get_length(&length))) {
|
||||||
fprintf(stderr, "failed to get function length\n");
|
fprintf(stderr, "failed to get function length\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -215,7 +218,16 @@ bool PDBSourceLineWriter::PrintSourceFiles() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
fwprintf(output_, L"FILE %d %s\n", file_id, file_name);
|
wstring file_name_string(file_name);
|
||||||
|
if (!FileIDIsCached(file_name_string)) {
|
||||||
|
// this is a new file name, cache it and output a FILE line.
|
||||||
|
CacheFileID(file_name_string, file_id);
|
||||||
|
fwprintf(output_, L"FILE %d %s\n", file_id, file_name);
|
||||||
|
} else {
|
||||||
|
// this file name has already been seen, just save this
|
||||||
|
// ID for later lookup.
|
||||||
|
StoreDuplicateFileID(file_name_string, file_id);
|
||||||
|
}
|
||||||
file.Release();
|
file.Release();
|
||||||
}
|
}
|
||||||
compiland.Release();
|
compiland.Release();
|
||||||
|
@ -255,7 +267,7 @@ bool PDBSourceLineWriter::PrintFunctions() {
|
||||||
// that PDBSourceLineWriter will output either a FUNC or PUBLIC line,
|
// that PDBSourceLineWriter will output either a FUNC or PUBLIC line,
|
||||||
// but not both.
|
// but not both.
|
||||||
if (tag == SymTagFunction) {
|
if (tag == SymTagFunction) {
|
||||||
if (!PrintFunction(symbol)) {
|
if (!PrintFunction(symbol, symbol)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (tag == SymTagPublicSymbol) {
|
} else if (tag == SymTagPublicSymbol) {
|
||||||
|
@ -266,6 +278,64 @@ bool PDBSourceLineWriter::PrintFunctions() {
|
||||||
symbol.Release();
|
symbol.Release();
|
||||||
} while (SUCCEEDED(symbols->Next(1, &symbol, &count)) && count == 1);
|
} while (SUCCEEDED(symbols->Next(1, &symbol, &count)) && count == 1);
|
||||||
|
|
||||||
|
// When building with PGO, the compiler can split functions into
|
||||||
|
// "hot" and "cold" blocks, and move the "cold" blocks out to separate
|
||||||
|
// pages, so the function can be noncontiguous. To find these blocks,
|
||||||
|
// we have to iterate over all the compilands, and then find blocks
|
||||||
|
// that are children of them. We can then find the lexical parents
|
||||||
|
// of those blocks and print out an extra FUNC line for blocks
|
||||||
|
// that are not contained in their parent functions.
|
||||||
|
CComPtr<IDiaSymbol> global;
|
||||||
|
if (FAILED(session_->get_globalScope(&global))) {
|
||||||
|
fprintf(stderr, "get_globalScope failed\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CComPtr<IDiaEnumSymbols> compilands;
|
||||||
|
if (FAILED(global->findChildren(SymTagCompiland, NULL,
|
||||||
|
nsNone, &compilands))) {
|
||||||
|
fprintf(stderr, "findChildren failed on the global\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CComPtr<IDiaSymbol> compiland;
|
||||||
|
while (SUCCEEDED(compilands->Next(1, &compiland, &count)) && count == 1) {
|
||||||
|
CComPtr<IDiaEnumSymbols> blocks;
|
||||||
|
if (FAILED(compiland->findChildren(SymTagBlock, NULL,
|
||||||
|
nsNone, &blocks))) {
|
||||||
|
fprintf(stderr, "findChildren failed on a compiland\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CComPtr<IDiaSymbol> block;
|
||||||
|
while (SUCCEEDED(blocks->Next(1, &block, &count)) && count == 1) {
|
||||||
|
// find this block's lexical parent function
|
||||||
|
CComPtr<IDiaSymbol> parent;
|
||||||
|
DWORD tag;
|
||||||
|
if (SUCCEEDED(block->get_lexicalParent(&parent)) &&
|
||||||
|
SUCCEEDED(parent->get_symTag(&tag)) &&
|
||||||
|
tag == SymTagFunction) {
|
||||||
|
// now get the block's offset and the function's offset and size,
|
||||||
|
// and determine if the block is outside of the function
|
||||||
|
DWORD func_rva, block_rva;
|
||||||
|
ULONGLONG func_length;
|
||||||
|
if (SUCCEEDED(block->get_relativeVirtualAddress(&block_rva)) &&
|
||||||
|
SUCCEEDED(parent->get_relativeVirtualAddress(&func_rva)) &&
|
||||||
|
SUCCEEDED(parent->get_length(&func_length))) {
|
||||||
|
if (block_rva < func_rva || block_rva > (func_rva + func_length)) {
|
||||||
|
if (!PrintFunction(parent, block)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parent.Release();
|
||||||
|
block.Release();
|
||||||
|
}
|
||||||
|
blocks.Release();
|
||||||
|
compiland.Release();
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
|
|
||||||
#include <atlcomcli.h>
|
#include <atlcomcli.h>
|
||||||
|
|
||||||
|
#include <hash_map>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
struct IDiaEnumLineNumbers;
|
struct IDiaEnumLineNumbers;
|
||||||
|
@ -44,6 +45,7 @@ struct IDiaSymbol;
|
||||||
namespace google_breakpad {
|
namespace google_breakpad {
|
||||||
|
|
||||||
using std::wstring;
|
using std::wstring;
|
||||||
|
using stdext::hash_map;
|
||||||
|
|
||||||
// A structure that carries information that identifies a pdb file.
|
// A structure that carries information that identifies a pdb file.
|
||||||
struct PDBModuleInfo {
|
struct PDBModuleInfo {
|
||||||
|
@ -111,8 +113,11 @@ class PDBSourceLineWriter {
|
||||||
bool PrintLines(IDiaEnumLineNumbers *lines);
|
bool PrintLines(IDiaEnumLineNumbers *lines);
|
||||||
|
|
||||||
// Outputs a function address and name, followed by its source line list.
|
// Outputs a function address and name, followed by its source line list.
|
||||||
|
// block can be the same object as function, or it can be a reference
|
||||||
|
// to a code block that is lexically part of this function, but
|
||||||
|
// resides at a separate address.
|
||||||
// Returns true on success.
|
// Returns true on success.
|
||||||
bool PrintFunction(IDiaSymbol *function);
|
bool PrintFunction(IDiaSymbol *function, IDiaSymbol *block);
|
||||||
|
|
||||||
// Outputs all functions as described above. Returns true on success.
|
// Outputs all functions as described above. Returns true on success.
|
||||||
bool PrintFunctions();
|
bool PrintFunctions();
|
||||||
|
@ -134,6 +139,37 @@ class PDBSourceLineWriter {
|
||||||
// its uuid and age.
|
// its uuid and age.
|
||||||
bool PrintPDBInfo();
|
bool PrintPDBInfo();
|
||||||
|
|
||||||
|
// Returns true if this filename has already been seen,
|
||||||
|
// and an ID is stored for it, or false if it has not.
|
||||||
|
bool FileIDIsCached(const wstring &file) {
|
||||||
|
return unique_files_.find(file) != unique_files_.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Cache this filename and ID for later reuse.
|
||||||
|
void CacheFileID(const wstring &file, DWORD id) {
|
||||||
|
unique_files_[file] = id;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Store this ID in the cache as a duplicate for this filename.
|
||||||
|
void StoreDuplicateFileID(const wstring &file, DWORD id) {
|
||||||
|
hash_map<wstring, DWORD>::iterator iter = unique_files_.find(file);
|
||||||
|
if (iter != unique_files_.end()) {
|
||||||
|
// map this id to the previously seen one
|
||||||
|
file_ids_[id] = iter->second;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Given a file's unique ID, return the ID that should be used to
|
||||||
|
// reference it. There may be multiple files with identical filenames
|
||||||
|
// but different unique IDs. The cache attempts to coalesce these into
|
||||||
|
// one ID per unique filename.
|
||||||
|
DWORD GetRealFileID(DWORD id) {
|
||||||
|
hash_map<DWORD, DWORD>::iterator iter = file_ids_.find(id);
|
||||||
|
if (iter == file_ids_.end())
|
||||||
|
return id;
|
||||||
|
return iter->second;
|
||||||
|
};
|
||||||
|
|
||||||
// Returns the function name for a symbol. If possible, the name is
|
// Returns the function name for a symbol. If possible, the name is
|
||||||
// undecorated. If the symbol's decorated form indicates the size of
|
// undecorated. If the symbol's decorated form indicates the size of
|
||||||
// parameters on the stack, this information is returned in stack_param_size.
|
// parameters on the stack, this information is returned in stack_param_size.
|
||||||
|
@ -153,6 +189,13 @@ class PDBSourceLineWriter {
|
||||||
// The current output file for this WriteMap invocation.
|
// The current output file for this WriteMap invocation.
|
||||||
FILE *output_;
|
FILE *output_;
|
||||||
|
|
||||||
|
// There may be many duplicate filenames with different IDs.
|
||||||
|
// This maps from the DIA "unique ID" to a single ID per unique
|
||||||
|
// filename.
|
||||||
|
hash_map<DWORD, DWORD> file_ids_;
|
||||||
|
// This maps unique filenames to file IDs.
|
||||||
|
hash_map<wstring, DWORD> unique_files_;
|
||||||
|
|
||||||
// Disallow copy ctor and operator=
|
// Disallow copy ctor and operator=
|
||||||
PDBSourceLineWriter(const PDBSourceLineWriter&);
|
PDBSourceLineWriter(const PDBSourceLineWriter&);
|
||||||
void operator=(const PDBSourceLineWriter&);
|
void operator=(const PDBSourceLineWriter&);
|
||||||
|
|
|
@ -94,8 +94,11 @@ typedef enum {
|
||||||
/* EXCEPTION_PRIV_INSTRUCTION */
|
/* EXCEPTION_PRIV_INSTRUCTION */
|
||||||
MD_EXCEPTION_CODE_WIN_STACK_OVERFLOW = 0xc00000fd,
|
MD_EXCEPTION_CODE_WIN_STACK_OVERFLOW = 0xc00000fd,
|
||||||
/* EXCEPTION_STACK_OVERFLOW */
|
/* EXCEPTION_STACK_OVERFLOW */
|
||||||
MD_EXCEPTION_CODE_WIN_POSSIBLE_DEADLOCK = 0xc0000194
|
MD_EXCEPTION_CODE_WIN_POSSIBLE_DEADLOCK = 0xc0000194,
|
||||||
/* EXCEPTION_POSSIBLE_DEADLOCK */
|
/* EXCEPTION_POSSIBLE_DEADLOCK */
|
||||||
|
MD_EXCEPTION_CODE_WIN_UNHANDLED_CPP_EXCEPTION = 0xe06d7363
|
||||||
|
/* Per http://support.microsoft.com/kb/185294,
|
||||||
|
generated by Visual C++ compiler */
|
||||||
} MDExceptionCodeWin;
|
} MDExceptionCodeWin;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -33,28 +33,14 @@
|
||||||
#ifndef GOOGLE_BREAKPAD_PROCESSOR_BASIC_SOURCE_LINE_RESOLVER_H__
|
#ifndef GOOGLE_BREAKPAD_PROCESSOR_BASIC_SOURCE_LINE_RESOLVER_H__
|
||||||
#define GOOGLE_BREAKPAD_PROCESSOR_BASIC_SOURCE_LINE_RESOLVER_H__
|
#define GOOGLE_BREAKPAD_PROCESSOR_BASIC_SOURCE_LINE_RESOLVER_H__
|
||||||
|
|
||||||
// TODO: Platforms that have no hash_map can use map, at the likely cost of
|
|
||||||
// performance.
|
|
||||||
#ifdef __SUNPRO_CC
|
|
||||||
#define BSLR_NO_HASH_MAP
|
|
||||||
#endif // __SUNPRO_CC
|
|
||||||
|
|
||||||
#ifdef BSLR_NO_HASH_MAP
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#else // BSLR_NO_HASH_MAP
|
|
||||||
#include <ext/hash_map>
|
|
||||||
#endif // BSLR_NO_HASH_MAP
|
|
||||||
|
|
||||||
#include "google_breakpad/processor/source_line_resolver_interface.h"
|
#include "google_breakpad/processor/source_line_resolver_interface.h"
|
||||||
|
|
||||||
namespace google_breakpad {
|
namespace google_breakpad {
|
||||||
|
|
||||||
using std::string;
|
using std::string;
|
||||||
#ifdef BSLR_NO_HASH_MAP
|
|
||||||
using std::map;
|
using std::map;
|
||||||
#else // BSLR_NO_HASH_MAP
|
|
||||||
using __gnu_cxx::hash_map;
|
|
||||||
#endif // BSLR_NO_HASH_MAP
|
|
||||||
|
|
||||||
class BasicSourceLineResolver : public SourceLineResolverInterface {
|
class BasicSourceLineResolver : public SourceLineResolverInterface {
|
||||||
public:
|
public:
|
||||||
|
@ -85,23 +71,13 @@ class BasicSourceLineResolver : public SourceLineResolverInterface {
|
||||||
struct Function;
|
struct Function;
|
||||||
struct PublicSymbol;
|
struct PublicSymbol;
|
||||||
struct File;
|
struct File;
|
||||||
#ifdef BSLR_NO_HASH_MAP
|
|
||||||
struct CompareString {
|
struct CompareString {
|
||||||
bool operator()(const string &s1, const string &s2) const;
|
bool operator()(const string &s1, const string &s2) const;
|
||||||
};
|
};
|
||||||
#else // BSLR_NO_HASH_MAP
|
|
||||||
struct HashString {
|
|
||||||
size_t operator()(const string &s) const;
|
|
||||||
};
|
|
||||||
#endif // BSLR_NO_HASH_MAP
|
|
||||||
class Module;
|
class Module;
|
||||||
|
|
||||||
// All of the modules we've loaded
|
// All of the modules we've loaded
|
||||||
#ifdef BSLR_NO_HASH_MAP
|
|
||||||
typedef map<string, Module*, CompareString> ModuleMap;
|
typedef map<string, Module*, CompareString> ModuleMap;
|
||||||
#else // BSLR_NO_HASH_MAP
|
|
||||||
typedef hash_map<string, Module*, HashString> ModuleMap;
|
|
||||||
#endif // BSLR_NO_HASH_MAP
|
|
||||||
ModuleMap *modules_;
|
ModuleMap *modules_;
|
||||||
|
|
||||||
// Disallow unwanted copy ctor and assignment operator
|
// Disallow unwanted copy ctor and assignment operator
|
||||||
|
|
|
@ -639,6 +639,46 @@ class MinidumpException : public MinidumpStream {
|
||||||
MinidumpContext* context_;
|
MinidumpContext* context_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// MinidumpAssertion wraps MDRawAssertionInfo, which contains information
|
||||||
|
// about an assertion that caused the minidump to be generated.
|
||||||
|
class MinidumpAssertion : public MinidumpStream {
|
||||||
|
public:
|
||||||
|
virtual ~MinidumpAssertion();
|
||||||
|
|
||||||
|
const MDRawAssertionInfo* assertion() const {
|
||||||
|
return valid_ ? &assertion_ : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
string expression() const {
|
||||||
|
return valid_ ? expression_ : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
string function() const {
|
||||||
|
return valid_ ? function_ : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
string file() const {
|
||||||
|
return valid_ ? file_ : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print a human-readable representation of the object to stdout.
|
||||||
|
void Print();
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class Minidump;
|
||||||
|
|
||||||
|
static const u_int32_t kStreamType = MD_ASSERTION_INFO_STREAM;
|
||||||
|
|
||||||
|
explicit MinidumpAssertion(Minidump* minidump);
|
||||||
|
|
||||||
|
bool Read(u_int32_t expected_size);
|
||||||
|
|
||||||
|
MDRawAssertionInfo assertion_;
|
||||||
|
string expression_;
|
||||||
|
string function_;
|
||||||
|
string file_;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// MinidumpSystemInfo wraps MDRawSystemInfo and provides information about
|
// MinidumpSystemInfo wraps MDRawSystemInfo and provides information about
|
||||||
// the system on which the minidump was generated. See also MinidumpMiscInfo.
|
// the system on which the minidump was generated. See also MinidumpMiscInfo.
|
||||||
|
@ -788,6 +828,7 @@ class Minidump {
|
||||||
MinidumpModuleList* GetModuleList();
|
MinidumpModuleList* GetModuleList();
|
||||||
MinidumpMemoryList* GetMemoryList();
|
MinidumpMemoryList* GetMemoryList();
|
||||||
MinidumpException* GetException();
|
MinidumpException* GetException();
|
||||||
|
MinidumpAssertion* GetAssertion();
|
||||||
MinidumpSystemInfo* GetSystemInfo();
|
MinidumpSystemInfo* GetSystemInfo();
|
||||||
MinidumpMiscInfo* GetMiscInfo();
|
MinidumpMiscInfo* GetMiscInfo();
|
||||||
MinidumpBreakpadInfo* GetBreakpadInfo();
|
MinidumpBreakpadInfo* GetBreakpadInfo();
|
||||||
|
|
|
@ -141,6 +141,11 @@ class MinidumpProcessor {
|
||||||
return (p != PROCESS_SYMBOL_SUPPLIER_INTERRUPTED);
|
return (p != PROCESS_SYMBOL_SUPPLIER_INTERRUPTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns a textual representation of an assertion included
|
||||||
|
// in the minidump. Returns an empty string if this information
|
||||||
|
// does not exist or cannot be determined.
|
||||||
|
static string GetAssertion(Minidump *dump);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SymbolSupplier *supplier_;
|
SymbolSupplier *supplier_;
|
||||||
SourceLineResolverInterface *resolver_;
|
SourceLineResolverInterface *resolver_;
|
||||||
|
|
|
@ -36,8 +36,9 @@
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "google_breakpad/processor/system_info.h"
|
|
||||||
#include "google_breakpad/common/breakpad_types.h"
|
#include "google_breakpad/common/breakpad_types.h"
|
||||||
|
#include "google_breakpad/processor/system_info.h"
|
||||||
|
#include "google_breakpad/processor/minidump.h"
|
||||||
|
|
||||||
namespace google_breakpad {
|
namespace google_breakpad {
|
||||||
|
|
||||||
|
@ -60,8 +61,12 @@ class ProcessState {
|
||||||
bool crashed() const { return crashed_; }
|
bool crashed() const { return crashed_; }
|
||||||
string crash_reason() const { return crash_reason_; }
|
string crash_reason() const { return crash_reason_; }
|
||||||
u_int64_t crash_address() const { return crash_address_; }
|
u_int64_t crash_address() const { return crash_address_; }
|
||||||
|
string assertion() const { return assertion_; }
|
||||||
int requesting_thread() const { return requesting_thread_; }
|
int requesting_thread() const { return requesting_thread_; }
|
||||||
const vector<CallStack*>* threads() const { return &threads_; }
|
const vector<CallStack*>* threads() const { return &threads_; }
|
||||||
|
const vector<MinidumpMemoryRegion*>* thread_memory_regions() const {
|
||||||
|
return &thread_memory_regions_;
|
||||||
|
}
|
||||||
const SystemInfo* system_info() const { return &system_info_; }
|
const SystemInfo* system_info() const { return &system_info_; }
|
||||||
const CodeModules* modules() const { return modules_; }
|
const CodeModules* modules() const { return modules_; }
|
||||||
|
|
||||||
|
@ -88,6 +93,11 @@ class ProcessState {
|
||||||
// this will be the address of the instruction that caused the fault.
|
// this will be the address of the instruction that caused the fault.
|
||||||
u_int64_t crash_address_;
|
u_int64_t crash_address_;
|
||||||
|
|
||||||
|
// If there was an assertion that was hit, a textual representation
|
||||||
|
// of that assertion, possibly including the file and line at which
|
||||||
|
// it occurred.
|
||||||
|
string assertion_;
|
||||||
|
|
||||||
// The index of the thread that requested a dump be written in the
|
// The index of the thread that requested a dump be written in the
|
||||||
// threads vector. If a dump was produced as a result of a crash, this
|
// threads vector. If a dump was produced as a result of a crash, this
|
||||||
// will point to the thread that crashed. If the dump was produced as
|
// will point to the thread that crashed. If the dump was produced as
|
||||||
|
@ -101,6 +111,7 @@ class ProcessState {
|
||||||
// Stacks for each thread (except possibly the exception handler
|
// Stacks for each thread (except possibly the exception handler
|
||||||
// thread) at the time of the crash.
|
// thread) at the time of the crash.
|
||||||
vector<CallStack*> threads_;
|
vector<CallStack*> threads_;
|
||||||
|
vector<MinidumpMemoryRegion*> thread_memory_regions_;
|
||||||
|
|
||||||
// OS and CPU information.
|
// OS and CPU information.
|
||||||
SystemInfo system_info_;
|
SystemInfo system_info_;
|
||||||
|
|
|
@ -58,7 +58,23 @@ struct StackFrameX86 : public StackFrame {
|
||||||
CONTEXT_VALID_ALL = -1
|
CONTEXT_VALID_ALL = -1
|
||||||
};
|
};
|
||||||
|
|
||||||
StackFrameX86() : context(), context_validity(CONTEXT_VALID_NONE) {}
|
// Indicates how well we trust the instruction pointer we derived
|
||||||
|
// during stack walking. Since the stack walker can resort to
|
||||||
|
// stack scanning, we can wind up with dubious frames.
|
||||||
|
// In rough order of "trust metric".
|
||||||
|
enum FrameTrust {
|
||||||
|
FRAME_TRUST_NONE, // Unknown
|
||||||
|
FRAME_TRUST_SCAN, // Scanned the stack, found this
|
||||||
|
FRAME_TRUST_CFI_SCAN, // Scanned the stack using call frame info, found this
|
||||||
|
FRAME_TRUST_FP, // Derived from frame pointer
|
||||||
|
FRAME_TRUST_CFI, // Derived from call frame info
|
||||||
|
FRAME_TRUST_CONTEXT // Given as instruction pointer in a context
|
||||||
|
};
|
||||||
|
|
||||||
|
StackFrameX86()
|
||||||
|
: context(),
|
||||||
|
context_validity(CONTEXT_VALID_NONE),
|
||||||
|
trust(FRAME_TRUST_NONE) {}
|
||||||
|
|
||||||
// Register state. This is only fully valid for the topmost frame in a
|
// Register state. This is only fully valid for the topmost frame in a
|
||||||
// stack. In other frames, the values of nonvolatile registers may be
|
// stack. In other frames, the values of nonvolatile registers may be
|
||||||
|
@ -70,6 +86,10 @@ struct StackFrameX86 : public StackFrame {
|
||||||
// the OR operator doesn't work well with enumerated types. This indicates
|
// the OR operator doesn't work well with enumerated types. This indicates
|
||||||
// which fields in context are valid.
|
// which fields in context are valid.
|
||||||
int context_validity;
|
int context_validity;
|
||||||
|
|
||||||
|
// Amount of trust the stack walker has in the instruction pointer
|
||||||
|
// of this frame.
|
||||||
|
FrameTrust trust;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct StackFramePPC : public StackFrame {
|
struct StackFramePPC : public StackFrame {
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
#define GOOGLE_BREAKPAD_PROCESSOR_STACKWALKER_H__
|
#define GOOGLE_BREAKPAD_PROCESSOR_STACKWALKER_H__
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include "google_breakpad/common/breakpad_types.h"
|
||||||
|
|
||||||
namespace google_breakpad {
|
namespace google_breakpad {
|
||||||
|
|
||||||
|
@ -95,6 +96,16 @@ class Stackwalker {
|
||||||
SymbolSupplier *supplier,
|
SymbolSupplier *supplier,
|
||||||
SourceLineResolverInterface *resolver);
|
SourceLineResolverInterface *resolver);
|
||||||
|
|
||||||
|
// This can be used to filter out potential return addresses when
|
||||||
|
// the stack walker resorts to stack scanning.
|
||||||
|
// Returns true if any of:
|
||||||
|
// * This address is within a loaded module, but we don't have symbols
|
||||||
|
// for that module.
|
||||||
|
// * This address is within a loaded module for which we have symbols,
|
||||||
|
// and falls inside a function in that module.
|
||||||
|
// Returns false otherwise.
|
||||||
|
bool InstructionAddressSeemsValid(u_int64_t address);
|
||||||
|
|
||||||
// Information about the system that produced the minidump. Subclasses
|
// Information about the system that produced the minidump. Subclasses
|
||||||
// and the SymbolSupplier may find this information useful.
|
// and the SymbolSupplier may find this information useful.
|
||||||
const SystemInfo *system_info_;
|
const SystemInfo *system_info_;
|
||||||
|
|
|
@ -51,9 +51,6 @@
|
||||||
using std::map;
|
using std::map;
|
||||||
using std::vector;
|
using std::vector;
|
||||||
using std::make_pair;
|
using std::make_pair;
|
||||||
#ifndef BSLR_NO_HASH_MAP
|
|
||||||
using __gnu_cxx::hash;
|
|
||||||
#endif // BSLR_NO_HASH_MAP
|
|
||||||
|
|
||||||
namespace google_breakpad {
|
namespace google_breakpad {
|
||||||
|
|
||||||
|
@ -125,11 +122,7 @@ class BasicSourceLineResolver::Module {
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class BasicSourceLineResolver;
|
friend class BasicSourceLineResolver;
|
||||||
#ifdef BSLR_NO_HASH_MAP
|
|
||||||
typedef map<int, string> FileMap;
|
typedef map<int, string> FileMap;
|
||||||
#else // BSLR_NO_HASH_MAP
|
|
||||||
typedef hash_map<int, string> FileMap;
|
|
||||||
#endif // BSLR_NO_HASH_MAP
|
|
||||||
|
|
||||||
// The types for stack_info_. This is equivalent to MS DIA's
|
// The types for stack_info_. This is equivalent to MS DIA's
|
||||||
// StackFrameTypeEnum. Each identifies a different type of frame
|
// StackFrameTypeEnum. Each identifies a different type of frame
|
||||||
|
@ -702,15 +695,9 @@ bool BasicSourceLineResolver::Module::ParseStackInfo(char *stack_info_line) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef BSLR_NO_HASH_MAP
|
|
||||||
bool BasicSourceLineResolver::CompareString::operator()(
|
bool BasicSourceLineResolver::CompareString::operator()(
|
||||||
const string &s1, const string &s2) const {
|
const string &s1, const string &s2) const {
|
||||||
return strcmp(s1.c_str(), s2.c_str()) < 0;
|
return strcmp(s1.c_str(), s2.c_str()) < 0;
|
||||||
}
|
}
|
||||||
#else // BSLR_NO_HASH_MAP
|
|
||||||
size_t BasicSourceLineResolver::HashString::operator()(const string &s) const {
|
|
||||||
return hash<const char*>()(s.c_str());
|
|
||||||
}
|
|
||||||
#endif // BSLR_NO_HASH_MAP
|
|
||||||
|
|
||||||
} // namespace google_breakpad
|
} // namespace google_breakpad
|
||||||
|
|
|
@ -243,6 +243,15 @@ static string* UTF16ToUTF8(const vector<u_int16_t>& in,
|
||||||
return out.release();
|
return out.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return the smaller of the number of code units in the UTF-16 string,
|
||||||
|
// not including the terminating null word, or maxlen.
|
||||||
|
static size_t UTF16codeunits(const u_int16_t *string, size_t maxlen) {
|
||||||
|
size_t count = 0;
|
||||||
|
while (count < maxlen && string[count] != 0)
|
||||||
|
count++;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// MinidumpObject
|
// MinidumpObject
|
||||||
|
@ -608,8 +617,9 @@ bool MinidumpContext::Read(u_int32_t expected_size) {
|
||||||
}
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
// Unknown context type
|
// Unknown context type - Don't log as an error yet. Let the
|
||||||
BPLOG(ERROR) << "MinidumpContext unknown context type " <<
|
// caller work that out.
|
||||||
|
BPLOG(INFO) << "MinidumpContext unknown context type " <<
|
||||||
HexString(cpu_type);
|
HexString(cpu_type);
|
||||||
return false;
|
return false;
|
||||||
break;
|
break;
|
||||||
|
@ -1302,7 +1312,7 @@ void MinidumpThread::Print() {
|
||||||
//
|
//
|
||||||
|
|
||||||
|
|
||||||
u_int32_t MinidumpThreadList::max_threads_ = 256;
|
u_int32_t MinidumpThreadList::max_threads_ = 4096;
|
||||||
|
|
||||||
|
|
||||||
MinidumpThreadList::MinidumpThreadList(Minidump* minidump)
|
MinidumpThreadList::MinidumpThreadList(Minidump* minidump)
|
||||||
|
@ -1464,8 +1474,8 @@ void MinidumpThreadList::Print() {
|
||||||
//
|
//
|
||||||
|
|
||||||
|
|
||||||
u_int32_t MinidumpModule::max_cv_bytes_ = 1024;
|
u_int32_t MinidumpModule::max_cv_bytes_ = 32768;
|
||||||
u_int32_t MinidumpModule::max_misc_bytes_ = 1024;
|
u_int32_t MinidumpModule::max_misc_bytes_ = 32768;
|
||||||
|
|
||||||
|
|
||||||
MinidumpModule::MinidumpModule(Minidump* minidump)
|
MinidumpModule::MinidumpModule(Minidump* minidump)
|
||||||
|
@ -2424,7 +2434,7 @@ void MinidumpModuleList::Print() {
|
||||||
//
|
//
|
||||||
|
|
||||||
|
|
||||||
u_int32_t MinidumpMemoryList::max_regions_ = 256;
|
u_int32_t MinidumpMemoryList::max_regions_ = 4096;
|
||||||
|
|
||||||
|
|
||||||
MinidumpMemoryList::MinidumpMemoryList(Minidump* minidump)
|
MinidumpMemoryList::MinidumpMemoryList(Minidump* minidump)
|
||||||
|
@ -2713,8 +2723,10 @@ MinidumpContext* MinidumpException::GetContext() {
|
||||||
|
|
||||||
scoped_ptr<MinidumpContext> context(new MinidumpContext(minidump_));
|
scoped_ptr<MinidumpContext> context(new MinidumpContext(minidump_));
|
||||||
|
|
||||||
|
// Don't log as an error if we can still fall back on th thread's context
|
||||||
|
// (which must be possible if we got his far.)
|
||||||
if (!context->Read(exception_.thread_context.data_size)) {
|
if (!context->Read(exception_.thread_context.data_size)) {
|
||||||
BPLOG(ERROR) << "MinidumpException cannot read context";
|
BPLOG(INFO) << "MinidumpException cannot read context";
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2765,6 +2777,109 @@ void MinidumpException::Print() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// MinidumpAssertion
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
MinidumpAssertion::MinidumpAssertion(Minidump* minidump)
|
||||||
|
: MinidumpStream(minidump),
|
||||||
|
assertion_(),
|
||||||
|
expression_(),
|
||||||
|
function_(),
|
||||||
|
file_() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
MinidumpAssertion::~MinidumpAssertion() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool MinidumpAssertion::Read(u_int32_t expected_size) {
|
||||||
|
// Invalidate cached data.
|
||||||
|
valid_ = false;
|
||||||
|
|
||||||
|
if (expected_size != sizeof(assertion_)) {
|
||||||
|
BPLOG(ERROR) << "MinidumpAssertion size mismatch, " << expected_size <<
|
||||||
|
" != " << sizeof(assertion_);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!minidump_->ReadBytes(&assertion_, sizeof(assertion_))) {
|
||||||
|
BPLOG(ERROR) << "MinidumpAssertion cannot read assertion";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each of {expression, function, file} is a UTF-16 string,
|
||||||
|
// we'll convert them to UTF-8 for ease of use.
|
||||||
|
// expression
|
||||||
|
// Since we don't have an explicit byte length for each string,
|
||||||
|
// we use UTF16codeunits to calculate word length, then derive byte
|
||||||
|
// length from that.
|
||||||
|
u_int32_t word_length = UTF16codeunits(assertion_.expression,
|
||||||
|
sizeof(assertion_.expression));
|
||||||
|
if (word_length > 0) {
|
||||||
|
u_int32_t byte_length = word_length * 2;
|
||||||
|
vector<u_int16_t> expression_utf16(word_length);
|
||||||
|
memcpy(&expression_utf16[0], &assertion_.expression[0], byte_length);
|
||||||
|
|
||||||
|
scoped_ptr<string> new_expression(UTF16ToUTF8(expression_utf16,
|
||||||
|
minidump_->swap()));
|
||||||
|
expression_ = *new_expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
// assertion
|
||||||
|
word_length = UTF16codeunits(assertion_.function,
|
||||||
|
sizeof(assertion_.function));
|
||||||
|
if (word_length) {
|
||||||
|
u_int32_t byte_length = word_length * 2;
|
||||||
|
vector<u_int16_t> function_utf16(word_length);
|
||||||
|
memcpy(&function_utf16[0], &assertion_.function[0], byte_length);
|
||||||
|
scoped_ptr<string> new_function(UTF16ToUTF8(function_utf16,
|
||||||
|
minidump_->swap()));
|
||||||
|
function_ = *new_function;
|
||||||
|
}
|
||||||
|
|
||||||
|
// file
|
||||||
|
word_length = UTF16codeunits(assertion_.file,
|
||||||
|
sizeof(assertion_.file));
|
||||||
|
if (word_length > 0) {
|
||||||
|
u_int32_t byte_length = word_length * 2;
|
||||||
|
vector<u_int16_t> file_utf16(word_length);
|
||||||
|
memcpy(&file_utf16[0], &assertion_.file[0], byte_length);
|
||||||
|
scoped_ptr<string> new_file(UTF16ToUTF8(file_utf16,
|
||||||
|
minidump_->swap()));
|
||||||
|
file_ = *new_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minidump_->swap()) {
|
||||||
|
Swap(&assertion_.line);
|
||||||
|
Swap(&assertion_.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
valid_ = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MinidumpAssertion::Print() {
|
||||||
|
if (!valid_) {
|
||||||
|
BPLOG(ERROR) << "MinidumpAssertion cannot print invalid data";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("MDAssertion\n");
|
||||||
|
printf(" expression = %s\n",
|
||||||
|
expression_.c_str());
|
||||||
|
printf(" function = %s\n",
|
||||||
|
function_.c_str());
|
||||||
|
printf(" file = %s\n",
|
||||||
|
file_.c_str());
|
||||||
|
printf(" line = %u\n",
|
||||||
|
assertion_.line);
|
||||||
|
printf(" type = %u\n",
|
||||||
|
assertion_.type);
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// MinidumpSystemInfo
|
// MinidumpSystemInfo
|
||||||
|
@ -3412,6 +3527,11 @@ MinidumpException* Minidump::GetException() {
|
||||||
return GetStream(&exception);
|
return GetStream(&exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MinidumpAssertion* Minidump::GetAssertion() {
|
||||||
|
MinidumpAssertion* assertion;
|
||||||
|
return GetStream(&assertion);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
MinidumpSystemInfo* Minidump::GetSystemInfo() {
|
MinidumpSystemInfo* Minidump::GetSystemInfo() {
|
||||||
MinidumpSystemInfo* system_info;
|
MinidumpSystemInfo* system_info;
|
||||||
|
|
|
@ -44,6 +44,7 @@ using google_breakpad::MinidumpThreadList;
|
||||||
using google_breakpad::MinidumpModuleList;
|
using google_breakpad::MinidumpModuleList;
|
||||||
using google_breakpad::MinidumpMemoryList;
|
using google_breakpad::MinidumpMemoryList;
|
||||||
using google_breakpad::MinidumpException;
|
using google_breakpad::MinidumpException;
|
||||||
|
using google_breakpad::MinidumpAssertion;
|
||||||
using google_breakpad::MinidumpSystemInfo;
|
using google_breakpad::MinidumpSystemInfo;
|
||||||
using google_breakpad::MinidumpMiscInfo;
|
using google_breakpad::MinidumpMiscInfo;
|
||||||
using google_breakpad::MinidumpBreakpadInfo;
|
using google_breakpad::MinidumpBreakpadInfo;
|
||||||
|
@ -89,6 +90,13 @@ static bool PrintMinidumpDump(const char *minidump_file) {
|
||||||
exception->Print();
|
exception->Print();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MinidumpAssertion *assertion = minidump.GetAssertion();
|
||||||
|
if (!assertion) {
|
||||||
|
BPLOG(INFO) << "minidump.GetAssertion() failed";
|
||||||
|
} else {
|
||||||
|
assertion->Print();
|
||||||
|
}
|
||||||
|
|
||||||
MinidumpSystemInfo *system_info = minidump.GetSystemInfo();
|
MinidumpSystemInfo *system_info = minidump.GetSystemInfo();
|
||||||
if (!system_info) {
|
if (!system_info) {
|
||||||
++errors;
|
++errors;
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
#include "google_breakpad/processor/minidump_processor.h"
|
#include "google_breakpad/processor/minidump_processor.h"
|
||||||
#include "google_breakpad/processor/call_stack.h"
|
#include "google_breakpad/processor/call_stack.h"
|
||||||
|
@ -85,6 +86,9 @@ ProcessResult MinidumpProcessor::Process(
|
||||||
dump, &process_state->crash_address_);
|
dump, &process_state->crash_address_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This will just return an empty string if it doesn't exist.
|
||||||
|
process_state->assertion_ = GetAssertion(dump);
|
||||||
|
|
||||||
MinidumpModuleList *module_list = dump->GetModuleList();
|
MinidumpModuleList *module_list = dump->GetModuleList();
|
||||||
|
|
||||||
// Put a copy of the module list into ProcessState object. This is not
|
// Put a copy of the module list into ProcessState object. This is not
|
||||||
|
@ -168,8 +172,10 @@ ProcessResult MinidumpProcessor::Process(
|
||||||
// of the thread's own context. For the crashed thread, the thread's
|
// of the thread's own context. For the crashed thread, the thread's
|
||||||
// own context is the state inside the exception handler. Using it
|
// own context is the state inside the exception handler. Using it
|
||||||
// would not result in the expected stack trace from the time of the
|
// would not result in the expected stack trace from the time of the
|
||||||
// crash.
|
// crash. If the exception context is invalid, however, we fall back
|
||||||
context = exception->GetContext();
|
// on the thread context.
|
||||||
|
MinidumpContext *ctx = exception->GetContext();
|
||||||
|
context = ctx ? ctx : thread->GetContext();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,6 +212,7 @@ ProcessResult MinidumpProcessor::Process(
|
||||||
interrupted = true;
|
interrupted = true;
|
||||||
}
|
}
|
||||||
process_state->threads_.push_back(stack.release());
|
process_state->threads_.push_back(stack.release());
|
||||||
|
process_state->thread_memory_regions_.push_back(thread_memory);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (interrupted) {
|
if (interrupted) {
|
||||||
|
@ -752,6 +759,9 @@ string MinidumpProcessor::GetCrashReason(Minidump *dump, u_int64_t *address) {
|
||||||
case MD_EXCEPTION_CODE_WIN_POSSIBLE_DEADLOCK:
|
case MD_EXCEPTION_CODE_WIN_POSSIBLE_DEADLOCK:
|
||||||
reason = "EXCEPTION_POSSIBLE_DEADLOCK";
|
reason = "EXCEPTION_POSSIBLE_DEADLOCK";
|
||||||
break;
|
break;
|
||||||
|
case MD_EXCEPTION_CODE_WIN_UNHANDLED_CPP_EXCEPTION:
|
||||||
|
reason = "Unhandled C++ Exception";
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
BPLOG(INFO) << "Unknown exception reason " << reason;
|
BPLOG(INFO) << "Unknown exception reason " << reason;
|
||||||
break;
|
break;
|
||||||
|
@ -999,4 +1009,57 @@ string MinidumpProcessor::GetCrashReason(Minidump *dump, u_int64_t *address) {
|
||||||
return reason;
|
return reason;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
string MinidumpProcessor::GetAssertion(Minidump *dump)
|
||||||
|
{
|
||||||
|
MinidumpAssertion *assertion = dump->GetAssertion();
|
||||||
|
if (!assertion)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
const MDRawAssertionInfo *raw_assertion = assertion->assertion();
|
||||||
|
if (!raw_assertion)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
string assertion_string;
|
||||||
|
switch (raw_assertion->type) {
|
||||||
|
case MD_ASSERTION_INFO_TYPE_INVALID_PARAMETER:
|
||||||
|
assertion_string = "Invalid parameter passed to library function";
|
||||||
|
break;
|
||||||
|
case MD_ASSERTION_INFO_TYPE_PURE_VIRTUAL_CALL:
|
||||||
|
assertion_string = "Pure virtual function called";
|
||||||
|
break;
|
||||||
|
default: {
|
||||||
|
char assertion_type[32];
|
||||||
|
sprintf(assertion_type, "0x%08x", raw_assertion->type);
|
||||||
|
assertion_string = "Unknown assertion type ";
|
||||||
|
assertion_string += assertion_type;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string expression = assertion->expression();
|
||||||
|
if (!expression.empty()) {
|
||||||
|
assertion_string.append(" " + expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
string function = assertion->function();
|
||||||
|
if (!function.empty()) {
|
||||||
|
assertion_string.append(" in function " + function);
|
||||||
|
}
|
||||||
|
|
||||||
|
string file = assertion->file();
|
||||||
|
if (!file.empty()) {
|
||||||
|
assertion_string.append(", in file " + file);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (raw_assertion->line != 0) {
|
||||||
|
char assertion_line[32];
|
||||||
|
sprintf(assertion_line, "%u", raw_assertion->line);
|
||||||
|
assertion_string.append(" at line ");
|
||||||
|
assertion_string.append(assertion_line);
|
||||||
|
}
|
||||||
|
|
||||||
|
return assertion_string;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace google_breakpad
|
} // namespace google_breakpad
|
||||||
|
|
|
@ -160,6 +160,28 @@ static void PrintStack(const CallStack *stack, const string &cpu) {
|
||||||
sequence = PrintRegister("edx", frame_x86->context.edx, sequence);
|
sequence = PrintRegister("edx", frame_x86->context.edx, sequence);
|
||||||
sequence = PrintRegister("efl", frame_x86->context.eflags, sequence);
|
sequence = PrintRegister("efl", frame_x86->context.eflags, sequence);
|
||||||
}
|
}
|
||||||
|
const char *trust_name;
|
||||||
|
switch (frame_x86->trust) {
|
||||||
|
case StackFrameX86::FRAME_TRUST_NONE:
|
||||||
|
trust_name = "unknown";
|
||||||
|
break;
|
||||||
|
case StackFrameX86::FRAME_TRUST_CONTEXT:
|
||||||
|
trust_name = "given as instruction pointer in context";
|
||||||
|
break;
|
||||||
|
case StackFrameX86::FRAME_TRUST_CFI:
|
||||||
|
trust_name = "call frame info";
|
||||||
|
break;
|
||||||
|
case StackFrameX86::FRAME_TRUST_CFI_SCAN:
|
||||||
|
trust_name = "call frame info with scanning";
|
||||||
|
break;
|
||||||
|
case StackFrameX86::FRAME_TRUST_FP:
|
||||||
|
trust_name = "previous frame's frame pointer";
|
||||||
|
break;
|
||||||
|
case StackFrameX86::FRAME_TRUST_SCAN:
|
||||||
|
trust_name = "stack scanning";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
printf("\n Found by: %s", trust_name);
|
||||||
} else if (cpu == "ppc") {
|
} else if (cpu == "ppc") {
|
||||||
const StackFramePPC *frame_ppc =
|
const StackFramePPC *frame_ppc =
|
||||||
reinterpret_cast<const StackFramePPC*>(frame);
|
reinterpret_cast<const StackFramePPC*>(frame);
|
||||||
|
@ -339,6 +361,11 @@ static void PrintProcessState(const ProcessState& process_state) {
|
||||||
printf("No crash\n");
|
printf("No crash\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string assertion = process_state.assertion();
|
||||||
|
if (!assertion.empty()) {
|
||||||
|
printf("Assertion: %s\n", assertion.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
// If the thread that requested the dump is known, print it first.
|
// If the thread that requested the dump is known, print it first.
|
||||||
int requesting_thread = process_state.requesting_thread();
|
int requesting_thread = process_state.requesting_thread();
|
||||||
if (requesting_thread != -1) {
|
if (requesting_thread != -1) {
|
||||||
|
@ -391,7 +418,15 @@ static void PrintProcessStateMachineReadable(const ProcessState& process_state)
|
||||||
StripSeparator(process_state.crash_reason()).c_str(),
|
StripSeparator(process_state.crash_reason()).c_str(),
|
||||||
kOutputSeparator, process_state.crash_address(), kOutputSeparator);
|
kOutputSeparator, process_state.crash_address(), kOutputSeparator);
|
||||||
} else {
|
} else {
|
||||||
printf("No crash%c%c", kOutputSeparator, kOutputSeparator);
|
// print assertion info, if available, in place of crash reason,
|
||||||
|
// instead of the unhelpful "No crash"
|
||||||
|
string assertion = process_state.assertion();
|
||||||
|
if (!assertion.empty()) {
|
||||||
|
printf("%s%c%c", StripSeparator(assertion).c_str(),
|
||||||
|
kOutputSeparator, kOutputSeparator);
|
||||||
|
} else {
|
||||||
|
printf("No crash%c%c", kOutputSeparator, kOutputSeparator);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requesting_thread != -1) {
|
if (requesting_thread != -1) {
|
||||||
|
|
|
@ -27,6 +27,8 @@
|
||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
#include "processor/pathname_stripper.h"
|
#include "processor/pathname_stripper.h"
|
||||||
#include "processor/logging.h"
|
#include "processor/logging.h"
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@
|
||||||
#define PROCESSOR_POSTFIX_EVALUATOR_INL_H__
|
#define PROCESSOR_POSTFIX_EVALUATOR_INL_H__
|
||||||
|
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
#include "processor/postfix_evaluator.h"
|
#include "processor/postfix_evaluator.h"
|
||||||
|
|
|
@ -48,6 +48,7 @@ void ProcessState::Clear() {
|
||||||
crashed_ = false;
|
crashed_ = false;
|
||||||
crash_reason_.clear();
|
crash_reason_.clear();
|
||||||
crash_address_ = 0;
|
crash_address_ = 0;
|
||||||
|
assertion_.clear();
|
||||||
requesting_thread_ = -1;
|
requesting_thread_ = -1;
|
||||||
for (vector<CallStack *>::const_iterator iterator = threads_.begin();
|
for (vector<CallStack *>::const_iterator iterator = threads_.begin();
|
||||||
iterator != threads_.end();
|
iterator != threads_.end();
|
||||||
|
|
|
@ -189,5 +189,39 @@ Stackwalker* Stackwalker::StackwalkerForCPU(
|
||||||
return cpu_stackwalker;
|
return cpu_stackwalker;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Stackwalker::InstructionAddressSeemsValid(u_int64_t address) {
|
||||||
|
const CodeModule *module = modules_->GetModuleForAddress(address);
|
||||||
|
if (!module) {
|
||||||
|
// not inside any loaded module
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resolver_ || !supplier_) {
|
||||||
|
// we don't have a resolver and or symbol supplier,
|
||||||
|
// but we're inside a known module
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resolver_->HasModule(module->code_file())) {
|
||||||
|
string symbol_data, symbol_file;
|
||||||
|
SymbolSupplier::SymbolResult symbol_result =
|
||||||
|
supplier_->GetSymbolFile(module, system_info_,
|
||||||
|
&symbol_file, &symbol_data);
|
||||||
|
|
||||||
|
if (symbol_result != SymbolSupplier::FOUND ||
|
||||||
|
!resolver_->LoadModuleUsingMapBuffer(module->code_file(),
|
||||||
|
symbol_data)) {
|
||||||
|
// we don't have symbols, but we're inside a loaded module
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StackFrame frame;
|
||||||
|
frame.module = module;
|
||||||
|
frame.instruction = address;
|
||||||
|
resolver_->FillSourceLineInfo(&frame);
|
||||||
|
// we have symbols, so return true if inside a function
|
||||||
|
return !frame.function_name.empty();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace google_breakpad
|
} // namespace google_breakpad
|
||||||
|
|
|
@ -79,6 +79,7 @@ StackFrame* StackwalkerX86::GetContextFrame() {
|
||||||
// straight out of the CPU context structure.
|
// straight out of the CPU context structure.
|
||||||
frame->context = *context_;
|
frame->context = *context_;
|
||||||
frame->context_validity = StackFrameX86::CONTEXT_VALID_ALL;
|
frame->context_validity = StackFrameX86::CONTEXT_VALID_ALL;
|
||||||
|
frame->trust = StackFrameX86::FRAME_TRUST_CONTEXT;
|
||||||
frame->instruction = frame->context.eip;
|
frame->instruction = frame->context.eip;
|
||||||
|
|
||||||
return frame;
|
return frame;
|
||||||
|
@ -92,7 +93,7 @@ StackFrame* StackwalkerX86::GetCallerFrame(
|
||||||
BPLOG(ERROR) << "Can't get caller frame without memory or stack";
|
BPLOG(ERROR) << "Can't get caller frame without memory or stack";
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
StackFrameX86::FrameTrust trust = StackFrameX86::FRAME_TRUST_NONE;
|
||||||
StackFrameX86 *last_frame = static_cast<StackFrameX86*>(
|
StackFrameX86 *last_frame = static_cast<StackFrameX86*>(
|
||||||
stack->frames()->back());
|
stack->frames()->back());
|
||||||
StackFrameInfo *last_frame_info = stack_frame_info.back().get();
|
StackFrameInfo *last_frame_info = stack_frame_info.back().get();
|
||||||
|
@ -183,6 +184,7 @@ StackFrame* StackwalkerX86::GetCallerFrame(
|
||||||
if (last_frame_info && last_frame_info->valid == StackFrameInfo::VALID_ALL) {
|
if (last_frame_info && last_frame_info->valid == StackFrameInfo::VALID_ALL) {
|
||||||
// FPO data available.
|
// FPO data available.
|
||||||
traditional_frame = false;
|
traditional_frame = false;
|
||||||
|
trust = StackFrameX86::FRAME_TRUST_CFI;
|
||||||
if (!last_frame_info->program_string.empty()) {
|
if (!last_frame_info->program_string.empty()) {
|
||||||
// The FPO data has its own program string, which will tell us how to
|
// The FPO data has its own program string, which will tell us how to
|
||||||
// get to the caller frame, and may even fill in the values of
|
// get to the caller frame, and may even fill in the values of
|
||||||
|
@ -280,6 +282,7 @@ StackFrame* StackwalkerX86::GetCallerFrame(
|
||||||
// %eip_new = *(%ebp_old + 4)
|
// %eip_new = *(%ebp_old + 4)
|
||||||
// %esp_new = %ebp_old + 8
|
// %esp_new = %ebp_old + 8
|
||||||
// %ebp_new = *(%ebp_old)
|
// %ebp_new = *(%ebp_old)
|
||||||
|
trust = StackFrameX86::FRAME_TRUST_FP;
|
||||||
program_string = "$eip $ebp 4 + ^ = "
|
program_string = "$eip $ebp 4 + ^ = "
|
||||||
"$esp $ebp 8 + = "
|
"$esp $ebp 8 + = "
|
||||||
"$ebp $ebp ^ =";
|
"$ebp $ebp ^ =";
|
||||||
|
@ -293,7 +296,26 @@ StackFrame* StackwalkerX86::GetCallerFrame(
|
||||||
if (!evaluator.Evaluate(program_string, &dictionary_validity) ||
|
if (!evaluator.Evaluate(program_string, &dictionary_validity) ||
|
||||||
dictionary_validity.find("$eip") == dictionary_validity.end() ||
|
dictionary_validity.find("$eip") == dictionary_validity.end() ||
|
||||||
dictionary_validity.find("$esp") == dictionary_validity.end()) {
|
dictionary_validity.find("$esp") == dictionary_validity.end()) {
|
||||||
return NULL;
|
// Program string evaluation failed. It may be that %eip is not somewhere
|
||||||
|
// with stack frame info, and %ebp is pointing to non-stack memory, so
|
||||||
|
// our evaluation couldn't succeed. We'll scan the stack for a return
|
||||||
|
// address. This can happen if the stack is in a module for which
|
||||||
|
// we don't have symbols, and that module is compiled without a
|
||||||
|
// frame pointer.
|
||||||
|
u_int32_t location_start = last_frame->context.esp;
|
||||||
|
u_int32_t location, eip;
|
||||||
|
if (!ScanForReturnAddress(location_start, location, eip)) {
|
||||||
|
// if we can't find an instruction pointer even with stack scanning,
|
||||||
|
// give up.
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This seems like a reasonable return address. Since program string
|
||||||
|
// evaluation failed, use it and set %esp to the location above the
|
||||||
|
// one where the return address was found.
|
||||||
|
dictionary["$eip"] = eip;
|
||||||
|
dictionary["$esp"] = location + 4;
|
||||||
|
trust = StackFrameX86::FRAME_TRUST_SCAN;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this stack frame did not use %ebp in a traditional way, locating the
|
// If this stack frame did not use %ebp in a traditional way, locating the
|
||||||
|
@ -321,33 +343,18 @@ StackFrame* StackwalkerX86::GetCallerFrame(
|
||||||
|
|
||||||
u_int32_t eip = dictionary["$eip"];
|
u_int32_t eip = dictionary["$eip"];
|
||||||
if (modules_ && !modules_->GetModuleForAddress(eip)) {
|
if (modules_ && !modules_->GetModuleForAddress(eip)) {
|
||||||
const int kRASearchWords = 15;
|
|
||||||
|
|
||||||
// The instruction pointer at .raSearchStart was invalid, so start
|
// The instruction pointer at .raSearchStart was invalid, so start
|
||||||
// looking one 32-bit word above that location.
|
// looking one 32-bit word above that location.
|
||||||
u_int32_t location_start = dictionary[".raSearchStart"] + 4;
|
u_int32_t location_start = dictionary[".raSearchStart"] + 4;
|
||||||
|
u_int32_t location;
|
||||||
for (u_int32_t location = location_start;
|
if (ScanForReturnAddress(location_start, location, eip)) {
|
||||||
location <= location_start + kRASearchWords * 4;
|
// This is a better return address that what program string
|
||||||
location += 4) {
|
// evaluation found. Use it, and set %esp to the location above the
|
||||||
if (!memory_->GetMemoryAtAddress(location, &eip))
|
// one where the return address was found.
|
||||||
break;
|
dictionary["$eip"] = eip;
|
||||||
|
dictionary["$esp"] = location + 4;
|
||||||
if (modules_->GetModuleForAddress(eip)) {
|
offset = location - location_start;
|
||||||
// This is a better return address that what program string
|
trust = StackFrameX86::FRAME_TRUST_CFI_SCAN;
|
||||||
// evaluation found. Use it, and set %esp to the location above the
|
|
||||||
// one where the return address was found.
|
|
||||||
//
|
|
||||||
// TODO(mmentovai): The return-address check can be made even
|
|
||||||
// stronger in modules for which debugging data is available. In
|
|
||||||
// that case, it's possible to check that the candidate return
|
|
||||||
// address is inside a known function.
|
|
||||||
|
|
||||||
dictionary["$eip"] = eip;
|
|
||||||
dictionary["$esp"] = location + 4;
|
|
||||||
offset = location - location_start;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -392,6 +399,7 @@ StackFrame* StackwalkerX86::GetCallerFrame(
|
||||||
// and fill it in.
|
// and fill it in.
|
||||||
StackFrameX86 *frame = new StackFrameX86();
|
StackFrameX86 *frame = new StackFrameX86();
|
||||||
|
|
||||||
|
frame->trust = trust;
|
||||||
frame->context = last_frame->context;
|
frame->context = last_frame->context;
|
||||||
frame->context.eip = dictionary["$eip"];
|
frame->context.eip = dictionary["$eip"];
|
||||||
frame->context.esp = dictionary["$esp"];
|
frame->context.esp = dictionary["$esp"];
|
||||||
|
@ -428,5 +436,27 @@ StackFrame* StackwalkerX86::GetCallerFrame(
|
||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool StackwalkerX86::ScanForReturnAddress(u_int32_t location_start,
|
||||||
|
u_int32_t &location_found,
|
||||||
|
u_int32_t &eip_found) {
|
||||||
|
const int kRASearchWords = 15;
|
||||||
|
for (u_int32_t location = location_start;
|
||||||
|
location <= location_start + kRASearchWords * 4;
|
||||||
|
location += 4) {
|
||||||
|
u_int32_t eip;
|
||||||
|
if (!memory_->GetMemoryAtAddress(location, &eip))
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (modules_ && modules_->GetModuleForAddress(eip) &&
|
||||||
|
InstructionAddressSeemsValid(eip)) {
|
||||||
|
|
||||||
|
eip_found = eip;
|
||||||
|
location_found = location;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// nothing found
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace google_breakpad
|
} // namespace google_breakpad
|
||||||
|
|
|
@ -70,6 +70,19 @@ class StackwalkerX86 : public Stackwalker {
|
||||||
const CallStack *stack,
|
const CallStack *stack,
|
||||||
const vector< linked_ptr<StackFrameInfo> > &stack_frame_info);
|
const vector< linked_ptr<StackFrameInfo> > &stack_frame_info);
|
||||||
|
|
||||||
|
// Scan the stack starting at location_start, looking for an address
|
||||||
|
// that looks like a valid instruction pointer. Addresses must
|
||||||
|
// 1) be contained in the current stack memory
|
||||||
|
// 2) pass the checks in Stackwalker::InstructionAddressSeemsValid
|
||||||
|
//
|
||||||
|
// Returns true if a valid-looking instruction pointer was found.
|
||||||
|
// When returning true, sets location_found to the address at which
|
||||||
|
// the value was found, and eip_found to the value contained at that
|
||||||
|
// location in memory.
|
||||||
|
bool ScanForReturnAddress(u_int32_t location_start,
|
||||||
|
u_int32_t &location_found,
|
||||||
|
u_int32_t &eip_found);
|
||||||
|
|
||||||
// Stores the CPU context corresponding to the innermost stack frame to
|
// Stores the CPU context corresponding to the innermost stack frame to
|
||||||
// be returned by GetContextFrame.
|
// be returned by GetContextFrame.
|
||||||
const MDRawContextX86 *context_;
|
const MDRawContextX86 *context_;
|
||||||
|
|
|
@ -12,12 +12,16 @@ Thread 0 (crashed)
|
||||||
eip = 0x0040429e esp = 0x0012fe84 ebp = 0x0012fe88 ebx = 0x7c80abc1
|
eip = 0x0040429e esp = 0x0012fe84 ebp = 0x0012fe88 ebx = 0x7c80abc1
|
||||||
esi = 0x00000002 edi = 0x00000a28 eax = 0x00000045 ecx = 0x0012fe94
|
esi = 0x00000002 edi = 0x00000a28 eax = 0x00000045 ecx = 0x0012fe94
|
||||||
edx = 0x0042bc58 efl = 0x00010246
|
edx = 0x0042bc58 efl = 0x00010246
|
||||||
|
Found by: given as instruction pointer in context
|
||||||
1 test_app.exe!main [test_app.cc : 65 + 0x4]
|
1 test_app.exe!main [test_app.cc : 65 + 0x4]
|
||||||
eip = 0x00404200 esp = 0x0012fe90 ebp = 0x0012ff70
|
eip = 0x00404200 esp = 0x0012fe90 ebp = 0x0012ff70
|
||||||
|
Found by: call frame info
|
||||||
2 test_app.exe!__tmainCRTStartup [crt0.c : 327 + 0x11]
|
2 test_app.exe!__tmainCRTStartup [crt0.c : 327 + 0x11]
|
||||||
eip = 0x004053ec esp = 0x0012ff78 ebp = 0x0012ffc0
|
eip = 0x004053ec esp = 0x0012ff78 ebp = 0x0012ffc0
|
||||||
|
Found by: call frame info
|
||||||
3 kernel32.dll!BaseProcessStart + 0x22
|
3 kernel32.dll!BaseProcessStart + 0x22
|
||||||
eip = 0x7c816fd7 esp = 0x0012ffc8 ebp = 0x0012fff0
|
eip = 0x7c816fd7 esp = 0x0012ffc8 ebp = 0x0012fff0
|
||||||
|
Found by: call frame info
|
||||||
|
|
||||||
Loaded modules:
|
Loaded modules:
|
||||||
0x00400000 - 0x0042cfff test_app.exe ??? (main)
|
0x00400000 - 0x0042cfff test_app.exe ??? (main)
|
||||||
|
|
533
toolkit/crashreporter/google-breakpad/src/third_party/linux/include/gflags/gflags.h
поставляемый
Normal file
533
toolkit/crashreporter/google-breakpad/src/third_party/linux/include/gflags/gflags.h
поставляемый
Normal file
|
@ -0,0 +1,533 @@
|
||||||
|
// Copyright (c) 2006, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
// ---
|
||||||
|
// Author: Ray Sidney
|
||||||
|
// Revamped and reorganized by Craig Silverstein
|
||||||
|
//
|
||||||
|
// This is the file that should be included by any file which declares
|
||||||
|
// or defines a command line flag or wants to parse command line flags
|
||||||
|
// or print a program usage message (which will include information about
|
||||||
|
// flags). Executive summary, in the form of an example foo.cc file:
|
||||||
|
//
|
||||||
|
// #include "foo.h" // foo.h has a line "DECLARE_int32(start);"
|
||||||
|
//
|
||||||
|
// DEFINE_int32(end, 1000, "The last record to read");
|
||||||
|
// DECLARE_bool(verbose); // some other file has a DEFINE_bool(verbose, ...)
|
||||||
|
//
|
||||||
|
// void MyFunc() {
|
||||||
|
// if (FLAGS_verbose) printf("Records %d-%d\n", FLAGS_start, FLAGS_end);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Then, at the command-line:
|
||||||
|
// ./foo --noverbose --start=5 --end=100
|
||||||
|
//
|
||||||
|
// For more details, see
|
||||||
|
// doc/gflags.html
|
||||||
|
//
|
||||||
|
// --- A note about thread-safety:
|
||||||
|
//
|
||||||
|
// We describe many functions in this routine as being thread-hostile,
|
||||||
|
// thread-compatible, or thread-safe. Here are the meanings we use:
|
||||||
|
//
|
||||||
|
// thread-safe: it is safe for multiple threads to call this routine
|
||||||
|
// (or, when referring to a class, methods of this class)
|
||||||
|
// concurrently.
|
||||||
|
// thread-hostile: it is not safe for multiple threads to call this
|
||||||
|
// routine (or methods of this class) concurrently. In gflags,
|
||||||
|
// most thread-hostile routines are intended to be called early in,
|
||||||
|
// or even before, main() -- that is, before threads are spawned.
|
||||||
|
// thread-compatible: it is safe for multiple threads to read from
|
||||||
|
// this variable (when applied to variables), or to call const
|
||||||
|
// methods of this class (when applied to classes), as long as no
|
||||||
|
// other thread is writing to the variable or calling non-const
|
||||||
|
// methods of this class.
|
||||||
|
|
||||||
|
#ifndef GOOGLE_GFLAGS_H_
|
||||||
|
#define GOOGLE_GFLAGS_H_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// We care a lot about number of bits things take up. Unfortunately,
|
||||||
|
// systems define their bit-specific ints in a lot of different ways.
|
||||||
|
// We use our own way, and have a typedef to get there.
|
||||||
|
// Note: these commands below may look like "#if 1" or "#if 0", but
|
||||||
|
// that's because they were constructed that way at ./configure time.
|
||||||
|
// Look at gflags.h.in to see how they're calculated (based on your config).
|
||||||
|
#if 1
|
||||||
|
#include <stdint.h> // the normal place uint16_t is defined
|
||||||
|
#endif
|
||||||
|
#if 1
|
||||||
|
#include <sys/types.h> // the normal place u_int16_t is defined
|
||||||
|
#endif
|
||||||
|
#if 1
|
||||||
|
#include <inttypes.h> // a third place for uint16_t or u_int16_t
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace google {
|
||||||
|
|
||||||
|
#if 1 // the C99 format
|
||||||
|
typedef int32_t int32;
|
||||||
|
typedef uint32_t uint32;
|
||||||
|
typedef int64_t int64;
|
||||||
|
typedef uint64_t uint64;
|
||||||
|
#elif 1 // the BSD format
|
||||||
|
typedef int32_t int32;
|
||||||
|
typedef u_int32_t uint32;
|
||||||
|
typedef int64_t int64;
|
||||||
|
typedef u_int64_t uint64;
|
||||||
|
#elif 0 // the windows (vc7) format
|
||||||
|
typedef __int32 int32;
|
||||||
|
typedef unsigned __int32 uint32;
|
||||||
|
typedef __int64 int64;
|
||||||
|
typedef unsigned __int64 uint64;
|
||||||
|
#else
|
||||||
|
#error Do not know how to define a 32-bit integer quantity on your system
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------
|
||||||
|
// To actually define a flag in a file, use DEFINE_bool,
|
||||||
|
// DEFINE_string, etc. at the bottom of this file. You may also find
|
||||||
|
// it useful to register a validator with the flag. This ensures that
|
||||||
|
// when the flag is parsed from the commandline, or is later set via
|
||||||
|
// SetCommandLineOption, we call the validation function.
|
||||||
|
//
|
||||||
|
// The validation function should return true if the flag value is valid, and
|
||||||
|
// false otherwise. If the function returns false for the new setting of the
|
||||||
|
// flag, the flag will retain its current value. If it returns false for the
|
||||||
|
// default value, InitGoogle will die.
|
||||||
|
//
|
||||||
|
// This function is safe to call at global construct time (as in the
|
||||||
|
// example below).
|
||||||
|
//
|
||||||
|
// Example use:
|
||||||
|
// static bool ValidatePort(const char* flagname, int32 value) {
|
||||||
|
// if (value > 0 && value < 32768) // value is ok
|
||||||
|
// return true;
|
||||||
|
// printf("Invalid value for --%s: %d\n", flagname, (int)value);
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
// DEFINE_int32(port, 0, "What port to listen on");
|
||||||
|
// static bool dummy = RegisterFlagValidator(&FLAGS_port, &ValidatePort);
|
||||||
|
|
||||||
|
// Returns true if successfully registered, false if not (because the
|
||||||
|
// first argument doesn't point to a command-line flag, or because a
|
||||||
|
// validator is already registered for this flag).
|
||||||
|
bool RegisterFlagValidator(const bool* flag,
|
||||||
|
bool (*validate_fn)(const char*, bool));
|
||||||
|
bool RegisterFlagValidator(const int32* flag,
|
||||||
|
bool (*validate_fn)(const char*, int32));
|
||||||
|
bool RegisterFlagValidator(const int64* flag,
|
||||||
|
bool (*validate_fn)(const char*, int64));
|
||||||
|
bool RegisterFlagValidator(const uint64* flag,
|
||||||
|
bool (*validate_fn)(const char*, uint64));
|
||||||
|
bool RegisterFlagValidator(const double* flag,
|
||||||
|
bool (*validate_fn)(const char*, double));
|
||||||
|
bool RegisterFlagValidator(const std::string* flag,
|
||||||
|
bool (*validate_fn)(const char*, const std::string&));
|
||||||
|
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------
|
||||||
|
// These methods are the best way to get access to info about the
|
||||||
|
// list of commandline flags. Note that these routines are pretty slow.
|
||||||
|
// GetAllFlags: mostly-complete info about the list, sorted by file.
|
||||||
|
// ShowUsageWithFlags: pretty-prints the list to stdout (what --help does)
|
||||||
|
// ShowUsageWithFlagsRestrict: limit to filenames with restrict as a substr
|
||||||
|
//
|
||||||
|
// In addition to accessing flags, you can also access argv[0] (the program
|
||||||
|
// name) and argv (the entire commandline), which we sock away a copy of.
|
||||||
|
// These variables are static, so you should only set them once.
|
||||||
|
|
||||||
|
struct CommandLineFlagInfo {
|
||||||
|
std::string name; // the name of the flag
|
||||||
|
std::string type; // the type of the flag: int32, etc
|
||||||
|
std::string description; // the "help text" associated with the flag
|
||||||
|
std::string current_value; // the current value, as a string
|
||||||
|
std::string default_value; // the default value, as a string
|
||||||
|
std::string filename; // 'cleaned' version of filename holding the flag
|
||||||
|
bool has_validator_fn; // true if RegisterFlagValidator called on flag
|
||||||
|
bool is_default; // true if the flag has default value
|
||||||
|
};
|
||||||
|
|
||||||
|
extern void GetAllFlags(std::vector<CommandLineFlagInfo>* OUTPUT);
|
||||||
|
// These two are actually defined in commandlineflags_reporting.cc.
|
||||||
|
extern void ShowUsageWithFlags(const char *argv0); // what --help does
|
||||||
|
extern void ShowUsageWithFlagsRestrict(const char *argv0, const char *restrict);
|
||||||
|
|
||||||
|
// Create a descriptive string for a flag.
|
||||||
|
// Goes to some trouble to make pretty line breaks.
|
||||||
|
extern std::string DescribeOneFlag(const CommandLineFlagInfo& flag);
|
||||||
|
|
||||||
|
// Thread-hostile; meant to be called before any threads are spawned.
|
||||||
|
extern void SetArgv(int argc, const char** argv);
|
||||||
|
// The following functions are thread-safe as long as SetArgv() is
|
||||||
|
// only called before any threads start.
|
||||||
|
extern const std::vector<std::string>& GetArgvs(); // all of argv as a vector
|
||||||
|
extern const char* GetArgv(); // all of argv as a string
|
||||||
|
extern const char* GetArgv0(); // only argv0
|
||||||
|
extern uint32 GetArgvSum(); // simple checksum of argv
|
||||||
|
extern const char* ProgramInvocationName(); // argv0, or "UNKNOWN" if not set
|
||||||
|
extern const char* ProgramInvocationShortName(); // basename(argv0)
|
||||||
|
// ProgramUsage() is thread-safe as long as SetUsageMessage() is only
|
||||||
|
// called before any threads start.
|
||||||
|
extern const char* ProgramUsage(); // string set by SetUsageMessage()
|
||||||
|
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------
|
||||||
|
// Normally you access commandline flags by just saying "if (FLAGS_foo)"
|
||||||
|
// or whatever, and set them by calling "FLAGS_foo = bar" (or, more
|
||||||
|
// commonly, via the DEFINE_foo macro). But if you need a bit more
|
||||||
|
// control, we have programmatic ways to get/set the flags as well.
|
||||||
|
// These programmatic ways to access flags are thread-safe, but direct
|
||||||
|
// access is only thread-compatible.
|
||||||
|
|
||||||
|
// Return true iff the flagname was found.
|
||||||
|
// OUTPUT is set to the flag's value, or unchanged if we return false.
|
||||||
|
extern bool GetCommandLineOption(const char* name, std::string* OUTPUT);
|
||||||
|
|
||||||
|
// Return true iff the flagname was found. OUTPUT is set to the flag's
|
||||||
|
// CommandLineFlagInfo or unchanged if we return false.
|
||||||
|
extern bool GetCommandLineFlagInfo(const char* name,
|
||||||
|
CommandLineFlagInfo* OUTPUT);
|
||||||
|
|
||||||
|
// Return the CommandLineFlagInfo of the flagname. exit() if name not found.
|
||||||
|
// Example usage, to check if a flag's value is currently the default value:
|
||||||
|
// if (GetCommandLineFlagInfoOrDie("foo").is_default) ...
|
||||||
|
extern CommandLineFlagInfo GetCommandLineFlagInfoOrDie(const char* name);
|
||||||
|
|
||||||
|
enum FlagSettingMode {
|
||||||
|
// update the flag's value (can call this multiple times).
|
||||||
|
SET_FLAGS_VALUE,
|
||||||
|
// update the flag's value, but *only if* it has not yet been updated
|
||||||
|
// with SET_FLAGS_VALUE, SET_FLAG_IF_DEFAULT, or "FLAGS_xxx = nondef".
|
||||||
|
SET_FLAG_IF_DEFAULT,
|
||||||
|
// set the flag's default value to this. If the flag has not yet updated
|
||||||
|
// yet (via SET_FLAGS_VALUE, SET_FLAG_IF_DEFAULT, or "FLAGS_xxx = nondef")
|
||||||
|
// change the flag's current value to the new default value as well.
|
||||||
|
SET_FLAGS_DEFAULT
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set a particular flag ("command line option"). Returns a string
|
||||||
|
// describing the new value that the option has been set to. The
|
||||||
|
// return value API is not well-specified, so basically just depend on
|
||||||
|
// it to be empty if the setting failed for some reason -- the name is
|
||||||
|
// not a valid flag name, or the value is not a valid value -- and
|
||||||
|
// non-empty else.
|
||||||
|
|
||||||
|
// SetCommandLineOption uses set_mode == SET_FLAGS_VALUE (the common case)
|
||||||
|
extern std::string SetCommandLineOption(const char* name, const char* value);
|
||||||
|
extern std::string SetCommandLineOptionWithMode(const char* name, const char* value,
|
||||||
|
FlagSettingMode set_mode);
|
||||||
|
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------
|
||||||
|
// Saves the states (value, default value, whether the user has set
|
||||||
|
// the flag, registered validators, etc) of all flags, and restores
|
||||||
|
// them when the FlagSaver is destroyed. This is very useful in
|
||||||
|
// tests, say, when you want to let your tests change the flags, but
|
||||||
|
// make sure that they get reverted to the original states when your
|
||||||
|
// test is complete.
|
||||||
|
//
|
||||||
|
// Example usage:
|
||||||
|
// void TestFoo() {
|
||||||
|
// FlagSaver s1;
|
||||||
|
// FLAG_foo = false;
|
||||||
|
// FLAG_bar = "some value";
|
||||||
|
//
|
||||||
|
// // test happens here. You can return at any time
|
||||||
|
// // without worrying about restoring the FLAG values.
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Note: This class is marked with __attribute__((unused)) because all the
|
||||||
|
// work is done in the constructor and destructor, so in the standard
|
||||||
|
// usage example above, the compiler would complain that it's an
|
||||||
|
// unused variable.
|
||||||
|
//
|
||||||
|
// This class is thread-safe.
|
||||||
|
|
||||||
|
class FlagSaver {
|
||||||
|
public:
|
||||||
|
FlagSaver();
|
||||||
|
~FlagSaver();
|
||||||
|
|
||||||
|
private:
|
||||||
|
class FlagSaverImpl* impl_; // we use pimpl here to keep API steady
|
||||||
|
|
||||||
|
FlagSaver(const FlagSaver&); // no copying!
|
||||||
|
void operator=(const FlagSaver&);
|
||||||
|
} __attribute__ ((unused));
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------
|
||||||
|
// Some deprecated or hopefully-soon-to-be-deprecated functions.
|
||||||
|
|
||||||
|
// This is often used for logging. TODO(csilvers): figure out a better way
|
||||||
|
extern std::string CommandlineFlagsIntoString();
|
||||||
|
// Usually where this is used, a FlagSaver should be used instead.
|
||||||
|
extern bool ReadFlagsFromString(const std::string& flagfilecontents,
|
||||||
|
const char* prog_name,
|
||||||
|
bool errors_are_fatal); // uses SET_FLAGS_VALUE
|
||||||
|
|
||||||
|
// These let you manually implement --flagfile functionality.
|
||||||
|
// DEPRECATED.
|
||||||
|
extern bool AppendFlagsIntoFile(const std::string& filename, const char* prog_name);
|
||||||
|
extern bool SaveCommandFlags(); // actually defined in google.cc !
|
||||||
|
extern bool ReadFromFlagsFile(const std::string& filename, const char* prog_name,
|
||||||
|
bool errors_are_fatal); // uses SET_FLAGS_VALUE
|
||||||
|
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------
|
||||||
|
// Useful routines for initializing flags from the environment.
|
||||||
|
// In each case, if 'varname' does not exist in the environment
|
||||||
|
// return defval. If 'varname' does exist but is not valid
|
||||||
|
// (e.g., not a number for an int32 flag), abort with an error.
|
||||||
|
// Otherwise, return the value. NOTE: for booleans, for true use
|
||||||
|
// 't' or 'T' or 'true' or '1', for false 'f' or 'F' or 'false' or '0'.
|
||||||
|
|
||||||
|
extern bool BoolFromEnv(const char *varname, bool defval);
|
||||||
|
extern int32 Int32FromEnv(const char *varname, int32 defval);
|
||||||
|
extern int64 Int64FromEnv(const char *varname, int64 defval);
|
||||||
|
extern uint64 Uint64FromEnv(const char *varname, uint64 defval);
|
||||||
|
extern double DoubleFromEnv(const char *varname, double defval);
|
||||||
|
extern const char *StringFromEnv(const char *varname, const char *defval);
|
||||||
|
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------
|
||||||
|
// The next two functions parse commandlineflags from main():
|
||||||
|
|
||||||
|
// Set the "usage" message for this program. For example:
|
||||||
|
// string usage("This program does nothing. Sample usage:\n");
|
||||||
|
// usage += argv[0] + " <uselessarg1> <uselessarg2>";
|
||||||
|
// SetUsageMessage(usage);
|
||||||
|
// Do not include commandline flags in the usage: we do that for you!
|
||||||
|
// Thread-hostile; meant to be called before any threads are spawned.
|
||||||
|
extern void SetUsageMessage(const std::string& usage);
|
||||||
|
|
||||||
|
// Looks for flags in argv and parses them. Rearranges argv to put
|
||||||
|
// flags first, or removes them entirely if remove_flags is true.
|
||||||
|
// If a flag is defined more than once in the command line or flag
|
||||||
|
// file, the last definition is used.
|
||||||
|
// See top-of-file for more details on this function.
|
||||||
|
#ifndef SWIG // In swig, use ParseCommandLineFlagsScript() instead.
|
||||||
|
extern uint32 ParseCommandLineFlags(int *argc, char*** argv,
|
||||||
|
bool remove_flags);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
// Calls to ParseCommandLineNonHelpFlags and then to
|
||||||
|
// HandleCommandLineHelpFlags can be used instead of a call to
|
||||||
|
// ParseCommandLineFlags during initialization, in order to allow for
|
||||||
|
// changing default values for some FLAGS (via
|
||||||
|
// e.g. SetCommandLineOptionWithMode calls) between the time of
|
||||||
|
// command line parsing and the time of dumping help information for
|
||||||
|
// the flags as a result of command line parsing.
|
||||||
|
// If a flag is defined more than once in the command line or flag
|
||||||
|
// file, the last definition is used.
|
||||||
|
extern uint32 ParseCommandLineNonHelpFlags(int *argc, char*** argv,
|
||||||
|
bool remove_flags);
|
||||||
|
// This is actually defined in commandlineflags_reporting.cc.
|
||||||
|
// This function is misnamed (it also handles --version, etc.), but
|
||||||
|
// it's too late to change that now. :-(
|
||||||
|
extern void HandleCommandLineHelpFlags(); // in commandlineflags_reporting.cc
|
||||||
|
|
||||||
|
// Allow command line reparsing. Disables the error normally
|
||||||
|
// generated when an unknown flag is found, since it may be found in a
|
||||||
|
// later parse. Thread-hostile; meant to be called before any threads
|
||||||
|
// are spawned.
|
||||||
|
extern void AllowCommandLineReparsing();
|
||||||
|
|
||||||
|
// Reparse the flags that have not yet been recognized.
|
||||||
|
// Only flags registered since the last parse will be recognized.
|
||||||
|
// Any flag value must be provided as part of the argument using "=",
|
||||||
|
// not as a separate command line argument that follows the flag argument.
|
||||||
|
// Intended for handling flags from dynamically loaded libraries,
|
||||||
|
// since their flags are not registered until they are loaded.
|
||||||
|
extern uint32 ReparseCommandLineNonHelpFlags();
|
||||||
|
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------
|
||||||
|
// Now come the command line flag declaration/definition macros that
|
||||||
|
// will actually be used. They're kind of hairy. A major reason
|
||||||
|
// for this is initialization: we want people to be able to access
|
||||||
|
// variables in global constructors and have that not crash, even if
|
||||||
|
// their global constructor runs before the global constructor here.
|
||||||
|
// (Obviously, we can't guarantee the flags will have the correct
|
||||||
|
// default value in that case, but at least accessing them is safe.)
|
||||||
|
// The only way to do that is have flags point to a static buffer.
|
||||||
|
// So we make one, using a union to ensure proper alignment, and
|
||||||
|
// then use placement-new to actually set up the flag with the
|
||||||
|
// correct default value. In the same vein, we have to worry about
|
||||||
|
// flag access in global destructors, so FlagRegisterer has to be
|
||||||
|
// careful never to destroy the flag-values it constructs.
|
||||||
|
//
|
||||||
|
// Note that when we define a flag variable FLAGS_<name>, we also
|
||||||
|
// preemptively define a junk variable, FLAGS_no<name>. This is to
|
||||||
|
// cause a link-time error if someone tries to define 2 flags with
|
||||||
|
// names like "logging" and "nologging". We do this because a bool
|
||||||
|
// flag FLAG can be set from the command line to true with a "-FLAG"
|
||||||
|
// argument, and to false with a "-noFLAG" argument, and so this can
|
||||||
|
// potentially avert confusion.
|
||||||
|
//
|
||||||
|
// We also put flags into their own namespace. It is purposefully
|
||||||
|
// named in an opaque way that people should have trouble typing
|
||||||
|
// directly. The idea is that DEFINE puts the flag in the weird
|
||||||
|
// namespace, and DECLARE imports the flag from there into the current
|
||||||
|
// namespace. The net result is to force people to use DECLARE to get
|
||||||
|
// access to a flag, rather than saying "extern bool FLAGS_whatever;"
|
||||||
|
// or some such instead. We want this so we can put extra
|
||||||
|
// functionality (like sanity-checking) in DECLARE if we want, and
|
||||||
|
// make sure it is picked up everywhere.
|
||||||
|
//
|
||||||
|
// We also put the type of the variable in the namespace, so that
|
||||||
|
// people can't DECLARE_int32 something that they DEFINE_bool'd
|
||||||
|
// elsewhere.
|
||||||
|
|
||||||
|
class FlagRegisterer {
|
||||||
|
public:
|
||||||
|
FlagRegisterer(const char* name, const char* type,
|
||||||
|
const char* help, const char* filename,
|
||||||
|
void* current_storage, void* defvalue_storage);
|
||||||
|
};
|
||||||
|
|
||||||
|
extern bool FlagsTypeWarn(const char *name);
|
||||||
|
|
||||||
|
// If your application #defines STRIP_FLAG_HELP to a non-zero value
|
||||||
|
// before #including this file, we remove the help message from the
|
||||||
|
// binary file. This can reduce the size of the resulting binary
|
||||||
|
// somewhat, and may also be useful for security reasons.
|
||||||
|
|
||||||
|
extern const char kStrippedFlagHelp[];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef SWIG // In swig, ignore the main flag declarations
|
||||||
|
|
||||||
|
#if defined(STRIP_FLAG_HELP) && STRIP_FLAG_HELP > 0
|
||||||
|
// Need this construct to avoid the 'defined but not used' warning.
|
||||||
|
#define MAYBE_STRIPPED_HELP(txt) (false ? (txt) : kStrippedFlagHelp)
|
||||||
|
#else
|
||||||
|
#define MAYBE_STRIPPED_HELP(txt) txt
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Each command-line flag has two variables associated with it: one
|
||||||
|
// with the current value, and one with the default value. However,
|
||||||
|
// we have a third variable, which is where value is assigned; it's a
|
||||||
|
// constant. This guarantees that FLAG_##value is initialized at
|
||||||
|
// static initialization time (e.g. before program-start) rather than
|
||||||
|
// than global construction time (which is after program-start but
|
||||||
|
// before main), at least when 'value' is a compile-time constant. We
|
||||||
|
// use a small trick for the "default value" variable, and call it
|
||||||
|
// FLAGS_no<name>. This serves the second purpose of assuring a
|
||||||
|
// compile error if someone tries to define a flag named no<name>
|
||||||
|
// which is illegal (--foo and --nofoo both affect the "foo" flag).
|
||||||
|
#define DEFINE_VARIABLE(type, shorttype, name, value, help) \
|
||||||
|
namespace fL##shorttype { \
|
||||||
|
static const type FLAGS_nono##name = value; \
|
||||||
|
type FLAGS_##name = FLAGS_nono##name; \
|
||||||
|
type FLAGS_no##name = FLAGS_nono##name; \
|
||||||
|
static ::google::FlagRegisterer o_##name( \
|
||||||
|
#name, #type, MAYBE_STRIPPED_HELP(help), __FILE__, \
|
||||||
|
&FLAGS_##name, &FLAGS_no##name); \
|
||||||
|
} \
|
||||||
|
using fL##shorttype::FLAGS_##name
|
||||||
|
|
||||||
|
#define DECLARE_VARIABLE(type, shorttype, name) \
|
||||||
|
namespace fL##shorttype { \
|
||||||
|
extern type FLAGS_##name; \
|
||||||
|
} \
|
||||||
|
using fL##shorttype::FLAGS_##name
|
||||||
|
|
||||||
|
// For DEFINE_bool, we want to do the extra check that the passed-in
|
||||||
|
// value is actually a bool, and not a string or something that can be
|
||||||
|
// coerced to a bool. These declarations (no definition needed!) will
|
||||||
|
// help us do that, and never evaluate From, which is important.
|
||||||
|
// We'll use 'sizeof(IsBool(val))' to distinguish. This code requires
|
||||||
|
// that the compiler have different sizes for bool & double. Since
|
||||||
|
// this is not guaranteed by the standard, we check it with a
|
||||||
|
// compile-time assert (msg[-1] will give a compile-time error).
|
||||||
|
namespace fLB {
|
||||||
|
struct CompileAssert {};
|
||||||
|
typedef CompileAssert expected_sizeof_double_neq_sizeof_bool[
|
||||||
|
(sizeof(double) != sizeof(bool)) ? 1 : -1];
|
||||||
|
template<typename From> double IsBoolFlag(const From& from);
|
||||||
|
bool IsBoolFlag(bool from);
|
||||||
|
} // namespace fLB
|
||||||
|
|
||||||
|
#define DECLARE_bool(name) DECLARE_VARIABLE(bool,B, name)
|
||||||
|
#define DEFINE_bool(name,val,txt) \
|
||||||
|
namespace fLB { \
|
||||||
|
typedef CompileAssert FLAG_##name##_value_is_not_a_bool[ \
|
||||||
|
(sizeof(::fLB::IsBoolFlag(val)) != sizeof(double)) ? 1 : -1]; \
|
||||||
|
} \
|
||||||
|
DEFINE_VARIABLE(bool,B, name, val, txt)
|
||||||
|
|
||||||
|
#define DECLARE_int32(name) DECLARE_VARIABLE(::google::int32,I, name)
|
||||||
|
#define DEFINE_int32(name,val,txt) DEFINE_VARIABLE(::google::int32,I, name, val, txt)
|
||||||
|
|
||||||
|
#define DECLARE_int64(name) DECLARE_VARIABLE(::google::int64,I64, name)
|
||||||
|
#define DEFINE_int64(name,val,txt) DEFINE_VARIABLE(::google::int64,I64, name, val, txt)
|
||||||
|
|
||||||
|
#define DECLARE_uint64(name) DECLARE_VARIABLE(::google::uint64,U64, name)
|
||||||
|
#define DEFINE_uint64(name,val,txt) DEFINE_VARIABLE(::google::uint64,U64, name, val, txt)
|
||||||
|
|
||||||
|
#define DECLARE_double(name) DECLARE_VARIABLE(double,D, name)
|
||||||
|
#define DEFINE_double(name,val,txt) DEFINE_VARIABLE(double,D, name, val, txt)
|
||||||
|
|
||||||
|
// Strings are trickier, because they're not a POD, so we can't
|
||||||
|
// construct them at static-initialization time (instead they get
|
||||||
|
// constructed at global-constructor time, which is much later). To
|
||||||
|
// try to avoid crashes in that case, we use a char buffer to store
|
||||||
|
// the string, which we can static-initialize, and then placement-new
|
||||||
|
// into it later. It's not perfect, but the best we can do.
|
||||||
|
#define DECLARE_string(name) namespace fLS { extern std::string& FLAGS_##name; } \
|
||||||
|
using fLS::FLAGS_##name
|
||||||
|
|
||||||
|
// We need to define a var named FLAGS_no##name so people don't define
|
||||||
|
// --string and --nostring. And we need a temporary place to put val
|
||||||
|
// so we don't have to evaluate it twice. Two great needs that go
|
||||||
|
// great together!
|
||||||
|
// The weird 'using' + 'extern' inside the fLS namespace is to work around
|
||||||
|
// an unknown compiler bug/issue with the gcc 4.2.1 on SUSE 10. See
|
||||||
|
// http://code.google.com/p/google-gflags/issues/detail?id=20
|
||||||
|
#define DEFINE_string(name, val, txt) \
|
||||||
|
namespace fLS { \
|
||||||
|
static union { void* align; char s[sizeof(std::string)]; } s_##name[2]; \
|
||||||
|
const std::string* const FLAGS_no##name = new (s_##name[0].s) std::string(val); \
|
||||||
|
static ::google::FlagRegisterer o_##name( \
|
||||||
|
#name, "string", MAYBE_STRIPPED_HELP(txt), __FILE__, \
|
||||||
|
s_##name[0].s, new (s_##name[1].s) std::string(*FLAGS_no##name)); \
|
||||||
|
extern std::string& FLAGS_##name; \
|
||||||
|
using fLS::FLAGS_##name; \
|
||||||
|
std::string& FLAGS_##name = *(reinterpret_cast<std::string*>(s_##name[0].s)); \
|
||||||
|
} \
|
||||||
|
using fLS::FLAGS_##name
|
||||||
|
|
||||||
|
#endif // SWIG
|
||||||
|
|
||||||
|
#endif // GOOGLE_GFLAGS_H_
|
121
toolkit/crashreporter/google-breakpad/src/third_party/linux/include/gflags/gflags_completions.h
поставляемый
Normal file
121
toolkit/crashreporter/google-breakpad/src/third_party/linux/include/gflags/gflags_completions.h
поставляемый
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
// Copyright (c) 2008, 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.
|
||||||
|
//
|
||||||
|
// ---
|
||||||
|
// Author: Dave Nicponski
|
||||||
|
//
|
||||||
|
// Implement helpful bash-style command line flag completions
|
||||||
|
//
|
||||||
|
// ** Functional API:
|
||||||
|
// HandleCommandLineCompletions() should be called early during
|
||||||
|
// program startup, but after command line flag code has been
|
||||||
|
// initialized, such as the beginning of HandleCommandLineHelpFlags().
|
||||||
|
// It checks the value of the flag --tab_completion_word. If this
|
||||||
|
// flag is empty, nothing happens here. If it contains a string,
|
||||||
|
// however, then HandleCommandLineCompletions() will hijack the
|
||||||
|
// process, attempting to identify the intention behind this
|
||||||
|
// completion. Regardless of the outcome of this deduction, the
|
||||||
|
// process will be terminated, similar to --helpshort flag
|
||||||
|
// handling.
|
||||||
|
//
|
||||||
|
// ** Overview of Bash completions:
|
||||||
|
// Bash can be told to programatically determine completions for the
|
||||||
|
// current 'cursor word'. It does this by (in this case) invoking a
|
||||||
|
// command with some additional arguments identifying the command
|
||||||
|
// being executed, the word being completed, and the previous word
|
||||||
|
// (if any). Bash then expects a sequence of output lines to be
|
||||||
|
// printed to stdout. If these lines all contain a common prefix
|
||||||
|
// longer than the cursor word, bash will replace the cursor word
|
||||||
|
// with that common prefix, and display nothing. If there isn't such
|
||||||
|
// a common prefix, bash will display the lines in pages using 'more'.
|
||||||
|
//
|
||||||
|
// ** Strategy taken for command line completions:
|
||||||
|
// If we can deduce either the exact flag intended, or a common flag
|
||||||
|
// prefix, we'll output exactly that. Otherwise, if information
|
||||||
|
// must be displayed to the user, we'll take the opportunity to add
|
||||||
|
// some helpful information beyond just the flag name (specifically,
|
||||||
|
// we'll include the default flag value and as much of the flag's
|
||||||
|
// description as can fit on a single terminal line width, as specified
|
||||||
|
// by the flag --tab_completion_columns). Furthermore, we'll try to
|
||||||
|
// make bash order the output such that the most useful or relevent
|
||||||
|
// flags are the most likely to be shown at the top.
|
||||||
|
//
|
||||||
|
// ** Additional features:
|
||||||
|
// To assist in finding that one really useful flag, substring matching
|
||||||
|
// was implemented. Before pressing a <TAB> to get completion for the
|
||||||
|
// current word, you can append one or more '?' to the flag to do
|
||||||
|
// substring matching. Here's the semantics:
|
||||||
|
// --foo<TAB> Show me all flags with names prefixed by 'foo'
|
||||||
|
// --foo?<TAB> Show me all flags with 'foo' somewhere in the name
|
||||||
|
// --foo??<TAB> Same as prior case, but also search in module
|
||||||
|
// definition path for 'foo'
|
||||||
|
// --foo???<TAB> Same as prior case, but also search in flag
|
||||||
|
// descriptions for 'foo'
|
||||||
|
// Finally, we'll trim the output to a relatively small number of
|
||||||
|
// flags to keep bash quiet about the verbosity of output. If one
|
||||||
|
// really wanted to see all possible matches, appending a '+' to the
|
||||||
|
// search word will force the exhaustive list of matches to be printed.
|
||||||
|
//
|
||||||
|
// ** How to have bash accept completions from a binary:
|
||||||
|
// Bash requires that it be informed about each command that programmatic
|
||||||
|
// completion should be enabled for. Example addition to a .bashrc
|
||||||
|
// file would be (your path to gflags_completions.sh file may differ):
|
||||||
|
|
||||||
|
/*
|
||||||
|
$ complete -o bashdefault -o default -o nospace -C \
|
||||||
|
'/usr/local/bin/gflags_completions.sh --tab_completion_columns $COLUMNS' \
|
||||||
|
time env binary_name another_binary [...]
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This would allow the following to work:
|
||||||
|
// $ /path/to/binary_name --vmodule<TAB>
|
||||||
|
// Or:
|
||||||
|
// $ ./bin/path/another_binary --gfs_u<TAB>
|
||||||
|
// (etc)
|
||||||
|
//
|
||||||
|
// Sadly, it appears that bash gives no easy way to force this behavior for
|
||||||
|
// all commands. That's where the "time" in the above example comes in.
|
||||||
|
// If you haven't specifically added a command to the list of completion
|
||||||
|
// supported commands, you can still get completions by prefixing the
|
||||||
|
// entire command with "env".
|
||||||
|
// $ env /some/brand/new/binary --vmod<TAB>
|
||||||
|
// Assuming that "binary" is a newly compiled binary, this should still
|
||||||
|
// produce the expected completion output.
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef GOOGLE_GFLAGS_COMPLETIONS_H_
|
||||||
|
#define GOOGLE_GFLAGS_COMPLETIONS_H_
|
||||||
|
|
||||||
|
namespace google {
|
||||||
|
|
||||||
|
void HandleCommandLineCompletions(void);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // GOOGLE_GFLAGS_COMPLETIONS_H_
|
84
toolkit/crashreporter/google-breakpad/src/third_party/linux/include/glog/log_severity.h
поставляемый
Normal file
84
toolkit/crashreporter/google-breakpad/src/third_party/linux/include/glog/log_severity.h
поставляемый
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
// Copyright (c) 2007, 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 BASE_LOG_SEVERITY_H__
|
||||||
|
#define BASE_LOG_SEVERITY_H__
|
||||||
|
|
||||||
|
// Annoying stuff for windows -- makes sure clients can import these functions
|
||||||
|
#ifndef GOOGLE_GLOG_DLL_DECL
|
||||||
|
# if defined(_WIN32) && !defined(__CYGWIN__)
|
||||||
|
# define GOOGLE_GLOG_DLL_DECL __declspec(dllimport)
|
||||||
|
# else
|
||||||
|
# define GOOGLE_GLOG_DLL_DECL
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Variables of type LogSeverity are widely taken to lie in the range
|
||||||
|
// [0, NUM_SEVERITIES-1]. Be careful to preserve this assumption if
|
||||||
|
// you ever need to change their values or add a new severity.
|
||||||
|
typedef int LogSeverity;
|
||||||
|
|
||||||
|
const int INFO = 0, WARNING = 1, ERROR = 2, FATAL = 3, NUM_SEVERITIES = 4;
|
||||||
|
|
||||||
|
// DFATAL is FATAL in debug mode, ERROR in normal mode
|
||||||
|
#ifdef NDEBUG
|
||||||
|
#define DFATAL_LEVEL ERROR
|
||||||
|
#else
|
||||||
|
#define DFATAL_LEVEL FATAL
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern GOOGLE_GLOG_DLL_DECL const char* const LogSeverityNames[NUM_SEVERITIES];
|
||||||
|
|
||||||
|
// NDEBUG usage helpers related to (RAW_)DCHECK:
|
||||||
|
//
|
||||||
|
// DEBUG_MODE is for small !NDEBUG uses like
|
||||||
|
// if (DEBUG_MODE) foo.CheckThatFoo();
|
||||||
|
// instead of substantially more verbose
|
||||||
|
// #ifndef NDEBUG
|
||||||
|
// foo.CheckThatFoo();
|
||||||
|
// #endif
|
||||||
|
//
|
||||||
|
// IF_DEBUG_MODE is for small !NDEBUG uses like
|
||||||
|
// IF_DEBUG_MODE( string error; )
|
||||||
|
// DCHECK(Foo(&error)) << error;
|
||||||
|
// instead of substantially more verbose
|
||||||
|
// #ifndef NDEBUG
|
||||||
|
// string error;
|
||||||
|
// DCHECK(Foo(&error)) << error;
|
||||||
|
// #endif
|
||||||
|
//
|
||||||
|
#ifdef NDEBUG
|
||||||
|
enum { DEBUG_MODE = 0 };
|
||||||
|
#define IF_DEBUG_MODE(x)
|
||||||
|
#else
|
||||||
|
enum { DEBUG_MODE = 1 };
|
||||||
|
#define IF_DEBUG_MODE(x) x
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // BASE_LOG_SEVERITY_H__
|
1499
toolkit/crashreporter/google-breakpad/src/third_party/linux/include/glog/logging.h
поставляемый
Normal file
1499
toolkit/crashreporter/google-breakpad/src/third_party/linux/include/glog/logging.h
поставляемый
Normal file
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
185
toolkit/crashreporter/google-breakpad/src/third_party/linux/include/glog/raw_logging.h
поставляемый
Normal file
185
toolkit/crashreporter/google-breakpad/src/third_party/linux/include/glog/raw_logging.h
поставляемый
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
// Copyright (c) 2006, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
//
|
||||||
|
// Author: Maxim Lifantsev
|
||||||
|
//
|
||||||
|
// Thread-safe logging routines that do not allocate any memory or
|
||||||
|
// acquire any locks, and can therefore be used by low-level memory
|
||||||
|
// allocation and synchronization code.
|
||||||
|
|
||||||
|
#ifndef BASE_RAW_LOGGING_H_
|
||||||
|
#define BASE_RAW_LOGGING_H_
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
namespace google {
|
||||||
|
|
||||||
|
#include "glog/log_severity.h"
|
||||||
|
#include "glog/vlog_is_on.h"
|
||||||
|
|
||||||
|
// Annoying stuff for windows -- makes sure clients can import these functions
|
||||||
|
#ifndef GOOGLE_GLOG_DLL_DECL
|
||||||
|
# if defined(_WIN32) && !defined(__CYGWIN__)
|
||||||
|
# define GOOGLE_GLOG_DLL_DECL __declspec(dllimport)
|
||||||
|
# else
|
||||||
|
# define GOOGLE_GLOG_DLL_DECL
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// This is similar to LOG(severity) << format... and VLOG(level) << format..,
|
||||||
|
// but
|
||||||
|
// * it is to be used ONLY by low-level modules that can't use normal LOG()
|
||||||
|
// * it is desiged to be a low-level logger that does not allocate any
|
||||||
|
// memory and does not need any locks, hence:
|
||||||
|
// * it logs straight and ONLY to STDERR w/o buffering
|
||||||
|
// * it uses an explicit format and arguments list
|
||||||
|
// * it will silently chop off really long message strings
|
||||||
|
// Usage example:
|
||||||
|
// RAW_LOG(ERROR, "Failed foo with %i: %s", status, error);
|
||||||
|
// RAW_VLOG(3, "status is %i", status);
|
||||||
|
// These will print an almost standard log lines like this to stderr only:
|
||||||
|
// E0821 211317 file.cc:123] RAW: Failed foo with 22: bad_file
|
||||||
|
// I0821 211317 file.cc:142] RAW: status is 20
|
||||||
|
#define RAW_LOG(severity, ...) \
|
||||||
|
do { \
|
||||||
|
switch (google::severity) { \
|
||||||
|
case 0: \
|
||||||
|
RAW_LOG_INFO(__VA_ARGS__); \
|
||||||
|
break; \
|
||||||
|
case 1: \
|
||||||
|
RAW_LOG_WARNING(__VA_ARGS__); \
|
||||||
|
break; \
|
||||||
|
case 2: \
|
||||||
|
RAW_LOG_ERROR(__VA_ARGS__); \
|
||||||
|
break; \
|
||||||
|
case 3: \
|
||||||
|
RAW_LOG_FATAL(__VA_ARGS__); \
|
||||||
|
break; \
|
||||||
|
default: \
|
||||||
|
break; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
// The following STRIP_LOG testing is performed in the header file so that it's
|
||||||
|
// possible to completely compile out the logging code and the log messages.
|
||||||
|
#if STRIP_LOG == 0
|
||||||
|
#define RAW_VLOG(verboselevel, ...) \
|
||||||
|
do { \
|
||||||
|
if (VLOG_IS_ON(verboselevel)) { \
|
||||||
|
RAW_LOG_INFO(__VA_ARGS__); \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
#else
|
||||||
|
#define RAW_VLOG(verboselevel, ...) RawLogStub__(0, __VA_ARGS__)
|
||||||
|
#endif // STRIP_LOG == 0
|
||||||
|
|
||||||
|
#if STRIP_LOG == 0
|
||||||
|
#define RAW_LOG_INFO(...) google::RawLog__(google::INFO, \
|
||||||
|
__FILE__, __LINE__, __VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define RAW_LOG_INFO(...) google::RawLogStub__(0, __VA_ARGS__)
|
||||||
|
#endif // STRIP_LOG == 0
|
||||||
|
|
||||||
|
#if STRIP_LOG <= 1
|
||||||
|
#define RAW_LOG_WARNING(...) google::RawLog__(google::WARNING, \
|
||||||
|
__FILE__, __LINE__, __VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define RAW_LOG_WARNING(...) google::RawLogStub__(0, __VA_ARGS__)
|
||||||
|
#endif // STRIP_LOG <= 1
|
||||||
|
|
||||||
|
#if STRIP_LOG <= 2
|
||||||
|
#define RAW_LOG_ERROR(...) google::RawLog__(google::ERROR, \
|
||||||
|
__FILE__, __LINE__, __VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define RAW_LOG_ERROR(...) google::RawLogStub__(0, __VA_ARGS__)
|
||||||
|
#endif // STRIP_LOG <= 2
|
||||||
|
|
||||||
|
#if STRIP_LOG <= 3
|
||||||
|
#define RAW_LOG_FATAL(...) google::RawLog__(google::FATAL, \
|
||||||
|
__FILE__, __LINE__, __VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define RAW_LOG_FATAL(...) \
|
||||||
|
do { \
|
||||||
|
google::RawLogStub__(0, __VA_ARGS__); \
|
||||||
|
exit(1); \
|
||||||
|
} while (0)
|
||||||
|
#endif // STRIP_LOG <= 3
|
||||||
|
|
||||||
|
// Similar to CHECK(condition) << message,
|
||||||
|
// but for low-level modules: we use only RAW_LOG that does not allocate memory.
|
||||||
|
// We do not want to provide args list here to encourage this usage:
|
||||||
|
// if (!cond) RAW_LOG(FATAL, "foo ...", hard_to_compute_args);
|
||||||
|
// so that the args are not computed when not needed.
|
||||||
|
#define RAW_CHECK(condition, message) \
|
||||||
|
do { \
|
||||||
|
if (!(condition)) { \
|
||||||
|
RAW_LOG(FATAL, "Check %s failed: %s", #condition, message); \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
// Debug versions of RAW_LOG and RAW_CHECK
|
||||||
|
#ifndef NDEBUG
|
||||||
|
|
||||||
|
#define RAW_DLOG(severity, ...) RAW_LOG(severity, __VA_ARGS__)
|
||||||
|
#define RAW_DCHECK(condition, message) RAW_CHECK(condition, message)
|
||||||
|
|
||||||
|
#else // NDEBUG
|
||||||
|
|
||||||
|
#define RAW_DLOG(severity, ...) \
|
||||||
|
while (false) \
|
||||||
|
RAW_LOG(severity, __VA_ARGS__)
|
||||||
|
#define RAW_DCHECK(condition, message) \
|
||||||
|
while (false) \
|
||||||
|
RAW_CHECK(condition, message)
|
||||||
|
|
||||||
|
#endif // NDEBUG
|
||||||
|
|
||||||
|
// Stub log function used to work around for unused variable warnings when
|
||||||
|
// building with STRIP_LOG > 0.
|
||||||
|
static inline void RawLogStub__(int ignored, ...) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to implement RAW_LOG and RAW_VLOG
|
||||||
|
// Logs format... at "severity" level, reporting it
|
||||||
|
// as called from file:line.
|
||||||
|
// This does not allocate memory or acquire locks.
|
||||||
|
GOOGLE_GLOG_DLL_DECL void RawLog__(LogSeverity severity,
|
||||||
|
const char* file,
|
||||||
|
int line,
|
||||||
|
const char* format, ...)
|
||||||
|
__attribute__((__format__ (__printf__, 4, 5)));
|
||||||
|
|
||||||
|
// Hack to propagate time information into this module so that
|
||||||
|
// this module does not have to directly call localtime_r(),
|
||||||
|
// which could allocate memory.
|
||||||
|
GOOGLE_GLOG_DLL_DECL void RawLog__SetLastTime(const struct tm& t, int usecs);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // BASE_RAW_LOGGING_H_
|
154
toolkit/crashreporter/google-breakpad/src/third_party/linux/include/glog/stl_logging.h
поставляемый
Normal file
154
toolkit/crashreporter/google-breakpad/src/third_party/linux/include/glog/stl_logging.h
поставляемый
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
// Copyright (c) 2003, 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.
|
||||||
|
//
|
||||||
|
// Stream output operators for STL containers; to be used for logging *only*.
|
||||||
|
// Inclusion of this file lets you do:
|
||||||
|
//
|
||||||
|
// list<string> x;
|
||||||
|
// LOG(INFO) << "data: " << x;
|
||||||
|
// vector<int> v1, v2;
|
||||||
|
// CHECK_EQ(v1, v2);
|
||||||
|
//
|
||||||
|
// Note that if you want to use these operators from the non-global namespace,
|
||||||
|
// you may get an error since they are not in namespace std (and they are not
|
||||||
|
// in namespace std since that would result in undefined behavior). You may
|
||||||
|
// need to write
|
||||||
|
//
|
||||||
|
// using ::operator<<;
|
||||||
|
//
|
||||||
|
// to fix these errors.
|
||||||
|
|
||||||
|
#ifndef UTIL_GTL_STL_LOGGING_INL_H_
|
||||||
|
#define UTIL_GTL_STL_LOGGING_INL_H_
|
||||||
|
|
||||||
|
#if !1
|
||||||
|
# error We do not support stl_logging for this compiler
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
|
#include <list>
|
||||||
|
#include <map>
|
||||||
|
#include <ostream>
|
||||||
|
#include <set>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#ifdef __GNUC__
|
||||||
|
# include <ext/hash_set>
|
||||||
|
# include <ext/hash_map>
|
||||||
|
# include <ext/slist>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template<class First, class Second>
|
||||||
|
inline std::ostream& operator<<(std::ostream& out,
|
||||||
|
const std::pair<First, Second>& p) {
|
||||||
|
out << '(' << p.first << ", " << p.second << ')';
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace google {
|
||||||
|
|
||||||
|
template<class Iter>
|
||||||
|
inline void PrintSequence(std::ostream& out, Iter begin, Iter end) {
|
||||||
|
using ::operator<<;
|
||||||
|
// Output at most 100 elements -- appropriate if used for logging.
|
||||||
|
for (int i = 0; begin != end && i < 100; ++i, ++begin) {
|
||||||
|
if (i > 0) out << ' ';
|
||||||
|
out << *begin;
|
||||||
|
}
|
||||||
|
if (begin != end) {
|
||||||
|
out << " ...";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#define OUTPUT_TWO_ARG_CONTAINER(Sequence) \
|
||||||
|
template<class T1, class T2> \
|
||||||
|
inline std::ostream& operator<<(std::ostream& out, \
|
||||||
|
const Sequence<T1, T2>& seq) { \
|
||||||
|
google::PrintSequence(out, seq.begin(), seq.end()); \
|
||||||
|
return out; \
|
||||||
|
}
|
||||||
|
|
||||||
|
OUTPUT_TWO_ARG_CONTAINER(std::vector)
|
||||||
|
OUTPUT_TWO_ARG_CONTAINER(std::deque)
|
||||||
|
OUTPUT_TWO_ARG_CONTAINER(std::list)
|
||||||
|
#ifdef __GNUC__
|
||||||
|
OUTPUT_TWO_ARG_CONTAINER(__gnu_cxx::slist)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#undef OUTPUT_TWO_ARG_CONTAINER
|
||||||
|
|
||||||
|
#define OUTPUT_THREE_ARG_CONTAINER(Sequence) \
|
||||||
|
template<class T1, class T2, class T3> \
|
||||||
|
inline std::ostream& operator<<(std::ostream& out, \
|
||||||
|
const Sequence<T1, T2, T3>& seq) { \
|
||||||
|
google::PrintSequence(out, seq.begin(), seq.end()); \
|
||||||
|
return out; \
|
||||||
|
}
|
||||||
|
|
||||||
|
OUTPUT_THREE_ARG_CONTAINER(std::set)
|
||||||
|
OUTPUT_THREE_ARG_CONTAINER(std::multiset)
|
||||||
|
|
||||||
|
#undef OUTPUT_THREE_ARG_CONTAINER
|
||||||
|
|
||||||
|
#define OUTPUT_FOUR_ARG_CONTAINER(Sequence) \
|
||||||
|
template<class T1, class T2, class T3, class T4> \
|
||||||
|
inline std::ostream& operator<<(std::ostream& out, \
|
||||||
|
const Sequence<T1, T2, T3, T4>& seq) { \
|
||||||
|
google::PrintSequence(out, seq.begin(), seq.end()); \
|
||||||
|
return out; \
|
||||||
|
}
|
||||||
|
|
||||||
|
OUTPUT_FOUR_ARG_CONTAINER(std::map)
|
||||||
|
OUTPUT_FOUR_ARG_CONTAINER(std::multimap)
|
||||||
|
#ifdef __GNUC__
|
||||||
|
OUTPUT_FOUR_ARG_CONTAINER(__gnu_cxx::hash_set)
|
||||||
|
OUTPUT_FOUR_ARG_CONTAINER(__gnu_cxx::hash_multiset)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#undef OUTPUT_FOUR_ARG_CONTAINER
|
||||||
|
|
||||||
|
#define OUTPUT_FIVE_ARG_CONTAINER(Sequence) \
|
||||||
|
template<class T1, class T2, class T3, class T4, class T5> \
|
||||||
|
inline std::ostream& operator<<(std::ostream& out, \
|
||||||
|
const Sequence<T1, T2, T3, T4, T5>& seq) { \
|
||||||
|
google::PrintSequence(out, seq.begin(), seq.end()); \
|
||||||
|
return out; \
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __GNUC__
|
||||||
|
OUTPUT_FIVE_ARG_CONTAINER(__gnu_cxx::hash_map)
|
||||||
|
OUTPUT_FIVE_ARG_CONTAINER(__gnu_cxx::hash_multimap)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#undef OUTPUT_FIVE_ARG_CONTAINER
|
||||||
|
|
||||||
|
#endif // UTIL_GTL_STL_LOGGING_INL_H_
|
128
toolkit/crashreporter/google-breakpad/src/third_party/linux/include/glog/vlog_is_on.h
поставляемый
Normal file
128
toolkit/crashreporter/google-breakpad/src/third_party/linux/include/glog/vlog_is_on.h
поставляемый
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
// Copyright (c) 1999, 2007, 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.
|
||||||
|
//
|
||||||
|
// Author: Ray Sidney and many others
|
||||||
|
//
|
||||||
|
// Defines the VLOG_IS_ON macro that controls the variable-verbosity
|
||||||
|
// conditional logging.
|
||||||
|
//
|
||||||
|
// It's used by VLOG and VLOG_IF in logging.h
|
||||||
|
// and by RAW_VLOG in raw_logging.h to trigger the logging.
|
||||||
|
//
|
||||||
|
// It can also be used directly e.g. like this:
|
||||||
|
// if (VLOG_IS_ON(2)) {
|
||||||
|
// // do some logging preparation and logging
|
||||||
|
// // that can't be accomplished e.g. via just VLOG(2) << ...;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// The truth value that VLOG_IS_ON(level) returns is determined by
|
||||||
|
// the three verbosity level flags:
|
||||||
|
// --v=<n> Gives the default maximal active V-logging level;
|
||||||
|
// 0 is the default.
|
||||||
|
// Normally positive values are used for V-logging levels.
|
||||||
|
// --vmodule=<str> Gives the per-module maximal V-logging levels to override
|
||||||
|
// the value given by --v.
|
||||||
|
// E.g. "my_module=2,foo*=3" would change the logging level
|
||||||
|
// for all code in source files "my_module.*" and "foo*.*"
|
||||||
|
// ("-inl" suffixes are also disregarded for this matching).
|
||||||
|
//
|
||||||
|
// SetVLOGLevel helper function is provided to do limited dynamic control over
|
||||||
|
// V-logging by overriding the per-module settings given via --vmodule flag.
|
||||||
|
//
|
||||||
|
// CAVEAT: --vmodule functionality is not available in non gcc compilers.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef BASE_VLOG_IS_ON_H_
|
||||||
|
#define BASE_VLOG_IS_ON_H_
|
||||||
|
|
||||||
|
#include "glog/log_severity.h"
|
||||||
|
|
||||||
|
// Annoying stuff for windows -- makes sure clients can import these functions
|
||||||
|
#ifndef GOOGLE_GLOG_DLL_DECL
|
||||||
|
# if defined(_WIN32) && !defined(__CYGWIN__)
|
||||||
|
# define GOOGLE_GLOG_DLL_DECL __declspec(dllimport)
|
||||||
|
# else
|
||||||
|
# define GOOGLE_GLOG_DLL_DECL
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__GNUC__)
|
||||||
|
// We emit an anonymous static int* variable at every VLOG_IS_ON(n) site.
|
||||||
|
// (Normally) the first time every VLOG_IS_ON(n) site is hit,
|
||||||
|
// we determine what variable will dynamically control logging at this site:
|
||||||
|
// it's either FLAGS_v or an appropriate internal variable
|
||||||
|
// matching the current source file that represents results of
|
||||||
|
// parsing of --vmodule flag and/or SetVLOGLevel calls.
|
||||||
|
#define VLOG_IS_ON(verboselevel) \
|
||||||
|
({ static google::int32* vlocal__ = &google::kLogSiteUninitialized; \
|
||||||
|
google::int32 verbose_level__ = (verboselevel); \
|
||||||
|
(*vlocal__ >= verbose_level__) && \
|
||||||
|
((vlocal__ != &google::kLogSiteUninitialized) || \
|
||||||
|
(google::InitVLOG3__(&vlocal__, &FLAGS_v, \
|
||||||
|
__FILE__, verbose_level__))); })
|
||||||
|
#else
|
||||||
|
// GNU extensions not available, so we do not support --vmodule.
|
||||||
|
// Dynamic value of FLAGS_v always controls the logging level.
|
||||||
|
#define VLOG_IS_ON(verboselevel) (FLAGS_v >= (verboselevel))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Set VLOG(_IS_ON) level for module_pattern to log_level.
|
||||||
|
// This lets us dynamically control what is normally set by the --vmodule flag.
|
||||||
|
// Returns the level that previously applied to module_pattern.
|
||||||
|
// NOTE: To change the log level for VLOG(_IS_ON) sites
|
||||||
|
// that have already executed after/during InitGoogleLogging,
|
||||||
|
// one needs to supply the exact --vmodule pattern that applied to them.
|
||||||
|
// (If no --vmodule pattern applied to them
|
||||||
|
// the value of FLAGS_v will continue to control them.)
|
||||||
|
extern GOOGLE_GLOG_DLL_DECL int SetVLOGLevel(const char* module_pattern,
|
||||||
|
int log_level);
|
||||||
|
|
||||||
|
// Various declarations needed for VLOG_IS_ON above: =========================
|
||||||
|
|
||||||
|
// Special value used to indicate that a VLOG_IS_ON site has not been
|
||||||
|
// initialized. We make this a large value, so the common-case check
|
||||||
|
// of "*vlocal__ >= verbose_level__" in VLOG_IS_ON definition
|
||||||
|
// passes in such cases and InitVLOG3__ is then triggered.
|
||||||
|
extern google::int32 kLogSiteUninitialized;
|
||||||
|
|
||||||
|
// Helper routine which determines the logging info for a particalur VLOG site.
|
||||||
|
// site_flag is the address of the site-local pointer to the controlling
|
||||||
|
// verbosity level
|
||||||
|
// site_default is the default to use for *site_flag
|
||||||
|
// fname is the current source file name
|
||||||
|
// verbose_level is the argument to VLOG_IS_ON
|
||||||
|
// We will return the return value for VLOG_IS_ON
|
||||||
|
// and if possible set *site_flag appropriately.
|
||||||
|
extern GOOGLE_GLOG_DLL_DECL bool InitVLOG3__(
|
||||||
|
google::int32** site_flag,
|
||||||
|
google::int32* site_default,
|
||||||
|
const char* fname,
|
||||||
|
google::int32 verbose_level);
|
||||||
|
|
||||||
|
#endif // BASE_VLOG_IS_ON_H_
|
Двоичные данные
toolkit/crashreporter/google-breakpad/src/third_party/linux/lib/gflags/libgflags.a
поставляемый
Normal file
Двоичные данные
toolkit/crashreporter/google-breakpad/src/third_party/linux/lib/gflags/libgflags.a
поставляемый
Normal file
Двоичный файл не отображается.
Двоичные данные
toolkit/crashreporter/google-breakpad/src/third_party/linux/lib/glog/libglog.a
поставляемый
Normal file
Двоичные данные
toolkit/crashreporter/google-breakpad/src/third_party/linux/lib/glog/libglog.a
поставляемый
Normal file
Двоичный файл не отображается.
|
@ -0,0 +1,603 @@
|
||||||
|
// 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 "common/linux/linux_syscall_support.h"
|
||||||
|
#include "common/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),
|
||||||
|
MD_MODULE_SIZE, 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;
|
||||||
|
}
|
|
@ -59,6 +59,7 @@ using google_breakpad::BasicSourceLineResolver;
|
||||||
using google_breakpad::CallStack;
|
using google_breakpad::CallStack;
|
||||||
using google_breakpad::CodeModule;
|
using google_breakpad::CodeModule;
|
||||||
using google_breakpad::CodeModules;
|
using google_breakpad::CodeModules;
|
||||||
|
using google_breakpad::Minidump;
|
||||||
using google_breakpad::MinidumpProcessor;
|
using google_breakpad::MinidumpProcessor;
|
||||||
using google_breakpad::OnDemandSymbolSupplier;
|
using google_breakpad::OnDemandSymbolSupplier;
|
||||||
using google_breakpad::PathnameStripper;
|
using google_breakpad::PathnameStripper;
|
||||||
|
@ -73,6 +74,7 @@ typedef struct {
|
||||||
NSString *minidumpPath;
|
NSString *minidumpPath;
|
||||||
NSString *searchDir;
|
NSString *searchDir;
|
||||||
NSString *symbolSearchDir;
|
NSString *symbolSearchDir;
|
||||||
|
BOOL printThreadMemory;
|
||||||
} Options;
|
} Options;
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
|
@ -220,10 +222,8 @@ static void PrintModules(const CodeModules *modules) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//=============================================================================
|
static void ProcessSingleReport(Options *options, NSString *file_path) {
|
||||||
static void Start(Options *options) {
|
string minidump_file([file_path fileSystemRepresentation]);
|
||||||
string minidump_file([options->minidumpPath fileSystemRepresentation]);
|
|
||||||
|
|
||||||
BasicSourceLineResolver resolver;
|
BasicSourceLineResolver resolver;
|
||||||
string search_dir = options->searchDir ?
|
string search_dir = options->searchDir ?
|
||||||
[options->searchDir fileSystemRepresentation] : "";
|
[options->searchDir fileSystemRepresentation] : "";
|
||||||
|
@ -234,8 +234,14 @@ static void Start(Options *options) {
|
||||||
scoped_ptr<MinidumpProcessor>
|
scoped_ptr<MinidumpProcessor>
|
||||||
minidump_processor(new MinidumpProcessor(symbol_supplier.get(), &resolver));
|
minidump_processor(new MinidumpProcessor(symbol_supplier.get(), &resolver));
|
||||||
ProcessState process_state;
|
ProcessState process_state;
|
||||||
if (minidump_processor->Process(minidump_file, &process_state) !=
|
scoped_ptr<Minidump> dump(new google_breakpad::Minidump(minidump_file));
|
||||||
MinidumpProcessor::PROCESS_OK) {
|
|
||||||
|
if (!dump->Read()) {
|
||||||
|
fprintf(stderr, "Minidump %s could not be read\n", dump->path().c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (minidump_processor->Process(dump.get(), &process_state) !=
|
||||||
|
google_breakpad::PROCESS_OK) {
|
||||||
fprintf(stderr, "MinidumpProcessor::Process failed\n");
|
fprintf(stderr, "MinidumpProcessor::Process failed\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -274,12 +280,20 @@ static void Start(Options *options) {
|
||||||
|
|
||||||
// Print all of the threads in the dump.
|
// Print all of the threads in the dump.
|
||||||
int thread_count = process_state.threads()->size();
|
int thread_count = process_state.threads()->size();
|
||||||
|
const std::vector<google_breakpad::MinidumpMemoryRegion*>
|
||||||
|
*thread_memory_regions = process_state.thread_memory_regions();
|
||||||
|
|
||||||
for (int thread_index = 0; thread_index < thread_count; ++thread_index) {
|
for (int thread_index = 0; thread_index < thread_count; ++thread_index) {
|
||||||
if (thread_index != requesting_thread) {
|
if (thread_index != requesting_thread) {
|
||||||
// Don't print the crash thread again, it was already printed.
|
// Don't print the crash thread again, it was already printed.
|
||||||
printf("\n");
|
printf("\n");
|
||||||
printf("Thread %d\n", thread_index);
|
printf("Thread %d\n", thread_index);
|
||||||
PrintStack(process_state.threads()->at(thread_index), cpu);
|
PrintStack(process_state.threads()->at(thread_index), cpu);
|
||||||
|
google_breakpad::MinidumpMemoryRegion *thread_stack_bytes =
|
||||||
|
thread_memory_regions->at(thread_index);
|
||||||
|
if (options->printThreadMemory) {
|
||||||
|
thread_stack_bytes->Print();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,11 +302,36 @@ static void Start(Options *options) {
|
||||||
printf("\nThread %d:", requesting_thread);
|
printf("\nThread %d:", requesting_thread);
|
||||||
PrintRegisters(process_state.threads()->at(requesting_thread), cpu);
|
PrintRegisters(process_state.threads()->at(requesting_thread), cpu);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print information about modules
|
// Print information about modules
|
||||||
PrintModules(process_state.modules());
|
PrintModules(process_state.modules());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
static void Start(Options *options) {
|
||||||
|
NSFileManager *manager = [NSFileManager defaultManager];
|
||||||
|
NSString *minidump_path = options->minidumpPath;
|
||||||
|
BOOL is_dir = NO;
|
||||||
|
BOOL file_exists = [manager fileExistsAtPath:minidump_path
|
||||||
|
isDirectory:&is_dir];
|
||||||
|
if (file_exists && is_dir) {
|
||||||
|
NSDirectoryEnumerator *enumerator =
|
||||||
|
[manager enumeratorAtPath:minidump_path];
|
||||||
|
NSString *current_file = nil;
|
||||||
|
while ((current_file = [enumerator nextObject])) {
|
||||||
|
if ([[current_file pathExtension] isEqualTo:@"dmp"]) {
|
||||||
|
printf("Attempting to process report: %s\n",
|
||||||
|
[current_file cStringUsingEncoding:NSASCIIStringEncoding]);
|
||||||
|
NSString *full_path =
|
||||||
|
[minidump_path stringByAppendingPathComponent:current_file];
|
||||||
|
ProcessSingleReport(options, full_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (file_exists) {
|
||||||
|
ProcessSingleReport(options, minidump_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
static void Usage(int argc, const char *argv[]) {
|
static void Usage(int argc, const char *argv[]) {
|
||||||
fprintf(stderr, "Convert a minidump to a crash report. Breakpad symbol "
|
fprintf(stderr, "Convert a minidump to a crash report. Breakpad symbol "
|
||||||
|
@ -303,9 +342,11 @@ static void Usage(int argc, const char *argv[]) {
|
||||||
"If modules cannot be found at the paths stored in the "
|
"If modules cannot be found at the paths stored in the "
|
||||||
"minidump file, they will be searched for at "
|
"minidump file, they will be searched for at "
|
||||||
"<module-search-dir>/<path-in-minidump-file>.\n");
|
"<module-search-dir>/<path-in-minidump-file>.\n");
|
||||||
fprintf(stderr, "Usage: %s [-s module-search-dir] [-S symbol-file-search-dir] minidump-file\n", argv[0]);
|
fprintf(stderr, "Usage: %s [-s module-search-dir] [-S symbol-file-search-dir] "
|
||||||
|
"minidump-file\n", argv[0]);
|
||||||
fprintf(stderr, "\t-s: Specify a search directory to use for missing modules\n"
|
fprintf(stderr, "\t-s: Specify a search directory to use for missing modules\n"
|
||||||
"\t-S: Specify a search directory to use for symbol files\n"
|
"\t-S: Specify a search directory to use for symbol files\n"
|
||||||
|
"\t-t: Print thread stack memory in hex\n"
|
||||||
"\t-h: Usage\n"
|
"\t-h: Usage\n"
|
||||||
"\t-?: Usage\n");
|
"\t-?: Usage\n");
|
||||||
}
|
}
|
||||||
|
@ -315,7 +356,7 @@ static void SetupOptions(int argc, const char *argv[], Options *options) {
|
||||||
extern int optind;
|
extern int optind;
|
||||||
char ch;
|
char ch;
|
||||||
|
|
||||||
while ((ch = getopt(argc, (char * const *)argv, "S:s:h?")) != -1) {
|
while ((ch = getopt(argc, (char * const *)argv, "S:s:ht?")) != -1) {
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
case 's':
|
case 's':
|
||||||
options->searchDir = [[NSFileManager defaultManager]
|
options->searchDir = [[NSFileManager defaultManager]
|
||||||
|
@ -329,6 +370,9 @@ static void SetupOptions(int argc, const char *argv[], Options *options) {
|
||||||
length:strlen(optarg)];
|
length:strlen(optarg)];
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 't':
|
||||||
|
options->printThreadMemory = YES;
|
||||||
|
break;
|
||||||
case 'h':
|
case 'h':
|
||||||
case '?':
|
case '?':
|
||||||
Usage(argc, argv);
|
Usage(argc, argv);
|
||||||
|
|
Загрузка…
Ссылка в новой задаче