Add breakpad support for Mac

This provides support for compiling the breakpad client into PhantomJS,
and generifies that Linux packaging scripts so that they also apply to
OS X and automate the symbol generation.

Building the Breakpad tool programs seems to be less than
straightforward on OS X, and documentation is poor. We have managed to
produce tools/dump-syms-mac.pro which allows building the dump_syms
program for dumping the debugging symbols. This needed a couple of
modifications to breakpad in order to compile successfully.

We have run out of time to work on making the minidump_stackwalk program
build. However, this is solely a developer tool and so it can wait until
after the 1.6 release before we complete this work.

Testing is welcome!

https://code.google.com/p/phantomjs/issues/detail?id=576
1.6
Tom Stuart & Jon Leighton 2012-06-18 22:35:38 +01:00 committed by Jon Leighton
parent c3c65df12d
commit 5fa0202f29
11 changed files with 520 additions and 114 deletions

1
.gitignore vendored
View File

@ -22,3 +22,4 @@ debian/*/
# ignore ctags
/tags
tools/dump_syms.app/

View File

@ -51,4 +51,10 @@ again to ensure a clean state. (Or SSH in and do a git clean.)
Packaging for OS X
------------------
TODO
Run `deploy/build-and-package.sh`. That's it.
However, if you have previously built the sources in release mode, you
should clean your tree to make sure all the debugging symbols gets
compiled:
$ make clean && cd src/qt && make clean && cd ../..

View File

@ -2,23 +2,43 @@
cd `dirname $0`/..
echo "Building Qt and PhantomJS in debug mode. If you have previously" \
"built in release mode, you should run:"
echo
echo " $ make clean && cd src/qt && make clean && cd ../.."
echo
# Build the project
./build.sh --qt-config "-debug -webkit-debug" || exit 1
rm deploy/*.tar.bz2
./deploy/package-linux-dynamic.sh || exit 1
# Package the release tarball
rm deploy/*.tar.bz2 2>/dev/null
./deploy/package.sh || exit 1
pushd src/breakpad
./configure && make || exit 1
popd
# Build the dump_syms program for dumping breakpad debugging symbols
if [[ $OSTYPE = darwin* ]]; then
pushd tools
../src/qt/bin/qmake dump-syms-mac.pro
popd
else
pushd src/breakpad
./configure && make || exit 1
popd
fi
# Dump and package the breakpad debugging symbols...
./tools/dump-symbols.sh
# The minidump_stackwalk program is architecture-specific, so copy the
# binary for later use. This means that e.g. a developer on x86_64 can
# analyse a crash dump produced by a i686 user.
cp src/breakpad/src/processor/minidump_stackwalk symbols/
#
# We don't yet have a process for building minidump_stackwalk on OS X
if [[ $OSTYPE != darwin* ]]; then
cp src/breakpad/src/processor/minidump_stackwalk symbols/
read -r -d '' README <<EOT
read -r -d '' README <<EOT
These are symbols files that can be used to analyse a crash dump
produced by the corresponding binary. To generate a crash report,
run:
@ -26,7 +46,8 @@ run:
./minidump_stackwalk /path/to/crash.dmp .
EOT
echo "$README" > symbols/README
echo "$README" > symbols/README
fi
tar -cjf $(ls deploy/*.bz2 | sed 's/\.tar\.bz2/-symbols.tar.bz2/') symbols/

View File

@ -1,92 +0,0 @@
#!/bin/bash
#
# usage: just run this script (after having run build.sh)
# and deploy the created tarball to your target machine.
#
# It creates a phantomjs-$version folder and copies the binary,
# example, license etc. together with all shared library dependencies
# to that folder. Furthermore brandelf is used to make the lib
# and binary compatible with older unix/linux machines that don't
# know the new Linux ELF ABI.
#
cd $(dirname $0)
if [[ ! -f ../bin/phantomjs ]]; then
echo "phantomjs was not built yet, please run build.sh first"
exit 1
fi
# get version
version=$(../bin/phantomjs --version | sed 's/ /-/' | sed 's/[()]//g')
echo "creating quasi-static deployable phantomjs $version"
if [[ ! -f brandelf ]]; then
echo
echo "brandelf executable not found in current dir"
echo -n "compiling it now..."
g++ brandelf.c -o brandelf || exit 1
echo "done"
fi
src=..
dest="phantomjs-$version-linux-$(uname -m)-dynamic"
rm -Rf $dest{.tar.bz2,} &> /dev/null
mkdir -p $dest/bin $dest/lib
echo
echo -n "copying files..."
cp $src/bin/phantomjs $dest/bin
cp -r $src/{ChangeLog,examples,LICENSE.BSD,README.md} $dest/
echo "done"
echo
echo -n "copying shared libs..."
libld=
for l in $(ldd $dest/bin/phantomjs | egrep -o "/[^ ]+ "); do
if [[ "$l" != "" ]]; then
ll=$(basename $l)
cp $l $dest/lib/$ll
# ensure OS ABI compatibility
./brandelf -t SVR4 $dest/lib/$ll
if [[ "$l" == *"ld-linux"* ]]; then
libld=$ll
fi
fi
done
echo "done"
echo
# strip to reduce file size
echo -n "stripping binary and libs..."
strip -s $dest/lib/* $dest/bin/*
echo "done"
echo
echo -n "writing run script..."
# write run scripts
mv $dest/bin/phantomjs $dest/bin/phantomjs.bin
run=$dest/bin/phantomjs
echo '#!/bin/sh' >> $run
echo 'path=$(dirname $(dirname $(readlink -f $0)))' >> $run
echo 'export LD_LIBRARY_PATH=$path/lib' >> $run
echo 'exec $path/lib/'$libld' $path/bin/phantomjs.bin $@' >> $run
chmod +x $run
echo "done"
echo
echo -n "creating tarball..."
tar -cjf $dest{.tar.bz2,}
echo "done"
echo
echo "you can now deploy $dest or $dest.tar.bz2"

104
deploy/package.sh Executable file
View File

@ -0,0 +1,104 @@
#!/bin/bash
#
# usage: just run this script (after having run build.sh)
# and deploy the created tarball to your target machine.
#
# It creates a phantomjs-$version folder and copies the binary,
# example, license etc. together with all shared library dependencies
# to that folder. Furthermore brandelf is used to make the lib
# and binary compatible with older unix/linux machines that don't
# know the new Linux ELF ABI.
#
cd $(dirname $0)
if [[ ! -f ../bin/phantomjs ]]; then
echo "phantomjs was not built yet, please run build.sh first"
exit 1
fi
version=$(../bin/phantomjs --version | sed 's/ /-/' | sed 's/[()]//g')
src=..
echo "packaging phantomjs $version"
if [[ $OSTYPE = darwin* ]]; then
dest="phantomjs-$version-macosx-static"
else
if [[ ! -f brandelf ]]; then
echo
echo "brandelf executable not found in current dir"
echo -n "compiling it now..."
g++ brandelf.c -o brandelf || exit 1
echo "done"
fi
dest="phantomjs-$version-linux-$(uname -m)-dynamic"
fi
rm -Rf $dest{.tar.bz2,} &> /dev/null
mkdir -p $dest/bin
if [[ $OSTYPE != darwin* ]]; then
mkdir -p $dest/lib
fi
echo
echo -n "copying files..."
cp $src/bin/phantomjs $dest/bin
cp -r $src/{ChangeLog,examples,LICENSE.BSD,README.md} $dest/
echo "done"
echo
if [[ $OSTYPE != darwin* ]]; then
echo -n "copying shared libs..."
libld=
for l in $(ldd $dest/bin/phantomjs | egrep -o "/[^ ]+ "); do
if [[ "$l" != "" ]]; then
ll=$(basename $l)
cp $l $dest/lib/$ll
# ensure OS ABI compatibility
./brandelf -t SVR4 $dest/lib/$ll
if [[ "$l" == *"ld-linux"* ]]; then
libld=$ll
fi
fi
done
echo "done"
echo
fi
# strip to reduce file size
echo -n "stripping binary and libs..."
if [[ $OSTYPE = darwin* ]]; then
strip -x $dest/bin/*
else
strip -s $dest/lib/* $dest/bin/*
fi
echo "done"
echo
if [[ $OSTYPE != darwin* ]]; then
echo -n "writing run script..."
# write run scripts
mv $dest/bin/phantomjs $dest/bin/phantomjs.bin
run=$dest/bin/phantomjs
echo '#!/bin/sh' >> $run
echo 'path=$(dirname $(dirname $(readlink -f $0)))' >> $run
echo 'export LD_LIBRARY_PATH=$path/lib' >> $run
echo 'exec $path/lib/'$libld' $path/bin/phantomjs.bin $@' >> $run
chmod +x $run
echo "done"
echo
fi
echo -n "creating tarball..."
tar -cjf $dest{.tar.bz2,}
echo "done"
echo
echo "you can now deploy $dest or $dest.tar.bz2"

View File

@ -45,9 +45,6 @@
#include "dynamic_images.h"
#include "mach_vm_compat.h"
#if !TARGET_OS_IPHONE && (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7)
#define HAS_PPC_SUPPORT
#endif
#if defined(__arm__)
#define HAS_ARM_SUPPORT
#elif defined(__i386__) || defined(__x86_64__)

View File

@ -0,0 +1,312 @@
// Copyright (c) 2010 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.
// Original author: Jim Blandy <jimb@mozilla.com> <jimb@red-bean.com>
// This file implements the google_breakpad::StabsReader class.
// See stabs_reader.h.
#include "common/stabs_reader.h"
#include <assert.h>
#include <stab.h>
#define N_UNDF 0x0 /* undefined */
#include <string.h>
using std::vector;
namespace google_breakpad {
StabsReader::EntryIterator::EntryIterator(const ByteBuffer *buffer,
bool big_endian, size_t value_size)
: value_size_(value_size), cursor_(buffer, big_endian) {
// Actually, we could handle weird sizes just fine, but they're
// probably mistakes --- expressed in bits, say.
assert(value_size == 4 || value_size == 8);
entry_.index = 0;
Fetch();
}
void StabsReader::EntryIterator::Fetch() {
cursor_
.Read(4, false, &entry_.name_offset)
.Read(1, false, &entry_.type)
.Read(1, false, &entry_.other)
.Read(2, false, &entry_.descriptor)
.Read(value_size_, false, &entry_.value);
entry_.at_end = !cursor_;
}
StabsReader::StabsReader(const uint8_t *stab, size_t stab_size,
const uint8_t *stabstr, size_t stabstr_size,
bool big_endian, size_t value_size, bool unitized,
StabsHandler *handler)
: entries_(stab, stab_size),
strings_(stabstr, stabstr_size),
iterator_(&entries_, big_endian, value_size),
unitized_(unitized),
handler_(handler),
string_offset_(0),
next_cu_string_offset_(0),
current_source_file_(NULL) { }
const char *StabsReader::SymbolString() {
ptrdiff_t offset = string_offset_ + iterator_->name_offset;
if (offset < 0 || (size_t) offset >= strings_.Size()) {
handler_->Warning("symbol %d: name offset outside the string section\n",
iterator_->index);
// Return our null string, to keep our promise about all names being
// taken from the string section.
offset = 0;
}
return reinterpret_cast<const char *>(strings_.start + offset);
}
bool StabsReader::Process() {
while (!iterator_->at_end) {
if (iterator_->type == N_SO) {
if (! ProcessCompilationUnit())
return false;
} else if (iterator_->type == N_UNDF && unitized_) {
// In unitized STABS (including Linux STABS, and pretty much anything
// else that puts STABS data in sections), at the head of each
// compilation unit's entries there is an N_UNDF stab giving the
// number of symbols in the compilation unit, and the number of bytes
// that compilation unit's strings take up in the .stabstr section.
// Each CU's strings are separate; the n_strx values are offsets
// within the current CU's portion of the .stabstr section.
//
// As an optimization, the GNU linker combines all the
// compilation units into one, with a single N_UNDF at the
// beginning. However, other linkers, like Gold, do not perform
// this optimization.
string_offset_ = next_cu_string_offset_;
next_cu_string_offset_ = iterator_->value;
++iterator_;
}
#if defined(HAVE_MACH_O_NLIST_H)
// Export symbols in Mach-O binaries look like this.
// This is necessary in order to be able to dump symbols
// from OS X system libraries.
else if ((iterator_->type & N_STAB) == 0 &&
(iterator_->type & N_TYPE) == N_SECT) {
ProcessExtern();
}
#endif
else {
++iterator_;
}
}
return true;
}
bool StabsReader::ProcessCompilationUnit() {
assert(!iterator_->at_end && iterator_->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;
++iterator_;
}
}
// We expect to see an N_SO entry with a filename next, indicating
// the start of the compilation unit.
{
if (iterator_->at_end || iterator_->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.
++iterator_;
return true;
}
current_source_file_ = name;
}
if (! handler_->StartCompilationUnit(current_source_file_,
iterator_->value,
build_directory))
return false;
++iterator_;
// 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 (!iterator_->at_end && iterator_->type != N_SO) {
if (iterator_->type == N_FUN) {
if (! ProcessFunction())
return false;
} else if (iterator_->type == N_SLINE) {
// Mac OS X STABS place SLINE records before functions.
Line line;
// The value of an N_SLINE entry that appears outside a function is
// the absolute address of the line.
line.address = iterator_->value;
line.filename = current_source_file_;
// 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.
line.number = (uint16_t) iterator_->descriptor;
queued_lines_.push_back(line);
++iterator_;
} else if (iterator_->type == N_SOL) {
current_source_file_ = SymbolString();
++iterator_;
} else {
// Ignore anything else.
++iterator_;
}
}
// An N_SO with an empty name indicates the end of the compilation
// unit. Default to zero.
uint64_t ending_address = 0;
if (!iterator_->at_end) {
assert(iterator_->type == N_SO);
const char *name = SymbolString();
if (name[0] == '\0') {
ending_address = iterator_->value;
++iterator_;
}
}
if (! handler_->EndCompilationUnit(ending_address))
return false;
queued_lines_.clear();
return true;
}
bool StabsReader::ProcessFunction() {
assert(!iterator_->at_end && iterator_->type == N_FUN);
uint64_t function_address = iterator_->value;
// 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;
++iterator_;
// If there were any SLINE records given before the function, report them now.
for (vector<Line>::const_iterator it = queued_lines_.begin();
it != queued_lines_.end(); it++) {
if (!handler_->Line(it->address, it->filename, it->number))
return false;
}
queued_lines_.clear();
while (!iterator_->at_end) {
if (iterator_->type == N_SO || iterator_->type == N_FUN)
break;
else if (iterator_->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 + iterator_->value;
// 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 = iterator_->descriptor;
if (! handler_->Line(line_address, current_source_file_, line_number))
return false;
++iterator_;
} else if (iterator_->type == N_SOL) {
current_source_file_ = SymbolString();
++iterator_;
} else
// Ignore anything else.
++iterator_;
}
// We've reached the end of the function. See if we can figure out its
// ending address.
uint64_t ending_address = 0;
if (!iterator_->at_end) {
assert(iterator_->type == N_SO || iterator_->type == N_FUN);
if (iterator_->type == N_FUN) {
const char *symbol_name = SymbolString();
if (symbol_name[0] == '\0') {
// An N_FUN entry with no name is a terminator for this function;
// its value is the function's size.
ending_address = function_address + iterator_->value;
++iterator_;
} else {
// An N_FUN entry with a name is the next function, and we can take
// its value as our ending address. Don't advance the iterator, as
// we'll use this symbol to start the next function as well.
ending_address = iterator_->value;
}
} else {
// An N_SO entry could be an end-of-compilation-unit marker, or the
// start of the next compilation unit, but in either case, its value
// is our ending address. We don't advance the iterator;
// ProcessCompilationUnit will decide what to do with this symbol.
ending_address = iterator_->value;
}
}
if (! handler_->EndFunction(ending_address))
return false;
return true;
}
bool StabsReader::ProcessExtern() {
#if defined(HAVE_MACH_O_NLIST_H)
assert(!iterator_->at_end &&
(iterator_->type & N_STAB) == 0 &&
(iterator_->type & N_TYPE) == N_SECT);
#endif
// TODO(mark): only do symbols in the text section?
if (!handler_->Extern(SymbolString(), iterator_->value))
return false;
++iterator_;
return true;
}
} // namespace google_breakpad

View File

@ -35,6 +35,9 @@
#ifdef Q_OS_LINUX
#include "client/linux/handler/exception_handler.h"
#endif
#ifdef Q_OS_MAC
#include "client/mac/handler/exception_handler.h"
#endif
#include <QApplication>
@ -47,6 +50,9 @@ int main(int argc, char** argv, const char** envp)
#ifdef Q_OS_LINUX
google_breakpad::ExceptionHandler eh("/tmp", NULL, Utils::exceptionHandler, NULL, true);
#endif
#ifdef Q_OS_MAC
google_breakpad::ExceptionHandler eh("/tmp", NULL, Utils::exceptionHandler, NULL, true, NULL);
#endif
QApplication app(argc, argv);
Phantom phantom;

View File

@ -55,25 +55,44 @@ include(gif/gif.pri)
include(mongoose/mongoose.pri)
include(linenoise/linenoise.pri)
linux* {
linux*|mac {
INCLUDEPATH += breakpad/src
SOURCES += breakpad/src/client/minidump_file_writer.cc \
breakpad/src/common/convert_UTF.c \
breakpad/src/common/md5.cc \
breakpad/src/common/string_conversion.cc
}
linux* {
SOURCES += breakpad/src/client/linux/crash_generation/crash_generation_client.cc \
breakpad/src/client/linux/handler/exception_handler.cc \
breakpad/src/client/linux/log/log.cc \
breakpad/src/client/linux/minidump_writer/linux_dumper.cc \
breakpad/src/client/linux/minidump_writer/linux_ptrace_dumper.cc \
breakpad/src/client/linux/minidump_writer/minidump_writer.cc \
breakpad/src/client/minidump_file_writer.cc \
breakpad/src/common/convert_UTF.c \
breakpad/src/common/md5.cc \
breakpad/src/common/string_conversion.cc \
breakpad/src/common/linux/file_id.cc \
breakpad/src/common/linux/guid_creator.cc \
breakpad/src/common/linux/memory_mapped_file.cc \
breakpad/src/common/linux/safe_readlink.cc
}
mac {
SOURCES += breakpad/src/client/mac/crash_generation/crash_generation_client.cc \
breakpad/src/client/mac/handler/exception_handler.cc \
breakpad/src/client/mac/handler/minidump_generator.cc \
breakpad/src/client/mac/handler/dynamic_images.cc \
breakpad/src/client/mac/handler/breakpad_nlist_64.cc \
breakpad/src/common/mac/bootstrap_compat.cc \
breakpad/src/common/mac/file_id.cc \
breakpad/src/common/mac/macho_id.cc \
breakpad/src/common/mac/macho_utilities.cc \
breakpad/src/common/mac/macho_walker.cc \
breakpad/src/common/mac/string_utilities.cc
OBJECTIVE_SOURCES += breakpad/src/common/mac/MachIPC.mm
}
win32: RC_FILE = phantomjs_win.rc
os2: RC_FILE = phantomjs_os2.rc

View File

@ -16,14 +16,23 @@ rm -r symbols/*
files=""
files+="bin/phantomjs "
files+="src/qt/lib/libQtCore.so.4.8.0 "
files+="src/qt/lib/libQtWebKit.so.4.9.0 "
files+="src/qt/lib/libQtGui.so.4.8.0 "
files+="src/qt/lib/libQtNetwork.so.4.8.0"
if [[ $OSTYPE = darwin* ]]; then
# To compile this program, run ../src/qt/bin/qmake dump-syms-mac.pro && make from tools/
dump_syms="tools/dump_syms.app/Contents/MacOS/dump_syms"
else
files+="src/qt/lib/libQtCore.so.4.8.0 "
files+="src/qt/lib/libQtWebKit.so.4.9.0 "
files+="src/qt/lib/libQtGui.so.4.8.0 "
files+="src/qt/lib/libQtNetwork.so.4.8.0"
# To compile this program, run ./configure && make from src/breakpad/
dump_syms="src/breakpad/src/tools/linux/dump_syms/dump_syms"
fi
for file in $files; do
name=`basename $file`
src/breakpad/src/tools/linux/dump_syms/dump_syms $file > $name.sym
$dump_syms $file > $name.sym
dir=symbols/$name/`head -n1 $name.sym | cut -d ' ' -f 4`
mkdir -p $dir
mv $name.sym $dir

23
tools/dump-syms-mac.pro Normal file
View File

@ -0,0 +1,23 @@
INCLUDEPATH += ../src/breakpad/src
OBJECTIVE_SOURCES += ../src/breakpad/src/tools/mac/dump_syms/dump_syms_tool.mm \
../src/breakpad/src/common/mac/dump_syms.mm
SOURCES += ../src/breakpad/src/common/module.cc \
../src/breakpad/src/common/dwarf/dwarf2diehandler.cc \
../src/breakpad/src/common/dwarf/bytereader.cc \
../src/breakpad/src/common/stabs_to_module.cc \
../src/breakpad/src/common/mac/stabs_reader.cc \
../src/breakpad/src/common/dwarf_cu_to_module.cc \
../src/breakpad/src/common/dwarf_cfi_to_module.cc \
../src/breakpad/src/common/dwarf_line_to_module.cc \
../src/breakpad/src/common/language.cc \
../src/breakpad/src/common/md5.cc \
../src/breakpad/src/common/mac/macho_reader.cc \
../src/breakpad/src/common/mac/file_id.cc \
../src/breakpad/src/common/mac/macho_id.cc \
../src/breakpad/src/common/mac/macho_utilities.cc \
../src/breakpad/src/common/mac/macho_walker.cc \
../src/breakpad/src/common/dwarf/dwarf2reader.cc
TARGET = dump_syms