aboutsummaryrefslogtreecommitdiff
path: root/src/client/linux
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/linux')
-rw-r--r--src/client/linux/handler/Makefile68
-rw-r--r--src/client/linux/handler/exception_handler.cc431
-rw-r--r--src/client/linux/handler/exception_handler.h142
-rw-r--r--src/client/linux/handler/exception_handler_test.cc124
-rw-r--r--src/client/linux/handler/exception_handler_unittest.cc256
-rw-r--r--src/client/linux/handler/linux_thread.cc411
-rw-r--r--src/client/linux/handler/linux_thread.h204
-rw-r--r--src/client/linux/handler/linux_thread_test.cc224
-rw-r--r--src/client/linux/handler/minidump_generator.cc816
-rw-r--r--src/client/linux/minidump_writer/directory_reader.h105
-rw-r--r--src/client/linux/minidump_writer/directory_reader_unittest.cc77
-rw-r--r--src/client/linux/minidump_writer/line_reader.h130
-rw-r--r--src/client/linux/minidump_writer/line_reader_unittest.cc184
-rw-r--r--src/client/linux/minidump_writer/linux_dumper.cc419
-rw-r--r--src/client/linux/minidump_writer/linux_dumper.h142
-rw-r--r--src/client/linux/minidump_writer/linux_dumper_unittest.cc118
-rw-r--r--src/client/linux/minidump_writer/minidump_writer.cc872
-rw-r--r--src/client/linux/minidump_writer/minidump_writer.h (renamed from src/client/linux/handler/minidump_generator.h)52
-rw-r--r--src/client/linux/minidump_writer/minidump_writer_unittest.cc (renamed from src/client/linux/handler/minidump_test.cc)85
19 files changed, 2668 insertions, 2192 deletions
diff --git a/src/client/linux/handler/Makefile b/src/client/linux/handler/Makefile
index 73be56d8..7fcf1b21 100644
--- a/src/client/linux/handler/Makefile
+++ b/src/client/linux/handler/Makefile
@@ -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
-
-THREAD_TEST_SRC=linux_thread_test.cc
-MINIDUMP_TEST_SRC=minidump_test.cc
-EXCEPTION_TEST_SRC=exception_handler_test.cc
-
-THREAD_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o,$(THREAD_SRC))
-SHARE_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o,$(SHARE_SRC))
-HANDLER_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o,$(HANDLER_SRC))
-SHARE_C_OBJ=$(patsubst %.c,$(OBJ_DIR)/%.o,$(SHARE_C_SRC)) md5.o
-THREAD_TEST_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o, $(THREAD_TEST_SRC))\
- $(THREAD_OBJ)
-MINIDUMP_TEST_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o, $(MINIDUMP_TEST_SRC))\
- $(THREAD_OBJ) $(SHARE_OBJ) $(SHARE_C_OBJ)
-EXCEPTION_TEST_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o, $(EXCEPTION_TEST_SRC))\
- $(THREAD_OBJ) $(SHARE_OBJ) $(SHARE_C_OBJ) $(HANDLER_OBJ)
-
-BIN=$(BIN_DIR)/minidump_test\
- $(BIN_DIR)/linux_thread_test\
- $(BIN_DIR)/exception_handler_test
+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
-.PHONY:all clean
+TEST_C_SRC = ../../../common/convert_UTF.c
-all:$(BIN)
+TEST_CC_OBJ=$(patsubst %.cc, $(OBJ_DIR)/%.o,$(TEST_CC_SRC))
+TEST_C_OBJ=$(patsubst %.c, $(OBJ_DIR)/%.o, $(TEST_C_SRC))
-$(BIN_DIR)/linux_thread_test:$(THREAD_TEST_OBJ)
- $(CXX) $(CXXFLAGS) $(LDFLAGS) $^ -o $@
+LINUX_CLIENT_BIN=$(BIN_DIR)/linux_client_test
-$(BIN_DIR)/minidump_test:$(MINIDUMP_TEST_OBJ)
- $(CXX) $(CXXFLAGS) $(LDFLAGS) $^ -o $@
+BIN=$(LINUX_CLIENT_BIN)
-$(BIN_DIR)/exception_handler_test:$(EXCEPTION_TEST_OBJ)
- $(CXX) $(CXXFLAGS) $(LDFLAGS) $^ -o $@
+.PHONY:all clean
+
+check:$(BIN)
+ $(LINUX_CLIENT_BIN)
-md5.o:../../../common/md5.c
- $(CC) $(CXXFLAGS) -c $^
+all:$(BIN)
+
+$(BIN_DIR)/linux_client_test:$(TEST_CC_OBJ) $(TEST_C_OBJ)
+ $(CXX) $(CXXFLAGS) $(LDFLAGS) $^ -o $@
clean:
- rm -f $(BIN) *.o *.dmp
+ rm -f $(BIN) $(TEST_CC_OBJ) $(TEST_C_OBJ)
diff --git a/src/client/linux/handler/exception_handler.cc b/src/client/linux/handler/exception_handler.cc
index 43f513c0..8738b27d 100644
--- a/src/client/linux/handler/exception_handler.cc
+++ b/src/client/linux/handler/exception_handler.cc
@@ -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,227 +116,207 @@ 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]);
-}
+ if (sigaltstack(&stack, NULL) == -1)
+ return false;
-void ExceptionHandler::SetupHandler(int signo) {
+ struct sigaction sa;
+ memset(&sa, 0, sizeof(sa));
+ sigemptyset(&sa.sa_mask);
- // 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];
+ // 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]);
- if (sigaction(signo, &act_, old_act) < 0)
- return;
-}
+ sa.sa_sigaction = SignalHandler;
+ sa.sa_flags = SA_ONSTACK | SA_SIGINFO;
-void ExceptionHandler::TeardownHandler(int signo) {
- TeardownHandler(signo, NULL);
+ 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));
+ }
}
-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));
+// 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();
}
-void ExceptionHandler::TeardownAllHandler() {
- for (size_t i = 0; i < sizeof(SigTable) / sizeof(SigTable[0]); ++i) {
- TeardownHandler(SigTable[i]);
+// Runs before crashing: normal context.
+void ExceptionHandler::UpdateNextID() {
+ GUID guid;
+ char guid_str[kGUIDStringLength + 1];
+ if (CreateGUID(&guid) && GUIDToString(&guid, guid_str, sizeof(guid_str))) {
+ next_minidump_id_ = guid_str;
+ next_minidump_id_c_ = next_minidump_id_.c_str();
+
+ char minidump_path[PATH_MAX];
+ snprintf(minidump_path, sizeof(minidump_path), "%s/%s.dmp",
+ dump_path_c_,
+ guid_str);
+
+ next_minidump_path_ = minidump_path;
+ next_minidump_path_c_ = next_minidump_path_.c_str();
}
}
+// This function runs in a compromised context: see the top of the file.
+// Runs on the crashing thread.
// 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));
+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_);
- 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);
- }
+ 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_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_);
+
+ // 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;
}
-bool ExceptionHandler::InternalWriteMinidump(int signo,
- uintptr_t sighandler_ebp,
- struct sigcontext **sig_ctx) {
+// 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;
- 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");
- }
+ // 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();
- success = minidump_generator_.WriteMinidumpToFile(
- next_minidump_path_c_, signo, sighandler_ebp, sig_ctx);
+ if (crash_handler_ && crash_handler_(&context, sizeof(context),
+ callback_context_))
+ return true;
- // Unblock the signals.
- if (blocked) {
- sigprocmask(SIG_SETMASK, &sig_old, NULL);
+ 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);
+ callback_context_, success);
+
return success;
}
-void ExceptionHandler::UpdateNextID() {
- GUID guid;
- char guid_str[kGUIDStringLength + 1];
- if (CreateGUID(&guid) && GUIDToString(&guid, guid_str, sizeof(guid_str))) {
- next_minidump_id_ = guid_str;
- next_minidump_id_c_ = next_minidump_id_.c_str();
-
- char minidump_path[PATH_MAX];
- snprintf(minidump_path, sizeof(minidump_path), "%s/%s.dmp",
- dump_path_c_,
- guid_str);
-
- next_minidump_path_ = minidump_path;
- next_minidump_path_c_ = next_minidump_path_.c_str();
- }
+// 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
diff --git a/src/client/linux/handler/exception_handler.h b/src/client/linux/handler/exception_handler.h
index 6ea09a11..b579a6a9 100644
--- a/src/client/linux/handler/exception_handler.h
+++ b/src/client/linux/handler/exception_handler.h
@@ -1,8 +1,6 @@
-// Copyright (c) 2006, Google Inc.
+// Copyright (c) 2009, Google Inc.
// All rights reserved.
//
-// Author: Li Liu
-//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
@@ -29,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__
-
-#include <pthread.h>
+#ifndef CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_
+#define CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_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);
- 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();
-
- // 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();
+ // 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:
- FilterCallback filter_;
- MinidumpCallback callback_;
- void *callback_context_;
+ bool InstallHandlers();
+ void UninstallHandlers();
+ void PreresolveSymbols();
- // 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_;
+ 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);
- // The basename of the next minidump to be written, without the extension
- string next_minidump_id_;
+ const FilterCallback filter_;
+ const MinidumpCallback callback_;
+ void* const callback_context_;
- // 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_
diff --git a/src/client/linux/handler/exception_handler_test.cc b/src/client/linux/handler/exception_handler_test.cc
deleted file mode 100644
index 2d94553d..00000000
--- a/src/client/linux/handler/exception_handler_test.cc
+++ /dev/null
@@ -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;
-}
diff --git a/src/client/linux/handler/exception_handler_unittest.cc b/src/client/linux/handler/exception_handler_unittest.cc
new file mode 100644
index 00000000..157c495a
--- /dev/null
+++ b/src/client/linux/handler/exception_handler_unittest.cc
@@ -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);
+}
diff --git a/src/client/linux/handler/linux_thread.cc b/src/client/linux/handler/linux_thread.cc
index c8ac4926..e69de29b 100644
--- a/src/client/linux/handler/linux_thread.cc
+++ b/src/client/linux/handler/linux_thread.cc
@@ -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
diff --git a/src/client/linux/handler/linux_thread.h b/src/client/linux/handler/linux_thread.h
deleted file mode 100644
index f738c2e0..00000000
--- a/src/client/linux/handler/linux_thread.h
+++ /dev/null
@@ -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__
diff --git a/src/client/linux/handler/linux_thread_test.cc b/src/client/linux/handler/linux_thread_test.cc
deleted file mode 100644
index aeb5e64c..00000000
--- a/src/client/linux/handler/linux_thread_test.cc
+++ /dev/null
@@ -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;
-}
diff --git a/src/client/linux/handler/minidump_generator.cc b/src/client/linux/handler/minidump_generator.cc
deleted file mode 100644
index d172092c..00000000
--- a/src/client/linux/handler/minidump_generator.cc
+++ /dev/null
@@ -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
diff --git a/src/client/linux/minidump_writer/directory_reader.h b/src/client/linux/minidump_writer/directory_reader.h
new file mode 100644
index 00000000..4698b7e5
--- /dev/null
+++ b/src/client/linux/minidump_writer/directory_reader.h
@@ -0,0 +1,105 @@
+// Copyright (c) 2009, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef CLIENT_LINUX_MINIDUMP_WRITER_DIRECTORY_READER_H_
+#define CLIENT_LINUX_MINIDUMP_WRITER_DIRECTORY_READER_H_
+
+#include <stdint.h>
+#include <unistd.h>
+#include <limits.h>
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+
+#include "common/linux/linux_syscall_support.h"
+
+namespace google_breakpad {
+
+// A class for enumerating a directory without using diropen/readdir or other
+// functions which may allocate memory.
+class DirectoryReader {
+ public:
+ DirectoryReader(int fd)
+ : fd_(fd),
+ buf_used_(0) {
+ }
+
+ // Return the next entry from the directory
+ // name: (output) the NUL terminated entry name
+ //
+ // Returns true iff successful (false on EOF).
+ //
+ // After calling this, one must call |PopEntry| otherwise you'll get the same
+ // entry over and over.
+ bool GetNextEntry(const char** name) {
+ struct kernel_dirent* const dent =
+ reinterpret_cast<kernel_dirent*>(buf_);
+
+ if (buf_used_ == 0) {
+ // need to read more entries.
+ const int n = sys_getdents(fd_, dent, sizeof(buf_));
+ if (n < 0) {
+ return false;
+ } else if (n == 0) {
+ hit_eof_ = true;
+ } else {
+ buf_used_ += n;
+ }
+ }
+
+ if (buf_used_ == 0 && hit_eof_)
+ return false;
+
+ assert(buf_used_ > 0);
+
+ *name = dent->d_name;
+ return true;
+ }
+
+ void PopEntry() {
+ if (!buf_used_)
+ return;
+
+ const struct kernel_dirent* const dent =
+ reinterpret_cast<kernel_dirent*>(buf_);
+
+ buf_used_ -= dent->d_reclen;
+ memmove(buf_, buf_ + dent->d_reclen, buf_used_);
+ }
+
+ private:
+ const int fd_;
+ bool hit_eof_;
+ unsigned buf_used_;
+ uint8_t buf_[sizeof(struct kernel_dirent) + NAME_MAX + 1];
+};
+
+} // namespace google_breakpad
+
+#endif // CLIENT_LINUX_MINIDUMP_WRITER_DIRECTORY_READER_H_
diff --git a/src/client/linux/minidump_writer/directory_reader_unittest.cc b/src/client/linux/minidump_writer/directory_reader_unittest.cc
new file mode 100644
index 00000000..3034e619
--- /dev/null
+++ b/src/client/linux/minidump_writer/directory_reader_unittest.cc
@@ -0,0 +1,77 @@
+// Copyright (c) 2009, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <set>
+#include <string>
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <sys/types.h>
+
+#include "client/linux/minidump_writer/directory_reader.h"
+#include "breakpad_googletest_includes.h"
+
+using namespace google_breakpad;
+
+namespace {
+typedef testing::Test DirectoryReaderTest;
+}
+
+TEST(DirectoryReaderTest, CompareResults) {
+ std::set<std::string> dent_set;
+
+ DIR *const dir = opendir("/proc/self");
+ ASSERT_TRUE(dir != NULL);
+
+ struct dirent* dent;
+ while ((dent = readdir(dir)))
+ dent_set.insert(dent->d_name);
+
+ closedir(dir);
+
+ const int fd = open("/proc/self", O_DIRECTORY | O_RDONLY);
+ ASSERT_GE(fd, 0);
+
+ DirectoryReader dir_reader(fd);
+ unsigned seen = 0;
+
+ const char* name;
+ while (dir_reader.GetNextEntry(&name)) {
+ ASSERT_TRUE(dent_set.find(name) != dent_set.end());
+ seen++;
+ dir_reader.PopEntry();
+ }
+
+ ASSERT_TRUE(dent_set.find("status") != dent_set.end());
+ ASSERT_TRUE(dent_set.find("stat") != dent_set.end());
+ ASSERT_TRUE(dent_set.find("cmdline") != dent_set.end());
+
+ ASSERT_EQ(dent_set.size(), seen);
+ close(fd);
+}
diff --git a/src/client/linux/minidump_writer/line_reader.h b/src/client/linux/minidump_writer/line_reader.h
new file mode 100644
index 00000000..5c0a1154
--- /dev/null
+++ b/src/client/linux/minidump_writer/line_reader.h
@@ -0,0 +1,130 @@
+// Copyright (c) 2009, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef CLIENT_LINUX_MINIDUMP_WRITER_LINE_READER_H_
+#define CLIENT_LINUX_MINIDUMP_WRITER_LINE_READER_H_
+
+#include <stdint.h>
+#include <assert.h>
+#include <string.h>
+
+#include "common/linux/linux_syscall_support.h"
+
+namespace google_breakpad {
+
+// A class for reading a file, line by line, without using fopen/fgets or other
+// functions which may allocate memory.
+class LineReader {
+ public:
+ LineReader(int fd)
+ : fd_(fd),
+ hit_eof_(false),
+ buf_used_(0) {
+ }
+
+ // The maximum length of a line.
+ static const size_t kMaxLineLen = 512;
+
+ // Return the next line from the file.
+ // line: (output) a pointer to the start of the line. The line is NUL
+ // terminated.
+ // len: (output) the length of the line (not inc the NUL byte)
+ //
+ // Returns true iff successful (false on EOF).
+ //
+ // One must call |PopLine| after this function, otherwise you'll continue to
+ // get the same line over and over.
+ bool GetNextLine(const char **line, unsigned *len) {
+ for (;;) {
+ if (buf_used_ == 0 && hit_eof_)
+ return false;
+
+ for (unsigned i = 0; i < buf_used_; ++i) {
+ if (buf_[i] == '\n' || buf_[i] == 0) {
+ buf_[i] = 0;
+ *len = i;
+ *line = buf_;
+ return true;
+ }
+ }
+
+ if (buf_used_ == sizeof(buf_)) {
+ // we scanned the whole buffer and didn't find an end-of-line marker.
+ // This line is too long to process.
+ return false;
+ }
+
+ // We didn't find any end-of-line terminators in the buffer. However, if
+ // this is the last line in the file it might not have one:
+ if (hit_eof_) {
+ assert(buf_used_);
+ // There's room for the NUL because of the buf_used_ == sizeof(buf_)
+ // check above.
+ buf_[buf_used_] = 0;
+ *len = buf_used_;
+ buf_used_ += 1; // since we appended the NUL.
+ *line = buf_;
+ return true;
+ }
+
+ // Otherwise, we should pull in more data from the file
+ const ssize_t n = sys_read(fd_, buf_ + buf_used_,
+ sizeof(buf_) - buf_used_);
+ if (n < 0) {
+ return false;
+ } else if (n == 0) {
+ hit_eof_ = true;
+ } else {
+ buf_used_ += n;
+ }
+
+ // At this point, we have either set the hit_eof_ flag, or we have more
+ // data to process...
+ }
+ }
+
+ void PopLine(unsigned len) {
+ // len doesn't include the NUL byte at the end.
+
+ assert(buf_used_ >= len + 1);
+ buf_used_ -= len + 1;
+ memmove(buf_, buf_ + len + 1, buf_used_);
+ }
+
+ private:
+ const int fd_;
+
+ bool hit_eof_;
+ unsigned buf_used_;
+ char buf_[kMaxLineLen];
+};
+
+} // namespace google_breakpad
+
+#endif // CLIENT_LINUX_MINIDUMP_WRITER_LINE_READER_H_
diff --git a/src/client/linux/minidump_writer/line_reader_unittest.cc b/src/client/linux/minidump_writer/line_reader_unittest.cc
new file mode 100644
index 00000000..222a098e
--- /dev/null
+++ b/src/client/linux/minidump_writer/line_reader_unittest.cc
@@ -0,0 +1,184 @@
+// Copyright (c) 2009, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include "client/linux/minidump_writer/line_reader.h"
+#include "breakpad_googletest_includes.h"
+
+using namespace google_breakpad;
+
+static int TemporaryFile() {
+ static const char templ[] = "/tmp/line-reader-unittest-XXXXXX";
+ char templ_copy[sizeof(templ)];
+ memcpy(templ_copy, templ, sizeof(templ));
+ const int fd = mkstemp(templ_copy);
+ if (fd >= 0)
+ unlink(templ_copy);
+
+ return fd;
+}
+
+namespace {
+typedef testing::Test LineReaderTest;
+}
+
+TEST(LineReaderTest, EmptyFile) {
+ const int fd = TemporaryFile();
+ LineReader reader(fd);
+
+ const char *line;
+ unsigned len;
+ ASSERT_FALSE(reader.GetNextLine(&line, &len));
+
+ close(fd);
+}
+
+TEST(LineReaderTest, OneLineTerminated) {
+ const int fd = TemporaryFile();
+ write(fd, "a\n", 2);
+ lseek(fd, 0, SEEK_SET);
+ LineReader reader(fd);
+
+ const char *line;
+ unsigned len;
+ ASSERT_TRUE(reader.GetNextLine(&line, &len));
+ ASSERT_EQ(len, 1);
+ ASSERT_EQ(line[0], 'a');
+ ASSERT_EQ(line[1], 0);
+ reader.PopLine(len);
+
+ ASSERT_FALSE(reader.GetNextLine(&line, &len));
+
+ close(fd);
+}
+
+TEST(LineReaderTest, OneLine) {
+ const int fd = TemporaryFile();
+ write(fd, "a", 1);
+ lseek(fd, 0, SEEK_SET);
+ LineReader reader(fd);
+
+ const char *line;
+ unsigned len;
+ ASSERT_TRUE(reader.GetNextLine(&line, &len));
+ ASSERT_EQ(len, 1);
+ ASSERT_EQ(line[0], 'a');
+ ASSERT_EQ(line[1], 0);
+ reader.PopLine(len);
+
+ ASSERT_FALSE(reader.GetNextLine(&line, &len));
+
+ close(fd);
+}
+
+TEST(LineReaderTest, TwoLinesTerminated) {
+ const int fd = TemporaryFile();
+ write(fd, "a\nb\n", 4);
+ lseek(fd, 0, SEEK_SET);
+ LineReader reader(fd);
+
+ const char *line;
+ unsigned len;
+ ASSERT_TRUE(reader.GetNextLine(&line, &len));
+ ASSERT_EQ(len, 1);
+ ASSERT_EQ(line[0], 'a');
+ ASSERT_EQ(line[1], 0);
+ reader.PopLine(len);
+
+ ASSERT_TRUE(reader.GetNextLine(&line, &len));
+ ASSERT_EQ(len, 1);
+ ASSERT_EQ(line[0], 'b');
+ ASSERT_EQ(line[1], 0);
+ reader.PopLine(len);
+
+ ASSERT_FALSE(reader.GetNextLine(&line, &len));
+
+ close(fd);
+}
+
+TEST(LineReaderTest, TwoLines) {
+ const int fd = TemporaryFile();
+ write(fd, "a\nb", 3);
+ lseek(fd, 0, SEEK_SET);
+ LineReader reader(fd);
+
+ const char *line;
+ unsigned len;
+ ASSERT_TRUE(reader.GetNextLine(&line, &len));
+ ASSERT_EQ(len, 1);
+ ASSERT_EQ(line[0], 'a');
+ ASSERT_EQ(line[1], 0);
+ reader.PopLine(len);
+
+ ASSERT_TRUE(reader.GetNextLine(&line, &len));
+ ASSERT_EQ(len, 1);
+ ASSERT_EQ(line[0], 'b');
+ ASSERT_EQ(line[1], 0);
+ reader.PopLine(len);
+
+ ASSERT_FALSE(reader.GetNextLine(&line, &len));
+
+ close(fd);
+}
+
+TEST(LineReaderTest, MaxLength) {
+ const int fd = TemporaryFile();
+ char l[LineReader::kMaxLineLen - 1];
+ memset(l, 'a', sizeof(l));
+ write(fd, l, sizeof(l));
+ lseek(fd, 0, SEEK_SET);
+ LineReader reader(fd);
+
+ const char *line;
+ unsigned len;
+ ASSERT_TRUE(reader.GetNextLine(&line, &len));
+ ASSERT_EQ(len, sizeof(l));
+ ASSERT_TRUE(memcmp(l, line, sizeof(l)) == 0);
+ ASSERT_EQ(line[len], 0);
+
+ close(fd);
+}
+
+TEST(LineReaderTest, TooLong) {
+ const int fd = TemporaryFile();
+ char l[LineReader::kMaxLineLen];
+ memset(l, 'a', sizeof(l));
+ write(fd, l, sizeof(l));
+ lseek(fd, 0, SEEK_SET);
+ LineReader reader(fd);
+
+ const char *line;
+ unsigned len;
+ ASSERT_FALSE(reader.GetNextLine(&line, &len));
+
+ close(fd);
+}
diff --git a/src/client/linux/minidump_writer/linux_dumper.cc b/src/client/linux/minidump_writer/linux_dumper.cc
new file mode 100644
index 00000000..e821db7d
--- /dev/null
+++ b/src/client/linux/minidump_writer/linux_dumper.cc
@@ -0,0 +1,419 @@
+// Copyright (c) 2009, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// This code deals with the mechanics of getting information about a crashed
+// process. Since this code may run in a compromised address space, the same
+// rules apply as detailed at the top of minidump_writer.h: no libc calls and
+// use the alternative allocator.
+
+#include "client/linux/minidump_writer/linux_dumper.h"
+
+#include <assert.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <sys/types.h>
+#include <sys/ptrace.h>
+#include <sys/wait.h>
+
+#include "client/linux/minidump_writer/directory_reader.h"
+#include "client/linux/minidump_writer/line_reader.h"
+#include "common/linux/linux_libc_support.h"
+#include "common/linux/linux_syscall_support.h"
+
+// Suspend a thread by attaching to it.
+static bool SuspendThread(pid_t pid) {
+ // This may fail if the thread has just died or debugged.
+ errno = 0;
+ if (sys_ptrace(PTRACE_ATTACH, pid, NULL, NULL) != 0 &&
+ errno != 0) {
+ return false;
+ }
+ while (sys_waitpid(pid, NULL, __WALL) < 0) {
+ if (errno != EINTR) {
+ sys_ptrace(PTRACE_DETACH, pid, NULL, NULL);
+ return false;
+ }
+ }
+ return true;
+}
+
+// Resume a thread by detaching from it.
+static bool ResumeThread(pid_t pid) {
+ return sys_ptrace(PTRACE_DETACH, pid, NULL, NULL) >= 0;
+}
+
+namespace google_breakpad {
+
+LinuxDumper::LinuxDumper(int pid)
+ : pid_(pid),
+ threads_suspened_(false),
+ threads_(&allocator_, 8),
+ mappings_(&allocator_) {
+}
+
+bool LinuxDumper::Init() {
+ return EnumerateThreads(&threads_) &&
+ EnumerateMappings(&mappings_);
+}
+
+bool LinuxDumper::ThreadsSuspend() {
+ if (threads_suspened_)
+ return true;
+ bool good = true;
+ for (size_t i = 0; i < threads_.size(); ++i)
+ good &= SuspendThread(threads_[i]);
+ threads_suspened_ = true;
+ return good;
+}
+
+bool LinuxDumper::ThreadsResume() {
+ if (!threads_suspened_)
+ return false;
+ bool good = true;
+ for (size_t i = 0; i < threads_.size(); ++i)
+ good &= ResumeThread(threads_[i]);
+ threads_suspened_ = false;
+ return good;
+}
+
+void
+LinuxDumper::BuildProcPath(char* path, pid_t pid, const char* node) const {
+ assert(path);
+ if (!path) {
+ return;
+ }
+
+ path[0] = '\0';
+
+ const unsigned pid_len = my_int_len(pid);
+
+ assert(node);
+ if (!node) {
+ return;
+ }
+
+ size_t node_len = my_strlen(node);
+ assert(node_len < NAME_MAX);
+ if (node_len >= NAME_MAX) {
+ return;
+ }
+
+ assert(node_len > 0);
+ if (node_len == 0) {
+ return;
+ }
+
+ assert(pid > 0);
+ if (pid <= 0) {
+ return;
+ }
+
+ const size_t total_length = 6 + pid_len + 1 + node_len;
+
+ assert(total_length < NAME_MAX);
+ if (total_length >= NAME_MAX) {
+ return;
+ }
+
+ memcpy(path, "/proc/", 6);
+ my_itos(path + 6, pid, pid_len);
+ memcpy(path + 6 + pid_len, "/", 1);
+ memcpy(path + 6 + pid_len + 1, node, node_len);
+ memcpy(path + total_length, "\0", 1);
+}
+
+void*
+LinuxDumper::FindBeginningOfLinuxGateSharedLibrary(const pid_t pid) const {
+ char auxv_path[80];
+ BuildProcPath(auxv_path, pid, "auxv");
+
+ // If BuildProcPath errors out due to invalid input, we'll handle it when
+ // we try to sys_open the file.
+
+ // Find the AT_SYSINFO_EHDR entry for linux-gate.so
+ // See http://www.trilithium.com/johan/2005/08/linux-gate/ for more
+ // information.
+ int fd = sys_open(auxv_path, O_RDONLY, 0);
+ if (fd < 0) {
+ return NULL;
+ }
+
+ elf_aux_entry one_aux_entry;
+ while (sys_read(fd,
+ &one_aux_entry,
+ sizeof(elf_aux_entry)) == sizeof(elf_aux_entry) &&
+ one_aux_entry.a_type != AT_NULL) {
+ if (one_aux_entry.a_type == AT_SYSINFO_EHDR) {
+ close(fd);
+ return reinterpret_cast<void*>(one_aux_entry.a_un.a_val);
+ }
+ }
+ close(fd);
+ return NULL;
+}
+
+bool
+LinuxDumper::EnumerateMappings(wasteful_vector<MappingInfo*>* result) const {
+ char maps_path[80];
+ BuildProcPath(maps_path, pid_, "maps");
+
+ // linux_gate_loc is the beginning of the kernel's mapping of
+ // linux-gate.so in the process. It doesn't actually show up in the
+ // maps list as a filename, so we use the aux vector to find it's
+ // load location and special case it's entry when creating the list
+ // of mappings.
+ const void* linux_gate_loc;
+ linux_gate_loc = FindBeginningOfLinuxGateSharedLibrary(pid_);
+
+ const int fd = sys_open(maps_path, O_RDONLY, 0);
+ if (fd < 0)
+ return false;
+ LineReader* const line_reader = new(allocator_) LineReader(fd);
+
+ const char* line;
+ unsigned line_len;
+ while (line_reader->GetNextLine(&line, &line_len)) {
+ uintptr_t start_addr, end_addr, offset;
+
+ const char* i1 = my_read_hex_ptr(&start_addr, line);
+ if (*i1 == '-') {
+ const char* i2 = my_read_hex_ptr(&end_addr, i1 + 1);
+ if (*i2 == ' ') {
+ const char* i3 = my_read_hex_ptr(&offset, i2 + 6 /* skip ' rwxp ' */);
+ if (*i3 == ' ') {
+ MappingInfo* const module = new(allocator_) MappingInfo;
+ memset(module, 0, sizeof(MappingInfo));
+ module->start_addr = start_addr;
+ module->size = end_addr - start_addr;
+ module->offset = offset;
+ const char* name = NULL;
+ // Only copy name if the name is a valid path name, or if
+ // we've found the VDSO image
+ if ((name = my_strchr(line, '/')) != NULL) {
+ const unsigned l = my_strlen(name);
+ if (l < sizeof(module->name))
+ memcpy(module->name, name, l);
+ } else if (linux_gate_loc &&
+ reinterpret_cast<void*>(module->start_addr) ==
+ linux_gate_loc) {
+ memcpy(module->name,
+ kLinuxGateLibraryName,
+ my_strlen(kLinuxGateLibraryName));
+ module->offset = 0;
+ }
+ result->push_back(module);
+ }
+ }
+ }
+ line_reader->PopLine(line_len);
+ }
+
+ sys_close(fd);
+
+ return result->size() > 0;
+}
+
+// Parse /proc/$pid/task to list all the threads of the process identified by
+// pid.
+bool LinuxDumper::EnumerateThreads(wasteful_vector<pid_t>* result) const {
+ char task_path[80];
+ BuildProcPath(task_path, pid_, "task");
+
+ const int fd = sys_open(task_path, O_RDONLY | O_DIRECTORY, 0);
+ if (fd < 0)
+ return false;
+ DirectoryReader* dir_reader = new(allocator_) DirectoryReader(fd);
+
+ // The directory may contain duplicate entries which we filter by assuming
+ // that they are consecutive.
+ int last_tid = -1;
+ const char* dent_name;
+ while (dir_reader->GetNextEntry(&dent_name)) {
+ if (my_strcmp(dent_name, ".") &&
+ my_strcmp(dent_name, "..")) {
+ int tid = 0;
+ if (my_strtoui(&tid, dent_name) &&
+ last_tid != tid) {
+ last_tid = tid;
+ result->push_back(tid);
+ }
+ }
+ dir_reader->PopEntry();
+ }
+
+ sys_close(fd);
+ return true;
+}
+
+// Read thread info from /proc/$pid/status.
+// Fill out the |tgid|, |ppid| and |pid| members of |info|. If unavailible,
+// these members are set to -1. Returns true iff all three members are
+// availible.
+bool LinuxDumper::ThreadInfoGet(pid_t tid, ThreadInfo* info) {
+ assert(info != NULL);
+ char status_path[80];
+ BuildProcPath(status_path, tid, "status");
+
+ const int fd = open(status_path, O_RDONLY);
+ if (fd < 0)
+ return false;
+
+ LineReader* const line_reader = new(allocator_) LineReader(fd);
+ const char* line;
+ unsigned line_len;
+
+ info->ppid = info->tgid = -1;
+
+ while (line_reader->GetNextLine(&line, &line_len)) {
+ if (my_strncmp("Tgid:\t", line, 6) == 0) {
+ my_strtoui(&info->tgid, line + 6);
+ } else if (my_strncmp("PPid:\t", line, 6) == 0) {
+ my_strtoui(&info->ppid, line + 6);
+ }
+
+ line_reader->PopLine(line_len);
+ }
+
+ if (info->ppid == -1 || info->tgid == -1)
+ return false;
+
+ if (sys_ptrace(PTRACE_GETREGS, tid, NULL, &info->regs) == -1 ||
+ sys_ptrace(PTRACE_GETFPREGS, tid, NULL, &info->fpregs) == -1) {
+ return false;
+ }
+
+#if defined(__i386) || defined(__x86_64)
+ if (sys_ptrace(PTRACE_GETFPXREGS, tid, NULL, &info->fpxregs) == -1)
+ return false;
+
+ for (unsigned i = 0; i < ThreadInfo::kNumDebugRegisters; ++i) {
+ if (sys_ptrace(
+ PTRACE_PEEKUSER, tid,
+ reinterpret_cast<void*> (offsetof(struct user,
+ u_debugreg[0]) + i *
+ sizeof(debugreg_t)),
+ &info->dregs[i]) == -1) {
+ return false;
+ }
+ }
+#endif
+
+ const uint8_t* stack_pointer;
+#if defined(__i386)
+ memcpy(&stack_pointer, &info->regs.esp, sizeof(info->regs.esp));
+#elif defined(__x86_64)
+ memcpy(&stack_pointer, &info->regs.rsp, sizeof(info->regs.rsp));
+#else
+#error "This code hasn't been ported to your platform yet."
+#endif
+
+ if (!GetStackInfo(&info->stack, &info->stack_len,
+ (uintptr_t) stack_pointer))
+ return false;
+
+ return true;
+}
+
+// Get information about the stack, given the stack pointer. We don't try to
+// walk the stack since we might not have all the information needed to do
+// unwind. So we just grab, up to, 32k of stack.
+bool LinuxDumper::GetStackInfo(const void** stack, size_t* stack_len,
+ uintptr_t int_stack_pointer) {
+#if defined(__i386) || defined(__x86_64)
+ static const bool stack_grows_down = true;
+ static const uintptr_t page_size = 4096;
+#else
+#error "This code has not been ported to your platform yet."
+#endif
+ // Move the stack pointer to the bottom of the page that it's in.
+ uint8_t* const stack_pointer =
+ reinterpret_cast<uint8_t*>(int_stack_pointer & ~(page_size - 1));
+
+ // The number of bytes of stack which we try to capture.
+ static unsigned kStackToCapture = 32 * 1024;
+
+ const MappingInfo* mapping = FindMapping(stack_pointer);
+ if (!mapping)
+ return false;
+ if (stack_grows_down) {
+ const ptrdiff_t offset = stack_pointer - (uint8_t*) mapping->start_addr;
+ const ptrdiff_t distance_to_end =
+ static_cast<ptrdiff_t>(mapping->size) - offset;
+ *stack_len = distance_to_end > kStackToCapture ?
+ kStackToCapture : distance_to_end;
+ *stack = stack_pointer;
+ } else {
+ const ptrdiff_t offset = stack_pointer - (uint8_t*) mapping->start_addr;
+ *stack_len = offset > kStackToCapture ? kStackToCapture : offset;
+ *stack = stack_pointer - *stack_len;
+ }
+
+ return true;
+}
+
+// static
+void LinuxDumper::CopyFromProcess(void* dest, pid_t child, const void* src,
+ size_t length) {
+ unsigned long tmp;
+ size_t done = 0;
+ static const size_t word_size = sizeof(tmp);
+ uint8_t* const local = (uint8_t*) dest;
+ uint8_t* const remote = (uint8_t*) src;
+
+ while (done < length) {
+ const size_t l = length - done > word_size ? word_size : length - done;
+ if (sys_ptrace(PTRACE_PEEKDATA, child, remote + done, &tmp) == -1)
+ tmp = 0;
+ memcpy(local + done, &tmp, l);
+ done += l;
+ }
+}
+
+// Find the mapping which the given memory address falls in.
+const MappingInfo* LinuxDumper::FindMapping(const void* address) const {
+ const uintptr_t addr = (uintptr_t) address;
+
+ for (size_t i = 0; i < mappings_.size(); ++i) {
+ const uintptr_t start = static_cast<uintptr_t>(mappings_[i]->start_addr);
+ if (addr >= start && addr - start < mappings_[i]->size)
+ return mappings_[i];
+ }
+
+ return NULL;
+}
+
+} // namespace google_breakpad
diff --git a/src/client/linux/minidump_writer/linux_dumper.h b/src/client/linux/minidump_writer/linux_dumper.h
new file mode 100644
index 00000000..252e9ee6
--- /dev/null
+++ b/src/client/linux/minidump_writer/linux_dumper.h
@@ -0,0 +1,142 @@
+// Copyright (c) 2009, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef CLIENT_LINUX_MINIDUMP_WRITER_LINUX_DUMPER_H_
+#define CLIENT_LINUX_MINIDUMP_WRITER_LINUX_DUMPER_H_
+
+#include <elf.h>
+#include <stdint.h>
+#include <sys/user.h>
+#include <linux/limits.h>
+
+#include "common/linux/memory.h"
+
+namespace google_breakpad {
+
+typedef typeof(((struct user*) 0)->u_debugreg[0]) debugreg_t;
+
+// Typedef for our parsing of the auxv variables in /proc/pid/auxv.
+#if defined(__i386)
+typedef Elf32_auxv_t elf_aux_entry;
+#elif defined(__x86_64__)
+typedef Elf64_auxv_t elf_aux_entry;
+#endif
+// When we find the VDSO mapping in the process's address space, this
+// is the name we use for it when writing it to the minidump.
+// This should always be less than NAME_MAX!
+const char kLinuxGateLibraryName[] = "linux-gate.so";
+
+// We produce one of these structures for each thread in the crashed process.
+struct ThreadInfo {
+ pid_t tgid; // thread group id
+ pid_t ppid; // parent process
+
+ // Even on platforms where the stack grows down, the following will point to
+ // the smallest address in the stack.
+ const void* stack; // pointer to the stack area
+ size_t stack_len; // length of the stack to copy
+
+ user_regs_struct regs;
+ user_fpregs_struct fpregs;
+#if defined(__i386) || defined(__x86_64)
+ user_fpxregs_struct fpxregs;
+
+ static const unsigned kNumDebugRegisters = 8;
+ debugreg_t dregs[8];
+#endif
+};
+
+// One of these is produced for each mapping in the process (i.e. line in
+// /proc/$x/maps).
+struct MappingInfo {
+ uintptr_t start_addr;
+ size_t size;
+ size_t offset; // offset into the backed file.
+ char name[NAME_MAX];
+};
+
+class LinuxDumper {
+ public:
+ explicit LinuxDumper(pid_t pid);
+
+ // Parse the data for |threads| and |mappings|.
+ bool Init();
+
+ // Suspend/resume all threads in the given process.
+ bool ThreadsSuspend();
+ bool ThreadsResume();
+
+ // Read information about the given thread. Returns true on success. One must
+ // have called |ThreadsSuspend| first.
+ bool ThreadInfoGet(pid_t tid, ThreadInfo* info);
+
+ // These are only valid after a call to |Init|.
+ const wasteful_vector<pid_t> &threads() { return threads_; }
+ const wasteful_vector<MappingInfo*> &mappings() { return mappings_; }
+ const MappingInfo* FindMapping(const void* address) const;
+
+ // Find a block of memory to take as the stack given the top of stack pointer.
+ // stack: (output) the lowest address in the memory area
+ // stack_len: (output) the length of the memory area
+ // stack_top: the current top of the stack
+ bool GetStackInfo(const void** stack, size_t* stack_len, uintptr_t stack_top);
+
+ PageAllocator* allocator() { return &allocator_; }
+
+ // memcpy from a remote process.
+ static void CopyFromProcess(void* dest, pid_t child, const void* src,
+ size_t length);
+
+ // Builds a proc path for a certain pid for a node. path is a
+ // character array that is overwritten, and node is the final node
+ // without any slashes.
+ void BuildProcPath(char* path, pid_t pid, const char* node) const;
+
+ // Utility method to find the location of where the kernel has
+ // mapped linux-gate.so in memory(shows up in /proc/pid/maps as
+ // [vdso], but we can't guarantee that it's the only virtual dynamic
+ // shared object. Parsing the auxilary vector for AT_SYSINFO_EHDR
+ // is the safest way to go.)
+ void* FindBeginningOfLinuxGateSharedLibrary(const pid_t pid) const;
+ private:
+ bool EnumerateMappings(wasteful_vector<MappingInfo*>* result) const;
+ bool EnumerateThreads(wasteful_vector<pid_t>* result) const;
+
+ const pid_t pid_;
+
+ mutable PageAllocator allocator_;
+
+ bool threads_suspened_;
+ wasteful_vector<pid_t> threads_; // the ids of all the threads
+ wasteful_vector<MappingInfo*> mappings_; // info from /proc/<pid>/maps
+};
+
+} // namespace google_breakpad
+
+#endif // CLIENT_LINUX_HANDLER_LINUX_DUMPER_H_
diff --git a/src/client/linux/minidump_writer/linux_dumper_unittest.cc b/src/client/linux/minidump_writer/linux_dumper_unittest.cc
new file mode 100644
index 00000000..f5ed914b
--- /dev/null
+++ b/src/client/linux/minidump_writer/linux_dumper_unittest.cc
@@ -0,0 +1,118 @@
+// Copyright (c) 2009, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <unistd.h>
+
+#include "client/linux/minidump_writer/linux_dumper.h"
+#include "breakpad_googletest_includes.h"
+
+using namespace google_breakpad;
+
+namespace {
+typedef testing::Test LinuxDumperTest;
+}
+
+TEST(LinuxDumperTest, Setup) {
+ LinuxDumper dumper(getpid());
+}
+
+TEST(LinuxDumperTest, FindMappings) {
+ LinuxDumper dumper(getpid());
+ ASSERT_TRUE(dumper.Init());
+
+ ASSERT_TRUE(dumper.FindMapping(reinterpret_cast<void*>(getpid)));
+ ASSERT_TRUE(dumper.FindMapping(reinterpret_cast<void*>(printf)));
+ ASSERT_FALSE(dumper.FindMapping(NULL));
+}
+
+TEST(LinuxDumperTest, ThreadList) {
+ LinuxDumper dumper(getpid());
+ ASSERT_TRUE(dumper.Init());
+
+ ASSERT_GE(dumper.threads().size(), 1);
+ bool found = false;
+ for (size_t i = 0; i < dumper.threads().size(); ++i) {
+ if (dumper.threads()[i] == getpid()) {
+ found = true;
+ break;
+ }
+ }
+}
+
+TEST(LinuxDumperTest, BuildProcPath) {
+ const pid_t pid = getpid();
+ LinuxDumper dumper(pid);
+
+ char maps_path[256] = "dummymappath";
+ char maps_path_expected[256];
+ snprintf(maps_path_expected, sizeof(maps_path_expected),
+ "/proc/%d/maps", pid);
+ dumper.BuildProcPath(maps_path, pid, "maps");
+ ASSERT_STREQ(maps_path, maps_path_expected);
+
+ // In release mode, we expect BuildProcPath to handle the invalid
+ // parameters correctly and fill map_path with an empty
+ // NULL-terminated string.
+#ifdef NDEBUG
+ snprintf(maps_path, sizeof(maps_path), "dummymappath");
+ dumper.BuildProcPath(maps_path, 0, "maps");
+ EXPECT_STREQ(maps_path, "");
+
+ snprintf(maps_path, sizeof(maps_path), "dummymappath");
+ dumper.BuildProcPath(maps_path, getpid(), "");
+ EXPECT_STREQ(maps_path, "");
+
+ snprintf(maps_path, sizeof(maps_path), "dummymappath");
+ dumper.BuildProcPath(maps_path, getpid(), NULL);
+ EXPECT_STREQ(maps_path, "");
+#endif
+}
+
+TEST(LinuxDumperTest, MappingsIncludeLinuxGate) {
+ LinuxDumper dumper(getpid());
+ ASSERT_TRUE(dumper.Init());
+
+ void* linux_gate_loc = dumper.FindBeginningOfLinuxGateSharedLibrary(getpid());
+ if (linux_gate_loc) {
+ bool found_linux_gate = false;
+
+ const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
+ const MappingInfo* mapping;
+ for (unsigned i = 0; i < mappings.size(); ++i) {
+ mapping = mappings[i];
+ if (!strcmp(mapping->name, kLinuxGateLibraryName)) {
+ found_linux_gate = true;
+ break;
+ }
+ }
+ EXPECT_TRUE(found_linux_gate);
+ EXPECT_EQ(linux_gate_loc, reinterpret_cast<void*>(mapping->start_addr));
+ EXPECT_EQ(0, memcmp(linux_gate_loc, ELFMAG, SELFMAG));
+ }
+}
diff --git a/src/client/linux/minidump_writer/minidump_writer.cc b/src/client/linux/minidump_writer/minidump_writer.cc
new file mode 100644
index 00000000..a99309f3
--- /dev/null
+++ b/src/client/linux/minidump_writer/minidump_writer.cc
@@ -0,0 +1,872 @@
+// Copyright (c) 2009, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// This code writes out minidump files:
+// http://msdn.microsoft.com/en-us/library/ms680378(VS.85,loband).aspx
+//
+// Minidumps are a Microsoft format which Breakpad uses for recording crash
+// dumps. This code has to run in a compromised environment (the address space
+// may have received SIGSEGV), thus the following rules apply:
+// * You may not enter the dynamic linker. This means that we cannot call
+// any symbols in a shared library (inc libc). Because of this we replace
+// libc functions in linux_libc_support.h.
+// * You may not call syscalls via the libc wrappers. This rule is a subset
+// of the first rule but it bears repeating. We have direct wrappers
+// around the system calls in linux_syscall_support.h.
+// * You may not malloc. There's an alternative allocator in memory.h and
+// a canonical instance in the LinuxDumper object. We use the placement
+// new form to allocate objects and we don't delete them.
+
+#include "client/linux/minidump_writer/minidump_writer.h"
+#include "client/minidump_file_writer-inl.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/ucontext.h>
+#include <sys/user.h>
+#include <sys/utsname.h>
+
+#include "client/minidump_file_writer.h"
+#include "google_breakpad/common/minidump_format.h"
+#include "google_breakpad/common/minidump_cpu_amd64.h"
+#include "google_breakpad/common/minidump_cpu_x86.h"
+
+#include "client/linux/handler/exception_handler.h"
+#include "client/linux/minidump_writer/line_reader.h"
+#include "client/linux/minidump_writer//linux_dumper.h"
+#include "common/linux/linux_libc_support.h"
+#include "common/linux/linux_syscall_support.h"
+
+// These are additional minidump stream values which are specific to the linux
+// breakpad implementation.
+enum {
+ MD_LINUX_CPU_INFO = 0x47670003, /* /proc/cpuinfo */
+ MD_LINUX_PROC_STATUS = 0x47670004, /* /proc/$x/status */
+ MD_LINUX_LSB_RELEASE = 0x47670005, /* /etc/lsb-release */
+ MD_LINUX_CMD_LINE = 0x47670006, /* /proc/$x/cmdline */
+ MD_LINUX_ENVIRON = 0x47670007, /* /proc/$x/environ */
+ MD_LINUX_AUXV = 0x47670008, /* /proc/$x/auxv */
+};
+
+// Minidump defines register structures which are different from the raw
+// structures which we get from the kernel. These are platform specific
+// functions to juggle the ucontext and user structures into minidump format.
+#if defined(__i386)
+typedef MDRawContextX86 RawContextCPU;
+
+// Write a uint16_t to memory
+// out: memory location to write to
+// v: value to write.
+static void U16(void* out, uint16_t v) {
+ memcpy(out, &v, sizeof(v));
+}
+
+// Write a uint32_t to memory
+// out: memory location to write to
+// v: value to write.
+static void U32(void* out, uint32_t v) {
+ memcpy(out, &v, sizeof(v));
+}
+
+// Juggle an x86 user_(fp|fpx|)regs_struct into minidump format
+// out: the minidump structure
+// info: the collection of register structures.
+static void CPUFillFromThreadInfo(MDRawContextX86 *out,
+ const google_breakpad::ThreadInfo &info) {
+ out->context_flags = MD_CONTEXT_X86_ALL;
+
+ out->dr0 = info.dregs[0];
+ out->dr1 = info.dregs[1];
+ out->dr2 = info.dregs[2];
+ out->dr3 = info.dregs[3];
+ // 4 and 5 deliberatly omitted because they aren't included in the minidump
+ // format.
+ out->dr6 = info.dregs[6];
+ out->dr7 = info.dregs[7];
+
+ out->gs = info.regs.xgs;
+ out->fs = info.regs.xfs;
+ out->es = info.regs.xes;
+ out->ds = info.regs.xds;
+
+ out->edi = info.regs.edi;
+ out->esi = info.regs.esi;
+ out->ebx = info.regs.ebx;
+ out->edx = info.regs.edx;
+ out->ecx = info.regs.ecx;
+ out->eax = info.regs.eax;
+
+ out->ebp = info.regs.ebp;
+ out->eip = info.regs.eip;
+ out->cs = info.regs.xcs;
+ out->eflags = info.regs.eflags;
+ out->esp = info.regs.esp;
+ out->ss = info.regs.xss;
+
+ out->float_save.control_word = info.fpregs.cwd;
+ out->float_save.status_word = info.fpregs.swd;
+ out->float_save.tag_word = info.fpregs.twd;
+ out->float_save.error_offset = info.fpregs.fip;
+ out->float_save.error_selector = info.fpregs.fcs;
+ out->float_save.data_offset = info.fpregs.foo;
+ out->float_save.data_selector = info.fpregs.fos;
+
+ // 8 registers * 10 bytes per register.
+ memcpy(out->float_save.register_area, info.fpregs.st_space, 10 * 8);
+
+ // This matches the Intel fpsave format.
+ U16(out->extended_registers + 0, info.fpregs.cwd);
+ U16(out->extended_registers + 2, info.fpregs.swd);
+ U16(out->extended_registers + 4, info.fpregs.twd);
+ U16(out->extended_registers + 6, info.fpxregs.fop);
+ U32(out->extended_registers + 8, info.fpxregs.fip);
+ U16(out->extended_registers + 12, info.fpxregs.fcs);
+ U32(out->extended_registers + 16, info.fpregs.foo);
+ U16(out->extended_registers + 20, info.fpregs.fos);
+ U32(out->extended_registers + 24, info.fpxregs.mxcsr);
+
+ memcpy(out->extended_registers + 32, &info.fpxregs.st_space, 128);
+ memcpy(out->extended_registers + 160, &info.fpxregs.xmm_space, 128);
+}
+
+// Juggle an x86 ucontext into minidump format
+// out: the minidump structure
+// info: the collection of register structures.
+static void CPUFillFromUContext(MDRawContextX86 *out, const ucontext *uc,
+ const struct _libc_fpstate* fp) {
+ const greg_t* regs = uc->uc_mcontext.gregs;
+
+ out->context_flags = MD_CONTEXT_X86_FULL |
+ MD_CONTEXT_X86_FLOATING_POINT;
+
+ out->gs = regs[REG_GS];
+ out->fs = regs[REG_FS];
+ out->es = regs[REG_ES];
+ out->ds = regs[REG_DS];
+
+ out->edi = regs[REG_EDI];
+ out->esi = regs[REG_ESI];
+ out->ebx = regs[REG_EBX];
+ out->edx = regs[REG_EDX];
+ out->ecx = regs[REG_ECX];
+ out->eax = regs[REG_EAX];
+
+ out->ebp = regs[REG_EBP];
+ out->eip = regs[REG_EIP];
+ out->cs = regs[REG_CS];
+ out->eflags = regs[REG_EFL];
+ out->esp = regs[REG_UESP];
+ out->ss = regs[REG_SS];
+
+ out->float_save.control_word = fp->cw;
+ out->float_save.status_word = fp->sw;
+ out->float_save.tag_word = fp->tag;
+ out->float_save.error_offset = fp->ipoff;
+ out->float_save.error_selector = fp->cssel;
+ out->float_save.data_offset = fp->dataoff;
+ out->float_save.data_selector = fp->datasel;
+
+ // 8 registers * 10 bytes per register.
+ memcpy(out->float_save.register_area, fp->_st, 10 * 8);
+}
+
+#elif defined(__x86_64)
+typedef MDRawContextAMD64 RawContextCPU;
+
+static void CPUFillFromThreadInfo(MDRawContextAMD64 *out,
+ const google_breakpad::ThreadInfo &info) {
+ out->context_flags = MD_CONTEXT_AMD64_FULL |
+ MD_CONTEXT_AMD64_SEGMENTS;
+
+ out->cs = info.regs.cs;
+
+ out->ds = info.regs.ds;
+ out->es = info.regs.es;
+ out->fs = info.regs.fs;
+ out->gs = info.regs.gs;
+
+ out->ss = info.regs.ss;
+ out->eflags = info.regs.eflags;
+
+ out->dr0 = info.dregs[0];
+ out->dr1 = info.dregs[1];
+ out->dr2 = info.dregs[2];
+ out->dr3 = info.dregs[3];
+ // 4 and 5 deliberatly omitted because they aren't included in the minidump
+ // format.
+ out->dr6 = info.dregs[6];
+ out->dr7 = info.dregs[7];
+
+ out->rax = info.regs.rax;
+ out->rcx = info.regs.rcx;
+ out->rdx = info.regs.rdx;
+ out->rbx = info.regs.rbx;
+
+ out->rsp = info.regs.rsp;
+
+ out->rbp = info.regs.rbp;
+ out->rsi = info.regs.rsi;
+ out->rdi = info.regs.rdi;
+ out->r8 = info.regs.r8;
+ out->r9 = info.regs.r9;
+ out->r10 = info.regs.r10;
+ out->r11 = info.regs.r11;
+ out->r12 = info.regs.r12;
+ out->r13 = info.regs.r13;
+ out->r14 = info.regs.r14;
+ out->r15 = info.regs.r15;
+
+ out->rip = info.regs.rip;
+
+ out->flt_save.control_word = info.fpregs.cwd;
+ out->flt_save.status_word = info.fpregs.swd;
+ out->flt_save.tag_word = info.fpregs.twd;
+ out->flt_save.error_opcode = info.fpregs.fop;
+ out->flt_save.error_offset = info.fpregs.rip;
+ out->flt_save.error_selector = 0; // We don't have this.
+ out->flt_save.data_offset = info.fpregs.rdp;
+ out->flt_save.data_selector = 0; // We don't have this.
+ out->flt_save.mx_csr = info.fpregs.mxcsr;
+ out->flt_save.mx_csr_mask = info.fpregs.mxcsr_mask;
+ memcpy(&out->flt_save.float_registers, &info.fpregs.st_space, 8 * 16);
+ memcpy(&out->flt_save.xmm_registers, &info.fpregs.xmm_space, 16 * 16);
+}
+
+static void CPUFillFromUContext(MDRawContextAMD64 *out, const ucontext *uc,
+ const struct _libc_fpstate* fpregs) {
+ const greg_t* regs = uc->gregs;
+
+ out->context_flags = MD_CONTEXT_AMD64_FULL;
+
+ out->cs = regs[REG_CSGSFS] & 0xffff;
+
+ out->fs = (regs[REG_CSGSFS] >> 32) & 0xffff;
+ out->gs = (regs[REG_CSGSFS] >> 16) & 0xffff;
+
+ out->eflags = regs[REG_EFL];
+
+ out->rax = regs[REG_RAX];
+ out->rcx = regs[REG_RCX];
+ out->rdx = regs[REG_RDX];
+ out->rbx = regs[REG_RBX];
+
+ out->rsp = regs[REG_RSP];
+ out->rbp = regs[REG_RBP];
+ out->rsi = regs[REG_RSI];
+ out->rdi = regs[REG_RDI];
+ out->r8 = regs[REG_R8];
+ out->r9 = regs[REG_R9];
+ out->r10 = regs[REG_R10];
+ out->r11 = regs[REG_R11];
+ out->r12 = regs[REG_R12];
+ out->r13 = regs[REG_R13];
+ out->r14 = regs[REG_R14];
+ out->r15 = regs[REG_R15];
+
+ out->rip = regs[REG_RIP];
+
+ out->flt_save.control_word = fpregs->cwd;
+ out->flt_save.status_word = fpregs->swd;
+ out->flt_save.tag_word = fpregs->ftw;
+ out->flt_save.error_opcode = fpregs->fop;
+ out->flt_save.error_offset = fpregs->rip;
+ out->flt_save.data_offset = fpregs->rdp;
+ out->flt_save.error_selector = 0; // We don't have this.
+ out->flt_save.data_selector = 0; // We don't have this.
+ out->flt_save.mx_csr = fpregs->mxcsr;
+ out->flt_save.mx_csr_mask = fpregs->mxcsr_mask;
+ memcpy(&out->flt_save.float_registers, &fpregs->_st, 8 * 16);
+ memcpy(&out->flt_save.xmm_registers, &fpregs->_xmm, 16 * 16);
+}
+
+#else
+#error "This code has not been ported to your platform yet."
+#endif
+
+namespace google_breakpad {
+
+class MinidumpWriter {
+ public:
+ MinidumpWriter(const char* filename,
+ pid_t crashing_pid,
+ const ExceptionHandler::CrashContext* context)
+ : filename_(filename),
+ siginfo_(&context->siginfo),
+ ucontext_(&context->context),
+ float_state_(&context->float_state),
+ crashing_tid_(context->tid),
+ dumper_(crashing_pid) {
+ }
+
+ bool Init() {
+ return dumper_.Init() && minidump_writer_.Open(filename_) &&
+ dumper_.ThreadsSuspend();
+ }
+
+ ~MinidumpWriter() {
+ minidump_writer_.Close();
+ dumper_.ThreadsResume();
+ }
+
+ bool Dump() {
+ // A minidump file contains a number of tagged streams. This is the number
+ // of stream which we write.
+ static const unsigned kNumWriters = 11;
+
+ TypedMDRVA<MDRawHeader> header(&minidump_writer_);
+ TypedMDRVA<MDRawDirectory> dir(&minidump_writer_);
+ if (!header.Allocate())
+ return false;
+ if (!dir.AllocateArray(kNumWriters))
+ return false;
+ memset(header.get(), 0, sizeof(MDRawHeader));
+
+ header.get()->signature = MD_HEADER_SIGNATURE;
+ header.get()->version = MD_HEADER_VERSION;
+ header.get()->time_date_stamp = time(NULL);
+ header.get()->stream_count = kNumWriters;
+ header.get()->stream_directory_rva = dir.position();
+
+ unsigned dir_index = 0;
+ MDRawDirectory dirent;
+
+ if (!WriteThreadListStream(&dirent))
+ return false;
+ dir.CopyIndex(dir_index++, &dirent);
+
+ if (!WriteMappings(&dirent))
+ return false;
+ dir.CopyIndex(dir_index++, &dirent);
+
+ if (!WriteExceptionStream(&dirent))
+ return false;
+ dir.CopyIndex(dir_index++, &dirent);
+
+ if (!WriteSystemInfoStream(&dirent))
+ return false;
+ dir.CopyIndex(dir_index++, &dirent);
+
+ dirent.stream_type = MD_LINUX_CPU_INFO;
+ if (!WriteFile(&dirent.location, "/proc/cpuinfo"))
+ NullifyDirectoryEntry(&dirent);
+ dir.CopyIndex(dir_index++, &dirent);
+
+ dirent.stream_type = MD_LINUX_PROC_STATUS;
+ if (!WriteProcFile(&dirent.location, crashing_tid_, "status"))
+ NullifyDirectoryEntry(&dirent);
+ dir.CopyIndex(dir_index++, &dirent);
+
+ dirent.stream_type = MD_LINUX_LSB_RELEASE;
+ if (!WriteFile(&dirent.location, "/etc/lsb-release"))
+ NullifyDirectoryEntry(&dirent);
+ dir.CopyIndex(dir_index++, &dirent);
+
+ dirent.stream_type = MD_LINUX_CMD_LINE;
+ if (!WriteProcFile(&dirent.location, crashing_tid_, "cmdline"))
+ NullifyDirectoryEntry(&dirent);
+ dir.CopyIndex(dir_index++, &dirent);
+
+ dirent.stream_type = MD_LINUX_ENVIRON;
+ if (!WriteProcFile(&dirent.location, crashing_tid_, "environ"))
+ NullifyDirectoryEntry(&dirent);
+ dir.CopyIndex(dir_index++, &dirent);
+
+ dirent.stream_type = MD_LINUX_AUXV;
+ if (!WriteProcFile(&dirent.location, crashing_tid_, "auxv"))
+ NullifyDirectoryEntry(&dirent);
+ dir.CopyIndex(dir_index++, &dirent);
+
+ dirent.stream_type = MD_LINUX_AUXV;
+ if (!WriteProcFile(&dirent.location, crashing_tid_, "maps"))
+ NullifyDirectoryEntry(&dirent);
+ dir.CopyIndex(dir_index++, &dirent);
+
+ // If you add more directory entries, don't forget to update kNumWriters,
+ // above.
+
+ dumper_.ThreadsResume();
+ return true;
+ }
+
+ // Write information about the threads.
+ bool WriteThreadListStream(MDRawDirectory* dirent) {
+ const unsigned num_threads = dumper_.threads().size();
+
+ TypedMDRVA<uint32_t> list(&minidump_writer_);
+ if (!list.AllocateObjectAndArray(num_threads, sizeof(MDRawThread)))
+ return false;
+
+ dirent->stream_type = MD_THREAD_LIST_STREAM;
+ dirent->location = list.location();
+
+ *list.get() = num_threads;
+
+ for (unsigned i = 0; i < num_threads; ++i) {
+ MDRawThread thread;
+ my_memset(&thread, 0, sizeof(thread));
+ thread.thread_id = dumper_.threads()[i];
+ // We have a different source of information for the crashing thread. If
+ // we used the actual state of the thread we would find it running in the
+ // signal handler with the alternative stack, which would be deeply
+ // unhelpful.
+ if (thread.thread_id == crashing_tid_) {
+ const void* stack;
+ size_t stack_len;
+ if (!dumper_.GetStackInfo(&stack, &stack_len, GetStackPointer()))
+ return false;
+ UntypedMDRVA memory(&minidump_writer_);
+ if (!memory.Allocate(stack_len))
+ return false;
+ uint8_t* stack_copy = (uint8_t*) dumper_.allocator()->Alloc(stack_len);
+ dumper_.CopyFromProcess(stack_copy, thread.thread_id, stack, stack_len);
+ memory.Copy(stack_copy, stack_len);
+ thread.stack.start_of_memory_range = (uintptr_t) (stack);
+ thread.stack.memory = memory.location();
+ TypedMDRVA<RawContextCPU> cpu(&minidump_writer_);
+ if (!cpu.Allocate())
+ return false;
+ my_memset(cpu.get(), 0, sizeof(RawContextCPU));
+ CPUFillFromUContext(cpu.get(), ucontext_, float_state_);
+ thread.thread_context = cpu.location();
+ crashing_thread_context_ = cpu.location();
+ } else {
+ ThreadInfo info;
+ if (!dumper_.ThreadInfoGet(dumper_.threads()[i], &info))
+ return false;
+ UntypedMDRVA memory(&minidump_writer_);
+ if (!memory.Allocate(info.stack_len))
+ return false;
+ uint8_t* stack_copy =
+ (uint8_t*) dumper_.allocator()->Alloc(info.stack_len);
+ dumper_.CopyFromProcess(stack_copy, thread.thread_id, info.stack,
+ info.stack_len);
+ memory.Copy(stack_copy, info.stack_len);
+ thread.stack.start_of_memory_range = (uintptr_t)(info.stack);
+ thread.stack.memory = memory.location();
+ TypedMDRVA<RawContextCPU> cpu(&minidump_writer_);
+ if (!cpu.Allocate())
+ return false;
+ my_memset(cpu.get(), 0, sizeof(RawContextCPU));
+ CPUFillFromThreadInfo(cpu.get(), info);
+ thread.thread_context = cpu.location();
+ }
+
+ list.CopyIndexAfterObject(i, &thread, sizeof(thread));
+ }
+
+ return true;
+ }
+
+ static bool ShouldIncludeMapping(const MappingInfo& mapping) {
+ if (mapping.name[0] == 0 || // we only want modules with filenames.
+ mapping.offset || // we only want to include one mapping per shared lib.
+ mapping.size < 4096) { // too small to get a signature for.
+ return false;
+ }
+
+ return true;
+ }
+
+ // Write information about the mappings in effect. Because we are using the
+ // minidump format, the information about the mappings is pretty limited.
+ // Because of this, we also include the full, unparsed, /proc/$x/maps file in
+ // another stream in the file.
+ bool WriteMappings(MDRawDirectory* dirent) {
+ const unsigned num_mappings = dumper_.mappings().size();
+ unsigned num_output_mappings = 0;
+
+ for (unsigned i = 0; i < dumper_.mappings().size(); ++i) {
+ const MappingInfo& mapping = *dumper_.mappings()[i];
+ if (ShouldIncludeMapping(mapping))
+ num_output_mappings++;
+ }
+
+ TypedMDRVA<uint32_t> list(&minidump_writer_);
+ if (!list.AllocateObjectAndArray(num_output_mappings, sizeof(MDRawModule)))
+ return false;
+
+ dirent->stream_type = MD_MODULE_LIST_STREAM;
+ dirent->location = list.location();
+ *list.get() = num_output_mappings;
+
+ for (unsigned i = 0, j = 0; i < num_mappings; ++i) {
+ const MappingInfo& mapping = *dumper_.mappings()[i];
+ if (!ShouldIncludeMapping(mapping))
+ continue;
+
+ MDRawModule mod;
+ my_memset(&mod, 0, sizeof(mod));
+ mod.base_of_image = mapping.start_addr;
+ mod.size_of_image = mapping.size;
+ const size_t filepath_len = my_strlen(mapping.name);
+
+ // Figure out file name from path
+ const char* filename_ptr = mapping.name + filepath_len - 1;
+ while (filename_ptr >= mapping.name) {
+ if (*filename_ptr == '/')
+ break;
+ filename_ptr--;
+ }
+ filename_ptr++;
+ const size_t filename_len = mapping.name + filepath_len - filename_ptr;
+
+ uint8_t cv_buf[MDCVInfoPDB70_minsize + NAME_MAX];
+ uint8_t* cv_ptr = cv_buf;
+ UntypedMDRVA cv(&minidump_writer_);
+ if (!cv.Allocate(MDCVInfoPDB70_minsize + filename_len + 1))
+ return false;
+
+ const uint32_t cv_signature = MD_CVINFOPDB70_SIGNATURE;
+ memcpy(cv_ptr, &cv_signature, sizeof(cv_signature));
+ cv_ptr += sizeof(cv_signature);
+
+ {
+ // We XOR the first page of the file to get a signature for it.
+ uint8_t xor_buf[sizeof(MDGUID)];
+ size_t done = 0;
+ uint8_t* signature = cv_ptr;
+ cv_ptr += sizeof(xor_buf);
+
+ my_memset(signature, 0, sizeof(xor_buf));
+ while (done < 4096) {
+ dumper_.CopyFromProcess(xor_buf, crashing_tid_,
+ (void *) (mod.base_of_image + done),
+ sizeof(xor_buf));
+ for (unsigned i = 0; i < sizeof(xor_buf); ++i)
+ signature[i] ^= xor_buf[i];
+ done += sizeof(xor_buf);
+ }
+ my_memset(cv_ptr, 0, sizeof(uint32_t)); // Set age to 0 on Linux.
+ cv_ptr += sizeof(uint32_t);
+ }
+
+ // Write pdb_file_name
+ memcpy(cv_ptr, filename_ptr, filename_len + 1);
+ cv.Copy(cv_buf, MDCVInfoPDB70_minsize + filename_len + 1);
+
+ mod.cv_record = cv.location();
+
+ MDLocationDescriptor ld;
+ if (!minidump_writer_.WriteString(mapping.name, filepath_len, &ld))
+ return false;
+ mod.module_name_rva = ld.rva;
+
+ list.CopyIndexAfterObject(j++, &mod, sizeof(mod));
+ }
+
+ return true;
+ }
+
+ bool WriteExceptionStream(MDRawDirectory* dirent) {
+ TypedMDRVA<MDRawExceptionStream> exc(&minidump_writer_);
+ if (!exc.Allocate())
+ return false;
+ my_memset(exc.get(), 0, sizeof(MDRawExceptionStream));
+
+ dirent->stream_type = MD_EXCEPTION_STREAM;
+ dirent->location = exc.location();
+
+ exc.get()->thread_id = crashing_tid_;
+ exc.get()->exception_record.exception_code = siginfo_->si_signo;
+ exc.get()->exception_record.exception_address =
+ (uintptr_t) siginfo_->si_addr;
+ exc.get()->thread_context = crashing_thread_context_;
+
+ return true;
+ }
+
+ bool WriteSystemInfoStream(MDRawDirectory* dirent) {
+ TypedMDRVA<MDRawSystemInfo> si(&minidump_writer_);
+ if (!si.Allocate())
+ return false;
+ my_memset(si.get(), 0, sizeof(MDRawSystemInfo));
+
+ dirent->stream_type = MD_SYSTEM_INFO_STREAM;
+ dirent->location = si.location();
+
+ WriteCPUInformation(si.get());
+ WriteOSInformation(si.get());
+
+ return true;
+ }
+
+ private:
+#if defined(__i386)
+ uintptr_t GetStackPointer() {
+ return ucontext_->uc_mcontext.gregs[REG_ESP];
+ }
+#elif defined(__x86_64)
+ uintptr_t GetStackPointer() {
+ return ucontext_->uc_mcontext.gregs[REG_RSP];
+ }
+#else
+#error "This code has not been ported to your platform yet."
+#endif
+
+ void NullifyDirectoryEntry(MDRawDirectory* dirent) {
+ dirent->stream_type = 0;
+ dirent->location.data_size = 0;
+ dirent->location.rva = 0;
+ }
+
+ bool WriteCPUInformation(MDRawSystemInfo* sys_info) {
+ char vendor_id[sizeof(sys_info->cpu.x86_cpu_info.vendor_id) + 1] = {0};
+ static const char vendor_id_name[] = "vendor_id";
+ static const size_t vendor_id_name_length = sizeof(vendor_id_name) - 1;
+
+ struct CpuInfoEntry {
+ const char* info_name;
+ int value;
+ bool found;
+ } cpu_info_table[] = {
+ { "processor", -1, false },
+ { "model", 0, false },
+ { "stepping", 0, false },
+ { "cpuid level", 0, false },
+ };
+
+ // processor_architecture should always be set, do this first
+ sys_info->processor_architecture =
+#if defined(__i386)
+ MD_CPU_ARCHITECTURE_X86;
+#elif defined(__x86_64)
+ MD_CPU_ARCHITECTURE_AMD64;
+#else
+#error "Unknown CPU arch"
+#endif
+
+ const int fd = sys_open("/proc/cpuinfo", O_RDONLY, 0);
+ if (fd < 0)
+ return false;
+
+ {
+ PageAllocator allocator;
+ LineReader* const line_reader = new(allocator) LineReader(fd);
+ const char* line;
+ unsigned line_len;
+ while (line_reader->GetNextLine(&line, &line_len)) {
+ for (size_t i = 0;
+ i < sizeof(cpu_info_table) / sizeof(cpu_info_table[0]);
+ i++) {
+ CpuInfoEntry* entry = &cpu_info_table[i];
+ if (entry->found)
+ continue;
+ if (!strncmp(line, entry->info_name, strlen(entry->info_name))) {
+ const char* value = strchr(line, ':');
+ if (!value)
+ continue;
+
+ // the above strncmp only matches the prefix, it might be the wrong
+ // line. i.e. we matched "model name" instead of "model".
+ // check and make sure there is only spaces between the prefix and
+ // the colon.
+ const char* space_ptr = line + strlen(entry->info_name);
+ for (; space_ptr < value; space_ptr++) {
+ if (!isspace(*space_ptr)) {
+ break;
+ }
+ }
+ if (space_ptr != value)
+ continue;
+
+ sscanf(++value, " %d", &(entry->value));
+ entry->found = true;
+ }
+ }
+
+ // special case for vendor_id
+ if (!strncmp(line, vendor_id_name, vendor_id_name_length)) {
+ const char* value = strchr(line, ':');
+ if (!value)
+ goto popline;
+
+ // skip ':" and all the spaces that follows
+ do {
+ value++;
+ } while (isspace(*value));
+
+ if (*value) {
+ size_t length = strlen(value);
+ if (length == 0)
+ goto popline;
+ // we don't want the trailing newline
+ if (value[length - 1] == '\n')
+ length--;
+ // ensure we have space for the value
+ if (length < sizeof(vendor_id))
+ strncpy(vendor_id, value, length);
+ }
+ }
+
+popline:
+ line_reader->PopLine(line_len);
+ }
+ sys_close(fd);
+ }
+
+ // make sure we got everything we wanted
+ for (size_t i = 0;
+ i < sizeof(cpu_info_table) / sizeof(cpu_info_table[0]);
+ i++) {
+ if (!cpu_info_table[i].found) {
+ return false;
+ }
+ }
+ // /proc/cpuinfo contains cpu id, change it into number by adding one.
+ cpu_info_table[0].value++;
+
+ sys_info->number_of_processors = cpu_info_table[0].value;
+ sys_info->processor_level = cpu_info_table[3].value;
+ sys_info->processor_revision = cpu_info_table[1].value << 8 |
+ cpu_info_table[2].value;
+
+ if (vendor_id[0] != '\0') {
+ memcpy(sys_info->cpu.x86_cpu_info.vendor_id, vendor_id,
+ sizeof(sys_info->cpu.x86_cpu_info.vendor_id));
+ }
+ return true;
+ }
+
+ bool WriteFile(MDLocationDescriptor* result, const char* filename) {
+ const int fd = sys_open(filename, O_RDONLY, 0);
+ if (fd < 0)
+ return false;
+
+ // We can't stat the files because several of the files that we want to
+ // read are kernel seqfiles, which always have a length of zero. So we have
+ // to read as much as we can into a buffer.
+ static const unsigned kMaxFileSize = 1024;
+ uint8_t* data = (uint8_t*) dumper_.allocator()->Alloc(kMaxFileSize);
+
+ size_t done = 0;
+ while (done < kMaxFileSize) {
+ ssize_t r;
+ do {
+ r = sys_read(fd, data + done, kMaxFileSize - done);
+ } while (r == -1 && errno == EINTR);
+
+ if (r < 1)
+ break;
+ done += r;
+ }
+ sys_close(fd);
+
+ if (!done)
+ return false;
+
+ UntypedMDRVA memory(&minidump_writer_);
+ if (!memory.Allocate(done))
+ return false;
+ memory.Copy(data, done);
+ *result = memory.location();
+ return true;
+ }
+
+ bool WriteOSInformation(MDRawSystemInfo* sys_info) {
+ sys_info->platform_id = MD_OS_LINUX;
+
+ struct utsname uts;
+ if (uname(&uts))
+ return false;
+
+ static const size_t buf_len = 512;
+ char buf[buf_len] = {0};
+ size_t space_left = buf_len - 1;
+ const char* info_table[] = {
+ uts.sysname,
+ uts.release,
+ uts.version,
+ uts.machine,
+ NULL
+ };
+ bool first_item = true;
+ for (const char** cur_info = info_table; *cur_info; cur_info++) {
+ static const char* separator = " ";
+ size_t separator_len = strlen(separator);
+ size_t info_len = strlen(*cur_info);
+ if (info_len == 0)
+ continue;
+
+ if (space_left < info_len + (first_item ? 0 : separator_len))
+ break;
+
+ if (!first_item) {
+ strcat(buf, separator);
+ space_left -= separator_len;
+ }
+
+ first_item = false;
+ strcat(buf, *cur_info);
+ space_left -= info_len;
+ }
+
+ MDLocationDescriptor location;
+ if (!minidump_writer_.WriteString(buf, 0, &location))
+ return false;
+ sys_info->csd_version_rva = location.rva;
+
+ return true;
+ }
+
+ bool WriteProcFile(MDLocationDescriptor* result, pid_t pid,
+ const char* filename) {
+ char buf[80];
+ memcpy(buf, "/proc/", 6);
+ const unsigned pid_len = my_int_len(pid);
+ my_itos(buf + 6, pid, pid_len);
+ buf[6 + pid_len] = '/';
+ memcpy(buf + 6 + pid_len + 1, filename, my_strlen(filename) + 1);
+ return WriteFile(result, buf);
+ }
+
+ const char* const filename_; // output filename
+ const siginfo_t* const siginfo_; // from the signal handler (see sigaction)
+ const struct ucontext* const ucontext_; // also from the signal handler
+ const struct _libc_fpstate* const float_state_; // ditto
+ const pid_t crashing_tid_; // the process which actually crashed
+ LinuxDumper dumper_;
+ MinidumpFileWriter minidump_writer_;
+ MDLocationDescriptor crashing_thread_context_;
+};
+
+bool WriteMinidump(const char* filename, pid_t crashing_process,
+ const void* blob, size_t blob_size) {
+ if (blob_size != sizeof(ExceptionHandler::CrashContext))
+ return false;
+ const ExceptionHandler::CrashContext* context =
+ reinterpret_cast<const ExceptionHandler::CrashContext*>(blob);
+ MinidumpWriter writer(filename, crashing_process, context);
+ if (!writer.Init())
+ return false;
+ return writer.Dump();
+}
+
+} // namespace google_breakpad
diff --git a/src/client/linux/handler/minidump_generator.h b/src/client/linux/minidump_writer/minidump_writer.h
index 7c0511f5..579f68cd 100644
--- a/src/client/linux/handler/minidump_generator.h
+++ b/src/client/linux/minidump_writer/minidump_writer.h
@@ -1,8 +1,6 @@
-// Copyright (c) 2006, Google Inc.
+// Copyright (c) 2009, Google Inc.
// All rights reserved.
//
-// Author: Li Liu
-//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
@@ -29,45 +27,27 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-#ifndef CLIENT_LINUX_HANDLER_MINIDUMP_GENERATOR_H__
-#define CLIENT_LINUX_HANDLER_MINIDUMP_GENERATOR_H__
+#ifndef CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_
+#define CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_
#include <stdint.h>
-
-#include "google_breakpad/common/breakpad_types.h"
-#include "processor/scoped_ptr.h"
-
-struct sigcontext;
+#include <unistd.h>
namespace google_breakpad {
+// Write a minidump to the filesystem. This function does not malloc nor use
+// libc functions which may. Thus, it can be used in contexts where the state
+// of the heap may be corrupt.
+// filename: the filename to write to. This is opened O_EXCL and fails if
+// open fails.
+// crashing_process: the pid of the crashing process. This must be trusted.
+// blob: a blob of data from the crashing process. See exception_handler.h
+// blob_size: the length of |blob|, in bytes
//
-// MinidumpGenerator
-//
-// Write a minidump to file based on the signo and sig_ctx.
-// A minidump generator should be created before any exception happen.
-//
-class MinidumpGenerator {
- public:
- MinidumpGenerator();
-
- ~MinidumpGenerator();
-
- // Write minidump.
- bool WriteMinidumpToFile(const char *file_pathname,
- int signo,
- uintptr_t sighandler_ebp,
- struct sigcontext **sig_ctx) const;
- private:
- // Allocate memory for stack.
- void AllocateStack();
-
- private:
- // Stack size of the writer thread.
- static const int kStackSize = 1024 * 1024;
- scoped_array<char> stack_;
-};
+// Returns true iff successful.
+bool WriteMinidump(const char* filename, pid_t crashing_process,
+ const void* blob, size_t blob_size);
} // namespace google_breakpad
-#endif // CLIENT_LINUX_HANDLER_MINIDUMP_GENERATOR_H__
+#endif // CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_
diff --git a/src/client/linux/handler/minidump_test.cc b/src/client/linux/minidump_writer/minidump_writer_unittest.cc
index f8c4e784..5ff336ce 100644
--- a/src/client/linux/handler/minidump_test.cc
+++ b/src/client/linux/minidump_writer/minidump_writer_unittest.cc
@@ -1,8 +1,6 @@
-// Copyright (c) 2006, Google Inc.
+// Copyright (c) 2009, Google Inc.
// All rights reserved.
//
-// Author: Li Liu
-//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
@@ -29,58 +27,53 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-#include <pthread.h>
#include <unistd.h>
+#include <sys/syscall.h>
-#include <cassert>
-#include <cstdio>
-#include <cstdlib>
-#include <cstring>
-
-#include "client/linux/handler/minidump_generator.h"
+#include "client/linux/handler/exception_handler.h"
+#include "client/linux/minidump_writer/minidump_writer.h"
+#include "breakpad_googletest_includes.h"
using namespace google_breakpad;
-// Thread use this to see if it should stop working.
-static bool should_exit = false;
-
-static void foo2(int arg) {
- // Stack variable, used for debugging stack dumps.
- int c = arg;
- c = 0xcccccccc;
- while (!should_exit)
- sleep(1);
-}
+// This provides a wrapper around system calls which may be
+// interrupted by a signal and return EINTR. See man 7 signal.
+#define HANDLE_EINTR(x) ({ \
+ typeof(x) __eintr_result__; \
+ do { \
+ __eintr_result__ = x; \
+ } while (__eintr_result__ == -1 && errno == EINTR); \
+ __eintr_result__;\
+})
-static void foo(int arg) {
- // Stack variable, used for debugging stack dumps.
- int b = arg;
- b = 0xbbbbbbbb;
- foo2(b);
+namespace {
+typedef testing::Test MinidumpWriterTest;
}
-static void *thread_main(void *) {
- // Stack variable, used for debugging stack dumps.
- int a = 0xaaaaaaaa;
- foo(a);
- return NULL;
-}
+TEST(MinidumpWriterTest, Setup) {
+ int fds[2];
+ ASSERT_NE(-1, pipe(fds));
-static void CreateThread(int num) {
- pthread_t h;
- for (int i = 0; i < num; ++i) {
- pthread_create(&h, NULL, thread_main, NULL);
- pthread_detach(h);
+ const pid_t child = fork();
+ if (child == 0) {
+ close(fds[1]);
+ char b;
+ HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
+ close(fds[0]);
+ syscall(__NR_exit);
}
-}
+ close(fds[0]);
+
+ ExceptionHandler::CrashContext context;
+ memset(&context, 0, sizeof(context));
+
+ char templ[] = "/tmp/minidump-writer-unittest-XXXXXX";
+ mktemp(templ);
+ ASSERT_TRUE(WriteMinidump(templ, child, &context, sizeof(context)));
+ struct stat st;
+ ASSERT_EQ(stat(templ, &st), 0);
+ ASSERT_GT(st.st_size, 0u);
+ unlink(templ);
-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;
+ close(fds[1]);
}