Merge of Breakpad Chrome Linux fork

A=agl, Lei Zhang
R=nealsid, agl



git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@384 4c0a9323-5329-0410-9bdc-e9ce6186880e
This commit is contained in:
nealsid
2009-08-17 23:12:53 +00:00
parent 7c48629d49
commit b0baafc4da
29 changed files with 6750 additions and 2288 deletions

View File

@@ -1,55 +1,45 @@
CXX=g++
CC=gcc
CXXFLAGS=-gstabs+ -I../../.. -Wall -D_REENTRANT
CXXFLAGS=-gstabs+ -I../../../ -I../../../testing/gtest/include -I../../../testing/include -I../../../testing/gtest -D_REENTRANT -m32
CFLAGS=$(CXXFLAGS)
LDFLAGS=-lpthread
OBJ_DIR=.
BIN_DIR=.
THREAD_SRC=linux_thread.cc
SHARE_SRC=../../minidump_file_writer.cc\
../../../common/string_conversion.cc\
../../../common/linux/file_id.cc\
minidump_generator.cc
HANDLER_SRC=exception_handler.cc\
../../../common/linux/guid_creator.cc
SHARE_C_SRC=../../../common/convert_UTF.c
TEST_CC_SRC=exception_handler_unittest.cc \
exception_handler.cc \
../../../testing/gtest/src/gtest-all.cc \
../../../common/linux/guid_creator.cc \
../minidump_writer/minidump_writer.cc \
../../minidump_file_writer.cc \
../minidump_writer/linux_dumper.cc \
../../../testing/gtest/src/gtest_main.cc \
../../../common/string_conversion.cc \
../minidump_writer/directory_reader_unittest.cc \
../minidump_writer/line_reader_unittest.cc \
../minidump_writer/linux_dumper_unittest.cc \
../minidump_writer/minidump_writer_unittest.cc
THREAD_TEST_SRC=linux_thread_test.cc
MINIDUMP_TEST_SRC=minidump_test.cc
EXCEPTION_TEST_SRC=exception_handler_test.cc
TEST_C_SRC = ../../../common/convert_UTF.c
THREAD_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o,$(THREAD_SRC))
SHARE_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o,$(SHARE_SRC))
HANDLER_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o,$(HANDLER_SRC))
SHARE_C_OBJ=$(patsubst %.c,$(OBJ_DIR)/%.o,$(SHARE_C_SRC)) md5.o
THREAD_TEST_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o, $(THREAD_TEST_SRC))\
$(THREAD_OBJ)
MINIDUMP_TEST_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o, $(MINIDUMP_TEST_SRC))\
$(THREAD_OBJ) $(SHARE_OBJ) $(SHARE_C_OBJ)
EXCEPTION_TEST_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o, $(EXCEPTION_TEST_SRC))\
$(THREAD_OBJ) $(SHARE_OBJ) $(SHARE_C_OBJ) $(HANDLER_OBJ)
TEST_CC_OBJ=$(patsubst %.cc, $(OBJ_DIR)/%.o,$(TEST_CC_SRC))
TEST_C_OBJ=$(patsubst %.c, $(OBJ_DIR)/%.o, $(TEST_C_SRC))
BIN=$(BIN_DIR)/minidump_test\
$(BIN_DIR)/linux_thread_test\
$(BIN_DIR)/exception_handler_test
LINUX_CLIENT_BIN=$(BIN_DIR)/linux_client_test
BIN=$(LINUX_CLIENT_BIN)
.PHONY:all clean
check:$(BIN)
$(LINUX_CLIENT_BIN)
all:$(BIN)
$(BIN_DIR)/linux_thread_test:$(THREAD_TEST_OBJ)
$(BIN_DIR)/linux_client_test:$(TEST_CC_OBJ) $(TEST_C_OBJ)
$(CXX) $(CXXFLAGS) $(LDFLAGS) $^ -o $@
$(BIN_DIR)/minidump_test:$(MINIDUMP_TEST_OBJ)
$(CXX) $(CXXFLAGS) $(LDFLAGS) $^ -o $@
$(BIN_DIR)/exception_handler_test:$(EXCEPTION_TEST_OBJ)
$(CXX) $(CXXFLAGS) $(LDFLAGS) $^ -o $@
md5.o:../../../common/md5.c
$(CC) $(CXXFLAGS) -c $^
clean:
rm -f $(BIN) *.o *.dmp
rm -f $(BIN) $(TEST_CC_OBJ) $(TEST_C_OBJ)

View File

@@ -1,8 +1,6 @@
// Copyright (c) 2006, Google Inc.
// Copyright (c) 2009, Google Inc.
// All rights reserved.
//
// Author: Li Liu
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
@@ -29,48 +27,87 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
// The ExceptionHandler object installs signal handlers for a number of
// signals. We rely on the signal handler running on the thread which crashed
// in order to identify it. This is true of the synchronous signals (SEGV etc),
// but not true of ABRT. Thus, if you send ABRT to yourself in a program which
// uses ExceptionHandler, you need to use tgkill to direct it to the current
// thread.
//
// The signal flow looks like this:
//
// SignalHandler (uses a global stack of ExceptionHandler objects to find
// | one to handle the signal. If the first rejects it, try
// | the second etc...)
// V
// HandleSignal ----------------------------| (clones a new process which
// | | shares an address space with
// (wait for cloned | the crashed process. This
// process) | allows us to ptrace the crashed
// | | process)
// V V
// (set signal handler to ThreadEntry (static function to bounce
// SIG_DFL and rethrow, | back into the object)
// killing the crashed |
// process) V
// DoDump (writes minidump)
// |
// V
// sys_exit
//
#include <cassert>
#include <cstdlib>
#include <cstdio>
#include <ctime>
#include <linux/limits.h>
// This code is a little fragmented. Different functions of the ExceptionHandler
// class run in a number of different contexts. Some of them run in a normal
// context and are easy to code, others run in a compromised context and the
// restrictions at the top of minidump_writer.cc apply: no libc and use the
// alternative malloc. Each function should have comment above it detailing the
// context which it runs in.
#include "client/linux/handler/exception_handler.h"
#include <errno.h>
#include <fcntl.h>
#include <linux/limits.h>
#include <sched.h>
#include <signal.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/signal.h>
#include <sys/syscall.h>
#include <sys/ucontext.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <unistd.h>
#include "common/linux/linux_libc_support.h"
#include "common/linux/linux_syscall_support.h"
#include "common/linux/memory.h"
#include "client/linux/minidump_writer//minidump_writer.h"
#include "common/linux/guid_creator.h"
#include "google_breakpad/common/minidump_format.h"
// A wrapper for the tgkill syscall: send a signal to a specific thread.
static int tgkill(pid_t tgid, pid_t tid, int sig) {
syscall(__NR_tgkill, tgid, tid, sig);
}
namespace google_breakpad {
// Signals that we are interested.
int SigTable[] = {
#if defined(SIGSEGV)
SIGSEGV,
#endif
#ifdef SIGABRT
SIGABRT,
#endif
#ifdef SIGFPE
SIGFPE,
#endif
#ifdef SIGILL
SIGILL,
#endif
#ifdef SIGBUS
SIGBUS,
#endif
// The list of signals which we consider to be crashes. The default action for
// all these signals must be Core (see man 7 signal) because we rethrow the
// signal after handling it and expect that it'll be fatal.
static const int kExceptionSignals[] = {
SIGSEGV, SIGABRT, SIGFPE, SIGILL, SIGBUS, -1
};
std::vector<ExceptionHandler*> *ExceptionHandler::handler_stack_ = NULL;
int ExceptionHandler::handler_stack_index_ = 0;
// We can stack multiple exception handlers. In that case, this is the global
// which holds the stack.
std::vector<ExceptionHandler*>* ExceptionHandler::handler_stack_ = NULL;
unsigned ExceptionHandler::handler_stack_index_ = 0;
pthread_mutex_t ExceptionHandler::handler_stack_mutex_ =
PTHREAD_MUTEX_INITIALIZER;
PTHREAD_MUTEX_INITIALIZER;
ExceptionHandler::ExceptionHandler(const string &dump_path,
// Runs before crashing: normal context.
ExceptionHandler::ExceptionHandler(const std::string &dump_path,
FilterCallback filter,
MinidumpCallback callback,
void *callback_context,
@@ -79,212 +116,76 @@ ExceptionHandler::ExceptionHandler(const string &dump_path,
callback_(callback),
callback_context_(callback_context),
dump_path_(),
installed_handler_(install_handler) {
handler_installed_(install_handler),
crash_handler_(NULL) {
set_dump_path(dump_path);
act_.sa_handler = HandleException;
act_.sa_flags = SA_ONSTACK;
sigemptyset(&act_.sa_mask);
// now, make sure we're blocking all the signals we are handling
// when we're handling any of them
for ( size_t i = 0; i < sizeof(SigTable) / sizeof(SigTable[0]); ++i) {
sigaddset(&act_.sa_mask, SigTable[i]);
}
if (install_handler) {
SetupHandler();
InstallHandlers();
pthread_mutex_lock(&handler_stack_mutex_);
if (handler_stack_ == NULL)
handler_stack_ = new std::vector<ExceptionHandler *>;
handler_stack_->push_back(this);
if (handler_stack_ == NULL)
handler_stack_ = new std::vector<ExceptionHandler *>;
handler_stack_->push_back(this);
pthread_mutex_unlock(&handler_stack_mutex_);
}
}
// Runs before crashing: normal context.
ExceptionHandler::~ExceptionHandler() {
TeardownAllHandler();
pthread_mutex_lock(&handler_stack_mutex_);
if (handler_stack_->back() == this) {
handler_stack_->pop_back();
} else {
fprintf(stderr, "warning: removing Breakpad handler out of order\n");
for (std::vector<ExceptionHandler *>::iterator iterator =
handler_stack_->begin();
iterator != handler_stack_->end();
++iterator) {
if (*iterator == this) {
handler_stack_->erase(iterator);
}
}
}
if (handler_stack_->empty()) {
// When destroying the last ExceptionHandler that installed a handler,
// clean up the handler stack.
delete handler_stack_;
handler_stack_ = NULL;
}
pthread_mutex_unlock(&handler_stack_mutex_);
UninstallHandlers();
}
bool ExceptionHandler::WriteMinidump() {
bool success = InternalWriteMinidump(0, 0, NULL);
UpdateNextID();
return success;
}
// Runs before crashing: normal context.
bool ExceptionHandler::InstallHandlers() {
// We run the signal handlers on an alternative stack because we might have
// crashed because of a stack overflow.
// static
bool ExceptionHandler::WriteMinidump(const string &dump_path,
MinidumpCallback callback,
void *callback_context) {
ExceptionHandler handler(dump_path, NULL, callback,
callback_context, false);
return handler.InternalWriteMinidump(0, 0, NULL);
}
// We use this value rather than SIGSTKSZ because we would end up overrunning
// such a small stack.
static const unsigned kSigStackSize = 8192;
void ExceptionHandler::SetupHandler() {
// Signal on a different stack to avoid using the stack
// of the crashing thread.
struct sigaltstack sig_stack;
sig_stack.ss_sp = malloc(MINSIGSTKSZ);
if (sig_stack.ss_sp == NULL)
return;
sig_stack.ss_size = MINSIGSTKSZ;
sig_stack.ss_flags = 0;
signal_stack = malloc(kSigStackSize);
stack_t stack;
memset(&stack, 0, sizeof(stack));
stack.ss_sp = signal_stack;
stack.ss_size = kSigStackSize;
if (sigaltstack(&sig_stack, NULL) < 0)
return;
for (size_t i = 0; i < sizeof(SigTable) / sizeof(SigTable[0]); ++i)
SetupHandler(SigTable[i]);
}
void ExceptionHandler::SetupHandler(int signo) {
// We're storing pointers to the old signal action
// structure, rather than copying the structure
// because we can't count on the sa_mask field to
// be scalar.
struct sigaction *old_act = &old_actions_[signo];
if (sigaction(signo, &act_, old_act) < 0)
return;
}
void ExceptionHandler::TeardownHandler(int signo) {
TeardownHandler(signo, NULL);
}
void ExceptionHandler::TeardownHandler(int signo, struct sigaction *final_handler) {
if (old_actions_[signo].sa_handler) {
struct sigaction *act = &old_actions_[signo];
sigaction(signo, act, final_handler);
memset(&old_actions_[signo], 0x0, sizeof(struct sigaction));
}
}
void ExceptionHandler::TeardownAllHandler() {
for (size_t i = 0; i < sizeof(SigTable) / sizeof(SigTable[0]); ++i) {
TeardownHandler(SigTable[i]);
}
}
// static
void ExceptionHandler::HandleException(int signo) {
// In Linux, the context information about the signal is put on the stack of
// the signal handler frame as value parameter. For some reasons, the
// prototype of the handler doesn't declare this information as parameter, we
// will do it by hand. It is the second parameter above the signal number.
// However, if we are being called by another signal handler passing the
// signal up the chain, then we may not have this random extra parameter,
// so we may have to walk the stack to find it. We do the actual work
// on another thread, where it's a little safer, but we want the ebp
// from this frame to find it.
uintptr_t current_ebp = 0;
asm volatile ("movl %%ebp, %0"
:"=m"(current_ebp));
pthread_mutex_lock(&handler_stack_mutex_);
ExceptionHandler *current_handler =
handler_stack_->at(handler_stack_->size() - ++handler_stack_index_);
pthread_mutex_unlock(&handler_stack_mutex_);
// Restore original handler.
struct sigaction old_action;
current_handler->TeardownHandler(signo, &old_action);
struct sigcontext *sig_ctx = NULL;
if (current_handler->InternalWriteMinidump(signo, current_ebp, &sig_ctx)) {
// Fully handled this exception, safe to exit.
exit(EXIT_FAILURE);
} else {
// Exception not fully handled, will call the next handler in stack to
// process it.
if (old_action.sa_handler != NULL && sig_ctx != NULL) {
// Have our own typedef, because of the comment above w.r.t signal
// context on the stack
typedef void (*SignalHandler)(int signo, struct sigcontext);
SignalHandler old_handler =
reinterpret_cast<SignalHandler>(old_action.sa_handler);
sigset_t old_set;
// Use SIG_BLOCK here because we don't want to unblock a signal
// that the signal handler we're currently in needs to block
sigprocmask(SIG_BLOCK, &old_action.sa_mask, &old_set);
old_handler(signo, *sig_ctx);
sigprocmask(SIG_SETMASK, &old_set, NULL);
}
}
pthread_mutex_lock(&handler_stack_mutex_);
current_handler->SetupHandler(signo);
--handler_stack_index_;
// All the handlers in stack have been invoked to handle the exception,
// normally the process should be terminated and should not reach here.
// In case we got here, ask the OS to handle it to avoid endless loop,
// normally the OS will generate a core and termiate the process. This
// may be desired to debug the program.
if (handler_stack_index_ == 0)
signal(signo, SIG_DFL);
pthread_mutex_unlock(&handler_stack_mutex_);
}
bool ExceptionHandler::InternalWriteMinidump(int signo,
uintptr_t sighandler_ebp,
struct sigcontext **sig_ctx) {
if (filter_ && !filter_(callback_context_))
if (sigaltstack(&stack, NULL) == -1)
return false;
bool success = false;
// Block all the signals we want to process when writting minidump.
// We don't want it to be interrupted.
sigset_t sig_blocked, sig_old;
bool blocked = true;
sigfillset(&sig_blocked);
for (size_t i = 0; i < sizeof(SigTable) / sizeof(SigTable[0]); ++i)
sigdelset(&sig_blocked, SigTable[i]);
if (sigprocmask(SIG_BLOCK, &sig_blocked, &sig_old) != 0) {
blocked = false;
fprintf(stderr, "google_breakpad::ExceptionHandler::HandleException: "
"failed to block signals.\n");
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sigemptyset(&sa.sa_mask);
// mask all exception signals when we're handling one of them.
for (unsigned i = 0; kExceptionSignals[i] != -1; ++i)
sigaddset(&sa.sa_mask, kExceptionSignals[i]);
sa.sa_sigaction = SignalHandler;
sa.sa_flags = SA_ONSTACK | SA_SIGINFO;
for (unsigned i = 0; kExceptionSignals[i] != -1; ++i) {
struct sigaction* old = new struct sigaction;
if (sigaction(kExceptionSignals[i], &sa, old) == -1)
return false;
old_handlers_.push_back(std::make_pair(kExceptionSignals[i], old));
}
success = minidump_generator_.WriteMinidumpToFile(
next_minidump_path_c_, signo, sighandler_ebp, sig_ctx);
// Unblock the signals.
if (blocked) {
sigprocmask(SIG_SETMASK, &sig_old, NULL);
}
if (callback_)
success = callback_(dump_path_c_, next_minidump_id_c_,
callback_context_, success);
return success;
}
// Runs before crashing: normal context.
void ExceptionHandler::UninstallHandlers() {
for (unsigned i = 0; i < old_handlers_.size(); ++i) {
struct sigaction *action =
reinterpret_cast<struct sigaction*>(old_handlers_[i].second);
sigaction(old_handlers_[i].first, action, NULL);
delete action;
}
old_handlers_.clear();
}
// Runs before crashing: normal context.
void ExceptionHandler::UpdateNextID() {
GUID guid;
char guid_str[kGUIDStringLength + 1];
@@ -302,4 +203,120 @@ void ExceptionHandler::UpdateNextID() {
}
}
// This function runs in a compromised context: see the top of the file.
// Runs on the crashing thread.
// static
void ExceptionHandler::SignalHandler(int sig, siginfo_t* info, void* uc) {
// All the exception signals are blocked at this point.
pthread_mutex_lock(&handler_stack_mutex_);
if (!handler_stack_->size()) {
pthread_mutex_unlock(&handler_stack_mutex_);
return;
}
for (int i = handler_stack_->size() - 1; i >= 0; --i) {
if ((*handler_stack_)[i]->HandleSignal(sig, info, uc)) {
// successfully handled: We are in an invalid state since an exception
// signal has been delivered. We don't call the exit handlers because
// they could end up corrupting on-disk state.
break;
}
}
pthread_mutex_unlock(&handler_stack_mutex_);
// Terminate ourselves with the same signal so that our parent knows that we
// crashed. The default action for all the signals which we catch is Core, so
// this is the end of us.
signal(sig, SIG_DFL);
tgkill(getpid(), sys_gettid(), sig);
// not reached.
}
struct ThreadArgument {
pid_t pid; // the crashing process
ExceptionHandler* handler;
const void* context; // a CrashContext structure
size_t context_size;
};
// This is the entry function for the cloned process. We are in a compromised
// context here: see the top of the file.
// static
int ExceptionHandler::ThreadEntry(void *arg) {
const ThreadArgument *thread_arg = reinterpret_cast<ThreadArgument*>(arg);
return thread_arg->handler->DoDump(thread_arg->pid, thread_arg->context,
thread_arg->context_size) == false;
}
// This function runs in a compromised context: see the top of the file.
// Runs on the crashing thread.
bool ExceptionHandler::HandleSignal(int sig, siginfo_t* info, void* uc) {
if (filter_ && !filter_(callback_context_))
return false;
// Allow ourselves to be dumped.
sys_prctl(PR_SET_DUMPABLE, 1);
CrashContext context;
memcpy(&context.siginfo, info, sizeof(siginfo_t));
memcpy(&context.context, uc, sizeof(struct ucontext));
memcpy(&context.float_state, ((struct ucontext *)uc)->uc_mcontext.fpregs,
sizeof(context.float_state));
context.tid = sys_gettid();
if (crash_handler_ && crash_handler_(&context, sizeof(context),
callback_context_))
return true;
static const unsigned kChildStackSize = 8000;
PageAllocator allocator;
uint8_t* stack = (uint8_t*) allocator.Alloc(kChildStackSize);
if (!stack)
return false;
// clone() needs the top-most address. (scrub just to be safe)
stack += kChildStackSize;
my_memset(stack - 16, 0, 16);
ThreadArgument thread_arg;
thread_arg.handler = this;
thread_arg.pid = getpid();
thread_arg.context = &context;
thread_arg.context_size = sizeof(context);
const pid_t child = sys_clone(
ThreadEntry, stack, CLONE_FILES | CLONE_FS | CLONE_UNTRACED,
&thread_arg, NULL, NULL, NULL);
int r, status;
do {
r = sys_waitpid(child, &status, __WALL);
} while (r == -1 && errno == EINTR);
if (r == -1) {
static const char msg[] = "ExceptionHandler::HandleSignal: waitpid failed:";
sys_write(2, msg, sizeof(msg) - 1);
sys_write(2, strerror(errno), strlen(strerror(errno)));
sys_write(2, "\n", 1);
}
bool success = r != -1 && WIFEXITED(status) && WEXITSTATUS(status) == 0;
if (callback_)
success = callback_(dump_path_c_, next_minidump_id_c_,
callback_context_, success);
return success;
}
// This function runs in a compromised context: see the top of the file.
// Runs on the cloned process.
bool ExceptionHandler::DoDump(pid_t crashing_process, const void* context,
size_t context_size) {
return google_breakpad::WriteMinidump(
next_minidump_path_c_, crashing_process, context, context_size);
}
} // namespace google_breakpad

View File

@@ -1,8 +1,6 @@
// Copyright (c) 2006, Google Inc.
// Copyright (c) 2009, Google Inc.
// All rights reserved.
//
// Author: Li Liu
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
@@ -29,26 +27,16 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H__
#define CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H__
#ifndef CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_
#define CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_
#include <pthread.h>
#include <map>
#include <string>
#include <signal.h>
#include <vector>
#include <string>
#include "client/linux/handler/minidump_generator.h"
// Context information when exception occured.
struct sigcontex;
#include <signal.h>
namespace google_breakpad {
using std::string;
//
// ExceptionHandler
//
// ExceptionHandler can write a minidump file when an exception occurs,
@@ -73,7 +61,6 @@ using std::string;
//
// Caller should try to make the callbacks as crash-friendly as possible,
// it should avoid use heap memory allocation as much as possible.
//
class ExceptionHandler {
public:
// A callback function to run before Breakpad performs any substantial
@@ -108,6 +95,15 @@ class ExceptionHandler {
void *context,
bool succeeded);
// In certain cases, a user may wish to handle the generation of the minidump
// themselves. In this case, they can install a handler callback which is
// called when a crash has occured. If this function returns true, no other
// processing of occurs and the process will shortly be crashed. If this
// returns false, the normal processing continues.
typedef bool (*HandlerCallback)(const void* crash_context,
size_t crash_context_size,
void* context);
// Creates a new ExceptionHandler instance to handle writing minidumps.
// Before writing a minidump, the optional filter callback will be called.
// Its return value determines whether or not Breakpad should write a
@@ -116,111 +112,87 @@ class ExceptionHandler {
// If install_handler is true, then a minidump will be written whenever
// an unhandled exception occurs. If it is false, minidumps will only
// be written when WriteMinidump is called.
ExceptionHandler(const string &dump_path,
ExceptionHandler(const std::string &dump_path,
FilterCallback filter, MinidumpCallback callback,
void *callback_context,
bool install_handler);
~ExceptionHandler();
// Get and set the minidump path.
string dump_path() const { return dump_path_; }
void set_dump_path(const string &dump_path) {
std::string dump_path() const { return dump_path_; }
void set_dump_path(const std::string &dump_path) {
dump_path_ = dump_path;
dump_path_c_ = dump_path_.c_str();
UpdateNextID();
}
void set_crash_handler(HandlerCallback callback) {
crash_handler_ = callback;
}
// Writes a minidump immediately. This can be used to capture the
// execution state independently of a crash. Returns true on success.
bool WriteMinidump();
// Convenience form of WriteMinidump which does not require an
// ExceptionHandler instance.
static bool WriteMinidump(const string &dump_path,
static bool WriteMinidump(const std::string &dump_path,
MinidumpCallback callback,
void *callback_context);
// This structure is passed to minidump_writer.h:WriteMinidump via an opaque
// blob. It shouldn't be needed in any user code.
struct CrashContext {
siginfo_t siginfo;
pid_t tid; // the crashing thread.
struct ucontext context;
struct _libc_fpstate float_state;
};
private:
// Setup crash handler.
void SetupHandler();
// Setup signal handler for a signal.
void SetupHandler(int signo);
// Teardown the handler for a signal.
void TeardownHandler(int signo);
// Teardown the handler for a signal.
void TeardownHandler(int signo, struct sigaction *old);
// Teardown all handlers.
void TeardownAllHandler();
bool InstallHandlers();
void UninstallHandlers();
void PreresolveSymbols();
// Signal handler.
static void HandleException(int signo);
// If called from a signal handler, sighandler_ebp is the ebp of
// that signal handler's frame, and sig_ctx is an out parameter
// that will be set to point at the sigcontext that was placed
// on the stack by the kernel. You can pass zero and NULL
// for the second and third parameters if you are not calling
// this from a signal handler.
bool InternalWriteMinidump(int signo, uintptr_t sighandler_ebp,
struct sigcontext **sig_ctx);
// Generates a new ID and stores it in next_minidump_id, and stores the
// path of the next minidump to be written in next_minidump_path_.
void UpdateNextID();
static void SignalHandler(int sig, siginfo_t* info, void* uc);
bool HandleSignal(int sig, siginfo_t* info, void* uc);
static int ThreadEntry(void* arg);
bool DoDump(pid_t crashing_process, const void* context,
size_t context_size);
private:
FilterCallback filter_;
MinidumpCallback callback_;
void *callback_context_;
const FilterCallback filter_;
const MinidumpCallback callback_;
void* const callback_context_;
// The directory in which a minidump will be written, set by the dump_path
// argument to the constructor, or set_dump_path.
string dump_path_;
// The basename of the next minidump to be written, without the extension
string next_minidump_id_;
// The full pathname of the next minidump to be written, including the file
// extension
string next_minidump_path_;
std::string dump_path_;
std::string next_minidump_path_;
std::string next_minidump_id_;
// Pointers to C-string representations of the above. These are set
// when the above are set so we can avoid calling c_str during
// an exception.
const char *dump_path_c_;
const char *next_minidump_id_c_;
const char *next_minidump_path_c_;
const char* dump_path_c_;
const char* next_minidump_path_c_;
const char* next_minidump_id_c_;
// True if the ExceptionHandler installed an unhandled exception filter
// when created (with an install_handler parameter set to true).
bool installed_handler_;
const bool handler_installed_;
void* signal_stack; // the handler stack.
HandlerCallback crash_handler_;
// The global exception handler stack. This is need becuase there may exist
// multiple ExceptionHandler instances in a process. Each will have itself
// registered in this stack.
static std::vector<ExceptionHandler *> *handler_stack_;
static std::vector<ExceptionHandler*> *handler_stack_;
// The index of the handler that should handle the next exception.
static int handler_stack_index_;
static unsigned handler_stack_index_;
static pthread_mutex_t handler_stack_mutex_;
// The minidump generator.
MinidumpGenerator minidump_generator_;
// disallow copy ctor and operator=
explicit ExceptionHandler(const ExceptionHandler &);
void operator=(const ExceptionHandler &);
// The sigactions structure we use for each signal
struct sigaction act_;
// Keep the previous handlers for the signal.
// We're wasting a bit of memory here since we only change
// the handler for some signals but i want to avoid allocating
// memory in the signal handler
struct sigaction old_actions_[NSIG];
// A vector of the old signal handlers. The void* is a pointer to a newly
// allocated sigaction structure to avoid pulling in too many includes.
std::vector<std::pair<int, void *> > old_handlers_;
};
} // namespace google_breakpad
#endif // CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H__
#endif // CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_

View File

@@ -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;
}

View File

@@ -0,0 +1,256 @@
// Copyright (c) 2009, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <string>
#include <stdint.h>
#include <unistd.h>
#include <signal.h>
#include <sys/poll.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include "client/linux/handler//exception_handler.h"
#include "client/linux/minidump_writer/minidump_writer.h"
#include "common/linux/linux_libc_support.h"
#include "common/linux/linux_syscall_support.h"
#include "breakpad_googletest_includes.h"
// This provides a wrapper around system calls which may be
// interrupted by a signal and return EINTR. See man 7 signal.
#define HANDLE_EINTR(x) ({ \
typeof(x) __eintr_result__; \
do { \
__eintr_result__ = x; \
} while (__eintr_result__ == -1 && errno == EINTR); \
__eintr_result__;\
})
using namespace google_breakpad;
static void sigchld_handler(int signo) { }
class ExceptionHandlerTest : public ::testing::Test {
protected:
void SetUp() {
// We need to be able to wait for children, so SIGCHLD cannot be SIG_IGN.
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = sigchld_handler;
ASSERT_NE(sigaction(SIGCHLD, &sa, &old_action), -1);
}
void TearDown() {
sigaction(SIGCHLD, &old_action, NULL);
}
struct sigaction old_action;
};
TEST(ExceptionHandlerTest, Simple) {
ExceptionHandler handler("/tmp", NULL, NULL, NULL, true);
}
static bool DoneCallback(const char* dump_path,
const char* minidump_id,
void* context,
bool succeeded) {
if (!succeeded)
return succeeded;
int fd = (int) context;
uint32_t len = my_strlen(minidump_id);
HANDLE_EINTR(sys_write(fd, &len, sizeof(len)));
HANDLE_EINTR(sys_write(fd, minidump_id, len));
sys_close(fd);
return true;
}
TEST(ExceptionHandlerTest, ChildCrash) {
int fds[2];
ASSERT_NE(pipe(fds), -1);
const pid_t child = fork();
if (child == 0) {
close(fds[0]);
ExceptionHandler handler("/tmp", NULL, DoneCallback, (void*) fds[1],
true);
*reinterpret_cast<int*>(NULL) = 0;
}
close(fds[1]);
int status;
ASSERT_NE(HANDLE_EINTR(waitpid(child, &status, 0)), -1);
ASSERT_TRUE(WIFSIGNALED(status));
ASSERT_EQ(WTERMSIG(status), SIGSEGV);
struct pollfd pfd;
memset(&pfd, 0, sizeof(pfd));
pfd.fd = fds[0];
pfd.events = POLLIN | POLLERR;
const int r = HANDLE_EINTR(poll(&pfd, 1, 0));
ASSERT_EQ(r, 1);
ASSERT_TRUE(pfd.revents & POLLIN);
uint32_t len;
ASSERT_EQ(read(fds[0], &len, sizeof(len)), sizeof(len));
ASSERT_LT(len, 2048);
char* filename = reinterpret_cast<char*>(malloc(len + 1));
ASSERT_EQ(read(fds[0], filename, len), len);
filename[len] = 0;
close(fds[0]);
const std::string minidump_filename = std::string("/tmp/") + filename +
".dmp";
struct stat st;
ASSERT_EQ(stat(minidump_filename.c_str(), &st), 0);
ASSERT_GT(st.st_size, 0u);
unlink(minidump_filename.c_str());
}
static const unsigned kControlMsgSize =
CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(struct ucred));
static bool
CrashHandler(const void* crash_context, size_t crash_context_size,
void* context) {
const int fd = (int) context;
int fds[2];
pipe(fds);
struct kernel_msghdr msg = {0};
struct kernel_iovec iov;
iov.iov_base = const_cast<void*>(crash_context);
iov.iov_len = crash_context_size;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
char cmsg[kControlMsgSize];
memset(cmsg, 0, kControlMsgSize);
msg.msg_control = cmsg;
msg.msg_controllen = sizeof(cmsg);
struct cmsghdr *hdr = CMSG_FIRSTHDR(&msg);
hdr->cmsg_level = SOL_SOCKET;
hdr->cmsg_type = SCM_RIGHTS;
hdr->cmsg_len = CMSG_LEN(sizeof(int));
*((int*) CMSG_DATA(hdr)) = fds[1];
hdr = CMSG_NXTHDR((struct msghdr*) &msg, hdr);
hdr->cmsg_level = SOL_SOCKET;
hdr->cmsg_type = SCM_CREDENTIALS;
hdr->cmsg_len = CMSG_LEN(sizeof(struct ucred));
struct ucred *cred = reinterpret_cast<struct ucred*>(CMSG_DATA(hdr));
cred->uid = getuid();
cred->gid = getgid();
cred->pid = getpid();
HANDLE_EINTR(sys_sendmsg(fd, &msg, 0));
sys_close(fds[1]);
char b;
HANDLE_EINTR(sys_read(fds[0], &b, 1));
return true;
}
TEST(ExceptionHandlerTest, ExternalDumper) {
int fds[2];
ASSERT_NE(socketpair(AF_UNIX, SOCK_DGRAM, 0, fds), -1);
static const int on = 1;
setsockopt(fds[0], SOL_SOCKET, SO_PASSCRED, &on, sizeof(on));
setsockopt(fds[1], SOL_SOCKET, SO_PASSCRED, &on, sizeof(on));
const pid_t child = fork();
if (child == 0) {
close(fds[0]);
ExceptionHandler handler("/tmp", NULL, NULL, (void*) fds[1], true);
handler.set_crash_handler(CrashHandler);
*reinterpret_cast<int*>(NULL) = 0;
}
close(fds[1]);
struct msghdr msg = {0};
struct iovec iov;
static const unsigned kCrashContextSize =
sizeof(ExceptionHandler::CrashContext);
char context[kCrashContextSize];
char control[kControlMsgSize];
iov.iov_base = context;
iov.iov_len = kCrashContextSize;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = control;
msg.msg_controllen = kControlMsgSize;
const ssize_t n = HANDLE_EINTR(recvmsg(fds[0], &msg, 0));
ASSERT_EQ(n, kCrashContextSize);
ASSERT_EQ(msg.msg_controllen, kControlMsgSize);
ASSERT_EQ(msg.msg_flags, 0);
pid_t crashing_pid = -1;
int signal_fd = -1;
for (struct cmsghdr *hdr = CMSG_FIRSTHDR(&msg); hdr;
hdr = CMSG_NXTHDR(&msg, hdr)) {
if (hdr->cmsg_level != SOL_SOCKET)
continue;
if (hdr->cmsg_type == SCM_RIGHTS) {
const unsigned len = hdr->cmsg_len -
(((uint8_t*)CMSG_DATA(hdr)) - (uint8_t*)hdr);
ASSERT_EQ(len, sizeof(int));
signal_fd = *((int *) CMSG_DATA(hdr));
} else if (hdr->cmsg_type == SCM_CREDENTIALS) {
const struct ucred *cred =
reinterpret_cast<struct ucred*>(CMSG_DATA(hdr));
crashing_pid = cred->pid;
}
}
ASSERT_NE(crashing_pid, -1);
ASSERT_NE(signal_fd, -1);
char templ[] = "/tmp/exception-handler-unittest-XXXXXX";
mktemp(templ);
ASSERT_TRUE(WriteMinidump(templ, crashing_pid, context,
kCrashContextSize));
static const char b = 0;
HANDLE_EINTR(write(signal_fd, &b, 1));
int status;
ASSERT_NE(HANDLE_EINTR(waitpid(child, &status, 0)), -1);
ASSERT_TRUE(WIFSIGNALED(status));
ASSERT_EQ(WTERMSIG(status), SIGSEGV);
struct stat st;
ASSERT_EQ(stat(templ, &st), 0);
ASSERT_GT(st.st_size, 0u);
unlink(templ);
}

View File

@@ -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

View File

@@ -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__

View File

@@ -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(&regs, 0, sizeof(regs));
if (threads->GetRegisters(thread_info.pid, &regs)) {
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;
}

View File

@@ -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, &regs)) {
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(&regs, 0, sizeof(regs));
if (!thread_lister->GetRegisters(thread_info.pid, &regs)) {
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(), &regs, &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

View File

@@ -1,73 +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_MINIDUMP_GENERATOR_H__
#define CLIENT_LINUX_HANDLER_MINIDUMP_GENERATOR_H__
#include <stdint.h>
#include "google_breakpad/common/breakpad_types.h"
#include "processor/scoped_ptr.h"
struct sigcontext;
namespace google_breakpad {
//
// MinidumpGenerator
//
// Write a minidump to file based on the signo and sig_ctx.
// A minidump generator should be created before any exception happen.
//
class MinidumpGenerator {
public:
MinidumpGenerator();
~MinidumpGenerator();
// Write minidump.
bool WriteMinidumpToFile(const char *file_pathname,
int signo,
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
#endif // CLIENT_LINUX_HANDLER_MINIDUMP_GENERATOR_H__

View File

@@ -1,86 +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/minidump_generator.h"
using namespace google_breakpad;
// Thread use this to see if it should stop working.
static bool should_exit = false;
static void foo2(int arg) {
// Stack variable, used for debugging stack dumps.
int c = arg;
c = 0xcccccccc;
while (!should_exit)
sleep(1);
}
static void foo(int arg) {
// Stack variable, used for debugging stack dumps.
int b = arg;
b = 0xbbbbbbbb;
foo2(b);
}
static void *thread_main(void *) {
// Stack variable, used for debugging stack dumps.
int a = 0xaaaaaaaa;
foo(a);
return NULL;
}
static void CreateThread(int num) {
pthread_t h;
for (int i = 0; i < num; ++i) {
pthread_create(&h, NULL, thread_main, NULL);
pthread_detach(h);
}
}
int main(int argc, char *argv[]) {
CreateThread(10);
google_breakpad::MinidumpGenerator mg;
if (mg.WriteMinidumpToFile("minidump_test.out", -1, 0, NULL))
printf("Succeeded written minidump\n");
else
printf("Failed to write minidump\n");
should_exit = true;
return 0;
}