aboutsummaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
authorluly81 <luly81@4c0a9323-5329-0410-9bdc-e9ce6186880e>2007-03-12 01:53:18 +0000
committerluly81 <luly81@4c0a9323-5329-0410-9bdc-e9ce6186880e>2007-03-12 01:53:18 +0000
commitbcd46f007919b5063164c8c5c6c2bd4dfb62681e (patch)
treebdf12a91fb24c51c99a1092c2374851b2e31370f /src/client
parentfixes for issue 129 : reviewed by Waylonis (diff)
downloadbreakpad-bcd46f007919b5063164c8c5c6c2bd4dfb62681e.tar.xz
Add Linux exception handler.
Add Linux stab symbol dumper. Add minidump & symbol uploader for Linux. git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@126 4c0a9323-5329-0410-9bdc-e9ce6186880e
Diffstat (limited to 'src/client')
-rw-r--r--src/client/linux/handler/Makefile51
-rw-r--r--src/client/linux/handler/exception_handler.cc236
-rw-r--r--src/client/linux/handler/exception_handler.h194
-rw-r--r--src/client/linux/handler/exception_handler_test.cc124
-rw-r--r--src/client/linux/handler/linux_thread.cc384
-rw-r--r--src/client/linux/handler/linux_thread.h201
-rw-r--r--src/client/linux/handler/linux_thread_test.cc224
-rw-r--r--src/client/linux/handler/minidump_generator.cc766
-rw-r--r--src/client/linux/handler/minidump_generator.h70
-rw-r--r--src/client/linux/handler/minidump_test.cc86
10 files changed, 2336 insertions, 0 deletions
diff --git a/src/client/linux/handler/Makefile b/src/client/linux/handler/Makefile
new file mode 100644
index 00000000..8d615b5d
--- /dev/null
+++ b/src/client/linux/handler/Makefile
@@ -0,0 +1,51 @@
+CC=g++
+
+CPPFLAGS=-gstabs -I../../.. -Wall -DNDEBUG -D_REENTRANT
+LDFLAGS=-lpthread -lssl
+
+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))
+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_SRC)
+
+BIN=$(BIN_DIR)/minidump_test\
+ $(BIN_DIR)/linux_thread_test\
+ $(BIN_DIR)/exception_handler_test
+
+.PHONY:all clean
+
+all:$(BIN)
+
+$(BIN_DIR)/linux_thread_test:$(THREAD_TEST_OBJ)
+ $(CC) $(CPPFLAGS) $(LDFLAGS) $^ -o $@
+
+$(BIN_DIR)/minidump_test:$(MINIDUMP_TEST_OBJ)
+ $(CC) $(CPPFLAGS) $(LDFLAGS) $^ -o $@
+
+$(BIN_DIR)/exception_handler_test:$(EXCEPTION_TEST_OBJ)
+ $(CC) $(CPPFLAGS) $(LDFLAGS) $^ -o $@
+
+clean:
+ rm -f $(BIN) *.o *.dmp
diff --git a/src/client/linux/handler/exception_handler.cc b/src/client/linux/handler/exception_handler.cc
new file mode 100644
index 00000000..59b50df7
--- /dev/null
+++ b/src/client/linux/handler/exception_handler.cc
@@ -0,0 +1,236 @@
+// 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 <asm/sigcontext.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <cassert>
+#include <cstdlib>
+#include <ctime>
+
+#include "client/linux/handler/exception_handler.h"
+#include "common/linux/guid_creator.h"
+#include "google_breakpad/common/minidump_format.h"
+
+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
+};
+
+std::vector<ExceptionHandler*> *ExceptionHandler::handler_stack_ = NULL;
+int ExceptionHandler::handler_stack_index_ = 0;
+pthread_mutex_t ExceptionHandler::handler_stack_mutex_ =
+PTHREAD_MUTEX_INITIALIZER;
+
+ExceptionHandler::ExceptionHandler(const string &dump_path,
+ FilterCallback filter,
+ MinidumpCallback callback,
+ void *callback_context,
+ bool install_handler)
+ : filter_(filter),
+ callback_(callback),
+ callback_context_(callback_context),
+ dump_path_(),
+ installed_handler_(install_handler) {
+ set_dump_path(dump_path);
+
+ if (install_handler) {
+ SetupHandler();
+ pthread_mutex_lock(&handler_stack_mutex_);
+ if (handler_stack_ == NULL)
+ handler_stack_ = new std::vector<ExceptionHandler *>;
+ handler_stack_->push_back(this);
+ pthread_mutex_unlock(&handler_stack_mutex_);
+ }
+}
+
+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_);
+}
+
+bool ExceptionHandler::WriteMinidump() {
+ return InternalWriteMinidump(0, NULL);
+}
+
+// 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, NULL);
+}
+
+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;
+
+ if (sigaltstack(&sig_stack, NULL) < 0)
+ return;
+ for (size_t i = 0; i < sizeof(SigTable) / sizeof(SigTable[0]); ++i)
+ SetupHandler(SigTable[i]);
+}
+
+void ExceptionHandler::SetupHandler(int signo) {
+ struct sigaction act, old_act;
+ act.sa_handler = HandleException;
+ act.sa_flags = SA_ONSTACK;
+ if (sigaction(signo, &act, &old_act) < 0)
+ return;
+ old_handlers_[signo] = old_act.sa_handler;
+}
+
+void ExceptionHandler::TeardownHandler(int signo) {
+ if (old_handlers_.find(signo) != old_handlers_.end()) {
+ struct sigaction act;
+ act.sa_handler = old_handlers_[signo];
+ act.sa_flags = 0;
+ sigaction(signo, &act, 0);
+ }
+}
+
+void ExceptionHandler::TeardownAllHandler() {
+ for (size_t i = 0; i < sizeof(SigTable) / sizeof(SigTable[0]); ++i) {
+ TeardownHandler(SigTable[i]);
+ }
+}
+
+// static
+void ExceptionHandler::HandleException(int signo) {
+ // In Linux, the context information about the signal is put on the stack of
+ // the signal handler frame as value parameter. For some reasons, the
+ // prototype of the handler doesn't declare this information as parameter, we
+ // will do it by hand. It is the second parameter above the signal number.
+ const struct sigcontext *sig_ctx =
+ reinterpret_cast<const struct sigcontext *>(&signo + 1);
+ 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.
+ current_handler->TeardownHandler(signo);
+ if (current_handler->InternalWriteMinidump(signo, 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.
+ typedef void (*SignalHandler)(int signo, struct sigcontext);
+ SignalHandler old_handler =
+ reinterpret_cast<SignalHandler>(current_handler->old_handlers_[signo]);
+ if (old_handler != NULL)
+ old_handler(signo, *sig_ctx);
+ }
+
+ pthread_mutex_lock(&handler_stack_mutex_);
+ current_handler->SetupHandler(signo);
+ --handler_stack_index_;
+ // All the handlers in stack have been invoked to handle the exception,
+ // normally the process should be terminated and should not reach here.
+ // In case we got here, ask the OS to handle it to avoid endless loop,
+ // normally the OS will generate a core and termiate the process. This
+ // may be desired to debug the program.
+ if (handler_stack_index_ == 0)
+ signal(signo, SIG_DFL);
+ pthread_mutex_unlock(&handler_stack_mutex_);
+}
+
+bool ExceptionHandler::InternalWriteMinidump(int signo,
+ const struct sigcontext *sig_ctx) {
+ if (filter_ && !filter_(callback_context_))
+ return false;
+
+ GUID guid;
+ bool success = false;;
+ char guid_str[kGUIDStringLength + 1];
+ if (CreateGUID(&guid) && GUIDToString(&guid, guid_str, sizeof(guid_str))) {
+ char minidump_path[PATH_MAX];
+ snprintf(minidump_path, sizeof(minidump_path), "%s/%s.dmp",
+ dump_path_c_,
+ guid_str);
+ success = minidump_generator_.WriteMinidumpToFile(
+ minidump_path, signo, sig_ctx);
+ if (callback_)
+ success = callback_(dump_path_c_, guid_str,
+ callback_context_, success);
+ }
+ return success;
+}
+
+} // namespace google_breakpad
diff --git a/src/client/linux/handler/exception_handler.h b/src/client/linux/handler/exception_handler.h
new file mode 100644
index 00000000..0c0a2e83
--- /dev/null
+++ b/src/client/linux/handler/exception_handler.h
@@ -0,0 +1,194 @@
+// 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_EXCEPTION_HANDLER_H__
+#define CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H__
+
+#include <pthread.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "client/linux/handler/minidump_generator.h"
+
+// Context information when exception occured.
+struct sigcontex;
+
+namespace google_breakpad {
+
+using std::string;
+
+//
+// ExceptionHandler
+//
+// ExceptionHandler can write a minidump file when an exception occurs,
+// or when WriteMinidump() is called explicitly by your program.
+//
+// To have the exception handler write minidumps when an uncaught exception
+// (crash) occurs, you should create an instance early in the execution
+// of your program, and keep it around for the entire time you want to
+// have crash handling active (typically, until shutdown).
+// (NOTE): There should be only be one this kind of exception handler
+// object per process.
+//
+// If you want to write minidumps without installing the exception handler,
+// you can create an ExceptionHandler with install_handler set to false,
+// then call WriteMinidump. You can also use this technique if you want to
+// use different minidump callbacks for different call sites.
+//
+// In either case, a callback function is called when a minidump is written,
+// which receives the unqiue id of the minidump. The caller can use this
+// id to collect and write additional application state, and to launch an
+// external crash-reporting application.
+//
+// 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
+ // processing of an exception. A FilterCallback is called before writing
+ // a minidump. context is the parameter supplied by the user as
+ // callback_context when the handler was created.
+ //
+ // If a FilterCallback returns true, Breakpad will continue processing,
+ // attempting to write a minidump. If a FilterCallback returns false,
+ // Breakpad will immediately report the exception as unhandled without
+ // writing a minidump, allowing another handler the opportunity to handle it.
+ typedef bool (*FilterCallback)(void *context);
+
+ // A callback function to run after the minidump has been written.
+ // minidump_id is a unique id for the dump, so the minidump
+ // file is <dump_path>\<minidump_id>.dmp. context is the parameter supplied
+ // by the user as callback_context when the handler was created. succeeded
+ // indicates whether a minidump file was successfully written.
+ //
+ // If an exception occurred and the callback returns true, Breakpad will
+ // treat the exception as fully-handled, suppressing any other handlers from
+ // being notified of the exception. If the callback returns false, Breakpad
+ // will treat the exception as unhandled, and allow another handler to handle
+ // it. If there are no other handlers, Breakpad will report the exception to
+ // the system as unhandled, allowing a debugger or native crash dialog the
+ // opportunity to handle the exception. Most callback implementations
+ // should normally return the value of |succeeded|, or when they wish to
+ // not report an exception of handled, false. Callbacks will rarely want to
+ // return true directly (unless |succeeded| is true).
+ typedef bool (*MinidumpCallback)(const char *dump_path,
+ const char *minidump_id,
+ void *context,
+ bool succeeded);
+
+ // 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
+ // minidump. Minidump files will be written to dump_path, and the optional
+ // callback is called after writing the dump file, as described above.
+ // 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,
+ 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) {
+ dump_path_ = dump_path;
+ dump_path_c_ = dump_path_.c_str();
+ }
+
+ // 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,
+ 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 all handlers.
+ void TeardownAllHandler();
+
+ // Signal handler.
+ static void HandleException(int signo);
+
+ bool InternalWriteMinidump(int signo, const struct sigcontext *sig_ctx);
+
+ private:
+ FilterCallback filter_;
+ MinidumpCallback callback_;
+ void *callback_context_;
+
+ // The directory in which a minidump will be written, set by the dump_path
+ // argument to the constructor, or set_dump_path.
+ string dump_path_;
+ // C style dump path. Keep this when setting dump path, since calling
+ // c_str() of std::string when crashing may not be safe.
+ const char *dump_path_c_;
+
+ // True if the ExceptionHandler installed an unhandled exception filter
+ // when created (with an install_handler parameter set to true).
+ bool installed_handler_;
+
+ // Keep the previous handlers for the signal.
+ typedef void (*sighandler_t)(int);
+ std::map<int, sighandler_t> old_handlers_;
+
+ // 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_;
+ // The index of the handler that should handle the next exception.
+ static int 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 &);
+};
+
+} // namespace google_breakpad
+
+#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
new file mode 100644
index 00000000..9c4a35a7
--- /dev/null
+++ b/src/client/linux/handler/exception_handler_test.cc
@@ -0,0 +1,124 @@
+// 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 = 1;
+ 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/linux_thread.cc b/src/client/linux/handler/linux_thread.cc
new file mode 100644
index 00000000..eeedec6e
--- /dev/null
+++ b/src/client/linux/handler/linux_thread.cc
@@ -0,0 +1,384 @@
+// 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 <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];
+ 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;
+}
+
+} // namespace google_breakpad
diff --git a/src/client/linux/handler/linux_thread.h b/src/client/linux/handler/linux_thread.h
new file mode 100644
index 00000000..9f5d479f
--- /dev/null
+++ b/src/client/linux/handler/linux_thread.h
@@ -0,0 +1,201 @@
+// 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_esp) const;
+
+ 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
new file mode 100644
index 00000000..aeb5e64c
--- /dev/null
+++ b/src/client/linux/handler/linux_thread_test.cc
@@ -0,0 +1,224 @@
+// 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
new file mode 100644
index 00000000..3e4befc9
--- /dev/null
+++ b/src/client/linux/handler/minidump_generator.cc
@@ -0,0 +1,766 @@
+// 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 <asm/sigcontext.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/utsname.h>
+#include <sys/wait.h>
+
+#include <cstdlib>
+#include <ctime>
+
+#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"
+
+// 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;
+
+ // Signal contex when crash happed. Can be NULL if this is a requested dump.
+ const 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) {
+ char *proc_cpu_path = "/proc/cpuinfo";
+ char line[128];
+
+ struct CpuInfoEntry {
+ char *info_name;
+ int value;
+ } cpu_info_table[] = {
+ { "processor", -1 },
+ { "model", 0 },
+ { "stepping", 0 },
+ { "cpuid level", 0 },
+ { NULL, -1 },
+ };
+
+ 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++;
+ }
+ }
+ 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;
+ }
+ 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);
+ char *os_info_table[] = {
+ uts.sysname,
+ uts.release,
+ uts.version,
+ uts.machine,
+ "GNU/Linux",
+ NULL
+ };
+ for (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);
+ return thread_lister->ListThreads(&callback_param) == 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
+ 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, module_name, module_name_length))
+ return false;
+
+ module->cv_record = cv.location();
+ MDCVInfoPDB70 *cv_ptr = cv.get();
+ 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;
+ }
+ return false;
+}
+
+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->sig_ctx != NULL) {
+ 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,
+ const 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.sig_ctx = sig_ctx;
+
+ int cloned_pid = clone(Write, stack_.get() + kStackSize,
+ CLONE_VM | CLONE_FILES | CLONE_FS | CLONE_UNTRACED,
+ (void*)&argument);
+ waitpid(cloned_pid, NULL, __WALL);
+ return true;
+ }
+
+ return false;
+}
+
+} // namespace google_breakpad
diff --git a/src/client/linux/handler/minidump_generator.h b/src/client/linux/handler/minidump_generator.h
new file mode 100644
index 00000000..db74f914
--- /dev/null
+++ b/src/client/linux/handler/minidump_generator.h
@@ -0,0 +1,70 @@
+// Copyright (c) 2006, Google Inc.
+// All rights reserved.
+//
+// Author: Li Liu
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef CLIENT_LINUX_HANDLER_MINIDUMP_GENERATOR_H__
+#define CLIENT_LINUX_HANDLER_MINIDUMP_GENERATOR_H__
+
+#include "google_breakpad/common/breakpad_types.h"
+#include "processor/scoped_ptr.h"
+
+struct sigcontext;
+
+namespace google_breakpad {
+
+//
+// MinidumpGenerator
+//
+// Write a minidump to file based on the signo and sig_ctx.
+// A minidump generator should be created before any exception happen.
+//
+class MinidumpGenerator {
+ public:
+ MinidumpGenerator();
+
+ ~MinidumpGenerator();
+
+ // Write minidump.
+ bool WriteMinidumpToFile(const char *file_pathname,
+ int signo,
+ const struct sigcontext *sig_ctx) const;
+ private:
+ // Allocate memory for stack.
+ void AllocateStack();
+
+ private:
+ // Stack size of the writer thread.
+ static const int kStackSize = 1024 * 1024;
+ scoped_array<char> stack_;
+};
+
+} // namespace google_breakpad
+
+#endif // CLIENT_LINUX_HANDLER_MINIDUMP_GENERATOR_H__
diff --git a/src/client/linux/handler/minidump_test.cc b/src/client/linux/handler/minidump_test.cc
new file mode 100644
index 00000000..a6ebcc20
--- /dev/null
+++ b/src/client/linux/handler/minidump_test.cc
@@ -0,0 +1,86 @@
+// Copyright (c) 2006, Google Inc.
+// All rights reserved.
+//
+// Author: Li Liu
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <pthread.h>
+#include <unistd.h>
+
+#include <cassert>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+
+#include "client/linux/handler/minidump_generator.h"
+
+using namespace google_breakpad;
+
+// Thread use this to see if it should stop working.
+static bool should_exit = false;
+
+static void foo2(int arg) {
+ // Stack variable, used for debugging stack dumps.
+ int c = arg;
+ c = 0xcccccccc;
+ while (!should_exit)
+ sleep(1);
+}
+
+static void foo(int arg) {
+ // Stack variable, used for debugging stack dumps.
+ int b = arg;
+ b = 0xbbbbbbbb;
+ foo2(b);
+}
+
+static void *thread_main(void *) {
+ // Stack variable, used for debugging stack dumps.
+ int a = 0xaaaaaaaa;
+ foo(a);
+ return NULL;
+}
+
+static void CreateThread(int num) {
+ pthread_t h;
+ for (int i = 0; i < num; ++i) {
+ pthread_create(&h, NULL, thread_main, NULL);
+ pthread_detach(h);
+ }
+}
+
+int main(int argc, char *argv[]) {
+ CreateThread(10);
+ google_breakpad::MinidumpGenerator mg;
+ if (mg.WriteMinidumpToFile("minidump_test.out", -1, NULL))
+ printf("Succeeded written minidump\n");
+ else
+ printf("Failed to write minidump\n");
+ should_exit = true;
+ return 0;
+}