20 Commits

Author SHA1 Message Date
nealsid
01ca4892d8 Forgot to add modified files for the last commit!
http://breakpad.appspot.com/33002

A=jim.blandy
R=nealsid


git-svn-id: http://google-breakpad.googlecode.com/svn/branches/linux-dwarf@415 4c0a9323-5329-0410-9bdc-e9ce6186880e
2009-10-08 19:20:06 +00:00
nealsid
11fbf26119 Handle DW_LNE_end_sequence, plus a better infrastructure for
creating DIE handlers using dynamic dispatch & virtual methods.

http://breakpad.appspot.com/33002 (although the version of upload.py I'm using doesn't appear to upload new files to the code review for git)

A=jim.blandy
R=nealsid


git-svn-id: http://google-breakpad.googlecode.com/svn/branches/linux-dwarf@414 4c0a9323-5329-0410-9bdc-e9ce6186880e
2009-10-08 19:19:56 +00:00
nealsid
548c407f4b Issue 32002: update char declarations to be unsigned
http://breakpad.appspot.com/32002

A=jim.blandy
R=nealsid


git-svn-id: http://google-breakpad.googlecode.com/svn/branches/linux-dwarf@413 4c0a9323-5329-0410-9bdc-e9ce6186880e
2009-10-08 19:19:49 +00:00
nealsid
52af30d80c Issue 33001: Add missing includes and update comments to DWARF code
http://breakpad.appspot.com/33001

A=jim.blandy
R=nealsid


git-svn-id: http://google-breakpad.googlecode.com/svn/branches/linux-dwarf@412 4c0a9323-5329-0410-9bdc-e9ce6186880e
2009-10-08 19:19:42 +00:00
nealsid
3d9c2ce401 Makefile refactoring/cleanup
A=jim.blandy
R=nealsid


git-svn-id: http://google-breakpad.googlecode.com/svn/branches/linux-dwarf@411 4c0a9323-5329-0410-9bdc-e9ce6186880e
2009-10-08 19:19:33 +00:00
nealsid
06a8c78818 Updated STABS comments and moved DWARF code to a common location. This should have been done already but I think I'm messing something up with my git-svn fiddling :-)
A=jim blandy
R=nealsid


git-svn-id: http://google-breakpad.googlecode.com/svn/branches/linux-dwarf@399 4c0a9323-5329-0410-9bdc-e9ce6186880e
2009-09-17 22:16:13 +00:00
nealsid
0f3b2d75d9 Updated STABS comments and moved DWARF code to a common location.
A=jim blandy
R=nealsid


git-svn-id: http://google-breakpad.googlecode.com/svn/branches/linux-dwarf@398 4c0a9323-5329-0410-9bdc-e9ce6186880e
2009-09-17 22:15:57 +00:00
nealsid
e1e8be4cae Continue past stray end of compilation unit marker rather than returning true early
A=jim blandy
R=nealsid


git-svn-id: http://google-breakpad.googlecode.com/svn/branches/linux-dwarf@397 4c0a9323-5329-0410-9bdc-e9ce6186880e
2009-09-17 22:11:45 +00:00
nealsid
8583d8ee76 Add a warning function to print a formatted string to stderr
A=jim.blandy
R=nealsid


git-svn-id: http://google-breakpad.googlecode.com/svn/branches/linux-dwarf@396 4c0a9323-5329-0410-9bdc-e9ce6186880e
2009-09-17 22:09:30 +00:00
nealsid
64892f177d Branch for Linux dwarf work
git-svn-id: http://google-breakpad.googlecode.com/svn/branches/linux-dwarf@395 4c0a9323-5329-0410-9bdc-e9ce6186880e
2009-09-17 19:19:22 +00:00
ted.mielczarek
b2bc3bcc84 Issue 328 - should have constant for VC++ exceptions, and stringify in MinidumpProcessor::GetCrashReason
r=nealsid at http://breakpad.appspot.com/25001/show



git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@394 4c0a9323-5329-0410-9bdc-e9ce6186880e
2009-09-04 20:00:33 +00:00
jimblandy@gmail.com
c426b3d98a Breakpad: Don't use the deprecated __gnu_cxx::hash_map container.
Modern GNU compilers warn about the #inclusion of <ext/hash_map>; that
container is deprecated, and code should use <tr1/unordered_map>
instead.  However, to stay within the boundaries of C++ '98, it's
probably fine just to use plain old std::map.

Breakpad uses hash_map in three cases:

o The DWARF reader's SectionMap type maps object file section names to
  data.  This map is consulted once per section kind per DWARF
  compilation unit; it is not performance-critical.

o The Mac dump_syms tool uses it to map machine architectures to
  section maps in Universal binaries.  It's hard to imagine there
  ever being more than two entries in such a map.

o The processor's BasicSourceLineResolver uses a hash_map to map file
  numbers to file names.  This is the map that will probably have the
  most entries, but it's only accessed once per frame, after we've
  found the frame's line entry.

a=jimblandy
r=nealsid


git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@393 4c0a9323-5329-0410-9bdc-e9ce6186880e
2009-09-03 18:27:16 +00:00
stuartmorgan
ddd71b75f7 Fix text field resizing for 10.5+ SDK
git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@392 4c0a9323-5329-0410-9bdc-e9ce6186880e
2009-08-28 22:08:34 +00:00
mmentovai
ebe77d7e3b Provide a real std::string hash, not just a forward declaration for something
that doesn't exist.

TBR=nealsid

Code review URL: http://groups.google.com/group/google-breakpad-dev/browse_thread/thread/292f9ed79dfdbdde


git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@391 4c0a9323-5329-0410-9bdc-e9ce6186880e
2009-08-20 19:29:41 +00:00
nealsid
b0baafc4da Merge of Breakpad Chrome Linux fork
A=agl, Lei Zhang
R=nealsid, agl



git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@384 4c0a9323-5329-0410-9bdc-e9ce6186880e
2009-08-17 23:12:53 +00:00
mmentovai
7c48629d49 Fix build errors with gcc 4.4. Patch by Silvius Rus <rus@google.com>.
git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@383 4c0a9323-5329-0410-9bdc-e9ce6186880e
2009-08-14 18:46:43 +00:00
jimblandy@gmail.com
d4a212a099 Linux dumper: fix comments in src/common/linux/module.h
Fix some typos and references to member functions that didn't make the
final cut.

a=jimblandy
r=nealsid


git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@381 4c0a9323-5329-0410-9bdc-e9ce6186880e
2009-08-07 22:11:32 +00:00
jimblandy@gmail.com
eab03fdb72 Linux dumper: Move the data structures representing the breakpad data into their own class.
src/linux/common/module.h defines a new class, google_breakpad::Module,
that can represent the contents of a breakpad symbol file.  Module::Write
writes a well-formed symbol file to the given stream.

src/linux/common/dump_symbols.cc can now lose its symbol-file-writing
code, and change DumpStabsHandler to populate a Module object, rather
than the old SymbolInfo/SourceFileInfo/... collection of types.

The code to compute function and line sizes, even in the absence of
reliable size data in STABS, is moved into a new Finalize method of
DumpStabsHandler, which is responsible for completing the Module's
contents.

a=jimblandy
r=nealsid


git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@380 4c0a9323-5329-0410-9bdc-e9ce6186880e
2009-08-07 19:28:45 +00:00
jimblandy@gmail.com
f7cc9ef6f5 Add files left behind by previous commit.
git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@379 4c0a9323-5329-0410-9bdc-e9ce6186880e
2009-08-07 19:26:55 +00:00
jimblandy@gmail.com
54bc5cfa2d Linux dumper: Move STABS parsing into its own class.
With this patch, dump_symbols.cc no longer knows about the details of
the STABS debugging format; that is handled by the StabsReader class.
dump_symbols.cc provides a subclass of StabsHandler that builds
dump_symbols' own representation of the data.

a=jimblandy
r=nealsid


git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@378 4c0a9323-5329-0410-9bdc-e9ce6186880e
2009-08-07 19:24:32 +00:00
58 changed files with 8482 additions and 2966 deletions

View File

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

View File

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

View File

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

View File

@@ -1,124 +0,0 @@
// Copyright (c) 2006, Google Inc.
// All rights reserved.
//
// Author: Li Liu
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <pthread.h>
#include <unistd.h>
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include "client/linux/handler/exception_handler.h"
#include "client/linux/handler/linux_thread.h"
using namespace google_breakpad;
// Thread use this to see if it should stop working.
static bool should_exit = false;
static int foo2(int arg) {
// Stack variable, used for debugging stack dumps.
/*DDDebug*/printf("%s:%d\n", __FUNCTION__, __LINE__);
int c = 0xcccccccc;
fprintf(stderr, "Thread trying to crash: %x\n", getpid());
c = *reinterpret_cast<int *>(0x5);
return c;
}
static int foo(int arg) {
// Stack variable, used for debugging stack dumps.
int b = 0xbbbbbbbb;
b = foo2(b);
return b;
}
static void *thread_crash(void *) {
// Stack variable, used for debugging stack dumps.
int a = 0xaaaaaaaa;
sleep(1);
a = foo(a);
printf("%x\n", a);
return NULL;
}
static void *thread_main(void *) {
while (!should_exit)
sleep(1);
return NULL;
}
static void CreateCrashThread() {
pthread_t h;
pthread_create(&h, NULL, thread_crash, NULL);
pthread_detach(h);
}
// Create working threads.
static void CreateThread(int num) {
pthread_t h;
for (int i = 0; i < num; ++i) {
pthread_create(&h, NULL, thread_main, NULL);
pthread_detach(h);
}
}
// Callback when minidump written.
static bool MinidumpCallback(const char *dump_path,
const char *minidump_id,
void *context,
bool succeeded) {
int index = reinterpret_cast<int>(context);
printf("%d %s: %s is dumped\n", index, __FUNCTION__, minidump_id);
if (index == 0) {
should_exit = true;
return true;
}
// Don't process it.
return false;
}
int main(int argc, char *argv[]) {
int handler_index = 0;
ExceptionHandler handler_ignore(".", NULL, MinidumpCallback,
(void*)handler_index, true);
++handler_index;
ExceptionHandler handler_process(".", NULL, MinidumpCallback,
(void*)handler_index, true);
CreateCrashThread();
CreateThread(10);
while (true)
sleep(1);
should_exit = true;
return 0;
}

View File

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

View File

@@ -1,411 +0,0 @@
// Copyright (c) 2006, Google Inc.
// All rights reserved.
//
// Author: Li Liu
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
#include <errno.h>
#include <dirent.h>
#include <fcntl.h>
#include <sys/ptrace.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <algorithm>
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <functional>
#include "client/linux/handler/linux_thread.h"
using namespace google_breakpad;
// This unamed namespace contains helper function.
namespace {
// Context information for the callbacks when validating address by listing
// modules.
struct AddressValidatingContext {
uintptr_t address;
bool is_mapped;
AddressValidatingContext() : address(0UL), is_mapped(false) {
}
};
// Convert from string to int.
bool LocalAtoi(char *s, int *r) {
assert(s != NULL);
assert(r != NULL);
char *endptr = NULL;
int ret = strtol(s, &endptr, 10);
if (endptr == s)
return false;
*r = ret;
return true;
}
// Fill the proc path of a thread given its id.
void FillProcPath(int pid, char *path, int path_size) {
char pid_str[32];
snprintf(pid_str, sizeof(pid_str), "%d", pid);
snprintf(path, path_size, "/proc/%s/", pid_str);
}
// Read thread info from /proc/$pid/status.
bool ReadThreadInfo(int pid, ThreadInfo *info) {
assert(info != NULL);
char status_path[80];
// Max size we want to read from status file.
static const int kStatusMaxSize = 1024;
char status_content[kStatusMaxSize];
FillProcPath(pid, status_path, sizeof(status_path));
strcat(status_path, "status");
int fd = open(status_path, O_RDONLY, 0);
if (fd < 0)
return false;
int num_read = read(fd, status_content, kStatusMaxSize - 1);
if (num_read < 0) {
close(fd);
return false;
}
close(fd);
status_content[num_read] = '\0';
char *tgid_start = strstr(status_content, "Tgid:");
if (tgid_start)
sscanf(tgid_start, "Tgid:\t%d\n", &(info->tgid));
else
// tgid not supported by kernel??
info->tgid = 0;
tgid_start = strstr(status_content, "Pid:");
if (tgid_start) {
sscanf(tgid_start, "Pid:\t%d\n" "PPid:\t%d\n", &(info->pid),
&(info->ppid));
return true;
}
return false;
}
// Callback invoked for each mapped module.
// It use the module's adderss range to validate the address.
bool IsAddressInModuleCallback(const ModuleInfo &module_info,
void *context) {
AddressValidatingContext *addr =
reinterpret_cast<AddressValidatingContext *>(context);
addr->is_mapped = ((addr->address >= module_info.start_addr) &&
(addr->address <= module_info.start_addr +
module_info.size));
return !addr->is_mapped;
}
#if defined(__i386__) && !defined(NO_FRAME_POINTER)
void *GetNextFrame(void **last_ebp) {
void *sp = *last_ebp;
if ((unsigned long)sp == (unsigned long)last_ebp)
return NULL;
if ((unsigned long)sp & (sizeof(void *) - 1))
return NULL;
if ((unsigned long)sp - (unsigned long)last_ebp > 100000)
return NULL;
return sp;
}
#else
void *GetNextFrame(void **last_ebp) {
return reinterpret_cast<void*>(last_ebp);
}
#endif
// Suspend a thread by attaching to it.
bool SuspendThread(int pid, void *context) {
// This may fail if the thread has just died or debugged.
errno = 0;
if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) != 0 &&
errno != 0) {
return false;
}
while (waitpid(pid, NULL, __WALL) < 0) {
if (errno != EINTR) {
ptrace(PTRACE_DETACH, pid, NULL, NULL);
return false;
}
}
return true;
}
// Resume a thread by detaching from it.
bool ResumeThread(int pid, void *context) {
return ptrace(PTRACE_DETACH, pid, NULL, NULL) >= 0;
}
// Callback to get the thread information.
// Will be called for each thread found.
bool ThreadInfoCallback(int pid, void *context) {
CallbackParam<ThreadCallback> *thread_callback =
reinterpret_cast<CallbackParam<ThreadCallback> *>(context);
ThreadInfo thread_info;
if (ReadThreadInfo(pid, &thread_info) && thread_callback) {
// Invoke callback from caller.
return (thread_callback->call_back)(thread_info, thread_callback->context);
}
return false;
}
} // namespace
namespace google_breakpad {
LinuxThread::LinuxThread(int pid) : pid_(pid) , threads_suspened_(false) {
}
LinuxThread::~LinuxThread() {
if (threads_suspened_)
ResumeAllThreads();
}
int LinuxThread::SuspendAllThreads() {
CallbackParam<PidCallback> callback_param(SuspendThread, NULL);
int thread_count = 0;
if ((thread_count = IterateProcSelfTask(pid_, &callback_param)) > 0)
threads_suspened_ = true;
return thread_count;
}
void LinuxThread::ResumeAllThreads() const {
CallbackParam<PidCallback> callback_param(ResumeThread, NULL);
IterateProcSelfTask(pid_, &callback_param);
}
int LinuxThread::GetThreadCount() const {
return IterateProcSelfTask(pid_, NULL);
}
int LinuxThread::ListThreads(
CallbackParam<ThreadCallback> *thread_callback_param) const {
CallbackParam<PidCallback> callback_param(ThreadInfoCallback,
thread_callback_param);
return IterateProcSelfTask(pid_, &callback_param);
}
bool LinuxThread::GetRegisters(int pid, user_regs_struct *regs) const {
assert(regs);
return (regs != NULL &&
(ptrace(PTRACE_GETREGS, pid, NULL, regs) == 0) &&
errno == 0);
}
// Get the floating-point registers of a thread.
// The caller must get the thread pid by ListThreads.
bool LinuxThread::GetFPRegisters(int pid, user_fpregs_struct *regs) const {
assert(regs);
return (regs != NULL &&
(ptrace(PTRACE_GETREGS, pid, NULL, regs) ==0) &&
errno == 0);
}
bool LinuxThread::GetFPXRegisters(int pid, user_fpxregs_struct *regs) const {
assert(regs);
return (regs != NULL &&
(ptrace(PTRACE_GETFPREGS, pid, NULL, regs) != 0) &&
errno == 0);
}
bool LinuxThread::GetDebugRegisters(int pid, DebugRegs *regs) const {
assert(regs);
#define GET_DR(name, num)\
name->dr##num = ptrace(PTRACE_PEEKUSER, pid,\
offsetof(struct user, u_debugreg[num]), NULL)
GET_DR(regs, 0);
GET_DR(regs, 1);
GET_DR(regs, 2);
GET_DR(regs, 3);
GET_DR(regs, 4);
GET_DR(regs, 5);
GET_DR(regs, 6);
GET_DR(regs, 7);
return true;
}
int LinuxThread::GetThreadStackDump(uintptr_t current_ebp,
uintptr_t current_esp,
void *buf,
int buf_size) const {
assert(buf);
assert(buf_size > 0);
uintptr_t stack_bottom = GetThreadStackBottom(current_ebp);
int size = stack_bottom - current_esp;
size = buf_size > size ? size : buf_size;
if (size > 0)
memcpy(buf, reinterpret_cast<void*>(current_esp), size);
return size;
}
// Get the stack bottom of a thread by stack walking. It works
// unless the stack has been corrupted or the frame pointer has been omited.
// This is just a temporary solution before we get better ideas about how
// this can be done.
//
// We will check each frame address by checking into module maps.
// TODO(liuli): Improve it.
uintptr_t LinuxThread::GetThreadStackBottom(uintptr_t current_ebp) const {
void **sp = reinterpret_cast<void **>(current_ebp);
void **previous_sp = sp;
while (sp && IsAddressMapped((uintptr_t)sp)) {
previous_sp = sp;
sp = reinterpret_cast<void **>(GetNextFrame(sp));
}
return (uintptr_t)previous_sp;
}
int LinuxThread::GetModuleCount() const {
return ListModules(NULL);
}
int LinuxThread::ListModules(
CallbackParam<ModuleCallback> *callback_param) const {
char line[512];
const char *maps_path = "/proc/self/maps";
int module_count = 0;
FILE *fp = fopen(maps_path, "r");
if (fp == NULL)
return -1;
uintptr_t start_addr;
uintptr_t end_addr;
while (fgets(line, sizeof(line), fp) != NULL) {
if (sscanf(line, "%x-%x", &start_addr, &end_addr) == 2) {
ModuleInfo module;
memset(&module, 0, sizeof(module));
module.start_addr = start_addr;
module.size = end_addr - start_addr;
char *name = NULL;
assert(module.size > 0);
// Only copy name if the name is a valid path name.
if ((name = strchr(line, '/')) != NULL) {
// Get rid of the last '\n' in line
char *last_return = strchr(line, '\n');
if (last_return != NULL)
*last_return = '\0';
// Keep a space for the ending 0.
strncpy(module.name, name, sizeof(module.name) - 1);
++module_count;
}
if (callback_param &&
!(callback_param->call_back(module, callback_param->context)))
break;
}
}
fclose(fp);
return module_count;
}
// Parse /proc/$pid/tasks to list all the threads of the process identified by
// pid.
int LinuxThread::IterateProcSelfTask(int pid,
CallbackParam<PidCallback> *callback_param) const {
char task_path[80];
FillProcPath(pid, task_path, sizeof(task_path));
strcat(task_path, "task");
DIR *dir = opendir(task_path);
if (dir == NULL)
return -1;
int pid_number = 0;
// Record the last pid we've found. This is used for duplicated thread
// removal. Duplicated thread information can be found in /proc/$pid/tasks.
int last_pid = -1;
struct dirent *entry = NULL;
while ((entry = readdir(dir)) != NULL) {
if (strcmp(entry->d_name, ".") &&
strcmp(entry->d_name, "..")) {
int tpid = 0;
if (LocalAtoi(entry->d_name, &tpid) &&
last_pid != tpid) {
last_pid = tpid;
++pid_number;
// Invoke the callback.
if (callback_param &&
!(callback_param->call_back)(tpid, callback_param->context))
break;
}
}
}
closedir(dir);
return pid_number;
}
// Check if the address is a valid virtual address.
// If the address is in any of the mapped modules, we take it as valid.
// Otherwise it is invalid.
bool LinuxThread::IsAddressMapped(uintptr_t address) const {
AddressValidatingContext addr;
addr.address = address;
CallbackParam<ModuleCallback> callback_param(IsAddressInModuleCallback,
&addr);
ListModules(&callback_param);
return addr.is_mapped;
}
bool LinuxThread::FindSigContext(uintptr_t sighandler_ebp,
struct sigcontext **sig_ctx) {
uintptr_t previous_ebp;
const int MAX_STACK_DEPTH = 10;
int depth_counter = 0;
do {
// We're looking for a |struct sigcontext| as the second parameter
// to a signal handler function call. Luckily, the sigcontext
// has an ebp member which should match the ebp pointed to
// by the ebp of the signal handler frame.
previous_ebp = reinterpret_cast<uintptr_t>(GetNextFrame(
reinterpret_cast<void**>(sighandler_ebp)));
// The stack looks like this:
// | previous ebp | previous eip | first param | second param |,
// so we need to offset by 3 to get to the second parameter.
*sig_ctx = reinterpret_cast<struct sigcontext*>(sighandler_ebp +
3 * sizeof(uintptr_t));
sighandler_ebp = previous_ebp;
depth_counter++;
} while(previous_ebp != (*sig_ctx)->ebp && sighandler_ebp != 0 &&
IsAddressMapped(sighandler_ebp) && depth_counter < MAX_STACK_DEPTH);
return previous_ebp == (*sig_ctx)->ebp && previous_ebp != 0;
}
} // namespace google_breakpad

View File

@@ -1,204 +0,0 @@
// Copyright (c) 2006, Google Inc.
// All rights reserved.
//
// Author: Li Liu
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
#ifndef CLIENT_LINUX_HANDLER_LINUX_THREAD_H__
#define CLIENT_LINUX_HANDLER_LINUX_THREAD_H__
#include <stdint.h>
#include <sys/user.h>
namespace google_breakpad {
// Max module path name length.
#define kMaxModuleNameLength 256
// Holding information about a thread in the process.
struct ThreadInfo {
// Id of the thread group.
int tgid;
// Id of the thread.
int pid;
// Id of the parent process.
int ppid;
};
// Holding infomaton about a module in the process.
struct ModuleInfo {
char name[kMaxModuleNameLength];
uintptr_t start_addr;
int size;
};
// Holding debug registers.
struct DebugRegs {
int dr0;
int dr1;
int dr2;
int dr3;
int dr4;
int dr5;
int dr6;
int dr7;
};
// A callback to run when got a thread in the process.
// Return true will go on to the next thread while return false will stop the
// iteration.
typedef bool (*ThreadCallback)(const ThreadInfo &thread_info, void *context);
// A callback to run when a new module is found in the process.
// Return true will go on to the next module while return false will stop the
// iteration.
typedef bool (*ModuleCallback)(const ModuleInfo &module_info, void *context);
// Holding the callback information.
template<class CallbackFunc>
struct CallbackParam {
// Callback function address.
CallbackFunc call_back;
// Callback context;
void *context;
CallbackParam() : call_back(NULL), context(NULL) {
}
CallbackParam(CallbackFunc func, void *func_context) :
call_back(func), context(func_context) {
}
};
///////////////////////////////////////////////////////////////////////////////
//
// LinuxThread
//
// Provides handy support for operation on linux threads.
// It uses ptrace to get thread registers. Since ptrace only works in a
// different process other than the one being ptraced, user of this class
// should create another process before using the class.
//
// The process should be created in the following way:
// int cloned_pid = clone(ProcessEntryFunction, stack_address,
// CLONE_VM | CLONE_FILES | CLONE_FS | CLONE_UNTRACED,
// (void*)&arguments);
// waitpid(cloned_pid, NULL, __WALL);
//
// If CLONE_VM is not used, GetThreadStackBottom, GetThreadStackDump
// will not work since it just use memcpy to get the stack dump.
//
class LinuxThread {
public:
// Create a LinuxThread instance to list all the threads in a process.
explicit LinuxThread(int pid);
~LinuxThread();
// Stop all the threads in the process.
// Return the number of stopped threads in the process.
// Return -1 means failed to stop threads.
int SuspendAllThreads();
// Resume all the suspended threads.
void ResumeAllThreads() const;
// Get the count of threads in the process.
// Return -1 means error.
int GetThreadCount() const;
// List the threads of process.
// Whenever there is a thread found, the callback will be invoked to process
// the information.
// Return number of threads listed.
int ListThreads(CallbackParam<ThreadCallback> *thread_callback_param) const;
// Get the general purpose registers of a thread.
// The caller must get the thread pid by ListThreads.
bool GetRegisters(int pid, user_regs_struct *regs) const;
// Get the floating-point registers of a thread.
// The caller must get the thread pid by ListThreads.
bool GetFPRegisters(int pid, user_fpregs_struct *regs) const;
// Get all the extended floating-point registers. May not work on all
// machines.
// The caller must get the thread pid by ListThreads.
bool GetFPXRegisters(int pid, user_fpxregs_struct *regs) const;
// Get the debug registers.
// The caller must get the thread pid by ListThreads.
bool GetDebugRegisters(int pid, DebugRegs *regs) const;
// Get the stack memory dump.
int GetThreadStackDump(uintptr_t current_ebp,
uintptr_t current_esp,
void *buf,
int buf_size) const;
// Get the module count of the current process.
int GetModuleCount() const;
// Get the mapped modules in the address space.
// Whenever a module is found, the callback will be invoked to process the
// information.
// Return how may modules are found.
int ListModules(CallbackParam<ModuleCallback> *callback_param) const;
// Get the bottom of the stack from ebp.
uintptr_t GetThreadStackBottom(uintptr_t current_ebp) const;
// Finds a sigcontext on the stack given the ebp of our signal handler.
bool FindSigContext(uintptr_t sighandler_ebp, struct sigcontext **sig_ctx);
private:
// This callback will run when a new thread has been found.
typedef bool (*PidCallback)(int pid, void *context);
// Read thread information from /proc/$pid/task.
// Whenever a thread has been found, and callback will be invoked with
// the pid of the thread.
// Return number of threads found.
// Return -1 means the directory doesn't exist.
int IterateProcSelfTask(int pid,
CallbackParam<PidCallback> *callback_param) const;
// Check if the address is a valid virtual address.
bool IsAddressMapped(uintptr_t address) const;
private:
// The pid of the process we are listing threads.
int pid_;
// Mark if we have suspended the threads.
bool threads_suspened_;
};
} // namespace google_breakpad
#endif // CLIENT_LINUX_HANDLER_LINUX_THREAD_H__

View File

@@ -1,224 +0,0 @@
// Copyright (c) 2006, Google Inc.
// All rights reserved.
//
// Author: Li Liu
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include "client/linux/handler/linux_thread.h"
using namespace google_breakpad;
// Thread use this to see if it should stop working.
static bool should_exit = false;
static void foo2(int *a) {
// Stack variable, used for debugging stack dumps.
int c = 0xcccccccc;
c = c;
while (!should_exit)
sleep(1);
}
static void foo() {
// Stack variable, used for debugging stack dumps.
int a = 0xaaaaaaaa;
foo2(&a);
}
static void *thread_main(void *) {
// Stack variable, used for debugging stack dumps.
int b = 0xbbbbbbbb;
b = b;
while (!should_exit) {
foo();
}
return NULL;
}
static void CreateThreads(int num) {
pthread_t handle;
for (int i = 0; i < num; i++) {
if (0 != pthread_create(&handle, NULL, thread_main, NULL))
fprintf(stderr, "Failed to create thread.\n");
else
pthread_detach(handle);
}
}
static bool ProcessOneModule(const struct ModuleInfo &module_info,
void *context) {
printf("0x%x[%8d] %s\n", module_info.start_addr, module_info.size,
module_info.name);
return true;
}
static bool ProcessOneThread(const struct ThreadInfo &thread_info,
void *context) {
printf("\n\nPID: %d, TGID: %d, PPID: %d\n",
thread_info.pid,
thread_info.tgid,
thread_info.ppid);
struct user_regs_struct regs;
struct user_fpregs_struct fp_regs;
struct user_fpxregs_struct fpx_regs;
struct DebugRegs dbg_regs;
LinuxThread *threads = reinterpret_cast<LinuxThread *>(context);
memset(&regs, 0, sizeof(regs));
if (threads->GetRegisters(thread_info.pid, &regs)) {
printf(" gs = 0x%lx\n", regs.xgs);
printf(" fs = 0x%lx\n", regs.xfs);
printf(" es = 0x%lx\n", regs.xes);
printf(" ds = 0x%lx\n", regs.xds);
printf(" edi = 0x%lx\n", regs.edi);
printf(" esi = 0x%lx\n", regs.esi);
printf(" ebx = 0x%lx\n", regs.ebx);
printf(" edx = 0x%lx\n", regs.edx);
printf(" ecx = 0x%lx\n", regs.ecx);
printf(" eax = 0x%lx\n", regs.eax);
printf(" ebp = 0x%lx\n", regs.ebp);
printf(" eip = 0x%lx\n", regs.eip);
printf(" cs = 0x%lx\n", regs.xcs);
printf(" eflags = 0x%lx\n", regs.eflags);
printf(" esp = 0x%lx\n", regs.esp);
printf(" ss = 0x%lx\n", regs.xss);
} else {
fprintf(stderr, "ERROR: Failed to get general purpose registers\n");
}
memset(&fp_regs, 0, sizeof(fp_regs));
if (threads->GetFPRegisters(thread_info.pid, &fp_regs)) {
printf("\n Floating point registers:\n");
printf(" fctl = 0x%lx\n", fp_regs.cwd);
printf(" fstat = 0x%lx\n", fp_regs.swd);
printf(" ftag = 0x%lx\n", fp_regs.twd);
printf(" fioff = 0x%lx\n", fp_regs.fip);
printf(" fiseg = 0x%lx\n", fp_regs.fcs);
printf(" fooff = 0x%lx\n", fp_regs.foo);
printf(" foseg = 0x%lx\n", fp_regs.fos);
int st_space_size = sizeof(fp_regs.st_space) / sizeof(fp_regs.st_space[0]);
printf(" st_space[%2d] = 0x", st_space_size);
for (int i = 0; i < st_space_size; ++i)
printf("%02lx", fp_regs.st_space[i]);
printf("\n");
} else {
fprintf(stderr, "ERROR: Failed to get floating-point registers\n");
}
memset(&fpx_regs, 0, sizeof(fpx_regs));
if (threads->GetFPXRegisters(thread_info.pid, &fpx_regs)) {
printf("\n Extended floating point registers:\n");
printf(" fctl = 0x%x\n", fpx_regs.cwd);
printf(" fstat = 0x%x\n", fpx_regs.swd);
printf(" ftag = 0x%x\n", fpx_regs.twd);
printf(" fioff = 0x%lx\n", fpx_regs.fip);
printf(" fiseg = 0x%lx\n", fpx_regs.fcs);
printf(" fooff = 0x%lx\n", fpx_regs.foo);
printf(" foseg = 0x%lx\n", fpx_regs.fos);
printf(" fop = 0x%x\n", fpx_regs.fop);
printf(" mxcsr = 0x%lx\n", fpx_regs.mxcsr);
int space_size = sizeof(fpx_regs.st_space) / sizeof(fpx_regs.st_space[0]);
printf(" st_space[%2d] = 0x", space_size);
for (int i = 0; i < space_size; ++i)
printf("%02lx", fpx_regs.st_space[i]);
printf("\n");
space_size = sizeof(fpx_regs.xmm_space) / sizeof(fpx_regs.xmm_space[0]);
printf(" xmm_space[%2d] = 0x", space_size);
for (int i = 0; i < space_size; ++i)
printf("%02lx", fpx_regs.xmm_space[i]);
printf("\n");
}
if (threads->GetDebugRegisters(thread_info.pid, &dbg_regs)) {
printf("\n Debug registers:\n");
printf(" dr0 = 0x%x\n", dbg_regs.dr0);
printf(" dr1 = 0x%x\n", dbg_regs.dr1);
printf(" dr2 = 0x%x\n", dbg_regs.dr2);
printf(" dr3 = 0x%x\n", dbg_regs.dr3);
printf(" dr4 = 0x%x\n", dbg_regs.dr4);
printf(" dr5 = 0x%x\n", dbg_regs.dr5);
printf(" dr6 = 0x%x\n", dbg_regs.dr6);
printf(" dr7 = 0x%x\n", dbg_regs.dr7);
printf("\n");
}
if (regs.esp != 0) {
// Print the stack content.
int size = 1024 * 2;
char *buf = new char[size];
size = threads->GetThreadStackDump(regs.ebp,
regs.esp,
(void*)buf, size);
printf(" Stack content: = 0x");
size /= sizeof(unsigned long);
unsigned long *p_buf = (unsigned long *)(buf);
for (int i = 0; i < size; i += 1)
printf("%.8lx ", p_buf[i]);
delete []buf;
printf("\n");
}
return true;
}
static int PrintAllThreads(void *argument) {
int pid = (int)argument;
LinuxThread threads(pid);
int total_thread = threads.SuspendAllThreads();
printf("There are %d threads in the process: %d\n", total_thread, pid);
int total_module = threads.GetModuleCount();
printf("There are %d modules in the process: %d\n", total_module, pid);
CallbackParam<ModuleCallback> module_callback(ProcessOneModule, &threads);
threads.ListModules(&module_callback);
CallbackParam<ThreadCallback> thread_callback(ProcessOneThread, &threads);
threads.ListThreads(&thread_callback);
return 0;
}
int main(int argc, char **argv) {
int pid = getpid();
printf("Main thread is %d\n", pid);
CreateThreads(1);
// Create stack for the process.
char *stack = new char[1024 * 100];
int cloned_pid = clone(PrintAllThreads, stack + 1024 * 100,
CLONE_VM | CLONE_FILES | CLONE_FS | CLONE_UNTRACED,
(void*)getpid());
waitpid(cloned_pid, NULL, __WALL);
should_exit = true;
printf("Test finished.\n");
delete []stack;
return 0;
}

View File

@@ -1,816 +0,0 @@
// Copyright (c) 2006, Google Inc.
// All rights reserved.
//
// Author: Li Liu
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/utsname.h>
#include <sys/wait.h>
#include <cstdlib>
#include <cstdio>
#include <ctime>
#include <string.h>
#include "common/linux/file_id.h"
#include "client/linux/handler/linux_thread.h"
#include "client/minidump_file_writer.h"
#include "client/minidump_file_writer-inl.h"
#include "google_breakpad/common/minidump_format.h"
#include "client/linux/handler/minidump_generator.h"
#ifndef CLONE_UNTRACED
#define CLONE_UNTRACED 0x00800000
#endif
// This unnamed namespace contains helper functions.
namespace {
using namespace google_breakpad;
// Argument for the writer function.
struct WriterArgument {
MinidumpFileWriter *minidump_writer;
// Context for the callback.
void *version_context;
// Pid of the thread who called WriteMinidumpToFile
int requester_pid;
// The stack bottom of the thread which caused the dump.
// Mainly used to find the thread id of the crashed thread since signal
// handler may not be called in the thread who caused it.
uintptr_t crashed_stack_bottom;
// Pid of the crashing thread.
int crashed_pid;
// Signal number when crash happed. Can be 0 if this is a requested dump.
int signo;
// The ebp of the signal handler frame. Can be zero if this
// is a requested dump.
uintptr_t sighandler_ebp;
// Signal context when crash happed. Can be NULL if this is a requested dump.
// This is actually an out parameter, but it will be filled in at the start
// of the writer thread.
struct sigcontext *sig_ctx;
// Used to get information about the threads.
LinuxThread *thread_lister;
};
// Holding context information for the callback of finding the crashing thread.
struct FindCrashThreadContext {
const LinuxThread *thread_lister;
uintptr_t crashing_stack_bottom;
int crashing_thread_pid;
FindCrashThreadContext() :
thread_lister(NULL),
crashing_stack_bottom(0UL),
crashing_thread_pid(-1) {
}
};
// Callback for list threads.
// It will compare the stack bottom of the provided thread with the stack
// bottom of the crashed thread, it they are eqaul, this is thread is the one
// who crashed.
bool IsThreadCrashedCallback(const ThreadInfo &thread_info, void *context) {
FindCrashThreadContext *crashing_context =
static_cast<FindCrashThreadContext *>(context);
const LinuxThread *thread_lister = crashing_context->thread_lister;
struct user_regs_struct regs;
if (thread_lister->GetRegisters(thread_info.pid, &regs)) {
uintptr_t last_ebp = regs.ebp;
uintptr_t stack_bottom = thread_lister->GetThreadStackBottom(last_ebp);
if (stack_bottom > last_ebp &&
stack_bottom == crashing_context->crashing_stack_bottom) {
// Got it. Stop iteration.
crashing_context->crashing_thread_pid = thread_info.pid;
return false;
}
}
return true;
}
// Find the crashing thread id.
// This is done based on stack bottom comparing.
int FindCrashingThread(uintptr_t crashing_stack_bottom,
int requester_pid,
const LinuxThread *thread_lister) {
FindCrashThreadContext context;
context.thread_lister = thread_lister;
context.crashing_stack_bottom = crashing_stack_bottom;
CallbackParam<ThreadCallback> callback_param(IsThreadCrashedCallback,
&context);
thread_lister->ListThreads(&callback_param);
return context.crashing_thread_pid;
}
// Write the thread stack info minidump.
bool WriteThreadStack(uintptr_t last_ebp,
uintptr_t last_esp,
const LinuxThread *thread_lister,
UntypedMDRVA *memory,
MDMemoryDescriptor *loc) {
// Maximum stack size for a thread.
uintptr_t stack_bottom = thread_lister->GetThreadStackBottom(last_ebp);
if (stack_bottom > last_esp) {
int size = stack_bottom - last_esp;
if (size > 0) {
if (!memory->Allocate(size))
return false;
memory->Copy(reinterpret_cast<void*>(last_esp), size);
loc->start_of_memory_range = 0 | last_esp;
loc->memory = memory->location();
}
return true;
}
return false;
}
// Write CPU context based on signal context.
bool WriteContext(MDRawContextX86 *context, const struct sigcontext *sig_ctx,
const DebugRegs *debug_regs) {
assert(sig_ctx != NULL);
context->context_flags = MD_CONTEXT_X86_FULL;
context->gs = sig_ctx->gs;
context->fs = sig_ctx->fs;
context->es = sig_ctx->es;
context->ds = sig_ctx->ds;
context->cs = sig_ctx->cs;
context->ss = sig_ctx->ss;
context->edi = sig_ctx->edi;
context->esi = sig_ctx->esi;
context->ebp = sig_ctx->ebp;
context->esp = sig_ctx->esp;
context->ebx = sig_ctx->ebx;
context->edx = sig_ctx->edx;
context->ecx = sig_ctx->ecx;
context->eax = sig_ctx->eax;
context->eip = sig_ctx->eip;
context->eflags = sig_ctx->eflags;
if (sig_ctx->fpstate != NULL) {
context->context_flags = MD_CONTEXT_X86_FULL |
MD_CONTEXT_X86_FLOATING_POINT;
context->float_save.control_word = sig_ctx->fpstate->cw;
context->float_save.status_word = sig_ctx->fpstate->sw;
context->float_save.tag_word = sig_ctx->fpstate->tag;
context->float_save.error_offset = sig_ctx->fpstate->ipoff;
context->float_save.error_selector = sig_ctx->fpstate->cssel;
context->float_save.data_offset = sig_ctx->fpstate->dataoff;
context->float_save.data_selector = sig_ctx->fpstate->datasel;
memcpy(context->float_save.register_area, sig_ctx->fpstate->_st,
sizeof(context->float_save.register_area));
}
if (debug_regs != NULL) {
context->context_flags |= MD_CONTEXT_X86_DEBUG_REGISTERS;
context->dr0 = debug_regs->dr0;
context->dr1 = debug_regs->dr1;
context->dr2 = debug_regs->dr2;
context->dr3 = debug_regs->dr3;
context->dr6 = debug_regs->dr6;
context->dr7 = debug_regs->dr7;
}
return true;
}
// Write CPU context based on provided registers.
bool WriteContext(MDRawContextX86 *context,
const struct user_regs_struct *regs,
const struct user_fpregs_struct *fp_regs,
const DebugRegs *dbg_regs) {
if (!context || !regs)
return false;
context->context_flags = MD_CONTEXT_X86_FULL;
context->cs = regs->xcs;
context->ds = regs->xds;
context->es = regs->xes;
context->fs = regs->xfs;
context->gs = regs->xgs;
context->ss = regs->xss;
context->edi = regs->edi;
context->esi = regs->esi;
context->ebx = regs->ebx;
context->edx = regs->edx;
context->ecx = regs->ecx;
context->eax = regs->eax;
context->ebp = regs->ebp;
context->eip = regs->eip;
context->esp = regs->esp;
context->eflags = regs->eflags;
if (dbg_regs != NULL) {
context->context_flags |= MD_CONTEXT_X86_DEBUG_REGISTERS;
context->dr0 = dbg_regs->dr0;
context->dr1 = dbg_regs->dr1;
context->dr2 = dbg_regs->dr2;
context->dr3 = dbg_regs->dr3;
context->dr6 = dbg_regs->dr6;
context->dr7 = dbg_regs->dr7;
}
if (fp_regs != NULL) {
context->context_flags |= MD_CONTEXT_X86_FLOATING_POINT;
context->float_save.control_word = fp_regs->cwd;
context->float_save.status_word = fp_regs->swd;
context->float_save.tag_word = fp_regs->twd;
context->float_save.error_offset = fp_regs->fip;
context->float_save.error_selector = fp_regs->fcs;
context->float_save.data_offset = fp_regs->foo;
context->float_save.data_selector = fp_regs->fos;
context->float_save.data_selector = fp_regs->fos;
memcpy(context->float_save.register_area, fp_regs->st_space,
sizeof(context->float_save.register_area));
}
return true;
}
// Write information about a crashed thread.
// When a thread crash, kernel will write something on the stack for processing
// signal. This makes the current stack not reliable, and our stack walker
// won't figure out the whole call stack for this. So we write the stack at the
// time of the crash into the minidump file, not the current stack.
bool WriteCrashedThreadStream(MinidumpFileWriter *minidump_writer,
const WriterArgument *writer_args,
const ThreadInfo &thread_info,
MDRawThread *thread) {
assert(writer_args->sig_ctx != NULL);
thread->thread_id = thread_info.pid;
UntypedMDRVA memory(minidump_writer);
if (!WriteThreadStack(writer_args->sig_ctx->ebp,
writer_args->sig_ctx->esp,
writer_args->thread_lister,
&memory,
&thread->stack))
return false;
TypedMDRVA<MDRawContextX86> context(minidump_writer);
if (!context.Allocate())
return false;
thread->thread_context = context.location();
memset(context.get(), 0, sizeof(MDRawContextX86));
return WriteContext(context.get(), writer_args->sig_ctx, NULL);
}
// Write information about a thread.
// This function only processes thread running normally at the crash.
bool WriteThreadStream(MinidumpFileWriter *minidump_writer,
const LinuxThread *thread_lister,
const ThreadInfo &thread_info,
MDRawThread *thread) {
thread->thread_id = thread_info.pid;
struct user_regs_struct regs;
memset(&regs, 0, sizeof(regs));
if (!thread_lister->GetRegisters(thread_info.pid, &regs)) {
perror(NULL);
return false;
}
UntypedMDRVA memory(minidump_writer);
if (!WriteThreadStack(regs.ebp,
regs.esp,
thread_lister,
&memory,
&thread->stack))
return false;
struct user_fpregs_struct fp_regs;
DebugRegs dbg_regs;
memset(&fp_regs, 0, sizeof(fp_regs));
// Get all the registers.
thread_lister->GetFPRegisters(thread_info.pid, &fp_regs);
thread_lister->GetDebugRegisters(thread_info.pid, &dbg_regs);
// Write context
TypedMDRVA<MDRawContextX86> context(minidump_writer);
if (!context.Allocate())
return false;
thread->thread_context = context.location();
memset(context.get(), 0, sizeof(MDRawContextX86));
return WriteContext(context.get(), &regs, &fp_regs, &dbg_regs);
}
bool WriteCPUInformation(MDRawSystemInfo *sys_info) {
const char *proc_cpu_path = "/proc/cpuinfo";
char line[128];
char vendor_id[13];
const char vendor_id_name[] = "vendor_id";
const size_t vendor_id_name_length = sizeof(vendor_id_name) - 1;
struct CpuInfoEntry {
const char *info_name;
int value;
} cpu_info_table[] = {
{ "processor", -1 },
{ "model", 0 },
{ "stepping", 0 },
{ "cpuid level", 0 },
{ NULL, -1 },
};
memset(vendor_id, 0, sizeof(vendor_id));
FILE *fp = fopen(proc_cpu_path, "r");
if (fp != NULL) {
while (fgets(line, sizeof(line), fp)) {
CpuInfoEntry *entry = &cpu_info_table[0];
while (entry->info_name != NULL) {
if (!strncmp(line, entry->info_name, strlen(entry->info_name))) {
char *value = strchr(line, ':');
value++;
if (value != NULL)
sscanf(value, " %d", &(entry->value));
}
entry++;
}
// special case for vendor_id
if (!strncmp(line, vendor_id_name, vendor_id_name_length)) {
char *value = strchr(line, ':');
if (value == NULL)
continue;
value++;
while (*value && isspace(*value))
value++;
if (*value) {
size_t length = strlen(value);
// we don't want the trailing newline
if (value[length - 1] == '\n')
length--;
// ensure we have space for the value
if (length < sizeof(vendor_id))
strncpy(vendor_id, value, length);
}
}
}
fclose(fp);
}
// /proc/cpuinfo contains cpu id, change it into number by adding one.
cpu_info_table[0].value++;
sys_info->number_of_processors = cpu_info_table[0].value;
sys_info->processor_level = cpu_info_table[3].value;
sys_info->processor_revision = cpu_info_table[1].value << 8 |
cpu_info_table[2].value;
sys_info->processor_architecture = MD_CPU_ARCHITECTURE_UNKNOWN;
struct utsname uts;
if (uname(&uts) == 0) {
// Match i*86 and x86* as X86 architecture.
if ((strstr(uts.machine, "x86") == uts.machine) ||
(strlen(uts.machine) == 4 &&
uts.machine[0] == 'i' &&
uts.machine[2] == '8' &&
uts.machine[3] == '6')) {
sys_info->processor_architecture = MD_CPU_ARCHITECTURE_X86;
if (vendor_id[0] != '\0')
memcpy(sys_info->cpu.x86_cpu_info.vendor_id, vendor_id,
sizeof(sys_info->cpu.x86_cpu_info.vendor_id));
}
}
return true;
}
bool WriteOSInformation(MinidumpFileWriter *minidump_writer,
MDRawSystemInfo *sys_info) {
sys_info->platform_id = MD_OS_LINUX;
struct utsname uts;
if (uname(&uts) == 0) {
char os_version[512];
size_t space_left = sizeof(os_version);
memset(os_version, 0, space_left);
const char *os_info_table[] = {
uts.sysname,
uts.release,
uts.version,
uts.machine,
"GNU/Linux",
NULL
};
for (const char **cur_os_info = os_info_table;
*cur_os_info != NULL;
cur_os_info++) {
if (cur_os_info != os_info_table && space_left > 1) {
strcat(os_version, " ");
space_left--;
}
if (space_left > strlen(*cur_os_info)) {
strcat(os_version, *cur_os_info);
space_left -= strlen(*cur_os_info);
} else {
break;
}
}
MDLocationDescriptor location;
if (!minidump_writer->WriteString(os_version, 0, &location))
return false;
sys_info->csd_version_rva = location.rva;
}
return true;
}
// Callback context for get writting thread information.
struct ThreadInfoCallbackCtx {
MinidumpFileWriter *minidump_writer;
const WriterArgument *writer_args;
TypedMDRVA<MDRawThreadList> *list;
int thread_index;
};
// Callback run for writing threads information in the process.
bool ThreadInfomationCallback(const ThreadInfo &thread_info,
void *context) {
ThreadInfoCallbackCtx *callback_context =
static_cast<ThreadInfoCallbackCtx *>(context);
bool success = true;
MDRawThread thread;
memset(&thread, 0, sizeof(MDRawThread));
if (thread_info.pid != callback_context->writer_args->crashed_pid ||
callback_context->writer_args->sig_ctx == NULL) {
success = WriteThreadStream(callback_context->minidump_writer,
callback_context->writer_args->thread_lister,
thread_info, &thread);
} else {
success = WriteCrashedThreadStream(callback_context->minidump_writer,
callback_context->writer_args,
thread_info, &thread);
}
if (success) {
callback_context->list->CopyIndexAfterObject(
callback_context->thread_index++,
&thread, sizeof(MDRawThread));
}
return success;
}
// Stream writers
bool WriteThreadListStream(MinidumpFileWriter *minidump_writer,
const WriterArgument *writer_args,
MDRawDirectory *dir) {
// Get the thread information.
const LinuxThread *thread_lister = writer_args->thread_lister;
int thread_count = thread_lister->GetThreadCount();
if (thread_count < 0)
return false;
TypedMDRVA<MDRawThreadList> list(minidump_writer);
if (!list.AllocateObjectAndArray(thread_count, sizeof(MDRawThread)))
return false;
dir->stream_type = MD_THREAD_LIST_STREAM;
dir->location = list.location();
list.get()->number_of_threads = thread_count;
ThreadInfoCallbackCtx context;
context.minidump_writer = minidump_writer;
context.writer_args = writer_args;
context.list = &list;
context.thread_index = 0;
CallbackParam<ThreadCallback> callback_param(ThreadInfomationCallback,
&context);
int written = thread_lister->ListThreads(&callback_param);
return written == thread_count;
}
bool WriteCVRecord(MinidumpFileWriter *minidump_writer,
MDRawModule *module,
const char *module_path) {
TypedMDRVA<MDCVInfoPDB70> cv(minidump_writer);
// Only return the last path component of the full module path
const char *module_name = strrchr(module_path, '/');
// Increment past the slash
if (module_name)
++module_name;
else
module_name = "<Unknown>";
size_t module_name_length = strlen(module_name);
if (!cv.AllocateObjectAndArray(module_name_length + 1, sizeof(u_int8_t)))
return false;
if (!cv.CopyIndexAfterObject(0, const_cast<char *>(module_name),
module_name_length))
return false;
module->cv_record = cv.location();
MDCVInfoPDB70 *cv_ptr = cv.get();
memset(cv_ptr, 0, sizeof(MDCVInfoPDB70));
cv_ptr->cv_signature = MD_CVINFOPDB70_SIGNATURE;
cv_ptr->age = 0;
// Get the module identifier
FileID file_id(module_path);
unsigned char identifier[16];
if (file_id.ElfFileIdentifier(identifier)) {
cv_ptr->signature.data1 = (uint32_t)identifier[0] << 24 |
(uint32_t)identifier[1] << 16 | (uint32_t)identifier[2] << 8 |
(uint32_t)identifier[3];
cv_ptr->signature.data2 = (uint32_t)identifier[4] << 8 | identifier[5];
cv_ptr->signature.data3 = (uint32_t)identifier[6] << 8 | identifier[7];
cv_ptr->signature.data4[0] = identifier[8];
cv_ptr->signature.data4[1] = identifier[9];
cv_ptr->signature.data4[2] = identifier[10];
cv_ptr->signature.data4[3] = identifier[11];
cv_ptr->signature.data4[4] = identifier[12];
cv_ptr->signature.data4[5] = identifier[13];
cv_ptr->signature.data4[6] = identifier[14];
cv_ptr->signature.data4[7] = identifier[15];
}
return true;
}
struct ModuleInfoCallbackCtx {
MinidumpFileWriter *minidump_writer;
const WriterArgument *writer_args;
TypedMDRVA<MDRawModuleList> *list;
int module_index;
};
bool ModuleInfoCallback(const ModuleInfo &module_info,
void *context) {
ModuleInfoCallbackCtx *callback_context =
static_cast<ModuleInfoCallbackCtx *>(context);
// Skip those modules without name, or those that are not modules.
if (strlen(module_info.name) == 0 ||
!strchr(module_info.name, '/'))
return true;
MDRawModule module;
memset(&module, 0, sizeof(module));
MDLocationDescriptor loc;
if (!callback_context->minidump_writer->WriteString(module_info.name, 0,
&loc))
return false;
module.base_of_image = (u_int64_t)module_info.start_addr;
module.size_of_image = module_info.size;
module.module_name_rva = loc.rva;
if (!WriteCVRecord(callback_context->minidump_writer, &module,
module_info.name))
return false;
callback_context->list->CopyIndexAfterObject(
callback_context->module_index++, &module, MD_MODULE_SIZE);
return true;
}
bool WriteModuleListStream(MinidumpFileWriter *minidump_writer,
const WriterArgument *writer_args,
MDRawDirectory *dir) {
TypedMDRVA<MDRawModuleList> list(minidump_writer);
int module_count = writer_args->thread_lister->GetModuleCount();
if (module_count <= 0 ||
!list.AllocateObjectAndArray(module_count, MD_MODULE_SIZE))
return false;
dir->stream_type = MD_MODULE_LIST_STREAM;
dir->location = list.location();
list.get()->number_of_modules = module_count;
ModuleInfoCallbackCtx context;
context.minidump_writer = minidump_writer;
context.writer_args = writer_args;
context.list = &list;
context.module_index = 0;
CallbackParam<ModuleCallback> callback(ModuleInfoCallback, &context);
return writer_args->thread_lister->ListModules(&callback) == module_count;
}
bool WriteSystemInfoStream(MinidumpFileWriter *minidump_writer,
const WriterArgument *writer_args,
MDRawDirectory *dir) {
TypedMDRVA<MDRawSystemInfo> sys_info(minidump_writer);
if (!sys_info.Allocate())
return false;
dir->stream_type = MD_SYSTEM_INFO_STREAM;
dir->location = sys_info.location();
return WriteCPUInformation(sys_info.get()) &&
WriteOSInformation(minidump_writer, sys_info.get());
}
bool WriteExceptionStream(MinidumpFileWriter *minidump_writer,
const WriterArgument *writer_args,
MDRawDirectory *dir) {
// This happenes when this is not a crash, but a requested dump.
if (writer_args->sig_ctx == NULL)
return false;
TypedMDRVA<MDRawExceptionStream> exception(minidump_writer);
if (!exception.Allocate())
return false;
dir->stream_type = MD_EXCEPTION_STREAM;
dir->location = exception.location();
exception.get()->thread_id = writer_args->crashed_pid;
exception.get()->exception_record.exception_code = writer_args->signo;
exception.get()->exception_record.exception_flags = 0;
if (writer_args->sig_ctx != NULL) {
exception.get()->exception_record.exception_address =
writer_args->sig_ctx->eip;
} else {
return true;
}
// Write context of the exception.
TypedMDRVA<MDRawContextX86> context(minidump_writer);
if (!context.Allocate())
return false;
exception.get()->thread_context = context.location();
memset(context.get(), 0, sizeof(MDRawContextX86));
return WriteContext(context.get(), writer_args->sig_ctx, NULL);
}
bool WriteMiscInfoStream(MinidumpFileWriter *minidump_writer,
const WriterArgument *writer_args,
MDRawDirectory *dir) {
TypedMDRVA<MDRawMiscInfo> info(minidump_writer);
if (!info.Allocate())
return false;
dir->stream_type = MD_MISC_INFO_STREAM;
dir->location = info.location();
info.get()->size_of_info = sizeof(MDRawMiscInfo);
info.get()->flags1 = MD_MISCINFO_FLAGS1_PROCESS_ID;
info.get()->process_id = writer_args->requester_pid;
return true;
}
bool WriteBreakpadInfoStream(MinidumpFileWriter *minidump_writer,
const WriterArgument *writer_args,
MDRawDirectory *dir) {
TypedMDRVA<MDRawBreakpadInfo> info(minidump_writer);
if (!info.Allocate())
return false;
dir->stream_type = MD_BREAKPAD_INFO_STREAM;
dir->location = info.location();
info.get()->validity = MD_BREAKPAD_INFO_VALID_DUMP_THREAD_ID |
MD_BREAKPAD_INFO_VALID_REQUESTING_THREAD_ID;
info.get()->dump_thread_id = getpid();
info.get()->requesting_thread_id = writer_args->requester_pid;
return true;
}
// Prototype of writer functions.
typedef bool (*WriteStringFN)(MinidumpFileWriter *,
const WriterArgument *,
MDRawDirectory *);
// Function table to writer a full minidump.
WriteStringFN writers[] = {
WriteThreadListStream,
WriteModuleListStream,
WriteSystemInfoStream,
WriteExceptionStream,
WriteMiscInfoStream,
WriteBreakpadInfoStream,
};
// Will call each writer function in the writers table.
// It runs in a different process from the crashing process, but sharing
// the same address space. This enables it to use ptrace functions.
int Write(void *argument) {
WriterArgument *writer_args =
static_cast<WriterArgument *>(argument);
if (!writer_args->thread_lister->SuspendAllThreads())
return -1;
if (writer_args->sighandler_ebp != 0 &&
writer_args->thread_lister->FindSigContext(writer_args->sighandler_ebp,
&writer_args->sig_ctx)) {
writer_args->crashed_stack_bottom =
writer_args->thread_lister->GetThreadStackBottom(
writer_args->sig_ctx->ebp);
int crashed_pid = FindCrashingThread(writer_args->crashed_stack_bottom,
writer_args->requester_pid,
writer_args->thread_lister);
if (crashed_pid > 0)
writer_args->crashed_pid = crashed_pid;
}
MinidumpFileWriter *minidump_writer = writer_args->minidump_writer;
TypedMDRVA<MDRawHeader> header(minidump_writer);
TypedMDRVA<MDRawDirectory> dir(minidump_writer);
if (!header.Allocate())
return 0;
int writer_count = sizeof(writers) / sizeof(writers[0]);
// Need directory space for all writers.
if (!dir.AllocateArray(writer_count))
return 0;
header.get()->signature = MD_HEADER_SIGNATURE;
header.get()->version = MD_HEADER_VERSION;
header.get()->time_date_stamp = time(NULL);
header.get()->stream_count = writer_count;
header.get()->stream_directory_rva = dir.position();
int dir_index = 0;
MDRawDirectory local_dir;
for (int i = 0; i < writer_count; ++i) {
if (writers[i](minidump_writer, writer_args, &local_dir))
dir.CopyIndex(dir_index++, &local_dir);
}
writer_args->thread_lister->ResumeAllThreads();
return 0;
}
} // namespace
namespace google_breakpad {
MinidumpGenerator::MinidumpGenerator() {
AllocateStack();
}
MinidumpGenerator::~MinidumpGenerator() {
}
void MinidumpGenerator::AllocateStack() {
stack_.reset(new char[kStackSize]);
}
bool MinidumpGenerator::WriteMinidumpToFile(const char *file_pathname,
int signo,
uintptr_t sighandler_ebp,
struct sigcontext **sig_ctx) const {
assert(file_pathname != NULL);
assert(stack_ != NULL);
if (stack_ == NULL || file_pathname == NULL)
return false;
MinidumpFileWriter minidump_writer;
if (minidump_writer.Open(file_pathname)) {
WriterArgument argument;
memset(&argument, 0, sizeof(argument));
LinuxThread thread_lister(getpid());
argument.thread_lister = &thread_lister;
argument.minidump_writer = &minidump_writer;
argument.requester_pid = getpid();
argument.crashed_pid = getpid();
argument.signo = signo;
argument.sighandler_ebp = sighandler_ebp;
argument.sig_ctx = NULL;
int cloned_pid = clone(Write, stack_.get() + kStackSize,
CLONE_VM | CLONE_FILES | CLONE_FS | CLONE_UNTRACED,
(void*)&argument);
waitpid(cloned_pid, NULL, __WALL);
if (sig_ctx != NULL)
*sig_ctx = argument.sig_ctx;
return true;
}
return false;
}
} // namespace google_breakpad

View File

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

View File

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

View File

@@ -0,0 +1,130 @@
// Copyright (c) 2009, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef CLIENT_LINUX_MINIDUMP_WRITER_LINE_READER_H_
#define CLIENT_LINUX_MINIDUMP_WRITER_LINE_READER_H_
#include <stdint.h>
#include <assert.h>
#include <string.h>
#include "common/linux/linux_syscall_support.h"
namespace google_breakpad {
// A class for reading a file, line by line, without using fopen/fgets or other
// functions which may allocate memory.
class LineReader {
public:
LineReader(int fd)
: fd_(fd),
hit_eof_(false),
buf_used_(0) {
}
// The maximum length of a line.
static const size_t kMaxLineLen = 512;
// Return the next line from the file.
// line: (output) a pointer to the start of the line. The line is NUL
// terminated.
// len: (output) the length of the line (not inc the NUL byte)
//
// Returns true iff successful (false on EOF).
//
// One must call |PopLine| after this function, otherwise you'll continue to
// get the same line over and over.
bool GetNextLine(const char **line, unsigned *len) {
for (;;) {
if (buf_used_ == 0 && hit_eof_)
return false;
for (unsigned i = 0; i < buf_used_; ++i) {
if (buf_[i] == '\n' || buf_[i] == 0) {
buf_[i] = 0;
*len = i;
*line = buf_;
return true;
}
}
if (buf_used_ == sizeof(buf_)) {
// we scanned the whole buffer and didn't find an end-of-line marker.
// This line is too long to process.
return false;
}
// We didn't find any end-of-line terminators in the buffer. However, if
// this is the last line in the file it might not have one:
if (hit_eof_) {
assert(buf_used_);
// There's room for the NUL because of the buf_used_ == sizeof(buf_)
// check above.
buf_[buf_used_] = 0;
*len = buf_used_;
buf_used_ += 1; // since we appended the NUL.
*line = buf_;
return true;
}
// Otherwise, we should pull in more data from the file
const ssize_t n = sys_read(fd_, buf_ + buf_used_,
sizeof(buf_) - buf_used_);
if (n < 0) {
return false;
} else if (n == 0) {
hit_eof_ = true;
} else {
buf_used_ += n;
}
// At this point, we have either set the hit_eof_ flag, or we have more
// data to process...
}
}
void PopLine(unsigned len) {
// len doesn't include the NUL byte at the end.
assert(buf_used_ >= len + 1);
buf_used_ -= len + 1;
memmove(buf_, buf_ + len + 1, buf_used_);
}
private:
const int fd_;
bool hit_eof_;
unsigned buf_used_;
char buf_[kMaxLineLen];
};
} // namespace google_breakpad
#endif // CLIENT_LINUX_MINIDUMP_WRITER_LINE_READER_H_

View File

@@ -0,0 +1,184 @@
// Copyright (c) 2009, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include "client/linux/minidump_writer/line_reader.h"
#include "breakpad_googletest_includes.h"
using namespace google_breakpad;
static int TemporaryFile() {
static const char templ[] = "/tmp/line-reader-unittest-XXXXXX";
char templ_copy[sizeof(templ)];
memcpy(templ_copy, templ, sizeof(templ));
const int fd = mkstemp(templ_copy);
if (fd >= 0)
unlink(templ_copy);
return fd;
}
namespace {
typedef testing::Test LineReaderTest;
}
TEST(LineReaderTest, EmptyFile) {
const int fd = TemporaryFile();
LineReader reader(fd);
const char *line;
unsigned len;
ASSERT_FALSE(reader.GetNextLine(&line, &len));
close(fd);
}
TEST(LineReaderTest, OneLineTerminated) {
const int fd = TemporaryFile();
write(fd, "a\n", 2);
lseek(fd, 0, SEEK_SET);
LineReader reader(fd);
const char *line;
unsigned len;
ASSERT_TRUE(reader.GetNextLine(&line, &len));
ASSERT_EQ(len, 1);
ASSERT_EQ(line[0], 'a');
ASSERT_EQ(line[1], 0);
reader.PopLine(len);
ASSERT_FALSE(reader.GetNextLine(&line, &len));
close(fd);
}
TEST(LineReaderTest, OneLine) {
const int fd = TemporaryFile();
write(fd, "a", 1);
lseek(fd, 0, SEEK_SET);
LineReader reader(fd);
const char *line;
unsigned len;
ASSERT_TRUE(reader.GetNextLine(&line, &len));
ASSERT_EQ(len, 1);
ASSERT_EQ(line[0], 'a');
ASSERT_EQ(line[1], 0);
reader.PopLine(len);
ASSERT_FALSE(reader.GetNextLine(&line, &len));
close(fd);
}
TEST(LineReaderTest, TwoLinesTerminated) {
const int fd = TemporaryFile();
write(fd, "a\nb\n", 4);
lseek(fd, 0, SEEK_SET);
LineReader reader(fd);
const char *line;
unsigned len;
ASSERT_TRUE(reader.GetNextLine(&line, &len));
ASSERT_EQ(len, 1);
ASSERT_EQ(line[0], 'a');
ASSERT_EQ(line[1], 0);
reader.PopLine(len);
ASSERT_TRUE(reader.GetNextLine(&line, &len));
ASSERT_EQ(len, 1);
ASSERT_EQ(line[0], 'b');
ASSERT_EQ(line[1], 0);
reader.PopLine(len);
ASSERT_FALSE(reader.GetNextLine(&line, &len));
close(fd);
}
TEST(LineReaderTest, TwoLines) {
const int fd = TemporaryFile();
write(fd, "a\nb", 3);
lseek(fd, 0, SEEK_SET);
LineReader reader(fd);
const char *line;
unsigned len;
ASSERT_TRUE(reader.GetNextLine(&line, &len));
ASSERT_EQ(len, 1);
ASSERT_EQ(line[0], 'a');
ASSERT_EQ(line[1], 0);
reader.PopLine(len);
ASSERT_TRUE(reader.GetNextLine(&line, &len));
ASSERT_EQ(len, 1);
ASSERT_EQ(line[0], 'b');
ASSERT_EQ(line[1], 0);
reader.PopLine(len);
ASSERT_FALSE(reader.GetNextLine(&line, &len));
close(fd);
}
TEST(LineReaderTest, MaxLength) {
const int fd = TemporaryFile();
char l[LineReader::kMaxLineLen - 1];
memset(l, 'a', sizeof(l));
write(fd, l, sizeof(l));
lseek(fd, 0, SEEK_SET);
LineReader reader(fd);
const char *line;
unsigned len;
ASSERT_TRUE(reader.GetNextLine(&line, &len));
ASSERT_EQ(len, sizeof(l));
ASSERT_TRUE(memcmp(l, line, sizeof(l)) == 0);
ASSERT_EQ(line[len], 0);
close(fd);
}
TEST(LineReaderTest, TooLong) {
const int fd = TemporaryFile();
char l[LineReader::kMaxLineLen];
memset(l, 'a', sizeof(l));
write(fd, l, sizeof(l));
lseek(fd, 0, SEEK_SET);
LineReader reader(fd);
const char *line;
unsigned len;
ASSERT_FALSE(reader.GetNextLine(&line, &len));
close(fd);
}

View File

@@ -0,0 +1,419 @@
// Copyright (c) 2009, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// This code deals with the mechanics of getting information about a crashed
// process. Since this code may run in a compromised address space, the same
// rules apply as detailed at the top of minidump_writer.h: no libc calls and
// use the alternative allocator.
#include "client/linux/minidump_writer/linux_dumper.h"
#include <assert.h>
#include <limits.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include "client/linux/minidump_writer/directory_reader.h"
#include "client/linux/minidump_writer/line_reader.h"
#include "common/linux/linux_libc_support.h"
#include "common/linux/linux_syscall_support.h"
// Suspend a thread by attaching to it.
static bool SuspendThread(pid_t pid) {
// This may fail if the thread has just died or debugged.
errno = 0;
if (sys_ptrace(PTRACE_ATTACH, pid, NULL, NULL) != 0 &&
errno != 0) {
return false;
}
while (sys_waitpid(pid, NULL, __WALL) < 0) {
if (errno != EINTR) {
sys_ptrace(PTRACE_DETACH, pid, NULL, NULL);
return false;
}
}
return true;
}
// Resume a thread by detaching from it.
static bool ResumeThread(pid_t pid) {
return sys_ptrace(PTRACE_DETACH, pid, NULL, NULL) >= 0;
}
namespace google_breakpad {
LinuxDumper::LinuxDumper(int pid)
: pid_(pid),
threads_suspened_(false),
threads_(&allocator_, 8),
mappings_(&allocator_) {
}
bool LinuxDumper::Init() {
return EnumerateThreads(&threads_) &&
EnumerateMappings(&mappings_);
}
bool LinuxDumper::ThreadsSuspend() {
if (threads_suspened_)
return true;
bool good = true;
for (size_t i = 0; i < threads_.size(); ++i)
good &= SuspendThread(threads_[i]);
threads_suspened_ = true;
return good;
}
bool LinuxDumper::ThreadsResume() {
if (!threads_suspened_)
return false;
bool good = true;
for (size_t i = 0; i < threads_.size(); ++i)
good &= ResumeThread(threads_[i]);
threads_suspened_ = false;
return good;
}
void
LinuxDumper::BuildProcPath(char* path, pid_t pid, const char* node) const {
assert(path);
if (!path) {
return;
}
path[0] = '\0';
const unsigned pid_len = my_int_len(pid);
assert(node);
if (!node) {
return;
}
size_t node_len = my_strlen(node);
assert(node_len < NAME_MAX);
if (node_len >= NAME_MAX) {
return;
}
assert(node_len > 0);
if (node_len == 0) {
return;
}
assert(pid > 0);
if (pid <= 0) {
return;
}
const size_t total_length = 6 + pid_len + 1 + node_len;
assert(total_length < NAME_MAX);
if (total_length >= NAME_MAX) {
return;
}
memcpy(path, "/proc/", 6);
my_itos(path + 6, pid, pid_len);
memcpy(path + 6 + pid_len, "/", 1);
memcpy(path + 6 + pid_len + 1, node, node_len);
memcpy(path + total_length, "\0", 1);
}
void*
LinuxDumper::FindBeginningOfLinuxGateSharedLibrary(const pid_t pid) const {
char auxv_path[80];
BuildProcPath(auxv_path, pid, "auxv");
// If BuildProcPath errors out due to invalid input, we'll handle it when
// we try to sys_open the file.
// Find the AT_SYSINFO_EHDR entry for linux-gate.so
// See http://www.trilithium.com/johan/2005/08/linux-gate/ for more
// information.
int fd = sys_open(auxv_path, O_RDONLY, 0);
if (fd < 0) {
return NULL;
}
elf_aux_entry one_aux_entry;
while (sys_read(fd,
&one_aux_entry,
sizeof(elf_aux_entry)) == sizeof(elf_aux_entry) &&
one_aux_entry.a_type != AT_NULL) {
if (one_aux_entry.a_type == AT_SYSINFO_EHDR) {
close(fd);
return reinterpret_cast<void*>(one_aux_entry.a_un.a_val);
}
}
close(fd);
return NULL;
}
bool
LinuxDumper::EnumerateMappings(wasteful_vector<MappingInfo*>* result) const {
char maps_path[80];
BuildProcPath(maps_path, pid_, "maps");
// linux_gate_loc is the beginning of the kernel's mapping of
// linux-gate.so in the process. It doesn't actually show up in the
// maps list as a filename, so we use the aux vector to find it's
// load location and special case it's entry when creating the list
// of mappings.
const void* linux_gate_loc;
linux_gate_loc = FindBeginningOfLinuxGateSharedLibrary(pid_);
const int fd = sys_open(maps_path, O_RDONLY, 0);
if (fd < 0)
return false;
LineReader* const line_reader = new(allocator_) LineReader(fd);
const char* line;
unsigned line_len;
while (line_reader->GetNextLine(&line, &line_len)) {
uintptr_t start_addr, end_addr, offset;
const char* i1 = my_read_hex_ptr(&start_addr, line);
if (*i1 == '-') {
const char* i2 = my_read_hex_ptr(&end_addr, i1 + 1);
if (*i2 == ' ') {
const char* i3 = my_read_hex_ptr(&offset, i2 + 6 /* skip ' rwxp ' */);
if (*i3 == ' ') {
MappingInfo* const module = new(allocator_) MappingInfo;
memset(module, 0, sizeof(MappingInfo));
module->start_addr = start_addr;
module->size = end_addr - start_addr;
module->offset = offset;
const char* name = NULL;
// Only copy name if the name is a valid path name, or if
// we've found the VDSO image
if ((name = my_strchr(line, '/')) != NULL) {
const unsigned l = my_strlen(name);
if (l < sizeof(module->name))
memcpy(module->name, name, l);
} else if (linux_gate_loc &&
reinterpret_cast<void*>(module->start_addr) ==
linux_gate_loc) {
memcpy(module->name,
kLinuxGateLibraryName,
my_strlen(kLinuxGateLibraryName));
module->offset = 0;
}
result->push_back(module);
}
}
}
line_reader->PopLine(line_len);
}
sys_close(fd);
return result->size() > 0;
}
// Parse /proc/$pid/task to list all the threads of the process identified by
// pid.
bool LinuxDumper::EnumerateThreads(wasteful_vector<pid_t>* result) const {
char task_path[80];
BuildProcPath(task_path, pid_, "task");
const int fd = sys_open(task_path, O_RDONLY | O_DIRECTORY, 0);
if (fd < 0)
return false;
DirectoryReader* dir_reader = new(allocator_) DirectoryReader(fd);
// The directory may contain duplicate entries which we filter by assuming
// that they are consecutive.
int last_tid = -1;
const char* dent_name;
while (dir_reader->GetNextEntry(&dent_name)) {
if (my_strcmp(dent_name, ".") &&
my_strcmp(dent_name, "..")) {
int tid = 0;
if (my_strtoui(&tid, dent_name) &&
last_tid != tid) {
last_tid = tid;
result->push_back(tid);
}
}
dir_reader->PopEntry();
}
sys_close(fd);
return true;
}
// Read thread info from /proc/$pid/status.
// Fill out the |tgid|, |ppid| and |pid| members of |info|. If unavailible,
// these members are set to -1. Returns true iff all three members are
// availible.
bool LinuxDumper::ThreadInfoGet(pid_t tid, ThreadInfo* info) {
assert(info != NULL);
char status_path[80];
BuildProcPath(status_path, tid, "status");
const int fd = open(status_path, O_RDONLY);
if (fd < 0)
return false;
LineReader* const line_reader = new(allocator_) LineReader(fd);
const char* line;
unsigned line_len;
info->ppid = info->tgid = -1;
while (line_reader->GetNextLine(&line, &line_len)) {
if (my_strncmp("Tgid:\t", line, 6) == 0) {
my_strtoui(&info->tgid, line + 6);
} else if (my_strncmp("PPid:\t", line, 6) == 0) {
my_strtoui(&info->ppid, line + 6);
}
line_reader->PopLine(line_len);
}
if (info->ppid == -1 || info->tgid == -1)
return false;
if (sys_ptrace(PTRACE_GETREGS, tid, NULL, &info->regs) == -1 ||
sys_ptrace(PTRACE_GETFPREGS, tid, NULL, &info->fpregs) == -1) {
return false;
}
#if defined(__i386) || defined(__x86_64)
if (sys_ptrace(PTRACE_GETFPXREGS, tid, NULL, &info->fpxregs) == -1)
return false;
for (unsigned i = 0; i < ThreadInfo::kNumDebugRegisters; ++i) {
if (sys_ptrace(
PTRACE_PEEKUSER, tid,
reinterpret_cast<void*> (offsetof(struct user,
u_debugreg[0]) + i *
sizeof(debugreg_t)),
&info->dregs[i]) == -1) {
return false;
}
}
#endif
const uint8_t* stack_pointer;
#if defined(__i386)
memcpy(&stack_pointer, &info->regs.esp, sizeof(info->regs.esp));
#elif defined(__x86_64)
memcpy(&stack_pointer, &info->regs.rsp, sizeof(info->regs.rsp));
#else
#error "This code hasn't been ported to your platform yet."
#endif
if (!GetStackInfo(&info->stack, &info->stack_len,
(uintptr_t) stack_pointer))
return false;
return true;
}
// Get information about the stack, given the stack pointer. We don't try to
// walk the stack since we might not have all the information needed to do
// unwind. So we just grab, up to, 32k of stack.
bool LinuxDumper::GetStackInfo(const void** stack, size_t* stack_len,
uintptr_t int_stack_pointer) {
#if defined(__i386) || defined(__x86_64)
static const bool stack_grows_down = true;
static const uintptr_t page_size = 4096;
#else
#error "This code has not been ported to your platform yet."
#endif
// Move the stack pointer to the bottom of the page that it's in.
uint8_t* const stack_pointer =
reinterpret_cast<uint8_t*>(int_stack_pointer & ~(page_size - 1));
// The number of bytes of stack which we try to capture.
static unsigned kStackToCapture = 32 * 1024;
const MappingInfo* mapping = FindMapping(stack_pointer);
if (!mapping)
return false;
if (stack_grows_down) {
const ptrdiff_t offset = stack_pointer - (uint8_t*) mapping->start_addr;
const ptrdiff_t distance_to_end =
static_cast<ptrdiff_t>(mapping->size) - offset;
*stack_len = distance_to_end > kStackToCapture ?
kStackToCapture : distance_to_end;
*stack = stack_pointer;
} else {
const ptrdiff_t offset = stack_pointer - (uint8_t*) mapping->start_addr;
*stack_len = offset > kStackToCapture ? kStackToCapture : offset;
*stack = stack_pointer - *stack_len;
}
return true;
}
// static
void LinuxDumper::CopyFromProcess(void* dest, pid_t child, const void* src,
size_t length) {
unsigned long tmp;
size_t done = 0;
static const size_t word_size = sizeof(tmp);
uint8_t* const local = (uint8_t*) dest;
uint8_t* const remote = (uint8_t*) src;
while (done < length) {
const size_t l = length - done > word_size ? word_size : length - done;
if (sys_ptrace(PTRACE_PEEKDATA, child, remote + done, &tmp) == -1)
tmp = 0;
memcpy(local + done, &tmp, l);
done += l;
}
}
// Find the mapping which the given memory address falls in.
const MappingInfo* LinuxDumper::FindMapping(const void* address) const {
const uintptr_t addr = (uintptr_t) address;
for (size_t i = 0; i < mappings_.size(); ++i) {
const uintptr_t start = static_cast<uintptr_t>(mappings_[i]->start_addr);
if (addr >= start && addr - start < mappings_[i]->size)
return mappings_[i];
}
return NULL;
}
} // namespace google_breakpad

View File

@@ -0,0 +1,142 @@
// Copyright (c) 2009, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef CLIENT_LINUX_MINIDUMP_WRITER_LINUX_DUMPER_H_
#define CLIENT_LINUX_MINIDUMP_WRITER_LINUX_DUMPER_H_
#include <elf.h>
#include <stdint.h>
#include <sys/user.h>
#include <linux/limits.h>
#include "common/linux/memory.h"
namespace google_breakpad {
typedef typeof(((struct user*) 0)->u_debugreg[0]) debugreg_t;
// Typedef for our parsing of the auxv variables in /proc/pid/auxv.
#if defined(__i386)
typedef Elf32_auxv_t elf_aux_entry;
#elif defined(__x86_64__)
typedef Elf64_auxv_t elf_aux_entry;
#endif
// When we find the VDSO mapping in the process's address space, this
// is the name we use for it when writing it to the minidump.
// This should always be less than NAME_MAX!
const char kLinuxGateLibraryName[] = "linux-gate.so";
// We produce one of these structures for each thread in the crashed process.
struct ThreadInfo {
pid_t tgid; // thread group id
pid_t ppid; // parent process
// Even on platforms where the stack grows down, the following will point to
// the smallest address in the stack.
const void* stack; // pointer to the stack area
size_t stack_len; // length of the stack to copy
user_regs_struct regs;
user_fpregs_struct fpregs;
#if defined(__i386) || defined(__x86_64)
user_fpxregs_struct fpxregs;
static const unsigned kNumDebugRegisters = 8;
debugreg_t dregs[8];
#endif
};
// One of these is produced for each mapping in the process (i.e. line in
// /proc/$x/maps).
struct MappingInfo {
uintptr_t start_addr;
size_t size;
size_t offset; // offset into the backed file.
char name[NAME_MAX];
};
class LinuxDumper {
public:
explicit LinuxDumper(pid_t pid);
// Parse the data for |threads| and |mappings|.
bool Init();
// Suspend/resume all threads in the given process.
bool ThreadsSuspend();
bool ThreadsResume();
// Read information about the given thread. Returns true on success. One must
// have called |ThreadsSuspend| first.
bool ThreadInfoGet(pid_t tid, ThreadInfo* info);
// These are only valid after a call to |Init|.
const wasteful_vector<pid_t> &threads() { return threads_; }
const wasteful_vector<MappingInfo*> &mappings() { return mappings_; }
const MappingInfo* FindMapping(const void* address) const;
// Find a block of memory to take as the stack given the top of stack pointer.
// stack: (output) the lowest address in the memory area
// stack_len: (output) the length of the memory area
// stack_top: the current top of the stack
bool GetStackInfo(const void** stack, size_t* stack_len, uintptr_t stack_top);
PageAllocator* allocator() { return &allocator_; }
// memcpy from a remote process.
static void CopyFromProcess(void* dest, pid_t child, const void* src,
size_t length);
// Builds a proc path for a certain pid for a node. path is a
// character array that is overwritten, and node is the final node
// without any slashes.
void BuildProcPath(char* path, pid_t pid, const char* node) const;
// Utility method to find the location of where the kernel has
// mapped linux-gate.so in memory(shows up in /proc/pid/maps as
// [vdso], but we can't guarantee that it's the only virtual dynamic
// shared object. Parsing the auxilary vector for AT_SYSINFO_EHDR
// is the safest way to go.)
void* FindBeginningOfLinuxGateSharedLibrary(const pid_t pid) const;
private:
bool EnumerateMappings(wasteful_vector<MappingInfo*>* result) const;
bool EnumerateThreads(wasteful_vector<pid_t>* result) const;
const pid_t pid_;
mutable PageAllocator allocator_;
bool threads_suspened_;
wasteful_vector<pid_t> threads_; // the ids of all the threads
wasteful_vector<MappingInfo*> mappings_; // info from /proc/<pid>/maps
};
} // namespace google_breakpad
#endif // CLIENT_LINUX_HANDLER_LINUX_DUMPER_H_

View File

@@ -0,0 +1,118 @@
// Copyright (c) 2009, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <unistd.h>
#include "client/linux/minidump_writer/linux_dumper.h"
#include "breakpad_googletest_includes.h"
using namespace google_breakpad;
namespace {
typedef testing::Test LinuxDumperTest;
}
TEST(LinuxDumperTest, Setup) {
LinuxDumper dumper(getpid());
}
TEST(LinuxDumperTest, FindMappings) {
LinuxDumper dumper(getpid());
ASSERT_TRUE(dumper.Init());
ASSERT_TRUE(dumper.FindMapping(reinterpret_cast<void*>(getpid)));
ASSERT_TRUE(dumper.FindMapping(reinterpret_cast<void*>(printf)));
ASSERT_FALSE(dumper.FindMapping(NULL));
}
TEST(LinuxDumperTest, ThreadList) {
LinuxDumper dumper(getpid());
ASSERT_TRUE(dumper.Init());
ASSERT_GE(dumper.threads().size(), 1);
bool found = false;
for (size_t i = 0; i < dumper.threads().size(); ++i) {
if (dumper.threads()[i] == getpid()) {
found = true;
break;
}
}
}
TEST(LinuxDumperTest, BuildProcPath) {
const pid_t pid = getpid();
LinuxDumper dumper(pid);
char maps_path[256] = "dummymappath";
char maps_path_expected[256];
snprintf(maps_path_expected, sizeof(maps_path_expected),
"/proc/%d/maps", pid);
dumper.BuildProcPath(maps_path, pid, "maps");
ASSERT_STREQ(maps_path, maps_path_expected);
// In release mode, we expect BuildProcPath to handle the invalid
// parameters correctly and fill map_path with an empty
// NULL-terminated string.
#ifdef NDEBUG
snprintf(maps_path, sizeof(maps_path), "dummymappath");
dumper.BuildProcPath(maps_path, 0, "maps");
EXPECT_STREQ(maps_path, "");
snprintf(maps_path, sizeof(maps_path), "dummymappath");
dumper.BuildProcPath(maps_path, getpid(), "");
EXPECT_STREQ(maps_path, "");
snprintf(maps_path, sizeof(maps_path), "dummymappath");
dumper.BuildProcPath(maps_path, getpid(), NULL);
EXPECT_STREQ(maps_path, "");
#endif
}
TEST(LinuxDumperTest, MappingsIncludeLinuxGate) {
LinuxDumper dumper(getpid());
ASSERT_TRUE(dumper.Init());
void* linux_gate_loc = dumper.FindBeginningOfLinuxGateSharedLibrary(getpid());
if (linux_gate_loc) {
bool found_linux_gate = false;
const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
const MappingInfo* mapping;
for (unsigned i = 0; i < mappings.size(); ++i) {
mapping = mappings[i];
if (!strcmp(mapping->name, kLinuxGateLibraryName)) {
found_linux_gate = true;
break;
}
}
EXPECT_TRUE(found_linux_gate);
EXPECT_EQ(linux_gate_loc, reinterpret_cast<void*>(mapping->start_addr));
EXPECT_EQ(0, memcmp(linux_gate_loc, ELFMAG, SELFMAG));
}
}

View File

@@ -0,0 +1,872 @@
// Copyright (c) 2009, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// This code writes out minidump files:
// http://msdn.microsoft.com/en-us/library/ms680378(VS.85,loband).aspx
//
// Minidumps are a Microsoft format which Breakpad uses for recording crash
// dumps. This code has to run in a compromised environment (the address space
// may have received SIGSEGV), thus the following rules apply:
// * You may not enter the dynamic linker. This means that we cannot call
// any symbols in a shared library (inc libc). Because of this we replace
// libc functions in linux_libc_support.h.
// * You may not call syscalls via the libc wrappers. This rule is a subset
// of the first rule but it bears repeating. We have direct wrappers
// around the system calls in linux_syscall_support.h.
// * You may not malloc. There's an alternative allocator in memory.h and
// a canonical instance in the LinuxDumper object. We use the placement
// new form to allocate objects and we don't delete them.
#include "client/linux/minidump_writer/minidump_writer.h"
#include "client/minidump_file_writer-inl.h"
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/ucontext.h>
#include <sys/user.h>
#include <sys/utsname.h>
#include "client/minidump_file_writer.h"
#include "google_breakpad/common/minidump_format.h"
#include "google_breakpad/common/minidump_cpu_amd64.h"
#include "google_breakpad/common/minidump_cpu_x86.h"
#include "client/linux/handler/exception_handler.h"
#include "client/linux/minidump_writer/line_reader.h"
#include "client/linux/minidump_writer//linux_dumper.h"
#include "common/linux/linux_libc_support.h"
#include "common/linux/linux_syscall_support.h"
// These are additional minidump stream values which are specific to the linux
// breakpad implementation.
enum {
MD_LINUX_CPU_INFO = 0x47670003, /* /proc/cpuinfo */
MD_LINUX_PROC_STATUS = 0x47670004, /* /proc/$x/status */
MD_LINUX_LSB_RELEASE = 0x47670005, /* /etc/lsb-release */
MD_LINUX_CMD_LINE = 0x47670006, /* /proc/$x/cmdline */
MD_LINUX_ENVIRON = 0x47670007, /* /proc/$x/environ */
MD_LINUX_AUXV = 0x47670008, /* /proc/$x/auxv */
};
// Minidump defines register structures which are different from the raw
// structures which we get from the kernel. These are platform specific
// functions to juggle the ucontext and user structures into minidump format.
#if defined(__i386)
typedef MDRawContextX86 RawContextCPU;
// Write a uint16_t to memory
// out: memory location to write to
// v: value to write.
static void U16(void* out, uint16_t v) {
memcpy(out, &v, sizeof(v));
}
// Write a uint32_t to memory
// out: memory location to write to
// v: value to write.
static void U32(void* out, uint32_t v) {
memcpy(out, &v, sizeof(v));
}
// Juggle an x86 user_(fp|fpx|)regs_struct into minidump format
// out: the minidump structure
// info: the collection of register structures.
static void CPUFillFromThreadInfo(MDRawContextX86 *out,
const google_breakpad::ThreadInfo &info) {
out->context_flags = MD_CONTEXT_X86_ALL;
out->dr0 = info.dregs[0];
out->dr1 = info.dregs[1];
out->dr2 = info.dregs[2];
out->dr3 = info.dregs[3];
// 4 and 5 deliberatly omitted because they aren't included in the minidump
// format.
out->dr6 = info.dregs[6];
out->dr7 = info.dregs[7];
out->gs = info.regs.xgs;
out->fs = info.regs.xfs;
out->es = info.regs.xes;
out->ds = info.regs.xds;
out->edi = info.regs.edi;
out->esi = info.regs.esi;
out->ebx = info.regs.ebx;
out->edx = info.regs.edx;
out->ecx = info.regs.ecx;
out->eax = info.regs.eax;
out->ebp = info.regs.ebp;
out->eip = info.regs.eip;
out->cs = info.regs.xcs;
out->eflags = info.regs.eflags;
out->esp = info.regs.esp;
out->ss = info.regs.xss;
out->float_save.control_word = info.fpregs.cwd;
out->float_save.status_word = info.fpregs.swd;
out->float_save.tag_word = info.fpregs.twd;
out->float_save.error_offset = info.fpregs.fip;
out->float_save.error_selector = info.fpregs.fcs;
out->float_save.data_offset = info.fpregs.foo;
out->float_save.data_selector = info.fpregs.fos;
// 8 registers * 10 bytes per register.
memcpy(out->float_save.register_area, info.fpregs.st_space, 10 * 8);
// This matches the Intel fpsave format.
U16(out->extended_registers + 0, info.fpregs.cwd);
U16(out->extended_registers + 2, info.fpregs.swd);
U16(out->extended_registers + 4, info.fpregs.twd);
U16(out->extended_registers + 6, info.fpxregs.fop);
U32(out->extended_registers + 8, info.fpxregs.fip);
U16(out->extended_registers + 12, info.fpxregs.fcs);
U32(out->extended_registers + 16, info.fpregs.foo);
U16(out->extended_registers + 20, info.fpregs.fos);
U32(out->extended_registers + 24, info.fpxregs.mxcsr);
memcpy(out->extended_registers + 32, &info.fpxregs.st_space, 128);
memcpy(out->extended_registers + 160, &info.fpxregs.xmm_space, 128);
}
// Juggle an x86 ucontext into minidump format
// out: the minidump structure
// info: the collection of register structures.
static void CPUFillFromUContext(MDRawContextX86 *out, const ucontext *uc,
const struct _libc_fpstate* fp) {
const greg_t* regs = uc->uc_mcontext.gregs;
out->context_flags = MD_CONTEXT_X86_FULL |
MD_CONTEXT_X86_FLOATING_POINT;
out->gs = regs[REG_GS];
out->fs = regs[REG_FS];
out->es = regs[REG_ES];
out->ds = regs[REG_DS];
out->edi = regs[REG_EDI];
out->esi = regs[REG_ESI];
out->ebx = regs[REG_EBX];
out->edx = regs[REG_EDX];
out->ecx = regs[REG_ECX];
out->eax = regs[REG_EAX];
out->ebp = regs[REG_EBP];
out->eip = regs[REG_EIP];
out->cs = regs[REG_CS];
out->eflags = regs[REG_EFL];
out->esp = regs[REG_UESP];
out->ss = regs[REG_SS];
out->float_save.control_word = fp->cw;
out->float_save.status_word = fp->sw;
out->float_save.tag_word = fp->tag;
out->float_save.error_offset = fp->ipoff;
out->float_save.error_selector = fp->cssel;
out->float_save.data_offset = fp->dataoff;
out->float_save.data_selector = fp->datasel;
// 8 registers * 10 bytes per register.
memcpy(out->float_save.register_area, fp->_st, 10 * 8);
}
#elif defined(__x86_64)
typedef MDRawContextAMD64 RawContextCPU;
static void CPUFillFromThreadInfo(MDRawContextAMD64 *out,
const google_breakpad::ThreadInfo &info) {
out->context_flags = MD_CONTEXT_AMD64_FULL |
MD_CONTEXT_AMD64_SEGMENTS;
out->cs = info.regs.cs;
out->ds = info.regs.ds;
out->es = info.regs.es;
out->fs = info.regs.fs;
out->gs = info.regs.gs;
out->ss = info.regs.ss;
out->eflags = info.regs.eflags;
out->dr0 = info.dregs[0];
out->dr1 = info.dregs[1];
out->dr2 = info.dregs[2];
out->dr3 = info.dregs[3];
// 4 and 5 deliberatly omitted because they aren't included in the minidump
// format.
out->dr6 = info.dregs[6];
out->dr7 = info.dregs[7];
out->rax = info.regs.rax;
out->rcx = info.regs.rcx;
out->rdx = info.regs.rdx;
out->rbx = info.regs.rbx;
out->rsp = info.regs.rsp;
out->rbp = info.regs.rbp;
out->rsi = info.regs.rsi;
out->rdi = info.regs.rdi;
out->r8 = info.regs.r8;
out->r9 = info.regs.r9;
out->r10 = info.regs.r10;
out->r11 = info.regs.r11;
out->r12 = info.regs.r12;
out->r13 = info.regs.r13;
out->r14 = info.regs.r14;
out->r15 = info.regs.r15;
out->rip = info.regs.rip;
out->flt_save.control_word = info.fpregs.cwd;
out->flt_save.status_word = info.fpregs.swd;
out->flt_save.tag_word = info.fpregs.twd;
out->flt_save.error_opcode = info.fpregs.fop;
out->flt_save.error_offset = info.fpregs.rip;
out->flt_save.error_selector = 0; // We don't have this.
out->flt_save.data_offset = info.fpregs.rdp;
out->flt_save.data_selector = 0; // We don't have this.
out->flt_save.mx_csr = info.fpregs.mxcsr;
out->flt_save.mx_csr_mask = info.fpregs.mxcsr_mask;
memcpy(&out->flt_save.float_registers, &info.fpregs.st_space, 8 * 16);
memcpy(&out->flt_save.xmm_registers, &info.fpregs.xmm_space, 16 * 16);
}
static void CPUFillFromUContext(MDRawContextAMD64 *out, const ucontext *uc,
const struct _libc_fpstate* fpregs) {
const greg_t* regs = uc->gregs;
out->context_flags = MD_CONTEXT_AMD64_FULL;
out->cs = regs[REG_CSGSFS] & 0xffff;
out->fs = (regs[REG_CSGSFS] >> 32) & 0xffff;
out->gs = (regs[REG_CSGSFS] >> 16) & 0xffff;
out->eflags = regs[REG_EFL];
out->rax = regs[REG_RAX];
out->rcx = regs[REG_RCX];
out->rdx = regs[REG_RDX];
out->rbx = regs[REG_RBX];
out->rsp = regs[REG_RSP];
out->rbp = regs[REG_RBP];
out->rsi = regs[REG_RSI];
out->rdi = regs[REG_RDI];
out->r8 = regs[REG_R8];
out->r9 = regs[REG_R9];
out->r10 = regs[REG_R10];
out->r11 = regs[REG_R11];
out->r12 = regs[REG_R12];
out->r13 = regs[REG_R13];
out->r14 = regs[REG_R14];
out->r15 = regs[REG_R15];
out->rip = regs[REG_RIP];
out->flt_save.control_word = fpregs->cwd;
out->flt_save.status_word = fpregs->swd;
out->flt_save.tag_word = fpregs->ftw;
out->flt_save.error_opcode = fpregs->fop;
out->flt_save.error_offset = fpregs->rip;
out->flt_save.data_offset = fpregs->rdp;
out->flt_save.error_selector = 0; // We don't have this.
out->flt_save.data_selector = 0; // We don't have this.
out->flt_save.mx_csr = fpregs->mxcsr;
out->flt_save.mx_csr_mask = fpregs->mxcsr_mask;
memcpy(&out->flt_save.float_registers, &fpregs->_st, 8 * 16);
memcpy(&out->flt_save.xmm_registers, &fpregs->_xmm, 16 * 16);
}
#else
#error "This code has not been ported to your platform yet."
#endif
namespace google_breakpad {
class MinidumpWriter {
public:
MinidumpWriter(const char* filename,
pid_t crashing_pid,
const ExceptionHandler::CrashContext* context)
: filename_(filename),
siginfo_(&context->siginfo),
ucontext_(&context->context),
float_state_(&context->float_state),
crashing_tid_(context->tid),
dumper_(crashing_pid) {
}
bool Init() {
return dumper_.Init() && minidump_writer_.Open(filename_) &&
dumper_.ThreadsSuspend();
}
~MinidumpWriter() {
minidump_writer_.Close();
dumper_.ThreadsResume();
}
bool Dump() {
// A minidump file contains a number of tagged streams. This is the number
// of stream which we write.
static const unsigned kNumWriters = 11;
TypedMDRVA<MDRawHeader> header(&minidump_writer_);
TypedMDRVA<MDRawDirectory> dir(&minidump_writer_);
if (!header.Allocate())
return false;
if (!dir.AllocateArray(kNumWriters))
return false;
memset(header.get(), 0, sizeof(MDRawHeader));
header.get()->signature = MD_HEADER_SIGNATURE;
header.get()->version = MD_HEADER_VERSION;
header.get()->time_date_stamp = time(NULL);
header.get()->stream_count = kNumWriters;
header.get()->stream_directory_rva = dir.position();
unsigned dir_index = 0;
MDRawDirectory dirent;
if (!WriteThreadListStream(&dirent))
return false;
dir.CopyIndex(dir_index++, &dirent);
if (!WriteMappings(&dirent))
return false;
dir.CopyIndex(dir_index++, &dirent);
if (!WriteExceptionStream(&dirent))
return false;
dir.CopyIndex(dir_index++, &dirent);
if (!WriteSystemInfoStream(&dirent))
return false;
dir.CopyIndex(dir_index++, &dirent);
dirent.stream_type = MD_LINUX_CPU_INFO;
if (!WriteFile(&dirent.location, "/proc/cpuinfo"))
NullifyDirectoryEntry(&dirent);
dir.CopyIndex(dir_index++, &dirent);
dirent.stream_type = MD_LINUX_PROC_STATUS;
if (!WriteProcFile(&dirent.location, crashing_tid_, "status"))
NullifyDirectoryEntry(&dirent);
dir.CopyIndex(dir_index++, &dirent);
dirent.stream_type = MD_LINUX_LSB_RELEASE;
if (!WriteFile(&dirent.location, "/etc/lsb-release"))
NullifyDirectoryEntry(&dirent);
dir.CopyIndex(dir_index++, &dirent);
dirent.stream_type = MD_LINUX_CMD_LINE;
if (!WriteProcFile(&dirent.location, crashing_tid_, "cmdline"))
NullifyDirectoryEntry(&dirent);
dir.CopyIndex(dir_index++, &dirent);
dirent.stream_type = MD_LINUX_ENVIRON;
if (!WriteProcFile(&dirent.location, crashing_tid_, "environ"))
NullifyDirectoryEntry(&dirent);
dir.CopyIndex(dir_index++, &dirent);
dirent.stream_type = MD_LINUX_AUXV;
if (!WriteProcFile(&dirent.location, crashing_tid_, "auxv"))
NullifyDirectoryEntry(&dirent);
dir.CopyIndex(dir_index++, &dirent);
dirent.stream_type = MD_LINUX_AUXV;
if (!WriteProcFile(&dirent.location, crashing_tid_, "maps"))
NullifyDirectoryEntry(&dirent);
dir.CopyIndex(dir_index++, &dirent);
// If you add more directory entries, don't forget to update kNumWriters,
// above.
dumper_.ThreadsResume();
return true;
}
// Write information about the threads.
bool WriteThreadListStream(MDRawDirectory* dirent) {
const unsigned num_threads = dumper_.threads().size();
TypedMDRVA<uint32_t> list(&minidump_writer_);
if (!list.AllocateObjectAndArray(num_threads, sizeof(MDRawThread)))
return false;
dirent->stream_type = MD_THREAD_LIST_STREAM;
dirent->location = list.location();
*list.get() = num_threads;
for (unsigned i = 0; i < num_threads; ++i) {
MDRawThread thread;
my_memset(&thread, 0, sizeof(thread));
thread.thread_id = dumper_.threads()[i];
// We have a different source of information for the crashing thread. If
// we used the actual state of the thread we would find it running in the
// signal handler with the alternative stack, which would be deeply
// unhelpful.
if (thread.thread_id == crashing_tid_) {
const void* stack;
size_t stack_len;
if (!dumper_.GetStackInfo(&stack, &stack_len, GetStackPointer()))
return false;
UntypedMDRVA memory(&minidump_writer_);
if (!memory.Allocate(stack_len))
return false;
uint8_t* stack_copy = (uint8_t*) dumper_.allocator()->Alloc(stack_len);
dumper_.CopyFromProcess(stack_copy, thread.thread_id, stack, stack_len);
memory.Copy(stack_copy, stack_len);
thread.stack.start_of_memory_range = (uintptr_t) (stack);
thread.stack.memory = memory.location();
TypedMDRVA<RawContextCPU> cpu(&minidump_writer_);
if (!cpu.Allocate())
return false;
my_memset(cpu.get(), 0, sizeof(RawContextCPU));
CPUFillFromUContext(cpu.get(), ucontext_, float_state_);
thread.thread_context = cpu.location();
crashing_thread_context_ = cpu.location();
} else {
ThreadInfo info;
if (!dumper_.ThreadInfoGet(dumper_.threads()[i], &info))
return false;
UntypedMDRVA memory(&minidump_writer_);
if (!memory.Allocate(info.stack_len))
return false;
uint8_t* stack_copy =
(uint8_t*) dumper_.allocator()->Alloc(info.stack_len);
dumper_.CopyFromProcess(stack_copy, thread.thread_id, info.stack,
info.stack_len);
memory.Copy(stack_copy, info.stack_len);
thread.stack.start_of_memory_range = (uintptr_t)(info.stack);
thread.stack.memory = memory.location();
TypedMDRVA<RawContextCPU> cpu(&minidump_writer_);
if (!cpu.Allocate())
return false;
my_memset(cpu.get(), 0, sizeof(RawContextCPU));
CPUFillFromThreadInfo(cpu.get(), info);
thread.thread_context = cpu.location();
}
list.CopyIndexAfterObject(i, &thread, sizeof(thread));
}
return true;
}
static bool ShouldIncludeMapping(const MappingInfo& mapping) {
if (mapping.name[0] == 0 || // we only want modules with filenames.
mapping.offset || // we only want to include one mapping per shared lib.
mapping.size < 4096) { // too small to get a signature for.
return false;
}
return true;
}
// Write information about the mappings in effect. Because we are using the
// minidump format, the information about the mappings is pretty limited.
// Because of this, we also include the full, unparsed, /proc/$x/maps file in
// another stream in the file.
bool WriteMappings(MDRawDirectory* dirent) {
const unsigned num_mappings = dumper_.mappings().size();
unsigned num_output_mappings = 0;
for (unsigned i = 0; i < dumper_.mappings().size(); ++i) {
const MappingInfo& mapping = *dumper_.mappings()[i];
if (ShouldIncludeMapping(mapping))
num_output_mappings++;
}
TypedMDRVA<uint32_t> list(&minidump_writer_);
if (!list.AllocateObjectAndArray(num_output_mappings, sizeof(MDRawModule)))
return false;
dirent->stream_type = MD_MODULE_LIST_STREAM;
dirent->location = list.location();
*list.get() = num_output_mappings;
for (unsigned i = 0, j = 0; i < num_mappings; ++i) {
const MappingInfo& mapping = *dumper_.mappings()[i];
if (!ShouldIncludeMapping(mapping))
continue;
MDRawModule mod;
my_memset(&mod, 0, sizeof(mod));
mod.base_of_image = mapping.start_addr;
mod.size_of_image = mapping.size;
const size_t filepath_len = my_strlen(mapping.name);
// Figure out file name from path
const char* filename_ptr = mapping.name + filepath_len - 1;
while (filename_ptr >= mapping.name) {
if (*filename_ptr == '/')
break;
filename_ptr--;
}
filename_ptr++;
const size_t filename_len = mapping.name + filepath_len - filename_ptr;
uint8_t cv_buf[MDCVInfoPDB70_minsize + NAME_MAX];
uint8_t* cv_ptr = cv_buf;
UntypedMDRVA cv(&minidump_writer_);
if (!cv.Allocate(MDCVInfoPDB70_minsize + filename_len + 1))
return false;
const uint32_t cv_signature = MD_CVINFOPDB70_SIGNATURE;
memcpy(cv_ptr, &cv_signature, sizeof(cv_signature));
cv_ptr += sizeof(cv_signature);
{
// We XOR the first page of the file to get a signature for it.
uint8_t xor_buf[sizeof(MDGUID)];
size_t done = 0;
uint8_t* signature = cv_ptr;
cv_ptr += sizeof(xor_buf);
my_memset(signature, 0, sizeof(xor_buf));
while (done < 4096) {
dumper_.CopyFromProcess(xor_buf, crashing_tid_,
(void *) (mod.base_of_image + done),
sizeof(xor_buf));
for (unsigned i = 0; i < sizeof(xor_buf); ++i)
signature[i] ^= xor_buf[i];
done += sizeof(xor_buf);
}
my_memset(cv_ptr, 0, sizeof(uint32_t)); // Set age to 0 on Linux.
cv_ptr += sizeof(uint32_t);
}
// Write pdb_file_name
memcpy(cv_ptr, filename_ptr, filename_len + 1);
cv.Copy(cv_buf, MDCVInfoPDB70_minsize + filename_len + 1);
mod.cv_record = cv.location();
MDLocationDescriptor ld;
if (!minidump_writer_.WriteString(mapping.name, filepath_len, &ld))
return false;
mod.module_name_rva = ld.rva;
list.CopyIndexAfterObject(j++, &mod, sizeof(mod));
}
return true;
}
bool WriteExceptionStream(MDRawDirectory* dirent) {
TypedMDRVA<MDRawExceptionStream> exc(&minidump_writer_);
if (!exc.Allocate())
return false;
my_memset(exc.get(), 0, sizeof(MDRawExceptionStream));
dirent->stream_type = MD_EXCEPTION_STREAM;
dirent->location = exc.location();
exc.get()->thread_id = crashing_tid_;
exc.get()->exception_record.exception_code = siginfo_->si_signo;
exc.get()->exception_record.exception_address =
(uintptr_t) siginfo_->si_addr;
exc.get()->thread_context = crashing_thread_context_;
return true;
}
bool WriteSystemInfoStream(MDRawDirectory* dirent) {
TypedMDRVA<MDRawSystemInfo> si(&minidump_writer_);
if (!si.Allocate())
return false;
my_memset(si.get(), 0, sizeof(MDRawSystemInfo));
dirent->stream_type = MD_SYSTEM_INFO_STREAM;
dirent->location = si.location();
WriteCPUInformation(si.get());
WriteOSInformation(si.get());
return true;
}
private:
#if defined(__i386)
uintptr_t GetStackPointer() {
return ucontext_->uc_mcontext.gregs[REG_ESP];
}
#elif defined(__x86_64)
uintptr_t GetStackPointer() {
return ucontext_->uc_mcontext.gregs[REG_RSP];
}
#else
#error "This code has not been ported to your platform yet."
#endif
void NullifyDirectoryEntry(MDRawDirectory* dirent) {
dirent->stream_type = 0;
dirent->location.data_size = 0;
dirent->location.rva = 0;
}
bool WriteCPUInformation(MDRawSystemInfo* sys_info) {
char vendor_id[sizeof(sys_info->cpu.x86_cpu_info.vendor_id) + 1] = {0};
static const char vendor_id_name[] = "vendor_id";
static const size_t vendor_id_name_length = sizeof(vendor_id_name) - 1;
struct CpuInfoEntry {
const char* info_name;
int value;
bool found;
} cpu_info_table[] = {
{ "processor", -1, false },
{ "model", 0, false },
{ "stepping", 0, false },
{ "cpuid level", 0, false },
};
// processor_architecture should always be set, do this first
sys_info->processor_architecture =
#if defined(__i386)
MD_CPU_ARCHITECTURE_X86;
#elif defined(__x86_64)
MD_CPU_ARCHITECTURE_AMD64;
#else
#error "Unknown CPU arch"
#endif
const int fd = sys_open("/proc/cpuinfo", O_RDONLY, 0);
if (fd < 0)
return false;
{
PageAllocator allocator;
LineReader* const line_reader = new(allocator) LineReader(fd);
const char* line;
unsigned line_len;
while (line_reader->GetNextLine(&line, &line_len)) {
for (size_t i = 0;
i < sizeof(cpu_info_table) / sizeof(cpu_info_table[0]);
i++) {
CpuInfoEntry* entry = &cpu_info_table[i];
if (entry->found)
continue;
if (!strncmp(line, entry->info_name, strlen(entry->info_name))) {
const char* value = strchr(line, ':');
if (!value)
continue;
// the above strncmp only matches the prefix, it might be the wrong
// line. i.e. we matched "model name" instead of "model".
// check and make sure there is only spaces between the prefix and
// the colon.
const char* space_ptr = line + strlen(entry->info_name);
for (; space_ptr < value; space_ptr++) {
if (!isspace(*space_ptr)) {
break;
}
}
if (space_ptr != value)
continue;
sscanf(++value, " %d", &(entry->value));
entry->found = true;
}
}
// special case for vendor_id
if (!strncmp(line, vendor_id_name, vendor_id_name_length)) {
const char* value = strchr(line, ':');
if (!value)
goto popline;
// skip ':" and all the spaces that follows
do {
value++;
} while (isspace(*value));
if (*value) {
size_t length = strlen(value);
if (length == 0)
goto popline;
// we don't want the trailing newline
if (value[length - 1] == '\n')
length--;
// ensure we have space for the value
if (length < sizeof(vendor_id))
strncpy(vendor_id, value, length);
}
}
popline:
line_reader->PopLine(line_len);
}
sys_close(fd);
}
// make sure we got everything we wanted
for (size_t i = 0;
i < sizeof(cpu_info_table) / sizeof(cpu_info_table[0]);
i++) {
if (!cpu_info_table[i].found) {
return false;
}
}
// /proc/cpuinfo contains cpu id, change it into number by adding one.
cpu_info_table[0].value++;
sys_info->number_of_processors = cpu_info_table[0].value;
sys_info->processor_level = cpu_info_table[3].value;
sys_info->processor_revision = cpu_info_table[1].value << 8 |
cpu_info_table[2].value;
if (vendor_id[0] != '\0') {
memcpy(sys_info->cpu.x86_cpu_info.vendor_id, vendor_id,
sizeof(sys_info->cpu.x86_cpu_info.vendor_id));
}
return true;
}
bool WriteFile(MDLocationDescriptor* result, const char* filename) {
const int fd = sys_open(filename, O_RDONLY, 0);
if (fd < 0)
return false;
// We can't stat the files because several of the files that we want to
// read are kernel seqfiles, which always have a length of zero. So we have
// to read as much as we can into a buffer.
static const unsigned kMaxFileSize = 1024;
uint8_t* data = (uint8_t*) dumper_.allocator()->Alloc(kMaxFileSize);
size_t done = 0;
while (done < kMaxFileSize) {
ssize_t r;
do {
r = sys_read(fd, data + done, kMaxFileSize - done);
} while (r == -1 && errno == EINTR);
if (r < 1)
break;
done += r;
}
sys_close(fd);
if (!done)
return false;
UntypedMDRVA memory(&minidump_writer_);
if (!memory.Allocate(done))
return false;
memory.Copy(data, done);
*result = memory.location();
return true;
}
bool WriteOSInformation(MDRawSystemInfo* sys_info) {
sys_info->platform_id = MD_OS_LINUX;
struct utsname uts;
if (uname(&uts))
return false;
static const size_t buf_len = 512;
char buf[buf_len] = {0};
size_t space_left = buf_len - 1;
const char* info_table[] = {
uts.sysname,
uts.release,
uts.version,
uts.machine,
NULL
};
bool first_item = true;
for (const char** cur_info = info_table; *cur_info; cur_info++) {
static const char* separator = " ";
size_t separator_len = strlen(separator);
size_t info_len = strlen(*cur_info);
if (info_len == 0)
continue;
if (space_left < info_len + (first_item ? 0 : separator_len))
break;
if (!first_item) {
strcat(buf, separator);
space_left -= separator_len;
}
first_item = false;
strcat(buf, *cur_info);
space_left -= info_len;
}
MDLocationDescriptor location;
if (!minidump_writer_.WriteString(buf, 0, &location))
return false;
sys_info->csd_version_rva = location.rva;
return true;
}
bool WriteProcFile(MDLocationDescriptor* result, pid_t pid,
const char* filename) {
char buf[80];
memcpy(buf, "/proc/", 6);
const unsigned pid_len = my_int_len(pid);
my_itos(buf + 6, pid, pid_len);
buf[6 + pid_len] = '/';
memcpy(buf + 6 + pid_len + 1, filename, my_strlen(filename) + 1);
return WriteFile(result, buf);
}
const char* const filename_; // output filename
const siginfo_t* const siginfo_; // from the signal handler (see sigaction)
const struct ucontext* const ucontext_; // also from the signal handler
const struct _libc_fpstate* const float_state_; // ditto
const pid_t crashing_tid_; // the process which actually crashed
LinuxDumper dumper_;
MinidumpFileWriter minidump_writer_;
MDLocationDescriptor crashing_thread_context_;
};
bool WriteMinidump(const char* filename, pid_t crashing_process,
const void* blob, size_t blob_size) {
if (blob_size != sizeof(ExceptionHandler::CrashContext))
return false;
const ExceptionHandler::CrashContext* context =
reinterpret_cast<const ExceptionHandler::CrashContext*>(blob);
MinidumpWriter writer(filename, crashing_process, context);
if (!writer.Init())
return false;
return writer.Dump();
}
} // namespace google_breakpad

View File

@@ -1,8 +1,6 @@
// Copyright (c) 2006, Google Inc.
// Copyright (c) 2009, Google Inc.
// All rights reserved.
//
// Author: Li Liu
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
@@ -29,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_

View File

@@ -1,8 +1,6 @@
// Copyright (c) 2006, Google Inc.
// Copyright (c) 2009, Google Inc.
// All rights reserved.
//
// Author: Li Liu
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
@@ -29,58 +27,53 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <pthread.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include "client/linux/handler/minidump_generator.h"
#include "client/linux/handler/exception_handler.h"
#include "client/linux/minidump_writer/minidump_writer.h"
#include "breakpad_googletest_includes.h"
using namespace google_breakpad;
// Thread use this to see if it should stop working.
static bool should_exit = false;
// This provides a wrapper around system calls which may be
// interrupted by a signal and return EINTR. See man 7 signal.
#define HANDLE_EINTR(x) ({ \
typeof(x) __eintr_result__; \
do { \
__eintr_result__ = x; \
} while (__eintr_result__ == -1 && errno == EINTR); \
__eintr_result__;\
})
static void foo2(int arg) {
// Stack variable, used for debugging stack dumps.
int c = arg;
c = 0xcccccccc;
while (!should_exit)
sleep(1);
namespace {
typedef testing::Test MinidumpWriterTest;
}
static void foo(int arg) {
// Stack variable, used for debugging stack dumps.
int b = arg;
b = 0xbbbbbbbb;
foo2(b);
}
TEST(MinidumpWriterTest, Setup) {
int fds[2];
ASSERT_NE(-1, pipe(fds));
static void *thread_main(void *) {
// Stack variable, used for debugging stack dumps.
int a = 0xaaaaaaaa;
foo(a);
return NULL;
}
static void CreateThread(int num) {
pthread_t h;
for (int i = 0; i < num; ++i) {
pthread_create(&h, NULL, thread_main, NULL);
pthread_detach(h);
const pid_t child = fork();
if (child == 0) {
close(fds[1]);
char b;
HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
close(fds[0]);
syscall(__NR_exit);
}
}
close(fds[0]);
int main(int argc, char *argv[]) {
CreateThread(10);
google_breakpad::MinidumpGenerator mg;
if (mg.WriteMinidumpToFile("minidump_test.out", -1, 0, NULL))
printf("Succeeded written minidump\n");
else
printf("Failed to write minidump\n");
should_exit = true;
return 0;
ExceptionHandler::CrashContext context;
memset(&context, 0, sizeof(context));
char templ[] = "/tmp/minidump-writer-unittest-XXXXXX";
mktemp(templ);
ASSERT_TRUE(WriteMinidump(templ, child, &context, sizeof(context)));
struct stat st;
ASSERT_EQ(stat(templ, &st), 0);
ASSERT_GT(st.st_size, 0u);
unlink(templ);
close(fds[1]);
}

View File

@@ -133,9 +133,9 @@
F9721F380E8B0CFC00D7E813 /* dump_syms.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = dump_syms.h; path = ../../../common/mac/dump_syms.h; sourceTree = SOURCE_ROOT; };
F9721F390E8B0D0D00D7E813 /* dump_syms.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = dump_syms.mm; path = ../../../common/mac/dump_syms.mm; sourceTree = SOURCE_ROOT; };
F9721F6B0E8B0D7000D7E813 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = "<absolute>"; };
F9721F760E8B0DC700D7E813 /* bytereader.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = bytereader.cc; path = ../../../common/mac/dwarf/bytereader.cc; sourceTree = SOURCE_ROOT; };
F9721F770E8B0DC700D7E813 /* dwarf2reader.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = dwarf2reader.cc; path = ../../../common/mac/dwarf/dwarf2reader.cc; sourceTree = SOURCE_ROOT; };
F9721F780E8B0DC700D7E813 /* functioninfo.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = functioninfo.cc; path = ../../../common/mac/dwarf/functioninfo.cc; sourceTree = SOURCE_ROOT; };
F9721F760E8B0DC700D7E813 /* bytereader.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = bytereader.cc; path = ../../../common/dwarf/bytereader.cc; sourceTree = SOURCE_ROOT; };
F9721F770E8B0DC700D7E813 /* dwarf2reader.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = dwarf2reader.cc; path = ../../../common/dwarf/dwarf2reader.cc; sourceTree = SOURCE_ROOT; };
F9721F780E8B0DC700D7E813 /* functioninfo.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = functioninfo.cc; path = ../../../common/dwarf/functioninfo.cc; sourceTree = SOURCE_ROOT; };
F9721FA10E8B0E2300D7E813 /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = /System/Library/Frameworks/SenTestingKit.framework; sourceTree = "<absolute>"; };
F9721FA80E8B0E4800D7E813 /* md5.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = md5.c; path = ../../../common/md5.c; sourceTree = SOURCE_ROOT; };
F982089A0DB3280D0017AECA /* breakpad_nlist_test.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = breakpad_nlist_test.h; sourceTree = "<group>"; };

View File

@@ -112,9 +112,12 @@ NSString *const kDefaultServerType = @"google";
@implementation NSTextField (ResizabilityExtentions)
- (float)breakpad_adjustHeightToFit {
NSRect oldFrame = [self frame];
// Starting with the 10.5 SDK, height won't grow, so make it huge to start.
NSRect presizeFrame = oldFrame;
presizeFrame.size.height = MAXFLOAT;
// sizeToFit will blow out the width rather than making the field taller, so
// we do it manually.
NSSize newSize = [[self cell] cellSizeForBounds:oldFrame];
NSSize newSize = [[self cell] cellSizeForBounds:presizeFrame];
NSRect newFrame = NSMakeRect(oldFrame.origin.x, oldFrame.origin.y,
NSWidth(oldFrame), newSize.height);
[self setFrame:newFrame];

View File

@@ -37,6 +37,8 @@
#include <string.h>
#include <unistd.h>
#include "common/linux/linux_syscall_support.h"
#include "common/linux/linux_libc_support.h"
#include "client/minidump_file_writer-inl.h"
#include "common/string_conversion.h"
@@ -53,7 +55,11 @@ MinidumpFileWriter::~MinidumpFileWriter() {
bool MinidumpFileWriter::Open(const char *path) {
assert(file_ == -1);
#if __linux__
file_ = sys_open(path, O_WRONLY | O_CREAT | O_EXCL, 0600);
#else
file_ = open(path, O_WRONLY | O_CREAT | O_EXCL, 0600);
#endif
return file_ != -1;
}
@@ -63,7 +69,11 @@ bool MinidumpFileWriter::Close() {
if (file_ != -1) {
ftruncate(file_, position_);
#if __linux__
result = (sys_close(file_) == 0);
#else
result = (close(file_) == 0);
#endif
file_ = -1;
}
@@ -227,9 +237,16 @@ bool MinidumpFileWriter::Copy(MDRVA position, const void *src, ssize_t size) {
return false;
// Seek and write the data
if (lseek(file_, position, SEEK_SET) == static_cast<off_t>(position))
if (write(file_, src, size) == size)
#if __linux__
if (sys_lseek(file_, position, SEEK_SET) == static_cast<off_t>(position)) {
if (sys_write(file_, src, size) == size) {
#else
if (lseek(file_, position, SEEK_SET) == static_cast<off_t>(position)) {
if (write(file_, src, size) == size) {
#endif
return true;
}
}
return false;
}

View File

@@ -29,7 +29,9 @@
#ifndef UTIL_DEBUGINFO_BYTEREADER_INL_H__
#define UTIL_DEBUGINFO_BYTEREADER_INL_H__
#include "common/mac/dwarf/bytereader.h"
#include <cassert>
#include "common/dwarf/bytereader.h"
namespace dwarf2reader {
@@ -37,9 +39,11 @@ inline uint8 ByteReader::ReadOneByte(const char* buffer) const {
return buffer[0];
}
inline uint16 ByteReader::ReadTwoBytes(const char* buffer) const {
const uint16 buffer0 = static_cast<uint16>(buffer[0]);
const uint16 buffer1 = static_cast<uint16>(buffer[1]);
inline uint16 ByteReader::ReadTwoBytes(const char* signed_buffer) const {
const unsigned char *buffer
= reinterpret_cast<const unsigned char *>(signed_buffer);
const uint16 buffer0 = buffer[0];
const uint16 buffer1 = buffer[1];
if (endian_ == ENDIANNESS_LITTLE) {
return buffer0 | buffer1 << 8;
} else {
@@ -47,11 +51,13 @@ inline uint16 ByteReader::ReadTwoBytes(const char* buffer) const {
}
}
inline uint64 ByteReader::ReadFourBytes(const char* buffer) const {
const uint32 buffer0 = static_cast<uint32>(buffer[0]);
const uint32 buffer1 = static_cast<uint32>(buffer[1]);
const uint32 buffer2 = static_cast<uint32>(buffer[2]);
const uint32 buffer3 = static_cast<uint32>(buffer[3]);
inline uint64 ByteReader::ReadFourBytes(const char* signed_buffer) const {
const unsigned char *buffer
= reinterpret_cast<const unsigned char *>(signed_buffer);
const uint32 buffer0 = buffer[0];
const uint32 buffer1 = buffer[1];
const uint32 buffer2 = buffer[2];
const uint32 buffer3 = buffer[3];
if (endian_ == ENDIANNESS_LITTLE) {
return buffer0 | buffer1 << 8 | buffer2 << 16 | buffer3 << 24;
} else {
@@ -59,15 +65,17 @@ inline uint64 ByteReader::ReadFourBytes(const char* buffer) const {
}
}
inline uint64 ByteReader::ReadEightBytes(const char* buffer) const {
const uint64 buffer0 = static_cast<uint64>(buffer[0]);
const uint64 buffer1 = static_cast<uint64>(buffer[1]);
const uint64 buffer2 = static_cast<uint64>(buffer[2]);
const uint64 buffer3 = static_cast<uint64>(buffer[3]);
const uint64 buffer4 = static_cast<uint64>(buffer[4]);
const uint64 buffer5 = static_cast<uint64>(buffer[5]);
const uint64 buffer6 = static_cast<uint64>(buffer[6]);
const uint64 buffer7 = static_cast<uint64>(buffer[7]);
inline uint64 ByteReader::ReadEightBytes(const char* signed_buffer) const {
const unsigned char *buffer
= reinterpret_cast<const unsigned char *>(signed_buffer);
const uint64 buffer0 = buffer[0];
const uint64 buffer1 = buffer[1];
const uint64 buffer2 = buffer[2];
const uint64 buffer3 = buffer[3];
const uint64 buffer4 = buffer[4];
const uint64 buffer5 = buffer[5];
const uint64 buffer6 = buffer[6];
const uint64 buffer7 = buffer[7];
if (endian_ == ENDIANNESS_LITTLE) {
return buffer0 | buffer1 << 8 | buffer2 << 16 | buffer3 << 24 |
buffer4 << 32 | buffer5 << 40 | buffer6 << 48 | buffer7 << 56;

View File

@@ -26,9 +26,9 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "common/mac/dwarf/bytereader-inl.h"
#include "common/dwarf/bytereader-inl.h"
#include "common/mac/dwarf/bytereader.h"
#include "common/dwarf/bytereader.h"
namespace dwarf2reader {

View File

@@ -26,11 +26,11 @@
// (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 COMMON_MAC_DWARF_BYTEREADER_H__
#define COMMON_MAC_DWARF_BYTEREADER_H__
#ifndef COMMON_DWARF_BYTEREADER_H__
#define COMMON_DWARF_BYTEREADER_H__
#include <string>
#include "common/mac/dwarf/types.h"
#include "common/dwarf/types.h"
namespace dwarf2reader {
@@ -129,4 +129,4 @@ class ByteReader {
} // namespace dwarf2reader
#endif // COMMON_MAC_DWARF_BYTEREADER_H__
#endif // COMMON_DWARF_BYTEREADER_H__

View File

@@ -0,0 +1,171 @@
// Copyright 2009 Google Inc. All Rights Reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Implementation of dwarf2reader::DieDispatcher class.
#include <cassert>
#include "common/dwarf/dwarf2diehandler.h"
namespace dwarf2reader {
DIEDispatcher::~DIEDispatcher() {
while (! die_handlers_.empty()) {
HandlerStack &entry = die_handlers_.top();
if (entry.handler_ != root_handler_)
delete entry.handler_;
die_handlers_.pop();
}
}
bool DIEDispatcher::StartCompilationUnit(uint64 offset, uint8 address_size,
uint8 offset_size, uint64 cu_length,
uint8 dwarf_version) {
return root_handler_->StartCompilationUnit(offset, address_size,
offset_size, cu_length,
dwarf_version);
}
bool DIEDispatcher::StartDIE(uint64 offset, enum DwarfTag tag,
const AttributeList& attrs) {
// The stack entry for the parent of this DIE, if there is one.
HandlerStack *parent = die_handlers_.empty() ? NULL : &die_handlers_.top();
// Does this call indicate that we're done receiving the parent's
// attributes' values? If so, call its EndAttributes member function.
if (parent && parent->handler_ && ! parent->reported_attributes_end_) {
parent->reported_attributes_end_ = true;
if (! parent->handler_->EndAttributes()) {
// Finish off this handler now. and edit *PARENT to indicate that
// we don't want to visit any of the children.
parent->handler_->Finish();
if (parent->handler_ != root_handler_) delete parent->handler_;
parent->handler_ = NULL;
return false;
}
}
// Find a handler for this DIE.
DIEHandler *handler;
if (parent) {
if (parent->handler_)
// Ask the parent to find a handler.
handler = parent->handler_->FindChildHandler(offset, tag, attrs);
else
// No parent handler means we're not interested in any of our
// children.
handler = NULL;
} else {
// This is the root DIE. For a non-root DIE, the parent's handler
// decides whether to visit it, but the root DIE has no parent
// handler, so we have a special method on the root DIE handler
// itself to decide.
if (root_handler_->StartRootDIE(offset, tag, attrs))
handler = root_handler_;
else
handler = NULL;
}
// Push a handler stack entry for this new handler. As an
// optimization, we don't push NULL-handler entries on top of other
// NULL-handler entries; we just let the oldest such entry stand for
// the whole subtree.
if (handler || (parent && parent->handler_)) {
HandlerStack entry;
entry.offset_ = offset;
entry.handler_ = handler;
entry.reported_attributes_end_ = false;
die_handlers_.push(entry);
}
return handler != NULL;
}
void DIEDispatcher::EndDIE(uint64 offset) {
assert(! die_handlers_.empty());
HandlerStack *entry = &die_handlers_.top();
if (entry->handler_) {
// This entry had better be the handler for this DIE.
assert(entry->offset_ == offset);
// If a DIE has no children, this EndDIE call indicates that we're
// done receiving its attributes' values.
if (! entry->reported_attributes_end_)
entry->handler_->EndAttributes(); // Ignore return value: no children.
entry->handler_->Finish();
if (entry->handler_ != root_handler_) delete entry->handler_;
} else {
// If this DIE is within a tree we're ignoring, then don't pop the
// handler stack: that entry stands for the whole tree.
if (entry->offset_ != offset)
return;
}
die_handlers_.pop();
}
void DIEDispatcher::ProcessAttributeUnsigned(uint64 offset,
enum DwarfAttribute attr,
enum DwarfForm form,
uint64 data) {
HandlerStack &current = die_handlers_.top();
// This had better be an attribute of the DIE we were meant to handle.
assert(offset == current.offset_);
current.handler_->ProcessAttributeUnsigned(attr, form, data);
}
void DIEDispatcher::ProcessAttributeSigned(uint64 offset,
enum DwarfAttribute attr,
enum DwarfForm form,
int64 data) {
HandlerStack &current = die_handlers_.top();
// This had better be an attribute of the DIE we were meant to handle.
assert(offset == current.offset_);
current.handler_->ProcessAttributeSigned(attr, form, data);
}
void DIEDispatcher::ProcessAttributeBuffer(uint64 offset,
enum DwarfAttribute attr,
enum DwarfForm form,
const char* data,
uint64 len) {
HandlerStack &current = die_handlers_.top();
// This had better be an attribute of the DIE we were meant to handle.
assert(offset == current.offset_);
current.handler_->ProcessAttributeBuffer(attr, form, data, len);
}
void DIEDispatcher::ProcessAttributeString(uint64 offset,
enum DwarfAttribute attr,
enum DwarfForm form,
const string& data) {
HandlerStack &current = die_handlers_.top();
// This had better be an attribute of the DIE we were meant to handle.
assert(offset == current.offset_);
current.handler_->ProcessAttributeString(attr, form, data);
}
} // namespace dwarf2reader

View File

@@ -0,0 +1,323 @@
// Copyright 2009 Google Inc. All Rights Reserved. -*- mode: c++ -*-
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// dwarf2reader::CompilationUnit is a simple and direct parser for
// DWARF data, but its handler interface is not convenient to use. In
// particular:
//
// - CompilationUnit calls Dwarf2Handler's member functions to report
// every attribute's value, regardless of what sort of DIE it is.
// As a result, the ProcessAttributeX functions end up looking like
// this:
//
// switch (parent_die_tag) {
// case DW_TAG_x:
// switch (attribute_name) {
// case DW_AT_y:
// handle attribute y of DIE type x
// ...
// } break;
// ...
// }
//
// In C++ it's much nicer to use virtual function dispatch to find
// the right code for a given case than to switch on the DIE tag
// like this.
//
// - Processing different kinds of DIEs requires different sets of
// data. It would be nice to be able to have separate classes for
// separate kinds of DIEs, each with the members appropriate to its
// role, instead of having one handler class that needs to hold data
// for all every DIE type.
//
// - It would be nice to have separate handler objects for separate
// DIEs, instead of a single handler instance required to keep track
// of everything.
//
// - It's not convenient to take some action after all attributes have
// been seen, but before visiting any children. The only indication
// you have that a DIE's attribute list is complete is that you get
// either a StartDIE or an EndDIE call.
//
// - It's not convenient to make use of the tree structure of the
// DIEs. Skipping all the children of a given die requires
// maintaining state and returning false from StartDIE until we get
// an EndDIE call with the appropriate offset. And it's not
// convenient to maintain the stack of contexts for the DIEs we have
// decided to enter.
//
// This interface tries to take care of all that. (How'd you guess?)
//
// Using the classes here, you provide an initial handler for the root
// DIE of the compilation unit. Each handler receives its DIE's
// attributes, and provides fresh handler objects for children of
// interest, if any.
//
// You use them as follows:
//
// - Define handler classes specialized to particular DIE types of
// interest. These handler classes must inherit from the DIEHandler
// class, defined below. Thus:
//
// class My_DW_TAG_X_Handler: public DIEHandler { ... };
// class My_DW_TAG_Y_Handler: public DIEHandler { ... };
//
// DIEHandler subclasses needn't correspond exactly to single DIE
// types, as shown here; the point is that you can write different
// classes for different kinds of DIEs.
//
// - In particular, define a DIE handler class for the compilation
// unit's root DIE, that inherits from the RootDIEHandler class
//
// class My_DW_TAG_compile_unit_Handler: public RootDIEHandler { ... };
//
// RootDIEHandler inherits from DIEHandler, adding a few additional
// member functions for examining the compilation unit as a whole,
// and other quirks of rootness.
//
// - Then, create a DIEDispatcher instance, passing it an instance of
// your root DIE handler, and use that as the
// dwarf2reader::CompilationUnit's handler:
//
// My_DW_TAG_compile_unit_Handler root_die_handler(...);
// DIEDispatcher die_dispatcher(&root_die_handler);
// CompilationUnit reader(sections, offset, bytereader, &die_dispatcher);
//
// Here, 'die_dispatcher' acts as a shim between 'reader' and the
// various DIE-specific handlers.
//
// - When you call reader.Start(), die_dispatcher behaves as follows,
// starting with your root die handler and the compilation unit's
// root DIE:
//
// - It calls the handler's ProcessAttributeX member functions for
// each of the DIE's attributes.
//
// - It calls the handler's EndAttributes member function. This
// should return true if any of the DIE's children should be
// visited, in which case:
//
// - die_dispatcher calls the handler's FindChildHandler member
// function. If that returns NULL, die_dispatcher ignores that
// child and its descendants. Otherwise, FindChildHandler
// returns a pointer to a DIEHandler instance; die_dispatcher
// uses that handler to process the child, using this procedure
// recursively.
//
// - When die_dispatcher has finished processing all the DIE's
// children, it invokes the handler's Finish() member function,
// and destroys the handler. (As a special case, it doesn't
// destroy the root DIE handler.)
//
// This allows the code for handling a particular kind of DIE to be
// gathered together in a single class, makes it easy to skip all the
// children or individual children of a particular DIE, and provides
// appropriate parental context for each die.
#ifndef COMMON_DWARF_DWARF2DIEHANDLER_H__
#define COMMON_DWARF_DWARF2DIEHANDLER_H__
#include <stack>
#include "common/dwarf/types.h"
#include "common/dwarf/dwarf2enums.h"
#include "common/dwarf/dwarf2reader.h"
namespace dwarf2reader {
// A parent class for handlers for specific DIE types.
// The series of calls made on a DIE handler is as follows:
//
// - construction, by the parent DIE's handler
// - for each attribute of the DIE:
// - ProcessAttributeX()
// - EndAttributes()
// - if that returned true, then for each child:
// - FindChildHandler()
// - if that returns non-NULL:
// - this sequence inserted recursively
// - Finish()
class DIEHandler {
public:
DIEHandler() { }
virtual ~DIEHandler() { }
// When we visit a DIE, we first use these member functions to
// report the DIE's attributes and their values. These have the
// same restrictions as the corresponding member functions of
// dwarf2reader::Dwarf2Handler.
//
// The default definitions ignore the values they are passed.
virtual void ProcessAttributeUnsigned(enum DwarfAttribute attr,
enum DwarfForm form,
uint64 data) { }
virtual void ProcessAttributeSigned(enum DwarfAttribute attr,
enum DwarfForm form,
int64 data) { }
virtual void ProcessAttributeBuffer(enum DwarfAttribute attr,
enum DwarfForm form,
const char* data,
uint64 len) { }
virtual void ProcessAttributeString(enum DwarfAttribute attr,
enum DwarfForm form,
const string& data) { }
// Once we have reported all the DIE's attributes' values, we call
// this member function. If it returns false, we skip all the DIE's
// children. If it returns true, we call FindChildHandler on each
// child. If that returns a handler object, we use that to visit
// the child; otherwise, we skip it.
//
// The default definition applies FindChildHandler to all children.
virtual bool EndAttributes() { return true; }
// If EndAttributes returns true to indicate that some of the DIE's
// children might be of interest, then we apply this function to
// each of the DIE's children. If it returns a handler object, then
// we use that to visit the child. If it returns NULL, we skip that
// child (and its children).
//
// OFFSET is the offset of the child; TAG indicates what kind of DIE
// it is; and ATTRS is the list of attributes the DIE will have, and
// their forms (their values are not provided).
//
// The default definition doesn't handle any children.
virtual DIEHandler *FindChildHandler(uint64 offset, enum DwarfTag tag,
const AttributeList &attrs) {
return NULL;
}
// When we are done processing a DIE, we call this member function.
// This happens after the EndAttributes call, all FindChildHandler
// calls (if any), and all operations on the children themselves (if
// any).
virtual void Finish() { };
};
// A subclass of DIEHandler, with additional kludges for handling the
// compilation unit's root die.
class RootDIEHandler: public DIEHandler {
public:
RootDIEHandler() { }
virtual ~RootDIEHandler() { }
// We pass the values reported via StartCompilationUnit to this
// member function, and skip the entire compilation unit if it
// returns false. So the root DIE handler is actually also a
// compilation unit handler. The default definition always visits
// the compilation unit.
virtual bool StartCompilationUnit(uint64 offset, uint8 address_size,
uint8 offset_size, uint64 cu_length,
uint8 dwarf_version) { return true; }
// For the root DIE handler only, we pass the offset, tag and
// attributes of the compilation unit's root DIE. This is the only
// way the root DIE handler can find the root DIE's tag. If this
// function returns true, we will visit the root DIE using the usual
// DIEHandler methods; otherwise, we skip the entire compilation
// unit.
//
// The default definition elects to visit the root DIE.
virtual bool StartRootDIE(uint64 offset, enum DwarfTag tag,
const AttributeList& attrs) { return true; }
};
class DIEDispatcher: public Dwarf2Handler {
public:
// Create a Dwarf2Handler which uses ROOT_HANDLER as the handler for
// the compilation unit's root die, as described for the DIEHandler
// class.
DIEDispatcher(RootDIEHandler *root_handler) :
root_handler_(root_handler) { }
// Destroying a DIEDispatcher destroys all active handler objects
// except the root handler.
~DIEDispatcher();
bool StartCompilationUnit(uint64 offset, uint8 address_size,
uint8 offset_size, uint64 cu_length,
uint8 dwarf_version);
bool StartDIE(uint64 offset, enum DwarfTag tag,
const AttributeList &attrs);
void ProcessAttributeUnsigned(uint64 offset,
enum DwarfAttribute attr,
enum DwarfForm form,
uint64 data);
void ProcessAttributeSigned(uint64 offset,
enum DwarfAttribute attr,
enum DwarfForm form,
int64 data);
void ProcessAttributeBuffer(uint64 offset,
enum DwarfAttribute attr,
enum DwarfForm form,
const char* data,
uint64 len);
void ProcessAttributeString(uint64 offset,
enum DwarfAttribute attr,
enum DwarfForm form,
const string &data);
void EndDIE(uint64 offset);
private:
// The type of a handler stack entry. This includes some fields
// which don't really need to be on the stack --- they could just be
// single data members of DIEDispatcher --- but putting them here
// makes it easy to see that the code is correct.
struct HandlerStack {
// The offset of the DIE for this handler stack entry.
uint64 offset_;
// The object interested in this DIE's attributes and children.
// If NULL, we're not interested in either.
DIEHandler *handler_;
// Have we reported the end of this DIE's attributes to the handler?
bool reported_attributes_end_;
};
// Stack of DIE attribute handlers. At StartDIE(D), the top of the
// stack is the handler of D's parent, whom we may ask for a handler
// for D itself. At EndDIE(D), the top of the stack is D's handler.
// Special cases:
//
// - Before we've seen the compilation unit's root DIE, the stack is
// empty; we'll call root_handler_'s special member functions, and
// perhaps push root_handler_ on the stack to look at the root's
// immediate children.
//
// - When we decide to ignore a subtree, we only push an entry on
// the stack for the root of the tree being ignored, rather than
// pushing lots of stack entries with ignore_children_ set.
stack<HandlerStack> die_handlers_;
// The root handler. We don't push it on die_handlers_ until we
// actually get the StartDIE call for the root.
RootDIEHandler *root_handler_;
};
} // namespace dwarf2reader
#endif // COMMON_DWARF_DWARF2DIEHANDLER_H__

View File

@@ -26,8 +26,8 @@
// (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 COMMON_MAC_DWARF_DWARF2ENUMS_H__
#define COMMON_MAC_DWARF_DWARF2ENUMS_H__
#ifndef COMMON_DWARF_DWARF2ENUMS_H__
#define COMMON_DWARF_DWARF2ENUMS_H__
namespace dwarf2reader {
@@ -486,5 +486,40 @@ enum DwarfOpcode {
DW_OP_GNU_push_tls_address =0xe0
};
// Source languages. These are values for DW_AT_language.
enum DwarfLanguage
{
DW_LANG_none =0x0000,
DW_LANG_C89 =0x0001,
DW_LANG_C =0x0002,
DW_LANG_Ada83 =0x0003,
DW_LANG_C_plus_plus =0x0004,
DW_LANG_Cobol74 =0x0005,
DW_LANG_Cobol85 =0x0006,
DW_LANG_Fortran77 =0x0007,
DW_LANG_Fortran90 =0x0008,
DW_LANG_Pascal83 =0x0009,
DW_LANG_Modula2 =0x000a,
DW_LANG_Java =0x000b,
DW_LANG_C99 =0x000c,
DW_LANG_Ada95 =0x000d,
DW_LANG_Fortran95 =0x000e,
DW_LANG_PLI =0x000f,
DW_LANG_ObjC =0x0010,
DW_LANG_ObjC_plus_plus =0x0011,
DW_LANG_UPC =0x0012,
DW_LANG_D =0x0013,
// Implementation-defined language code range.
DW_LANG_lo_user = 0x8000,
DW_LANG_hi_user = 0xffff,
// Extensions.
// MIPS assembly language. The GNU toolchain uses this for all
// assembly languages, since there's no generic DW_LANG_ value for that.
DW_LANG_Mips_Assembler =0x8001,
DW_LANG_Upc =0x8765 // Unified Parallel C
};
} // namespace dwarf2reader
#endif // COMMON_MAC_DWARF_DWARF2ENUMS_H__
#endif // COMMON_DWARF_DWARF2ENUMS_H__

View File

@@ -26,25 +26,15 @@
// (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 <ext/hash_map>
#include <stack>
#include <utility>
#include <memory>
#include <cstring>
#include "common/mac/dwarf/bytereader-inl.h"
#include "common/mac/dwarf/dwarf2reader.h"
#include "common/mac/dwarf/bytereader.h"
#include "common/mac/dwarf/line_state_machine.h"
namespace __gnu_cxx
{
template<> struct hash< std::string >
{
size_t operator()( const std::string& x ) const
{
return hash< const char* >()( x.c_str() );
}
};
}
#include "common/dwarf/bytereader-inl.h"
#include "common/dwarf/dwarf2reader.h"
#include "common/dwarf/bytereader.h"
#include "common/dwarf/line_state_machine.h"
namespace dwarf2reader {
@@ -87,8 +77,12 @@ void CompilationUnit::ReadAbbrevs() {
if (abbrevs_)
return;
// First get the debug_abbrev section
SectionMap::const_iterator iter = sections_.find("__debug_abbrev");
// First get the debug_abbrev section. ".debug_abbrev" is the name
// recommended in the DWARF spec, and used on Linux;
// "__debug_abbrev" is the name used in Mac OS X Mach-O files.
SectionMap::const_iterator iter = sections_.find(".debug_abbrev");
if (iter == sections_.end())
iter = sections_.find("__debug_abbrev");
assert(iter != sections_.end());
abbrevs_ = new vector<Abbrev>;
@@ -100,7 +94,9 @@ void CompilationUnit::ReadAbbrevs() {
const char* abbrev_start = iter->second.first +
header_.abbrev_offset;
const char* abbrevptr = abbrev_start;
#ifndef NDEBUG
const uint64 abbrev_length = iter->second.second - header_.abbrev_offset;
#endif
while (1) {
CompilationUnit::Abbrev abbrev;
@@ -273,8 +269,12 @@ void CompilationUnit::ReadHeader() {
}
uint64 CompilationUnit::Start() {
// First get the debug_info section
SectionMap::const_iterator iter = sections_.find("__debug_info");
// First get the debug_info section. ".debug_info" is the name
// recommended in the DWARF spec, and used on Linux; "__debug_info"
// is the name used in Mac OS X Mach-O files.
SectionMap::const_iterator iter = sections_.find(".debug_info");
if (iter == sections_.end())
iter = sections_.find("__debug_info");
assert(iter != sections_.end());
// Set up our buffer
@@ -304,8 +304,12 @@ uint64 CompilationUnit::Start() {
// Otherwise, continue by reading our abbreviation entries.
ReadAbbrevs();
// Set the string section if we have one.
iter = sections_.find("__debug_str");
// Set the string section if we have one. ".debug_str" is the name
// recommended in the DWARF spec, and used on Linux; "__debug_str"
// is the name used in Mac OS X Mach-O files.
iter = sections_.find(".debug_str");
if (iter == sections_.end())
iter = sections_.find("__debug_str");
if (iter != sections_.end()) {
string_buffer_ = iter->second.first;
string_buffer_length_ = iter->second.second;
@@ -476,7 +480,7 @@ void CompilationUnit::ProcessDIEs() {
// we need semantics of boost scoped_ptr here - no intention of trasnferring
// ownership of the stack. use const, but then we limit ourselves to not
// ever being able to call .reset() on the smart pointer.
auto_ptr<stack<uint64> > const die_stack(new stack<uint64>);
std::auto_ptr<stack<uint64> > const die_stack(new stack<uint64>);
while (dieptr < (lengthstart + header_.length)) {
// We give the user the absolute offset from the beginning of
@@ -811,16 +815,18 @@ void LineInfo::ReadLines() {
lengthstart += 4;
const char* lineptr = after_header_;
lsm.Reset(header_.default_is_stmt);
while (lineptr < lengthstart + header_.total_length) {
lsm.Reset(header_.default_is_stmt);
while (!lsm.end_sequence) {
size_t oplength;
bool add_line = ProcessOneOpcode(reader_, handler_, header_,
lineptr, &lsm, &oplength, (uintptr_t)-1, NULL);
if (add_line)
handler_->AddLine(lsm.address, lsm.file_num, lsm.line_num,
lsm.column_num);
lineptr += oplength;
size_t oplength;
bool add_line = ProcessOneOpcode(reader_, handler_, header_,
lineptr, &lsm, &oplength, (uintptr_t)-1, NULL);
lineptr += oplength;
if (lsm.end_sequence) {
handler_->EndSequence(lsm.address);
lsm.Reset(header_.default_is_stmt);
} else if (add_line) {
handler_->AddLine(lsm.address, lsm.file_num, lsm.line_num,
lsm.column_num);
}
}

View File

@@ -33,20 +33,19 @@
// reading if you wish to modify the implementation.
// Only a cursory attempt is made to explain terminology that is
// used here, as it is much better explained in the standard documents
#ifndef COMMON_MAC_DWARF_DWARF2READER_H__
#define COMMON_MAC_DWARF_DWARF2READER_H__
#ifndef COMMON_DWARF_DWARF2READER_H__
#define COMMON_DWARF_DWARF2READER_H__
#include <ext/hash_map>
#include <list>
#include <map>
#include <string>
#include <utility>
#include <vector>
#include "common/mac/dwarf/dwarf2enums.h"
#include "common/mac/dwarf/types.h"
#include "common/dwarf/dwarf2enums.h"
#include "common/dwarf/types.h"
using namespace std;
using namespace __gnu_cxx;
namespace dwarf2reader {
struct LineStateMachine;
@@ -56,7 +55,7 @@ class LineInfoHandler;
// This maps from a string naming a section to a pair containing a
// the data for the section, and the size of the section.
typedef hash_map<string, pair<const char*, uint64> > SectionMap;
typedef map<string, pair<const char*, uint64> > SectionMap;
typedef list<pair<enum DwarfAttribute, enum DwarfForm> > AttributeList;
typedef AttributeList::iterator AttributeIterator;
typedef AttributeList::const_iterator ConstAttributeIterator;
@@ -174,6 +173,16 @@ class LineInfoHandler {
// starts at, if we know it (0 otherwise).
virtual void AddLine(uint64 address, uint32 file_num, uint32 line_num,
uint32 column_num) { }
// Called at the end of a sequence of lines. ADDRESS is the address
// of the first byte after the final machine instruction of the
// sequence.
//
// Note that this is *not* necessarily the end of the line data for
// the compilation unit: a single compilation unit's line program
// may contain several "end of sequence" markers, to describe (for
// example) several discontiguous regions of code.
virtual void EndSequence(uint64 address) { }
};
// The base of DWARF2/3 debug info is a DIE (Debugging Information
@@ -225,8 +234,11 @@ class CompilationUnit {
// Begin reading a Dwarf2 compilation unit, and calling the
// callbacks in the Dwarf2Handler
// Return the offset of the end of the compilation unit - the passed
// in offset.
// Return the full length of the compilation unit, including
// headers. This plus the starting offset passed to the constructor
// is the offset of the end of the compilation unit --- the start of
// the next compilation unit, if there is one.
uint64 Start();
private:

View File

@@ -34,19 +34,9 @@
#include <vector>
#include "common/mac/dwarf/functioninfo.h"
#include "common/dwarf/functioninfo.h"
#include "common/mac/dwarf/bytereader.h"
namespace __gnu_cxx
{
template<>
struct hash<std::string>
{
size_t operator()(const std::string& k) const;
};
}
#include "common/dwarf/bytereader.h"
namespace dwarf2reader {
@@ -131,6 +121,17 @@ void CULineInfoHandler::AddLine(uint64 address, uint32 file_num,
}
}
void CULineInfoHandler::EndSequence(uint64 address) {
// To preserve this code's behavior prior to the addition of the
// EndSequence member function, we duplicate the previous line,
// changing its address to ADDRESS.
LineMap::iterator it = linemap_->lower_bound(address);
if (it != linemap_->begin()) {
it--;
linemap_->insert(make_pair(address, it->second));
}
}
bool CUFunctionInfoHandler::StartCompilationUnit(uint64 offset,
uint8 address_size,
uint8 offset_size,

View File

@@ -31,15 +31,15 @@
// collector that uses the DWARF2/3 reader interface to build a mapping
// of addresses to files, lines, and functions.
#ifndef COMMON_MAC_DWARF_FUNCTIONINFO_H__
#define COMMON_MAC_DWARF_FUNCTIONINFO_H__
#ifndef COMMON_DWARF_FUNCTIONINFO_H__
#define COMMON_DWARF_FUNCTIONINFO_H__
#include <map>
#include <string>
#include <utility>
#include <vector>
#include "common/mac/dwarf/dwarf2reader.h"
#include "common/dwarf/dwarf2reader.h"
namespace dwarf2reader {
@@ -99,6 +99,16 @@ class CULineInfoHandler: public LineInfoHandler {
virtual void AddLine(uint64 address, uint32 file_num, uint32 line_num,
uint32 column_num);
// Called at the end of a sequence of lines. ADDRESS is the address
// of the first byte after the final machine instruction of the
// sequence.
//
// Note that this is *not* necessarily the end of the line data for
// the compilation unit: a single compilation unit's line program
// may contain several "end of sequence" markers, to describe (for
// example) several discontiguous regions of code.
virtual void EndSequence(uint64 address);
private:
LineMap* linemap_;
@@ -175,4 +185,4 @@ class CUFunctionInfoHandler: public Dwarf2Handler {
};
} // namespace dwarf2reader
#endif // COMMON_MAC_DWARF_FUNCTIONINFO_H__
#endif // COMMON_DWARF_FUNCTIONINFO_H__

View File

@@ -27,8 +27,8 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef COMMON_MAC_DWARF_LINE_STATE_MACHINE_H__
#define COMMON_MAC_DWARF_LINE_STATE_MACHINE_H__
#ifndef COMMON_DWARF_LINE_STATE_MACHINE_H__
#define COMMON_DWARF_LINE_STATE_MACHINE_H__
namespace dwarf2reader {
@@ -58,4 +58,4 @@ struct LineStateMachine {
} // namespace dwarf2reader
#endif // COMMON_MAC_DWARF_LINE_STATE_MACHINE_H__
#endif // COMMON_DWARF_LINE_STATE_MACHINE_H__

View File

@@ -30,8 +30,8 @@
// This file contains some typedefs for basic types
#ifndef _COMMON_MAC_DWARF_TYPES_H__
#define _COMMON_MAC_DWARF_TYPES_H__
#ifndef _COMMON_DWARF_TYPES_H__
#define _COMMON_DWARF_TYPES_H__
typedef signed char int8;
typedef short int16;
@@ -43,4 +43,4 @@ typedef unsigned short uint16;
typedef unsigned int uint32;
typedef unsigned long long uint64;
#endif // _COMMON_MAC_DWARF_TYPES_H__
#endif // _COMMON_DWARF_TYPES_H__

View File

@@ -27,147 +27,50 @@
// (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 <a.out.h>
#include <cstdarg>
#include <cstdlib>
#include <cstdio>
#include <assert.h>
#include <cxxabi.h>
#include <elf.h>
#include <errno.h>
#include <fcntl.h>
#include <link.h>
#include <string.h>
#include <sys/mman.h>
#include <stab.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <algorithm>
#include <string>
#include <algorithm>
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <list>
#include <vector>
#include <map>
#include <string.h>
#include <string>
#include <vector>
#include "common/linux/dump_symbols.h"
#include "common/linux/file_id.h"
#include "common/linux/guid_creator.h"
#include "common/linux/module.h"
#include "common/linux/stabs_reader.h"
#include "processor/scoped_ptr.h"
// This namespace contains helper functions.
namespace {
struct SourceFileInfo;
// Infomation of a line.
struct LineInfo {
// Offset from start of the function.
// Load from stab symbol.
ElfW(Off) rva_to_func;
// Offset from base of the loading binary.
ElfW(Off) rva_to_base;
// Size of the line.
// It is the difference of the starting address of the line and starting
// address of the next N_SLINE, N_FUN or N_SO.
uint32_t size;
// Line number.
uint32_t line_num;
// The source file this line belongs to.
SourceFileInfo *file;
};
typedef std::list<struct LineInfo> LineInfoList;
// Information of a function.
struct FuncInfo {
// Name of the function.
std::string name;
// Offset from the base of the loading address.
ElfW(Off) rva_to_base;
// Virtual address of the function.
// Load from stab symbol.
ElfW(Addr) addr;
// Size of the function.
// It is the difference of the starting address of the function and starting
// address of the next N_FUN or N_SO.
uint32_t size;
// Total size of stack parameters.
uint32_t stack_param_size;
// Line information array.
LineInfoList line_info;
};
typedef std::list<struct FuncInfo> FuncInfoList;
// Information of a source file.
struct SourceFileInfo {
// Name of the source file.
const char *name;
// Starting address of the source file.
ElfW(Addr) addr;
// Id of the source file.
int source_id;
// Functions information.
FuncInfoList func_info;
};
// A simple std::list of pointers to SourceFileInfo structures, that
// owns the structures pointed to: destroying the list destroys them,
// as well.
class SourceFileInfoList : public std::list<SourceFileInfo *> {
public:
~SourceFileInfoList() {
for (iterator it = this->begin(); it != this->end(); it++)
delete *it;
}
};
typedef std::map<const char *, SourceFileInfo *> NameToFileMap;
// Information of a symbol table.
// This is the root of all types of symbol.
struct SymbolInfo {
// The main files used in this module. This does not include header
// files; it includes only files that were provided as the primary
// source file for the compilation unit. In STABS, these are files
// named in 'N_SO' entries.
SourceFileInfoList main_files;
// Map from file names to source file structures. Note that this
// map's keys are compared as pointers, not strings, so if the same
// name appears at two different addresses in stabstr, the map will
// treat that as two different names. If the linker didn't unify
// names in .stabstr (which it does), this would result in duplicate
// FILE lines, which is benign.
NameToFileMap name_to_file;
// An array of some addresses at which a file boundary occurs.
//
// The STABS information describing a compilation unit gives the
// unit's start address, but not its ending address or size. Those
// must be inferred by finding the start address of the next file.
// For the last compilation unit, or when one compilation unit ends
// before the next one starts, STABS includes an N_SO entry whose
// filename is the empty string; such an entry's address serves
// simply to mark the end of the preceding compilation unit. Rather
// than create FuncInfoList for such entries, we record their
// addresses here. These are not necessarily sorted.
std::vector<ElfW(Addr)> file_boundaries;
// The current source file, for line number information. This is
// persistent across functions.
SourceFileInfo *current_source_file;
};
using google_breakpad::Module;
using std::vector;
// Stab section name.
static const char *kStabName = ".stab";
// Demangle using abi call.
// Older GCC may not support it.
static std::string Demangle(const char *mangled) {
static std::string Demangle(const std::string &mangled) {
int status = 0;
char *demangled = abi::__cxa_demangle(mangled, NULL, NULL, &status);
char *demangled = abi::__cxa_demangle(mangled.c_str(), NULL, NULL, &status);
if (status == 0 && demangled != NULL) {
std::string str(demangled);
free(demangled);
@@ -220,285 +123,227 @@ static const ElfW(Shdr) *FindSectionByName(const char *name,
for (int i = 0; i < nsection; ++i) {
const char *section_name =
(char*)(strtab->sh_offset + sections[i].sh_name);
reinterpret_cast<char*>(strtab->sh_offset + sections[i].sh_name);
if (!strncmp(name, section_name, name_len))
return sections + i;
}
return NULL;
}
// Return the SourceFileInfo for the file named NAME in SYMBOLS, as
// recorden in the name_to_file map. If none exists, create a new
// one.
//
// If the file is a main file, it is the caller's responsibility to
// set its address and add it to the list of main files.
//
// When creating a new file, this function does not make a copy of
// NAME; NAME must stay alive for as long as the symbol table does.
static SourceFileInfo *FindSourceFileInfo(SymbolInfo *symbols,
const char *name) {
SourceFileInfo **map_entry = &symbols->name_to_file[name];
SourceFileInfo *file;
if (*map_entry)
file = *map_entry;
else {
file = new SourceFileInfo;
file->name = name;
file->source_id = -1;
file->addr = 0;
*map_entry = file;
}
return file;
}
// Our handler class for STABS data.
class DumpStabsHandler: public google_breakpad::StabsHandler {
public:
DumpStabsHandler(Module *module) :
module_(module),
comp_unit_base_address_(0),
current_function_(NULL),
current_source_file_(NULL),
current_source_file_name_(NULL) { }
static int LoadLineInfo(struct nlist *list,
struct nlist *list_end,
SymbolInfo *symbols,
struct SourceFileInfo *source_file_info,
struct FuncInfo *func_info,
const ElfW(Shdr) *stabstr_section) {
struct nlist *cur_list = list;
// The name of the file any subsequent lines would belong to.
const char *last_source_name = symbols->current_source_file->name;
do {
// Skip non line information.
while (cur_list < list_end && cur_list->n_type != N_SLINE) {
// Only exit when got another function, or source file.
if (cur_list->n_type == N_FUN || cur_list->n_type == N_SO)
return cur_list - list;
// N_SOL means source lines following it will be from another
// source file. But don't actually create a file entry yet;
// wait until we see executable code attributed to the file.
if (cur_list->n_type == N_SOL
&& cur_list->n_un.n_strx > 0)
last_source_name = reinterpret_cast<char *>(cur_list->n_un.n_strx
+ stabstr_section->sh_offset);
++cur_list;
}
struct LineInfo line;
while (cur_list < list_end && cur_list->n_type == N_SLINE) {
// If this line is attributed to a new file, create its entry now.
if (last_source_name != symbols->current_source_file->name) {
symbols->current_source_file
= FindSourceFileInfo(symbols, last_source_name);
}
line.file = symbols->current_source_file;
line.rva_to_func = cur_list->n_value;
// n_desc is a signed short
line.line_num = (unsigned short)cur_list->n_desc;
// We will compute these later. For now, pacify compiler warnings.
line.size = 0;
line.rva_to_base = 0;
func_info->line_info.push_back(line);
++cur_list;
}
} while (list < list_end);
bool StartCompilationUnit(const char *name, uint64_t address,
const char *build_directory);
bool EndCompilationUnit(uint64_t address);
bool StartFunction(const std::string &name, uint64_t address);
bool EndFunction(uint64_t address);
bool Line(uint64_t address, const char *name, int number);
void Warning(const char *format, ...);
return cur_list - list;
}
// Do any final processing necessary to make module_ contain all the
// data provided by the STABS reader.
//
// Because STABS does not provide reliable size information for
// functions and lines, we need to make a pass over the data after
// processing all the STABS to compute those sizes. We take care of
// that here.
void Finalize();
static int LoadFuncSymbols(struct nlist *list,
struct nlist *list_end,
SymbolInfo *symbols,
struct SourceFileInfo *source_file_info,
const ElfW(Shdr) *stabstr_section) {
struct nlist *cur_list = list;
assert(cur_list->n_type == N_SO);
++cur_list;
source_file_info->func_info.clear();
while (cur_list < list_end) {
// Go until the function symbol.
while (cur_list < list_end && cur_list->n_type != N_FUN) {
if (cur_list->n_type == N_SO) {
return cur_list - list;
}
++cur_list;
continue;
}
if (cur_list->n_type == N_FUN) {
struct FuncInfo func_info;
// The STABS data for an N_FUN entry is the function's (mangled)
// name, followed by a colon, followed by type information. We
// want to retain the name only.
const char *stabs_name
= reinterpret_cast<char *>(cur_list->n_un.n_strx +
stabstr_section->sh_offset);
const char *name_end = strchr(stabs_name, ':');
if (! name_end)
name_end = stabs_name + strlen(stabs_name);
func_info.name = std::string(stabs_name, name_end - stabs_name);
func_info.addr = cur_list->n_value;
func_info.rva_to_base = 0;
func_info.size = 0;
func_info.stack_param_size = 0;
cur_list++;
private:
// Line info.
cur_list += LoadLineInfo(cur_list,
list_end,
symbols,
source_file_info,
&func_info,
stabstr_section);
// An arbitrary, but very large, size to use for functions whose
// size we can't compute properly.
static const uint64_t kFallbackSize = 0x10000000;
// Functions in this module should have address bigger than the module
// startring address.
// There maybe a lot of duplicated entry for a function in the symbol,
// only one of them can met this.
if (func_info.addr >= source_file_info->addr) {
source_file_info->func_info.push_back(func_info);
}
}
}
return cur_list - list;
}
// The module we're contributing debugging info to.
Module *module_;
// Compute size and rva information based on symbols loaded from stab section.
static bool ComputeSizeAndRVA(ElfW(Addr) loading_addr,
struct SymbolInfo *symbols) {
SourceFileInfoList::iterator file_it;
FuncInfoList::iterator func_it;
LineInfoList::iterator line_it;
// The functions we've generated so far. We don't add these to
// module_ as we parse them. Instead, we wait until we've computed
// their ending address, and their lines' ending addresses.
//
// We could just stick them in module_ from the outset, but if
// module_ already contains data gathered from other debugging
// formats, that would complicate the size computation.
vector<Module::Function *> functions_;
// A table of all the addresses at which files and functions start
// or end. We build this from the file boundary list and our lists
// of files and functions, sort it, and then use it to find the ends
// of functions and source lines for which we have no size
// information.
std::vector<ElfW(Addr)> boundaries = symbols->file_boundaries;
for (file_it = symbols->main_files.begin();
file_it != symbols->main_files.end(); file_it++) {
boundaries.push_back((*file_it)->addr);
for (func_it = (*file_it)->func_info.begin();
func_it != (*file_it)->func_info.end(); func_it++)
boundaries.push_back(func_it->addr);
}
std::sort(boundaries.begin(), boundaries.end());
// Boundary addresses. STABS doesn't necessarily supply sizes for
// functions and lines, so we need to compute them ourselves by
// finding the next object.
vector<Module::Address> boundaries_;
int no_next_addr_count = 0;
for (file_it = symbols->main_files.begin();
file_it != symbols->main_files.end(); file_it++) {
for (func_it = (*file_it)->func_info.begin();
func_it != (*file_it)->func_info.end(); func_it++) {
struct FuncInfo &func_info = *func_it;
assert(func_info.addr >= loading_addr);
func_info.rva_to_base = func_info.addr - loading_addr;
func_info.size = 0;
std::vector<ElfW(Addr)>::iterator boundary
= std::upper_bound(boundaries.begin(), boundaries.end(),
func_info.addr);
ElfW(Addr) next_addr = (boundary == boundaries.end()) ? 0 : *boundary;
// I've noticed functions with an address bigger than any other functions
// and source files modules, this is probably the last function in the
// module, due to limitions of Linux stab symbol, it is impossible to get
// the exact size of this kind of function, thus we give it a default
// very big value. This should be safe since this is the last function.
// But it is a ugly hack.....
// The following code can reproduce the case:
// template<class T>
// void Foo(T value) {
// }
//
// int main(void) {
// Foo(10);
// Foo(std::string("hello"));
// return 0;
// }
// TODO(liuli): Find a better solution.
static const int kDefaultSize = 0x10000000;
if (next_addr != 0) {
func_info.size = next_addr - func_info.addr;
} else {
if (no_next_addr_count > 1) {
fprintf(stderr, "Got more than one funtion without the \
following symbol. Igore this function.\n");
fprintf(stderr, "The dumped symbol may not correct.\n");
assert(!"This should not happen!\n");
func_info.size = 0;
continue;
}
// The base address of the current compilation unit. We use this to
// recognize functions we should omit from the symbol file. (If you
// know the details of why we omit these, please patch this
// comment.)
Module::Address comp_unit_base_address_;
no_next_addr_count++;
func_info.size = kDefaultSize;
}
// Compute line size.
for (line_it = func_info.line_info.begin();
line_it != func_info.line_info.end(); line_it++) {
struct LineInfo &line_info = *line_it;
LineInfoList::iterator next_line_it = line_it;
next_line_it++;
line_info.size = 0;
if (next_line_it != func_info.line_info.end()) {
line_info.size =
next_line_it->rva_to_func - line_info.rva_to_func;
} else {
// The last line in the function.
// If we can find a function or source file symbol immediately
// following the line, we can get the size of the line by computing
// the difference of the next address to the starting address of this
// line.
// Otherwise, we need to set a default big enough value. This occurs
// mostly because the this function is the last one in the module.
if (next_addr != 0) {
ElfW(Off) next_addr_offset = next_addr - func_info.addr;
line_info.size = next_addr_offset - line_info.rva_to_func;
} else {
line_info.size = kDefaultSize;
}
}
line_info.rva_to_base = line_info.rva_to_func + func_info.rva_to_base;
} // for each line.
} // for each function.
} // for each source file.
// The function we're currently contributing lines to.
Module::Function *current_function_;
// The last Module::File we got a line number in.
Module::File *current_source_file_;
// The pointer in the .stabstr section of the name that
// current_source_file_ is built from. This allows us to quickly
// recognize when the current line is in the same file as the
// previous one (which it usually is).
const char *current_source_file_name_;
};
bool DumpStabsHandler::StartCompilationUnit(const char *name, uint64_t address,
const char *build_directory) {
assert(! comp_unit_base_address_);
current_source_file_name_ = name;
current_source_file_ = module_->FindFile(name);
comp_unit_base_address_ = address;
boundaries_.push_back(static_cast<Module::Address>(address));
return true;
}
bool DumpStabsHandler::EndCompilationUnit(uint64_t address) {
assert(comp_unit_base_address_);
comp_unit_base_address_ = 0;
current_source_file_ = NULL;
current_source_file_name_ = NULL;
if (address)
boundaries_.push_back(static_cast<Module::Address>(address));
return true;
}
bool DumpStabsHandler::StartFunction(const std::string &name,
uint64_t address) {
assert(! current_function_);
Module::Function *f = new Module::Function;
f->name_ = Demangle(name);
f->address_ = address;
f->size_ = 0; // We compute this in DumpStabsHandler::Finalize().
f->parameter_size_ = 0; // We don't provide this information.
current_function_ = f;
boundaries_.push_back(static_cast<Module::Address>(address));
return true;
}
bool DumpStabsHandler::EndFunction(uint64_t address) {
assert(current_function_);
// Functions in this compilation unit should have address bigger
// than the compilation unit's starting address. There may be a lot
// of duplicated entries for functions in the STABS data; only one
// entry can meet this requirement.
//
// (I don't really understand the above comment; just bringing it
// along from the previous code, and leaving the behaivor unchanged.
// If you know the whole story, please patch this comment. --jimb)
if (current_function_->address_ >= comp_unit_base_address_)
functions_.push_back(current_function_);
else
delete current_function_;
current_function_ = NULL;
if (address)
boundaries_.push_back(static_cast<Module::Address>(address));
return true;
}
bool DumpStabsHandler::Line(uint64_t address, const char *name, int number) {
assert(current_function_);
assert(current_source_file_);
if (name != current_source_file_name_) {
current_source_file_ = module_->FindFile(name);
current_source_file_name_ = name;
}
Module::Line line;
line.address_ = address;
line.size_ = 0; // We compute this in DumpStabsHandler::Finalize().
line.file_ = current_source_file_;
line.number_ = number;
current_function_->lines_.push_back(line);
return true;
}
void DumpStabsHandler::Warning(const char *format, ...) {
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
}
void DumpStabsHandler::Finalize() {
// Sort our boundary list, so we can search it quickly.
sort(boundaries_.begin(), boundaries_.end());
// Sort all functions by address, just for neatness.
sort(functions_.begin(), functions_.end(),
Module::Function::CompareByAddress);
for (vector<Module::Function *>::iterator func_it = functions_.begin();
func_it != functions_.end();
func_it++) {
Module::Function *f = *func_it;
// Compute the function f's size.
vector<Module::Address>::iterator boundary
= std::upper_bound(boundaries_.begin(), boundaries_.end(), f->address_);
if (boundary != boundaries_.end())
f->size_ = *boundary - f->address_;
else
// If this is the last function in the module, and the STABS
// reader was unable to give us its ending address, then assign
// it a bogus, very large value. This will happen at most once
// per module: since we've added all functions' addresses to the
// boundary table, only one can be the last.
f->size_ = kFallbackSize;
// Compute sizes for each of the function f's lines --- if it has any.
if (! f->lines_.empty()) {
stable_sort(f->lines_.begin(), f->lines_.end(),
Module::Line::CompareByAddress);
vector<Module::Line>::iterator last_line = f->lines_.end() - 1;
for (vector<Module::Line>::iterator line_it = f->lines_.begin();
line_it != last_line; line_it++)
line_it[0].size_ = line_it[1].address_ - line_it[0].address_;
// Compute the size of the last line from f's end address.
last_line->size_ = (f->address_ + f->size_) - last_line->address_;
}
}
// Now that everything has a size, add our functions to the module, and
// dispose of our private list.
module_->AddFunctions(functions_.begin(), functions_.end());
functions_.clear();
}
static bool LoadSymbols(const ElfW(Shdr) *stab_section,
const ElfW(Shdr) *stabstr_section,
ElfW(Addr) loading_addr,
struct SymbolInfo *symbols) {
Module *module) {
if (stab_section == NULL || stabstr_section == NULL)
return false;
struct nlist *lists =
reinterpret_cast<struct nlist *>(stab_section->sh_offset);
int nstab = stab_section->sh_size / sizeof(struct nlist);
// First pass, load all symbols from the object file.
for (int i = 0; i < nstab; ) {
int step = 1;
struct nlist *cur_list = lists + i;
if (cur_list->n_type == N_SO) {
if (cur_list->n_un.n_strx) {
const char *name = reinterpret_cast<char *>(cur_list->n_un.n_strx
+ stabstr_section->sh_offset);
struct SourceFileInfo *source_file_info
= FindSourceFileInfo(symbols, name);
// Add it to the list; use ADDR to tell whether we've already done so.
if (! source_file_info->addr)
symbols->main_files.push_back(source_file_info);
source_file_info->addr = cur_list->n_value;
symbols->current_source_file = source_file_info;
step = LoadFuncSymbols(cur_list, lists + nstab, symbols,
source_file_info, stabstr_section);
} else {
// N_SO entries with no name mark file boundary addresses.
symbols->file_boundaries.push_back(cur_list->n_value);
}
}
i += step;
}
// Second pass, compute the size of functions and lines.
return ComputeSizeAndRVA(loading_addr, symbols);
// A callback object to handle data from the STABS reader.
DumpStabsHandler handler(module);
// Find the addresses of the STABS data, and create a STABS reader object.
uint8_t *stabs = reinterpret_cast<uint8_t *>(stab_section->sh_offset);
uint8_t *stabstr = reinterpret_cast<uint8_t *>(stabstr_section->sh_offset);
google_breakpad::StabsReader reader(stabs, stab_section->sh_size,
stabstr, stabstr_section->sh_size,
&handler);
// Read the STABS data, and do post-processing.
if (! reader.Process())
return false;
handler.Finalize();
return true;
}
static bool LoadSymbols(ElfW(Ehdr) *elf_header, struct SymbolInfo *symbols) {
static bool LoadSymbols(ElfW(Ehdr) *elf_header, Module *module) {
// Translate all offsets in section headers into address.
FixAddress(elf_header);
ElfW(Addr) loading_addr = GetLoadingAddress(
reinterpret_cast<ElfW(Phdr) *>(elf_header->e_phoff),
elf_header->e_phnum);
module->SetLoadAddress(loading_addr);
const ElfW(Shdr) *sections =
reinterpret_cast<ElfW(Shdr) *>(elf_header->e_shoff);
@@ -512,153 +357,7 @@ static bool LoadSymbols(ElfW(Ehdr) *elf_header, struct SymbolInfo *symbols) {
const ElfW(Shdr) *stabstr_section = stab_section->sh_link + sections;
// Load symbols.
return LoadSymbols(stab_section, stabstr_section, loading_addr, symbols);
}
static bool WriteModuleInfo(FILE *file,
ElfW(Half) arch,
const std::string &obj_file) {
const char *arch_name = NULL;
if (arch == EM_386)
arch_name = "x86";
else if (arch == EM_X86_64)
arch_name = "x86_64";
else
return false;
unsigned char identifier[16];
google_breakpad::FileID file_id(obj_file.c_str());
if (file_id.ElfFileIdentifier(identifier)) {
char identifier_str[40];
file_id.ConvertIdentifierToString(identifier,
identifier_str, sizeof(identifier_str));
char id_no_dash[40];
int id_no_dash_len = 0;
memset(id_no_dash, 0, sizeof(id_no_dash));
for (int i = 0; identifier_str[i] != '\0'; ++i)
if (identifier_str[i] != '-')
id_no_dash[id_no_dash_len++] = identifier_str[i];
// Add an extra "0" by the end.
id_no_dash[id_no_dash_len++] = '0';
std::string filename = obj_file;
size_t slash_pos = obj_file.find_last_of("/");
if (slash_pos != std::string::npos)
filename = obj_file.substr(slash_pos + 1);
return 0 <= fprintf(file, "MODULE Linux %s %s %s\n", arch_name,
id_no_dash, filename.c_str());
}
return false;
}
// Set *INCLUDED_FILES to the list of included files in SYMBOLS,
// ordered appropriately for output. Included files should appear in
// the order in which they are first referenced by source line info.
// Assign these files source id numbers starting with NEXT_SOURCE_ID.
//
// Note that the name_to_file map may contain #included files that are
// unreferenced; these are the result of LoadFuncSymbols omitting
// functions from the list whose addresses fall outside the address
// range of the file that contains them.
static void CollectIncludedFiles(const struct SymbolInfo &symbols,
std::vector<SourceFileInfo *> *included_files,
int next_source_id) {
for (SourceFileInfoList::const_iterator file_it = symbols.main_files.begin();
file_it != symbols.main_files.end(); file_it++) {
for (FuncInfoList::const_iterator func_it = (*file_it)->func_info.begin();
func_it != (*file_it)->func_info.end(); func_it++) {
for (LineInfoList::const_iterator line_it = func_it->line_info.begin();
line_it != func_it->line_info.end(); line_it++) {
SourceFileInfo *file = line_it->file;
if (file->source_id == -1) {
file->source_id = next_source_id++;
// Here we use the source id as a mark, ensuring that each
// file appears in the list only once.
included_files->push_back(file);
}
}
}
}
}
// Write 'FILE' lines for all source files in SYMBOLS to FILE. We
// assign source id numbers to files here.
static bool WriteSourceFileInfo(FILE *file, struct SymbolInfo &symbols) {
int next_source_id = 0;
// Assign source id numbers to main files, and write them out to the file.
for (SourceFileInfoList::iterator file_it = symbols.main_files.begin();
file_it != symbols.main_files.end(); file_it++) {
SourceFileInfo *file_info = *file_it;
assert(file_info->addr);
// We only output 'FILE' lines for main files if their names
// contain '.'. The extensionless C++ header files are #included,
// not main files, so it wouldn't affect them. If you know the
// story, please patch this comment.
if (strchr(file_info->name, '.')) {
file_info->source_id = next_source_id++;
if (0 > fprintf(file, "FILE %d %s\n",
file_info->source_id, file_info->name))
return false;
}
}
// Compute the list of included files, and write them out.
// Can't use SourceFileInfoList here, because that owns the files it
// points to.
std::vector<SourceFileInfo *> included_files;
std::vector<SourceFileInfo *>::const_iterator file_it;
CollectIncludedFiles(symbols, &included_files, next_source_id);
for (file_it = included_files.begin(); file_it != included_files.end();
file_it++) {
if (0 > fprintf(file, "FILE %d %s\n",
(*file_it)->source_id, (*file_it)->name))
return false;
}
return true;
}
static bool WriteOneFunction(FILE *file,
const struct FuncInfo &func_info){
std::string func_name = Demangle(func_info.name.c_str());
if (func_info.size <= 0)
return true;
if (0 <= fprintf(file, "FUNC %lx %lx %d %s\n",
(unsigned long) func_info.rva_to_base,
(unsigned long) func_info.size,
func_info.stack_param_size,
func_name.c_str())) {
for (LineInfoList::const_iterator it = func_info.line_info.begin();
it != func_info.line_info.end(); it++) {
const struct LineInfo &line_info = *it;
if (0 > fprintf(file, "%lx %lx %d %d\n",
(unsigned long) line_info.rva_to_base,
(unsigned long) line_info.size,
line_info.line_num,
line_info.file->source_id))
return false;
}
return true;
}
return false;
}
static bool WriteFunctionInfo(FILE *file, const struct SymbolInfo &symbols) {
for (SourceFileInfoList::const_iterator it = symbols.main_files.begin();
it != symbols.main_files.end(); it++) {
const struct SourceFileInfo &file_info = **it;
for (FuncInfoList::const_iterator fiIt = file_info.func_info.begin();
fiIt != file_info.func_info.end(); fiIt++) {
const struct FuncInfo &func_info = *fiIt;
if (!WriteOneFunction(file, func_info))
return false;
}
}
return true;
}
static bool DumpStabSymbols(FILE *file, struct SymbolInfo &symbols) {
return WriteSourceFileInfo(file, symbols) &&
WriteFunctionInfo(file, symbols);
return LoadSymbols(stab_section, stabstr_section, module);
}
//
@@ -713,6 +412,48 @@ class MmapWrapper {
size_t size_;
};
// Return the breakpad symbol file identifier for the architecture of
// ELF_HEADER.
const char *ElfArchitecture(const ElfW(Ehdr) *elf_header) {
ElfW(Half) arch = elf_header->e_machine;
if (arch == EM_386)
return "x86";
else if (arch == EM_X86_64)
return "x86_64";
else
return NULL;
}
// Format the Elf file identifier in IDENTIFIER as a UUID with the
// dashes removed.
std::string FormatIdentifier(unsigned char identifier[16]) {
char identifier_str[40];
google_breakpad::FileID::ConvertIdentifierToString(
identifier,
identifier_str,
sizeof(identifier_str));
std::string id_no_dash;
for (int i = 0; identifier_str[i] != '\0'; ++i)
if (identifier_str[i] != '-')
id_no_dash += identifier_str[i];
// Add an extra "0" by the end. PDB files on Windows have an 'age'
// number appended to the end of the file identifier; this isn't
// really used or necessary on other platforms, but let's preserve
// the pattern.
id_no_dash += '0';
return id_no_dash;
}
// Return the non-directory portion of FILENAME: the portion after the
// last slash, or the whole filename if there are no slashes.
std::string BaseFileName(const std::string &filename) {
// Lots of copies! basename's behavior is less than ideal.
char *c_filename = strdup(filename.c_str());
std::string base = basename(c_filename);
free(c_filename);
return base;
}
} // namespace
namespace google_breakpad {
@@ -734,16 +475,27 @@ bool DumpSymbols::WriteSymbolFile(const std::string &obj_file,
ElfW(Ehdr) *elf_header = reinterpret_cast<ElfW(Ehdr) *>(obj_base);
if (!IsValidElf(elf_header))
return false;
struct SymbolInfo symbols;
if (!LoadSymbols(elf_header, &symbols))
return false;
// Write to symbol file.
if (WriteModuleInfo(sym_file, elf_header->e_machine, obj_file) &&
DumpStabSymbols(sym_file, symbols))
return true;
unsigned char identifier[16];
google_breakpad::FileID file_id(obj_file.c_str());
if (! file_id.ElfFileIdentifier(identifier))
return false;
return false;
const char *architecture = ElfArchitecture(elf_header);
if (! architecture)
return false;
std::string name = BaseFileName(obj_file);
std::string os = "Linux";
std::string id = FormatIdentifier(identifier);
Module module(name, os, architecture, id);
if (!LoadSymbols(elf_header, &module))
return false;
if (!module.Write(sym_file))
return false;
return true;
}
} // namespace google_breakpad

View File

@@ -32,104 +32,74 @@
// See file_id.h for documentation
//
#include <cassert>
#include <cstdio>
#include "common/linux/file_id.h"
#include <arpa/inet.h>
#include <elf.h>
#include <fcntl.h>
#include <link.h>
#include <sys/mman.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include "common/linux/file_id.h"
#include "common/md5.h"
#include <cassert>
#include <cstdio>
namespace google_breakpad {
static bool FindElfTextSection(const void *elf_mapped_base,
const void **text_start,
int *text_size) {
assert(elf_mapped_base);
assert(text_start);
assert(text_size);
const unsigned char *elf_base =
static_cast<const unsigned char *>(elf_mapped_base);
const ElfW(Ehdr) *elf_header =
reinterpret_cast<const ElfW(Ehdr) *>(elf_base);
if (memcmp(elf_header, ELFMAG, SELFMAG) != 0)
return false;
*text_start = NULL;
*text_size = 0;
const ElfW(Shdr) *sections =
reinterpret_cast<const ElfW(Shdr) *>(elf_base + elf_header->e_shoff);
const char *text_section_name = ".text";
int name_len = strlen(text_section_name);
const ElfW(Shdr) *string_section = sections + elf_header->e_shstrndx;
const ElfW(Shdr) *text_section = NULL;
for (int i = 0; i < elf_header->e_shnum; ++i) {
if (sections[i].sh_type == SHT_PROGBITS) {
const char *section_name = (char*)(elf_base +
string_section->sh_offset +
sections[i].sh_name);
if (!strncmp(section_name, text_section_name, name_len)) {
text_section = &sections[i];
break;
}
}
}
if (text_section != NULL && text_section->sh_size > 0) {
int text_section_size = text_section->sh_size;
*text_start = elf_base + text_section->sh_offset;
*text_size = text_section_size;
}
return true;
}
FileID::FileID(const char *path) {
FileID::FileID(const char* path) {
strncpy(path_, path, sizeof(path_));
}
bool FileID::ElfFileIdentifier(unsigned char identifier[16]) {
bool FileID::ElfFileIdentifier(uint8_t identifier[kMDGUIDSize]) {
const ssize_t mapped_len = 4096; // Page size (matches WriteMappings())
int fd = open(path_, O_RDONLY);
if (fd < 0)
return false;
struct stat st;
if (fstat(fd, &st) != 0 && st.st_size <= 0) {
if (fstat(fd, &st) != 0 || st.st_size <= mapped_len) {
close(fd);
return false;
}
void *base = mmap(NULL, st.st_size,
void* base = mmap(NULL, mapped_len,
PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
if (base == MAP_FAILED) {
close(fd);
close(fd);
if (base == MAP_FAILED)
return false;
}
bool success = false;
const void *text_section = NULL;
int text_size = 0;
if (FindElfTextSection(base, &text_section, &text_size) && (text_size > 0)) {
struct MD5Context md5;
MD5Init(&md5);
MD5Update(&md5,
static_cast<const unsigned char*>(text_section),
text_size);
MD5Final(identifier, &md5);
success = true;
memset(identifier, 0, kMDGUIDSize);
uint8_t* ptr = reinterpret_cast<uint8_t*>(base);
uint8_t* ptr_end = ptr + mapped_len;
while (ptr < ptr_end) {
for (unsigned i = 0; i < kMDGUIDSize; i++)
identifier[i] ^= ptr[i];
ptr += kMDGUIDSize;
}
close(fd);
munmap(base, st.st_size);
return success;
munmap(base, mapped_len);
return true;
}
// static
void FileID::ConvertIdentifierToString(const unsigned char identifier[16],
char *buffer, int buffer_length) {
void FileID::ConvertIdentifierToString(const uint8_t identifier[kMDGUIDSize],
char* buffer, int buffer_length) {
uint8_t identifier_swapped[kMDGUIDSize];
// Endian-ness swap to match dump processor expectation.
memcpy(identifier_swapped, identifier, kMDGUIDSize);
uint32_t* data1 = reinterpret_cast<uint32_t*>(identifier_swapped);
*data1 = htonl(*data1);
uint16_t* data2 = reinterpret_cast<uint16_t*>(identifier_swapped + 4);
*data2 = htons(*data2);
uint16_t* data3 = reinterpret_cast<uint16_t*>(identifier_swapped + 6);
*data3 = htons(*data3);
int buffer_idx = 0;
for (int idx = 0; (buffer_idx < buffer_length) && (idx < 16); ++idx) {
int hi = (identifier[idx] >> 4) & 0x0F;
int lo = (identifier[idx]) & 0x0F;
for (unsigned int idx = 0;
(buffer_idx < buffer_length) && (idx < kMDGUIDSize);
++idx) {
int hi = (identifier_swapped[idx] >> 4) & 0x0F;
int lo = (identifier_swapped[idx]) & 0x0F;
if (idx == 4 || idx == 6 || idx == 8 || idx == 10)
buffer[buffer_idx++] = '-';

View File

@@ -35,25 +35,30 @@
#include <limits.h>
#include "common/linux/guid_creator.h"
namespace google_breakpad {
static const size_t kMDGUIDSize = sizeof(MDGUID);
class FileID {
public:
FileID(const char *path);
~FileID() {};
explicit FileID(const char* path);
~FileID() {}
// Load the identifier for the elf file path specified in the constructor into
// |identifier|. Return false if the identifier could not be created for the
// file.
// The current implementation will return the MD5 hash of the file's bytes.
bool ElfFileIdentifier(unsigned char identifier[16]);
// The current implementation will XOR the first page of data to generate an
// identifier.
bool ElfFileIdentifier(uint8_t identifier[kMDGUIDSize]);
// Convert the |identifier| data to a NULL terminated string. The string will
// be formatted as a UUID (e.g., 22F065BB-FC9C-49F7-80FE-26A7CEBD7BCE).
// The |buffer| should be at least 37 bytes long to receive all of the data
// and termination. Shorter buffers will contain truncated data.
static void ConvertIdentifierToString(const unsigned char identifier[16],
char *buffer, int buffer_length);
static void ConvertIdentifierToString(const uint8_t identifier[kMDGUIDSize],
char* buffer, int buffer_length);
private:
// Storage for the path specified
@@ -63,4 +68,3 @@ class FileID {
} // namespace google_breakpad
#endif // COMMON_LINUX_FILE_ID_H__

View File

@@ -0,0 +1,178 @@
// Copyright (c) 2009, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// This header provides replacements for libc functions that we need. We if
// call the libc functions directly we risk crashing in the dynamic linker as
// it tries to resolve uncached PLT entries.
#ifndef CLIENT_LINUX_LINUX_LIBC_SUPPORT_H_
#define CLIENT_LINUX_LINUX_LIBC_SUPPORT_H_
#include <stdint.h>
#include <limits.h>
#include <sys/types.h>
extern "C" {
static inline size_t
my_strlen(const char* s) {
size_t len = 0;
while (*s++) len++;
return len;
}
static inline int
my_strcmp(const char* a, const char* b) {
for (;;) {
if (*a < *b)
return -1;
else if (*a > *b)
return 1;
else if (*a == 0)
return 0;
a++;
b++;
}
}
static inline int
my_strncmp(const char* a, const char* b, size_t len) {
for (size_t i = 0; i < len; ++i) {
if (*a < *b)
return -1;
else if (*a > *b)
return 1;
else if (*a == 0)
return 0;
a++;
b++;
}
return 0;
}
// Parse a non-negative integer.
// result: (output) the resulting non-negative integer
// s: a NUL terminated string
// Return true iff successful.
static inline bool
my_strtoui(int* result, const char* s) {
if (*s == 0)
return false;
int r = 0;
for (;; s++) {
if (*s == 0)
break;
const int old_r = r;
r *= 10;
if (*s < '0' || *s > '9')
return false;
r += *s - '0';
if (r < old_r)
return false;
}
*result = r;
return true;
}
// Return the length of the given, non-negative integer when expressed in base
// 10.
static inline unsigned
my_int_len(int i) {
if (!i)
return 1;
int len = 0;
while (i) {
len++;
i /= 10;
}
return len;
}
// Convert a non-negative integer to a string
// output: (output) the resulting string is written here. This buffer must be
// large enough to hold the resulting string. Call |my_int_len| to get the
// required length.
// i: the non-negative integer to serialise.
// i_len: the length of the integer in base 10 (see |my_int_len|).
static inline void
my_itos(char* output, int i, unsigned i_len) {
for (unsigned index = i_len; index; --index, i /= 10)
output[index - 1] = '0' + (i % 10);
}
static inline const char*
my_strchr(const char* haystack, char needle) {
while (*haystack && *haystack != needle)
haystack++;
if (*haystack == needle)
return haystack;
return (const char*) 0;
}
// Read a hex value
// result: (output) the resulting value
// s: a string
// Returns a pointer to the first invalid charactor.
static inline const char*
my_read_hex_ptr(uintptr_t* result, const char* s) {
uintptr_t r = 0;
for (;; ++s) {
if (*s >= '0' && *s <= '9') {
r <<= 4;
r += *s - '0';
} else if (*s >= 'a' && *s <= 'f') {
r <<= 4;
r += (*s - 'a') + 10;
} else if (*s >= 'A' && *s <= 'F') {
r <<= 4;
r += (*s - 'A') + 10;
} else {
break;
}
}
*result = r;
return s;
}
static inline void
my_memset(void* ip, char c, size_t len) {
char* p = (char *) ip;
while (len--)
*p++ = c;
}
} // extern "C"
#endif // CLIENT_LINUX_LINUX_LIBC_SUPPORT_H_

View File

@@ -0,0 +1,153 @@
// Copyright (c) 2009, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "breakpad/linux/linux_libc_support.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
typedef testing::Test LinuxLibcSupportTest;
}
TEST(LinuxLibcSupportTest, strlen) {
static const char* test_data[] = { "", "a", "aa", "aaa", "aabc", NULL };
for (unsigned i = 0; ; ++i) {
if (!test_data[i])
break;
ASSERT_EQ(strlen(test_data[i]), my_strlen(test_data[i]));
}
}
TEST(LinuxLibcSupportTest, strcmp) {
static const char* test_data[] = {
"", "",
"a", "",
"", "a",
"a", "b",
"a", "a",
"ab", "aa",
"abc", "ab",
"abc", "abc",
NULL,
};
for (unsigned i = 0; ; ++i) {
if (!test_data[i*2])
break;
ASSERT_EQ(my_strcmp(test_data[i*2], test_data[i*2 + 1]),
strcmp(test_data[i*2], test_data[i*2 + 1]));
}
}
TEST(LinuxLibcSupportTest, strtoui) {
int result;
ASSERT_FALSE(my_strtoui(&result, ""));
ASSERT_FALSE(my_strtoui(&result, "-1"));
ASSERT_FALSE(my_strtoui(&result, "-"));
ASSERT_FALSE(my_strtoui(&result, "a"));
ASSERT_FALSE(my_strtoui(&result, "23472893472938472987987398472398"));
ASSERT_TRUE(my_strtoui(&result, "0"));
ASSERT_EQ(result, 0);
ASSERT_TRUE(my_strtoui(&result, "1"));
ASSERT_EQ(result, 1);
ASSERT_TRUE(my_strtoui(&result, "12"));
ASSERT_EQ(result, 12);
ASSERT_TRUE(my_strtoui(&result, "123"));
ASSERT_EQ(result, 123);
ASSERT_TRUE(my_strtoui(&result, "0123"));
ASSERT_EQ(result, 123);
}
TEST(LinuxLibcSupportTest, int_len) {
ASSERT_EQ(my_int_len(0), 1);
ASSERT_EQ(my_int_len(2), 1);
ASSERT_EQ(my_int_len(5), 1);
ASSERT_EQ(my_int_len(9), 1);
ASSERT_EQ(my_int_len(10), 2);
ASSERT_EQ(my_int_len(99), 2);
ASSERT_EQ(my_int_len(100), 3);
ASSERT_EQ(my_int_len(101), 3);
ASSERT_EQ(my_int_len(1000), 4);
}
TEST(LinuxLibcSupportTest, itos) {
char buf[10];
my_itos(buf, 0, 1);
ASSERT_EQ(0, memcmp(buf, "0", 1));
my_itos(buf, 1, 1);
ASSERT_EQ(0, memcmp(buf, "1", 1));
my_itos(buf, 10, 2);
ASSERT_EQ(0, memcmp(buf, "10", 2));
my_itos(buf, 63, 2);
ASSERT_EQ(0, memcmp(buf, "63", 2));
my_itos(buf, 101, 3);
ASSERT_EQ(0, memcmp(buf, "101", 2));
}
TEST(LinuxLibcSupportTest, strchr) {
ASSERT_EQ(NULL, my_strchr("abc", 'd'));
ASSERT_EQ(NULL, my_strchr("", 'd'));
ASSERT_EQ(NULL, my_strchr("efghi", 'd'));
ASSERT_TRUE(my_strchr("a", 'a'));
ASSERT_TRUE(my_strchr("abc", 'a'));
ASSERT_TRUE(my_strchr("bcda", 'a'));
ASSERT_TRUE(my_strchr("sdfasdf", 'a'));
}
TEST(LinuxLibcSupportTest, read_hex_ptr) {
uintptr_t result;
const char* last;
last = my_read_hex_ptr(&result, "");
ASSERT_EQ(result, 0);
ASSERT_EQ(*last, 0);
last = my_read_hex_ptr(&result, "0");
ASSERT_EQ(result, 0);
ASSERT_EQ(*last, 0);
last = my_read_hex_ptr(&result, "0123");
ASSERT_EQ(result, 0x123);
ASSERT_EQ(*last, 0);
last = my_read_hex_ptr(&result, "0123a");
ASSERT_EQ(result, 0x123a);
ASSERT_EQ(*last, 0);
last = my_read_hex_ptr(&result, "0123a-");
ASSERT_EQ(result, 0x123a);
ASSERT_EQ(*last, '-');
}

File diff suppressed because it is too large Load Diff

176
src/common/linux/memory.h Normal file
View File

@@ -0,0 +1,176 @@
// Copyright (c) 2009, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef CLIENT_LINUX_HANDLER_MEMORY_H_
#define CLIENT_LINUX_HANDLER_MEMORY_H_
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include "common/linux/linux_syscall_support.h"
namespace google_breakpad {
// This is very simple allocator which fetches pages from the kernel directly.
// Thus, it can be used even when the heap may be corrupted.
//
// There is no free operation. The pages are only freed when the object is
// destroyed.
class PageAllocator {
public:
PageAllocator()
: page_size_(getpagesize()),
last_(NULL),
current_page_(NULL),
page_offset_(0) {
}
~PageAllocator() {
FreeAll();
}
void *Alloc(unsigned bytes) {
if (!bytes)
return NULL;
if (current_page_ && page_size_ - page_offset_ >= bytes) {
uint8_t *const ret = current_page_ + page_offset_;
page_offset_ += bytes;
if (page_offset_ == page_size_) {
page_offset_ = 0;
current_page_ = NULL;
}
return ret;
}
const unsigned pages =
(bytes + sizeof(PageHeader) + page_size_ - 1) / page_size_;
uint8_t *const ret = GetNPages(pages);
if (!ret)
return NULL;
page_offset_ = (page_size_ - (page_size_ * pages - (bytes + sizeof(PageHeader)))) % page_size_;
current_page_ = page_offset_ ? ret + page_size_ * (pages - 1) : NULL;
return ret + sizeof(PageHeader);
}
private:
uint8_t *GetNPages(unsigned num_pages) {
void *a = sys_mmap2(NULL, page_size_ * num_pages, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (a == MAP_FAILED)
return NULL;
struct PageHeader *header = reinterpret_cast<PageHeader*>(a);
header->next = last_;
header->num_pages = num_pages;
last_ = header;
return reinterpret_cast<uint8_t*>(a);
}
void FreeAll() {
PageHeader *next;
for (PageHeader *cur = last_; cur; cur = next) {
next = cur->next;
sys_munmap(cur, cur->num_pages * page_size_);
}
}
struct PageHeader {
PageHeader *next; // pointer to the start of the next set of pages.
unsigned num_pages; // the number of pages in this set.
};
const unsigned page_size_;
PageHeader *last_;
uint8_t *current_page_;
unsigned page_offset_;
};
// A wasteful vector is like a normal std::vector, except that it's very much
// simplier and it allocates memory from a PageAllocator. It's wasteful
// because, when resizing, it always allocates a whole new array since the
// PageAllocator doesn't support realloc.
template<class T>
class wasteful_vector {
public:
wasteful_vector(PageAllocator *allocator, unsigned size_hint = 16)
: allocator_(allocator),
a_((T*) allocator->Alloc(sizeof(T) * size_hint)),
allocated_(size_hint),
used_(0) {
}
void push_back(const T& new_element) {
if (used_ == allocated_)
Realloc(allocated_ * 2);
a_[used_++] = new_element;
}
size_t size() const {
return used_;
}
T& operator[](size_t index) {
return a_[index];
}
const T& operator[](size_t index) const {
return a_[index];
}
private:
void Realloc(unsigned new_size) {
T *new_array =
reinterpret_cast<T*>(allocator_->Alloc(sizeof(T) * new_size));
memcpy(new_array, a_, used_ * sizeof(T));
a_ = new_array;
allocated_ = new_size;
}
PageAllocator *const allocator_;
T *a_; // pointer to an array of |allocated_| elements.
unsigned allocated_; // size of |a_|, in elements.
unsigned used_; // number of used slots in |a_|.
};
} // namespace google_breakpad
inline void* operator new(size_t nbytes,
google_breakpad::PageAllocator& allocator) {
return allocator.Alloc(nbytes);
}
#endif // CLIENT_LINUX_HANDLER_MEMORY_H_

View File

@@ -0,0 +1,84 @@
// Copyright (c) 2009, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "breakpad/linux/memory.h"
#include "testing/gtest/include/gtest/gtest.h"
using namespace google_breakpad;
namespace {
typedef testing::Test PageAllocatorTest;
}
TEST(PageAllocatorTest, Setup) {
PageAllocator allocator;
}
TEST(PageAllocatorTest, SmallObjects) {
PageAllocator allocator;
for (unsigned i = 1; i < 1024; ++i) {
uint8_t *p = reinterpret_cast<uint8_t*>(allocator.Alloc(i));
ASSERT_FALSE(p == NULL);
memset(p, 0, i);
}
}
TEST(PageAllocatorTest, LargeObject) {
PageAllocator allocator;
uint8_t *p = reinterpret_cast<uint8_t*>(allocator.Alloc(10000));
ASSERT_FALSE(p == NULL);
for (unsigned i = 1; i < 10; ++i) {
uint8_t *p = reinterpret_cast<uint8_t*>(allocator.Alloc(i));
ASSERT_FALSE(p == NULL);
memset(p, 0, i);
}
}
namespace {
typedef testing::Test WastefulVectorTest;
}
TEST(WastefulVectorTest, Setup) {
PageAllocator allocator_;
wasteful_vector<int> v(&allocator_);
ASSERT_EQ(v.size(), 0u);
}
TEST(WastefulVectorTest, Simple) {
PageAllocator allocator_;
wasteful_vector<int> v(&allocator_);
for (unsigned i = 0; i < 256; ++i)
v.push_back(i);
ASSERT_EQ(v.size(), 256u);
for (unsigned i = 0; i < 256; ++i)
ASSERT_EQ(v[i], i);
}

167
src/common/linux/module.cc Normal file
View File

@@ -0,0 +1,167 @@
// Copyright (c) 2009, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <cerrno>
#include <cstring>
#include "common/linux/module.h"
namespace google_breakpad {
Module::Module(const string &name, const string &os,
const string &architecture, const string &id) :
name_(name),
os_(os),
architecture_(architecture),
id_(id),
load_address_(0) { }
Module::~Module() {
for (FileByNameMap::iterator it = files_.begin(); it != files_.end(); it++)
delete it->second;
for (vector<Function *>::iterator it = functions_.begin();
it != functions_.end(); it++)
delete *it;
}
void Module::SetLoadAddress(Address address) {
load_address_ = address;
}
void Module::AddFunction(Function *function) {
functions_.push_back(function);
}
void Module::AddFunctions(vector<Function *>::iterator begin,
vector<Function *>::iterator end) {
functions_.insert(functions_.end(), begin, end);
}
Module::File *Module::FindFile(const string &name) {
// A tricky bit here. The key of each map entry needs to be a
// pointer to the entry's File's name string. This means that we
// can't do the initial lookup with any operation that would create
// an empty entry for us if the name isn't found (like, say,
// operator[] or insert do), because such a created entry's key will
// be a pointer the string passed as our argument. Since the key of
// a map's value type is const, we can't fix it up once we've
// created our file. lower_bound does the lookup without doing an
// insertion, and returns a good hint iterator to pass to insert.
// Our "destiny" is where we belong, whether we're there or not now.
FileByNameMap::iterator destiny = files_.lower_bound(&name);
if (destiny == files_.end()
|| *destiny->first != name) { // Repeated string comparison, boo hoo.
File *file = new File;
file->name_ = name;
file->source_id_ = -1;
destiny = files_.insert(destiny,
FileByNameMap::value_type(&file->name_, file));
}
return destiny->second;
}
Module::File *Module::FindFile(const char *name) {
string name_string = name;
return FindFile(name_string);
}
void Module::AssignSourceIds() {
// First, give every source file an id of -1.
for (FileByNameMap::iterator file_it = files_.begin();
file_it != files_.end(); file_it++)
file_it->second->source_id_ = -1;
// Next, mark all files actually cited by our functions' line number
// info, by setting each one's source id to zero.
for (vector<Function *>::const_iterator func_it = functions_.begin();
func_it != functions_.end(); func_it++) {
Function *func = *func_it;
for (vector<Line>::iterator line_it = func->lines_.begin();
line_it != func->lines_.end(); line_it++)
line_it->file_->source_id_ = 0;
}
// Finally, assign source ids to those files that have been marked.
// We could have just assigned source id numbers while traversing
// the line numbers, but doing it this way numbers the files in
// lexicographical order by name, which is neat.
int next_source_id = 0;
for (FileByNameMap::iterator file_it = files_.begin();
file_it != files_.end(); file_it++)
if (! file_it->second->source_id_)
file_it->second->source_id_ = next_source_id++;
}
bool Module::ReportError() {
fprintf(stderr, "error writing symbol file: %s\n",
strerror (errno));
return false;
}
bool Module::Write(FILE *stream) {
if (0 > fprintf(stream, "MODULE %s %s %s %s\n",
os_.c_str(), architecture_.c_str(), id_.c_str(),
name_.c_str()))
return ReportError();
// Write out files.
AssignSourceIds();
for (FileByNameMap::iterator file_it = files_.begin();
file_it != files_.end(); file_it++) {
File *file = file_it->second;
if (file->source_id_ >= 0) {
if (0 > fprintf(stream, "FILE %d %s\n",
file->source_id_, file->name_.c_str()))
return ReportError();
}
}
// Write out functions and their lines.
for (vector<Function *>::const_iterator func_it = functions_.begin();
func_it != functions_.end(); func_it++) {
Function *func = *func_it;
if (0 > fprintf(stream, "FUNC %lx %lx %lu %s\n",
(unsigned long) (func->address_ - load_address_),
(unsigned long) func->size_,
(unsigned long) func->parameter_size_,
func->name_.c_str()))
return ReportError();
for (vector<Line>::iterator line_it = func->lines_.begin();
line_it != func->lines_.end(); line_it++)
if (0 > fprintf(stream, "%lx %lx %d %d\n",
(unsigned long) (line_it->address_ - load_address_),
(unsigned long) line_it->size_,
line_it->number_,
line_it->file_->source_id_))
return ReportError();
}
return true;
}
} // namespace google_breakpad

193
src/common/linux/module.h Normal file
View File

@@ -0,0 +1,193 @@
// Copyright (c) 2009, Google Inc. -*- mode: c++ -*-
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// module.h: defines google_breakpad::Module, for writing breakpad symbol files
#ifndef COMMON_LINUX_MODULE_H__
#define COMMON_LINUX_MODULE_H__
#include <map>
#include <string>
#include <vector>
#include <cstdio>
#include "google_breakpad/common/breakpad_types.h"
namespace google_breakpad {
using std::string;
using std::vector;
using std::map;
// A Module represents the contents of a module, and supports methods
// for adding information produced by parsing STABS or DWARF data
// --- possibly both from the same file --- and then writing out the
// unified contents as a Breakpad-format symbol file.
class Module {
public:
// The type of addresses and sizes in a symbol table.
typedef u_int64_t Address;
struct File;
struct Function;
struct Line;
// Addresses appearing in File, Function, and Line structures are
// absolute, not relative to the the module's load address. That
// is, if the module were loaded at its nominal load address, the
// addresses would be correct.
// A source file.
struct File {
// The name of the source file.
string name_;
// The file's source id. The Write member function clears this
// field and assigns source ids a fresh, so any value placed here
// before calling Write will be lost.
int source_id_;
};
// A function.
struct Function {
// For sorting by address. (Not style-guide compliant, but it's
// stupid not to put this in the struct.)
static bool CompareByAddress(const Function *x, const Function *y) {
return x->address_ < y->address_;
}
// The function's name.
string name_;
// The start address and length of the function's code.
Address address_, size_;
// The function's parameter size.
Address parameter_size_;
// Source lines belonging to this function, sorted by increasing
// address.
vector<Line> lines_;
};
// A source line.
struct Line {
// For sorting by address. (Not style-guide compliant, but it's
// stupid not to put this in the struct.)
static bool CompareByAddress(const Module::Line &x, const Module::Line &y) {
return x.address_ < y.address_;
}
Address address_, size_; // The address and size of the line's code.
File *file_; // The source file.
int number_; // The source line number.
};
// Create a new module with the given name, operating system,
// architecture, and ID string.
Module(const string &name, const string &os, const string &architecture,
const string &id);
~Module();
// Set the module's load address to LOAD_ADDRESS; addresses given
// for functions and lines will be written to the Breakpad symbol
// file as offsets from this address. Construction initializes this
// module's load address to zero: addresses written to the symbol
// file will be the same as they appear in the File and Line
// structures.
void SetLoadAddress(Address load_address);
// Add FUNCTION to the module.
// Destroying this module frees all Function objects that have been
// added with this function.
void AddFunction(Function *function);
// Add all the functions in [BEGIN,END) to the module.
// Destroying this module frees all Function objects that have been
// added with this function.
void AddFunctions(vector<Function *>::iterator begin,
vector<Function *>::iterator end);
// If this module has a file named NAME, return a pointer to it. If
// it has none, then create one and return a pointer to the new
// file. Destroying this module frees all File objects that have
// been created using this function, or with Insert.
File *FindFile(const string &name);
File *FindFile(const char *name);
// Write this module to STREAM in the breakpad symbol format.
// Return true if all goes well, or false if an error occurs. This
// method writes out:
// - a header based on the values given to the constructor,
// - the source files added via FindFile, and finally
// - the functions added via AddFunctions, each with its lines.
// Addresses in the output are all relative to the load address
// established by SetLoadAddress.
bool Write(FILE *stream);
private:
// Find those files in this module that are actually referred to by
// functions' line number data, and assign them source id numbers.
// Set the source id numbers for all other files --- unused by the
// source line data --- to -1. We do this before writing out the
// symbol file, at which point we omit any unused files.
void AssignSourceIds();
// Report an error that has occurred writing the symbol file, using
// errno to find the appropriate cause. Return false.
static bool ReportError();
// Module header entries.
string name_, os_, architecture_, id_;
// The module's nominal load address. Addresses for functions and
// lines are absolute, assuming the module is loaded at this
// address.
Address load_address_;
// Relation for maps whose keys are strings shared with some other
// structure.
struct CompareStringPtrs {
bool operator()(const string *x, const string *y) { return *x < *y; };
};
// A map from filenames to File structures. The map's keys are
// pointers to the Files' names.
typedef map<const string *, File *, CompareStringPtrs> FileByNameMap;
// The module owns all the files and functions that have been added
// to it; destroying the module frees the Files and Functions these
// point to.
FileByNameMap files_; // This module's source files.
vector<Function *> functions_; // This module's functions.
};
} // namespace google_breakpad
#endif // COMMON_LINUX_MODULE_H__

View File

@@ -0,0 +1,200 @@
// Copyright 2009 Google Inc. All Rights Reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// This file implements the google_breakpad::StabsReader class.
#include <a.out.h>
#include <stab.h>
#include <cstring>
#include <cassert>
#include "common/linux/stabs_reader.h"
namespace google_breakpad {
StabsReader::StabsReader(const uint8_t *stab, size_t stab_size,
const uint8_t *stabstr, size_t stabstr_size,
StabsHandler *handler) :
stabstr_(stabstr),
stabstr_size_(stabstr_size),
handler_(handler),
symbol_(NULL),
current_source_file_(NULL) {
symbols_ = reinterpret_cast<const struct nlist *>(stab);
symbols_end_ = symbols_ + (stab_size / sizeof (*symbols_));
}
const char *StabsReader::SymbolString() {
ptrdiff_t offset = symbol_->n_un.n_strx;
if (offset < 0 || (size_t) offset >= stabstr_size_) {
handler_->Warning("symbol %d: name offset outside the string section",
symbol_ - symbols_);
// Return our null string, to keep our promise about all names being
// taken from the string section.
offset = 0;
}
return reinterpret_cast<const char *>(stabstr_ + offset);
}
bool StabsReader::Process() {
symbol_ = symbols_;
while (symbol_ < symbols_end_) {
if (symbol_->n_type == N_SO) {
if (! ProcessCompilationUnit())
return false;
} else
symbol_++;
}
return true;
}
bool StabsReader::ProcessCompilationUnit() {
assert(symbol_ < symbols_end_ && symbol_->n_type == N_SO);
// There may be an N_SO entry whose name ends with a slash,
// indicating the directory in which the compilation occurred.
// The build directory defaults to NULL.
const char *build_directory = NULL;
{
const char *name = SymbolString();
if (name[0] && name[strlen(name) - 1] == '/') {
build_directory = name;
symbol_++;
}
}
// We expect to see an N_SO entry with a filename next, indicating
// the start of the compilation unit.
{
if (symbol_ >= symbols_end_ || symbol_->n_type != N_SO)
return true;
const char *name = SymbolString();
if (name[0] == '\0') {
// This seems to be a stray end-of-compilation-unit marker;
// consume it, but don't report the end, since we didn't see a
// beginning.
symbol_++;
return true;
}
current_source_file_ = name;
}
if (! handler_->StartCompilationUnit(current_source_file_,
SymbolValue(),
build_directory))
return false;
symbol_++;
// The STABS documentation says that some compilers may emit
// additional N_SO entries with names immediately following the
// first, and that they should be ignored. However, the original
// Breakpad STABS reader doesn't ignore them, so we won't either.
// Process the body of the compilation unit, up to the next N_SO.
while (symbol_ < symbols_end_ && symbol_->n_type != N_SO) {
if (symbol_->n_type == N_FUN) {
if (! ProcessFunction())
return false;
} else
// Ignore anything else.
symbol_++;
}
// An N_SO with an empty name indicates the end of the compilation
// unit. Default to zero.
uint64_t ending_address = 0;
if (symbol_ < symbols_end_) {
assert(symbol_->n_type == N_SO);
const char *name = SymbolString();
if (name[0] == '\0') {
ending_address = SymbolValue();
symbol_++;
}
}
if (! handler_->EndCompilationUnit(ending_address))
return false;
return true;
}
bool StabsReader::ProcessFunction() {
assert(symbol_ < symbols_end_ && symbol_->n_type == N_FUN);
uint64_t function_address = SymbolValue();
// The STABS string for an N_FUN entry is the name of the function,
// followed by a colon, followed by type information for the
// function. We want to pass the name alone to StartFunction.
const char *stab_string = SymbolString();
const char *name_end = strchr(stab_string, ':');
if (! name_end)
name_end = stab_string + strlen(stab_string);
std::string name(stab_string, name_end - stab_string);
if (! handler_->StartFunction(name, function_address))
return false;
symbol_++;
while (symbol_ < symbols_end_) {
if (symbol_->n_type == N_SO || symbol_->n_type == N_FUN)
break;
else if (symbol_->n_type == N_SLINE) {
// The value of an N_SLINE entry is the offset of the line from
// the function's start address.
uint64_t line_address = function_address + SymbolValue();
// The n_desc of a N_SLINE entry is the line number. It's a
// signed 16-bit field; line numbers from 32768 to 65535 are
// stored as n-65536.
uint16_t line_number = symbol_->n_desc;
if (! handler_->Line(line_address, current_source_file_, line_number))
return false;
symbol_++;
} else if (symbol_->n_type == N_SOL) {
current_source_file_ = SymbolString();
symbol_++;
} else
// Ignore anything else.
symbol_++;
}
// If there is a subsequent N_SO or N_FUN entry, its address is our
// end address.
uint64_t ending_address = 0;
if (symbol_ < symbols_end_) {
assert(symbol_->n_type == N_SO || symbol_->n_type == N_FUN);
ending_address = SymbolValue();
// Note: we do not increment symbol_ here, since we haven't consumed it.
}
if (! handler_->EndFunction(ending_address))
return false;
return true;
}
} // namespace google_breakpad

View File

@@ -0,0 +1,193 @@
// Copyright 2009 Google Inc. All Rights Reserved. -*- mode: c++ -*-
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// This file contains definitions related to the STABS reader and
// its handler interfaces.
// A description of the STABS debugging format can be found at
// http://sourceware.org/gdb/current/onlinedocs/stabs_toc.html
// The comments here assume you understand the format.
//
// This reader assumes that the system's <a.out.h> and <stab.h>
// headers accurately describe the layout of the STABS data; this code
// is not cross-platform safe.
#ifndef COMMON_LINUX_STABS_READER_H__
#define COMMON_LINUX_STABS_READER_H__
#include <stdint.h>
#include <cstddef>
#include <a.out.h>
#include <string>
namespace google_breakpad {
class StabsHandler;
class StabsReader {
public:
// Create a reader for the STABS debug information whose .stab
// section is the STAB_SIZE bytes at STAB, and whose .stabstr
// section is the STABSTR_SIZE bytes at STABSTR. The reader will
// call the member functions of HANDLER to report the information it
// finds, when the reader's 'Process' member function is called.
//
// Note that, in ELF, the .stabstr section should be found using the
// 'sh_link' field of the .stab section header, not by name.
StabsReader(const uint8_t *stab, size_t stab_size,
const uint8_t *stabstr, size_t stabstr_size,
StabsHandler *handler);
// Process the STABS data, calling the handler's member functions to
// report what we find. While the handler functions return true,
// continue to process until we reach the end of the section. If we
// processed the entire section and all handlers returned true,
// return true. If any handler returned false, return false.
bool Process();
private:
// Return the name of the current symbol.
const char *SymbolString();
// Return the value of the current symbol.
const uint64_t SymbolValue() {
return symbol_->n_value;
}
// Process a compilation unit starting at symbol_. Return true
// to continue processing, or false to abort.
bool ProcessCompilationUnit();
// Process a function in current_source_file_ starting at symbol_.
// Return true to continue processing, or false to abort.
bool ProcessFunction();
// The debugging information we're reading.
const struct nlist *symbols_, *symbols_end_;
const uint8_t *stabstr_;
size_t stabstr_size_;
StabsHandler *handler_;
// The current symbol we're processing.
const struct nlist *symbol_;
// The current source file name.
const char *current_source_file_;
};
// Consumer-provided callback structure for the STABS reader. Clients
// of the STABS reader provide an instance of this structure. The
// reader then invokes the member functions of that instance to report
// the information it finds.
//
// The default definitions of the member functions do nothing, and return
// true so processing will continue.
class StabsHandler {
public:
StabsHandler() { }
virtual ~StabsHandler() { }
// Some general notes about the handler callback functions:
// Processing proceeds until the end of the .stabs section, or until
// one of these functions returns false.
// The addresses given are as reported in the STABS info, without
// regard for whether the module may be loaded at different
// addresses at different times (a shared library, say). When
// processing STABS from an ELF shared library, the addresses given
// all assume the library is loaded at its nominal load address.
// They are *not* offsets from the nominal load address. If you
// want offsets, you must subtract off the library's nominal load
// address.
// The arguments to these functions named FILENAME are all
// references to strings stored in the .stabstr section. Because
// both the Linux and Solaris linkers factor out duplicate strings
// from the .stabstr section, the consumer can assume that if two
// FILENAME values are different addresses, they represent different
// file names.
//
// Thus, it's safe to use (say) std::map<char *, ...>, which does
// string address comparisons, not string content comparisons.
// Since all the strings are in same array of characters --- the
// .stabstr section --- comparing their addresses produces
// predictable, if not lexicographically meaningful, results.
// Begin processing a compilation unit whose main source file is
// named FILENAME, and whose base address is ADDRESS. If
// BUILD_DIRECTORY is non-NULL, it is the name of the build
// directory in which the compilation occurred.
virtual bool StartCompilationUnit(const char *filename, uint64_t address,
const char *build_directory) {
return true;
}
// Finish processing the compilation unit. If ADDRESS is non-zero,
// it is the ending address of the compilation unit. If ADDRESS is
// zero, then the compilation unit's ending address is not
// available, and the consumer must infer it by other means.
virtual bool EndCompilationUnit(uint64_t address) { return true; }
// Begin processing a function named NAME, whose starting address is
// ADDRESS. This function belongs to the compilation unit that was
// most recently started but not ended.
//
// Note that, unlike filenames, NAME is not a pointer into the
// .stabstr section; this is because the name as it appears in the
// STABS data is followed by type information. The value passed to
// StartFunction is the function name alone.
//
// In languages that use name mangling, like C++, NAME is mangled.
virtual bool StartFunction(const std::string &name, uint64_t address) {
return true;
}
// Finish processing the function. If ADDRESS is non-zero, it is
// the ending address for the function. If ADDRESS is zero, then
// the function's ending address is not available, and the consumer
// must infer it by other means.
virtual bool EndFunction(uint64_t address) { return true; }
// Report that the code at ADDRESS is attributable to line NUMBER of
// the source file named FILENAME. The caller must infer the ending
// address of the line.
virtual bool Line(uint64_t address, const char *filename, int number) {
return true;
}
// Report a warning. FORMAT is a printf-like format string,
// specifying how to format the subsequent arguments. By default,
// print the message to the standard error output.
virtual void Warning(const char *format, ...) = 0;
};
} // namespace google_breakpad
#endif // COMMON_LINUX_STABS_READER_H__

View File

@@ -33,11 +33,11 @@
#import <Foundation/Foundation.h>
#include <mach-o/loader.h>
#include "common/mac/dwarf/dwarf2reader.h"
#include "common/dwarf/dwarf2reader.h"
// This will map from an architecture string to a SectionMap, which
// will contain the offsets for all the sections in the dictionary
typedef hash_map<string, dwarf2reader::SectionMap *> ArchSectionMap;
typedef map<string, dwarf2reader::SectionMap *> ArchSectionMap;
@interface DumpSymbols : NSObject {
@protected

View File

@@ -47,9 +47,9 @@
#import "dump_syms.h"
#import "common/mac/file_id.h"
#import "common/mac/macho_utilities.h"
#import "common/mac/dwarf/dwarf2reader.h"
#import "common/mac/dwarf/functioninfo.h"
#import "common/mac/dwarf/bytereader.h"
#import "common/dwarf/dwarf2reader.h"
#import "common/dwarf/functioninfo.h"
#import "common/dwarf/bytereader.h"
using google_breakpad::FileID;
@@ -68,15 +68,6 @@ static NSString *kHeaderCPUTypeKey = @"cpuType";
// for pruning out extraneous non-function symbols.
static const int kTextSection = 1;
namespace __gnu_cxx {
template<>
struct hash<std::string> {
size_t operator()(const std::string& k) const {
return hash< const char* >()( k.c_str() );
}
};
}
// Dump FunctionMap to stdout. Print address, function name, file
// name, line number, lowpc, and highpc if available.
void DumpFunctionMap(const dwarf2reader::FunctionMap function_map) {

View File

@@ -94,8 +94,11 @@ typedef enum {
/* EXCEPTION_PRIV_INSTRUCTION */
MD_EXCEPTION_CODE_WIN_STACK_OVERFLOW = 0xc00000fd,
/* EXCEPTION_STACK_OVERFLOW */
MD_EXCEPTION_CODE_WIN_POSSIBLE_DEADLOCK = 0xc0000194
MD_EXCEPTION_CODE_WIN_POSSIBLE_DEADLOCK = 0xc0000194,
/* EXCEPTION_POSSIBLE_DEADLOCK */
MD_EXCEPTION_CODE_WIN_UNHANDLED_CPP_EXCEPTION = 0xe06d7363
/* Per http://support.microsoft.com/kb/185294,
generated by Visual C++ compiler */
} MDExceptionCodeWin;

View File

@@ -33,28 +33,14 @@
#ifndef GOOGLE_BREAKPAD_PROCESSOR_BASIC_SOURCE_LINE_RESOLVER_H__
#define GOOGLE_BREAKPAD_PROCESSOR_BASIC_SOURCE_LINE_RESOLVER_H__
// TODO: Platforms that have no hash_map can use map, at the likely cost of
// performance.
#ifdef __SUNPRO_CC
#define BSLR_NO_HASH_MAP
#endif // __SUNPRO_CC
#ifdef BSLR_NO_HASH_MAP
#include <map>
#else // BSLR_NO_HASH_MAP
#include <ext/hash_map>
#endif // BSLR_NO_HASH_MAP
#include "google_breakpad/processor/source_line_resolver_interface.h"
namespace google_breakpad {
using std::string;
#ifdef BSLR_NO_HASH_MAP
using std::map;
#else // BSLR_NO_HASH_MAP
using __gnu_cxx::hash_map;
#endif // BSLR_NO_HASH_MAP
class BasicSourceLineResolver : public SourceLineResolverInterface {
public:
@@ -85,23 +71,13 @@ class BasicSourceLineResolver : public SourceLineResolverInterface {
struct Function;
struct PublicSymbol;
struct File;
#ifdef BSLR_NO_HASH_MAP
struct CompareString {
bool operator()(const string &s1, const string &s2) const;
};
#else // BSLR_NO_HASH_MAP
struct HashString {
size_t operator()(const string &s) const;
};
#endif // BSLR_NO_HASH_MAP
class Module;
// All of the modules we've loaded
#ifdef BSLR_NO_HASH_MAP
typedef map<string, Module*, CompareString> ModuleMap;
#else // BSLR_NO_HASH_MAP
typedef hash_map<string, Module*, HashString> ModuleMap;
#endif // BSLR_NO_HASH_MAP
ModuleMap *modules_;
// Disallow unwanted copy ctor and assignment operator

View File

@@ -51,9 +51,6 @@
using std::map;
using std::vector;
using std::make_pair;
#ifndef BSLR_NO_HASH_MAP
using __gnu_cxx::hash;
#endif // BSLR_NO_HASH_MAP
namespace google_breakpad {
@@ -125,11 +122,7 @@ class BasicSourceLineResolver::Module {
private:
friend class BasicSourceLineResolver;
#ifdef BSLR_NO_HASH_MAP
typedef map<int, string> FileMap;
#else // BSLR_NO_HASH_MAP
typedef hash_map<int, string> FileMap;
#endif // BSLR_NO_HASH_MAP
// The types for stack_info_. This is equivalent to MS DIA's
// StackFrameTypeEnum. Each identifies a different type of frame
@@ -702,15 +695,9 @@ bool BasicSourceLineResolver::Module::ParseStackInfo(char *stack_info_line) {
return true;
}
#ifdef BSLR_NO_HASH_MAP
bool BasicSourceLineResolver::CompareString::operator()(
const string &s1, const string &s2) const {
return strcmp(s1.c_str(), s2.c_str()) < 0;
}
#else // BSLR_NO_HASH_MAP
size_t BasicSourceLineResolver::HashString::operator()(const string &s) const {
return hash<const char*>()(s.c_str());
}
#endif // BSLR_NO_HASH_MAP
} // namespace google_breakpad

View File

@@ -28,6 +28,7 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <cassert>
#include <cstdio>
#include "google_breakpad/processor/minidump_processor.h"
#include "google_breakpad/processor/call_stack.h"
@@ -753,6 +754,9 @@ string MinidumpProcessor::GetCrashReason(Minidump *dump, u_int64_t *address) {
case MD_EXCEPTION_CODE_WIN_POSSIBLE_DEADLOCK:
reason = "EXCEPTION_POSSIBLE_DEADLOCK";
break;
case MD_EXCEPTION_CODE_WIN_UNHANDLED_CPP_EXCEPTION:
reason = "Unhandled C++ Exception";
break;
default:
BPLOG(INFO) << "Unknown exception reason " << reason;
break;

View File

@@ -27,6 +27,8 @@
// (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 <cstdio>
#include "processor/pathname_stripper.h"
#include "processor/logging.h"

View File

@@ -38,6 +38,7 @@
#define PROCESSOR_POSTFIX_EVALUATOR_INL_H__
#include <cstdio>
#include <sstream>
#include "processor/postfix_evaluator.h"

View File

@@ -16,22 +16,40 @@ BIN=dump_syms
all:$(BIN)
DUMP_OBJ=dump_symbols.o guid_creator.o dump_syms.o file_id.o md5.o
TOP = ../../../..
VPATH = $(TOP)/src/common/linux
VPATH += $(TOP)/src/common
VPATH += $(TOP)/src/common/dwarf
DUMP_OBJ = \
dump_symbols.o \
dump_syms.o \
dwarf2diehandler.o \
file_id.o \
guid_creator.o \
md5.o \
module.o \
stabs_reader.o \
$(NULL)
dump_syms:$(DUMP_OBJ)
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -o $@ $^
dump_symbols.o:../../../common/linux/dump_symbols.cc
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $^
# From this directory:
dump_syms.o: dump_syms.cc
guid_creator.o:../../../common/linux/guid_creator.cc
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $^
# From src/common/linux:
dump_symbols.o: dump_symbols.cc
stabs_reader.o: stabs_reader.cc
module.o: module.cc
guid_creator.o: guid_creator.cc
file_id.o: file_id.cc
file_id.o:../../../common/linux/file_id.cc
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $^
# From src/common:
md5.o: md5.c
md5.o:../../../common/md5.c
$(CC) $(CPPFLAGS) $(CXXFLAGS) -c $^
# From src/common/dwarf:
dwarf2diehandler.o: dwarf2diehandler.cc
clean:
rm -f $(BIN) $(DUMP_OBJ)

View File

@@ -0,0 +1,602 @@
// Copyright (c) 2009, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Converts a minidump file to a core file which gdb can read.
// Large parts lifted from the userspace core dumper:
// http://code.google.com/p/google-coredumper/
//
// Usage: minidump-2-core 1234.dmp > core
#include <vector>
#include <stdio.h>
#include <string.h>
#include <elf.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/user.h>
#include <sys/mman.h>
#include "google_breakpad/common/minidump_format.h"
#include "google_breakpad/common/minidump_cpu_x86.h"
#include "breakpad/linux/minidump_format_linux.h"
#if __WORDSIZE == 64
#define ELF_CLASS ELFCLASS64
#define Ehdr Elf64_Ehdr
#define Phdr Elf64_Phdr
#define Shdr Elf64_Shdr
#define Nhdr Elf64_Nhdr
#define auxv_t Elf64_auxv_t
#else
#define ELF_CLASS ELFCLASS32
#define Ehdr Elf32_Ehdr
#define Phdr Elf32_Phdr
#define Shdr Elf32_Shdr
#define Nhdr Elf32_Nhdr
#define auxv_t Elf32_auxv_t
#endif
#if defined(__x86_64__)
#define ELF_ARCH EM_X86_64
#elif defined(__i386__)
#define ELF_ARCH EM_386
#elif defined(__ARM_ARCH_3__)
#define ELF_ARCH EM_ARM
#elif defined(__mips__)
#define ELF_ARCH EM_MIPS
#endif
static int usage(const char* argv0) {
fprintf(stderr, "Usage: %s <minidump file>\n", argv0);
return 1;
}
// Write all of the given buffer, handling short writes and EINTR. Return true
// iff successful.
static bool
writea(int fd, const void* idata, size_t length) {
const uint8_t* data = (const uint8_t*) idata;
size_t done = 0;
while (done < length) {
ssize_t r;
do {
r = write(fd, data + done, length - done);
} while (r == -1 && errno == EINTR);
if (r < 1)
return false;
done += r;
}
return true;
}
// A range of a mmaped file.
class MMappedRange {
public:
MMappedRange(const void* data, size_t length)
: data_(reinterpret_cast<const uint8_t*>(data)),
length_(length) {
}
// Get an object of |length| bytes at |offset| and return a pointer to it
// unless it's out of bounds.
const void* GetObject(size_t offset, size_t length) {
if (offset + length < offset)
return NULL;
if (offset + length > length_)
return NULL;
return data_ + offset;
}
// Get element |index| of an array of objects of length |length| starting at
// |offset| bytes. Return NULL if out of bounds.
const void* GetArrayElement(size_t offset, size_t length, unsigned index) {
const size_t element_offset = offset + index * length;
return GetObject(element_offset, length);
}
// Return a new range which is a subset of this range.
MMappedRange Subrange(const MDLocationDescriptor& location) const {
if (location.rva > length_ ||
location.rva + location.data_size < location.rva ||
location.rva + location.data_size > length_) {
return MMappedRange(NULL, 0);
}
return MMappedRange(data_ + location.rva, location.data_size);
}
const uint8_t* data() const { return data_; }
size_t length() const { return length_; }
private:
const uint8_t* const data_;
const size_t length_;
};
/* Dynamically determines the byte sex of the system. Returns non-zero
* for big-endian machines.
*/
static inline int sex() {
int probe = 1;
return !*(char *)&probe;
}
typedef struct elf_timeval { /* Time value with microsecond resolution */
long tv_sec; /* Seconds */
long tv_usec; /* Microseconds */
} elf_timeval;
typedef struct elf_siginfo { /* Information about signal (unused) */
int32_t si_signo; /* Signal number */
int32_t si_code; /* Extra code */
int32_t si_errno; /* Errno */
} elf_siginfo;
typedef struct prstatus { /* Information about thread; includes CPU reg*/
elf_siginfo pr_info; /* Info associated with signal */
uint16_t pr_cursig; /* Current signal */
unsigned long pr_sigpend; /* Set of pending signals */
unsigned long pr_sighold; /* Set of held signals */
pid_t pr_pid; /* Process ID */
pid_t pr_ppid; /* Parent's process ID */
pid_t pr_pgrp; /* Group ID */
pid_t pr_sid; /* Session ID */
elf_timeval pr_utime; /* User time */
elf_timeval pr_stime; /* System time */
elf_timeval pr_cutime; /* Cumulative user time */
elf_timeval pr_cstime; /* Cumulative system time */
user_regs_struct pr_reg; /* CPU registers */
uint32_t pr_fpvalid; /* True if math co-processor being used */
} prstatus;
typedef struct prpsinfo { /* Information about process */
unsigned char pr_state; /* Numeric process state */
char pr_sname; /* Char for pr_state */
unsigned char pr_zomb; /* Zombie */
signed char pr_nice; /* Nice val */
unsigned long pr_flag; /* Flags */
#if defined(__x86_64__) || defined(__mips__)
uint32_t pr_uid; /* User ID */
uint32_t pr_gid; /* Group ID */
#else
uint16_t pr_uid; /* User ID */
uint16_t pr_gid; /* Group ID */
#endif
pid_t pr_pid; /* Process ID */
pid_t pr_ppid; /* Parent's process ID */
pid_t pr_pgrp; /* Group ID */
pid_t pr_sid; /* Session ID */
char pr_fname[16]; /* Filename of executable */
char pr_psargs[80]; /* Initial part of arg list */
} prpsinfo;
// We parse the minidump file and keep the parsed information in this structure.
struct CrashedProcess {
CrashedProcess()
: crashing_tid(-1),
auxv(NULL),
auxv_length(0) {
memset(&prps, 0, sizeof(prps));
prps.pr_sname = 'R';
}
struct Mapping {
uint64_t start_address, end_address;
};
std::vector<Mapping> mappings;
pid_t crashing_tid;
int fatal_signal;
struct Thread {
pid_t tid;
user_regs_struct regs;
user_fpregs_struct fpregs;
user_fpxregs_struct fpxregs;
uintptr_t stack_addr;
const uint8_t* stack;
size_t stack_length;
};
std::vector<Thread> threads;
const uint8_t* auxv;
size_t auxv_length;
prpsinfo prps;
};
static uint32_t
U32(const uint8_t* data) {
uint32_t v;
memcpy(&v, data, sizeof(v));
return v;
}
static uint16_t
U16(const uint8_t* data) {
uint16_t v;
memcpy(&v, data, sizeof(v));
return v;
}
#if defined(__i386__)
static void
ParseThreadRegisters(CrashedProcess::Thread* thread, MMappedRange range) {
const MDRawContextX86* rawregs =
(const MDRawContextX86*) range.GetObject(0, sizeof(MDRawContextX86));
thread->regs.ebx = rawregs->ebx;
thread->regs.ecx = rawregs->ecx;
thread->regs.edx = rawregs->edx;
thread->regs.esi = rawregs->esi;
thread->regs.edi = rawregs->edi;
thread->regs.ebp = rawregs->ebp;
thread->regs.eax = rawregs->eax;
thread->regs.xds = rawregs->ds;
thread->regs.xes = rawregs->es;
thread->regs.xfs = rawregs->fs;
thread->regs.xgs = rawregs->gs;
thread->regs.orig_eax = rawregs->eax;
thread->regs.eip = rawregs->eip;
thread->regs.xcs = rawregs->cs;
thread->regs.eflags = rawregs->eflags;
thread->regs.esp = rawregs->esp;
thread->regs.xss = rawregs->ss;
thread->fpregs.cwd = rawregs->float_save.control_word;
thread->fpregs.swd = rawregs->float_save.status_word;
thread->fpregs.twd = rawregs->float_save.tag_word;
thread->fpregs.fip = rawregs->float_save.error_offset;
thread->fpregs.fcs = rawregs->float_save.error_selector;
thread->fpregs.foo = rawregs->float_save.data_offset;
thread->fpregs.fos = rawregs->float_save.data_selector;
memcpy(thread->fpregs.st_space, rawregs->float_save.register_area,
10 * 8);
thread->fpxregs.cwd = rawregs->float_save.control_word;
thread->fpxregs.swd = rawregs->float_save.status_word;
thread->fpxregs.twd = rawregs->float_save.tag_word;
thread->fpxregs.fop = U16(rawregs->extended_registers + 6);
thread->fpxregs.fip = U16(rawregs->extended_registers + 8);
thread->fpxregs.fcs = U16(rawregs->extended_registers + 12);
thread->fpxregs.foo = U16(rawregs->extended_registers + 16);
thread->fpxregs.fos = U16(rawregs->extended_registers + 20);
thread->fpxregs.mxcsr = U32(rawregs->extended_registers + 24);
memcpy(thread->fpxregs.st_space, rawregs->extended_registers + 32, 128);
memcpy(thread->fpxregs.xmm_space, rawregs->extended_registers + 160, 128);
}
#else
#error "This code has not been ported to your platform yet"
#endif
static void
ParseThreadList(CrashedProcess* crashinfo, MMappedRange range,
const MMappedRange& full_file) {
const uint32_t num_threads =
*(const uint32_t*) range.GetObject(0, sizeof(uint32_t));
for (unsigned i = 0; i < num_threads; ++i) {
CrashedProcess::Thread thread;
memset(&thread, 0, sizeof(thread));
const MDRawThread* rawthread =
(MDRawThread*) range.GetArrayElement(sizeof(uint32_t),
sizeof(MDRawThread), i);
thread.tid = rawthread->thread_id;
thread.stack_addr = rawthread->stack.start_of_memory_range;
MMappedRange stack_range = full_file.Subrange(rawthread->stack.memory);
thread.stack = stack_range.data();
thread.stack_length = rawthread->stack.memory.data_size;
ParseThreadRegisters(&thread,
full_file.Subrange(rawthread->thread_context));
crashinfo->threads.push_back(thread);
}
}
static void
ParseAuxVector(CrashedProcess* crashinfo, MMappedRange range) {
crashinfo->auxv = range.data();
crashinfo->auxv_length = range.length();
}
static void
ParseCmdLine(CrashedProcess* crashinfo, MMappedRange range) {
const char* cmdline = (const char*) range.data();
for (size_t i = 0; i < range.length(); ++i) {
if (cmdline[i] == 0) {
static const size_t fname_len = sizeof(crashinfo->prps.pr_fname) - 1;
static const size_t args_len = sizeof(crashinfo->prps.pr_psargs) - 1;
memset(crashinfo->prps.pr_fname, 0, fname_len + 1);
memset(crashinfo->prps.pr_psargs, 0, args_len + 1);
const char* binary_name = strrchr(cmdline, '/');
if (binary_name) {
binary_name++;
const unsigned len = strlen(binary_name);
memcpy(crashinfo->prps.pr_fname, binary_name,
len > fname_len ? fname_len : len);
} else {
memcpy(crashinfo->prps.pr_fname, cmdline,
i > fname_len ? fname_len : i);
}
const unsigned len = range.length() > args_len ?
args_len : range.length();
memcpy(crashinfo->prps.pr_psargs, cmdline, len);
for (unsigned i = 0; i < len; ++i) {
if (crashinfo->prps.pr_psargs[i] == 0)
crashinfo->prps.pr_psargs[i] = ' ';
}
}
}
}
static void
ParseExceptionStream(CrashedProcess* crashinfo, MMappedRange range) {
const MDRawExceptionStream* exp =
(MDRawExceptionStream*) range.GetObject(0, sizeof(MDRawExceptionStream));
crashinfo->crashing_tid = exp->thread_id;
crashinfo->fatal_signal = (int) exp->exception_record.exception_code;
}
static bool
WriteThread(const CrashedProcess::Thread& thread, int fatal_signal) {
struct prstatus pr;
memset(&pr, 0, sizeof(pr));
pr.pr_info.si_signo = fatal_signal;
pr.pr_cursig = fatal_signal;
pr.pr_pid = thread.tid;
memcpy(&pr.pr_reg, &thread.regs, sizeof(user_regs_struct));
Nhdr nhdr;
memset(&nhdr, 0, sizeof(nhdr));
nhdr.n_namesz = 5;
nhdr.n_descsz = sizeof(struct prstatus);
nhdr.n_type = NT_PRSTATUS;
if (!writea(1, &nhdr, sizeof(nhdr)) ||
!writea(1, "CORE\0\0\0\0", 8) ||
!writea(1, &pr, sizeof(struct prstatus))) {
return false;
}
nhdr.n_descsz = sizeof(user_fpregs_struct);
nhdr.n_type = NT_FPREGSET;
if (!writea(1, &nhdr, sizeof(nhdr)) ||
!writea(1, "CORE\0\0\0\0", 8) ||
!writea(1, &thread.fpregs, sizeof(user_fpregs_struct))) {
return false;
}
nhdr.n_descsz = sizeof(user_fpxregs_struct);
nhdr.n_type = NT_PRXFPREG;
if (!writea(1, &nhdr, sizeof(nhdr)) ||
!writea(1, "LINUX\0\0\0", 8) ||
!writea(1, &thread.fpxregs, sizeof(user_fpxregs_struct))) {
return false;
}
return true;
}
static void
ParseModuleStream(CrashedProcess* crashinfo, MMappedRange range) {
const uint32_t num_mappings =
*(const uint32_t*) range.GetObject(0, sizeof(uint32_t));
for (unsigned i = 0; i < num_mappings; ++i) {
CrashedProcess::Mapping mapping;
const MDRawModule* rawmodule =
(MDRawModule*) range.GetArrayElement(sizeof(uint32_t),
sizeof(MDRawModule), i);
mapping.start_address = rawmodule->base_of_image;
mapping.end_address = rawmodule->size_of_image + rawmodule->base_of_image;
crashinfo->mappings.push_back(mapping);
}
}
int
main(int argc, char** argv) {
if (argc != 2)
return usage(argv[0]);
const int fd = open(argv[1], O_RDONLY);
if (fd < 0)
return usage(argv[0]);
struct stat st;
fstat(fd, &st);
const void* bytes = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
close(fd);
if (bytes == MAP_FAILED) {
perror("Failed to mmap dump file");
return 1;
}
MMappedRange dump(bytes, st.st_size);
const MDRawHeader* header =
(const MDRawHeader*) dump.GetObject(0, sizeof(MDRawHeader));
CrashedProcess crashinfo;
for (unsigned i = 0; i < header->stream_count; ++i) {
const MDRawDirectory* dirent =
(const MDRawDirectory*) dump.GetArrayElement(
header->stream_directory_rva, sizeof(MDRawDirectory), i);
switch (dirent->stream_type) {
case MD_THREAD_LIST_STREAM:
ParseThreadList(&crashinfo, dump.Subrange(dirent->location), dump);
break;
case MD_LINUX_AUXV:
ParseAuxVector(&crashinfo, dump.Subrange(dirent->location));
break;
case MD_LINUX_CMD_LINE:
ParseCmdLine(&crashinfo, dump.Subrange(dirent->location));
break;
case MD_EXCEPTION_STREAM:
ParseExceptionStream(&crashinfo, dump.Subrange(dirent->location));
break;
case MD_MODULE_LIST_STREAM:
ParseModuleStream(&crashinfo, dump.Subrange(dirent->location));
default:
fprintf(stderr, "Skipping %x\n", dirent->stream_type);
}
}
// Write the ELF header. The file will look like:
// ELF header
// Phdr for the PT_NOTE
// Phdr for each of the thread stacks
// PT_NOTE
// each of the thread stacks
Ehdr ehdr;
memset(&ehdr, 0, sizeof(Ehdr));
ehdr.e_ident[0] = ELFMAG0;
ehdr.e_ident[1] = ELFMAG1;
ehdr.e_ident[2] = ELFMAG2;
ehdr.e_ident[3] = ELFMAG3;
ehdr.e_ident[4] = ELF_CLASS;
ehdr.e_ident[5] = sex() ? ELFDATA2MSB : ELFDATA2LSB;
ehdr.e_ident[6] = EV_CURRENT;
ehdr.e_type = ET_CORE;
ehdr.e_machine = ELF_ARCH;
ehdr.e_version = EV_CURRENT;
ehdr.e_phoff = sizeof(Ehdr);
ehdr.e_ehsize = sizeof(Ehdr);
ehdr.e_phentsize= sizeof(Phdr);
ehdr.e_phnum = 1 + crashinfo.threads.size() + crashinfo.mappings.size();
ehdr.e_shentsize= sizeof(Shdr);
if (!writea(1, &ehdr, sizeof(Ehdr)))
return 1;
size_t offset = sizeof(Ehdr) +
(1 + crashinfo.threads.size() +
crashinfo.mappings.size()) * sizeof(Phdr);
size_t filesz = sizeof(Nhdr) + 8 + sizeof(prpsinfo) +
// sizeof(Nhdr) + 8 + sizeof(user) +
sizeof(Nhdr) + 8 + crashinfo.auxv_length +
crashinfo.threads.size() * (
(sizeof(Nhdr) + 8 + sizeof(prstatus)) +
sizeof(Nhdr) + 8 + sizeof(user_fpregs_struct) +
sizeof(Nhdr) + 8 + sizeof(user_fpxregs_struct));
Phdr phdr;
memset(&phdr, 0, sizeof(Phdr));
phdr.p_type = PT_NOTE;
phdr.p_offset = offset;
phdr.p_filesz = filesz;
if (!writea(1, &phdr, sizeof(phdr)))
return 1;
phdr.p_type = PT_LOAD;
phdr.p_align = getpagesize();
size_t note_align = phdr.p_align - ((offset+filesz) % phdr.p_align);
if (note_align == phdr.p_align)
note_align = 0;
offset += note_align;
for (unsigned i = 0; i < crashinfo.threads.size(); ++i) {
const CrashedProcess::Thread& thread = crashinfo.threads[i];
offset += filesz;
filesz = thread.stack_length;
phdr.p_offset = offset;
phdr.p_vaddr = thread.stack_addr;
phdr.p_filesz = phdr.p_memsz = filesz;
phdr.p_flags = PF_R | PF_W;
if (!writea(1, &phdr, sizeof(phdr)))
return 1;
}
for (unsigned i = 0; i < crashinfo.mappings.size(); ++i) {
const CrashedProcess::Mapping& mapping = crashinfo.mappings[i];
phdr.p_offset = 0;
phdr.p_vaddr = mapping.start_address;
phdr.p_filesz = 0;
phdr.p_flags = PF_R;
phdr.p_memsz = mapping.end_address - mapping.start_address;
if (!writea(1, &phdr, sizeof(phdr)))
return 1;
}
Nhdr nhdr;
memset(&nhdr, 0, sizeof(nhdr));
nhdr.n_namesz = 5;
nhdr.n_descsz = sizeof(prpsinfo);
nhdr.n_type = NT_PRPSINFO;
if (!writea(1, &nhdr, sizeof(nhdr)) ||
!writea(1, "CORE\0\0\0\0", 8) ||
!writea(1, &crashinfo.prps, sizeof(prpsinfo))) {
return 1;
}
nhdr.n_descsz = crashinfo.auxv_length;
nhdr.n_type = NT_AUXV;
if (!writea(1, &nhdr, sizeof(nhdr)) ||
!writea(1, "CORE\0\0\0\0", 8) ||
!writea(1, &crashinfo.auxv, crashinfo.auxv_length)) {
return 1;
}
for (unsigned i = 0; i < crashinfo.threads.size(); ++i) {
if (crashinfo.threads[i].tid == crashinfo.crashing_tid) {
WriteThread(crashinfo.threads[i], crashinfo.fatal_signal);
break;
}
}
for (unsigned i = 0; i < crashinfo.threads.size(); ++i) {
if (crashinfo.threads[i].tid != crashinfo.crashing_tid)
WriteThread(crashinfo.threads[i], 0);
}
if (note_align) {
char scratch[note_align];
memset(scratch, 0, sizeof(scratch));
if (!writea(1, scratch, sizeof(scratch)))
return 1;
}
for (unsigned i = 0; i < crashinfo.threads.size(); ++i) {
const CrashedProcess::Thread& thread = crashinfo.threads[i];
if (!writea(1, thread.stack, thread.stack_length))
return 1;
}
munmap(const_cast<void*>(bytes), st.st_size);
return 0;
}

View File

@@ -130,9 +130,9 @@
9BE650AF0B52FE3000611104 /* macho_id.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = macho_id.h; path = ../../../common/mac/macho_id.h; sourceTree = SOURCE_ROOT; };
9BE650B00B52FE3000611104 /* macho_walker.cc */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = macho_walker.cc; path = ../../../common/mac/macho_walker.cc; sourceTree = SOURCE_ROOT; };
9BE650B10B52FE3000611104 /* macho_walker.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = macho_walker.h; path = ../../../common/mac/macho_walker.h; sourceTree = SOURCE_ROOT; };
F9C7ECE20E8ABCA600E953AD /* bytereader.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = bytereader.cc; path = ../../../common/mac/dwarf/bytereader.cc; sourceTree = SOURCE_ROOT; };
F9C7ECE30E8ABCA600E953AD /* dwarf2reader.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = dwarf2reader.cc; path = ../../../common/mac/dwarf/dwarf2reader.cc; sourceTree = SOURCE_ROOT; };
F9C7ECE40E8ABCA600E953AD /* functioninfo.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = functioninfo.cc; path = ../../../common/mac/dwarf/functioninfo.cc; sourceTree = SOURCE_ROOT; };
F9C7ECE20E8ABCA600E953AD /* bytereader.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = bytereader.cc; path = ../../../common/dwarf/bytereader.cc; sourceTree = SOURCE_ROOT; };
F9C7ECE30E8ABCA600E953AD /* dwarf2reader.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = dwarf2reader.cc; path = ../../../common/dwarf/dwarf2reader.cc; sourceTree = SOURCE_ROOT; };
F9C7ECE40E8ABCA600E953AD /* functioninfo.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = functioninfo.cc; path = ../../../common/dwarf/functioninfo.cc; sourceTree = SOURCE_ROOT; };
FD6625C40CF4D438004AC844 /* stackwalker_amd64.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = stackwalker_amd64.cc; path = ../../../processor/stackwalker_amd64.cc; sourceTree = SOURCE_ROOT; };
FD6625C50CF4D438004AC844 /* stackwalker_amd64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = stackwalker_amd64.h; path = ../../../processor/stackwalker_amd64.h; sourceTree = SOURCE_ROOT; };
FD8EDEAC0CADDAD400A5EDF1 /* stackwalker_sparc.cc */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = stackwalker_sparc.cc; path = ../../../processor/stackwalker_sparc.cc; sourceTree = SOURCE_ROOT; };

View File

@@ -45,15 +45,15 @@
9BE650440B52F6D800611104 /* macho_id.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = macho_id.h; path = ../../../common/mac/macho_id.h; sourceTree = SOURCE_ROOT; };
9BE650450B52F6D800611104 /* macho_walker.cc */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = macho_walker.cc; path = ../../../common/mac/macho_walker.cc; sourceTree = SOURCE_ROOT; };
9BE650460B52F6D800611104 /* macho_walker.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = macho_walker.h; path = ../../../common/mac/macho_walker.h; sourceTree = SOURCE_ROOT; };
F95B422B0E0E22D100DBDE83 /* bytereader-inl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "bytereader-inl.h"; path = "../../../common/mac/dwarf/bytereader-inl.h"; sourceTree = SOURCE_ROOT; };
F95B422C0E0E22D100DBDE83 /* bytereader.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = bytereader.cc; path = ../../../common/mac/dwarf/bytereader.cc; sourceTree = SOURCE_ROOT; };
F95B422D0E0E22D100DBDE83 /* bytereader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = bytereader.h; path = ../../../common/mac/dwarf/bytereader.h; sourceTree = SOURCE_ROOT; };
F95B422E0E0E22D100DBDE83 /* dwarf2enums.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = dwarf2enums.h; path = ../../../common/mac/dwarf/dwarf2enums.h; sourceTree = SOURCE_ROOT; };
F95B422F0E0E22D100DBDE83 /* dwarf2reader.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = dwarf2reader.cc; path = ../../../common/mac/dwarf/dwarf2reader.cc; sourceTree = SOURCE_ROOT; };
F95B42300E0E22D100DBDE83 /* dwarf2reader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = dwarf2reader.h; path = ../../../common/mac/dwarf/dwarf2reader.h; sourceTree = SOURCE_ROOT; };
F95B42310E0E22D100DBDE83 /* line_state_machine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = line_state_machine.h; path = ../../../common/mac/dwarf/line_state_machine.h; sourceTree = SOURCE_ROOT; };
F9C7ED420E8AD93000E953AD /* functioninfo.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = functioninfo.cc; path = ../../../common/mac/dwarf/functioninfo.cc; sourceTree = SOURCE_ROOT; };
F9F5344D0E7C902C0012363F /* functioninfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = functioninfo.h; path = ../../../common/mac/dwarf/functioninfo.h; sourceTree = SOURCE_ROOT; };
F95B422B0E0E22D100DBDE83 /* bytereader-inl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "bytereader-inl.h"; path = "../../../common/dwarf/bytereader-inl.h"; sourceTree = SOURCE_ROOT; };
F95B422C0E0E22D100DBDE83 /* bytereader.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = bytereader.cc; path = ../../../common/dwarf/bytereader.cc; sourceTree = SOURCE_ROOT; };
F95B422D0E0E22D100DBDE83 /* bytereader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = bytereader.h; path = ../../../common/dwarf/bytereader.h; sourceTree = SOURCE_ROOT; };
F95B422E0E0E22D100DBDE83 /* dwarf2enums.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = dwarf2enums.h; path = ../../../common/dwarf/dwarf2enums.h; sourceTree = SOURCE_ROOT; };
F95B422F0E0E22D100DBDE83 /* dwarf2reader.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = dwarf2reader.cc; path = ../../../common/dwarf/dwarf2reader.cc; sourceTree = SOURCE_ROOT; };
F95B42300E0E22D100DBDE83 /* dwarf2reader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = dwarf2reader.h; path = ../../../common/dwarf/dwarf2reader.h; sourceTree = SOURCE_ROOT; };
F95B42310E0E22D100DBDE83 /* line_state_machine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = line_state_machine.h; path = ../../../common/dwarf/line_state_machine.h; sourceTree = SOURCE_ROOT; };
F9C7ED420E8AD93000E953AD /* functioninfo.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = functioninfo.cc; path = ../../../common/dwarf/functioninfo.cc; sourceTree = SOURCE_ROOT; };
F9F5344D0E7C902C0012363F /* functioninfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = functioninfo.h; path = ../../../common/dwarf/functioninfo.h; sourceTree = SOURCE_ROOT; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */