1
0
mirror of https://github.com/tomahawk-player/tomahawk.git synced 2025-09-06 20:20:41 +02:00

Compare commits

...

3 Commits

Author SHA1 Message Date
Dominik Schmidt
c823ab0cdf Fix launching the crash reporter from build dir on OSX 2014-04-15 19:43:23 +02:00
Dominik Schmidt
8ffa6634b8 Fix breakpad on OSX 2014-04-15 19:42:55 +02:00
Dominik Schmidt
b4f05b0831 Update breakpad to make it work with MinGW 2014-04-14 19:23:44 +02:00
1051 changed files with 57563 additions and 329090 deletions

View File

@@ -103,10 +103,16 @@ ENDIF()
# set paths
SET( THIRDPARTY_DIR ${CMAKE_SOURCE_DIR}/thirdparty )
SET( CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" )
SET( CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" )
SET( CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" )
# put executables into bundle dir on on osx
IF(APPLE)
SET( CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/tomahawk.app/Contents/MacOS/" )
ELSE()
SET( CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" )
ENDIF()
# make predefined install dirs available everywhere
INCLUDE( GNUInstallDirs )

View File

@@ -28,7 +28,7 @@
#define LOGFILE TomahawkUtils::appLogDir().filePath( "Tomahawk.log" ).toLocal8Bit()
#define RESPATH ":/data/"
#define PRODUCT_NAME "WaterWolf"
CrashReporter::CrashReporter( const QUrl& url, const QStringList& args )
: m_reply( 0 )
@@ -60,9 +60,7 @@ CrashReporter::CrashReporter( const QUrl& url, const QStringList& args )
m_request = new QNetworkRequest( m_url );
m_dir = args.value( 1 );
m_minidump = m_dir + '/' + args.value( 2 ) + ".dmp";
m_product_name = args.value( 3 );
m_minidump_file_path = args.value( 1 );
//hide until "send report" has been clicked
ui.progressBar->setVisible( false );
@@ -99,17 +97,46 @@ CrashReporter::send()
// socorro expects a 10 digit build id
QRegExp rx( "(\\d+\\.\\d+\\.\\d+).(\\d+)" );
rx.exactMatch( TomahawkUtils::appFriendlyVersion() );
QString const version = rx.cap( 1 );
//QString const version = rx.cap( 1 );
QString const buildId = rx.cap( 2 ).leftJustified( 10, '0' );
// add parameters
typedef QPair<QByteArray, QByteArray> Pair;
QList<Pair> pairs;
pairs << Pair( "BuildID", buildId.toUtf8() )
<< Pair( "ProductName", m_product_name.toUtf8() )
<< Pair( "ProductName", PRODUCT_NAME)
<< Pair( "Version", TomahawkUtils::appFriendlyVersion().toLocal8Bit() )
<< Pair( "Vendor", "Tomahawk" )
<< Pair( "timestamp", QByteArray::number( QDateTime::currentDateTime().toTime_t() ) );
//<< Pair( "Vendor", "Tomahawk" )
//<< Pair( "timestamp", QByteArray::number( QDateTime::currentDateTime().toTime_t() ) )
// << Pair("InstallTime", "1357622062")
// << Pair("Theme", "classic/1.0")
// << Pair("Version", "30")
// << Pair("id", "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}")
// << Pair("Vendor", "Mozilla")
// << Pair("EMCheckCompatibility", "true")
// << Pair("Throttleable", "0")
// << Pair("URL", "http://code.google.com/p/crashme/")
// << Pair("version", "20.0a1")
// << Pair("CrashTime", "1357770042")
// << Pair("ReleaseChannel", "nightly")
// << Pair("submitted_timestamp", "2013-01-09T22:21:18.646733+00:00")
// << Pair("buildid", "20130107030932")
// << Pair("timestamp", "1357770078.646789")
// << Pair("Notes", "OpenGL: NVIDIA Corporation -- GeForce 8600M GT/PCIe/SSE2 -- 3.3.0 NVIDIA 313.09 -- texture_from_pixmap\r\n")
// << Pair("StartupTime", "1357769913")
// << Pair("FramePoisonSize", "4096")
// << Pair("FramePoisonBase", "7ffffffff0dea000")
// << Pair("Add-ons", "%7B972ce4c6-7e08-4474-a285-3208198ce6fd%7D:20.0a1,crashme%40ted.mielczarek.org:0.4")
// << Pair("BuildID", "YYYYMMDDHH")
// << Pair("SecondsSinceLastCrash", "1831736")
// << Pair("ProductName", "WaterWolf")
// << Pair("legacy_processing", "0")
// << Pair("ProductID", "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}")
;
foreach ( Pair const pair, pairs )
{
@@ -119,15 +146,19 @@ CrashReporter::send()
pair.second + "\r\n";
}
// TODO: check if dump file actually exists ...
// add minidump file
body += "--thkboundary\r\n";
body += "Content-Disposition: form-data; name=\"upload_file_minidump\"; filename=\""
+ QFileInfo( m_minidump ).fileName() + "\"\r\n";
+ QFileInfo( m_minidump_file_path ).fileName() + "\"\r\n";
body += "Content-Type: application/octet-stream\r\n";
body += "\r\n";
body += contents( m_minidump );
body += contents( m_minidump_file_path );
body += "\r\n";
// add logfile
body += "--thkboundary\r\n";
body += "Content-Disposition: form-data; name=\"upload_file_tomahawklog\"; filename=\"Tomahawk.log\"\r\n";

View File

@@ -38,9 +38,7 @@ public:
private:
Ui::CrashReporter ui;
QString m_minidump;
QString m_dir;
QString m_product_name;
QString m_minidump_file_path;
QNetworkRequest* m_request;
QNetworkReply* m_reply;
QUrl m_url;

View File

@@ -26,7 +26,7 @@
const char* k_usage =
"Usage:\n"
" CrashReporter <logDir> <dumpFileName> <productName>\n";
" CrashReporter <dumpFilePath>\n";
int main( int argc, char* argv[] )
{
@@ -39,13 +39,13 @@ int main( int argc, char* argv[] )
QApplication app( argc, argv );
TomahawkUtils::installTranslator( &app );
if ( app.arguments().size() != 4 )
if ( app.arguments().size() != 2 )
{
std::cout << k_usage;
return 1;
}
CrashReporter reporter( QUrl( "http://oops.tomahawk-player.org/addreport.php" ), app.arguments() );
CrashReporter reporter( QUrl( "http://crash-reports.tomahawk-player.org/submit" ), app.arguments() );
reporter.show();
return app.exec();

View File

@@ -3,6 +3,8 @@ setup_qt()
enable_testing()
SET( CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/tests" )
include_directories(${CMAKE_CURRENT_LIST_DIR}/../src/tomahawk ${CMAKE_CURRENT_LIST_DIR}/../src/libtomahawk)
include(tomahawk_add_test.cmake)

View File

@@ -162,6 +162,9 @@ SET_TARGET_PROPERTIES(tomahawk_bin
AUTOMOC TRUE
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_BINARY_DIR}/Info.plist"
RUNTIME_OUTPUT_NAME tomahawk
# OSX: MACOSX_BUNDLE messes with paths
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}
)
@@ -184,12 +187,12 @@ TARGET_LINK_LIBRARIES( tomahawk_bin
${TOMAHAWK_WIDGETS_LIBRARIES}
${TOMAHAWK_PLAYDARAPI_LIBRARIES}
${TOMAHAWK_LIBRARIES}
${OS_SPECIFIC_LINK_LIBRARIES}
${QT_LIBRARIES}
${MAC_EXTRA_LIBS}
${ECHONEST_LIBRARIES}
${QJSON_LIBRARIES}
${TAGLIB_LIBRARIES}
${OS_SPECIFIC_LINK_LIBRARIES}
)
IF( APPLE )

View File

@@ -1,5 +1,3 @@
SET( CMAKE_BUILD_TYPE "Release" )
ADD_DEFINITIONS( /DNOMINMAX )
ADD_DEFINITIONS( /DWIN32_LEAN_AND_MEAN )
ADD_DEFINITIONS( -static-libgcc )

View File

@@ -26,6 +26,11 @@
#include <QFileInfo>
#include <string.h>
#ifdef Q_OS_LINUX
#include "client/linux/handler/minidump_descriptor.h"
#endif
#define CRASH_REPORTER_BINARY "tomahawk_crash_reporter"
bool s_active = true;
@@ -35,15 +40,39 @@ bool s_active = true;
static bool
LaunchUploader( const char* dump_dir, const char* minidump_id, void* that, bool succeeded )
#ifdef Q_OS_LINUX
LaunchUploader( const google_breakpad::MinidumpDescriptor& descriptor, void* context, bool succeeded )
{
#else // Q_OS_MAC
LaunchUploader( const char* dump_dir, const char* minidump_id, void* context, bool succeeded)
{
#endif
if ( !succeeded )
{
printf("Could not write crash dump file");
return false;
}
// DON'T USE THE HEAP!!!
// So that indeed means, no QStrings, no qDebug(), no QAnything, seriously!
if ( !succeeded )
return false;
#ifdef Q_OS_LINUX
const char* path = descriptor.path();
#else // Q_OS_MAC
const char* extension = "dmp";
const char* crashReporter = static_cast<BreakPad*>(that)->crashReporter();
char path[4096];
strcpy(path, dump_dir);
strcat(path, "/");
strcat(path, minidump_id);
strcat(path, ".");
strcat(path, extension);
#endif
printf("Dump file was written to: %s\n", path);
const char* crashReporter = static_cast<BreakPad*>(context)->crashReporter();
if ( !s_active || strlen( crashReporter ) == 0 )
return false;
@@ -55,9 +84,7 @@ LaunchUploader( const char* dump_dir, const char* minidump_id, void* that, bool
// we are the fork
execl( crashReporter,
crashReporter,
dump_dir,
minidump_id,
minidump_id,
path,
(char*) 0 );
// execl replaces this process, so no more code will be executed
@@ -72,8 +99,10 @@ LaunchUploader( const char* dump_dir, const char* minidump_id, void* that, bool
BreakPad::BreakPad( const QString& path, bool active )
#ifdef Q_OS_LINUX
: google_breakpad::ExceptionHandler( path.toStdString(), 0, LaunchUploader, this, true )
#if defined Q_OS_LINUX
: google_breakpad::ExceptionHandler( google_breakpad::MinidumpDescriptor(path.toStdString()), NULL, LaunchUploader, this, true, -1 )
#elif defined Q_OS_MAC
: google_breakpad::ExceptionHandler( path.toStdString(), NULL, LaunchUploader, this, true, NULL)
#else
: google_breakpad::ExceptionHandler( path.toStdString(), 0, LaunchUploader, this, true, 0 )
#endif
@@ -117,7 +146,7 @@ LaunchUploader( const wchar_t* dump_dir, const wchar_t* minidump_id, void* that,
// const char* productName = static_cast<BreakPad*>(that)->productName();s
// convert productName to widechars, which sadly means the product name must be Latin1
wchar_t product_name[ 256 ] = L"tomahawk";;
wchar_t product_name[ 256 ] = L"tomahawk";
// char* out = (char*)product_name;
// const char* in = productName - 1;
@@ -130,11 +159,10 @@ LaunchUploader( const wchar_t* dump_dir, const wchar_t* minidump_id, void* that,
wchar_t command[MAX_PATH * 3 + 6];
wcscpy( command, CRASH_REPORTER_BINARY L" \"" );
wcscat( command, dump_dir );
wcscat( command, L"\" \"" );
wcscat( command, L"/" );
wcscat( command, minidump_id );
wcscat( command, L"\" \"" );
wcscat( command, product_name );
wcscat( command, L"\"" );
wcscat( command, L".dmp\"" );
STARTUPINFO si;
PROCESS_INFORMATION pi;

View File

@@ -24,8 +24,6 @@ IF(UNIX)
client/mac/handler/exception_handler.cc
client/mac/handler/minidump_generator.cc
client/mac/handler/protected_memory_allocator.cc
# client/mac/Framework/Breakpad.mm
# client/mac/Framework/OnDemandServer.mm
common/mac/file_id.cc
common/mac/macho_id.cc
common/mac/macho_reader.cc
@@ -35,29 +33,32 @@ IF(UNIX)
common/md5.cc
common/mac/dump_syms.mm
common/mac/MachIPC.mm
common/mac/SimpleStringDictionary.mm
common/dwarf/dwarf2reader.cc
common/mac/bootstrap_compat.cc
common/dwarf_cfi_to_module.cc
)
ELSE(APPLE)
SET( breakpadSources
common/linux/safe_readlink.cc
client/linux/crash_generation/crash_generation_client.cc
client/linux/crash_generation/crash_generation_server.cc
client/linux/minidump_writer/minidump_writer.cc
client/linux/minidump_writer/linux_dumper.cc
client/linux/handler/exception_handler.cc
common/linux/dump_symbols.cc
common/linux/file_id.cc
common/linux/libcurl_wrapper.cc
common/linux/google_crashdump_uploader.cc
common/linux/synth_elf.cc
common/linux/http_upload.cc
common/linux/guid_creator.cc
common/linux/elf_symbols_to_module.cc
client/minidump_file_writer.cc
client/linux/minidump_writer/linux_ptrace_dumper.cc
common/linux/memory_mapped_file.cc
client/linux/handler/minidump_descriptor.cc
client/linux/log/log.cc
client/linux/minidump_writer/linux_dumper.cc
client/linux/minidump_writer/linux_ptrace_dumper.cc
client/linux/minidump_writer/minidump_writer.cc
client/minidump_file_writer.cc
common/convert_UTF.c
# common/md5.c
common/string_conversion.cc
common/linux/elfutils.cc
common/linux/file_id.cc
common/linux/guid_creator.cc
common/linux/linux_libc_support.cc
common/linux/memory_mapped_file.cc
common/linux/safe_readlink.cc
)
ENDIF(APPLE)
@@ -82,3 +83,7 @@ INCLUDE_DIRECTORIES(.)
ADD_DEFINITIONS( -fPIC )
ADD_LIBRARY( tomahawk_breakpad STATIC ${breakpadSources} )
TARGET_LINK_LIBRARIES( tomahawk_breakpad )
IF(WIN32)
TARGET_LINK_LIBRARIES(tomahawk_breakpad "mingwex")
ENDIF()

View File

@@ -27,51 +27,31 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Android runs a fairly new Linux kernel, so signal info is there,
// but the NDK doesn't have the structs defined, so define
// them here.
// Adapted from platform-linux.cc in V8
#ifndef BREAKPAD_GOOGLETEST_INCLUDES_H__
#define BREAKPAD_GOOGLETEST_INCLUDES_H__
#ifndef GOOGLE_BREAKPAD_CLIENT_LINUX_ANDROID_UCONTEXT_H_
#define GOOGLE_BREAKPAD_CLIENT_LINUX_ANDROID_UCONTEXT_H_
#include <signal.h>
#if !defined(__GLIBC__) && (defined(__arm__) || defined(__thumb__))
struct sigcontext {
uint32_t trap_no;
uint32_t error_code;
uint32_t oldmask;
uint32_t arm_r0;
uint32_t arm_r1;
uint32_t arm_r2;
uint32_t arm_r3;
uint32_t arm_r4;
uint32_t arm_r5;
uint32_t arm_r6;
uint32_t arm_r7;
uint32_t arm_r8;
uint32_t arm_r9;
uint32_t arm_r10;
uint32_t arm_fp;
uint32_t arm_ip;
uint32_t arm_sp;
uint32_t arm_lr;
uint32_t arm_pc;
uint32_t arm_cpsr;
uint32_t fault_address;
};
typedef uint32_t __sigset_t;
typedef struct sigcontext mcontext_t;
typedef struct ucontext {
uint32_t uc_flags;
struct ucontext* uc_link;
stack_t uc_stack;
mcontext_t uc_mcontext;
__sigset_t uc_sigmask;
} ucontext_t;
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/include/gmock/gmock.h"
// If AddressSanitizer is used, NULL pointer dereferences generate SIGILL
// (illegal instruction) instead of SIGSEGV (segmentation fault). Also,
// the number of memory regions differs, so there is no point in running
// this test if AddressSanitizer is used.
//
// Ideally we'd use this attribute to disable ASAN on a per-func basis,
// but this doesn't seem to actually work, and it's changed names over
// time. So just stick with disabling the actual tests.
// http://crbug.com/304575
//#define NO_ASAN __attribute__((no_sanitize_address))
#if defined(__clang__) && defined(__has_feature)
// Have to keep this check sep from above as newer gcc will barf on it.
# if __has_feature(address_sanitizer)
# define ADDRESS_SANITIZER
# endif
#elif defined(__GNUC__) && defined(__SANITIZE_ADDRESS__)
# define ADDRESS_SANITIZER
#else
# undef ADDRESS_SANITIZER
#endif
#endif // GOOGLE_BREAKPAD_CLIENT_LINUX_ANDROID_UCONTEXT_H_
#endif // BREAKPAD_GOOGLETEST_INCLUDES_H__

View File

@@ -0,0 +1,221 @@
// Copyright (c) 2011, 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.
// Framework to provide a simple C API to crash reporting for
// applications. By default, if any machine-level exception (e.g.,
// EXC_BAD_ACCESS) occurs, it will be handled by the BreakpadRef
// object as follows:
//
// 1. Create a minidump file (see Breakpad for details)
// 2. Create a config file.
//
// These files can then be uploaded to a server.
typedef void *BreakpadRef;
#ifdef __cplusplus
extern "C" {
#endif
#include <Foundation/Foundation.h>
#include <client/apple/Framework/BreakpadDefines.h>
// The keys in the dictionary returned by |BreakpadGenerateReport|.
#define BREAKPAD_OUTPUT_DUMP_FILE "BreakpadDumpFile"
#define BREAKPAD_OUTPUT_CONFIG_FILE "BreakpadConfigFile"
// Optional user-defined function to decide if we should handle this crash or
// forward it along.
// Return true if you want Breakpad to handle it.
// Return false if you want Breakpad to skip it
// The exception handler always returns false, as if SEND_AND_EXIT were false
// (which means the next exception handler will take the exception)
typedef bool (*BreakpadFilterCallback)(int exception_type,
int exception_code,
mach_port_t crashing_thread,
void *context);
// Create a new BreakpadRef object and install it as an exception
// handler. The |parameters| will typically be the contents of your
// bundle's Info.plist.
//
// You can also specify these additional keys for customizable behavior:
// Key: Value:
// BREAKPAD_PRODUCT Product name (e.g., "MyAwesomeProduct")
// This one is used as the key to identify
// the product when uploading. Falls back to
// CFBundleName if not specified.
// REQUIRED
//
// BREAKPAD_PRODUCT_DISPLAY This is the display name, e.g. a pretty
// name for the product when the crash_sender
// pops up UI for the user. Falls back first to
// CFBundleDisplayName and then to
// BREAKPAD_PRODUCT if not specified.
//
// BREAKPAD_VERSION Product version (e.g., 1.2.3), used
// as metadata for crash report. Falls back to
// CFBundleVersion if not specified.
// REQUIRED
//
// BREAKPAD_VENDOR Vendor name, used in UI (e.g. "A report has
// been created that you can send to <vendor>")
//
// BREAKPAD_URL URL destination for reporting
// REQUIRED
//
// BREAKPAD_DUMP_DIRECTORY The directory to store crash-dumps
// in. By default, we use
// ~/Library/Cache/Breakpad/<BREAKPAD_PRODUCT>
// The path you specify here is tilde-expanded.
//
// BREAKPAD_SERVER_TYPE A parameter that tells Breakpad how to
// rewrite the upload parameters for a specific
// server type. The currently valid values are
// 'socorro' or 'google'. If you want to add
// other types, see the function in
// crash_report_sender.m that maps parameters to
// URL parameters. Defaults to 'google'.
//
// BREAKPAD_SERVER_PARAMETER_DICT A plist dictionary of static
// parameters that are uploaded to the
// server. The parameters are sent as
// is to the crash server. Their
// content isn't added to the minidump
// but pass as URL parameters when
// uploading theminidump to the crash
// server.
//=============================================================================
// The BREAKPAD_PRODUCT, BREAKPAD_VERSION and BREAKPAD_URL are
// required to have non-NULL values. By default, the BREAKPAD_PRODUCT
// will be the CFBundleName and the BREAKPAD_VERSION will be the
// CFBundleVersion when these keys are present in the bundle's
// Info.plist, which is usually passed in to BreakpadCreate() as an
// NSDictionary (you could also pass in another dictionary that had
// the same keys configured). If the BREAKPAD_PRODUCT or
// BREAKPAD_VERSION are ultimately undefined, BreakpadCreate() will
// fail. You have been warned.
//
// If you are running in a debugger, Breakpad will not install, unless the
// BREAKPAD_IGNORE_DEBUGGER envionment variable is set and/or non-zero.
//
//=============================================================================
// The following are NOT user-supplied but are documented here for
// completeness. They are calculated by Breakpad during initialization &
// crash-dump generation, or entered in by the user.
//
// BREAKPAD_PROCESS_START_TIME The time, in seconds since the Epoch, the
// process started
//
// BREAKPAD_PROCESS_CRASH_TIME The time, in seconds since the Epoch, the
// process crashed.
//
// BREAKPAD_PROCESS_UP_TIME The total time in milliseconds the process
// has been running. This parameter is not
// set until the crash-dump-generation phase.
//
// BREAKPAD_SERVER_PARAMETER_PREFIX This prefix is used by Breakpad
// internally, because Breakpad uses
// the same dictionary internally to
// track both its internal
// configuration parameters and
// parameters meant to be uploaded
// to the server. This string is
// used internally by Breakpad to
// prefix user-supplied parameter
// names so those can be sent to the
// server without leaking Breakpad's
// internal values.
// Returns a new BreakpadRef object on success, NULL otherwise.
BreakpadRef BreakpadCreate(NSDictionary *parameters);
// Uninstall and release the data associated with |ref|.
void BreakpadRelease(BreakpadRef ref);
// User defined key and value string storage. Generally this is used
// to configure Breakpad's internal operation, such as whether the
// crash_sender should prompt the user, or the filesystem location for
// the minidump file. See Breakpad.h for some parameters that can be
// set. Anything longer than 255 bytes will be truncated. Note that
// the string is converted to UTF8 before truncation, so any multibyte
// character that straddles the 255(256 - 1 for terminator) byte limit
// will be mangled.
//
// A maximum number of 64 key/value pairs are supported. An assert()
// will fire if more than this number are set. Unfortunately, right
// now, the same dictionary is used for both Breakpad's parameters AND
// the Upload parameters.
//
// TODO (nealsid): Investigate how necessary this is if we don't
// automatically upload parameters to the server anymore.
// TODO (nealsid): separate server parameter dictionary from the
// dictionary used to configure Breakpad, and document limits for each
// independently.
void BreakpadSetKeyValue(BreakpadRef ref, NSString *key, NSString *value);
NSString *BreakpadKeyValue(BreakpadRef ref, NSString *key);
void BreakpadRemoveKeyValue(BreakpadRef ref, NSString *key);
// You can use this method to specify parameters that will be uploaded
// to the crash server. They will be automatically encoded as
// necessary. Note that as mentioned above there are limits on both
// the number of keys and their length.
void BreakpadAddUploadParameter(BreakpadRef ref, NSString *key,
NSString *value);
// This method will remove a previously-added parameter from the
// upload parameter set.
void BreakpadRemoveUploadParameter(BreakpadRef ref, NSString *key);
// Method to handle uploading data to the server
// Returns the number of crash reports waiting to send to the server.
int BreakpadGetCrashReportCount(BreakpadRef ref);
// Upload next report to the server.
// |server_parameters| is additional server parameters to send (optional).
void BreakpadUploadNextReport(BreakpadRef ref,
NSDictionary *server_parameters = nil);
// Upload a file to the server. |data| is the content of the file to sent.
// |server_parameters| is additional server parameters to send.
void BreakpadUploadData(BreakpadRef ref, NSData *data, NSString *name,
NSDictionary *server_parameters);
// Generate a breakpad minidump and configuration file in the dump directory.
// The report will be available for uploading. The paths of the created files
// are returned in the dictionary. |server_parameters| is additional server
// parameters to add in the config file.
NSDictionary *BreakpadGenerateReport(BreakpadRef ref,
NSDictionary *server_parameters);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,837 @@
// Copyright (c) 2011, 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.
#define IGNORE_DEBUGGER "BREAKPAD_IGNORE_DEBUGGER"
#import "client/ios/Breakpad.h"
#include <assert.h>
#import <Foundation/Foundation.h>
#include <pthread.h>
#include <sys/stat.h>
#include <sys/sysctl.h>
#import "client/ios/handler/ios_exception_minidump_generator.h"
#import "client/mac/crash_generation/ConfigFile.h"
#import "client/mac/handler/exception_handler.h"
#import "client/mac/handler/minidump_generator.h"
#import "client/mac/sender/uploader.h"
#import "client/mac/handler/protected_memory_allocator.h"
#import "common/simple_string_dictionary.h"
#ifndef __EXCEPTIONS
// This file uses C++ try/catch (but shouldn't). Duplicate the macros from
// <c++/4.2.1/exception_defines.h> allowing this file to work properly with
// exceptions disabled even when other C++ libraries are used. #undef the try
// and catch macros first in case libstdc++ is in use and has already provided
// its own definitions.
#undef try
#define try if (true)
#undef catch
#define catch(X) if (false)
#endif // __EXCEPTIONS
using google_breakpad::ConfigFile;
using google_breakpad::EnsureDirectoryPathExists;
using google_breakpad::SimpleStringDictionary;
//=============================================================================
// We want any memory allocations which are used by breakpad during the
// exception handling process (after a crash has happened) to be read-only
// to prevent them from being smashed before a crash occurs. Unfortunately
// we cannot protect against smashes to our exception handling thread's
// stack.
//
// NOTE: Any memory allocations which are not used during the exception
// handling process may be allocated in the normal ways.
//
// The ProtectedMemoryAllocator class provides an Allocate() method which
// we'll using in conjunction with placement operator new() to control
// allocation of C++ objects. Note that we don't use operator delete()
// but instead call the objects destructor directly: object->~ClassName();
//
ProtectedMemoryAllocator *gMasterAllocator = NULL;
ProtectedMemoryAllocator *gKeyValueAllocator = NULL;
ProtectedMemoryAllocator *gBreakpadAllocator = NULL;
// Mutex for thread-safe access to the key/value dictionary used by breakpad.
// It's a global instead of an instance variable of Breakpad
// since it can't live in a protected memory area.
pthread_mutex_t gDictionaryMutex;
//=============================================================================
// Stack-based object for thread-safe access to a memory-protected region.
// It's assumed that normally the memory block (allocated by the allocator)
// is protected (read-only). Creating a stack-based instance of
// ProtectedMemoryLocker will unprotect this block after taking the lock.
// Its destructor will first re-protect the memory then release the lock.
class ProtectedMemoryLocker {
public:
ProtectedMemoryLocker(pthread_mutex_t *mutex,
ProtectedMemoryAllocator *allocator)
: mutex_(mutex),
allocator_(allocator) {
// Lock the mutex
int rv = pthread_mutex_lock(mutex_);
assert(rv == 0);
// Unprotect the memory
allocator_->Unprotect();
}
~ProtectedMemoryLocker() {
// First protect the memory
allocator_->Protect();
// Then unlock the mutex
int rv = pthread_mutex_unlock(mutex_);
assert(rv == 0);
};
private:
ProtectedMemoryLocker();
ProtectedMemoryLocker(const ProtectedMemoryLocker&);
ProtectedMemoryLocker& operator=(const ProtectedMemoryLocker&);
pthread_mutex_t *mutex_;
ProtectedMemoryAllocator *allocator_;
};
//=============================================================================
class Breakpad {
public:
// factory method
static Breakpad *Create(NSDictionary *parameters) {
// Allocate from our special allocation pool
Breakpad *breakpad =
new (gBreakpadAllocator->Allocate(sizeof(Breakpad)))
Breakpad();
if (!breakpad)
return NULL;
if (!breakpad->Initialize(parameters)) {
// Don't use operator delete() here since we allocated from special pool
breakpad->~Breakpad();
return NULL;
}
return breakpad;
}
~Breakpad();
void SetKeyValue(NSString *key, NSString *value);
NSString *KeyValue(NSString *key);
void RemoveKeyValue(NSString *key);
NSArray *CrashReportsToUpload();
NSString *NextCrashReportToUpload();
void UploadNextReport(NSDictionary *server_parameters);
void UploadData(NSData *data, NSString *name,
NSDictionary *server_parameters);
NSDictionary *GenerateReport(NSDictionary *server_parameters);
private:
Breakpad()
: handler_(NULL),
config_params_(NULL) {}
bool Initialize(NSDictionary *parameters);
bool ExtractParameters(NSDictionary *parameters);
// Dispatches to HandleMinidump()
static bool HandleMinidumpCallback(const char *dump_dir,
const char *minidump_id,
void *context, bool succeeded);
bool HandleMinidump(const char *dump_dir,
const char *minidump_id);
// NSException handler
static void UncaughtExceptionHandler(NSException *exception);
// Handle an uncaught NSException.
void HandleUncaughtException(NSException *exception);
// Since ExceptionHandler (w/o namespace) is defined as typedef in OSX's
// MachineExceptions.h, we have to explicitly name the handler.
google_breakpad::ExceptionHandler *handler_; // The actual handler (STRONG)
SimpleStringDictionary *config_params_; // Create parameters (STRONG)
ConfigFile config_file_;
// A static reference to the current Breakpad instance. Used for handling
// NSException.
static Breakpad *current_breakpad_;
};
Breakpad *Breakpad::current_breakpad_ = NULL;
#pragma mark -
#pragma mark Helper functions
//=============================================================================
// Helper functions
//=============================================================================
static BOOL IsDebuggerActive() {
BOOL result = NO;
NSUserDefaults *stdDefaults = [NSUserDefaults standardUserDefaults];
// We check both defaults and the environment variable here
BOOL ignoreDebugger = [stdDefaults boolForKey:@IGNORE_DEBUGGER];
if (!ignoreDebugger) {
char *ignoreDebuggerStr = getenv(IGNORE_DEBUGGER);
ignoreDebugger =
(ignoreDebuggerStr ? strtol(ignoreDebuggerStr, NULL, 10) : 0) != 0;
}
if (!ignoreDebugger) {
pid_t pid = getpid();
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
int mibSize = sizeof(mib) / sizeof(int);
size_t actualSize;
if (sysctl(mib, mibSize, NULL, &actualSize, NULL, 0) == 0) {
struct kinfo_proc *info = (struct kinfo_proc *)malloc(actualSize);
if (info) {
// This comes from looking at the Darwin xnu Kernel
if (sysctl(mib, mibSize, info, &actualSize, NULL, 0) == 0)
result = (info->kp_proc.p_flag & P_TRACED) ? YES : NO;
free(info);
}
}
}
return result;
}
//=============================================================================
bool Breakpad::HandleMinidumpCallback(const char *dump_dir,
const char *minidump_id,
void *context, bool succeeded) {
Breakpad *breakpad = (Breakpad *)context;
// If our context is damaged or something, just return false to indicate that
// the handler should continue without us.
if (!breakpad || !succeeded)
return false;
return breakpad->HandleMinidump(dump_dir, minidump_id);
}
//=============================================================================
void Breakpad::UncaughtExceptionHandler(NSException *exception) {
NSSetUncaughtExceptionHandler(NULL);
if (current_breakpad_) {
current_breakpad_->HandleUncaughtException(exception);
}
}
//=============================================================================
#pragma mark -
//=============================================================================
bool Breakpad::Initialize(NSDictionary *parameters) {
// Initialize
current_breakpad_ = this;
config_params_ = NULL;
handler_ = NULL;
// Gather any user specified parameters
if (!ExtractParameters(parameters)) {
return false;
}
// Check for debugger
if (IsDebuggerActive()) {
return true;
}
// Create the handler (allocating it in our special protected pool)
handler_ =
new (gBreakpadAllocator->Allocate(
sizeof(google_breakpad::ExceptionHandler)))
google_breakpad::ExceptionHandler(
config_params_->GetValueForKey(BREAKPAD_DUMP_DIRECTORY),
0, &HandleMinidumpCallback, this, true, 0);
NSSetUncaughtExceptionHandler(&Breakpad::UncaughtExceptionHandler);
return true;
}
//=============================================================================
Breakpad::~Breakpad() {
NSSetUncaughtExceptionHandler(NULL);
current_breakpad_ = NULL;
// Note that we don't use operator delete() on these pointers,
// since they were allocated by ProtectedMemoryAllocator objects.
//
if (config_params_) {
config_params_->~SimpleStringDictionary();
}
if (handler_)
handler_->~ExceptionHandler();
}
//=============================================================================
bool Breakpad::ExtractParameters(NSDictionary *parameters) {
NSString *serverType = [parameters objectForKey:@BREAKPAD_SERVER_TYPE];
NSString *display = [parameters objectForKey:@BREAKPAD_PRODUCT_DISPLAY];
NSString *product = [parameters objectForKey:@BREAKPAD_PRODUCT];
NSString *version = [parameters objectForKey:@BREAKPAD_VERSION];
NSString *urlStr = [parameters objectForKey:@BREAKPAD_URL];
NSString *vendor =
[parameters objectForKey:@BREAKPAD_VENDOR];
// We check both parameters and the environment variable here.
char *envVarDumpSubdirectory = getenv(BREAKPAD_DUMP_DIRECTORY);
NSString *dumpSubdirectory = envVarDumpSubdirectory ?
[NSString stringWithUTF8String:envVarDumpSubdirectory] :
[parameters objectForKey:@BREAKPAD_DUMP_DIRECTORY];
NSDictionary *serverParameters =
[parameters objectForKey:@BREAKPAD_SERVER_PARAMETER_DICT];
if (!product)
product = [parameters objectForKey:@"CFBundleName"];
if (!display) {
display = [parameters objectForKey:@"CFBundleDisplayName"];
if (!display) {
display = product;
}
}
if (!version)
version = [parameters objectForKey:@"CFBundleVersion"];
if (!vendor) {
vendor = @"Vendor not specified";
}
if (!dumpSubdirectory) {
NSString *cachePath =
[NSSearchPathForDirectoriesInDomains(NSCachesDirectory,
NSUserDomainMask,
YES)
objectAtIndex:0];
dumpSubdirectory =
[cachePath stringByAppendingPathComponent:@kDefaultLibrarySubdirectory];
EnsureDirectoryPathExists(dumpSubdirectory);
}
// The product, version, and URL are required values.
if (![product length]) {
return false;
}
if (![version length]) {
return false;
}
if (![urlStr length]) {
return false;
}
config_params_ =
new (gKeyValueAllocator->Allocate(sizeof(SimpleStringDictionary)) )
SimpleStringDictionary();
SimpleStringDictionary &dictionary = *config_params_;
dictionary.SetKeyValue(BREAKPAD_SERVER_TYPE, [serverType UTF8String]);
dictionary.SetKeyValue(BREAKPAD_PRODUCT_DISPLAY, [display UTF8String]);
dictionary.SetKeyValue(BREAKPAD_PRODUCT, [product UTF8String]);
dictionary.SetKeyValue(BREAKPAD_VERSION, [version UTF8String]);
dictionary.SetKeyValue(BREAKPAD_URL, [urlStr UTF8String]);
dictionary.SetKeyValue(BREAKPAD_VENDOR, [vendor UTF8String]);
dictionary.SetKeyValue(BREAKPAD_DUMP_DIRECTORY,
[dumpSubdirectory UTF8String]);
struct timeval tv;
gettimeofday(&tv, NULL);
char timeStartedString[32];
sprintf(timeStartedString, "%zd", tv.tv_sec);
dictionary.SetKeyValue(BREAKPAD_PROCESS_START_TIME, timeStartedString);
if (serverParameters) {
// For each key-value pair, call BreakpadAddUploadParameter()
NSEnumerator *keyEnumerator = [serverParameters keyEnumerator];
NSString *aParameter;
while ((aParameter = [keyEnumerator nextObject])) {
BreakpadAddUploadParameter(this, aParameter,
[serverParameters objectForKey:aParameter]);
}
}
return true;
}
//=============================================================================
void Breakpad::SetKeyValue(NSString *key, NSString *value) {
// We allow nil values. This is the same as removing the keyvalue.
if (!config_params_ || !key)
return;
config_params_->SetKeyValue([key UTF8String], [value UTF8String]);
}
//=============================================================================
NSString *Breakpad::KeyValue(NSString *key) {
if (!config_params_ || !key)
return nil;
const char *value = config_params_->GetValueForKey([key UTF8String]);
return value ? [NSString stringWithUTF8String:value] : nil;
}
//=============================================================================
void Breakpad::RemoveKeyValue(NSString *key) {
if (!config_params_ || !key) return;
config_params_->RemoveKey([key UTF8String]);
}
//=============================================================================
NSArray *Breakpad::CrashReportsToUpload() {
NSString *directory = KeyValue(@BREAKPAD_DUMP_DIRECTORY);
if (!directory)
return nil;
NSArray *dirContents = [[NSFileManager defaultManager]
contentsOfDirectoryAtPath:directory error:nil];
NSArray *configs = [dirContents filteredArrayUsingPredicate:[NSPredicate
predicateWithFormat:@"self BEGINSWITH 'Config-'"]];
return configs;
}
//=============================================================================
NSString *Breakpad::NextCrashReportToUpload() {
NSString *directory = KeyValue(@BREAKPAD_DUMP_DIRECTORY);
if (!directory)
return nil;
NSString *config = [CrashReportsToUpload() lastObject];
if (!config)
return nil;
return [NSString stringWithFormat:@"%@/%@", directory, config];
}
//=============================================================================
void Breakpad::UploadNextReport(NSDictionary *server_parameters) {
NSString *configFile = NextCrashReportToUpload();
if (configFile) {
Uploader *uploader = [[[Uploader alloc]
initWithConfigFile:[configFile UTF8String]] autorelease];
if (uploader) {
for (NSString *key in server_parameters) {
[uploader addServerParameter:[server_parameters objectForKey:key]
forKey:key];
}
[uploader report];
}
}
}
//=============================================================================
void Breakpad::UploadData(NSData *data, NSString *name,
NSDictionary *server_parameters) {
NSMutableDictionary *config = [NSMutableDictionary dictionary];
SimpleStringDictionary::Iterator it(*config_params_);
while (const SimpleStringDictionary::Entry *next = it.Next()) {
[config setValue:[NSString stringWithUTF8String:next->value]
forKey:[NSString stringWithUTF8String:next->key]];
}
Uploader *uploader =
[[[Uploader alloc] initWithConfig:config] autorelease];
for (NSString *key in server_parameters) {
[uploader addServerParameter:[server_parameters objectForKey:key]
forKey:key];
}
[uploader uploadData:data name:name];
}
//=============================================================================
NSDictionary *Breakpad::GenerateReport(NSDictionary *server_parameters) {
NSString *dumpDirAsNSString = KeyValue(@BREAKPAD_DUMP_DIRECTORY);
if (!dumpDirAsNSString)
return nil;
const char *dumpDir = [dumpDirAsNSString UTF8String];
google_breakpad::MinidumpGenerator generator(mach_task_self(),
MACH_PORT_NULL);
std::string dumpId;
std::string dumpFilename = generator.UniqueNameInDirectory(dumpDir, &dumpId);
bool success = generator.Write(dumpFilename.c_str());
if (!success)
return nil;
SimpleStringDictionary params = *config_params_;
for (NSString *key in server_parameters) {
params.SetKeyValue([key UTF8String],
[[server_parameters objectForKey:key] UTF8String]);
}
ConfigFile config_file;
config_file.WriteFile(dumpDir, &params, dumpDir, dumpId.c_str());
// Handle results.
NSMutableDictionary *result = [NSMutableDictionary dictionary];
NSString *dumpFullPath = [NSString stringWithUTF8String:dumpFilename.c_str()];
[result setValue:dumpFullPath
forKey:@BREAKPAD_OUTPUT_DUMP_FILE];
[result setValue:[NSString stringWithUTF8String:config_file.GetFilePath()]
forKey:@BREAKPAD_OUTPUT_CONFIG_FILE];
return result;
}
//=============================================================================
bool Breakpad::HandleMinidump(const char *dump_dir,
const char *minidump_id) {
config_file_.WriteFile(dump_dir,
config_params_,
dump_dir,
minidump_id);
// Return true here to indicate that we've processed things as much as we
// want.
return true;
}
//=============================================================================
void Breakpad::HandleUncaughtException(NSException *exception) {
// Generate the minidump.
google_breakpad::IosExceptionMinidumpGenerator generator(exception);
const char *minidump_path =
config_params_->GetValueForKey(BREAKPAD_DUMP_DIRECTORY);
std::string minidump_id;
std::string minidump_filename = generator.UniqueNameInDirectory(minidump_path,
&minidump_id);
generator.Write(minidump_filename.c_str());
// Copy the config params and our custom parameter. This is necessary for 2
// reasons:
// 1- config_params_ is protected.
// 2- If the application crash while trying to handle this exception, a usual
// report will be generated. This report must not contain these special
// keys.
SimpleStringDictionary params = *config_params_;
params.SetKeyValue(BREAKPAD_SERVER_PARAMETER_PREFIX "type", "exception");
params.SetKeyValue(BREAKPAD_SERVER_PARAMETER_PREFIX "exceptionName",
[[exception name] UTF8String]);
params.SetKeyValue(BREAKPAD_SERVER_PARAMETER_PREFIX "exceptionReason",
[[exception reason] UTF8String]);
// And finally write the config file.
ConfigFile config_file;
config_file.WriteFile(minidump_path,
&params,
minidump_path,
minidump_id.c_str());
}
//=============================================================================
#pragma mark -
#pragma mark Public API
//=============================================================================
BreakpadRef BreakpadCreate(NSDictionary *parameters) {
try {
// This is confusing. Our two main allocators for breakpad memory are:
// - gKeyValueAllocator for the key/value memory
// - gBreakpadAllocator for the Breakpad, ExceptionHandler, and other
// breakpad allocations which are accessed at exception handling time.
//
// But in order to avoid these two allocators themselves from being smashed,
// we'll protect them as well by allocating them with gMasterAllocator.
//
// gMasterAllocator itself will NOT be protected, but this doesn't matter,
// since once it does its allocations and locks the memory, smashes to
// itself don't affect anything we care about.
gMasterAllocator =
new ProtectedMemoryAllocator(sizeof(ProtectedMemoryAllocator) * 2);
gKeyValueAllocator =
new (gMasterAllocator->Allocate(sizeof(ProtectedMemoryAllocator)))
ProtectedMemoryAllocator(sizeof(SimpleStringDictionary));
// Create a mutex for use in accessing the SimpleStringDictionary
int mutexResult = pthread_mutex_init(&gDictionaryMutex, NULL);
if (mutexResult == 0) {
// With the current compiler, gBreakpadAllocator is allocating 1444 bytes.
// Let's round up to the nearest page size.
//
int breakpad_pool_size = 4096;
/*
sizeof(Breakpad)
+ sizeof(google_breakpad::ExceptionHandler)
+ sizeof( STUFF ALLOCATED INSIDE ExceptionHandler )
*/
gBreakpadAllocator =
new (gMasterAllocator->Allocate(sizeof(ProtectedMemoryAllocator)))
ProtectedMemoryAllocator(breakpad_pool_size);
// Stack-based autorelease pool for Breakpad::Create() obj-c code.
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
Breakpad *breakpad = Breakpad::Create(parameters);
if (breakpad) {
// Make read-only to protect against memory smashers
gMasterAllocator->Protect();
gKeyValueAllocator->Protect();
gBreakpadAllocator->Protect();
// Can uncomment this line to figure out how much space was actually
// allocated using this allocator
// printf("gBreakpadAllocator allocated size = %d\n",
// gBreakpadAllocator->GetAllocatedSize() );
[pool release];
return (BreakpadRef)breakpad;
}
[pool release];
}
} catch(...) { // don't let exceptions leave this C API
fprintf(stderr, "BreakpadCreate() : error\n");
}
if (gKeyValueAllocator) {
gKeyValueAllocator->~ProtectedMemoryAllocator();
gKeyValueAllocator = NULL;
}
if (gBreakpadAllocator) {
gBreakpadAllocator->~ProtectedMemoryAllocator();
gBreakpadAllocator = NULL;
}
delete gMasterAllocator;
gMasterAllocator = NULL;
return NULL;
}
//=============================================================================
void BreakpadRelease(BreakpadRef ref) {
try {
Breakpad *breakpad = (Breakpad *)ref;
if (gMasterAllocator) {
gMasterAllocator->Unprotect();
gKeyValueAllocator->Unprotect();
gBreakpadAllocator->Unprotect();
breakpad->~Breakpad();
// Unfortunately, it's not possible to deallocate this stuff
// because the exception handling thread is still finishing up
// asynchronously at this point... OK, it could be done with
// locks, etc. But since BreakpadRelease() should usually only
// be called right before the process exits, it's not worth
// deallocating this stuff.
#if 0
gKeyValueAllocator->~ProtectedMemoryAllocator();
gBreakpadAllocator->~ProtectedMemoryAllocator();
delete gMasterAllocator;
gMasterAllocator = NULL;
gKeyValueAllocator = NULL;
gBreakpadAllocator = NULL;
#endif
pthread_mutex_destroy(&gDictionaryMutex);
}
} catch(...) { // don't let exceptions leave this C API
fprintf(stderr, "BreakpadRelease() : error\n");
}
}
//=============================================================================
void BreakpadSetKeyValue(BreakpadRef ref, NSString *key, NSString *value) {
try {
// Not called at exception time
Breakpad *breakpad = (Breakpad *)ref;
if (breakpad && key && gKeyValueAllocator) {
ProtectedMemoryLocker locker(&gDictionaryMutex, gKeyValueAllocator);
breakpad->SetKeyValue(key, value);
}
} catch(...) { // don't let exceptions leave this C API
fprintf(stderr, "BreakpadSetKeyValue() : error\n");
}
}
void BreakpadAddUploadParameter(BreakpadRef ref,
NSString *key,
NSString *value) {
// The only difference, internally, between an upload parameter and
// a key value one that is set with BreakpadSetKeyValue is that we
// prepend the keyname with a special prefix. This informs the
// crash sender that the parameter should be sent along with the
// POST of the crash dump upload.
try {
Breakpad *breakpad = (Breakpad *)ref;
if (breakpad && key && gKeyValueAllocator) {
ProtectedMemoryLocker locker(&gDictionaryMutex, gKeyValueAllocator);
NSString *prefixedKey = [@BREAKPAD_SERVER_PARAMETER_PREFIX
stringByAppendingString:key];
breakpad->SetKeyValue(prefixedKey, value);
}
} catch(...) { // don't let exceptions leave this C API
fprintf(stderr, "BreakpadSetKeyValue() : error\n");
}
}
void BreakpadRemoveUploadParameter(BreakpadRef ref,
NSString *key) {
try {
// Not called at exception time
Breakpad *breakpad = (Breakpad *)ref;
if (breakpad && key && gKeyValueAllocator) {
ProtectedMemoryLocker locker(&gDictionaryMutex, gKeyValueAllocator);
NSString *prefixedKey = [NSString stringWithFormat:@"%@%@",
@BREAKPAD_SERVER_PARAMETER_PREFIX, key];
breakpad->RemoveKeyValue(prefixedKey);
}
} catch(...) { // don't let exceptions leave this C API
fprintf(stderr, "BreakpadRemoveKeyValue() : error\n");
}
}
//=============================================================================
NSString *BreakpadKeyValue(BreakpadRef ref, NSString *key) {
NSString *value = nil;
try {
// Not called at exception time
Breakpad *breakpad = (Breakpad *)ref;
if (!breakpad || !key || !gKeyValueAllocator)
return nil;
ProtectedMemoryLocker locker(&gDictionaryMutex, gKeyValueAllocator);
value = breakpad->KeyValue(key);
} catch(...) { // don't let exceptions leave this C API
fprintf(stderr, "BreakpadKeyValue() : error\n");
}
return value;
}
//=============================================================================
void BreakpadRemoveKeyValue(BreakpadRef ref, NSString *key) {
try {
// Not called at exception time
Breakpad *breakpad = (Breakpad *)ref;
if (breakpad && key && gKeyValueAllocator) {
ProtectedMemoryLocker locker(&gDictionaryMutex, gKeyValueAllocator);
breakpad->RemoveKeyValue(key);
}
} catch(...) { // don't let exceptions leave this C API
fprintf(stderr, "BreakpadRemoveKeyValue() : error\n");
}
}
//=============================================================================
int BreakpadGetCrashReportCount(BreakpadRef ref) {
try {
// Not called at exception time
Breakpad *breakpad = (Breakpad *)ref;
if (breakpad) {
return [breakpad->CrashReportsToUpload() count];
}
} catch(...) { // don't let exceptions leave this C API
fprintf(stderr, "BreakpadGetCrashReportCount() : error\n");
}
return false;
}
//=============================================================================
void BreakpadUploadNextReport(BreakpadRef ref,
NSDictionary *server_parameters) {
try {
// Not called at exception time
Breakpad *breakpad = (Breakpad *)ref;
if (breakpad) {
breakpad->UploadNextReport(server_parameters);
}
} catch(...) { // don't let exceptions leave this C API
fprintf(stderr, "BreakpadUploadNextReport() : error\n");
}
}
//=============================================================================
void BreakpadUploadData(BreakpadRef ref, NSData *data, NSString *name,
NSDictionary *server_parameters) {
try {
// Not called at exception time
Breakpad *breakpad = (Breakpad *)ref;
if (breakpad) {
breakpad->UploadData(data, name, server_parameters);
}
} catch(...) { // don't let exceptions leave this C API
fprintf(stderr, "BreakpadUploadData() : error\n");
}
}
//=============================================================================
NSDictionary *BreakpadGenerateReport(BreakpadRef ref,
NSDictionary *server_parameters) {
try {
// Not called at exception time
Breakpad *breakpad = (Breakpad *)ref;
if (breakpad) {
return breakpad->GenerateReport(server_parameters);
} else {
return nil;
}
} catch(...) { // don't let exceptions leave this C API
fprintf(stderr, "BreakpadGenerateReport() : error\n");
return nil;
}
}

View File

@@ -0,0 +1,574 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
14569321182CE29F0029C465 /* ucontext_compat.h in Headers */ = {isa = PBXBuildFile; fileRef = 14569320182CE29F0029C465 /* ucontext_compat.h */; };
14569323182CE2C10029C465 /* mach_vm_compat.h in Headers */ = {isa = PBXBuildFile; fileRef = 14569322182CE2C10029C465 /* mach_vm_compat.h */; };
16BFA67014E195E9009704F8 /* ios_exception_minidump_generator.h in Headers */ = {isa = PBXBuildFile; fileRef = 16BFA66E14E195E9009704F8 /* ios_exception_minidump_generator.h */; };
16BFA67214E1965A009704F8 /* ios_exception_minidump_generator.mm in Sources */ = {isa = PBXBuildFile; fileRef = 16BFA67114E1965A009704F8 /* ios_exception_minidump_generator.mm */; };
16C7CCCB147D4A4300776EAD /* BreakpadDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7C968147D4A4200776EAD /* BreakpadDefines.h */; };
16C7CCCC147D4A4300776EAD /* Breakpad.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7C96A147D4A4200776EAD /* Breakpad.h */; };
16C7CCCD147D4A4300776EAD /* Breakpad.mm in Sources */ = {isa = PBXBuildFile; fileRef = 16C7C96B147D4A4200776EAD /* Breakpad.mm */; };
16C7CDE8147D4A4300776EAD /* ConfigFile.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CB9E147D4A4300776EAD /* ConfigFile.h */; };
16C7CDE9147D4A4300776EAD /* ConfigFile.mm in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CB9F147D4A4300776EAD /* ConfigFile.mm */; };
16C7CDF5147D4A4300776EAD /* breakpad_nlist_64.cc in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CBAD147D4A4300776EAD /* breakpad_nlist_64.cc */; };
16C7CDF6147D4A4300776EAD /* breakpad_nlist_64.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CBAE147D4A4300776EAD /* breakpad_nlist_64.h */; };
16C7CDF7147D4A4300776EAD /* dynamic_images.cc in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CBAF147D4A4300776EAD /* dynamic_images.cc */; };
16C7CDF8147D4A4300776EAD /* dynamic_images.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CBB0147D4A4300776EAD /* dynamic_images.h */; };
16C7CDF9147D4A4300776EAD /* exception_handler.cc in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CBB1147D4A4300776EAD /* exception_handler.cc */; };
16C7CDFA147D4A4300776EAD /* exception_handler.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CBB2147D4A4300776EAD /* exception_handler.h */; };
16C7CDFC147D4A4300776EAD /* minidump_generator.cc in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CBB4147D4A4300776EAD /* minidump_generator.cc */; };
16C7CDFD147D4A4300776EAD /* minidump_generator.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CBB5147D4A4300776EAD /* minidump_generator.h */; };
16C7CDFE147D4A4300776EAD /* protected_memory_allocator.cc in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CBBC147D4A4300776EAD /* protected_memory_allocator.cc */; };
16C7CDFF147D4A4300776EAD /* protected_memory_allocator.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CBBD147D4A4300776EAD /* protected_memory_allocator.h */; };
16C7CE08147D4A4300776EAD /* uploader.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CBEA147D4A4300776EAD /* uploader.h */; };
16C7CE09147D4A4300776EAD /* uploader.mm in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CBEB147D4A4300776EAD /* uploader.mm */; };
16C7CE18147D4A4300776EAD /* minidump_file_writer-inl.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CC04147D4A4300776EAD /* minidump_file_writer-inl.h */; };
16C7CE19147D4A4300776EAD /* minidump_file_writer.cc in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CC05147D4A4300776EAD /* minidump_file_writer.cc */; };
16C7CE1A147D4A4300776EAD /* minidump_file_writer.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CC06147D4A4300776EAD /* minidump_file_writer.h */; };
16C7CE40147D4A4300776EAD /* convert_UTF.c in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CC4A147D4A4300776EAD /* convert_UTF.c */; };
16C7CE41147D4A4300776EAD /* convert_UTF.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CC4B147D4A4300776EAD /* convert_UTF.h */; };
16C7CE78147D4A4300776EAD /* GTMLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CC88147D4A4300776EAD /* GTMLogger.h */; };
16C7CE79147D4A4300776EAD /* GTMLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CC89147D4A4300776EAD /* GTMLogger.m */; };
16C7CE7A147D4A4300776EAD /* HTTPMultipartUpload.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CC8A147D4A4300776EAD /* HTTPMultipartUpload.h */; };
16C7CE7B147D4A4300776EAD /* HTTPMultipartUpload.m in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CC8B147D4A4300776EAD /* HTTPMultipartUpload.m */; };
16C7CE83147D4A4300776EAD /* file_id.cc in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CC93147D4A4300776EAD /* file_id.cc */; };
16C7CE84147D4A4300776EAD /* file_id.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CC94147D4A4300776EAD /* file_id.h */; };
16C7CE85147D4A4300776EAD /* macho_id.cc in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CC95147D4A4300776EAD /* macho_id.cc */; };
16C7CE86147D4A4300776EAD /* macho_id.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CC96147D4A4300776EAD /* macho_id.h */; };
16C7CE8A147D4A4300776EAD /* macho_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CC9A147D4A4300776EAD /* macho_utilities.cc */; };
16C7CE8B147D4A4300776EAD /* macho_utilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CC9B147D4A4300776EAD /* macho_utilities.h */; };
16C7CE8C147D4A4300776EAD /* macho_walker.cc in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CC9C147D4A4300776EAD /* macho_walker.cc */; };
16C7CE8D147D4A4300776EAD /* macho_walker.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CC9D147D4A4300776EAD /* macho_walker.h */; };
16C7CE8F147D4A4300776EAD /* string_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CC9F147D4A4300776EAD /* string_utilities.cc */; };
16C7CE90147D4A4300776EAD /* string_utilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CCA0147D4A4300776EAD /* string_utilities.h */; };
16C7CE93147D4A4300776EAD /* md5.cc in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CCA4147D4A4300776EAD /* md5.cc */; };
16C7CE94147D4A4300776EAD /* md5.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CCA5147D4A4300776EAD /* md5.h */; };
16C7CEA7147D4A4300776EAD /* string_conversion.cc in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CCB9147D4A4300776EAD /* string_conversion.cc */; };
16C7CEA8147D4A4300776EAD /* string_conversion.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CCBA147D4A4300776EAD /* string_conversion.h */; };
16C92FAD150DF8330053D7BA /* BreakpadController.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C92FAB150DF8330053D7BA /* BreakpadController.h */; };
16C92FAE150DF8330053D7BA /* BreakpadController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 16C92FAC150DF8330053D7BA /* BreakpadController.mm */; };
1EEEB60F1720821900F7E689 /* simple_string_dictionary.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1EEEB60C1720821900F7E689 /* simple_string_dictionary.cc */; };
1EEEB6101720821900F7E689 /* simple_string_dictionary.h in Headers */ = {isa = PBXBuildFile; fileRef = 1EEEB60D1720821900F7E689 /* simple_string_dictionary.h */; };
AA747D9F0F9514B9006C5449 /* Breakpad_Prefix.pch in Headers */ = {isa = PBXBuildFile; fileRef = AA747D9E0F9514B9006C5449 /* Breakpad_Prefix.pch */; };
AACBBE4A0F95108600F1A2B1 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AACBBE490F95108600F1A2B1 /* Foundation.framework */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
14569320182CE29F0029C465 /* ucontext_compat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ucontext_compat.h; sourceTree = "<group>"; };
14569322182CE2C10029C465 /* mach_vm_compat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mach_vm_compat.h; sourceTree = "<group>"; };
16BFA66E14E195E9009704F8 /* ios_exception_minidump_generator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ios_exception_minidump_generator.h; sourceTree = "<group>"; };
16BFA67114E1965A009704F8 /* ios_exception_minidump_generator.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ios_exception_minidump_generator.mm; sourceTree = "<group>"; };
16C7C968147D4A4200776EAD /* BreakpadDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BreakpadDefines.h; sourceTree = "<group>"; };
16C7C96A147D4A4200776EAD /* Breakpad.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Breakpad.h; sourceTree = "<group>"; };
16C7C96B147D4A4200776EAD /* Breakpad.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = Breakpad.mm; sourceTree = "<group>"; };
16C7CB9E147D4A4300776EAD /* ConfigFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ConfigFile.h; sourceTree = "<group>"; };
16C7CB9F147D4A4300776EAD /* ConfigFile.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ConfigFile.mm; sourceTree = "<group>"; };
16C7CBAD147D4A4300776EAD /* breakpad_nlist_64.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = breakpad_nlist_64.cc; sourceTree = "<group>"; };
16C7CBAE147D4A4300776EAD /* breakpad_nlist_64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = breakpad_nlist_64.h; sourceTree = "<group>"; };
16C7CBAF147D4A4300776EAD /* dynamic_images.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = dynamic_images.cc; sourceTree = "<group>"; };
16C7CBB0147D4A4300776EAD /* dynamic_images.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dynamic_images.h; sourceTree = "<group>"; };
16C7CBB1147D4A4300776EAD /* exception_handler.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = exception_handler.cc; sourceTree = "<group>"; };
16C7CBB2147D4A4300776EAD /* exception_handler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = exception_handler.h; sourceTree = "<group>"; };
16C7CBB4147D4A4300776EAD /* minidump_generator.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = minidump_generator.cc; sourceTree = "<group>"; };
16C7CBB5147D4A4300776EAD /* minidump_generator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = minidump_generator.h; sourceTree = "<group>"; };
16C7CBBC147D4A4300776EAD /* protected_memory_allocator.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = protected_memory_allocator.cc; sourceTree = "<group>"; };
16C7CBBD147D4A4300776EAD /* protected_memory_allocator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = protected_memory_allocator.h; sourceTree = "<group>"; };
16C7CBEA147D4A4300776EAD /* uploader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = uploader.h; sourceTree = "<group>"; };
16C7CBEB147D4A4300776EAD /* uploader.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = uploader.mm; sourceTree = "<group>"; };
16C7CC04147D4A4300776EAD /* minidump_file_writer-inl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "minidump_file_writer-inl.h"; sourceTree = "<group>"; };
16C7CC05147D4A4300776EAD /* minidump_file_writer.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = minidump_file_writer.cc; sourceTree = "<group>"; };
16C7CC06147D4A4300776EAD /* minidump_file_writer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = minidump_file_writer.h; sourceTree = "<group>"; };
16C7CC07147D4A4300776EAD /* minidump_file_writer_unittest.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = minidump_file_writer_unittest.cc; sourceTree = "<group>"; };
16C7CC4A147D4A4300776EAD /* convert_UTF.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = convert_UTF.c; sourceTree = "<group>"; };
16C7CC4B147D4A4300776EAD /* convert_UTF.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = convert_UTF.h; sourceTree = "<group>"; };
16C7CC88147D4A4300776EAD /* GTMLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMLogger.h; sourceTree = "<group>"; };
16C7CC89147D4A4300776EAD /* GTMLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMLogger.m; sourceTree = "<group>"; };
16C7CC8A147D4A4300776EAD /* HTTPMultipartUpload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPMultipartUpload.h; sourceTree = "<group>"; };
16C7CC8B147D4A4300776EAD /* HTTPMultipartUpload.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPMultipartUpload.m; sourceTree = "<group>"; };
16C7CC93147D4A4300776EAD /* file_id.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = file_id.cc; sourceTree = "<group>"; };
16C7CC94147D4A4300776EAD /* file_id.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = file_id.h; sourceTree = "<group>"; };
16C7CC95147D4A4300776EAD /* macho_id.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = macho_id.cc; sourceTree = "<group>"; };
16C7CC96147D4A4300776EAD /* macho_id.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = macho_id.h; sourceTree = "<group>"; };
16C7CC9A147D4A4300776EAD /* macho_utilities.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = macho_utilities.cc; sourceTree = "<group>"; };
16C7CC9B147D4A4300776EAD /* macho_utilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = macho_utilities.h; sourceTree = "<group>"; };
16C7CC9C147D4A4300776EAD /* macho_walker.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = macho_walker.cc; sourceTree = "<group>"; };
16C7CC9D147D4A4300776EAD /* macho_walker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = macho_walker.h; sourceTree = "<group>"; };
16C7CC9F147D4A4300776EAD /* string_utilities.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = string_utilities.cc; sourceTree = "<group>"; };
16C7CCA0147D4A4300776EAD /* string_utilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = string_utilities.h; sourceTree = "<group>"; };
16C7CCA4147D4A4300776EAD /* md5.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = md5.cc; sourceTree = "<group>"; };
16C7CCA5147D4A4300776EAD /* md5.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = md5.h; sourceTree = "<group>"; };
16C7CCB9147D4A4300776EAD /* string_conversion.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = string_conversion.cc; sourceTree = "<group>"; };
16C7CCBA147D4A4300776EAD /* string_conversion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = string_conversion.h; sourceTree = "<group>"; };
16C92FAB150DF8330053D7BA /* BreakpadController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BreakpadController.h; sourceTree = "<group>"; };
16C92FAC150DF8330053D7BA /* BreakpadController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = BreakpadController.mm; sourceTree = "<group>"; };
1EEEB60C1720821900F7E689 /* simple_string_dictionary.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = simple_string_dictionary.cc; sourceTree = "<group>"; };
1EEEB60D1720821900F7E689 /* simple_string_dictionary.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = simple_string_dictionary.h; sourceTree = "<group>"; };
AA747D9E0F9514B9006C5449 /* Breakpad_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Breakpad_Prefix.pch; sourceTree = SOURCE_ROOT; };
AACBBE490F95108600F1A2B1 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
D2AAC07E0554694100DB518D /* libBreakpad.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libBreakpad.a; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
D2AAC07C0554694100DB518D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
AACBBE4A0F95108600F1A2B1 /* Foundation.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
034768DFFF38A50411DB9C8B /* Products */ = {
isa = PBXGroup;
children = (
D2AAC07E0554694100DB518D /* libBreakpad.a */,
);
name = Products;
sourceTree = "<group>";
};
0867D691FE84028FC02AAC07 /* Breakpad */ = {
isa = PBXGroup;
children = (
08FB77AEFE84172EC02AAC07 /* Classes */,
32C88DFF0371C24200C91783 /* Other Sources */,
0867D69AFE84028FC02AAC07 /* Frameworks */,
034768DFFF38A50411DB9C8B /* Products */,
);
name = Breakpad;
sourceTree = "<group>";
};
0867D69AFE84028FC02AAC07 /* Frameworks */ = {
isa = PBXGroup;
children = (
AACBBE490F95108600F1A2B1 /* Foundation.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
08FB77AEFE84172EC02AAC07 /* Classes */ = {
isa = PBXGroup;
children = (
16C7C965147D4A4200776EAD /* client */,
16C7CC47147D4A4300776EAD /* common */,
);
name = Classes;
sourceTree = "<group>";
};
16BFA66A14E195E9009704F8 /* handler */ = {
isa = PBXGroup;
children = (
16BFA67114E1965A009704F8 /* ios_exception_minidump_generator.mm */,
16BFA66E14E195E9009704F8 /* ios_exception_minidump_generator.h */,
);
path = handler;
sourceTree = "<group>";
};
16C7C965147D4A4200776EAD /* client */ = {
isa = PBXGroup;
children = (
16C7C966147D4A4200776EAD /* apple */,
16C7C969147D4A4200776EAD /* ios */,
16C7C99E147D4A4200776EAD /* mac */,
16C7CC04147D4A4300776EAD /* minidump_file_writer-inl.h */,
16C7CC05147D4A4300776EAD /* minidump_file_writer.cc */,
16C7CC06147D4A4300776EAD /* minidump_file_writer.h */,
16C7CC07147D4A4300776EAD /* minidump_file_writer_unittest.cc */,
);
name = client;
path = ..;
sourceTree = SOURCE_ROOT;
};
16C7C966147D4A4200776EAD /* apple */ = {
isa = PBXGroup;
children = (
16C7C967147D4A4200776EAD /* Framework */,
);
path = apple;
sourceTree = "<group>";
};
16C7C967147D4A4200776EAD /* Framework */ = {
isa = PBXGroup;
children = (
16C7C968147D4A4200776EAD /* BreakpadDefines.h */,
);
path = Framework;
sourceTree = "<group>";
};
16C7C969147D4A4200776EAD /* ios */ = {
isa = PBXGroup;
children = (
16C92FAB150DF8330053D7BA /* BreakpadController.h */,
16C92FAC150DF8330053D7BA /* BreakpadController.mm */,
16BFA66A14E195E9009704F8 /* handler */,
16C7C96A147D4A4200776EAD /* Breakpad.h */,
16C7C96B147D4A4200776EAD /* Breakpad.mm */,
);
path = ios;
sourceTree = "<group>";
};
16C7C99E147D4A4200776EAD /* mac */ = {
isa = PBXGroup;
children = (
16C7CB9D147D4A4300776EAD /* crash_generation */,
16C7CBAA147D4A4300776EAD /* handler */,
16C7CBC8147D4A4300776EAD /* sender */,
);
path = mac;
sourceTree = "<group>";
};
16C7CB9D147D4A4300776EAD /* crash_generation */ = {
isa = PBXGroup;
children = (
16C7CB9E147D4A4300776EAD /* ConfigFile.h */,
16C7CB9F147D4A4300776EAD /* ConfigFile.mm */,
);
path = crash_generation;
sourceTree = "<group>";
};
16C7CBAA147D4A4300776EAD /* handler */ = {
isa = PBXGroup;
children = (
16C7CBAD147D4A4300776EAD /* breakpad_nlist_64.cc */,
16C7CBAE147D4A4300776EAD /* breakpad_nlist_64.h */,
16C7CBAF147D4A4300776EAD /* dynamic_images.cc */,
16C7CBB0147D4A4300776EAD /* dynamic_images.h */,
16C7CBB1147D4A4300776EAD /* exception_handler.cc */,
16C7CBB2147D4A4300776EAD /* exception_handler.h */,
14569322182CE2C10029C465 /* mach_vm_compat.h */,
16C7CBB4147D4A4300776EAD /* minidump_generator.cc */,
16C7CBB5147D4A4300776EAD /* minidump_generator.h */,
16C7CBBC147D4A4300776EAD /* protected_memory_allocator.cc */,
16C7CBBD147D4A4300776EAD /* protected_memory_allocator.h */,
14569320182CE29F0029C465 /* ucontext_compat.h */,
);
path = handler;
sourceTree = "<group>";
};
16C7CBC8147D4A4300776EAD /* sender */ = {
isa = PBXGroup;
children = (
16C7CBEA147D4A4300776EAD /* uploader.h */,
16C7CBEB147D4A4300776EAD /* uploader.mm */,
);
path = sender;
sourceTree = "<group>";
};
16C7CC47147D4A4300776EAD /* common */ = {
isa = PBXGroup;
children = (
1EEEB60C1720821900F7E689 /* simple_string_dictionary.cc */,
1EEEB60D1720821900F7E689 /* simple_string_dictionary.h */,
16C7CC4A147D4A4300776EAD /* convert_UTF.c */,
16C7CC4B147D4A4300776EAD /* convert_UTF.h */,
16C7CC82147D4A4300776EAD /* mac */,
16C7CCA4147D4A4300776EAD /* md5.cc */,
16C7CCA5147D4A4300776EAD /* md5.h */,
16C7CCB9147D4A4300776EAD /* string_conversion.cc */,
16C7CCBA147D4A4300776EAD /* string_conversion.h */,
);
name = common;
path = ../../common;
sourceTree = SOURCE_ROOT;
};
16C7CC82147D4A4300776EAD /* mac */ = {
isa = PBXGroup;
children = (
16C7CC88147D4A4300776EAD /* GTMLogger.h */,
16C7CC89147D4A4300776EAD /* GTMLogger.m */,
16C7CC8A147D4A4300776EAD /* HTTPMultipartUpload.h */,
16C7CC8B147D4A4300776EAD /* HTTPMultipartUpload.m */,
16C7CC93147D4A4300776EAD /* file_id.cc */,
16C7CC94147D4A4300776EAD /* file_id.h */,
16C7CC95147D4A4300776EAD /* macho_id.cc */,
16C7CC96147D4A4300776EAD /* macho_id.h */,
16C7CC9A147D4A4300776EAD /* macho_utilities.cc */,
16C7CC9B147D4A4300776EAD /* macho_utilities.h */,
16C7CC9C147D4A4300776EAD /* macho_walker.cc */,
16C7CC9D147D4A4300776EAD /* macho_walker.h */,
16C7CC9F147D4A4300776EAD /* string_utilities.cc */,
16C7CCA0147D4A4300776EAD /* string_utilities.h */,
);
path = mac;
sourceTree = "<group>";
};
32C88DFF0371C24200C91783 /* Other Sources */ = {
isa = PBXGroup;
children = (
AA747D9E0F9514B9006C5449 /* Breakpad_Prefix.pch */,
);
name = "Other Sources";
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
D2AAC07A0554694100DB518D /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
AA747D9F0F9514B9006C5449 /* Breakpad_Prefix.pch in Headers */,
16C7CCCB147D4A4300776EAD /* BreakpadDefines.h in Headers */,
16C7CCCC147D4A4300776EAD /* Breakpad.h in Headers */,
16C7CDE8147D4A4300776EAD /* ConfigFile.h in Headers */,
14569321182CE29F0029C465 /* ucontext_compat.h in Headers */,
16C7CDF6147D4A4300776EAD /* breakpad_nlist_64.h in Headers */,
16C7CDF8147D4A4300776EAD /* dynamic_images.h in Headers */,
16C7CDFA147D4A4300776EAD /* exception_handler.h in Headers */,
16C7CDFD147D4A4300776EAD /* minidump_generator.h in Headers */,
16C7CDFF147D4A4300776EAD /* protected_memory_allocator.h in Headers */,
16C7CE08147D4A4300776EAD /* uploader.h in Headers */,
16C7CE18147D4A4300776EAD /* minidump_file_writer-inl.h in Headers */,
16C7CE1A147D4A4300776EAD /* minidump_file_writer.h in Headers */,
16C7CE41147D4A4300776EAD /* convert_UTF.h in Headers */,
16C7CE78147D4A4300776EAD /* GTMLogger.h in Headers */,
16C7CE7A147D4A4300776EAD /* HTTPMultipartUpload.h in Headers */,
16C7CE84147D4A4300776EAD /* file_id.h in Headers */,
16C7CE86147D4A4300776EAD /* macho_id.h in Headers */,
16C7CE8B147D4A4300776EAD /* macho_utilities.h in Headers */,
16C7CE8D147D4A4300776EAD /* macho_walker.h in Headers */,
16C7CE90147D4A4300776EAD /* string_utilities.h in Headers */,
16C7CE94147D4A4300776EAD /* md5.h in Headers */,
16C7CEA8147D4A4300776EAD /* string_conversion.h in Headers */,
16BFA67014E195E9009704F8 /* ios_exception_minidump_generator.h in Headers */,
16C92FAD150DF8330053D7BA /* BreakpadController.h in Headers */,
1EEEB6101720821900F7E689 /* simple_string_dictionary.h in Headers */,
14569323182CE2C10029C465 /* mach_vm_compat.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
D2AAC07D0554694100DB518D /* Breakpad */ = {
isa = PBXNativeTarget;
buildConfigurationList = 1DEB921E08733DC00010E9CD /* Build configuration list for PBXNativeTarget "Breakpad" */;
buildPhases = (
D2AAC07A0554694100DB518D /* Headers */,
D2AAC07B0554694100DB518D /* Sources */,
D2AAC07C0554694100DB518D /* Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = Breakpad;
productName = Breakpad;
productReference = D2AAC07E0554694100DB518D /* libBreakpad.a */;
productType = "com.apple.product-type.library.static";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
0867D690FE84028FC02AAC07 /* Project object */ = {
isa = PBXProject;
attributes = {
};
buildConfigurationList = 1DEB922208733DC00010E9CD /* Build configuration list for PBXProject "Breakpad" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 1;
knownRegions = (
English,
Japanese,
French,
German,
da,
de,
es,
fr,
it,
ja,
nl,
no,
sl,
sv,
tr,
);
mainGroup = 0867D691FE84028FC02AAC07 /* Breakpad */;
productRefGroup = 034768DFFF38A50411DB9C8B /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
D2AAC07D0554694100DB518D /* Breakpad */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
D2AAC07B0554694100DB518D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
16C7CCCD147D4A4300776EAD /* Breakpad.mm in Sources */,
16C7CDE9147D4A4300776EAD /* ConfigFile.mm in Sources */,
16C7CDF5147D4A4300776EAD /* breakpad_nlist_64.cc in Sources */,
16C7CDF7147D4A4300776EAD /* dynamic_images.cc in Sources */,
16C7CDF9147D4A4300776EAD /* exception_handler.cc in Sources */,
16C7CDFC147D4A4300776EAD /* minidump_generator.cc in Sources */,
16C7CDFE147D4A4300776EAD /* protected_memory_allocator.cc in Sources */,
16C7CE09147D4A4300776EAD /* uploader.mm in Sources */,
16C7CE19147D4A4300776EAD /* minidump_file_writer.cc in Sources */,
16C7CE40147D4A4300776EAD /* convert_UTF.c in Sources */,
16C7CE79147D4A4300776EAD /* GTMLogger.m in Sources */,
16C7CE7B147D4A4300776EAD /* HTTPMultipartUpload.m in Sources */,
16C7CE83147D4A4300776EAD /* file_id.cc in Sources */,
16C7CE85147D4A4300776EAD /* macho_id.cc in Sources */,
16C7CE8A147D4A4300776EAD /* macho_utilities.cc in Sources */,
16C7CE8C147D4A4300776EAD /* macho_walker.cc in Sources */,
16C7CE8F147D4A4300776EAD /* string_utilities.cc in Sources */,
16C7CE93147D4A4300776EAD /* md5.cc in Sources */,
16C7CEA7147D4A4300776EAD /* string_conversion.cc in Sources */,
16BFA67214E1965A009704F8 /* ios_exception_minidump_generator.mm in Sources */,
16C92FAE150DF8330053D7BA /* BreakpadController.mm in Sources */,
1EEEB60F1720821900F7E689 /* simple_string_dictionary.cc in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
1DEB921F08733DC00010E9CD /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
COPY_PHASE_STRIP = NO;
DSTROOT = /tmp/Breakpad.dst;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"\"$(SRCROOT)/../mac/build/Debug\"",
);
GCC_DYNAMIC_NO_PIC = NO;
GCC_MODEL_TUNING = G5;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = Breakpad_Prefix.pch;
INSTALL_PATH = /usr/local/lib;
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"\"$(SRCROOT)/../mac/build/Breakpad.build/Debug/Breakpad.build/Objects-normal/i386\"",
"\"$(SRCROOT)/../mac/build/Breakpad.build/Debug/Breakpad.build/Objects-normal/x86_64\"",
"\"$(SRCROOT)/../mac/build/Breakpad.build/Debug/breakpadUtilities.build/Objects-normal/i386\"",
"\"$(SRCROOT)/../mac/build/Breakpad.build/Debug/breakpadUtilities.build/Objects-normal/x86_64\"",
"\"$(SRCROOT)/../mac/build/Breakpad.build/Debug/gtest.build/Objects-normal/i386\"",
"\"$(SRCROOT)/../mac/build/Breakpad.build/Debug/gtest.build/Objects-normal/x86_64\"",
"\"$(SRCROOT)/../mac/build/Debug\"",
"\"$(SRCROOT)/../mac/gcov\"",
);
PRODUCT_NAME = Breakpad;
};
name = Debug;
};
1DEB922008733DC00010E9CD /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
DSTROOT = /tmp/Breakpad.dst;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"\"$(SRCROOT)/../mac/build/Debug\"",
);
GCC_MODEL_TUNING = G5;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = Breakpad_Prefix.pch;
INSTALL_PATH = /usr/local/lib;
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"\"$(SRCROOT)/../mac/build/Breakpad.build/Debug/Breakpad.build/Objects-normal/i386\"",
"\"$(SRCROOT)/../mac/build/Breakpad.build/Debug/Breakpad.build/Objects-normal/x86_64\"",
"\"$(SRCROOT)/../mac/build/Breakpad.build/Debug/breakpadUtilities.build/Objects-normal/i386\"",
"\"$(SRCROOT)/../mac/build/Breakpad.build/Debug/breakpadUtilities.build/Objects-normal/x86_64\"",
"\"$(SRCROOT)/../mac/build/Breakpad.build/Debug/gtest.build/Objects-normal/i386\"",
"\"$(SRCROOT)/../mac/build/Breakpad.build/Debug/gtest.build/Objects-normal/x86_64\"",
"\"$(SRCROOT)/../mac/build/Debug\"",
"\"$(SRCROOT)/../mac/gcov\"",
);
PRODUCT_NAME = Breakpad;
};
name = Release;
};
1DEB922308733DC00010E9CD /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
GCC_C_LANGUAGE_STANDARD = c99;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_SHADOW = YES;
GCC_WARN_SIGN_COMPARE = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNKNOWN_PRAGMAS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_LABEL = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = (
../../,
../../client/apple/Framework,
../../common/mac,
);
OTHER_LDFLAGS = "-ObjC";
SDKROOT = iphoneos;
};
name = Debug;
};
1DEB922408733DC00010E9CD /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
GCC_C_LANGUAGE_STANDARD = c99;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_SHADOW = YES;
GCC_WARN_SIGN_COMPARE = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNKNOWN_PRAGMAS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_LABEL = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = (
../../,
../../client/apple/Framework,
../../common/mac,
);
OTHER_LDFLAGS = "-ObjC";
SDKROOT = iphoneos;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
1DEB921E08733DC00010E9CD /* Build configuration list for PBXNativeTarget "Breakpad" */ = {
isa = XCConfigurationList;
buildConfigurations = (
1DEB921F08733DC00010E9CD /* Debug */,
1DEB922008733DC00010E9CD /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
1DEB922208733DC00010E9CD /* Build configuration list for PBXProject "Breakpad" */ = {
isa = XCConfigurationList;
buildConfigurations = (
1DEB922308733DC00010E9CD /* Debug */,
1DEB922408733DC00010E9CD /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 0867D690FE84028FC02AAC07 /* Project object */;
}

View File

@@ -0,0 +1,127 @@
// Copyright (c) 2012, 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_IOS_HANDLER_IOS_BREAKPAD_CONTROLLER_H_
#define CLIENT_IOS_HANDLER_IOS_BREAKPAD_CONTROLLER_H_
#import <Foundation/Foundation.h>
#import "client/ios/Breakpad.h"
// This class is used to offer a higher level API around BreakpadRef. It
// configures it, ensures thread-safety, and sends crash reports back to the
// collecting server. By default, no crash reports are sent, the user must call
// |setUploadingEnabled:YES| to start the uploading.
@interface BreakpadController : NSObject {
@private
// The dispatch queue that will own the breakpad reference.
dispatch_queue_t queue_;
// Instance of Breakpad crash reporter. This is owned by the queue, but can
// be created on the main thread at startup.
BreakpadRef breakpadRef_;
// The dictionary that contains configuration for breakpad. Modifying it
// should only happen when the controller is not started. The initial value
// is the infoDictionary of the bundle of the application.
NSMutableDictionary* configuration_;
// Whether or not crash reports should be uploaded.
BOOL enableUploads_;
// Whether the controller has been started on the main thread. This is only
// used to assert the initialization order is correct.
BOOL started_;
// The interval to wait between two uploads. Value is 0 if no upload must be
// done.
int uploadIntervalInSeconds_;
// The dictionary that contains additional server parameters to send when
// uploading crash reports.
NSDictionary* uploadTimeParameters_;
}
// Singleton.
+ (BreakpadController*)sharedInstance;
// Update the controller configuration. Merges its old configuration with the
// new one. Merge is done by replacing the old values by the new values.
- (void)updateConfiguration:(NSDictionary*)configuration;
// Reset the controller configuration to its initial value, which is the
// infoDictionary of the bundle of the application.
- (void)resetConfiguration;
// Configure the URL to upload the report to. This must be called at least once
// if the URL is not in the bundle information.
- (void)setUploadingURL:(NSString*)url;
// Set the minimal interval between two uploads in seconds. This must be called
// at least once if the interval is not in the bundle information. A value of 0
// will prevent uploads.
- (void)setUploadInterval:(int)intervalInSeconds;
// Set additional server parameters to send when uploading crash reports.
- (void)setParametersToAddAtUploadTime:(NSDictionary*)uploadTimeParameters;
// Specify an upload parameter that will be added to the crash report when a
// crash report is generated. See |BreakpadAddUploadParameter|.
- (void)addUploadParameter:(NSString*)value forKey:(NSString*)key;
// Remove a previously-added parameter from the upload parameter set. See
// |BreakpadRemoveUploadParameter|.
- (void)removeUploadParameterForKey:(NSString*)key;
// Access the underlying BreakpadRef. This method is asynchronous, and will be
// executed on the thread owning the BreakpadRef variable. Moreover, if the
// controller is not started, the block will be called with a NULL parameter.
- (void)withBreakpadRef:(void(^)(BreakpadRef))callback;
// Starts the BreakpadController by registering crash handlers. If
// |onCurrentThread| is YES, all setup is done on the current thread, otherwise
// it is done on a private queue.
- (void)start:(BOOL)onCurrentThread;
// Unregisters the crash handlers.
- (void)stop;
// Enables or disables uploading of crash reports, but does not stop the
// BreakpadController.
- (void)setUploadingEnabled:(BOOL)enabled;
// Check if there is currently a crash report to upload.
- (void)hasReportToUpload:(void(^)(BOOL))callback;
// Get the number of crash reports waiting to upload.
- (void)getCrashReportCount:(void(^)(int))callback;
@end
#endif // CLIENT_IOS_HANDLER_IOS_BREAKPAD_CONTROLLER_H_

View File

@@ -0,0 +1,318 @@
// Copyright (c) 2012, 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.
#import "BreakpadController.h"
#import <UIKit/UIKit.h>
#include <asl.h>
#include <execinfo.h>
#include <signal.h>
#include <unistd.h>
#include <sys/sysctl.h>
#include <common/scoped_ptr.h>
#pragma mark -
#pragma mark Private Methods
@interface BreakpadController ()
// Init the singleton instance.
- (id)initSingleton;
// Load a crash report and send it to the server.
- (void)sendStoredCrashReports;
// Returns when a report can be sent. |-1| means never, |0| means that a report
// can be sent immediately, a positive number is the number of seconds to wait
// before being allowed to upload a report.
- (int)sendDelay;
// Notifies that a report will be sent, and update the last sending time
// accordingly.
- (void)reportWillBeSent;
@end
#pragma mark -
#pragma mark Anonymous namespace
namespace {
// The name of the user defaults key for the last submission to the crash
// server.
NSString* const kLastSubmission = @"com.google.Breakpad.LastSubmission";
// Returns a NSString describing the current platform.
NSString* GetPlatform() {
// Name of the system call for getting the platform.
static const char kHwMachineSysctlName[] = "hw.machine";
NSString* result = nil;
size_t size = 0;
if (sysctlbyname(kHwMachineSysctlName, NULL, &size, NULL, 0) || size == 0)
return nil;
google_breakpad::scoped_array<char> machine(new char[size]);
if (sysctlbyname(kHwMachineSysctlName, machine.get(), &size, NULL, 0) == 0)
result = [NSString stringWithUTF8String:machine.get()];
return result;
}
} // namespace
#pragma mark -
#pragma mark BreakpadController Implementation
@implementation BreakpadController
+ (BreakpadController*)sharedInstance {
@synchronized(self) {
static BreakpadController* sharedInstance_ =
[[BreakpadController alloc] initSingleton];
return sharedInstance_;
}
}
- (id)init {
return nil;
}
- (id)initSingleton {
self = [super init];
if (self) {
queue_ = dispatch_queue_create("com.google.BreakpadQueue", NULL);
enableUploads_ = NO;
started_ = NO;
[self resetConfiguration];
}
return self;
}
// Since this class is a singleton, this method is not expected to be called.
- (void)dealloc {
assert(!breakpadRef_);
dispatch_release(queue_);
[configuration_ release];
[uploadTimeParameters_ release];
[super dealloc];
}
#pragma mark -
- (void)start:(BOOL)onCurrentThread {
if (started_)
return;
started_ = YES;
void(^startBlock)() = ^{
assert(!breakpadRef_);
breakpadRef_ = BreakpadCreate(configuration_);
if (breakpadRef_) {
BreakpadAddUploadParameter(breakpadRef_, @"platform", GetPlatform());
}
};
if (onCurrentThread)
startBlock();
else
dispatch_async(queue_, startBlock);
}
- (void)stop {
if (!started_)
return;
started_ = NO;
dispatch_sync(queue_, ^{
if (breakpadRef_) {
BreakpadRelease(breakpadRef_);
breakpadRef_ = NULL;
}
});
}
- (void)setUploadingEnabled:(BOOL)enabled {
NSAssert(started_,
@"The controller must be started before setUploadingEnabled is called");
dispatch_async(queue_, ^{
if (enabled == enableUploads_)
return;
if (enabled) {
// Set this before calling doSendStoredCrashReport, because that
// calls sendDelay, which in turn checks this flag.
enableUploads_ = YES;
[self sendStoredCrashReports];
} else {
enableUploads_ = NO;
[NSObject cancelPreviousPerformRequestsWithTarget:self
selector:@selector(sendStoredCrashReports)
object:nil];
}
});
}
- (void)updateConfiguration:(NSDictionary*)configuration {
NSAssert(!started_,
@"The controller must not be started when updateConfiguration is called");
[configuration_ addEntriesFromDictionary:configuration];
NSString* uploadInterval =
[configuration_ valueForKey:@BREAKPAD_REPORT_INTERVAL];
if (uploadInterval)
[self setUploadInterval:[uploadInterval intValue]];
}
- (void)resetConfiguration {
NSAssert(!started_,
@"The controller must not be started when resetConfiguration is called");
[configuration_ autorelease];
configuration_ = [[[NSBundle mainBundle] infoDictionary] mutableCopy];
NSString* uploadInterval =
[configuration_ valueForKey:@BREAKPAD_REPORT_INTERVAL];
[self setUploadInterval:[uploadInterval intValue]];
[self setParametersToAddAtUploadTime:nil];
}
- (void)setUploadingURL:(NSString*)url {
NSAssert(!started_,
@"The controller must not be started when setUploadingURL is called");
[configuration_ setValue:url forKey:@BREAKPAD_URL];
}
- (void)setUploadInterval:(int)intervalInSeconds {
NSAssert(!started_,
@"The controller must not be started when setUploadInterval is called");
[configuration_ removeObjectForKey:@BREAKPAD_REPORT_INTERVAL];
uploadIntervalInSeconds_ = intervalInSeconds;
if (uploadIntervalInSeconds_ < 0)
uploadIntervalInSeconds_ = 0;
}
- (void)setParametersToAddAtUploadTime:(NSDictionary*)uploadTimeParameters {
NSAssert(!started_, @"The controller must not be started when "
"setParametersToAddAtUploadTime is called");
[uploadTimeParameters_ autorelease];
uploadTimeParameters_ = [uploadTimeParameters copy];
}
- (void)addUploadParameter:(NSString*)value forKey:(NSString*)key {
NSAssert(started_,
@"The controller must be started before addUploadParameter is called");
dispatch_async(queue_, ^{
if (breakpadRef_)
BreakpadAddUploadParameter(breakpadRef_, key, value);
});
}
- (void)removeUploadParameterForKey:(NSString*)key {
NSAssert(started_, @"The controller must be started before "
"removeUploadParameterForKey is called");
dispatch_async(queue_, ^{
if (breakpadRef_)
BreakpadRemoveUploadParameter(breakpadRef_, key);
});
}
- (void)withBreakpadRef:(void(^)(BreakpadRef))callback {
NSAssert(started_,
@"The controller must be started before withBreakpadRef is called");
dispatch_async(queue_, ^{
callback(breakpadRef_);
});
}
- (void)hasReportToUpload:(void(^)(BOOL))callback {
NSAssert(started_, @"The controller must be started before "
"hasReportToUpload is called");
dispatch_async(queue_, ^{
callback(breakpadRef_ && (BreakpadGetCrashReportCount(breakpadRef_) > 0));
});
}
- (void)getCrashReportCount:(void(^)(int))callback {
NSAssert(started_, @"The controller must be started before "
"getCrashReportCount is called");
dispatch_async(queue_, ^{
callback(breakpadRef_ ? BreakpadGetCrashReportCount(breakpadRef_) : 0);
});
}
#pragma mark -
- (int)sendDelay {
if (!breakpadRef_ || uploadIntervalInSeconds_ <= 0 || !enableUploads_)
return -1;
// To prevent overloading the crash server, crashes are not sent than one
// report every |uploadIntervalInSeconds_|. A value in the user defaults is
// used to keep the time of the last upload.
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSNumber *lastTimeNum = [userDefaults objectForKey:kLastSubmission];
NSTimeInterval lastTime = lastTimeNum ? [lastTimeNum floatValue] : 0;
NSTimeInterval spanSeconds = CFAbsoluteTimeGetCurrent() - lastTime;
if (spanSeconds >= uploadIntervalInSeconds_)
return 0;
return uploadIntervalInSeconds_ - static_cast<int>(spanSeconds);
}
- (void)reportWillBeSent {
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:[NSNumber numberWithDouble:CFAbsoluteTimeGetCurrent()]
forKey:kLastSubmission];
[userDefaults synchronize];
}
- (void)sendStoredCrashReports {
dispatch_async(queue_, ^{
if (BreakpadGetCrashReportCount(breakpadRef_) == 0)
return;
int timeToWait = [self sendDelay];
// Unable to ever send report.
if (timeToWait == -1)
return;
// A report can be sent now.
if (timeToWait == 0) {
[self reportWillBeSent];
BreakpadUploadNextReport(breakpadRef_, uploadTimeParameters_);
// If more reports must be sent, make sure this method is called again.
if (BreakpadGetCrashReportCount(breakpadRef_) > 0)
timeToWait = uploadIntervalInSeconds_;
}
// A report must be sent later.
if (timeToWait > 0)
[self performSelector:@selector(sendStoredCrashReports)
withObject:nil
afterDelay:timeToWait];
});
}
@end

View File

@@ -0,0 +1,7 @@
//
// Prefix header for all source files of the 'CocoaTouchStaticLibrary' target in the 'CocoaTouchStaticLibrary' project.
//
#ifdef __OBJC__
#import <Foundation/Foundation.h>
#endif

View File

@@ -0,0 +1,74 @@
// Copyright (c) 2012, 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.
// ios_exception_minidump_generator.h: Create a fake minidump from a
// NSException.
#ifndef CLIENT_IOS_HANDLER_IOS_EXCEPTION_MINIDUMP_GENERATOR_H_
#define CLIENT_IOS_HANDLER_IOS_EXCEPTION_MINIDUMP_GENERATOR_H_
#include <Foundation/Foundation.h>
#include "client/mac/handler/minidump_generator.h"
namespace google_breakpad {
class IosExceptionMinidumpGenerator : public MinidumpGenerator {
public:
explicit IosExceptionMinidumpGenerator(NSException *exception);
virtual ~IosExceptionMinidumpGenerator();
protected:
virtual bool WriteExceptionStream(MDRawDirectory *exception_stream);
virtual bool WriteThreadStream(mach_port_t thread_id, MDRawThread *thread);
private:
// Get the crashing program counter from the exception.
uintptr_t GetPCFromException();
// Get the crashing link register from the exception.
uintptr_t GetLRFromException();
// Write a virtual thread context for the crashing site.
bool WriteCrashingContext(MDLocationDescriptor *register_location);
// Per-CPU implementations of the above method.
#ifdef HAS_ARM_SUPPORT
bool WriteCrashingContextARM(MDLocationDescriptor *register_location);
#endif
#ifdef HAS_ARM64_SUPPORT
bool WriteCrashingContextARM64(MDLocationDescriptor *register_location);
#endif
NSArray *return_addresses_;
};
} // namespace google_breakpad
#endif // CLIENT_IOS_HANDLER_IOS_EXCEPTION_MINIDUMP_GENERATOR_H_

View File

@@ -0,0 +1,208 @@
// Copyright (c) 2012, 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 "client/ios/handler/ios_exception_minidump_generator.h"
#include <pthread.h>
#include "google_breakpad/common/minidump_cpu_arm.h"
#include "google_breakpad/common/minidump_cpu_arm64.h"
#include "google_breakpad/common/minidump_exception_mac.h"
#include "client/minidump_file_writer-inl.h"
#include "common/scoped_ptr.h"
#if defined(HAS_ARM_SUPPORT) && defined(HAS_ARM64_SUPPORT)
#error "This file should be compiled for only one architecture at a time"
#endif
namespace {
const int kExceptionType = EXC_SOFTWARE;
const int kExceptionCode = MD_EXCEPTION_CODE_MAC_NS_EXCEPTION;
#if defined(HAS_ARM_SUPPORT) || defined(HAS_ARM64_SUPPORT)
const uintptr_t kExpectedFinalFp = sizeof(uintptr_t);
const uintptr_t kExpectedFinalSp = 0;
// Append the given value to the sp position of the stack represented
// by memory.
void AppendToMemory(uint8_t *memory, uintptr_t sp, uintptr_t data) {
memcpy(memory + sp, &data, sizeof(data));
}
#endif
} // namespace
namespace google_breakpad {
IosExceptionMinidumpGenerator::IosExceptionMinidumpGenerator(
NSException *exception)
: MinidumpGenerator(mach_task_self(), 0) {
return_addresses_ = [[exception callStackReturnAddresses] retain];
SetExceptionInformation(kExceptionType,
kExceptionCode,
0,
pthread_mach_thread_np(pthread_self()));
}
IosExceptionMinidumpGenerator::~IosExceptionMinidumpGenerator() {
[return_addresses_ release];
}
bool IosExceptionMinidumpGenerator::WriteCrashingContext(
MDLocationDescriptor *register_location) {
#ifdef HAS_ARM_SUPPORT
return WriteCrashingContextARM(register_location);
#elif defined(HAS_ARM64_SUPPORT)
return WriteCrashingContextARM64(register_location);
#else
assert(false);
return false;
#endif
}
#ifdef HAS_ARM_SUPPORT
bool IosExceptionMinidumpGenerator::WriteCrashingContextARM(
MDLocationDescriptor *register_location) {
TypedMDRVA<MDRawContextARM> context(&writer_);
if (!context.Allocate())
return false;
*register_location = context.location();
MDRawContextARM *context_ptr = context.get();
memset(context_ptr, 0, sizeof(MDRawContextARM));
context_ptr->context_flags = MD_CONTEXT_ARM_FULL;
context_ptr->iregs[MD_CONTEXT_ARM_REG_IOS_FP] = kExpectedFinalFp; // FP
context_ptr->iregs[MD_CONTEXT_ARM_REG_SP] = kExpectedFinalSp; // SP
context_ptr->iregs[MD_CONTEXT_ARM_REG_LR] = GetLRFromException(); // LR
context_ptr->iregs[MD_CONTEXT_ARM_REG_PC] = GetPCFromException(); // PC
return true;
}
#endif
#ifdef HAS_ARM64_SUPPORT
bool IosExceptionMinidumpGenerator::WriteCrashingContextARM64(
MDLocationDescriptor *register_location) {
TypedMDRVA<MDRawContextARM64> context(&writer_);
if (!context.Allocate())
return false;
*register_location = context.location();
MDRawContextARM64 *context_ptr = context.get();
memset(context_ptr, 0, sizeof(*context_ptr));
context_ptr->context_flags = MD_CONTEXT_ARM64_FULL;
context_ptr->iregs[MD_CONTEXT_ARM64_REG_FP] = kExpectedFinalFp; // FP
context_ptr->iregs[MD_CONTEXT_ARM64_REG_SP] = kExpectedFinalSp; // SP
context_ptr->iregs[MD_CONTEXT_ARM64_REG_LR] = GetLRFromException(); // LR
context_ptr->iregs[MD_CONTEXT_ARM64_REG_PC] = GetPCFromException(); // PC
return true;
}
#endif
uintptr_t IosExceptionMinidumpGenerator::GetPCFromException() {
return [[return_addresses_ objectAtIndex:0] unsignedIntegerValue];
}
uintptr_t IosExceptionMinidumpGenerator::GetLRFromException() {
return [[return_addresses_ objectAtIndex:1] unsignedIntegerValue];
}
bool IosExceptionMinidumpGenerator::WriteExceptionStream(
MDRawDirectory *exception_stream) {
#if defined(HAS_ARM_SUPPORT) || defined(HAS_ARM64_SUPPORT)
TypedMDRVA<MDRawExceptionStream> exception(&writer_);
if (!exception.Allocate())
return false;
exception_stream->stream_type = MD_EXCEPTION_STREAM;
exception_stream->location = exception.location();
MDRawExceptionStream *exception_ptr = exception.get();
exception_ptr->thread_id = pthread_mach_thread_np(pthread_self());
// This naming is confusing, but it is the proper translation from
// mach naming to minidump naming.
exception_ptr->exception_record.exception_code = kExceptionType;
exception_ptr->exception_record.exception_flags = kExceptionCode;
if (!WriteCrashingContext(&exception_ptr->thread_context))
return false;
exception_ptr->exception_record.exception_address = GetPCFromException();
return true;
#else
return MinidumpGenerator::WriteExceptionStream(exception_stream);
#endif
}
bool IosExceptionMinidumpGenerator::WriteThreadStream(mach_port_t thread_id,
MDRawThread *thread) {
#if defined(HAS_ARM_SUPPORT) || defined(HAS_ARM64_SUPPORT)
if (pthread_mach_thread_np(pthread_self()) != thread_id)
return MinidumpGenerator::WriteThreadStream(thread_id, thread);
size_t frame_count = [return_addresses_ count];
UntypedMDRVA memory(&writer_);
size_t pointer_size = sizeof(uintptr_t);
size_t frame_record_size = 2 * pointer_size;
size_t stack_size = frame_record_size * (frame_count - 1) + pointer_size;
if (!memory.Allocate(stack_size))
return false;
scoped_array<uint8_t> stack_memory(new uint8_t[stack_size]);
uintptr_t sp = stack_size - pointer_size;
uintptr_t fp = 0;
uintptr_t lr = 0;
for (int current_frame = frame_count - 1;
current_frame > 0;
--current_frame) {
AppendToMemory(stack_memory.get(), sp, lr);
sp -= pointer_size;
AppendToMemory(stack_memory.get(), sp, fp);
fp = sp;
sp -= pointer_size;
lr = [[return_addresses_ objectAtIndex:current_frame] unsignedIntegerValue];
}
if (!memory.Copy(stack_memory.get(), stack_size))
return false;
assert(sp == kExpectedFinalSp);
assert(fp == kExpectedFinalFp);
assert(lr == GetLRFromException());
thread->stack.start_of_memory_range = sp;
thread->stack.memory = memory.location();
memory_blocks_.push_back(thread->stack);
if (!WriteCrashingContext(&thread->thread_context))
return false;
thread->thread_id = thread_id;
return true;
#else
return MinidumpGenerator::WriteThreadStream(thread_id, thread);
#endif
}
} // namespace google_breakpad

View File

@@ -34,7 +34,16 @@ namespace google_breakpad {
class CrashGenerationServer;
struct ClientInfo {
class ClientInfo {
public:
ClientInfo(pid_t pid, CrashGenerationServer* crash_server)
: crash_server_(crash_server),
pid_(pid) {}
CrashGenerationServer* crash_server() const { return crash_server_; }
pid_t pid() const { return pid_; }
private:
CrashGenerationServer* crash_server_;
pid_t pid_;
};

View File

@@ -35,6 +35,7 @@
#include "client/linux/crash_generation/crash_generation_client.h"
#include "common/linux/eintr_wrapper.h"
#include "common/linux/ignore_ret.h"
#include "common/linux/linux_libc_support.h"
#include "third_party/lss/linux_syscall_support.h"
@@ -67,12 +68,14 @@ CrashGenerationClient::RequestDump(const void* blob, size_t blob_size)
int* p = reinterpret_cast<int*>(CMSG_DATA(hdr));
*p = fds[1];
HANDLE_EINTR(sys_sendmsg(server_fd_, &msg, 0));
ssize_t ret = HANDLE_EINTR(sys_sendmsg(server_fd_, &msg, 0));
sys_close(fds[1]);
if (ret <= 0)
return false;
// wait for an ACK from the server
char b;
HANDLE_EINTR(sys_read(fds[0], &b, 1));
IGNORE_RET(HANDLE_EINTR(sys_read(fds[0], &b, 1)));
return true;
}

View File

@@ -90,7 +90,7 @@ GetInodeForProcPath(ino_t* inode_out, const char* path)
}
char* endptr;
const u_int64_t inode_ul =
const uint64_t inode_ul =
strtoull(buf + sizeof(kSocketLinkPrefix) - 1, &endptr, 10);
if (*endptr != ']')
return false;
@@ -170,7 +170,7 @@ CrashGenerationServer::CrashGenerationServer(
OnClientExitingCallback exit_callback,
void* exit_context,
bool generate_dumps,
const std::string* dump_path) :
const string* dump_path) :
server_fd_(listen_fd),
dump_callback_(dump_callback),
dump_context_(dump_context),
@@ -349,7 +349,7 @@ CrashGenerationServer::ClientEvent(short revents)
// A nasty process could try and send us too many descriptors and
// force a leak.
for (unsigned i = 0; i < num_fds; ++i)
HANDLE_EINTR(close(reinterpret_cast<int*>(CMSG_DATA(hdr))[i]));
close(reinterpret_cast<int*>(CMSG_DATA(hdr))[i]);
return true;
} else {
signal_fd = reinterpret_cast<int*>(CMSG_DATA(hdr))[0];
@@ -363,7 +363,7 @@ CrashGenerationServer::ClientEvent(short revents)
if (crashing_pid == -1 || signal_fd == -1) {
if (signal_fd)
HANDLE_EINTR(close(signal_fd));
close(signal_fd);
return true;
}
@@ -375,31 +375,28 @@ CrashGenerationServer::ClientEvent(short revents)
ino_t inode_number;
if (!GetInodeForFileDescriptor(&inode_number, signal_fd)) {
HANDLE_EINTR(close(signal_fd));
close(signal_fd);
return true;
}
if (!FindProcessHoldingSocket(&crashing_pid, inode_number - 1)) {
HANDLE_EINTR(close(signal_fd));
close(signal_fd);
return true;
}
std::string minidump_filename;
string minidump_filename;
if (!MakeMinidumpFilename(minidump_filename))
return true;
if (!google_breakpad::WriteMinidump(minidump_filename.c_str(),
crashing_pid, crash_context,
kCrashContextSize)) {
HANDLE_EINTR(close(signal_fd));
close(signal_fd);
return true;
}
if (dump_callback_) {
ClientInfo info;
info.crash_server_ = this;
info.pid_ = crashing_pid;
ClientInfo info(crashing_pid, this);
dump_callback_(dump_context_, &info, &minidump_filename);
}
@@ -413,7 +410,7 @@ CrashGenerationServer::ClientEvent(short revents)
msg.msg_iovlen = 1;
HANDLE_EINTR(sendmsg(signal_fd, &msg, MSG_DONTWAIT | MSG_NOSIGNAL));
HANDLE_EINTR(close(signal_fd));
close(signal_fd);
return true;
}
@@ -440,7 +437,7 @@ CrashGenerationServer::ControlEvent(short revents)
}
bool
CrashGenerationServer::MakeMinidumpFilename(std::string& outFilename)
CrashGenerationServer::MakeMinidumpFilename(string& outFilename)
{
GUID guid;
char guidString[kGUIDStringLength+1];

View File

@@ -34,6 +34,8 @@
#include <string>
#include "common/using_std_string.h"
namespace google_breakpad {
class ClientInfo;
@@ -45,7 +47,7 @@ public:
// be thread safe.
typedef void (*OnClientDumpRequestCallback)(void* context,
const ClientInfo* client_info,
const std::string* file_path);
const string* file_path);
typedef void (*OnClientExitingCallback)(void* context,
const ClientInfo* client_info);
@@ -69,7 +71,7 @@ public:
OnClientExitingCallback exit_callback,
void* exit_context,
bool generate_dumps,
const std::string* dump_path);
const string* dump_path);
~CrashGenerationServer();
@@ -100,7 +102,7 @@ private:
bool ControlEvent(short revents);
// Return a unique filename at which a minidump can be written
bool MakeMinidumpFilename(std::string& outFilename);
bool MakeMinidumpFilename(string& outFilename);
// Trampoline to |Run()|
static void* ThreadMain(void* arg);
@@ -115,7 +117,7 @@ private:
bool generate_dumps_;
std::string dump_dir_;
string dump_dir_;
bool started_;

View File

@@ -77,27 +77,27 @@
#include <sys/wait.h>
#include <unistd.h>
#if !defined(__ANDROID__)
#include <sys/signal.h>
#include <sys/ucontext.h>
#include <sys/user.h>
#include <ucontext.h>
#endif
#include <algorithm>
#include <utility>
#include <vector>
#include "common/basictypes.h"
#include "common/linux/linux_libc_support.h"
#include "common/memory.h"
#include "client/linux/log/log.h"
#include "client/linux/minidump_writer/linux_dumper.h"
#include "client/linux/minidump_writer/minidump_writer.h"
#include "common/linux/guid_creator.h"
#include "common/linux/eintr_wrapper.h"
#include "third_party/lss/linux_syscall_support.h"
#if defined(__ANDROID__)
#include "linux/sched.h"
#endif
#ifndef PR_SET_PTRACER
#define PR_SET_PTRACER 0x59616d61
@@ -111,93 +111,140 @@ static int tgkill(pid_t tgid, pid_t tid, int sig) {
namespace google_breakpad {
namespace {
// 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
const int kExceptionSignals[] = {
SIGSEGV, SIGABRT, SIGFPE, SIGILL, SIGBUS
};
const int kNumHandledSignals =
sizeof(kExceptionSignals) / sizeof(kExceptionSignals[0]);
struct sigaction old_handlers[kNumHandledSignals];
bool handlers_installed = false;
// InstallAlternateStackLocked will store the newly installed stack in new_stack
// and (if it exists) the previously installed stack in old_stack.
stack_t old_stack;
stack_t new_stack;
bool stack_installed = false;
// Create an alternative stack to run the signal handlers on. This is done since
// the signal might have been caused by a stack overflow.
// Runs before crashing: normal context.
void InstallAlternateStackLocked() {
if (stack_installed)
return;
memset(&old_stack, 0, sizeof(old_stack));
memset(&new_stack, 0, sizeof(new_stack));
// SIGSTKSZ may be too small to prevent the signal handlers from overrunning
// the alternative stack. Ensure that the size of the alternative stack is
// large enough.
static const unsigned kSigStackSize = std::max(8192, SIGSTKSZ);
// Only set an alternative stack if there isn't already one, or if the current
// one is too small.
if (sys_sigaltstack(NULL, &old_stack) == -1 || !old_stack.ss_sp ||
old_stack.ss_size < kSigStackSize) {
new_stack.ss_sp = malloc(kSigStackSize);
new_stack.ss_size = kSigStackSize;
if (sys_sigaltstack(&new_stack, NULL) == -1) {
free(new_stack.ss_sp);
return;
}
stack_installed = true;
}
}
// Runs before crashing: normal context.
void RestoreAlternateStackLocked() {
if (!stack_installed)
return;
stack_t current_stack;
if (sys_sigaltstack(NULL, &current_stack) == -1)
return;
// Only restore the old_stack if the current alternative stack is the one
// installed by the call to InstallAlternateStackLocked.
if (current_stack.ss_sp == new_stack.ss_sp) {
if (old_stack.ss_sp) {
if (sys_sigaltstack(&old_stack, NULL) == -1)
return;
} else {
stack_t disable_stack;
disable_stack.ss_flags = SS_DISABLE;
if (sys_sigaltstack(&disable_stack, NULL) == -1)
return;
}
}
free(new_stack.ss_sp);
stack_installed = false;
}
} // namespace
// 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;
// Runs before crashing: normal context.
ExceptionHandler::ExceptionHandler(const std::string &dump_path,
FilterCallback filter,
MinidumpCallback callback,
void *callback_context,
bool install_handler)
: filter_(filter),
callback_(callback),
callback_context_(callback_context),
handler_installed_(install_handler)
{
Init(dump_path, -1);
}
ExceptionHandler::ExceptionHandler(const std::string &dump_path,
ExceptionHandler::ExceptionHandler(const MinidumpDescriptor& descriptor,
FilterCallback filter,
MinidumpCallback callback,
void* callback_context,
bool install_handler,
const int server_fd)
: filter_(filter),
callback_(callback),
callback_context_(callback_context),
handler_installed_(install_handler)
{
Init(dump_path, server_fd);
}
: filter_(filter),
callback_(callback),
callback_context_(callback_context),
minidump_descriptor_(descriptor),
crash_handler_(NULL) {
if (server_fd >= 0)
crash_generation_client_.reset(CrashGenerationClient::TryCreate(server_fd));
// Runs before crashing: normal context.
ExceptionHandler::~ExceptionHandler() {
UninstallHandlers();
}
void ExceptionHandler::Init(const std::string &dump_path,
const int server_fd)
{
crash_handler_ = NULL;
if (0 <= server_fd)
crash_generation_client_
.reset(CrashGenerationClient::TryCreate(server_fd));
if (handler_installed_)
InstallHandlers();
if (!IsOutOfProcess())
set_dump_path(dump_path);
if (!IsOutOfProcess() && !minidump_descriptor_.IsFD())
minidump_descriptor_.UpdatePath();
pthread_mutex_lock(&handler_stack_mutex_);
if (handler_stack_ == NULL)
handler_stack_ = new std::vector<ExceptionHandler *>;
if (!handler_stack_)
handler_stack_ = new std::vector<ExceptionHandler*>;
if (install_handler) {
InstallAlternateStackLocked();
InstallHandlersLocked();
}
handler_stack_->push_back(this);
pthread_mutex_unlock(&handler_stack_mutex_);
}
// 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.
ExceptionHandler::~ExceptionHandler() {
pthread_mutex_lock(&handler_stack_mutex_);
std::vector<ExceptionHandler*>::iterator handler =
std::find(handler_stack_->begin(), handler_stack_->end(), this);
handler_stack_->erase(handler);
if (handler_stack_->empty()) {
RestoreAlternateStackLocked();
RestoreHandlersLocked();
}
pthread_mutex_unlock(&handler_stack_mutex_);
}
// We use this value rather than SIGSTKSZ because we would end up overrunning
// such a small stack.
static const unsigned kSigStackSize = 8192;
// Runs before crashing: normal context.
// static
bool ExceptionHandler::InstallHandlersLocked() {
if (handlers_installed)
return false;
stack_t stack;
// Only set an alternative stack if there isn't already one, or if the current
// one is too small.
if (sys_sigaltstack(NULL, &stack) == -1 || !stack.ss_sp ||
stack.ss_size < kSigStackSize) {
memset(&stack, 0, sizeof(stack));
stack.ss_sp = malloc(kSigStackSize);
stack.ss_size = kSigStackSize;
if (sys_sigaltstack(&stack, NULL) == -1)
// Fail if unable to store all the old handlers.
for (int i = 0; i < kNumHandledSignals; ++i) {
if (sigaction(kExceptionSignals[i], NULL, &old_handlers[i]) == -1)
return false;
}
@@ -205,54 +252,36 @@ bool ExceptionHandler::InstallHandlers() {
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)
// Mask all exception signals when we're handling one of them.
for (int i = 0; i < kNumHandledSignals; ++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));
for (int i = 0; i < kNumHandledSignals; ++i) {
if (sigaction(kExceptionSignals[i], &sa, NULL) == -1) {
// At this point it is impractical to back out changes, and so failure to
// install a signal is intentionally ignored.
}
}
handlers_installed = true;
return true;
}
// 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;
}
pthread_mutex_lock(&handler_stack_mutex_);
std::vector<ExceptionHandler*>::iterator handler =
std::find(handler_stack_->begin(), handler_stack_->end(), this);
handler_stack_->erase(handler);
pthread_mutex_unlock(&handler_stack_mutex_);
old_handlers_.clear();
}
// Runs before crashing: normal context.
void ExceptionHandler::UpdateNextID() {
GUID guid;
char guid_str[kGUIDStringLength + 1];
if (CreateGUID(&guid) && GUIDToString(&guid, guid_str, sizeof(guid_str))) {
next_minidump_id_ = guid_str;
next_minidump_id_c_ = next_minidump_id_.c_str();
char minidump_path[PATH_MAX];
snprintf(minidump_path, sizeof(minidump_path), "%s/%s.dmp",
dump_path_c_,
guid_str);
next_minidump_path_ = minidump_path;
next_minidump_path_c_ = next_minidump_path_.c_str();
// This function runs in a compromised context: see the top of the file.
// Runs on the crashing thread.
// static
void ExceptionHandler::RestoreHandlersLocked() {
if (!handlers_installed)
return;
for (int i = 0; i < kNumHandledSignals; ++i) {
if (sigaction(kExceptionSignals[i], &old_handlers[i], NULL) == -1) {
signal(kExceptionSignals[i], SIG_DFL);
}
}
handlers_installed = false;
}
// void ExceptionHandler::set_crash_handler(HandlerCallback callback) {
@@ -266,26 +295,58 @@ 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()) {
// Sometimes, Breakpad runs inside a process where some other buggy code
// saves and restores signal handlers temporarily with 'signal'
// instead of 'sigaction'. This loses the SA_SIGINFO flag associated
// with this function. As a consequence, the values of 'info' and 'uc'
// become totally bogus, generally inducing a crash.
//
// The following code tries to detect this case. When it does, it
// resets the signal handlers with sigaction + SA_SIGINFO and returns.
// This forces the signal to be thrown again, but this time the kernel
// will call the function with the right arguments.
struct sigaction cur_handler;
if (sigaction(sig, NULL, &cur_handler) == 0 &&
(cur_handler.sa_flags & SA_SIGINFO) == 0) {
// Reset signal handler with the right flags.
sigemptyset(&cur_handler.sa_mask);
sigaddset(&cur_handler.sa_mask, sig);
cur_handler.sa_sigaction = SignalHandler;
cur_handler.sa_flags = SA_ONSTACK | SA_SIGINFO;
if (sigaction(sig, &cur_handler, NULL) == -1) {
// When resetting the handler fails, try to reset the
// default one to avoid an infinite loop here.
signal(sig, SIG_DFL);
}
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;
}
bool handled = false;
for (int i = handler_stack_->size() - 1; !handled && i >= 0; --i) {
handled = (*handler_stack_)[i]->HandleSignal(sig, info, uc);
}
// Upon returning from this signal handler, sig will become unmasked and then
// it will be retriggered. If one of the ExceptionHandlers handled it
// successfully, restore the default handler. Otherwise, restore the
// previously installed handler. Then, when the signal is retriggered, it will
// be delivered to the appropriate handler.
if (handled) {
signal(sig, SIG_DFL);
} else {
RestoreHandlersLocked();
}
pthread_mutex_unlock(&handler_stack_mutex_);
if (info->si_pid) {
if (info->si_pid || sig == SIGABRT) {
// This signal was triggered by somebody sending us the signal with kill().
// In order to retrigger it, we have to queue a new signal by calling
// kill() ourselves.
// kill() ourselves. The special case (si_pid == 0 && sig == SIGABRT) is
// due to the kernel sending a SIGABRT from a user request via SysRQ.
if (tgkill(getpid(), syscall(__NR_gettid), sig) < 0) {
// If we failed to kill ourselves (e.g. because a sandbox disallows us
// to do so), we instead resort to terminating our process. This will
@@ -297,17 +358,11 @@ void ExceptionHandler::SignalHandler(int sig, siginfo_t* info, void* uc) {
// No need to reissue the signal. It will automatically trigger again,
// when we return from the signal handler.
}
// As soon as we return from the signal handler, our signal will become
// unmasked. At that time, we will get terminated with the same signal that
// was triggered originally. This allows our parent to know 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);
}
struct ThreadArgument {
pid_t pid; // the crashing process
const MinidumpDescriptor* minidump_descriptor;
ExceptionHandler* handler;
const void* context; // a CrashContext structure
size_t context_size;
@@ -338,13 +393,15 @@ bool ExceptionHandler::HandleSignal(int sig, siginfo_t* info, void* uc) {
bool signal_pid_trusted = info->si_code == SI_USER ||
info->si_code == SI_TKILL;
if (signal_trusted || (signal_pid_trusted && info->si_pid == getpid())) {
sys_prctl(PR_SET_DUMPABLE, 1);
sys_prctl(PR_SET_DUMPABLE, 1, 0, 0, 0);
}
CrashContext context;
memcpy(&context.siginfo, info, sizeof(siginfo_t));
memcpy(&context.context, uc, sizeof(struct ucontext));
#if !defined(__ARM_EABI__)
#if !defined(__ARM_EABI__) && !defined(__mips__)
// FP state is not part of user ABI on ARM Linux.
// In case of MIPS Linux FP state is already part of struct ucontext
// and 'float_state' is not a member of CrashContext.
struct ucontext *uc_ptr = (struct ucontext*)uc;
if (uc_ptr->uc_mcontext.fpregs) {
memcpy(&context.float_state,
@@ -354,20 +411,34 @@ bool ExceptionHandler::HandleSignal(int sig, siginfo_t* info, void* uc) {
#endif
context.tid = syscall(__NR_gettid);
if (crash_handler_ != NULL) {
if (crash_handler_(&context, sizeof(context),
callback_context_)) {
if (crash_handler_(&context, sizeof(context), callback_context_)) {
return true;
}
}
return GenerateDump(&context);
}
// This is a public interface to HandleSignal that allows the client to
// generate a crash dump. This function may run in a compromised context.
bool ExceptionHandler::SimulateSignalDelivery(int sig) {
siginfo_t siginfo = {};
// Mimic a trusted signal to allow tracing the process (see
// ExceptionHandler::HandleSignal().
siginfo.si_code = SI_USER;
siginfo.si_pid = getpid();
struct ucontext context;
getcontext(&context);
return HandleSignal(sig, &siginfo, &context);
}
// This function may run in a compromised context: see the top of the file.
bool ExceptionHandler::GenerateDump(CrashContext *context) {
if (IsOutOfProcess())
return crash_generation_client_->RequestDump(context, sizeof(*context));
static const unsigned kChildStackSize = 8000;
// Allocating too much stack isn't a problem, and better to err on the side
// of caution than smash it into random locations.
static const unsigned kChildStackSize = 16000;
PageAllocator allocator;
uint8_t* stack = (uint8_t*) allocator.Alloc(kChildStackSize);
if (!stack)
@@ -378,6 +449,7 @@ bool ExceptionHandler::GenerateDump(CrashContext *context) {
ThreadArgument thread_arg;
thread_arg.handler = this;
thread_arg.minidump_descriptor = &minidump_descriptor_;
thread_arg.pid = getpid();
thread_arg.context = context;
thread_arg.context_size = sizeof(*context);
@@ -397,18 +469,13 @@ bool ExceptionHandler::GenerateDump(CrashContext *context) {
logger::write("\n", 1);
}
#if defined(__ANDROID__)
const pid_t child = clone(
ThreadEntry, stack, CLONE_FILES | CLONE_FS | CLONE_UNTRACED,
&thread_arg);
#else
const pid_t child = sys_clone(
ThreadEntry, stack, CLONE_FILES | CLONE_FS | CLONE_UNTRACED,
&thread_arg, NULL, NULL, NULL);
#endif
int r, status;
// Allow the child to ptrace us
sys_prctl(PR_SET_PTRACER, child);
sys_prctl(PR_SET_PTRACER, child, 0, 0, 0);
SendContinueSignalToChild();
do {
r = sys_waitpid(child, &status, __WALL);
@@ -425,11 +492,8 @@ bool ExceptionHandler::GenerateDump(CrashContext *context) {
}
bool success = r != -1 && WIFEXITED(status) && WEXITSTATUS(status) == 0;
if (callback_)
success = callback_(dump_path_c_, next_minidump_id_c_,
callback_context_, success);
success = callback_(minidump_descriptor_, callback_context_, success);
return success;
}
@@ -466,44 +530,115 @@ void ExceptionHandler::WaitForContinueSignal() {
// 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_,
if (minidump_descriptor_.IsFD()) {
return google_breakpad::WriteMinidump(minidump_descriptor_.fd(),
minidump_descriptor_.size_limit(),
crashing_process,
context,
context_size,
mapping_list_,
app_memory_list_);
}
return google_breakpad::WriteMinidump(minidump_descriptor_.path(),
minidump_descriptor_.size_limit(),
crashing_process,
context,
context_size,
mapping_list_);
mapping_list_,
app_memory_list_);
}
// static
bool ExceptionHandler::WriteMinidump(const std::string &dump_path,
bool ExceptionHandler::WriteMinidump(const string& dump_path,
MinidumpCallback callback,
void* callback_context) {
ExceptionHandler eh(dump_path, NULL, callback, callback_context, false);
MinidumpDescriptor descriptor(dump_path);
ExceptionHandler eh(descriptor, NULL, callback, callback_context, false, -1);
return eh.WriteMinidump();
}
// In order to making using EBP to calculate the desired value for ESP
// a valid operation, ensure that this function is compiled with a
// frame pointer using the following attribute. This attribute
// is supported on GCC but not on clang.
#if defined(__i386__) && defined(__GNUC__) && !defined(__clang__)
__attribute__((optimize("no-omit-frame-pointer")))
#endif
bool ExceptionHandler::WriteMinidump() {
#if !defined(__ARM_EABI__)
// Allow ourselves to be dumped.
sys_prctl(PR_SET_DUMPABLE, 1);
if (!IsOutOfProcess() && !minidump_descriptor_.IsFD()) {
// Update the path of the minidump so that this can be called multiple times
// and new files are created for each minidump. This is done before the
// generation happens, as clients may want to access the MinidumpDescriptor
// after this call to find the exact path to the minidump file.
minidump_descriptor_.UpdatePath();
} else if (minidump_descriptor_.IsFD()) {
// Reposition the FD to its beginning and resize it to get rid of the
// previous minidump info.
lseek(minidump_descriptor_.fd(), 0, SEEK_SET);
ignore_result(ftruncate(minidump_descriptor_.fd(), 0));
}
// Allow this process to be dumped.
sys_prctl(PR_SET_DUMPABLE, 1, 0, 0, 0);
CrashContext context;
int getcontext_result = getcontext(&context.context);
if (getcontext_result)
return false;
#if defined(__i386__)
// In CPUFillFromUContext in minidumpwriter.cc the stack pointer is retrieved
// from REG_UESP instead of from REG_ESP. REG_UESP is the user stack pointer
// and it only makes sense when running in kernel mode with a different stack
// pointer. When WriteMiniDump is called during normal processing REG_UESP is
// zero which leads to bad minidump files.
if (!context.context.uc_mcontext.gregs[REG_UESP]) {
// If REG_UESP is set to REG_ESP then that includes the stack space for the
// CrashContext object in this function, which is about 128 KB. Since the
// Linux dumper only records 32 KB of stack this would mean that nothing
// useful would be recorded. A better option is to set REG_UESP to REG_EBP,
// perhaps with a small negative offset in case there is any code that
// objects to them being equal.
context.context.uc_mcontext.gregs[REG_UESP] =
context.context.uc_mcontext.gregs[REG_EBP] - 16;
// The stack saving is based off of REG_ESP so it must be set to match the
// new REG_UESP.
context.context.uc_mcontext.gregs[REG_ESP] =
context.context.uc_mcontext.gregs[REG_UESP];
}
#endif
#if !defined(__ARM_EABI__) && !defined(__mips__)
// FPU state is not part of ARM EABI ucontext_t.
memcpy(&context.float_state, context.context.uc_mcontext.fpregs,
sizeof(context.float_state));
#endif
context.tid = sys_gettid();
bool success = GenerateDump(&context);
UpdateNextID();
return success;
// Add an exception stream to the minidump for better reporting.
memset(&context.siginfo, 0, sizeof(context.siginfo));
context.siginfo.si_signo = MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED;
#if defined(__i386__)
context.siginfo.si_addr =
reinterpret_cast<void*>(context.context.uc_mcontext.gregs[REG_EIP]);
#elif defined(__x86_64__)
context.siginfo.si_addr =
reinterpret_cast<void*>(context.context.uc_mcontext.gregs[REG_RIP]);
#elif defined(__arm__)
context.siginfo.si_addr =
reinterpret_cast<void*>(context.context.uc_mcontext.arm_pc);
#elif defined(__mips__)
context.siginfo.si_addr =
reinterpret_cast<void*>(context.context.uc_mcontext.pc);
#else
return false;
#endif // !defined(__ARM_EABI__)
#error "This code has not been ported to your platform yet."
#endif
return GenerateDump(&context);
}
void ExceptionHandler::AddMappingInfo(const std::string& name,
const u_int8_t identifier[sizeof(MDGUID)],
void ExceptionHandler::AddMappingInfo(const string& name,
const uint8_t identifier[sizeof(MDGUID)],
uintptr_t start_address,
size_t mapping_size,
size_t file_offset) {
@@ -520,4 +655,43 @@ void ExceptionHandler::AddMappingInfo(const std::string& name,
mapping_list_.push_back(mapping);
}
void ExceptionHandler::RegisterAppMemory(void* ptr, size_t length) {
AppMemoryList::iterator iter =
std::find(app_memory_list_.begin(), app_memory_list_.end(), ptr);
if (iter != app_memory_list_.end()) {
// Don't allow registering the same pointer twice.
return;
}
AppMemory app_memory;
app_memory.ptr = ptr;
app_memory.length = length;
app_memory_list_.push_back(app_memory);
}
void ExceptionHandler::UnregisterAppMemory(void* ptr) {
AppMemoryList::iterator iter =
std::find(app_memory_list_.begin(), app_memory_list_.end(), ptr);
if (iter != app_memory_list_.end()) {
app_memory_list_.erase(iter);
}
}
// static
bool ExceptionHandler::WriteMinidumpForChild(pid_t child,
pid_t child_blamed_thread,
const string& dump_path,
MinidumpCallback callback,
void* callback_context) {
// This function is not run in a compromised context.
MinidumpDescriptor descriptor(dump_path);
descriptor.UpdatePath();
if (!google_breakpad::WriteMinidump(descriptor.path(),
child,
child_blamed_thread))
return false;
return callback ? callback(descriptor, callback_context, true) : true;
}
} // namespace google_breakpad

View File

@@ -37,21 +37,17 @@
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/ucontext.h>
#if defined(__ANDROID__)
#include "client/linux/android_ucontext.h"
#endif
#include "client/linux/crash_generation/crash_generation_client.h"
#include "client/linux/handler/minidump_descriptor.h"
#include "client/linux/minidump_writer/minidump_writer.h"
#include "common/scoped_ptr.h"
#include "common/using_std_string.h"
#include "google_breakpad/common/minidump_format.h"
#include "processor/scoped_ptr.h"
struct sigaction;
namespace google_breakpad {
class ExceptionHandler;
// ExceptionHandler
//
// ExceptionHandler can write a minidump file when an exception occurs,
@@ -70,17 +66,18 @@ class ExceptionHandler;
// use different minidump callbacks for different call sites.
//
// In either case, a callback function is called when a minidump is written,
// which receives the unqiue id of the minidump. The caller can use this
// id to collect and write additional application state, and to launch an
// external crash-reporting application.
// which receives the full path or file descriptor of the minidump. The
// caller can collect and write additional application state to that minidump,
// and launch an external crash-reporting application.
//
// Caller should try to make the callbacks as crash-friendly as possible,
// it should avoid use heap memory allocation as much as possible.
class ExceptionHandler {
public:
// A callback function to run before Breakpad performs any substantial
// processing of an exception. A FilterCallback is called before writing
// a minidump. context is the parameter supplied by the user as
// a minidump. |context| is the parameter supplied by the user as
// callback_context when the handler was created.
//
// If a FilterCallback returns true, Breakpad will continue processing,
@@ -90,10 +87,10 @@ class ExceptionHandler {
typedef bool (*FilterCallback)(void *context);
// A callback function to run after the minidump has been written.
// minidump_id is a unique id for the dump, so the minidump
// file is <dump_path>\<minidump_id>.dmp. context is the parameter supplied
// by the user as callback_context when the handler was created. succeeded
// indicates whether a minidump file was successfully written.
// |descriptor| contains the file descriptor or file path containing the
// minidump. |context| is the parameter supplied by the user as
// callback_context when the handler was created. |succeeded| indicates
// whether a minidump file was successfully written.
//
// If an exception occurred and the callback returns true, Breakpad will
// treat the exception as fully-handled, suppressing any other handlers from
@@ -105,9 +102,8 @@ class ExceptionHandler {
// should normally return the value of |succeeded|, or when they wish to
// not report an exception of handled, false. Callbacks will rarely want to
// return true directly (unless |succeeded| is true).
typedef bool (*MinidumpCallback)(const char *dump_path,
const char *minidump_id,
void *context,
typedef bool (*MinidumpCallback)(const MinidumpDescriptor& descriptor,
void* context,
bool succeeded);
// In certain cases, a user may wish to handle the generation of the minidump
@@ -120,52 +116,73 @@ class ExceptionHandler {
void* context);
// Creates a new ExceptionHandler instance to handle writing minidumps.
// Before writing a minidump, the optional filter callback will be called.
// Before writing a minidump, the optional |filter| callback will be called.
// Its return value determines whether or not Breakpad should write a
// minidump. Minidump files will be written to dump_path, and the optional
// callback is called after writing the dump file, as described above.
// minidump. The minidump content will be written to the file path or file
// descriptor from |descriptor|, and the optional |callback| is called after
// writing the dump file, as described above.
// If install_handler is true, then a minidump will be written whenever
// an unhandled exception occurs. If it is false, minidumps will only
// be written when WriteMinidump is called.
ExceptionHandler(const std::string &dump_path,
FilterCallback filter, MinidumpCallback callback,
// If |server_fd| is valid, the minidump is generated out-of-process. If it
// is -1, in-process generation will always be used.
ExceptionHandler(const MinidumpDescriptor& descriptor,
FilterCallback filter,
MinidumpCallback callback,
void *callback_context,
bool install_handler);
// Creates a new ExceptionHandler instance that can attempt to
// perform out-of-process dump generation if server_fd is valid. If
// server_fd is invalid, in-process dump generation will be
// used. See the above ctor for a description of the other
// parameters.
ExceptionHandler(const std::string& dump_path,
FilterCallback filter, MinidumpCallback callback,
void* callback_context,
bool install_handler,
const int server_fd);
~ExceptionHandler();
// Get and set the minidump 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();
const MinidumpDescriptor& minidump_descriptor() const {
return minidump_descriptor_;
}
void set_minidump_descriptor(const MinidumpDescriptor& descriptor) {
minidump_descriptor_ = descriptor;
}
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.
// Writes a minidump immediately. This can be used to capture the execution
// state independently of a crash.
// Returns true on success.
// If the ExceptionHandler has been created with a path, a new file is
// generated for each minidump. The file path can be retrieved in the
// MinidumpDescriptor passed to the MinidumpCallback or by accessing the
// MinidumpDescriptor directly from the ExceptionHandler (with
// minidump_descriptor()).
// If the ExceptionHandler has been created with a file descriptor, the file
// descriptor is repositioned to its beginning and the previous generated
// minidump is overwritten.
// Note that this method is not supposed to be called from a compromised
// context as it uses the heap.
bool WriteMinidump();
// Convenience form of WriteMinidump which does not require an
// ExceptionHandler instance.
static bool WriteMinidump(const std::string &dump_path,
static bool WriteMinidump(const string& dump_path,
MinidumpCallback callback,
void *callback_context);
void* callback_context);
// Write a minidump of |child| immediately. This can be used to
// capture the execution state of |child| independently of a crash.
// Pass a meaningful |child_blamed_thread| to make that thread in
// the child process the one from which a crash signature is
// extracted.
//
// WARNING: the return of this function *must* happen before
// the code that will eventually reap |child| executes.
// Otherwise there's a pernicious race condition in which |child|
// exits, is reaped, another process created with its pid, then that
// new process dumped.
static bool WriteMinidumpForChild(pid_t child,
pid_t child_blamed_thread,
const 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.
@@ -173,8 +190,10 @@ class ExceptionHandler {
siginfo_t siginfo;
pid_t tid; // the crashing thread.
struct ucontext context;
#if !defined(__ARM_EABI__)
#if !defined(__ARM_EABI__) && !defined(__mips__)
// #ifdef this out because FP state is not part of user ABI for Linux ARM.
// In case of MIPS Linux FP state is already part of struct ucontext
// so 'float_state' is not required.
struct _libc_fpstate float_state;
#endif
};
@@ -187,25 +206,36 @@ class ExceptionHandler {
// Add information about a memory mapping. This can be used if
// a custom library loader is used that maps things in a way
// that the linux dumper can't handle by reading the maps file.
void AddMappingInfo(const std::string& name,
const u_int8_t identifier[sizeof(MDGUID)],
void AddMappingInfo(const string& name,
const uint8_t identifier[sizeof(MDGUID)],
uintptr_t start_address,
size_t mapping_size,
size_t file_offset);
// Register a block of memory of length bytes starting at address ptr
// to be copied to the minidump when a crash happens.
void RegisterAppMemory(void* ptr, size_t length);
// Unregister a block of memory that was registered with RegisterAppMemory.
void UnregisterAppMemory(void* ptr);
// Force signal handling for the specified signal.
bool SimulateSignalDelivery(int sig);
// Report a crash signal from an SA_SIGINFO signal handler.
bool HandleSignal(int sig, siginfo_t* info, void* uc);
private:
void Init(const std::string &dump_path,
const int server_fd);
bool InstallHandlers();
void UninstallHandlers();
// Save the old signal handlers and install new ones.
static bool InstallHandlersLocked();
// Restore the old signal handlers.
static void RestoreHandlersLocked();
void PreresolveSymbols();
bool GenerateDump(CrashContext *context);
void SendContinueSignalToChild();
void WaitForContinueSignal();
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);
@@ -216,31 +246,16 @@ class ExceptionHandler {
scoped_ptr<CrashGenerationClient> crash_generation_client_;
std::string dump_path_;
std::string next_minidump_path_;
std::string next_minidump_id_;
MinidumpDescriptor minidump_descriptor_;
// 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_path_c_;
const char* next_minidump_id_c_;
const bool handler_installed_;
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_;
// The index of the handler that should handle the next exception.
static unsigned handler_stack_index_;
static pthread_mutex_t handler_stack_mutex_;
// A vector of the old signal handlers.
std::vector<std::pair<int, struct sigaction *> > old_handlers_;
// We need to explicitly enable ptrace of parent processes on some
// kernels, but we need to know the PID of the cloned process before we
// can do this. We create a pipe which we can use to block the
@@ -251,6 +266,10 @@ class ExceptionHandler {
// Callers can add extra info about mappings for cases where the
// dumper code cannot extract enough information from /proc/<pid>/maps.
MappingList mapping_list_;
// Callers can request additional memory regions to be included in
// the dump.
AppMemoryList app_memory_list_;
};
} // namespace google_breakpad

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
// Copyright 2005, Google Inc.
// Copyright (c) 2012 Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
@@ -27,42 +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.
// A sample program demonstrating using Google C++ testing framework.
//
// Author: wan@google.com (Zhanyong Wan)
#include <stdio.h>
#include "sample1.h"
#include "client/linux/handler/minidump_descriptor.h"
// Returns n! (the factorial of n). For negative n, n! is defined to be 1.
int Factorial(int n) {
int result = 1;
for (int i = 1; i <= n; i++) {
result *= i;
}
#include "common/linux/guid_creator.h"
return result;
namespace google_breakpad {
MinidumpDescriptor::MinidumpDescriptor(const MinidumpDescriptor& descriptor)
: fd_(descriptor.fd_),
directory_(descriptor.directory_),
c_path_(NULL),
size_limit_(descriptor.size_limit_) {
// The copy constructor is not allowed to be called on a MinidumpDescriptor
// with a valid path_, as getting its c_path_ would require the heap which
// can cause problems in compromised environments.
assert(descriptor.path_.empty());
}
// Returns true iff n is a prime number.
bool IsPrime(int n) {
// Trivial case 1: small numbers
if (n <= 1) return false;
MinidumpDescriptor& MinidumpDescriptor::operator=(
const MinidumpDescriptor& descriptor) {
assert(descriptor.path_.empty());
// Trivial case 2: even numbers
if (n % 2 == 0) return n == 2;
fd_ = descriptor.fd_;
directory_ = descriptor.directory_;
path_.clear();
if (c_path_) {
// This descriptor already had a path set, so generate a new one.
c_path_ = NULL;
UpdatePath();
}
size_limit_ = descriptor.size_limit_;
return *this;
}
// Now, we have that n is odd and n >= 3.
void MinidumpDescriptor::UpdatePath() {
assert(fd_ == -1 && !directory_.empty());
// Try to divide n by every odd number i, starting from 3
for (int i = 3; ; i += 2) {
// We only have to try i up to the squre root of n
if (i > n/i) break;
// Now, we have i <= n/i < n.
// If n is divisible by i, n is not prime.
if (n % i == 0) return false;
GUID guid;
char guid_str[kGUIDStringLength + 1];
if (!CreateGUID(&guid) || !GUIDToString(&guid, guid_str, sizeof(guid_str))) {
assert(false);
}
// n has no integer factor in the range (1, n), and thus is prime.
return true;
path_.clear();
path_ = directory_ + "/" + guid_str + ".dmp";
c_path_ = path_.c_str();
}
} // namespace google_breakpad

View File

@@ -0,0 +1,100 @@
// Copyright (c) 2012 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_MINIDUMP_DESCRIPTOR_H_
#define CLIENT_LINUX_HANDLER_MINIDUMP_DESCRIPTOR_H_
#include <assert.h>
#include <sys/types.h>
#include <string>
#include "common/using_std_string.h"
// The MinidumpDescriptor describes how to access a minidump: it can contain
// either a file descriptor or a path.
// Note that when using files, it is created with the path to a directory.
// The actual path where the minidump is generated is created by this class.
namespace google_breakpad {
class MinidumpDescriptor {
public:
MinidumpDescriptor() : fd_(-1), size_limit_(-1) {}
explicit MinidumpDescriptor(const string& directory)
: fd_(-1),
directory_(directory),
c_path_(NULL),
size_limit_(-1) {
assert(!directory.empty());
}
explicit MinidumpDescriptor(int fd)
: fd_(fd),
c_path_(NULL),
size_limit_(-1) {
assert(fd != -1);
}
explicit MinidumpDescriptor(const MinidumpDescriptor& descriptor);
MinidumpDescriptor& operator=(const MinidumpDescriptor& descriptor);
bool IsFD() const { return fd_ != -1; }
int fd() const { return fd_; }
string directory() const { return directory_; }
const char* path() const { return c_path_; }
// Updates the path so it is unique.
// Should be called from a normal context: this methods uses the heap.
void UpdatePath();
off_t size_limit() const { return size_limit_; }
void set_size_limit(off_t limit) { size_limit_ = limit; }
private:
// The file descriptor where the minidump is generated.
int fd_;
// The directory where the minidump should be generated.
string directory_;
// The full path to the generated minidump.
string path_;
// The C string of |path_|. Precomputed so it can be access from a compromised
// context.
const char* c_path_;
off_t size_limit_;
};
} // namespace google_breakpad
#endif // CLIENT_LINUX_HANDLER_MINIDUMP_DESCRIPTOR_H_

View File

@@ -0,0 +1,144 @@
// Copyright (c) 2013, 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_CPU_SET_H_
#define CLIENT_LINUX_MINIDUMP_WRITER_CPU_SET_H_
#include <stdint.h>
#include <assert.h>
#include <string.h>
#include "common/linux/linux_libc_support.h"
#include "third_party/lss/linux_syscall_support.h"
namespace google_breakpad {
// Helper class used to model a set of CPUs, as read from sysfs
// files like /sys/devices/system/cpu/present
// See See http://www.kernel.org/doc/Documentation/cputopology.txt
class CpuSet {
public:
// The maximum number of supported CPUs.
static const size_t kMaxCpus = 1024;
CpuSet() {
my_memset(mask_, 0, sizeof(mask_));
}
// Parse a sysfs file to extract the corresponding CPU set.
bool ParseSysFile(int fd) {
char buffer[512];
int ret = sys_read(fd, buffer, sizeof(buffer)-1);
if (ret < 0)
return false;
buffer[ret] = '\0';
// Expected format: comma-separated list of items, where each
// item can be a decimal integer, or two decimal integers separated
// by a dash.
// E.g.:
// 0
// 0,1,2,3
// 0-3
// 1,10-23
const char* p = buffer;
const char* p_end = p + ret;
while (p < p_end) {
// Skip leading space, if any
while (p < p_end && my_isspace(*p))
p++;
// Find start and size of current item.
const char* item = p;
size_t item_len = static_cast<size_t>(p_end - p);
const char* item_next =
static_cast<const char*>(my_memchr(p, ',', item_len));
if (item_next != NULL) {
p = item_next + 1;
item_len = static_cast<size_t>(item_next - item);
} else {
p = p_end;
item_next = p_end;
}
// Ignore trailing spaces.
while (item_next > item && my_isspace(item_next[-1]))
item_next--;
// skip empty items.
if (item_next == item)
continue;
// read first decimal value.
uintptr_t start = 0;
const char* next = my_read_decimal_ptr(&start, item);
uintptr_t end = start;
if (*next == '-')
my_read_decimal_ptr(&end, next+1);
while (start <= end)
SetBit(start++);
}
return true;
}
// Intersect this CPU set with another one.
void IntersectWith(const CpuSet& other) {
for (size_t nn = 0; nn < kMaskWordCount; ++nn)
mask_[nn] &= other.mask_[nn];
}
// Return the number of CPUs in this set.
int GetCount() {
int result = 0;
for (size_t nn = 0; nn < kMaskWordCount; ++nn) {
result += __builtin_popcount(mask_[nn]);
}
return result;
}
private:
void SetBit(uintptr_t index) {
size_t nn = static_cast<size_t>(index);
if (nn < kMaxCpus)
mask_[nn / kMaskWordBits] |= (1U << (nn % kMaskWordBits));
}
typedef uint32_t MaskWordType;
static const size_t kMaskWordBits = 8*sizeof(MaskWordType);
static const size_t kMaskWordCount =
(kMaxCpus + kMaskWordBits - 1) / kMaskWordBits;
MaskWordType mask_[kMaskWordCount];
};
} // namespace google_breakpad
#endif // CLIENT_LINUX_MINIDUMP_WRITER_CPU_SET_H_

View File

@@ -0,0 +1,164 @@
// Copyright (c) 2013, 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 <stdio.h>
#include <errno.h>
#include <string.h>
#include "breakpad_googletest_includes.h"
#include "client/linux/minidump_writer/cpu_set.h"
#include "common/linux/tests/auto_testfile.h"
using namespace google_breakpad;
namespace {
typedef testing::Test CpuSetTest;
// Helper class to write test text file to a temporary file and return
// its file descriptor.
class ScopedTestFile : public AutoTestFile {
public:
explicit ScopedTestFile(const char* text)
: AutoTestFile("cpu_set", text) {
}
};
}
TEST(CpuSetTest, EmptyCount) {
CpuSet set;
ASSERT_EQ(0, set.GetCount());
}
TEST(CpuSetTest, OneCpu) {
ScopedTestFile file("10");
ASSERT_TRUE(file.IsOk());
CpuSet set;
ASSERT_TRUE(set.ParseSysFile(file.GetFd()));
ASSERT_EQ(1, set.GetCount());
}
TEST(CpuSetTest, OneCpuTerminated) {
ScopedTestFile file("10\n");
ASSERT_TRUE(file.IsOk());
CpuSet set;
ASSERT_TRUE(set.ParseSysFile(file.GetFd()));
ASSERT_EQ(1, set.GetCount());
}
TEST(CpuSetTest, TwoCpusWithComma) {
ScopedTestFile file("1,10");
ASSERT_TRUE(file.IsOk());
CpuSet set;
ASSERT_TRUE(set.ParseSysFile(file.GetFd()));
ASSERT_EQ(2, set.GetCount());
}
TEST(CpuSetTest, TwoCpusWithRange) {
ScopedTestFile file("1-2");
ASSERT_TRUE(file.IsOk());
CpuSet set;
ASSERT_TRUE(set.ParseSysFile(file.GetFd()));
ASSERT_EQ(2, set.GetCount());
}
TEST(CpuSetTest, TenCpusWithRange) {
ScopedTestFile file("9-18");
ASSERT_TRUE(file.IsOk());
CpuSet set;
ASSERT_TRUE(set.ParseSysFile(file.GetFd()));
ASSERT_EQ(10, set.GetCount());
}
TEST(CpuSetTest, MultiItems) {
ScopedTestFile file("0, 2-4, 128");
ASSERT_TRUE(file.IsOk());
CpuSet set;
ASSERT_TRUE(set.ParseSysFile(file.GetFd()));
ASSERT_EQ(5, set.GetCount());
}
TEST(CpuSetTest, IntersectWith) {
ScopedTestFile file1("9-19");
ASSERT_TRUE(file1.IsOk());
CpuSet set1;
ASSERT_TRUE(set1.ParseSysFile(file1.GetFd()));
ASSERT_EQ(11, set1.GetCount());
ScopedTestFile file2("16-24");
ASSERT_TRUE(file2.IsOk());
CpuSet set2;
ASSERT_TRUE(set2.ParseSysFile(file2.GetFd()));
ASSERT_EQ(9, set2.GetCount());
set1.IntersectWith(set2);
ASSERT_EQ(4, set1.GetCount());
ASSERT_EQ(9, set2.GetCount());
}
TEST(CpuSetTest, SelfIntersection) {
ScopedTestFile file1("9-19");
ASSERT_TRUE(file1.IsOk());
CpuSet set1;
ASSERT_TRUE(set1.ParseSysFile(file1.GetFd()));
ASSERT_EQ(11, set1.GetCount());
set1.IntersectWith(set1);
ASSERT_EQ(11, set1.GetCount());
}
TEST(CpuSetTest, EmptyIntersection) {
ScopedTestFile file1("0-19");
ASSERT_TRUE(file1.IsOk());
CpuSet set1;
ASSERT_TRUE(set1.ParseSysFile(file1.GetFd()));
ASSERT_EQ(20, set1.GetCount());
ScopedTestFile file2("20-39");
ASSERT_TRUE(file2.IsOk());
CpuSet set2;
ASSERT_TRUE(set2.ParseSysFile(file2.GetFd()));
ASSERT_EQ(20, set2.GetCount());
set1.IntersectWith(set2);
ASSERT_EQ(0, set1.GetCount());
ASSERT_EQ(20, set2.GetCount());
}

View File

@@ -37,6 +37,7 @@
#include <errno.h>
#include <string.h>
#include "common/linux/linux_libc_support.h"
#include "third_party/lss/linux_syscall_support.h"
namespace google_breakpad {
@@ -90,7 +91,7 @@ class DirectoryReader {
reinterpret_cast<kernel_dirent*>(buf_);
buf_used_ -= dent->d_reclen;
memmove(buf_, buf_ + dent->d_reclen, buf_used_);
my_memmove(buf_, buf_ + dent->d_reclen, buf_used_);
}
private:

View File

@@ -35,6 +35,7 @@
#include <sys/types.h>
#include "client/linux/minidump_writer/directory_reader.h"
#include "common/using_std_string.h"
#include "breakpad_googletest_includes.h"
using namespace google_breakpad;
@@ -44,7 +45,7 @@ typedef testing::Test DirectoryReaderTest;
}
TEST(DirectoryReaderTest, CompareResults) {
std::set<std::string> dent_set;
std::set<string> dent_set;
DIR *const dir = opendir("/proc/self");
ASSERT_TRUE(dir != NULL);

View File

@@ -34,6 +34,7 @@
#include <assert.h>
#include <string.h>
#include "common/linux/linux_libc_support.h"
#include "third_party/lss/linux_syscall_support.h"
namespace google_breakpad {
@@ -114,7 +115,7 @@ class LineReader {
assert(buf_used_ >= len + 1);
buf_used_ -= len + 1;
memmove(buf_, buf_ + len + 1, buf_used_);
my_memmove(buf_, buf_ + len + 1, buf_used_);
}
private:

View File

@@ -33,48 +33,41 @@
#include "client/linux/minidump_writer/line_reader.h"
#include "breakpad_googletest_includes.h"
#include "common/linux/eintr_wrapper.h"
#include "common/linux/tests/auto_testfile.h"
using namespace google_breakpad;
#if !defined(__ANDROID__)
#define TEMPDIR "/tmp"
#else
#define TEMPDIR "/data/local/tmp"
#endif
static int TemporaryFile() {
static const char templ[] = TEMPDIR "/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;
class ScopedTestFile : public AutoTestFile {
public:
explicit ScopedTestFile(const char* text)
: AutoTestFile("line_reader", text) {
}
ScopedTestFile(const char* text, size_t text_len)
: AutoTestFile("line_reader", text, text_len) {
}
};
}
TEST(LineReaderTest, EmptyFile) {
const int fd = TemporaryFile();
LineReader reader(fd);
ScopedTestFile file("");
ASSERT_TRUE(file.IsOk());
LineReader reader(file.GetFd());
const char *line;
unsigned len;
ASSERT_FALSE(reader.GetNextLine(&line, &len));
close(fd);
}
TEST(LineReaderTest, OneLineTerminated) {
const int fd = TemporaryFile();
const int r = HANDLE_EINTR(write(fd, "a\n", 2));
ASSERT_EQ(2, r);
lseek(fd, 0, SEEK_SET);
LineReader reader(fd);
ScopedTestFile file("a\n");
ASSERT_TRUE(file.IsOk());
LineReader reader(file.GetFd());
const char *line;
unsigned int len;
@@ -85,16 +78,12 @@ TEST(LineReaderTest, OneLineTerminated) {
reader.PopLine(len);
ASSERT_FALSE(reader.GetNextLine(&line, &len));
close(fd);
}
TEST(LineReaderTest, OneLine) {
const int fd = TemporaryFile();
const int r = HANDLE_EINTR(write(fd, "a", 1));
ASSERT_EQ(1, r);
lseek(fd, 0, SEEK_SET);
LineReader reader(fd);
ScopedTestFile file("a");
ASSERT_TRUE(file.IsOk());
LineReader reader(file.GetFd());
const char *line;
unsigned len;
@@ -105,16 +94,12 @@ TEST(LineReaderTest, OneLine) {
reader.PopLine(len);
ASSERT_FALSE(reader.GetNextLine(&line, &len));
close(fd);
}
TEST(LineReaderTest, TwoLinesTerminated) {
const int fd = TemporaryFile();
const int r = HANDLE_EINTR(write(fd, "a\nb\n", 4));
ASSERT_EQ(4, r);
lseek(fd, 0, SEEK_SET);
LineReader reader(fd);
ScopedTestFile file("a\nb\n");
ASSERT_TRUE(file.IsOk());
LineReader reader(file.GetFd());
const char *line;
unsigned len;
@@ -131,16 +116,12 @@ TEST(LineReaderTest, TwoLinesTerminated) {
reader.PopLine(len);
ASSERT_FALSE(reader.GetNextLine(&line, &len));
close(fd);
}
TEST(LineReaderTest, TwoLines) {
const int fd = TemporaryFile();
const int r = HANDLE_EINTR(write(fd, "a\nb", 3));
ASSERT_EQ(3, r);
lseek(fd, 0, SEEK_SET);
LineReader reader(fd);
ScopedTestFile file("a\nb");
ASSERT_TRUE(file.IsOk());
LineReader reader(file.GetFd());
const char *line;
unsigned len;
@@ -157,18 +138,14 @@ TEST(LineReaderTest, TwoLines) {
reader.PopLine(len);
ASSERT_FALSE(reader.GetNextLine(&line, &len));
close(fd);
}
TEST(LineReaderTest, MaxLength) {
const int fd = TemporaryFile();
char l[LineReader::kMaxLineLen - 1];
char l[LineReader::kMaxLineLen-1];
memset(l, 'a', sizeof(l));
const int r = HANDLE_EINTR(write(fd, l, sizeof(l)));
ASSERT_EQ(sizeof(l), r);
lseek(fd, 0, SEEK_SET);
LineReader reader(fd);
ScopedTestFile file(l, sizeof(l));
ASSERT_TRUE(file.IsOk());
LineReader reader(file.GetFd());
const char *line;
unsigned len;
@@ -176,22 +153,17 @@ TEST(LineReaderTest, MaxLength) {
ASSERT_EQ(sizeof(l), len);
ASSERT_TRUE(memcmp(l, line, sizeof(l)) == 0);
ASSERT_EQ('\0', line[len]);
close(fd);
}
TEST(LineReaderTest, TooLong) {
const int fd = TemporaryFile();
// Note: this writes kMaxLineLen 'a' chars in the test file.
char l[LineReader::kMaxLineLen];
memset(l, 'a', sizeof(l));
const int r = HANDLE_EINTR(write(fd, l, sizeof(l)));
ASSERT_EQ(sizeof(l), r);
lseek(fd, 0, SEEK_SET);
LineReader reader(fd);
ScopedTestFile file(l, sizeof(l));
ASSERT_TRUE(file.IsOk());
LineReader reader(file.GetFd());
const char *line;
unsigned len;
ASSERT_FALSE(reader.GetNextLine(&line, &len));
close(fd);
}

View File

@@ -99,12 +99,14 @@ bool LinuxCoreDumper::GetThreadInfoByIndex(size_t index, ThreadInfo* info) {
memcpy(&stack_pointer, &info->regs.rsp, sizeof(info->regs.rsp));
#elif defined(__ARM_EABI__)
memcpy(&stack_pointer, &info->regs.ARM_sp, sizeof(info->regs.ARM_sp));
#elif defined(__mips__)
stack_pointer =
reinterpret_cast<uint8_t*>(info->regs.regs[MD_CONTEXT_MIPS_REG_SP]);
#else
#error "This code hasn't been ported to your platform yet."
#endif
return GetStackInfo(&info->stack, &info->stack_len,
reinterpret_cast<uintptr_t>(stack_pointer));
info->stack_pointer = reinterpret_cast<uintptr_t>(stack_pointer);
return true;
}
bool LinuxCoreDumper::IsPostMortem() const {

View File

@@ -30,11 +30,13 @@
// linux_core_dumper_unittest.cc:
// Unit tests for google_breakpad::LinuxCoreDumoer.
#include <string>
#include "breakpad_googletest_includes.h"
#include "client/linux/minidump_writer/linux_core_dumper.h"
#include "common/linux/tests/crash_generator.h"
#include "common/using_std_string.h"
using std::string;
using namespace google_breakpad;
TEST(LinuxCoreDumperTest, BuildProcPath) {
@@ -81,11 +83,10 @@ TEST(LinuxCoreDumperTest, VerifyDumpWithMultipleThreads) {
return;
}
pid_t pid = getpid();
const string core_file = crash_generator.GetCoreFilePath();
const string procfs_path = crash_generator.GetDirectoryOfProcFilesCopy();
LinuxCoreDumper dumper(child_pid, core_file.c_str(), procfs_path.c_str());
dumper.Init();
EXPECT_TRUE(dumper.Init());
EXPECT_TRUE(dumper.IsPostMortem());
@@ -95,7 +96,7 @@ TEST(LinuxCoreDumperTest, VerifyDumpWithMultipleThreads) {
// LinuxCoreDumper cannot determine the crash address and thus it always
// sets the crash address to 0.
EXPECT_EQ(0, dumper.crash_address());
EXPECT_EQ(0U, dumper.crash_address());
EXPECT_EQ(kCrashSignal, dumper.crash_signal());
EXPECT_EQ(crash_generator.GetThreadId(kCrashThread),
dumper.crash_thread());
@@ -104,6 +105,9 @@ TEST(LinuxCoreDumperTest, VerifyDumpWithMultipleThreads) {
for (unsigned i = 0; i < kNumOfThreads; ++i) {
ThreadInfo info;
EXPECT_TRUE(dumper.GetThreadInfoByIndex(i, &info));
const void* stack;
size_t stack_len;
EXPECT_TRUE(dumper.GetStackInfo(&stack, &stack_len, info.stack_pointer));
EXPECT_EQ(getpid(), info.ppid);
}
}

View File

@@ -66,28 +66,34 @@ inline static bool IsMappedFileOpenUnsafe(
namespace google_breakpad {
// All interesting auvx entry types are below AT_SYSINFO_EHDR
#define AT_MAX AT_SYSINFO_EHDR
LinuxDumper::LinuxDumper(pid_t pid)
: pid_(pid),
crash_address_(0),
crash_signal_(0),
crash_thread_(0),
crash_thread_(pid),
threads_(&allocator_, 8),
mappings_(&allocator_) {
mappings_(&allocator_),
auxv_(&allocator_, AT_MAX + 1) {
// The passed-in size to the constructor (above) is only a hint.
// Must call .resize() to do actual initialization of the elements.
auxv_.resize(AT_MAX + 1);
}
LinuxDumper::~LinuxDumper() {
}
bool LinuxDumper::Init() {
return EnumerateThreads() && EnumerateMappings();
return ReadAuxv() && EnumerateThreads() && EnumerateMappings();
}
bool
LinuxDumper::ElfFileIdentifierForMapping(const MappingInfo& mapping,
bool member,
unsigned int mapping_id,
uint8_t identifier[sizeof(MDGUID)])
{
uint8_t identifier[sizeof(MDGUID)]) {
assert(!member || mapping_id < mappings_.size());
my_memset(identifier, 0, sizeof(MDGUID));
if (IsMappedFileOpenUnsafe(mapping))
@@ -95,15 +101,14 @@ LinuxDumper::ElfFileIdentifierForMapping(const MappingInfo& mapping,
// Special-case linux-gate because it's not a real file.
if (my_strcmp(mapping.name, kLinuxGateLibraryName) == 0) {
const uintptr_t kPageSize = getpagesize();
void* linux_gate = NULL;
if (pid_ == sys_getpid()) {
linux_gate = reinterpret_cast<void*>(mapping.start_addr);
} else {
linux_gate = allocator_.Alloc(kPageSize);
linux_gate = allocator_.Alloc(mapping.size);
CopyFromProcess(linux_gate, pid_,
reinterpret_cast<const void*>(mapping.start_addr),
kPageSize);
mapping.size);
}
return FileID::ElfFileIdentifierFromMappedFile(linux_gate, identifier);
}
@@ -113,7 +118,7 @@ LinuxDumper::ElfFileIdentifierForMapping(const MappingInfo& mapping,
assert(filename_len < NAME_MAX);
if (filename_len >= NAME_MAX)
return false;
memcpy(filename, mapping.name, filename_len);
my_memcpy(filename, mapping.name, filename_len);
filename[filename_len] = '\0';
bool filename_modified = HandleDeletedFileInMapping(filename);
@@ -131,58 +136,30 @@ LinuxDumper::ElfFileIdentifierForMapping(const MappingInfo& mapping,
return success;
}
void*
LinuxDumper::FindBeginningOfLinuxGateSharedLibrary(pid_t pid) const {
bool LinuxDumper::ReadAuxv() {
char auxv_path[NAME_MAX];
if (!BuildProcPath(auxv_path, pid, "auxv"))
return NULL;
if (!BuildProcPath(auxv_path, pid_, "auxv")) {
return false;
}
// 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;
return false;
}
elf_aux_entry one_aux_entry;
bool res = false;
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);
if (one_aux_entry.a_type <= AT_MAX) {
auxv_[one_aux_entry.a_type] = one_aux_entry.a_un.a_val;
res = true;
}
}
close(fd);
return NULL;
}
void*
LinuxDumper::FindEntryPoint(pid_t pid) const {
char auxv_path[NAME_MAX];
if (!BuildProcPath(auxv_path, pid, "auxv"))
return NULL;
int fd = sys_open(auxv_path, O_RDONLY, 0);
if (fd < 0) {
return NULL;
}
// Find the AT_ENTRY entry
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_ENTRY) {
close(fd);
return reinterpret_cast<void*>(one_aux_entry.a_un.a_val);
}
}
close(fd);
return NULL;
sys_close(fd);
return res;
}
bool LinuxDumper::EnumerateMappings() {
@@ -192,15 +169,17 @@ bool LinuxDumper::EnumerateMappings() {
// 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_);
// maps list as a filename, but it can be found using the AT_SYSINFO_EHDR
// aux vector entry, which gives the information necessary to special
// case its entry when creating the list of mappings.
// See http://www.trilithium.com/johan/2005/08/linux-gate/ for more
// information.
const void* linux_gate_loc =
reinterpret_cast<void *>(auxv_[AT_SYSINFO_EHDR]);
// Although the initial executable is usually the first mapping, it's not
// guaranteed (see http://crosbug.com/25355); therefore, try to use the
// actual entry point to find the mapping.
const void* entry_point_loc = FindEntryPoint(pid_);
const void* entry_point_loc = reinterpret_cast<void *>(auxv_[AT_ENTRY]);
const int fd = sys_open(maps_path, O_RDONLY, 0);
if (fd < 0)
@@ -240,14 +219,14 @@ bool LinuxDumper::EnumerateMappings() {
}
}
MappingInfo* const module = new(allocator_) MappingInfo;
memset(module, 0, sizeof(MappingInfo));
my_memset(module, 0, sizeof(MappingInfo));
module->start_addr = start_addr;
module->size = end_addr - start_addr;
module->offset = offset;
if (name != NULL) {
const unsigned l = my_strlen(name);
if (l < sizeof(module->name))
memcpy(module->name, name, l);
my_memcpy(module->name, name, l);
}
// If this is the entry-point mapping, and it's not already the
// first one, then we need to make it be first. This is because
@@ -296,7 +275,8 @@ bool LinuxDumper::GetStackInfo(const void** stack, size_t* stack_len,
const MappingInfo* mapping = FindMapping(stack_pointer);
if (!mapping)
return false;
const ptrdiff_t offset = stack_pointer - (uint8_t*) mapping->start_addr;
const ptrdiff_t offset = stack_pointer -
reinterpret_cast<uint8_t*>(mapping->start_addr);
const ptrdiff_t distance_to_end =
static_cast<ptrdiff_t>(mapping->size) - offset;
*stack_len = distance_to_end > kStackToCapture ?
@@ -351,7 +331,7 @@ bool LinuxDumper::HandleDeletedFileInMapping(char* path) const {
return false;
}
memcpy(path, exe_link, NAME_MAX);
my_memcpy(path, exe_link, NAME_MAX);
return true;
}

View File

@@ -42,9 +42,7 @@
#include <linux/limits.h>
#include <stdint.h>
#include <sys/types.h>
#if !defined(__ANDROID__)
#include <sys/user.h>
#endif
#include "common/memory.h"
#include "google_breakpad/common/minidump_format.h"
@@ -56,27 +54,14 @@ typedef typeof(((struct user*) 0)->u_debugreg[0]) debugreg_t;
#endif
// Typedef for our parsing of the auxv variables in /proc/pid/auxv.
#if defined(__i386) || defined(__ARM_EABI__)
#if !defined(__ANDROID__)
#if defined(__i386) || defined(__ARM_EABI__) || defined(__mips__)
typedef Elf32_auxv_t elf_aux_entry;
#else
// Android is missing this structure definition
typedef struct
{
uint32_t a_type; /* Entry type */
union
{
uint32_t a_val; /* Integer value */
} a_un;
} elf_aux_entry;
#if !defined(AT_SYSINFO_EHDR)
#define AT_SYSINFO_EHDR 33
#endif
#endif // __ANDROID__
#elif defined(__x86_64)
typedef Elf64_auxv_t elf_aux_entry;
#endif
typedef typeof(((elf_aux_entry*) 0)->a_un.a_val) elf_aux_val_t;
// 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!
@@ -87,10 +72,7 @@ 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
uintptr_t stack_pointer; // thread stack pointer
#if defined(__i386) || defined(__x86_64)
@@ -104,12 +86,14 @@ struct ThreadInfo {
#elif defined(__ARM_EABI__)
// Mimicking how strace does this(see syscall.c, search for GETREGS)
#if defined(__ANDROID__)
struct pt_regs regs;
#else
struct user_regs regs;
struct user_fpregs fpregs;
#endif // __ANDROID__
#elif defined(__mips__)
user_regs_struct regs;
user_fpregs_struct fpregs;
uint32_t hi[3];
uint32_t lo[3];
uint32_t dsp_control;
#endif
};
@@ -146,6 +130,7 @@ class LinuxDumper {
const wasteful_vector<pid_t> &threads() { return threads_; }
const wasteful_vector<MappingInfo*> &mappings() { return mappings_; }
const MappingInfo* FindMapping(const void* address) const;
const wasteful_vector<elf_aux_val_t>& auxv() { return auxv_; }
// 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
@@ -173,15 +158,6 @@ class LinuxDumper {
unsigned int mapping_id,
uint8_t identifier[sizeof(MDGUID)]);
// 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(pid_t pid) const;
// Utility method to find the entry point location.
void* FindEntryPoint(pid_t pid) const;
uintptr_t crash_address() const { return crash_address_; }
void set_crash_address(uintptr_t crash_address) {
crash_address_ = crash_address;
@@ -194,6 +170,8 @@ class LinuxDumper {
void set_crash_thread(pid_t crash_thread) { crash_thread_ = crash_thread; }
protected:
bool ReadAuxv();
virtual bool EnumerateMappings();
virtual bool EnumerateThreads() = 0;
@@ -228,6 +206,9 @@ class LinuxDumper {
// Info from /proc/<pid>/maps.
wasteful_vector<MappingInfo*> mappings_;
// Info from /proc/<pid>/auxv
wasteful_vector<elf_aux_val_t> auxv_;
};
} // namespace google_breakpad

View File

@@ -38,6 +38,7 @@
#include <sys/syscall.h>
#include <unistd.h>
#include "common/scoped_ptr.h"
#include "third_party/lss/linux_syscall_support.h"
#if defined(__ARM_EABI__)
@@ -46,6 +47,8 @@
#define TID_PTR_REGISTER "ecx"
#elif defined(__x86_64)
#define TID_PTR_REGISTER "rcx"
#elif defined(__mips__)
#define TID_PTR_REGISTER "$1"
#else
#error This test has not been ported to this platform.
#endif
@@ -77,7 +80,7 @@ int main(int argc, char *argv[]) {
fprintf(stderr, "ERROR: number of threads is 0");
return 1;
}
pthread_t threads[num_threads];
google_breakpad::scoped_array<pthread_t> threads(new pthread_t[num_threads]);
pthread_attr_t thread_attributes;
pthread_attr_init(&thread_attributes);
pthread_attr_setdetachstate(&thread_attributes, PTHREAD_CREATE_DETACHED);

View File

@@ -49,6 +49,10 @@
#include <sys/ptrace.h>
#include <sys/wait.h>
#if defined(__i386)
#include <cpuid.h>
#endif
#include "client/linux/minidump_writer/directory_reader.h"
#include "client/linux/minidump_writer/line_reader.h"
#include "common/linux/linux_libc_support.h"
@@ -112,15 +116,15 @@ bool LinuxPtraceDumper::BuildProcPath(char* path, pid_t pid,
if (node_len == 0)
return false;
const unsigned pid_len = my_int_len(pid);
const unsigned pid_len = my_uint_len(pid);
const size_t total_length = 6 + pid_len + 1 + node_len;
if (total_length >= NAME_MAX)
return false;
memcpy(path, "/proc/", 6);
my_itos(path + 6, pid, pid_len);
my_memcpy(path, "/proc/", 6);
my_uitos(path + 6, pid, pid_len);
path[6 + pid_len] = '/';
memcpy(path + 6 + pid_len + 1, node, node_len);
my_memcpy(path + 6 + pid_len + 1, node, node_len);
path[total_length] = '\0';
return true;
}
@@ -138,7 +142,7 @@ void LinuxPtraceDumper::CopyFromProcess(void* dest, pid_t child,
if (sys_ptrace(PTRACE_PEEKDATA, child, remote + done, &tmp) == -1) {
tmp = 0;
}
memcpy(local + done, &tmp, l);
my_memcpy(local + done, &tmp, l);
done += l;
}
}
@@ -186,16 +190,25 @@ bool LinuxPtraceDumper::GetThreadInfoByIndex(size_t index, ThreadInfo* info) {
return false;
}
#if !defined(__ANDROID__)
if (sys_ptrace(PTRACE_GETFPREGS, tid, NULL, &info->fpregs) == -1) {
return false;
}
#endif
#if defined(__i386)
if (sys_ptrace(PTRACE_GETFPXREGS, tid, NULL, &info->fpxregs) == -1)
return false;
#if !defined(bit_FXSAVE) // e.g. Clang
#define bit_FXSAVE bit_FXSR
#endif
// Detect if the CPU supports the FXSAVE/FXRSTOR instructions
int eax, ebx, ecx, edx;
__cpuid(1, eax, ebx, ecx, edx);
if (edx & bit_FXSAVE) {
if (sys_ptrace(PTRACE_GETFPXREGS, tid, NULL, &info->fpxregs) == -1) {
return false;
}
} else {
memset(&info->fpxregs, 0, sizeof(info->fpxregs));
}
#endif // defined(__i386)
#if defined(__i386) || defined(__x86_64)
for (unsigned i = 0; i < ThreadInfo::kNumDebugRegisters; ++i) {
@@ -210,19 +223,33 @@ bool LinuxPtraceDumper::GetThreadInfoByIndex(size_t index, ThreadInfo* info) {
}
#endif
#if defined(__mips__)
for (int i = 0; i < 3; ++i) {
sys_ptrace(PTRACE_PEEKUSER, tid,
reinterpret_cast<void*>(DSP_BASE + (i * 2)), &info->hi[i]);
sys_ptrace(PTRACE_PEEKUSER, tid,
reinterpret_cast<void*>(DSP_BASE + (i * 2) + 1), &info->lo[i]);
}
sys_ptrace(PTRACE_PEEKUSER, tid,
reinterpret_cast<void*>(DSP_CONTROL), &info->dsp_control);
#endif
const uint8_t* stack_pointer;
#if defined(__i386)
memcpy(&stack_pointer, &info->regs.esp, sizeof(info->regs.esp));
my_memcpy(&stack_pointer, &info->regs.esp, sizeof(info->regs.esp));
#elif defined(__x86_64)
memcpy(&stack_pointer, &info->regs.rsp, sizeof(info->regs.rsp));
my_memcpy(&stack_pointer, &info->regs.rsp, sizeof(info->regs.rsp));
#elif defined(__ARM_EABI__)
memcpy(&stack_pointer, &info->regs.ARM_sp, sizeof(info->regs.ARM_sp));
my_memcpy(&stack_pointer, &info->regs.ARM_sp, sizeof(info->regs.ARM_sp));
#elif defined(__mips__)
stack_pointer =
reinterpret_cast<uint8_t*>(info->regs.regs[MD_CONTEXT_MIPS_REG_SP]);
#else
#error "This code hasn't been ported to your platform yet."
#endif
info->stack_pointer = reinterpret_cast<uintptr_t>(stack_pointer);
return GetStackInfo(&info->stack, &info->stack_len,
(uintptr_t) stack_pointer);
return true;
}
bool LinuxPtraceDumper::IsPostMortem() const {
@@ -237,8 +264,8 @@ bool LinuxPtraceDumper::ThreadsSuspend() {
// If the thread either disappeared before we could attach to it, or if
// it was part of the seccomp sandbox's trusted code, it is OK to
// silently drop it from the minidump.
memmove(&threads_[i], &threads_[i+1],
(threads_.size() - i - 1) * sizeof(threads_[i]));
my_memmove(&threads_[i], &threads_[i+1],
(threads_.size() - i - 1) * sizeof(threads_[i]));
threads_.resize(threads_.size() - 1);
--i;
}

View File

@@ -28,17 +28,20 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// linux_ptrace_dumper_unittest.cc:
// Unit tests for google_breakpad::LinuxPtraceDumoer.
// Unit tests for google_breakpad::LinuxPtraceDumper.
//
// This file was renamed from linux_dumper_unittest.cc and modified due
// to LinuxDumper being splitted into two classes.
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <unistd.h>
#include <signal.h>
#include <stdint.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/poll.h>
#include <sys/stat.h>
#include <sys/types.h>
@@ -47,43 +50,80 @@
#include "breakpad_googletest_includes.h"
#include "client/linux/minidump_writer/linux_ptrace_dumper.h"
#include "client/linux/minidump_writer/minidump_writer_unittest_utils.h"
#include "common/linux/eintr_wrapper.h"
#include "common/linux/file_id.h"
#include "common/linux/ignore_ret.h"
#include "common/linux/safe_readlink.h"
#include "common/memory.h"
#include "common/using_std_string.h"
#ifndef PR_SET_PTRACER
#define PR_SET_PTRACER 0x59616d61
#endif
using std::string;
using namespace google_breakpad;
namespace {
typedef testing::Test LinuxPtraceDumperTest;
string GetHelperBinary() {
// Locate helper binary next to the current binary.
char self_path[PATH_MAX];
if (!SafeReadLink("/proc/self/exe", self_path)) {
return "";
/* Fixture for running tests in a child process. */
class LinuxPtraceDumperChildTest : public testing::Test {
protected:
virtual void SetUp() {
child_pid_ = fork();
#ifndef __ANDROID__
prctl(PR_SET_PTRACER, child_pid_);
#endif
}
string helper_path(self_path);
size_t pos = helper_path.rfind('/');
if (pos == string::npos) {
return "";
}
helper_path.erase(pos + 1);
helper_path += "linux_dumper_unittest_helper";
return helper_path;
}
/* Gtest is calling TestBody from this class, which sets up a child
* process in which the RealTestBody virtual member is called.
* As such, TestBody is not supposed to be overridden in derived classes.
*/
virtual void TestBody() /* final */ {
if (child_pid_ == 0) {
// child process
RealTestBody();
exit(HasFatalFailure() ? kFatalFailure :
(HasNonfatalFailure() ? kNonFatalFailure : 0));
}
ASSERT_TRUE(child_pid_ > 0);
int status;
waitpid(child_pid_, &status, 0);
if (WEXITSTATUS(status) == kFatalFailure) {
GTEST_FATAL_FAILURE_("Test failed in child process");
} else if (WEXITSTATUS(status) == kNonFatalFailure) {
GTEST_NONFATAL_FAILURE_("Test failed in child process");
}
}
/* Gtest defines TestBody functions through its macros, but classes
* derived from this one need to define RealTestBody instead.
* This is achieved by defining a TestBody macro further below.
*/
virtual void RealTestBody() = 0;
private:
static const int kFatalFailure = 1;
static const int kNonFatalFailure = 2;
pid_t child_pid_;
};
} // namespace
TEST(LinuxPtraceDumperTest, Setup) {
LinuxPtraceDumper dumper(getpid());
/* Replace TestBody declarations within TEST*() with RealTestBody
* declarations */
#define TestBody RealTestBody
TEST_F(LinuxPtraceDumperChildTest, Setup) {
LinuxPtraceDumper dumper(getppid());
}
TEST(LinuxPtraceDumperTest, FindMappings) {
LinuxPtraceDumper dumper(getpid());
TEST_F(LinuxPtraceDumperChildTest, FindMappings) {
LinuxPtraceDumper dumper(getppid());
ASSERT_TRUE(dumper.Init());
ASSERT_TRUE(dumper.FindMapping(reinterpret_cast<void*>(getpid)));
@@ -91,30 +131,41 @@ TEST(LinuxPtraceDumperTest, FindMappings) {
ASSERT_FALSE(dumper.FindMapping(NULL));
}
TEST(LinuxPtraceDumperTest, ThreadList) {
LinuxPtraceDumper dumper(getpid());
TEST_F(LinuxPtraceDumperChildTest, ThreadList) {
LinuxPtraceDumper dumper(getppid());
ASSERT_TRUE(dumper.Init());
ASSERT_GE(dumper.threads().size(), (size_t)1);
bool found = false;
for (size_t i = 0; i < dumper.threads().size(); ++i) {
if (dumper.threads()[i] == getpid()) {
if (dumper.threads()[i] == getppid()) {
ASSERT_FALSE(found);
found = true;
break;
}
}
ASSERT_TRUE(found);
}
// Helper stack class to close a file descriptor and unmap
// a mmap'ed mapping.
class StackHelper {
public:
StackHelper(int fd, char* mapping, size_t size)
: fd_(fd), mapping_(mapping), size_(size) {}
StackHelper()
: fd_(-1), mapping_(NULL), size_(0) {}
~StackHelper() {
munmap(mapping_, size_);
close(fd_);
if (size_)
munmap(mapping_, size_);
if (fd_ >= 0)
close(fd_);
}
void Init(int fd, char* mapping, size_t size) {
fd_ = fd;
mapping_ = mapping;
size_ = size;
}
char* mapping() const { return mapping_; }
size_t size() const { return size_; }
private:
int fd_;
@@ -122,19 +173,29 @@ class StackHelper {
size_t size_;
};
TEST(LinuxPtraceDumperTest, MergedMappings) {
string helper_path(GetHelperBinary());
if (helper_path.empty()) {
class LinuxPtraceDumperMappingsTest : public LinuxPtraceDumperChildTest {
protected:
virtual void SetUp();
string helper_path_;
size_t page_size_;
StackHelper helper_;
};
void LinuxPtraceDumperMappingsTest::SetUp() {
helper_path_ = GetHelperBinary();
if (helper_path_.empty()) {
FAIL() << "Couldn't find helper binary";
exit(1);
}
// mmap two segments out of the helper binary, one
// enclosed in the other, but with different protections.
const size_t kPageSize = sysconf(_SC_PAGESIZE);
const size_t kMappingSize = 3 * kPageSize;
int fd = open(helper_path.c_str(), O_RDONLY);
ASSERT_NE(-1, fd);
page_size_ = sysconf(_SC_PAGESIZE);
const size_t kMappingSize = 3 * page_size_;
int fd = open(helper_path_.c_str(), O_RDONLY);
ASSERT_NE(-1, fd) << "Failed to open file: " << helper_path_
<< ", Error: " << strerror(errno);
char* mapping =
reinterpret_cast<char*>(mmap(NULL,
kMappingSize,
@@ -144,41 +205,162 @@ TEST(LinuxPtraceDumperTest, MergedMappings) {
0));
ASSERT_TRUE(mapping);
const uintptr_t kMappingAddress = reinterpret_cast<uintptr_t>(mapping);
// Ensure that things get cleaned up.
StackHelper helper(fd, mapping, kMappingSize);
helper_.Init(fd, mapping, kMappingSize);
// Carve a page out of the first mapping with different permissions.
char* inside_mapping = reinterpret_cast<char*>(
mmap(mapping + 2 *kPageSize,
kPageSize,
mmap(mapping + 2 * page_size_,
page_size_,
PROT_NONE,
MAP_SHARED | MAP_FIXED,
fd,
// Map a different offset just to
// better test real-world conditions.
kPageSize));
page_size_));
ASSERT_TRUE(inside_mapping);
LinuxPtraceDumperChildTest::SetUp();
}
TEST_F(LinuxPtraceDumperMappingsTest, MergedMappings) {
// Now check that LinuxPtraceDumper interpreted the mappings properly.
LinuxPtraceDumper dumper(getpid());
LinuxPtraceDumper dumper(getppid());
ASSERT_TRUE(dumper.Init());
int mapping_count = 0;
for (unsigned i = 0; i < dumper.mappings().size(); ++i) {
const MappingInfo& mapping = *dumper.mappings()[i];
if (strcmp(mapping.name, helper_path.c_str()) == 0) {
if (strcmp(mapping.name, this->helper_path_.c_str()) == 0) {
// This mapping should encompass the entire original mapped
// range.
EXPECT_EQ(kMappingAddress, mapping.start_addr);
EXPECT_EQ(kMappingSize, mapping.size);
EXPECT_EQ(0, mapping.offset);
EXPECT_EQ(reinterpret_cast<uintptr_t>(this->helper_.mapping()),
mapping.start_addr);
EXPECT_EQ(this->helper_.size(), mapping.size);
EXPECT_EQ(0U, mapping.offset);
mapping_count++;
}
}
EXPECT_EQ(1, mapping_count);
}
TEST_F(LinuxPtraceDumperChildTest, BuildProcPath) {
const pid_t pid = getppid();
LinuxPtraceDumper dumper(pid);
char maps_path[NAME_MAX] = "";
char maps_path_expected[NAME_MAX];
snprintf(maps_path_expected, sizeof(maps_path_expected),
"/proc/%d/maps", pid);
EXPECT_TRUE(dumper.BuildProcPath(maps_path, pid, "maps"));
EXPECT_STREQ(maps_path_expected, maps_path);
EXPECT_FALSE(dumper.BuildProcPath(NULL, pid, "maps"));
EXPECT_FALSE(dumper.BuildProcPath(maps_path, 0, "maps"));
EXPECT_FALSE(dumper.BuildProcPath(maps_path, pid, ""));
EXPECT_FALSE(dumper.BuildProcPath(maps_path, pid, NULL));
char long_node[NAME_MAX];
size_t long_node_len = NAME_MAX - strlen("/proc/123") - 1;
memset(long_node, 'a', long_node_len);
long_node[long_node_len] = '\0';
EXPECT_FALSE(dumper.BuildProcPath(maps_path, 123, long_node));
}
#if !defined(__ARM_EABI__) && !defined(__mips__)
// Ensure that the linux-gate VDSO is included in the mapping list.
TEST_F(LinuxPtraceDumperChildTest, MappingsIncludeLinuxGate) {
LinuxPtraceDumper dumper(getppid());
ASSERT_TRUE(dumper.Init());
void* linux_gate_loc =
reinterpret_cast<void *>(dumper.auxv()[AT_SYSINFO_EHDR]);
ASSERT_TRUE(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));
}
// Ensure that the linux-gate VDSO can generate a non-zeroed File ID.
TEST_F(LinuxPtraceDumperChildTest, LinuxGateMappingID) {
LinuxPtraceDumper dumper(getppid());
ASSERT_TRUE(dumper.Init());
bool found_linux_gate = false;
const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
unsigned index = 0;
for (unsigned i = 0; i < mappings.size(); ++i) {
if (!strcmp(mappings[i]->name, kLinuxGateLibraryName)) {
found_linux_gate = true;
index = i;
break;
}
}
ASSERT_TRUE(found_linux_gate);
// Need to suspend the child so ptrace actually works.
ASSERT_TRUE(dumper.ThreadsSuspend());
uint8_t identifier[sizeof(MDGUID)];
ASSERT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[index],
true,
index,
identifier));
uint8_t empty_identifier[sizeof(MDGUID)];
memset(empty_identifier, 0, sizeof(empty_identifier));
EXPECT_NE(0, memcmp(empty_identifier, identifier, sizeof(identifier)));
EXPECT_TRUE(dumper.ThreadsResume());
}
#endif
TEST_F(LinuxPtraceDumperChildTest, FileIDsMatch) {
// Calculate the File ID of our binary using both
// FileID::ElfFileIdentifier and LinuxDumper::ElfFileIdentifierForMapping
// and ensure that we get the same result from both.
char exe_name[PATH_MAX];
ASSERT_TRUE(SafeReadLink("/proc/self/exe", exe_name));
LinuxPtraceDumper dumper(getppid());
ASSERT_TRUE(dumper.Init());
const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
bool found_exe = false;
unsigned i;
for (i = 0; i < mappings.size(); ++i) {
const MappingInfo* mapping = mappings[i];
if (!strcmp(mapping->name, exe_name)) {
found_exe = true;
break;
}
}
ASSERT_TRUE(found_exe);
uint8_t identifier1[sizeof(MDGUID)];
uint8_t identifier2[sizeof(MDGUID)];
EXPECT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[i], true, i,
identifier1));
FileID fileid(exe_name);
EXPECT_TRUE(fileid.ElfFileIdentifier(identifier2));
char identifier_string1[37];
char identifier_string2[37];
FileID::ConvertIdentifierToString(identifier1, identifier_string1,
37);
FileID::ConvertIdentifierToString(identifier2, identifier_string2,
37);
EXPECT_STREQ(identifier_string1, identifier_string2);
}
/* Get back to normal behavior of TEST*() macros wrt TestBody. */
#undef TestBody
TEST(LinuxPtraceDumperTest, VerifyStackReadWithMultipleThreads) {
static const int kNumberOfThreadsInHelperProgram = 5;
char kNumberOfThreadsArgument[2];
@@ -224,7 +406,8 @@ TEST(LinuxPtraceDumperTest, VerifyStackReadWithMultipleThreads) {
ASSERT_EQ(1, r);
ASSERT_TRUE(pfd.revents & POLLIN);
uint8_t junk;
ASSERT_EQ(read(fds[0], &junk, sizeof(junk)), sizeof(junk));
ASSERT_EQ(read(fds[0], &junk, sizeof(junk)),
static_cast<ssize_t>(sizeof(junk)));
}
close(fds[0]);
@@ -242,14 +425,21 @@ TEST(LinuxPtraceDumperTest, VerifyStackReadWithMultipleThreads) {
ThreadInfo one_thread;
for (size_t i = 0; i < dumper.threads().size(); ++i) {
EXPECT_TRUE(dumper.GetThreadInfoByIndex(i, &one_thread));
const void* stack;
size_t stack_len;
EXPECT_TRUE(dumper.GetStackInfo(&stack, &stack_len,
one_thread.stack_pointer));
// In the helper program, we stored a pointer to the thread id in a
// specific register. Check that we can recover its value.
#if defined(__ARM_EABI__)
pid_t *process_tid_location = (pid_t *)(one_thread.regs.uregs[3]);
pid_t* process_tid_location = (pid_t*)(one_thread.regs.uregs[3]);
#elif defined(__i386)
pid_t *process_tid_location = (pid_t *)(one_thread.regs.ecx);
pid_t* process_tid_location = (pid_t*)(one_thread.regs.ecx);
#elif defined(__x86_64)
pid_t *process_tid_location = (pid_t *)(one_thread.regs.rcx);
pid_t* process_tid_location = (pid_t*)(one_thread.regs.rcx);
#elif defined(__mips__)
pid_t* process_tid_location =
reinterpret_cast<pid_t*>(one_thread.regs.regs[1]);
#else
#error This test has not been ported to this platform.
#endif
@@ -269,177 +459,3 @@ TEST(LinuxPtraceDumperTest, VerifyStackReadWithMultipleThreads) {
ASSERT_TRUE(WIFSIGNALED(status));
ASSERT_EQ(SIGKILL, WTERMSIG(status));
}
TEST(LinuxPtraceDumperTest, BuildProcPath) {
const pid_t pid = getpid();
LinuxPtraceDumper dumper(pid);
char maps_path[NAME_MAX] = "";
char maps_path_expected[NAME_MAX];
snprintf(maps_path_expected, sizeof(maps_path_expected),
"/proc/%d/maps", pid);
EXPECT_TRUE(dumper.BuildProcPath(maps_path, pid, "maps"));
EXPECT_STREQ(maps_path_expected, maps_path);
EXPECT_FALSE(dumper.BuildProcPath(NULL, pid, "maps"));
EXPECT_FALSE(dumper.BuildProcPath(maps_path, 0, "maps"));
EXPECT_FALSE(dumper.BuildProcPath(maps_path, pid, ""));
EXPECT_FALSE(dumper.BuildProcPath(maps_path, pid, NULL));
char long_node[NAME_MAX];
size_t long_node_len = NAME_MAX - strlen("/proc/123") - 1;
memset(long_node, 'a', long_node_len);
long_node[long_node_len] = '\0';
EXPECT_FALSE(dumper.BuildProcPath(maps_path, 123, long_node));
}
#if !defined(__ARM_EABI__)
// Ensure that the linux-gate VDSO is included in the mapping list.
TEST(LinuxPtraceDumperTest, MappingsIncludeLinuxGate) {
LinuxPtraceDumper dumper(getpid());
ASSERT_TRUE(dumper.Init());
void* linux_gate_loc = dumper.FindBeginningOfLinuxGateSharedLibrary(getpid());
ASSERT_TRUE(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));
}
// Ensure that the linux-gate VDSO can generate a non-zeroed File ID.
TEST(LinuxPtraceDumperTest, LinuxGateMappingID) {
LinuxPtraceDumper dumper(getpid());
ASSERT_TRUE(dumper.Init());
bool found_linux_gate = false;
const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
unsigned index = 0;
for (unsigned i = 0; i < mappings.size(); ++i) {
if (!strcmp(mappings[i]->name, kLinuxGateLibraryName)) {
found_linux_gate = true;
index = i;
break;
}
}
ASSERT_TRUE(found_linux_gate);
uint8_t identifier[sizeof(MDGUID)];
ASSERT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[index],
true,
index,
identifier));
uint8_t empty_identifier[sizeof(MDGUID)];
memset(empty_identifier, 0, sizeof(empty_identifier));
EXPECT_NE(0, memcmp(empty_identifier, identifier, sizeof(identifier)));
}
// Ensure that the linux-gate VDSO can generate a non-zeroed File ID
// from a child process.
TEST(LinuxPtraceDumperTest, LinuxGateMappingIDChild) {
int fds[2];
ASSERT_NE(-1, pipe(fds));
// Fork a child so ptrace works.
const pid_t child = fork();
if (child == 0) {
close(fds[1]);
// Now wait forever for the parent.
char b;
HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
close(fds[0]);
syscall(__NR_exit);
}
close(fds[0]);
LinuxPtraceDumper dumper(child);
ASSERT_TRUE(dumper.Init());
bool found_linux_gate = false;
const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
unsigned index = 0;
for (unsigned i = 0; i < mappings.size(); ++i) {
if (!strcmp(mappings[i]->name, kLinuxGateLibraryName)) {
found_linux_gate = true;
index = i;
break;
}
}
ASSERT_TRUE(found_linux_gate);
// Need to suspend the child so ptrace actually works.
ASSERT_TRUE(dumper.ThreadsSuspend());
uint8_t identifier[sizeof(MDGUID)];
ASSERT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[index],
true,
index,
identifier));
uint8_t empty_identifier[sizeof(MDGUID)];
memset(empty_identifier, 0, sizeof(empty_identifier));
EXPECT_NE(0, memcmp(empty_identifier, identifier, sizeof(identifier)));
EXPECT_TRUE(dumper.ThreadsResume());
close(fds[1]);
}
#endif
TEST(LinuxPtraceDumperTest, FileIDsMatch) {
// Calculate the File ID of our binary using both
// FileID::ElfFileIdentifier and LinuxDumper::ElfFileIdentifierForMapping
// and ensure that we get the same result from both.
char exe_name[PATH_MAX];
ASSERT_TRUE(SafeReadLink("/proc/self/exe", exe_name));
int fds[2];
ASSERT_NE(-1, pipe(fds));
// Fork a child so ptrace works.
const pid_t child = fork();
if (child == 0) {
close(fds[1]);
// Now wait forever for the parent.
char b;
HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
close(fds[0]);
syscall(__NR_exit);
}
close(fds[0]);
LinuxPtraceDumper dumper(child);
ASSERT_TRUE(dumper.Init());
const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
bool found_exe = false;
unsigned i;
for (i = 0; i < mappings.size(); ++i) {
const MappingInfo* mapping = mappings[i];
if (!strcmp(mapping->name, exe_name)) {
found_exe = true;
break;
}
}
ASSERT_TRUE(found_exe);
uint8_t identifier1[sizeof(MDGUID)];
uint8_t identifier2[sizeof(MDGUID)];
EXPECT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[i], true, i,
identifier1));
FileID fileid(exe_name);
EXPECT_TRUE(fileid.ElfFileIdentifier(identifier2));
char identifier_string1[37];
char identifier_string2[37];
FileID::ConvertIdentifierToString(identifier1, identifier_string1,
37);
FileID::ConvertIdentifierToString(identifier2, identifier_string2,
37);
EXPECT_STREQ(identifier_string1, identifier_string2);
close(fds[1]);
}

File diff suppressed because it is too large Load Diff

View File

@@ -31,6 +31,7 @@
#define CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_
#include <stdint.h>
#include <sys/types.h>
#include <unistd.h>
#include <list>
@@ -45,32 +46,78 @@ class ExceptionHandler;
struct MappingEntry {
MappingInfo first;
u_int8_t second[sizeof(MDGUID)];
uint8_t second[sizeof(MDGUID)];
};
// A list of <MappingInfo, GUID>
typedef std::list<MappingEntry> MappingList;
// Write a minidump to the filesystem. This function does not malloc nor use
// These entries store a list of memory regions that the client wants included
// in the minidump.
struct AppMemory {
void* ptr;
size_t length;
bool operator==(const struct AppMemory& other) const {
return ptr == other.ptr;
}
bool operator==(const void* other) const {
return ptr == other;
}
};
typedef std::list<AppMemory> AppMemoryList;
// Writes a minidump to the filesystem. These functions do 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.
// minidump_path: the path to the file to write to. This is opened O_EXCL and
// fails 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
//
// Returns true iff successful.
bool WriteMinidump(const char* filename, pid_t crashing_process,
bool WriteMinidump(const char* minidump_path, pid_t crashing_process,
const void* blob, size_t blob_size);
// Same as above but takes an open file descriptor instead of a path.
bool WriteMinidump(int minidump_fd, pid_t crashing_process,
const void* blob, size_t blob_size);
// This overload also allows passing a list of known mappings.
bool WriteMinidump(const char* filename, pid_t crashing_process,
// Alternate form of WriteMinidump() that works with processes that
// are not expected to have crashed. If |process_blamed_thread| is
// meaningful, it will be the one from which a crash signature is
// extracted. It is not expected that this function will be called
// from a compromised context, but it is safe to do so.
bool WriteMinidump(const char* minidump_path, pid_t process,
pid_t process_blamed_thread);
// These overloads also allow passing a list of known mappings and
// a list of additional memory regions to be included in the minidump.
bool WriteMinidump(const char* minidump_path, pid_t crashing_process,
const void* blob, size_t blob_size,
const MappingList& mappings);
const MappingList& mappings,
const AppMemoryList& appdata);
bool WriteMinidump(int minidump_fd, pid_t crashing_process,
const void* blob, size_t blob_size,
const MappingList& mappings,
const AppMemoryList& appdata);
// These overloads also allow passing a file size limit for the minidump.
bool WriteMinidump(const char* minidump_path, off_t minidump_size_limit,
pid_t crashing_process,
const void* blob, size_t blob_size,
const MappingList& mappings,
const AppMemoryList& appdata);
bool WriteMinidump(int minidump_fd, off_t minidump_size_limit,
pid_t crashing_process,
const void* blob, size_t blob_size,
const MappingList& mappings,
const AppMemoryList& appdata);
bool WriteMinidump(const char* filename,
const MappingList& mappings,
const AppMemoryList& appdata,
LinuxDumper* dumper);
} // namespace google_breakpad

View File

@@ -32,6 +32,7 @@
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <ucontext.h>
#include <unistd.h>
#include <string>
@@ -40,10 +41,15 @@
#include "client/linux/handler/exception_handler.h"
#include "client/linux/minidump_writer/linux_dumper.h"
#include "client/linux/minidump_writer/minidump_writer.h"
#include "client/linux/minidump_writer/minidump_writer_unittest_utils.h"
#include "common/linux/eintr_wrapper.h"
#include "common/linux/file_id.h"
#include "common/linux/ignore_ret.h"
#include "common/linux/safe_readlink.h"
#include "common/scoped_ptr.h"
#include "common/tests/auto_tempdir.h"
#include "common/tests/file_utils.h"
#include "common/using_std_string.h"
#include "google_breakpad/processor/minidump.h"
using namespace google_breakpad;
@@ -53,10 +59,41 @@ using namespace google_breakpad;
const int kGUIDStringSize = 37;
namespace {
typedef testing::Test MinidumpWriterTest;
const char kMDWriterUnitTestFileName[] = "/minidump-writer-unittest";
TEST(MinidumpWriterTest, SetupWithPath) {
int fds[2];
ASSERT_NE(-1, pipe(fds));
const pid_t child = fork();
if (child == 0) {
close(fds[1]);
char b;
IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b))));
close(fds[0]);
syscall(__NR_exit);
}
close(fds[0]);
ExceptionHandler::CrashContext context;
memset(&context, 0, sizeof(context));
AutoTempDir temp_dir;
string templ = temp_dir.path() + kMDWriterUnitTestFileName;
// Set a non-zero tid to avoid tripping asserts.
context.tid = child;
ASSERT_TRUE(WriteMinidump(templ.c_str(), child, &context, sizeof(context)));
struct stat st;
ASSERT_EQ(0, stat(templ.c_str(), &st));
ASSERT_GT(st.st_size, 0);
close(fds[1]);
}
TEST(MinidumpWriterTest, Setup) {
TEST(MinidumpWriterTest, SetupWithFD) {
int fds[2];
ASSERT_NE(-1, pipe(fds));
@@ -74,13 +111,14 @@ TEST(MinidumpWriterTest, Setup) {
memset(&context, 0, sizeof(context));
AutoTempDir temp_dir;
std::string templ = temp_dir.path() + "/minidump-writer-unittest";
string templ = temp_dir.path() + kMDWriterUnitTestFileName;
int fd = open(templ.c_str(), O_CREAT | O_WRONLY, S_IRWXU);
// Set a non-zero tid to avoid tripping asserts.
context.tid = 1;
ASSERT_TRUE(WriteMinidump(templ.c_str(), child, &context, sizeof(context)));
context.tid = child;
ASSERT_TRUE(WriteMinidump(fd, child, &context, sizeof(context)));
struct stat st;
ASSERT_EQ(stat(templ.c_str(), &st), 0);
ASSERT_GT(st.st_size, 0u);
ASSERT_EQ(0, stat(templ.c_str(), &st));
ASSERT_GT(st.st_size, 0);
close(fds[1]);
}
@@ -93,9 +131,9 @@ TEST(MinidumpWriterTest, MappingInfo) {
// These are defined here so the parent can use them to check the
// data from the minidump afterwards.
const u_int32_t kMemorySize = sysconf(_SC_PAGESIZE);
const uint32_t memory_size = sysconf(_SC_PAGESIZE);
const char* kMemoryName = "a fake module";
const u_int8_t kModuleGUID[sizeof(MDGUID)] = {
const uint8_t kModuleGUID[sizeof(MDGUID)] = {
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
};
@@ -116,7 +154,7 @@ TEST(MinidumpWriterTest, MappingInfo) {
// Get some memory.
char* memory =
reinterpret_cast<char*>(mmap(NULL,
kMemorySize,
memory_size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON,
-1,
@@ -128,7 +166,7 @@ TEST(MinidumpWriterTest, MappingInfo) {
if (child == 0) {
close(fds[1]);
char b;
HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b))));
close(fds[0]);
syscall(__NR_exit);
}
@@ -136,30 +174,32 @@ TEST(MinidumpWriterTest, MappingInfo) {
ExceptionHandler::CrashContext context;
memset(&context, 0, sizeof(context));
context.tid = 1;
ASSERT_EQ(0, getcontext(&context.context));
context.tid = child;
AutoTempDir temp_dir;
std::string templ = temp_dir.path() + "/minidump-writer-unittest";
string templ = temp_dir.path() + kMDWriterUnitTestFileName;
// Add information about the mapped memory.
MappingInfo info;
info.start_addr = kMemoryAddress;
info.size = kMemorySize;
info.size = memory_size;
info.offset = 0;
strcpy(info.name, kMemoryName);
MappingList mappings;
AppMemoryList memory_list;
MappingEntry mapping;
mapping.first = info;
memcpy(mapping.second, kModuleGUID, sizeof(MDGUID));
mappings.push_back(mapping);
ASSERT_TRUE(WriteMinidump(templ.c_str(), child, &context, sizeof(context),
mappings));
mappings, memory_list));
// Read the minidump. Load the module list, and ensure that
// the mmap'ed |memory| is listed with the given module name
// and debug ID.
Minidump minidump(templ.c_str());
Minidump minidump(templ);
ASSERT_TRUE(minidump.Read());
MinidumpModuleList* module_list = minidump.GetModuleList();
@@ -169,10 +209,24 @@ TEST(MinidumpWriterTest, MappingInfo) {
ASSERT_TRUE(module);
EXPECT_EQ(kMemoryAddress, module->base_address());
EXPECT_EQ(kMemorySize, module->size());
EXPECT_EQ(memory_size, module->size());
EXPECT_EQ(kMemoryName, module->code_file());
EXPECT_EQ(module_identifier, module->debug_identifier());
uint32_t len;
// These streams are expected to be there
EXPECT_TRUE(minidump.SeekToStreamType(MD_THREAD_LIST_STREAM, &len));
EXPECT_TRUE(minidump.SeekToStreamType(MD_MEMORY_LIST_STREAM, &len));
EXPECT_TRUE(minidump.SeekToStreamType(MD_EXCEPTION_STREAM, &len));
EXPECT_TRUE(minidump.SeekToStreamType(MD_SYSTEM_INFO_STREAM, &len));
EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_CPU_INFO, &len));
EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_PROC_STATUS, &len));
EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_CMD_LINE, &len));
EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_ENVIRON, &len));
EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_AUXV, &len));
EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_MAPS, &len));
EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_DSO_DEBUG, &len));
close(fds[1]);
}
@@ -185,9 +239,9 @@ TEST(MinidumpWriterTest, MappingInfoContained) {
// These are defined here so the parent can use them to check the
// data from the minidump afterwards.
const u_int32_t kMemorySize = sysconf(_SC_PAGESIZE);
const int32_t memory_size = sysconf(_SC_PAGESIZE);
const char* kMemoryName = "a fake module";
const u_int8_t kModuleGUID[sizeof(MDGUID)] = {
const uint8_t kModuleGUID[sizeof(MDGUID)] = {
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
};
@@ -207,19 +261,19 @@ TEST(MinidumpWriterTest, MappingInfoContained) {
// mmap a file
AutoTempDir temp_dir;
std::string tempfile = temp_dir.path() + "/minidump-writer-unittest-temp";
string tempfile = temp_dir.path() + "/minidump-writer-unittest-temp";
int fd = open(tempfile.c_str(), O_RDWR | O_CREAT, 0);
ASSERT_NE(-1, fd);
unlink(tempfile.c_str());
// fill with zeros
char buffer[kMemorySize];
memset(buffer, 0, kMemorySize);
ASSERT_EQ(kMemorySize, write(fd, buffer, kMemorySize));
google_breakpad::scoped_array<char> buffer(new char[memory_size]);
memset(buffer.get(), 0, memory_size);
ASSERT_EQ(memory_size, write(fd, buffer.get(), memory_size));
lseek(fd, 0, SEEK_SET);
char* memory =
reinterpret_cast<char*>(mmap(NULL,
kMemorySize,
memory_size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE,
fd,
@@ -232,7 +286,7 @@ TEST(MinidumpWriterTest, MappingInfoContained) {
if (child == 0) {
close(fds[1]);
char b;
HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b))));
close(fds[0]);
syscall(__NR_exit);
}
@@ -242,29 +296,29 @@ TEST(MinidumpWriterTest, MappingInfoContained) {
memset(&context, 0, sizeof(context));
context.tid = 1;
std::string dumpfile = temp_dir.path() + "/minidump-writer-unittest";
string dumpfile = temp_dir.path() + kMDWriterUnitTestFileName;
// Add information about the mapped memory. Report it as being larger than
// it actually is.
MappingInfo info;
info.start_addr = kMemoryAddress - kMemorySize;
info.size = kMemorySize * 3;
info.start_addr = kMemoryAddress - memory_size;
info.size = memory_size * 3;
info.offset = 0;
strcpy(info.name, kMemoryName);
MappingList mappings;
AppMemoryList memory_list;
MappingEntry mapping;
mapping.first = info;
memcpy(mapping.second, kModuleGUID, sizeof(MDGUID));
mappings.push_back(mapping);
ASSERT_TRUE(
WriteMinidump(dumpfile.c_str(), child, &context, sizeof(context),
mappings));
ASSERT_TRUE(WriteMinidump(dumpfile.c_str(), child, &context, sizeof(context),
mappings, memory_list));
// Read the minidump. Load the module list, and ensure that
// the mmap'ed |memory| is listed with the given module name
// and debug ID.
Minidump minidump(dumpfile.c_str());
Minidump minidump(dumpfile);
ASSERT_TRUE(minidump.Read());
MinidumpModuleList* module_list = minidump.GetModuleList();
@@ -282,32 +336,18 @@ TEST(MinidumpWriterTest, MappingInfoContained) {
}
TEST(MinidumpWriterTest, DeletedBinary) {
static const int kNumberOfThreadsInHelperProgram = 1;
char kNumberOfThreadsArgument[2];
sprintf(kNumberOfThreadsArgument, "%d", kNumberOfThreadsInHelperProgram);
// Locate helper binary next to the current binary.
char self_path[PATH_MAX];
if (!SafeReadLink("/proc/self/exe", self_path)) {
FAIL() << "readlink failed";
const string kNumberOfThreadsArgument = "1";
const string helper_path(GetHelperBinary());
if (helper_path.empty()) {
FAIL() << "Couldn't find helper binary";
exit(1);
}
string helper_path(self_path);
size_t pos = helper_path.rfind('/');
if (pos == string::npos) {
FAIL() << "no trailing slash in path: " << helper_path;
exit(1);
}
helper_path.erase(pos + 1);
helper_path += "linux_dumper_unittest_helper";
// Copy binary to a temp file.
AutoTempDir temp_dir;
std::string binpath = temp_dir.path() + "/linux-dumper-unittest-helper";
char cmdline[2 * PATH_MAX];
sprintf(cmdline, "/bin/cp \"%s\" \"%s\"", helper_path.c_str(),
binpath.c_str());
ASSERT_EQ(0, system(cmdline));
string binpath = temp_dir.path() + "/linux-dumper-unittest-helper";
ASSERT_TRUE(CopyFile(helper_path.c_str(), binpath.c_str()))
<< "Failed to copy " << helper_path << " to " << binpath;
ASSERT_EQ(0, chmod(binpath.c_str(), 0755));
int fds[2];
@@ -324,7 +364,7 @@ TEST(MinidumpWriterTest, DeletedBinary) {
execl(binpath.c_str(),
binpath.c_str(),
pipe_fd_string,
kNumberOfThreadsArgument,
kNumberOfThreadsArgument.c_str(),
NULL);
}
close(fds[1]);
@@ -339,7 +379,7 @@ TEST(MinidumpWriterTest, DeletedBinary) {
ASSERT_TRUE(pfd.revents & POLLIN);
uint8_t junk;
const int nr = HANDLE_EINTR(read(fds[0], &junk, sizeof(junk)));
ASSERT_EQ(sizeof(junk), nr);
ASSERT_EQ(static_cast<ssize_t>(sizeof(junk)), nr);
close(fds[0]);
// Child is ready now.
@@ -349,20 +389,18 @@ TEST(MinidumpWriterTest, DeletedBinary) {
ExceptionHandler::CrashContext context;
memset(&context, 0, sizeof(context));
std::string templ = temp_dir.path() + "/minidump-writer-unittest";
string templ = temp_dir.path() + kMDWriterUnitTestFileName;
// Set a non-zero tid to avoid tripping asserts.
context.tid = 1;
context.tid = child_pid;
ASSERT_TRUE(WriteMinidump(templ.c_str(), child_pid, &context,
sizeof(context)));
kill(child_pid, SIGKILL);
struct stat st;
ASSERT_EQ(stat(templ.c_str(), &st), 0);
ASSERT_GT(st.st_size, 0u);
ASSERT_EQ(0, stat(templ.c_str(), &st));
ASSERT_GT(st.st_size, 0);
Minidump minidump(templ.c_str());
Minidump minidump(templ);
ASSERT_TRUE(minidump.Read());
// Check that the main module filename is correct.
@@ -380,6 +418,7 @@ TEST(MinidumpWriterTest, DeletedBinary) {
kGUIDStringSize);
string module_identifier(identifier_string);
// Strip out dashes
size_t pos;
while ((pos = module_identifier.find('-')) != string::npos) {
module_identifier.erase(pos, 1);
}
@@ -388,3 +427,337 @@ TEST(MinidumpWriterTest, DeletedBinary) {
module_identifier += "0";
EXPECT_EQ(module_identifier, module->debug_identifier());
}
// Test that an additional memory region can be added to the minidump.
TEST(MinidumpWriterTest, AdditionalMemory) {
int fds[2];
ASSERT_NE(-1, pipe(fds));
// These are defined here so the parent can use them to check the
// data from the minidump afterwards.
const uint32_t kMemorySize = sysconf(_SC_PAGESIZE);
// Get some heap memory.
uint8_t* memory = new uint8_t[kMemorySize];
const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory);
ASSERT_TRUE(memory);
// Stick some data into the memory so the contents can be verified.
for (uint32_t i = 0; i < kMemorySize; ++i) {
memory[i] = i % 255;
}
const pid_t child = fork();
if (child == 0) {
close(fds[1]);
char b;
HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
close(fds[0]);
syscall(__NR_exit);
}
close(fds[0]);
ExceptionHandler::CrashContext context;
// This needs a valid context for minidump writing to work, but getting
// a useful one from the child is too much work, so just use one from
// the parent since the child is just a forked copy anyway.
ASSERT_EQ(0, getcontext(&context.context));
context.tid = child;
AutoTempDir temp_dir;
string templ = temp_dir.path() + kMDWriterUnitTestFileName;
unlink(templ.c_str());
MappingList mappings;
AppMemoryList memory_list;
// Add the memory region to the list of memory to be included.
AppMemory app_memory;
app_memory.ptr = memory;
app_memory.length = kMemorySize;
memory_list.push_back(app_memory);
ASSERT_TRUE(WriteMinidump(templ.c_str(), child, &context, sizeof(context),
mappings, memory_list));
// Read the minidump. Ensure that the memory region is present
Minidump minidump(templ);
ASSERT_TRUE(minidump.Read());
MinidumpMemoryList* dump_memory_list = minidump.GetMemoryList();
ASSERT_TRUE(dump_memory_list);
const MinidumpMemoryRegion* region =
dump_memory_list->GetMemoryRegionForAddress(kMemoryAddress);
ASSERT_TRUE(region);
EXPECT_EQ(kMemoryAddress, region->GetBase());
EXPECT_EQ(kMemorySize, region->GetSize());
// Verify memory contents.
EXPECT_EQ(0, memcmp(region->GetMemory(), memory, kMemorySize));
delete[] memory;
close(fds[1]);
}
// Test that an invalid thread stack pointer still results in a minidump.
TEST(MinidumpWriterTest, InvalidStackPointer) {
int fds[2];
ASSERT_NE(-1, pipe(fds));
const pid_t child = fork();
if (child == 0) {
close(fds[1]);
char b;
HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
close(fds[0]);
syscall(__NR_exit);
}
close(fds[0]);
ExceptionHandler::CrashContext context;
// This needs a valid context for minidump writing to work, but getting
// a useful one from the child is too much work, so just use one from
// the parent since the child is just a forked copy anyway.
ASSERT_EQ(0, getcontext(&context.context));
context.tid = child;
// Fake the child's stack pointer for its crashing thread. NOTE: This must
// be an invalid memory address for the child process (stack or otherwise).
#if defined(__i386)
// Try 1MB below the current stack.
uintptr_t invalid_stack_pointer =
reinterpret_cast<uintptr_t>(&context) - 1024*1024;
context.context.uc_mcontext.gregs[REG_ESP] = invalid_stack_pointer;
#elif defined(__x86_64)
// Try 1MB below the current stack.
uintptr_t invalid_stack_pointer =
reinterpret_cast<uintptr_t>(&context) - 1024*1024;
context.context.uc_mcontext.gregs[REG_RSP] = invalid_stack_pointer;
#elif defined(__ARM_EABI__)
// Try 1MB below the current stack.
uintptr_t invalid_stack_pointer =
reinterpret_cast<uintptr_t>(&context) - 1024*1024;
context.context.uc_mcontext.arm_sp = invalid_stack_pointer;
#elif defined(__mips__)
// Try 1MB below the current stack.
uintptr_t invalid_stack_pointer =
reinterpret_cast<uintptr_t>(&context) - 1024 * 1024;
context.context.uc_mcontext.gregs[MD_CONTEXT_MIPS_REG_SP] =
invalid_stack_pointer;
#else
# error "This code has not been ported to your platform yet."
#endif
AutoTempDir temp_dir;
string templ = temp_dir.path() + kMDWriterUnitTestFileName;
// NOTE: In previous versions of Breakpad, WriteMinidump() would fail if
// presented with an invalid stack pointer.
ASSERT_TRUE(WriteMinidump(templ.c_str(), child, &context, sizeof(context)));
// Read the minidump. Ensure that the memory region is present
Minidump minidump(templ);
ASSERT_TRUE(minidump.Read());
// TODO(ted.mielczarek,mkrebs): Enable this part of the test once
// https://breakpad.appspot.com/413002/ is committed.
#if 0
// Make sure there's a thread without a stack. NOTE: It's okay if
// GetThreadList() shows the error: "ERROR: MinidumpThread has a memory
// region problem".
MinidumpThreadList* dump_thread_list = minidump.GetThreadList();
ASSERT_TRUE(dump_thread_list);
bool found_empty_stack = false;
for (int i = 0; i < dump_thread_list->thread_count(); i++) {
MinidumpThread* thread = dump_thread_list->GetThreadAtIndex(i);
ASSERT_TRUE(thread->thread() != NULL);
// When the stack size is zero bytes, GetMemory() returns NULL.
if (thread->GetMemory() == NULL) {
found_empty_stack = true;
break;
}
}
// NOTE: If you fail this, first make sure that "invalid_stack_pointer"
// above is indeed set to an invalid address.
ASSERT_TRUE(found_empty_stack);
#endif
close(fds[1]);
}
// Test that limiting the size of the minidump works.
TEST(MinidumpWriterTest, MinidumpSizeLimit) {
static const int kNumberOfThreadsInHelperProgram = 40;
char number_of_threads_arg[3];
sprintf(number_of_threads_arg, "%d", kNumberOfThreadsInHelperProgram);
string helper_path(GetHelperBinary());
if (helper_path.empty()) {
FAIL() << "Couldn't find helper binary";
exit(1);
}
int fds[2];
ASSERT_NE(-1, pipe(fds));
pid_t child_pid = fork();
if (child_pid == 0) {
// In child process.
close(fds[0]);
// Pass the pipe fd and the number of threads as arguments.
char pipe_fd_string[8];
sprintf(pipe_fd_string, "%d", fds[1]);
execl(helper_path.c_str(),
helper_path.c_str(),
pipe_fd_string,
number_of_threads_arg,
NULL);
}
close(fds[1]);
// Wait for all child threads to indicate that they have started
for (int threads = 0; threads < kNumberOfThreadsInHelperProgram; threads++) {
struct pollfd pfd;
memset(&pfd, 0, sizeof(pfd));
pfd.fd = fds[0];
pfd.events = POLLIN | POLLERR;
const int r = HANDLE_EINTR(poll(&pfd, 1, 1000));
ASSERT_EQ(1, r);
ASSERT_TRUE(pfd.revents & POLLIN);
uint8_t junk;
ASSERT_EQ(read(fds[0], &junk, sizeof(junk)),
static_cast<ssize_t>(sizeof(junk)));
}
close(fds[0]);
// There is a race here because we may stop a child thread before
// it is actually running the busy loop. Empirically this sleep
// is sufficient to avoid the race.
usleep(100000);
// Child and its threads are ready now.
off_t normal_file_size;
int total_normal_stack_size = 0;
AutoTempDir temp_dir;
// First, write a minidump with no size limit.
{
string normal_dump = temp_dir.path() +
"/minidump-writer-unittest.dmp";
ASSERT_TRUE(WriteMinidump(normal_dump.c_str(), -1,
child_pid, NULL, 0,
MappingList(), AppMemoryList()));
struct stat st;
ASSERT_EQ(0, stat(normal_dump.c_str(), &st));
ASSERT_GT(st.st_size, 0);
normal_file_size = st.st_size;
Minidump minidump(normal_dump);
ASSERT_TRUE(minidump.Read());
MinidumpThreadList* dump_thread_list = minidump.GetThreadList();
ASSERT_TRUE(dump_thread_list);
for (unsigned int i = 0; i < dump_thread_list->thread_count(); i++) {
MinidumpThread* thread = dump_thread_list->GetThreadAtIndex(i);
ASSERT_TRUE(thread->thread() != NULL);
// When the stack size is zero bytes, GetMemory() returns NULL.
MinidumpMemoryRegion* memory = thread->GetMemory();
ASSERT_TRUE(memory != NULL);
total_normal_stack_size += memory->GetSize();
}
}
// Second, write a minidump with a size limit big enough to not trigger
// anything.
{
// Set size limit arbitrarily 1MB larger than the normal file size -- such
// that the limiting code will not kick in.
const off_t minidump_size_limit = normal_file_size + 1024*1024;
string same_dump = temp_dir.path() +
"/minidump-writer-unittest-same.dmp";
ASSERT_TRUE(WriteMinidump(same_dump.c_str(), minidump_size_limit,
child_pid, NULL, 0,
MappingList(), AppMemoryList()));
struct stat st;
ASSERT_EQ(0, stat(same_dump.c_str(), &st));
// Make sure limiting wasn't actually triggered. NOTE: If you fail this,
// first make sure that "minidump_size_limit" above is indeed set to a
// large enough value -- the limit-checking code in minidump_writer.cc
// does just a rough estimate.
ASSERT_EQ(normal_file_size, st.st_size);
}
// Third, write a minidump with a size limit small enough to be triggered.
{
// Set size limit to some arbitrary amount, such that the limiting code
// will kick in. The equation used to set this value was determined by
// simply reversing the size-limit logic a little bit in order to pick a
// size we know will trigger it. The definition of
// kLimitAverageThreadStackLength here was copied from class
// MinidumpWriter in minidump_writer.cc.
static const unsigned kLimitAverageThreadStackLength = 8 * 1024;
off_t minidump_size_limit = kNumberOfThreadsInHelperProgram *
kLimitAverageThreadStackLength;
// If, in reality, each of the threads' stack is *smaller* than
// kLimitAverageThreadStackLength, the normal file size could very well be
// smaller than the arbitrary limit that was just set. In that case,
// either of these numbers should trigger the size-limiting code, but we
// might as well pick the smallest.
if (normal_file_size < minidump_size_limit)
minidump_size_limit = normal_file_size;
string limit_dump = temp_dir.path() +
"/minidump-writer-unittest-limit.dmp";
ASSERT_TRUE(WriteMinidump(limit_dump.c_str(), minidump_size_limit,
child_pid, NULL, 0,
MappingList(), AppMemoryList()));
struct stat st;
ASSERT_EQ(0, stat(limit_dump.c_str(), &st));
ASSERT_GT(st.st_size, 0);
// Make sure the file size is at least smaller than the original. If this
// fails because it's the same size, then the size-limit logic didn't kick
// in like it was supposed to.
EXPECT_LT(st.st_size, normal_file_size);
Minidump minidump(limit_dump);
ASSERT_TRUE(minidump.Read());
MinidumpThreadList* dump_thread_list = minidump.GetThreadList();
ASSERT_TRUE(dump_thread_list);
int total_limit_stack_size = 0;
for (unsigned int i = 0; i < dump_thread_list->thread_count(); i++) {
MinidumpThread* thread = dump_thread_list->GetThreadAtIndex(i);
ASSERT_TRUE(thread->thread() != NULL);
// When the stack size is zero bytes, GetMemory() returns NULL.
MinidumpMemoryRegion* memory = thread->GetMemory();
ASSERT_TRUE(memory != NULL);
total_limit_stack_size += memory->GetSize();
}
// Make sure stack size shrunk by at least 1KB per extra thread. The
// definition of kLimitBaseThreadCount here was copied from class
// MinidumpWriter in minidump_writer.cc.
// Note: The 1KB is arbitrary, and assumes that the thread stacks are big
// enough to shrink by that much. For example, if each thread stack was
// originally only 2KB, the current size-limit logic wouldn't actually
// shrink them because that's the size to which it tries to shrink. If
// you fail this part of the test due to something like that, the test
// logic should probably be improved to account for your situation.
const unsigned kLimitBaseThreadCount = 20;
const unsigned kMinPerExtraThreadStackReduction = 1024;
const int min_expected_reduction = (kNumberOfThreadsInHelperProgram -
kLimitBaseThreadCount) * kMinPerExtraThreadStackReduction;
EXPECT_LT(total_limit_stack_size,
total_normal_stack_size - min_expected_reduction);
}
// Kill the helper program.
kill(child_pid, SIGKILL);
}
} // namespace

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2007, Google Inc.
// Copyright (c) 2011 Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
@@ -26,43 +26,41 @@
// 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.
//
// Author: Sergey Ioffe
// The common part of the striplog tests.
// minidump_writer_unittest_utils.cc:
// Shared routines used by unittests under client/linux/minidump_writer.
#include <stdio.h>
#include <string>
#include <iosfwd>
#include "glog/logging.h"
#include "base/commandlineflags.h"
#include "config.h"
#include <limits.h>
#include <stdlib.h>
DECLARE_bool(logtostderr);
#include "client/linux/minidump_writer/minidump_writer_unittest_utils.h"
#include "common/linux/safe_readlink.h"
#include "common/using_std_string.h"
using std::string;
using namespace GOOGLE_NAMESPACE;
namespace google_breakpad {
int CheckNoReturn(bool b) {
string s;
if (b) {
LOG(FATAL) << "Fatal";
string GetHelperBinary() {
string helper_path;
char *bindir = getenv("bindir");
if (bindir) {
helper_path = string(bindir) + "/";
} else {
return 0;
// Locate helper binary next to the current binary.
char self_path[PATH_MAX];
if (!SafeReadLink("/proc/self/exe", self_path)) {
return "";
}
helper_path = string(self_path);
size_t pos = helper_path.rfind('/');
if (pos == string::npos) {
return "";
}
helper_path.erase(pos + 1);
}
helper_path += "linux_dumper_unittest_helper";
return helper_path;
}
struct A { };
std::ostream &operator<<(std::ostream &str, const A&) {return str;}
int main(int argc, char* argv[]) {
FLAGS_logtostderr = true;
InitGoogleLogging(argv[0]);
LOG(INFO) << "TESTMESSAGE INFO";
LOG(WARNING) << 2 << "something" << "TESTMESSAGE WARNING"
<< 1 << 'c' << A() << std::endl;
LOG(ERROR) << "TESTMESSAGE ERROR";
bool flag = true;
(flag ? LOG(INFO) : LOG(ERROR)) << "TESTMESSAGE COND";
LOG(FATAL) << "TESTMESSAGE FATAL";
}
} // namespace google_breakpad

View File

@@ -1,4 +1,4 @@
// Copyright 2006, Google Inc.
// Copyright (c) 2012, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
@@ -26,32 +26,24 @@
// 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.
//
// Author: wan@google.com (Zhanyong Wan)
//
// Unit test for include/gtest/gtest_prod.h.
#include <gtest/gtest.h>
#include "test/production.h"
// minidump_writer_unittest_utils.h:
// Shared routines used by unittests under client/linux/minidump_writer.
// Tests that private members can be accessed from a TEST declared as
// a friend of the class.
TEST(PrivateCodeTest, CanAccessPrivateMembers) {
PrivateCode a;
EXPECT_EQ(0, a.x_);
#ifndef CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_UNITTEST_UTILS_H_
#define CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_UNITTEST_UTILS_H_
a.set_x(1);
EXPECT_EQ(1, a.x_);
}
#include <string>
typedef testing::Test PrivateCodeFixtureTest;
#include "common/using_std_string.h"
// Tests that private members can be accessed from a TEST_F declared
// as a friend of the class.
TEST_F(PrivateCodeFixtureTest, CanAccessPrivateMembers) {
PrivateCode a;
EXPECT_EQ(0, a.x_);
namespace google_breakpad {
a.set_x(2);
EXPECT_EQ(2, a.x_);
}
// Returns the full path to linux_dumper_unittest_helper. The full path is
// discovered either by using the environment variable "bindir" or by using
// the location of the main module of the currently running process.
string GetHelperBinary();
} // namespace google_breakpad
#endif // CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_UNITTEST_UTILS_H_

View File

@@ -0,0 +1,130 @@
// Copyright (c) 2013, 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_PROC_CPUINFO_READER_H_
#define CLIENT_LINUX_MINIDUMP_WRITER_PROC_CPUINFO_READER_H_
#include <stdint.h>
#include <assert.h>
#include <string.h>
#include "client/linux/minidump_writer/line_reader.h"
#include "common/linux/linux_libc_support.h"
#include "third_party/lss/linux_syscall_support.h"
namespace google_breakpad {
// A class for reading /proc/cpuinfo without using fopen/fgets or other
// functions which may allocate memory.
class ProcCpuInfoReader {
public:
ProcCpuInfoReader(int fd)
: line_reader_(fd), pop_count_(-1) {
}
// Return the next field name, or NULL in case of EOF.
// field: (output) Pointer to zero-terminated field name.
// Returns true on success, or false on EOF or error (line too long).
bool GetNextField(const char** field) {
for (;;) {
const char* line;
unsigned line_len;
// Try to read next line.
if (pop_count_ >= 0) {
line_reader_.PopLine(pop_count_);
pop_count_ = -1;
}
if (!line_reader_.GetNextLine(&line, &line_len))
return false;
pop_count_ = static_cast<int>(line_len);
const char* line_end = line + line_len;
// Expected format: <field-name> <space>+ ':' <space> <value>
// Note that:
// - empty lines happen.
// - <field-name> can contain spaces.
// - some fields have an empty <value>
char* sep = static_cast<char*>(my_memchr(line, ':', line_len));
if (sep == NULL)
continue;
// Record the value. Skip leading space after the column to get
// its start.
const char* val = sep+1;
while (val < line_end && my_isspace(*val))
val++;
value_ = val;
value_len_ = static_cast<size_t>(line_end - val);
// Remove trailing spaces before the column to properly 0-terminate
// the field name.
while (sep > line && my_isspace(sep[-1]))
sep--;
if (sep == line)
continue;
// zero-terminate field name.
*sep = '\0';
*field = line;
return true;
}
}
// Return the field value. This must be called after a succesful
// call to GetNextField().
const char* GetValue() {
assert(value_);
return value_;
}
// Same as GetValue(), but also returns the length in characters of
// the value.
const char* GetValueAndLen(size_t* length) {
assert(value_);
*length = value_len_;
return value_;
}
private:
LineReader line_reader_;
int pop_count_;
const char* value_;
size_t value_len_;
};
} // namespace google_breakpad
#endif // CLIENT_LINUX_MINIDUMP_WRITER_PROC_CPUINFO_READER_H_

View File

@@ -0,0 +1,199 @@
// Copyright (c) 2013, 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 <stdio.h>
#include <errno.h>
#include <string.h>
#include "client/linux/minidump_writer/proc_cpuinfo_reader.h"
#include "breakpad_googletest_includes.h"
#include "common/linux/tests/auto_testfile.h"
using namespace google_breakpad;
#if !defined(__ANDROID__)
#define TEMPDIR "/tmp"
#else
#define TEMPDIR "/data/local/tmp"
#endif
namespace {
typedef testing::Test ProcCpuInfoReaderTest;
class ScopedTestFile : public AutoTestFile {
public:
explicit ScopedTestFile(const char* text)
: AutoTestFile("proc_cpuinfo_reader", text) {
}
};
}
TEST(ProcCpuInfoReaderTest, EmptyFile) {
ScopedTestFile file("");
ASSERT_TRUE(file.IsOk());
ProcCpuInfoReader reader(file.GetFd());
const char *field;
ASSERT_FALSE(reader.GetNextField(&field));
}
TEST(ProcCpuInfoReaderTest, OneLineTerminated) {
ScopedTestFile file("foo : bar\n");
ASSERT_TRUE(file.IsOk());
ProcCpuInfoReader reader(file.GetFd());
const char *field;
ASSERT_TRUE(reader.GetNextField(&field));
ASSERT_STREQ("foo", field);
ASSERT_STREQ("bar", reader.GetValue());
ASSERT_FALSE(reader.GetNextField(&field));
}
TEST(ProcCpuInfoReaderTest, OneLine) {
ScopedTestFile file("foo : bar");
ASSERT_TRUE(file.IsOk());
ProcCpuInfoReader reader(file.GetFd());
const char *field;
size_t value_len;
ASSERT_TRUE(reader.GetNextField(&field));
ASSERT_STREQ("foo", field);
ASSERT_STREQ("bar", reader.GetValueAndLen(&value_len));
ASSERT_EQ(3U, value_len);
ASSERT_FALSE(reader.GetNextField(&field));
}
TEST(ProcCpuInfoReaderTest, TwoLinesTerminated) {
ScopedTestFile file("foo : bar\nzoo : tut\n");
ASSERT_TRUE(file.IsOk());
ProcCpuInfoReader reader(file.GetFd());
const char* field;
ASSERT_TRUE(reader.GetNextField(&field));
ASSERT_STREQ("foo", field);
ASSERT_STREQ("bar", reader.GetValue());
ASSERT_TRUE(reader.GetNextField(&field));
ASSERT_STREQ("zoo", field);
ASSERT_STREQ("tut", reader.GetValue());
ASSERT_FALSE(reader.GetNextField(&field));
}
TEST(ProcCpuInfoReaderTest, SkipMalformedLine) {
ScopedTestFile file("this line should have a column\nfoo : bar\n");
ASSERT_TRUE(file.IsOk());
ProcCpuInfoReader reader(file.GetFd());
const char* field;
ASSERT_TRUE(reader.GetNextField(&field));
ASSERT_STREQ("foo", field);
ASSERT_STREQ("bar", reader.GetValue());
ASSERT_FALSE(reader.GetNextField(&field));
}
TEST(ProcCpuInfoReaderTest, SkipOneEmptyLine) {
ScopedTestFile file("\n\nfoo : bar\n");
ASSERT_TRUE(file.IsOk());
ProcCpuInfoReader reader(file.GetFd());
const char* field;
ASSERT_TRUE(reader.GetNextField(&field));
ASSERT_STREQ("foo", field);
ASSERT_STREQ("bar", reader.GetValue());
ASSERT_FALSE(reader.GetNextField(&field));
}
TEST(ProcCpuInfoReaderTest, SkipEmptyField) {
ScopedTestFile file(" : bar\nzoo : tut\n");
ASSERT_TRUE(file.IsOk());
ProcCpuInfoReader reader(file.GetFd());
const char* field;
ASSERT_TRUE(reader.GetNextField(&field));
ASSERT_STREQ("zoo", field);
ASSERT_STREQ("tut", reader.GetValue());
ASSERT_FALSE(reader.GetNextField(&field));
}
TEST(ProcCpuInfoReaderTest, SkipTwoEmptyLines) {
ScopedTestFile file("foo : bar\n\n\nfoo : bar\n");
ASSERT_TRUE(file.IsOk());
ProcCpuInfoReader reader(file.GetFd());
const char* field;
ASSERT_TRUE(reader.GetNextField(&field));
ASSERT_STREQ("foo", field);
ASSERT_STREQ("bar", reader.GetValue());
ASSERT_TRUE(reader.GetNextField(&field));
ASSERT_STREQ("foo", field);
ASSERT_STREQ("bar", reader.GetValue());
ASSERT_FALSE(reader.GetNextField(&field));
}
TEST(ProcCpuInfoReaderTest, FieldWithSpaces) {
ScopedTestFile file("foo bar : zoo\n");
ASSERT_TRUE(file.IsOk());
ProcCpuInfoReader reader(file.GetFd());
const char* field;
ASSERT_TRUE(reader.GetNextField(&field));
ASSERT_STREQ("foo bar", field);
ASSERT_STREQ("zoo", reader.GetValue());
ASSERT_FALSE(reader.GetNextField(&field));
}
TEST(ProcCpuInfoReaderTest, EmptyValue) {
ScopedTestFile file("foo :\n");
ASSERT_TRUE(file.IsOk());
ProcCpuInfoReader reader(file.GetFd());
const char* field;
ASSERT_TRUE(reader.GetNextField(&field));
ASSERT_STREQ("foo", field);
size_t value_len;
ASSERT_STREQ("", reader.GetValueAndLen(&value_len));
ASSERT_EQ(0U, value_len);
ASSERT_FALSE(reader.GetNextField(&field));
}

View File

@@ -32,7 +32,7 @@
#include <string>
#include <iostream>
using std::string;
#include "common/using_std_string.h"
DEFINE_string(crash_server, "https://clients2.google.com/cr",
"The crash server to upload minidumps to.");
@@ -59,7 +59,7 @@ DEFINE_string(proxy_userpasswd, "",
bool CheckForRequiredFlagsOrDie() {
std::string error_text = "";
string error_text = "";
if (FLAGS_product_name.empty()) {
error_text.append("\nProduct name must be specified.");
}

View File

@@ -35,11 +35,20 @@
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
162F64F2161C577500CD68D5 /* arch_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = 162F64F0161C577500CD68D5 /* arch_utilities.cc */; };
162F64F3161C577500CD68D5 /* arch_utilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 162F64F1161C577500CD68D5 /* arch_utilities.h */; };
162F64F4161C579B00CD68D5 /* arch_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = 162F64F0161C577500CD68D5 /* arch_utilities.cc */; };
162F64F5161C579B00CD68D5 /* arch_utilities.h in Sources */ = {isa = PBXBuildFile; fileRef = 162F64F1161C577500CD68D5 /* arch_utilities.h */; };
163201D61443019E00C4DBF5 /* ConfigFile.h in Headers */ = {isa = PBXBuildFile; fileRef = 163201D41443019E00C4DBF5 /* ConfigFile.h */; };
163201D71443019E00C4DBF5 /* ConfigFile.mm in Sources */ = {isa = PBXBuildFile; fileRef = 163201D51443019E00C4DBF5 /* ConfigFile.mm */; };
163201E31443029300C4DBF5 /* ConfigFile.mm in Sources */ = {isa = PBXBuildFile; fileRef = 163201D51443019E00C4DBF5 /* ConfigFile.mm */; };
16C7C918147D45AE00776EAD /* BreakpadDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7C917147D45AE00776EAD /* BreakpadDefines.h */; settings = {ATTRIBUTES = (Public, ); }; };
16E02DB8147410F0008C604D /* uploader.mm in Sources */ = {isa = PBXBuildFile; fileRef = 16E02DB4147410D4008C604D /* uploader.mm */; };
1EEEB6231720829E00F7E689 /* simple_string_dictionary.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1EEEB6211720829E00F7E689 /* simple_string_dictionary.cc */; };
1EEEB6241720829E00F7E689 /* simple_string_dictionary.h in Headers */ = {isa = PBXBuildFile; fileRef = 1EEEB6221720829E00F7E689 /* simple_string_dictionary.h */; };
1EEEB6271720831E00F7E689 /* BreakpadFramework_Test.mm in Sources */ = {isa = PBXBuildFile; fileRef = F91AF5CF0FD60393009D8BE2 /* BreakpadFramework_Test.mm */; };
1EEEB62A1720859200F7E689 /* simple_string_dictionary_unittest.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1EEEB6251720830600F7E689 /* simple_string_dictionary_unittest.cc */; };
1EEEB62B1720868C00F7E689 /* simple_string_dictionary.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1EEEB6211720829E00F7E689 /* simple_string_dictionary.cc */; };
3329D4ED0FA16D820007BBC5 /* Breakpad.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3329D4EC0FA16D820007BBC5 /* Breakpad.xib */; };
33880C800F9E097100817F82 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 33880C7E0F9E097100817F82 /* InfoPlist.strings */; };
4084699D0F5D9CF900FDCA37 /* crash_report_sender.icns in Resources */ = {isa = PBXBuildFile; fileRef = 4084699C0F5D9CF900FDCA37 /* crash_report_sender.icns */; };
@@ -137,7 +146,6 @@
D2F9A53F121383A1002747C1 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0867D69BFE84028FC02AAC07 /* Foundation.framework */; };
D2F9A541121383A1002747C1 /* libgtest.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D2F9A41512131EF0002747C1 /* libgtest.a */; };
D2F9A553121383DC002747C1 /* crash_generation_server_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F9A4CE121336F7002747C1 /* crash_generation_server_test.cc */; };
F91AF5D00FD60393009D8BE2 /* BreakpadFramework_Test.mm in Sources */ = {isa = PBXBuildFile; fileRef = F91AF5CF0FD60393009D8BE2 /* BreakpadFramework_Test.mm */; };
F91AF6210FD60784009D8BE2 /* Breakpad.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8DC2EF5B0486A6940098B216 /* Breakpad.framework */; };
F9286B3A0F7EB25800A4DCC8 /* InspectorMain.mm in Sources */ = {isa = PBXBuildFile; fileRef = F9286B390F7EB25800A4DCC8 /* InspectorMain.mm */; };
F92C53B80ECCE7B3009BE4BA /* Inspector.mm in Sources */ = {isa = PBXBuildFile; fileRef = F92C53B70ECCE7B3009BE4BA /* Inspector.mm */; };
@@ -155,7 +163,6 @@
F92C56450ECD10CA009BE4BA /* MachIPC.mm in Sources */ = {isa = PBXBuildFile; fileRef = F92C53790ECCE635009BE4BA /* MachIPC.mm */; };
F92C56460ECD10CA009BE4BA /* minidump_file_writer.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C538F0ECCE70A009BE4BA /* minidump_file_writer.cc */; };
F92C56470ECD10CA009BE4BA /* minidump_generator.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C536F0ECCE3FD009BE4BA /* minidump_generator.cc */; };
F92C56480ECD10CA009BE4BA /* SimpleStringDictionary.mm in Sources */ = {isa = PBXBuildFile; fileRef = F92C53810ECCE635009BE4BA /* SimpleStringDictionary.mm */; };
F92C56490ECD10CA009BE4BA /* string_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53820ECCE635009BE4BA /* string_utilities.cc */; };
F92C564A0ECD10CA009BE4BA /* string_conversion.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53850ECCE6AD009BE4BA /* string_conversion.cc */; };
F92C564C0ECD10DD009BE4BA /* breakpadUtilities.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F92C563C0ECD10B3009BE4BA /* breakpadUtilities.dylib */; };
@@ -207,8 +214,6 @@
F9C44EA20EF09F93003AEBAA /* HTTPMultipartUpload.m in Sources */ = {isa = PBXBuildFile; fileRef = F92C53770ECCE635009BE4BA /* HTTPMultipartUpload.m */; };
F9C44EE50EF0A006003AEBAA /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F9C44EE40EF0A006003AEBAA /* SystemConfiguration.framework */; };
F9C44EE90EF0A3C1003AEBAA /* GTMLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = F9C44EE80EF0A3C1003AEBAA /* GTMLogger.m */; };
F9C77DE20F7DD7E30045F7DB /* SimpleStringDictionaryTest.mm in Sources */ = {isa = PBXBuildFile; fileRef = F9C77DE10F7DD7E30045F7DB /* SimpleStringDictionaryTest.mm */; };
F9C77DE40F7DD82F0045F7DB /* SimpleStringDictionary.mm in Sources */ = {isa = PBXBuildFile; fileRef = F92C53810ECCE635009BE4BA /* SimpleStringDictionary.mm */; };
F9C77E130F7DDF810045F7DB /* GTMSenTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = F9C77E120F7DDF810045F7DB /* GTMSenTestCase.m */; };
/* End PBXBuildFile section */
@@ -555,11 +560,16 @@
0867D69BFE84028FC02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
0867D6A5FE840307C02AAC07 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
162F64F0161C577500CD68D5 /* arch_utilities.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = arch_utilities.cc; path = ../../common/mac/arch_utilities.cc; sourceTree = "<group>"; };
162F64F1161C577500CD68D5 /* arch_utilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = arch_utilities.h; path = ../../common/mac/arch_utilities.h; sourceTree = "<group>"; };
163201D41443019E00C4DBF5 /* ConfigFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ConfigFile.h; path = crash_generation/ConfigFile.h; sourceTree = "<group>"; };
163201D51443019E00C4DBF5 /* ConfigFile.mm */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; name = ConfigFile.mm; path = crash_generation/ConfigFile.mm; sourceTree = "<group>"; };
163202431443201300C4DBF5 /* uploader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = uploader.h; path = sender/uploader.h; sourceTree = "<group>"; };
16C7C917147D45AE00776EAD /* BreakpadDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BreakpadDefines.h; sourceTree = "<group>"; };
16E02DB4147410D4008C604D /* uploader.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = uploader.mm; path = sender/uploader.mm; sourceTree = "<group>"; };
1EEEB6211720829E00F7E689 /* simple_string_dictionary.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = simple_string_dictionary.cc; path = ../../common/simple_string_dictionary.cc; sourceTree = "<group>"; };
1EEEB6221720829E00F7E689 /* simple_string_dictionary.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = simple_string_dictionary.h; path = ../../common/simple_string_dictionary.h; sourceTree = "<group>"; };
1EEEB6251720830600F7E689 /* simple_string_dictionary_unittest.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = simple_string_dictionary_unittest.cc; path = ../../common/simple_string_dictionary_unittest.cc; sourceTree = "<group>"; };
32DBCF5E0370ADEE00C91783 /* Breakpad_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Breakpad_Prefix.pch; path = Framework/Breakpad_Prefix.pch; sourceTree = "<group>"; };
3329D4EC0FA16D820007BBC5 /* Breakpad.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Breakpad.xib; path = sender/Breakpad.xib; sourceTree = "<group>"; };
33880C7F0F9E097100817F82 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = sender/English.lproj/InfoPlist.strings; sourceTree = "<group>"; };
@@ -645,8 +655,6 @@
F92C537D0ECCE635009BE4BA /* macho_utilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = macho_utilities.h; path = ../../common/mac/macho_utilities.h; sourceTree = SOURCE_ROOT; };
F92C537E0ECCE635009BE4BA /* macho_walker.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = macho_walker.cc; path = ../../common/mac/macho_walker.cc; sourceTree = SOURCE_ROOT; };
F92C537F0ECCE635009BE4BA /* macho_walker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = macho_walker.h; path = ../../common/mac/macho_walker.h; sourceTree = SOURCE_ROOT; };
F92C53800ECCE635009BE4BA /* SimpleStringDictionary.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SimpleStringDictionary.h; path = ../../common/mac/SimpleStringDictionary.h; sourceTree = SOURCE_ROOT; };
F92C53810ECCE635009BE4BA /* SimpleStringDictionary.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = SimpleStringDictionary.mm; path = ../../common/mac/SimpleStringDictionary.mm; sourceTree = SOURCE_ROOT; };
F92C53820ECCE635009BE4BA /* string_utilities.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = string_utilities.cc; path = ../../common/mac/string_utilities.cc; sourceTree = SOURCE_ROOT; };
F92C53830ECCE635009BE4BA /* string_utilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = string_utilities.h; path = ../../common/mac/string_utilities.h; sourceTree = SOURCE_ROOT; };
F92C53850ECCE6AD009BE4BA /* string_conversion.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = string_conversion.cc; path = ../../common/string_conversion.cc; sourceTree = SOURCE_ROOT; };
@@ -693,8 +701,6 @@
F9C44EE80EF0A3C1003AEBAA /* GTMLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTMLogger.m; path = ../../common/mac/GTMLogger.m; sourceTree = SOURCE_ROOT; };
F9C77DDA0F7DD5CF0045F7DB /* UnitTests.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UnitTests.octest; sourceTree = BUILT_PRODUCTS_DIR; };
F9C77DDB0F7DD5CF0045F7DB /* UnitTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "UnitTests-Info.plist"; sourceTree = "<group>"; };
F9C77DE00F7DD7E30045F7DB /* SimpleStringDictionaryTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SimpleStringDictionaryTest.h; path = tests/SimpleStringDictionaryTest.h; sourceTree = "<group>"; };
F9C77DE10F7DD7E30045F7DB /* SimpleStringDictionaryTest.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = SimpleStringDictionaryTest.mm; path = tests/SimpleStringDictionaryTest.mm; sourceTree = "<group>"; };
F9C77E110F7DDF810045F7DB /* GTMSenTestCase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTMSenTestCase.h; path = ../../common/mac/testing/GTMSenTestCase.h; sourceTree = SOURCE_ROOT; };
F9C77E120F7DDF810045F7DB /* GTMSenTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTMSenTestCase.m; path = ../../common/mac/testing/GTMSenTestCase.m; sourceTree = SOURCE_ROOT; };
/* End PBXFileReference section */
@@ -930,6 +936,8 @@
F92C53870ECCE6C0009BE4BA /* convert_UTF.c */,
F92C53880ECCE6C0009BE4BA /* convert_UTF.h */,
4D72CA0D13DFAD5C006CABE3 /* md5.cc */,
1EEEB6211720829E00F7E689 /* simple_string_dictionary.cc */,
1EEEB6221720829E00F7E689 /* simple_string_dictionary.h */,
F92C53850ECCE6AD009BE4BA /* string_conversion.cc */,
F92C53860ECCE6AD009BE4BA /* string_conversion.h */,
F92C53840ECCE68D009BE4BA /* mac */,
@@ -940,6 +948,8 @@
F92C53840ECCE68D009BE4BA /* mac */ = {
isa = PBXGroup;
children = (
162F64F0161C577500CD68D5 /* arch_utilities.cc */,
162F64F1161C577500CD68D5 /* arch_utilities.h */,
8B31022211F0CE1000FCF3E4 /* GTMGarbageCollection.h */,
8B31007011F0CD3C00FCF3E4 /* GTMDefines.h */,
F9C77E0F0F7DDF650045F7DB /* testing */,
@@ -959,8 +969,6 @@
F92C537D0ECCE635009BE4BA /* macho_utilities.h */,
F92C537E0ECCE635009BE4BA /* macho_walker.cc */,
F92C537F0ECCE635009BE4BA /* macho_walker.h */,
F92C53800ECCE635009BE4BA /* SimpleStringDictionary.h */,
F92C53810ECCE635009BE4BA /* SimpleStringDictionary.mm */,
F92C53820ECCE635009BE4BA /* string_utilities.cc */,
F92C53830ECCE635009BE4BA /* string_utilities.h */,
);
@@ -1108,12 +1116,11 @@
F9C77DDF0F7DD7CF0045F7DB /* tests */ = {
isa = PBXGroup;
children = (
1EEEB6251720830600F7E689 /* simple_string_dictionary_unittest.cc */,
D23F4B9A12A8688800686C8D /* minidump_generator_test_helper.cc */,
D23F4B2C12A7E13200686C8D /* minidump_generator_test.cc */,
D2F9A4CE121336F7002747C1 /* crash_generation_server_test.cc */,
D2F9A3D41212F87C002747C1 /* exception_handler_test.cc */,
F9C77DE00F7DD7E30045F7DB /* SimpleStringDictionaryTest.h */,
F9C77DE10F7DD7E30045F7DB /* SimpleStringDictionaryTest.mm */,
F91AF5CF0FD60393009D8BE2 /* BreakpadFramework_Test.mm */,
);
name = tests;
@@ -1142,6 +1149,8 @@
D2F9A4CC121336C7002747C1 /* crash_generation_server.h in Headers */,
163201D61443019E00C4DBF5 /* ConfigFile.h in Headers */,
16C7C918147D45AE00776EAD /* BreakpadDefines.h in Headers */,
162F64F3161C577500CD68D5 /* arch_utilities.h in Headers */,
1EEEB6241720829E00F7E689 /* simple_string_dictionary.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1666,6 +1675,8 @@
D2F9A4CB121336C7002747C1 /* crash_generation_client.cc in Sources */,
D2F9A4CD121336C7002747C1 /* crash_generation_server.cc in Sources */,
163201D71443019E00C4DBF5 /* ConfigFile.mm in Sources */,
162F64F2161C577500CD68D5 /* arch_utilities.cc in Sources */,
1EEEB6231720829E00F7E689 /* simple_string_dictionary.cc in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1747,6 +1758,8 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
162F64F4161C579B00CD68D5 /* arch_utilities.cc in Sources */,
162F64F5161C579B00CD68D5 /* arch_utilities.h in Sources */,
D2A5DD301188633800081F03 /* breakpad_nlist_64.cc in Sources */,
F92C563F0ECD10CA009BE4BA /* convert_UTF.c in Sources */,
F92C56400ECD10CA009BE4BA /* dynamic_images.cc in Sources */,
@@ -1758,7 +1771,6 @@
4D72CA0E13DFAD5C006CABE3 /* md5.cc in Sources */,
F92C56460ECD10CA009BE4BA /* minidump_file_writer.cc in Sources */,
F92C56470ECD10CA009BE4BA /* minidump_generator.cc in Sources */,
F92C56480ECD10CA009BE4BA /* SimpleStringDictionary.mm in Sources */,
F92C56490ECD10CA009BE4BA /* string_utilities.cc in Sources */,
F92C564A0ECD10CA009BE4BA /* string_conversion.cc in Sources */,
4D61A25F14F43CFC002D5862 /* bootstrap_compat.cc in Sources */,
@@ -1802,6 +1814,8 @@
D23F4B2E12A7E13200686C8D /* minidump_generator_test.cc in Sources */,
4D72CA2F13DFAE65006CABE3 /* md5.cc in Sources */,
4D61A26D14F43D43002D5862 /* bootstrap_compat.cc in Sources */,
1EEEB62B1720868C00F7E689 /* simple_string_dictionary.cc in Sources */,
1EEEB62A1720859200F7E689 /* simple_string_dictionary_unittest.cc in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1843,6 +1857,7 @@
D244540B12439BA0009BBCE0 /* memory_unittest.cc in Sources */,
4D72CA3813DFAE91006CABE3 /* md5.cc in Sources */,
4D61A26E14F43D45002D5862 /* bootstrap_compat.cc in Sources */,
1EEEB6271720831E00F7E689 /* BreakpadFramework_Test.mm in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1860,10 +1875,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
F9C77DE40F7DD82F0045F7DB /* SimpleStringDictionary.mm in Sources */,
F9C77DE20F7DD7E30045F7DB /* SimpleStringDictionaryTest.mm in Sources */,
F9C77E130F7DDF810045F7DB /* GTMSenTestCase.m in Sources */,
F91AF5D00FD60393009D8BE2 /* BreakpadFramework_Test.mm in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@@ -28,22 +28,16 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
#define VERBOSE 0
#if VERBOSE
static bool gDebugLog = true;
#else
static bool gDebugLog = false;
#endif
#define DEBUGLOG if (gDebugLog) fprintf
#define IGNORE_DEBUGGER "BREAKPAD_IGNORE_DEBUGGER"
#import "client/mac/Framework/Breakpad.h"
#include <assert.h>
#import <Foundation/Foundation.h>
#import <sys/stat.h>
#import <sys/sysctl.h>
#include <pthread.h>
#include <sys/stat.h>
#include <sys/sysctl.h>
#import "client/mac/crash_generation/Inspector.h"
#import "client/mac/handler/exception_handler.h"
@@ -51,15 +45,25 @@
#import "client/mac/Framework/OnDemandServer.h"
#import "client/mac/handler/protected_memory_allocator.h"
#import "common/mac/MachIPC.h"
#import "common/mac/SimpleStringDictionary.h"
#import "common/simple_string_dictionary.h"
#ifndef __EXCEPTIONS
// This file uses C++ try/catch (but shouldn't). Duplicate the macros from
// <c++/4.2.1/exception_defines.h> allowing this file to work properly with
// exceptions disabled even when other C++ libraries are used. #undef the try
// and catch macros first in case libstdc++ is in use and has already provided
// its own definitions.
#undef try
#define try if (true)
#undef catch
#define catch(X) if (false)
#endif // __EXCEPTIONS
using google_breakpad::KeyValueEntry;
using google_breakpad::MachPortSender;
using google_breakpad::MachReceiveMessage;
using google_breakpad::MachSendMessage;
using google_breakpad::ReceivePort;
using google_breakpad::SimpleStringDictionary;
using google_breakpad::SimpleStringDictionaryIterator;
//=============================================================================
// We want any memory allocations which are used by breakpad during the
@@ -92,36 +96,32 @@ pthread_mutex_t gDictionaryMutex;
// ProtectedMemoryLocker will unprotect this block after taking the lock.
// Its destructor will first re-protect the memory then release the lock.
class ProtectedMemoryLocker {
public:
// allocator may be NULL, in which case no Protect() or Unprotect() calls
// will be made, but a lock will still be taken
public:
ProtectedMemoryLocker(pthread_mutex_t *mutex,
ProtectedMemoryAllocator *allocator)
: mutex_(mutex), allocator_(allocator) {
: mutex_(mutex),
allocator_(allocator) {
// Lock the mutex
assert(pthread_mutex_lock(mutex_) == 0);
int rv = pthread_mutex_lock(mutex_);
assert(rv == 0);
// Unprotect the memory
if (allocator_ ) {
allocator_->Unprotect();
}
allocator_->Unprotect();
}
~ProtectedMemoryLocker() {
// First protect the memory
if (allocator_) {
allocator_->Protect();
}
allocator_->Protect();
// Then unlock the mutex
assert(pthread_mutex_unlock(mutex_) == 0);
int rv = pthread_mutex_unlock(mutex_);
assert(rv == 0);
};
private:
// Keep anybody from ever creating one of these things not on the stack.
ProtectedMemoryLocker() { }
private:
ProtectedMemoryLocker();
ProtectedMemoryLocker(const ProtectedMemoryLocker&);
ProtectedMemoryLocker & operator=(ProtectedMemoryLocker&);
ProtectedMemoryLocker& operator=(const ProtectedMemoryLocker&);
pthread_mutex_t *mutex_;
ProtectedMemoryAllocator *allocator_;
@@ -300,7 +300,6 @@ NSString * GetResourcePath() {
// executable code, since that's how the Breakpad framework is built.
resourcePath = [bundlePath stringByAppendingPathComponent:@"Resources/"];
} else {
DEBUGLOG(stderr, "Could not find GetResourcePath\n");
// fallback plan
NSBundle *bundle =
[NSBundle bundleWithIdentifier:@"com.Google.BreakpadFramework"];
@@ -319,7 +318,6 @@ bool Breakpad::Initialize(NSDictionary *parameters) {
// Check for debugger
if (IsDebuggerActive()) {
DEBUGLOG(stderr, "Debugger is active: Not installing handler\n");
return true;
}
@@ -494,7 +492,6 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) {
if (!inspectorPathString || !reporterPathString) {
resourcePath = GetResourcePath();
if (!resourcePath) {
DEBUGLOG(stderr, "Could not get resource path\n");
return false;
}
}
@@ -507,7 +504,6 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) {
// Verify that there is an Inspector tool.
if (![[NSFileManager defaultManager] fileExistsAtPath:inspectorPathString]) {
DEBUGLOG(stderr, "Cannot find Inspector tool\n");
return false;
}
@@ -523,7 +519,6 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) {
// Verify that there is a Reporter application.
if (![[NSFileManager defaultManager]
fileExistsAtPath:reporterPathString]) {
DEBUGLOG(stderr, "Cannot find Reporter tool\n");
return false;
}
@@ -533,17 +528,14 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) {
// The product, version, and URL are required values.
if (![product length]) {
DEBUGLOG(stderr, "Missing required product key.\n");
return false;
}
if (![version length]) {
DEBUGLOG(stderr, "Missing required version key.\n");
return false;
}
if (![urlStr length]) {
DEBUGLOG(stderr, "Missing required URL key.\n");
return false;
}
@@ -640,8 +632,6 @@ bool Breakpad::HandleException(int exception_type,
int exception_code,
int exception_subcode,
mach_port_t crashing_thread) {
DEBUGLOG(stderr, "Breakpad: an exception occurred\n");
if (filter_callback_) {
bool should_handle = filter_callback_(exception_type,
exception_code,
@@ -684,8 +674,8 @@ bool Breakpad::HandleException(int exception_type,
if (result == KERN_SUCCESS) {
// Now, send a series of key-value pairs to the Inspector.
const KeyValueEntry *entry = NULL;
SimpleStringDictionaryIterator iter(*config_params_);
const SimpleStringDictionary::Entry *entry = NULL;
SimpleStringDictionary::Iterator iter(*config_params_);
while ( (entry = iter.Next()) ) {
KeyValueMessageData keyvalue_data(*entry);

View File

@@ -27,13 +27,12 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <iostream>
#import <mach/mach.h>
#import <servers/bootstrap.h>
#import <stdio.h>
#import <stdlib.h>
#import <sys/stat.h>
#import <unistd.h>
#include <mach/mach.h>
#include <servers/bootstrap.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
//==============================================================================
// class OnDemandServer :

View File

@@ -123,11 +123,14 @@ kern_return_t OnDemandServer::Initialize(const char *server_command,
strlcpy(service_name_, service_name, sizeof(service_name_));
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
// Create a service called service_name, and return send rights to
// that port in service_port_.
kr = bootstrap_create_service(server_port_,
const_cast<char*>(service_name),
&service_port_);
#pragma clang diagnostic pop
if (kr != BOOTSTRAP_SUCCESS) {
PRINT_BOOTSTRAP_RESULT(kr, "bootstrap_create_service(): ");

View File

@@ -31,7 +31,7 @@
#import <Foundation/Foundation.h>
#import "common/mac/SimpleStringDictionary.h"
#include "common/simple_string_dictionary.h"
namespace google_breakpad {

View File

@@ -36,18 +36,8 @@
#include <sys/time.h>
#import "client/apple/Framework/BreakpadDefines.h"
#import "common/mac/SimpleStringDictionary.h"
#import "GTMDefines.h"
#define VERBOSE 0
#if VERBOSE
bool gDebugLog = true;
#else
bool gDebugLog = false;
#endif
#define DEBUGLOG if (gDebugLog) fprintf
namespace google_breakpad {
@@ -78,13 +68,10 @@ BOOL ConfigFile::AppendConfigData(const char *key,
assert(config_file_ != -1);
if (!key) {
DEBUGLOG(stderr, "Breakpad: Missing Key\n");
return NO;
}
if (!data) {
DEBUGLOG(stderr, "Breakpad: Missing data for key: %s\n", key ? key :
"<Unknown Key>");
return NO;
}
@@ -148,14 +135,8 @@ void ConfigFile::WriteFile(const char* directory,
config_file_ = mkstemp(config_file_path_);
if (config_file_ == -1) {
DEBUGLOG(stderr,
"mkstemp(config_file_path_) == -1 (%s)\n",
strerror(errno));
return;
}
else {
DEBUGLOG(stderr, "Writing config file to (%s)\n", config_file_path_);
}
has_created_file_ = true;
@@ -167,15 +148,11 @@ void ConfigFile::WriteFile(const char* directory,
BOOL result = YES;
const SimpleStringDictionary &dictionary = *configurationParameters;
const KeyValueEntry *entry = NULL;
SimpleStringDictionaryIterator iter(dictionary);
const SimpleStringDictionary::Entry *entry = NULL;
SimpleStringDictionary::Iterator iter(dictionary);
while ((entry = iter.Next())) {
DEBUGLOG(stderr,
"config: (%s) -> (%s)\n",
entry->GetKey(),
entry->GetValue());
result = AppendConfigString(entry->GetKey(), entry->GetValue());
result = AppendConfigString(entry->key, entry->value);
if (!result)
break;

View File

@@ -30,7 +30,7 @@
// Interface file between the Breakpad.framework and
// the Inspector process.
#import "common/mac/SimpleStringDictionary.h"
#include "common/simple_string_dictionary.h"
#import <Foundation/Foundation.h>
#include <mach/mach.h>
@@ -38,9 +38,6 @@
#import "client/mac/crash_generation/ConfigFile.h"
#import "client/mac/handler/minidump_generator.h"
extern bool gDebugLog;
#define DEBUGLOG if (gDebugLog) fprintf
// Types of mach messsages (message IDs)
enum {
@@ -65,13 +62,14 @@ struct InspectorInfo {
struct KeyValueMessageData {
public:
KeyValueMessageData() {}
KeyValueMessageData(const google_breakpad::KeyValueEntry &inEntry) {
strlcpy(key, inEntry.GetKey(), sizeof(key) );
strlcpy(value, inEntry.GetValue(), sizeof(value) );
explicit KeyValueMessageData(
const google_breakpad::SimpleStringDictionary::Entry &inEntry) {
strlcpy(key, inEntry.key, sizeof(key) );
strlcpy(value, inEntry.value, sizeof(value) );
}
char key[google_breakpad::KeyValueEntry::MAX_STRING_STORAGE_SIZE];
char value[google_breakpad::KeyValueEntry::MAX_STRING_STORAGE_SIZE];
char key[google_breakpad::SimpleStringDictionary::key_size];
char value[google_breakpad::SimpleStringDictionary::value_size];
};
using std::string;
@@ -86,7 +84,6 @@ class MinidumpLocation {
// Ensure that the path exists. Fallback to /tmp if unable to locate path.
assert(minidumpDir);
if (!EnsureDirectoryPathExists(minidumpDir)) {
DEBUGLOG(stderr, "Unable to create: %s\n", [minidumpDir UTF8String]);
minidumpDir = @"/tmp";
}

View File

@@ -41,7 +41,6 @@
#import "client/mac/Framework/Breakpad.h"
#import "client/mac/handler/minidump_generator.h"
#import "common/mac/SimpleStringDictionary.h"
#import "common/mac/MachIPC.h"
#include "common/mac/bootstrap_compat.h"
@@ -253,8 +252,6 @@ kern_return_t Inspector::ReadMessages() {
}
}
if (parameters_read != info.parameter_count) {
DEBUGLOG(stderr, "Only read %d parameters instead of %d, aborting crash "
"dump generation.", parameters_read, info.parameter_count);
return KERN_FAILURE;
}
}
@@ -266,7 +263,6 @@ kern_return_t Inspector::ReadMessages() {
bool Inspector::InspectTask() {
// keep the task quiet while we're looking at it
task_suspend(remote_task_);
DEBUGLOG(stderr, "Suspended Remote task\n");
NSString *minidumpDir;
@@ -299,9 +295,6 @@ bool Inspector::InspectTask() {
minidumpDir = [[NSString stringWithUTF8String:minidumpDirectory]
stringByExpandingTildeInPath];
}
DEBUGLOG(stderr,
"Writing minidump to directory (%s)\n",
[minidumpDir UTF8String]);
MinidumpLocation minidumpLocation(minidumpDir);
@@ -318,11 +311,6 @@ bool Inspector::InspectTask() {
minidumpPath = [minidumpPath
stringByAppendingPathExtension:@"dmp"];
DEBUGLOG(stderr,
"minidump path (%s)\n",
[minidumpPath UTF8String]);
config_file_.WriteFile( 0,
&config_params_,
minidumpLocation.GetPath(),
@@ -341,15 +329,8 @@ bool Inspector::InspectTask() {
bool result = generator.Write([minidumpPath fileSystemRepresentation]);
if (result) {
DEBUGLOG(stderr, "Wrote minidump - OK\n");
} else {
DEBUGLOG(stderr, "Error writing minidump - errno=%s\n", strerror(errno));
}
// let the task continue
task_resume(remote_task_);
DEBUGLOG(stderr, "Resumed remote task\n");
return result;
}
@@ -362,9 +343,6 @@ kern_return_t Inspector::SendAcknowledgement() {
MachPortSender sender(ack_port_);
MachSendMessage ack_message(kMsgType_InspectorAcknowledgement);
DEBUGLOG(stderr, "Inspector: trying to send acknowledgement to port %d\n",
ack_port_);
kern_return_t result = sender.SendMessage(ack_message, 2000);
#if VERBOSE
@@ -374,7 +352,6 @@ kern_return_t Inspector::SendAcknowledgement() {
return result;
}
DEBUGLOG(stderr, "Inspector: port translation failure!\n");
return KERN_INVALID_NAME;
}
@@ -383,7 +360,6 @@ void Inspector::LaunchReporter(const char *inConfigFilePath) {
// Extract the path to the reporter executable.
const char *reporterExecutablePath =
config_params_.GetValueForKey(BREAKPAD_REPORTER_EXE_LOCATION);
DEBUGLOG(stderr, "reporter path = %s\n", reporterExecutablePath);
// Setup and launch the crash dump sender.
const char *argv[3];
@@ -399,7 +375,6 @@ void Inspector::LaunchReporter(const char *inConfigFilePath) {
if (pid == 0) {
execv(argv[0], (char * const *)argv);
config_file_.Unlink(); // launch failed - get rid of config file
DEBUGLOG(stderr, "Inspector: unable to launch reporter app\n");
_exit(1);
}
@@ -417,8 +392,7 @@ void Inspector::LaunchReporter(const char *inConfigFilePath) {
// The child has not yet finished.
sleep(1);
} else if (result == -1) {
DEBUGLOG(stderr, "Inspector: waitpid error (%d) waiting for reporter app\n",
errno);
// error occurred.
break;
} else {
// child has finished

View File

@@ -29,6 +29,8 @@
#include "client/mac/crash_generation/crash_generation_server.h"
#include <pthread.h>
#include "client/mac/crash_generation/client_info.h"
#include "client/mac/handler/minidump_generator.h"
#include "common/mac/scoped_task_suspend-inl.h"
@@ -37,6 +39,8 @@ namespace google_breakpad {
CrashGenerationServer::CrashGenerationServer(
const char *mach_port_name,
FilterCallback filter,
void *filter_context,
OnClientDumpRequestCallback dump_callback,
void *dump_context,
OnClientExitingCallback exit_callback,
@@ -44,6 +48,8 @@ CrashGenerationServer::CrashGenerationServer(
bool generate_dumps,
const std::string &dump_path)
: dump_callback_(dump_callback),
filter_(filter),
filter_context_(filter_context),
dump_context_(dump_context),
exit_callback_(exit_callback),
exit_context_(exit_context),
@@ -110,7 +116,7 @@ bool CrashGenerationServer::WaitForOneMessage() {
bool result;
std::string dump_path;
if (generate_dumps_) {
if (generate_dumps_ && (!filter_ || filter_(filter_context_))) {
ScopedTaskSuspend suspend(remote_task);
MinidumpGenerator generator(remote_task, handler_thread);

View File

@@ -65,10 +65,14 @@ class CrashGenerationServer {
typedef void (*OnClientExitingCallback)(void *context,
const ClientInfo &client_info);
// If a FilterCallback returns false, the dump will not be written.
typedef bool (*FilterCallback)(void *context);
// Create an instance with the given parameters.
//
// mach_port_name: Named server port to listen on.
// filter: Callback for a client to cancel writing a dump.
// filter_context: Context for the filter callback.
// dump_callback: Callback for a client crash dump request.
// dump_context: Context for client crash dump request callback.
// exit_callback: Callback for client process exit.
@@ -80,6 +84,8 @@ class CrashGenerationServer {
// dump_path: Path for generating dumps; required only if true is
// passed for generateDumps parameter; NULL can be passed otherwise.
CrashGenerationServer(const char *mach_port_name,
FilterCallback filter,
void *filter_context,
OnClientDumpRequestCallback dump_callback,
void *dump_context,
OnClientExitingCallback exit_callback,
@@ -109,6 +115,9 @@ class CrashGenerationServer {
// if a quit message was received or if an error occurred.
bool WaitForOneMessage();
FilterCallback filter_;
void *filter_context_;
OnClientDumpRequestCallback dump_callback_;
void *dump_context_;

View File

@@ -204,11 +204,11 @@ int __breakpad_fdnlist(int fd, nlist_type *list, const char **symbolNames,
*((unsigned int *)&buf) == FAT_MAGIC) {
/* Get host info */
host_t host = mach_host_self();
unsigned i = HOST_BASIC_INFO_COUNT;
unsigned hic = HOST_BASIC_INFO_COUNT;
struct host_basic_info hbi;
kern_return_t kr;
if ((kr = host_info(host, HOST_BASIC_INFO,
(host_info_t)(&hbi), &i)) != KERN_SUCCESS) {
(host_info_t)(&hbi), &hic)) != KERN_SUCCESS) {
return -1;
}
mach_port_deallocate(mach_task_self(), host);
@@ -361,7 +361,7 @@ int __breakpad_fdnlist(int fd, nlist_type *list, const char **symbolNames,
if (read(fd, (char *)space, m) != m)
break;
n -= m;
long savpos = lseek(fd, 0, SEEK_CUR);
off_t savpos = lseek(fd, 0, SEEK_CUR);
if (savpos == -1) {
return -1;
}

View File

@@ -41,6 +41,7 @@ extern "C" { // needed to compile on Leopard
#include <mach/task_info.h>
#include <sys/sysctl.h>
#include <TargetConditionals.h>
#include <unistd.h>
#include <algorithm>
#include <string>
@@ -261,8 +262,8 @@ bool FindTextSection(DynamicImage& image) {
reinterpret_cast<const mach_segment_command_type *>(cmd);
if (!strcmp(seg->segname, "__TEXT")) {
image.vmaddr_ = seg->vmaddr;
image.vmsize_ = seg->vmsize;
image.vmaddr_ = static_cast<mach_vm_address_t>(seg->vmaddr);
image.vmsize_ = static_cast<mach_vm_size_t>(seg->vmsize);
image.slide_ = 0;
if (seg->fileoff == 0 && seg->filesize != 0) {
@@ -475,8 +476,6 @@ void ReadImageInfo(DynamicImages& images,
mach_header_bytes) != KERN_SUCCESS)
continue;
header = reinterpret_cast<mach_header_type*>(&mach_header_bytes[0]);
// Read the file name from the task's memory space.
string file_path;
if (info.file_path_) {
@@ -492,7 +491,7 @@ void ReadImageInfo(DynamicImages& images,
header_size,
info.load_address_,
file_path,
info.file_mod_date_,
static_cast<uintptr_t>(info.file_mod_date_),
images.task_,
images.cpu_type_);

View File

@@ -285,6 +285,8 @@ class DynamicImages {
return CPU_TYPE_POWERPC64;
#elif defined(__arm__)
return CPU_TYPE_ARM;
#elif defined(__arm64__)
return CPU_TYPE_ARM64;
#else
#error "GetNativeCPUType not implemented for this architecture"
#endif

View File

@@ -27,19 +27,32 @@
// (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 <map>
#include <mach/exc.h>
#include <mach/mig.h>
#include <pthread.h>
#include <signal.h>
#include <TargetConditionals.h>
#include <map>
#include "client/mac/handler/exception_handler.h"
#include "client/mac/handler/minidump_generator.h"
#include "common/mac/macho_utilities.h"
#include "common/mac/scoped_task_suspend-inl.h"
#include "google_breakpad/common/minidump_exception_mac.h"
#ifndef __EXCEPTIONS
// This file uses C++ try/catch (but shouldn't). Duplicate the macros from
// <c++/4.2.1/exception_defines.h> allowing this file to work properly with
// exceptions disabled even when other C++ libraries are used. #undef the try
// and catch macros first in case libstdc++ is in use and has already provided
// its own definitions.
#undef try
#define try if (true)
#undef catch
#define catch(X) if (false)
#endif // __EXCEPTIONS
#ifndef USE_PROTECTED_ALLOCATIONS
#if TARGET_OS_IPHONE
#define USE_PROTECTED_ALLOCATIONS 1
@@ -103,11 +116,10 @@ exception_mask_t s_exception_mask = EXC_MASK_BAD_ACCESS |
EXC_MASK_BAD_INSTRUCTION | EXC_MASK_ARITHMETIC | EXC_MASK_BREAKPOINT;
#if !TARGET_OS_IPHONE
extern "C"
{
extern "C" {
// Forward declarations for functions that need "C" style compilation
boolean_t exc_server(mach_msg_header_t *request,
mach_msg_header_t *reply);
boolean_t exc_server(mach_msg_header_t* request,
mach_msg_header_t* reply);
// This symbol must be visible to dlsym() - see
// http://code.google.com/p/google-breakpad/issues/detail?id=345 for details.
@@ -129,19 +141,19 @@ kern_return_t ForwardException(mach_port_t task,
#if TARGET_OS_IPHONE
// Implementation is based on the implementation generated by mig.
boolean_t breakpad_exc_server(mach_msg_header_t *InHeadP,
mach_msg_header_t *OutHeadP) {
OutHeadP->msgh_bits =
MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(InHeadP->msgh_bits), 0);
OutHeadP->msgh_remote_port = InHeadP->msgh_remote_port;
/* Minimal size: routine() will update it if different */
OutHeadP->msgh_size = (mach_msg_size_t)sizeof(mig_reply_error_t);
OutHeadP->msgh_local_port = MACH_PORT_NULL;
OutHeadP->msgh_id = InHeadP->msgh_id + 100;
boolean_t breakpad_exc_server(mach_msg_header_t* InHeadP,
mach_msg_header_t* OutHeadP) {
OutHeadP->msgh_bits =
MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(InHeadP->msgh_bits), 0);
OutHeadP->msgh_remote_port = InHeadP->msgh_remote_port;
/* Minimal size: routine() will update it if different */
OutHeadP->msgh_size = (mach_msg_size_t)sizeof(mig_reply_error_t);
OutHeadP->msgh_local_port = MACH_PORT_NULL;
OutHeadP->msgh_id = InHeadP->msgh_id + 100;
if (InHeadP->msgh_id != 2401) {
((mig_reply_error_t *)OutHeadP)->NDR = NDR_record;
((mig_reply_error_t *)OutHeadP)->RetCode = MIG_BAD_ID;
((mig_reply_error_t*)OutHeadP)->NDR = NDR_record;
((mig_reply_error_t*)OutHeadP)->RetCode = MIG_BAD_ID;
return FALSE;
}
@@ -171,8 +183,8 @@ boolean_t breakpad_exc_server(mach_msg_header_t *InHeadP,
#pragma pack()
#endif
Request *In0P = (Request *)InHeadP;
Reply *OutP = (Reply *)OutHeadP;
Request* In0P = (Request*)InHeadP;
Reply* OutP = (Reply*)OutHeadP;
if (In0P->task.name != mach_task_self()) {
return FALSE;
@@ -186,8 +198,8 @@ boolean_t breakpad_exc_server(mach_msg_header_t *InHeadP,
return TRUE;
}
#else
boolean_t breakpad_exc_server(mach_msg_header_t *request,
mach_msg_header_t *reply) {
boolean_t breakpad_exc_server(mach_msg_header_t* request,
mach_msg_header_t* reply) {
return exc_server(request, reply);
}
@@ -207,9 +219,9 @@ kern_return_t catch_exception_raise(mach_port_t port, mach_port_t failed_thread,
ExceptionHandler::ExceptionHandler(const string &dump_path,
FilterCallback filter,
MinidumpCallback callback,
void *callback_context,
void* callback_context,
bool install_handler,
const char *port_name)
const char* port_name)
: dump_path_(),
filter_(filter),
callback_(callback),
@@ -235,7 +247,7 @@ ExceptionHandler::ExceptionHandler(const string &dump_path,
// special constructor if we want to bypass minidump writing and
// simply get a callback with the exception information
ExceptionHandler::ExceptionHandler(DirectCallback callback,
void *callback_context,
void* callback_context,
bool install_handler)
: dump_path_(),
filter_(NULL),
@@ -269,9 +281,13 @@ bool ExceptionHandler::WriteMinidump(bool write_exception_stream) {
if (pthread_mutex_lock(&minidump_write_mutex_) == 0) {
// Send an empty message to the handle port so that a minidump will
// be written
SendMessageToHandlerThread(write_exception_stream ?
kWriteDumpWithExceptionMessage :
kWriteDumpMessage);
bool result = SendMessageToHandlerThread(write_exception_stream ?
kWriteDumpWithExceptionMessage :
kWriteDumpMessage);
if (!result) {
pthread_mutex_unlock(&minidump_write_mutex_);
return false;
}
// Wait for the minidump writer to complete its writing. It will unlock
// the mutex when completed
@@ -287,18 +303,18 @@ bool ExceptionHandler::WriteMinidump(bool write_exception_stream) {
bool ExceptionHandler::WriteMinidump(const string &dump_path,
bool write_exception_stream,
MinidumpCallback callback,
void *callback_context) {
void* callback_context) {
ExceptionHandler handler(dump_path, NULL, callback, callback_context, false,
NULL);
NULL);
return handler.WriteMinidump(write_exception_stream);
}
// static
bool ExceptionHandler::WriteMinidumpForChild(mach_port_t child,
mach_port_t child_blamed_thread,
const string &dump_path,
MinidumpCallback callback,
void *callback_context) {
mach_port_t child_blamed_thread,
const string &dump_path,
MinidumpCallback callback,
void* callback_context) {
ScopedTaskSuspend suspend(child);
MinidumpGenerator generator(child, MACH_PORT_NULL);
@@ -306,32 +322,34 @@ bool ExceptionHandler::WriteMinidumpForChild(mach_port_t child,
string dump_filename = generator.UniqueNameInDirectory(dump_path, &dump_id);
generator.SetExceptionInformation(EXC_BREAKPOINT,
#if defined (__i386__) || defined(__x86_64__)
EXC_I386_BPT,
#elif defined (__ppc__) || defined (__ppc64__)
EXC_PPC_BREAKPOINT,
#elif defined (__arm__)
EXC_ARM_BREAKPOINT,
#if defined(__i386__) || defined(__x86_64__)
EXC_I386_BPT,
#elif defined(__ppc__) || defined(__ppc64__)
EXC_PPC_BREAKPOINT,
#elif defined(__arm__) || defined(__arm64__)
EXC_ARM_BREAKPOINT,
#else
#error architecture not supported
#endif
0,
child_blamed_thread);
0,
child_blamed_thread);
bool result = generator.Write(dump_filename.c_str());
if (callback) {
return callback(dump_path.c_str(), dump_id.c_str(),
callback_context, result);
callback_context, result);
}
return result;
}
bool ExceptionHandler::WriteMinidumpWithException(int exception_type,
int exception_code,
int exception_subcode,
mach_port_t thread_name,
bool exit_after_write,
bool report_current_thread) {
bool ExceptionHandler::WriteMinidumpWithException(
int exception_type,
int exception_code,
int exception_subcode,
breakpad_ucontext_t* task_context,
mach_port_t thread_name,
bool exit_after_write,
bool report_current_thread) {
bool result = false;
if (directCallback_) {
@@ -349,12 +367,15 @@ bool ExceptionHandler::WriteMinidumpWithException(int exception_type,
// If this is a real exception, give the filter (if any) a chance to
// decide if this should be sent.
if (filter_ && !filter_(callback_context_))
return false;
return crash_generation_client_->RequestDumpForException(
exception_type,
exception_code,
exception_subcode,
thread_name);
return false;
result = crash_generation_client_->RequestDumpForException(
exception_type,
exception_code,
exception_subcode,
thread_name);
if (result && exit_after_write) {
_exit(exception_type);
}
}
#endif
} else {
@@ -366,6 +387,7 @@ bool ExceptionHandler::WriteMinidumpWithException(int exception_type,
MinidumpGenerator md(mach_task_self(),
report_current_thread ? MACH_PORT_NULL :
mach_thread_self());
md.SetTaskContext(task_context);
if (exception_type && exception_code) {
// If this is a real exception, give the filter (if any) a chance to
// decide if this should be sent.
@@ -432,12 +454,13 @@ kern_return_t ForwardException(mach_port_t task, mach_port_t failed_thread,
exception_behavior_t target_behavior = current.behaviors[found];
kern_return_t result;
// TODO: Handle the case where |target_behavior| has MACH_EXCEPTION_CODES
// set. https://code.google.com/p/google-breakpad/issues/detail?id=551
switch (target_behavior) {
case EXCEPTION_DEFAULT:
result = exception_raise(target_port, failed_thread, task, exception,
code, code_count);
break;
default:
fprintf(stderr, "** Unknown exception behavior: %d\n", target_behavior);
result = KERN_FAILURE;
@@ -448,9 +471,9 @@ kern_return_t ForwardException(mach_port_t task, mach_port_t failed_thread,
}
// static
void *ExceptionHandler::WaitForMessage(void *exception_handler_class) {
ExceptionHandler *self =
reinterpret_cast<ExceptionHandler *>(exception_handler_class);
void* ExceptionHandler::WaitForMessage(void* exception_handler_class) {
ExceptionHandler* self =
reinterpret_cast<ExceptionHandler*>(exception_handler_class);
ExceptionMessage receive;
// Wait for the exception info
@@ -495,11 +518,11 @@ void *ExceptionHandler::WaitForMessage(void *exception_handler_class) {
if (receive.header.msgh_id == kWriteDumpWithExceptionMessage) {
thread = receive.thread.name;
exception_type = EXC_BREAKPOINT;
#if defined (__i386__) || defined(__x86_64__)
#if defined(__i386__) || defined(__x86_64__)
exception_code = EXC_I386_BPT;
#elif defined (__ppc__) || defined (__ppc64__)
#elif defined(__ppc__) || defined(__ppc64__)
exception_code = EXC_PPC_BREAKPOINT;
#elif defined (__arm__)
#elif defined(__arm__) || defined(__arm64__)
exception_code = EXC_ARM_BREAKPOINT;
#else
#error architecture not supported
@@ -509,7 +532,7 @@ void *ExceptionHandler::WaitForMessage(void *exception_handler_class) {
// Write out the dump and save the result for later retrieval
self->last_minidump_write_result_ =
self->WriteMinidumpWithException(exception_type, exception_code,
0, thread,
0, NULL, thread,
false, false);
#if USE_PROTECTED_ALLOCATIONS
@@ -544,8 +567,8 @@ void *ExceptionHandler::WaitForMessage(void *exception_handler_class) {
// Generate the minidump with the exception data.
self->WriteMinidumpWithException(receive.exception, receive.code[0],
subcode, receive.thread.name, true,
false);
subcode, NULL, receive.thread.name,
true, false);
#if USE_PROTECTED_ALLOCATIONS
// This may have become protected again within
@@ -580,7 +603,7 @@ void *ExceptionHandler::WaitForMessage(void *exception_handler_class) {
return NULL;
}
//static
// static
void ExceptionHandler::SignalHandler(int sig, siginfo_t* info, void* uc) {
#if USE_PROTECTED_ALLOCATIONS
if (gBreakpadAllocator)
@@ -590,6 +613,7 @@ void ExceptionHandler::SignalHandler(int sig, siginfo_t* info, void* uc) {
EXC_SOFTWARE,
MD_EXCEPTION_CODE_MAC_ABORT,
0,
static_cast<breakpad_ucontext_t*>(uc),
mach_thread_self(),
true,
true);
@@ -604,7 +628,6 @@ bool ExceptionHandler::InstallHandler() {
if (gProtectedData.handler != NULL) {
return false;
}
#if TARGET_OS_IPHONE
if (!IsOutOfProcess()) {
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
@@ -624,7 +647,6 @@ bool ExceptionHandler::InstallHandler() {
mprotect(gProtectedData.protected_buffer, PAGE_SIZE, PROT_READ);
#endif
}
#endif
try {
#if USE_PROTECTED_ALLOCATIONS
@@ -633,7 +655,6 @@ bool ExceptionHandler::InstallHandler() {
#else
previous_ = new ExceptionParameters();
#endif
}
catch (std::bad_alloc) {
return false;
@@ -732,7 +753,7 @@ bool ExceptionHandler::Setup(bool install_handler) {
result = thread_create_result ? KERN_FAILURE : KERN_SUCCESS;
}
return result == KERN_SUCCESS ? true : false;
return result == KERN_SUCCESS;
}
bool ExceptionHandler::Teardown() {

View File

@@ -41,7 +41,8 @@
#include <string>
#include "processor/scoped_ptr.h"
#include "client/mac/handler/ucontext_compat.h"
#include "common/scoped_ptr.h"
#if !TARGET_OS_IPHONE
#include "client/mac/crash_generation/crash_generation_client.h"
@@ -182,10 +183,13 @@ class ExceptionHandler {
// success, false otherwise.
bool SendMessageToHandlerThread(HandlerThreadMessage message_id);
// All minidump writing goes through this one routine
// All minidump writing goes through this one routine.
// |task_context| can be NULL. If not, it will be used to retrieve the
// context of the current thread, instead of using |thread_get_state|.
bool WriteMinidumpWithException(int exception_type,
int exception_code,
int exception_subcode,
breakpad_ucontext_t *task_context,
mach_port_t thread_name,
bool exit_after_write,
bool report_current_thread);

View File

@@ -32,15 +32,18 @@
#include <TargetConditionals.h>
// On iOS 5, mach/mach_vm.h is not supported anymore. As the architecture is 32
// bits, we can use the simple vm_ functions instead of the mach_vm_ ones.
// On iOS 5 and higher, mach/mach_vm.h is not supported. Use the corresponding
// vm_map functions instead.
#if TARGET_OS_IPHONE
#include <mach/vm_map.h>
#define mach_vm_address_t vm_address_t
#define mach_vm_deallocate vm_deallocate
#define mach_vm_read vm_read
#define mach_vm_region vm_region
#if defined(__LP64__)
#define mach_vm_region_recurse vm_region_recurse_64
#else
#define mach_vm_region_recurse vm_region_recurse
#endif
#define mach_vm_size_t vm_size_t
#else
#include <mach/mach_vm.h>

View File

@@ -31,6 +31,7 @@
#include <cstdio>
#include <mach/host_info.h>
#include <mach/machine.h>
#include <mach/vm_statistics.h>
#include <mach-o/dyld.h>
#include <mach-o/loader.h>
@@ -41,7 +42,7 @@
#include "client/mac/handler/minidump_generator.h"
#ifdef HAS_ARM_SUPPORT
#if defined(HAS_ARM_SUPPORT) || defined(HAS_ARM64_SUPPORT)
#include <mach/arm/thread_status.h>
#endif
#ifdef HAS_PPC_SUPPORT
@@ -77,6 +78,7 @@ MinidumpGenerator::MinidumpGenerator()
crashing_task_(mach_task_self()),
handler_thread_(mach_thread_self()),
cpu_type_(DynamicImages::GetNativeCPUType()),
task_context_(NULL),
dynamic_images_(NULL),
memory_blocks_(&allocator_) {
GatherSystemInformation();
@@ -94,6 +96,7 @@ MinidumpGenerator::MinidumpGenerator(mach_port_t crashing_task,
crashing_task_(crashing_task),
handler_thread_(handler_thread),
cpu_type_(DynamicImages::GetNativeCPUType()),
task_context_(NULL),
dynamic_images_(NULL),
memory_blocks_(&allocator_) {
if (crashing_task != mach_task_self()) {
@@ -168,6 +171,10 @@ void MinidumpGenerator::GatherSystemInformation() {
os_build_number_ = IntegerValueAtIndex(product_str, 2);
}
void MinidumpGenerator::SetTaskContext(breakpad_ucontext_t *task_context) {
task_context_ = task_context;
}
string MinidumpGenerator::UniqueNameInDirectory(const string &dir,
string *unique_name) {
CFUUIDRef uuid = CFUUIDCreate(NULL);
@@ -288,7 +295,7 @@ size_t MinidumpGenerator::CalculateStackSize(mach_vm_address_t start_addr) {
mach_vm_address_t proposed_next_region_base = next_region_base;
mach_vm_size_t next_region_size;
nesting_level = 0;
mach_msg_type_number_t info_count = VM_REGION_SUBMAP_INFO_COUNT_64;
info_count = VM_REGION_SUBMAP_INFO_COUNT_64;
result = mach_vm_region_recurse(crashing_task_, &next_region_base,
&next_region_size, &nesting_level,
region_info, &info_count);
@@ -362,6 +369,10 @@ bool MinidumpGenerator::WriteStack(breakpad_thread_state_data_t state,
case CPU_TYPE_ARM:
return WriteStackARM(state, stack_location);
#endif
#ifdef HAS_ARM64_SUPPORT
case CPU_TYPE_ARM64:
return WriteStackARM64(state, stack_location);
#endif
#ifdef HAS_PPC_SUPPORT
case CPU_TYPE_POWERPC:
return WriteStackPPC(state, stack_location);
@@ -386,6 +397,10 @@ bool MinidumpGenerator::WriteContext(breakpad_thread_state_data_t state,
case CPU_TYPE_ARM:
return WriteContextARM(state, register_location);
#endif
#ifdef HAS_ARM64_SUPPORT
case CPU_TYPE_ARM64:
return WriteContextARM64(state, register_location);
#endif
#ifdef HAS_PPC_SUPPORT
case CPU_TYPE_POWERPC:
return WriteContextPPC(state, register_location);
@@ -403,13 +418,17 @@ bool MinidumpGenerator::WriteContext(breakpad_thread_state_data_t state,
}
}
u_int64_t MinidumpGenerator::CurrentPCForStack(
uint64_t MinidumpGenerator::CurrentPCForStack(
breakpad_thread_state_data_t state) {
switch (cpu_type_) {
#ifdef HAS_ARM_SUPPORT
case CPU_TYPE_ARM:
return CurrentPCForStackARM(state);
#endif
#ifdef HAS_ARM64_SUPPORT
case CPU_TYPE_ARM64:
return CurrentPCForStackARM64(state);
#endif
#ifdef HAS_PPC_SUPPORT
case CPU_TYPE_POWERPC:
return CurrentPCForStackPPC(state);
@@ -423,7 +442,7 @@ u_int64_t MinidumpGenerator::CurrentPCForStack(
return CurrentPCForStackX86_64(state);
#endif
default:
assert("Unknown CPU type!");
assert(0 && "Unknown CPU type!");
return 0;
}
}
@@ -437,7 +456,7 @@ bool MinidumpGenerator::WriteStackARM(breakpad_thread_state_data_t state,
return WriteStackFromStartAddress(start_addr, stack_location);
}
u_int64_t
uint64_t
MinidumpGenerator::CurrentPCForStackARM(breakpad_thread_state_data_t state) {
arm_thread_state_t *machine_state =
reinterpret_cast<arm_thread_state_t *>(state);
@@ -479,7 +498,82 @@ bool MinidumpGenerator::WriteContextARM(breakpad_thread_state_data_t state,
AddGPR(10);
AddGPR(11);
AddGPR(12);
#undef AddReg
#undef AddGPR
return true;
}
#endif
#ifdef HAS_ARM64_SUPPORT
bool MinidumpGenerator::WriteStackARM64(breakpad_thread_state_data_t state,
MDMemoryDescriptor *stack_location) {
arm_thread_state64_t *machine_state =
reinterpret_cast<arm_thread_state64_t *>(state);
mach_vm_address_t start_addr = REGISTER_FROM_THREADSTATE(machine_state, sp);
return WriteStackFromStartAddress(start_addr, stack_location);
}
uint64_t
MinidumpGenerator::CurrentPCForStackARM64(breakpad_thread_state_data_t state) {
arm_thread_state64_t *machine_state =
reinterpret_cast<arm_thread_state64_t *>(state);
return REGISTER_FROM_THREADSTATE(machine_state, pc);
}
bool
MinidumpGenerator::WriteContextARM64(breakpad_thread_state_data_t state,
MDLocationDescriptor *register_location)
{
TypedMDRVA<MDRawContextARM64> context(&writer_);
arm_thread_state64_t *machine_state =
reinterpret_cast<arm_thread_state64_t *>(state);
if (!context.Allocate())
return false;
*register_location = context.location();
MDRawContextARM64 *context_ptr = context.get();
context_ptr->context_flags = MD_CONTEXT_ARM64_FULL;
#define AddGPR(a) context_ptr->iregs[a] = \
REGISTER_FROM_THREADSTATE(machine_state, x[a])
context_ptr->iregs[29] = REGISTER_FROM_THREADSTATE(machine_state, fp);
context_ptr->iregs[30] = REGISTER_FROM_THREADSTATE(machine_state, lr);
context_ptr->iregs[31] = REGISTER_FROM_THREADSTATE(machine_state, sp);
context_ptr->iregs[32] = REGISTER_FROM_THREADSTATE(machine_state, pc);
context_ptr->cpsr = REGISTER_FROM_THREADSTATE(machine_state, cpsr);
AddGPR(0);
AddGPR(1);
AddGPR(2);
AddGPR(3);
AddGPR(4);
AddGPR(5);
AddGPR(6);
AddGPR(7);
AddGPR(8);
AddGPR(9);
AddGPR(10);
AddGPR(11);
AddGPR(12);
AddGPR(13);
AddGPR(14);
AddGPR(15);
AddGPR(16);
AddGPR(17);
AddGPR(18);
AddGPR(19);
AddGPR(20);
AddGPR(21);
AddGPR(22);
AddGPR(23);
AddGPR(24);
AddGPR(25);
AddGPR(26);
AddGPR(27);
AddGPR(28);
#undef AddGPR
return true;
@@ -503,7 +597,7 @@ bool MinidumpGenerator::WriteStackPPC64(breakpad_thread_state_data_t state,
return WriteStackFromStartAddress(start_addr, stack_location);
}
u_int64_t
uint64_t
MinidumpGenerator::CurrentPCForStackPPC(breakpad_thread_state_data_t state) {
ppc_thread_state_t *machine_state =
reinterpret_cast<ppc_thread_state_t *>(state);
@@ -511,7 +605,7 @@ MinidumpGenerator::CurrentPCForStackPPC(breakpad_thread_state_data_t state) {
return REGISTER_FROM_THREADSTATE(machine_state, srr0);
}
u_int64_t
uint64_t
MinidumpGenerator::CurrentPCForStackPPC64(breakpad_thread_state_data_t state) {
ppc_thread_state64_t *machine_state =
reinterpret_cast<ppc_thread_state64_t *>(state);
@@ -665,7 +759,7 @@ bool MinidumpGenerator::WriteStackX86_64(breakpad_thread_state_data_t state,
return WriteStackFromStartAddress(start_addr, stack_location);
}
u_int64_t
uint64_t
MinidumpGenerator::CurrentPCForStackX86(breakpad_thread_state_data_t state) {
i386_thread_state_t *machine_state =
reinterpret_cast<i386_thread_state_t *>(state);
@@ -673,7 +767,7 @@ MinidumpGenerator::CurrentPCForStackX86(breakpad_thread_state_data_t state) {
return REGISTER_FROM_THREADSTATE(machine_state, eip);
}
u_int64_t
uint64_t
MinidumpGenerator::CurrentPCForStackX86_64(breakpad_thread_state_data_t state) {
x86_thread_state64_t *machine_state =
reinterpret_cast<x86_thread_state64_t *>(state);
@@ -757,7 +851,7 @@ bool MinidumpGenerator::WriteContextX86_64(
// not used in the flags register. Since the minidump format
// specifies 32 bits for the flags register, we can truncate safely
// with no loss.
context_ptr->eflags = static_cast<u_int32_t>(REGISTER_FROM_THREADSTATE(machine_state, rflags));
context_ptr->eflags = static_cast<uint32_t>(REGISTER_FROM_THREADSTATE(machine_state, rflags));
AddReg(cs);
AddReg(fs);
AddReg(gs);
@@ -770,6 +864,40 @@ bool MinidumpGenerator::WriteContextX86_64(
bool MinidumpGenerator::GetThreadState(thread_act_t target_thread,
thread_state_t state,
mach_msg_type_number_t *count) {
if (task_context_ && target_thread == mach_thread_self()) {
switch (cpu_type_) {
#ifdef HAS_ARM_SUPPORT
case CPU_TYPE_ARM:
size_t final_size =
std::min(static_cast<size_t>(*count), sizeof(arm_thread_state_t));
memcpy(state, &task_context_->breakpad_uc_mcontext->__ss, final_size);
*count = final_size;
return true;
#endif
#ifdef HAS_ARM64_SUPPORT
case CPU_TYPE_ARM64: {
size_t final_size =
std::min(static_cast<size_t>(*count), sizeof(arm_thread_state64_t));
memcpy(state, &task_context_->breakpad_uc_mcontext->__ss, final_size);
*count = final_size;
return true;
}
#endif
#ifdef HAS_X86_SUPPORT
case CPU_TYPE_I386:
case CPU_TYPE_X86_64: {
size_t state_size = cpu_type_ == CPU_TYPE_I386 ?
sizeof(i386_thread_state_t) : sizeof(x86_thread_state64_t);
size_t final_size =
std::min(static_cast<size_t>(*count), state_size);
memcpy(state, &task_context_->breakpad_uc_mcontext->__ss, final_size);
*count = final_size;
return true;
}
#endif
}
}
thread_state_flavor_t flavor;
switch (cpu_type_) {
#ifdef HAS_ARM_SUPPORT
@@ -777,6 +905,11 @@ bool MinidumpGenerator::GetThreadState(thread_act_t target_thread,
flavor = ARM_THREAD_STATE;
break;
#endif
#ifdef HAS_ARM64_SUPPORT
case CPU_TYPE_ARM64:
flavor = ARM_THREAD_STATE64;
break;
#endif
#ifdef HAS_PPC_SUPPORT
case CPU_TYPE_POWERPC:
flavor = PPC_THREAD_STATE;
@@ -878,26 +1011,25 @@ bool MinidumpGenerator::WriteMemoryListStream(
mach_msg_type_number_t stateCount
= static_cast<mach_msg_type_number_t>(sizeof(state));
if (thread_get_state(exception_thread_,
BREAKPAD_MACHINE_THREAD_STATE,
state,
&stateCount) == KERN_SUCCESS) {
u_int64_t ip = CurrentPCForStack(state);
if (GetThreadState(exception_thread_, state, &stateCount)) {
uint64_t ip = CurrentPCForStack(state);
// Bound it to the upper and lower bounds of the region
// it's contained within. If it's not in a known memory region,
// don't bother trying to write it.
mach_vm_address_t addr = ip;
mach_vm_address_t addr = static_cast<vm_address_t>(ip);
mach_vm_size_t size;
natural_t nesting_level = 0;
vm_region_submap_info_64 info;
mach_msg_type_number_t info_count = VM_REGION_SUBMAP_INFO_COUNT_64;
vm_region_recurse_info_t recurse_info;
recurse_info = reinterpret_cast<vm_region_recurse_info_t>(&info);
kern_return_t ret =
mach_vm_region_recurse(crashing_task_,
&addr,
&size,
&nesting_level,
(vm_region_recurse_info_t)&info,
recurse_info,
&info_count);
if (ret == KERN_SUCCESS && ip >= addr && ip < (addr + size)) {
// Try to get 128 bytes before and after the IP, but
@@ -909,7 +1041,8 @@ bool MinidumpGenerator::WriteMemoryListStream(
std::min(uintptr_t(ip + (kIPMemorySize / 2)),
uintptr_t(addr + size));
ip_memory_d.memory.data_size =
end_of_range - ip_memory_d.start_of_memory_range;
end_of_range -
static_cast<uintptr_t>(ip_memory_d.start_of_memory_range);
have_ip_memory = true;
// This needs to get appended to the list even though
// the memory bytes aren't filled in yet so the entire
@@ -1027,6 +1160,11 @@ bool MinidumpGenerator::WriteSystemInfoStream(
info_ptr->processor_architecture = MD_CPU_ARCHITECTURE_ARM;
break;
#endif
#ifdef HAS_ARM64_SUPPORT
case CPU_TYPE_ARM64:
info_ptr->processor_architecture = MD_CPU_ARCHITECTURE_ARM64;
break;
#endif
#ifdef HAS_PPC_SUPPORT
case CPU_TYPE_POWERPC:
case CPU_TYPE_POWERPC64:
@@ -1103,7 +1241,7 @@ bool MinidumpGenerator::WriteSystemInfoStream(
break;
}
info_ptr->number_of_processors = number_of_processors;
info_ptr->number_of_processors = static_cast<uint8_t>(number_of_processors);
#if TARGET_OS_IPHONE
info_ptr->platform_id = MD_OS_IOS;
#else
@@ -1142,13 +1280,13 @@ bool MinidumpGenerator::WriteModuleStream(unsigned int index,
return false;
module->base_of_image = image->GetVMAddr() + image->GetVMAddrSlide();
module->size_of_image = static_cast<u_int32_t>(image->GetVMSize());
module->size_of_image = static_cast<uint32_t>(image->GetVMSize());
module->module_name_rva = string_location.rva;
// We'll skip the executable module, because they don't have
// LC_ID_DYLIB load commands, and the crash processing server gets
// version information from the Plist file, anyway.
if (index != (uint32_t)FindExecutableModule()) {
if (index != static_cast<uint32_t>(FindExecutableModule())) {
module->version_info.signature = MD_VSFIXEDFILEINFO_SIGNATURE;
module->version_info.struct_version |= MD_VSFIXEDFILEINFO_VERSION;
// Convert MAC dylib version format, which is a 32 bit number, to the
@@ -1208,7 +1346,7 @@ bool MinidumpGenerator::WriteModuleStream(unsigned int index,
return false;
module->base_of_image = seg->vmaddr + slide;
module->size_of_image = static_cast<u_int32_t>(seg->vmsize);
module->size_of_image = static_cast<uint32_t>(seg->vmsize);
module->module_name_rva = string_location.rva;
bool in_memory = false;
@@ -1267,7 +1405,7 @@ bool MinidumpGenerator::WriteCVRecord(MDRawModule *module, int cpu_type,
size_t module_name_length = strlen(module_name);
if (!cv.AllocateObjectAndArray(module_name_length + 1, sizeof(u_int8_t)))
if (!cv.AllocateObjectAndArray(module_name_length + 1, sizeof(uint8_t)))
return false;
if (!cv.CopyIndexAfterObject(0, module_name, module_name_length))
@@ -1285,22 +1423,27 @@ bool MinidumpGenerator::WriteCVRecord(MDRawModule *module, int cpu_type,
MacFileUtilities::MachoID macho(module_path,
reinterpret_cast<void *>(module->base_of_image),
static_cast<size_t>(module->size_of_image));
result = macho.UUIDCommand(cpu_type, identifier);
result = macho.UUIDCommand(cpu_type, CPU_SUBTYPE_MULTIPLE, identifier);
if (!result)
result = macho.MD5(cpu_type, identifier);
result = macho.MD5(cpu_type, CPU_SUBTYPE_MULTIPLE, identifier);
}
if (!result) {
FileID file_id(module_path);
result = file_id.MachoIdentifier(cpu_type, identifier);
result = file_id.MachoIdentifier(cpu_type, CPU_SUBTYPE_MULTIPLE,
identifier);
}
if (result) {
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.data1 =
static_cast<uint32_t>(identifier[0]) << 24 |
static_cast<uint32_t>(identifier[1]) << 16 |
static_cast<uint32_t>(identifier[2]) << 8 |
static_cast<uint32_t>(identifier[3]);
cv_ptr->signature.data2 =
static_cast<uint16_t>(identifier[4] << 8) | identifier[5];
cv_ptr->signature.data3 =
static_cast<uint16_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];
@@ -1363,7 +1506,7 @@ bool MinidumpGenerator::WriteMiscInfoStream(MDRawDirectory *misc_info_stream) {
misc_info_stream->location = info.location();
MDRawMiscInfo *info_ptr = info.get();
info_ptr->size_of_info = static_cast<u_int32_t>(sizeof(MDRawMiscInfo));
info_ptr->size_of_info = static_cast<uint32_t>(sizeof(MDRawMiscInfo));
info_ptr->flags1 = MD_MISCINFO_FLAGS1_PROCESS_ID |
MD_MISCINFO_FLAGS1_PROCESS_TIMES |
MD_MISCINFO_FLAGS1_PROCESSOR_POWER_INFO;
@@ -1376,18 +1519,18 @@ bool MinidumpGenerator::WriteMiscInfoStream(MDRawDirectory *misc_info_stream) {
if (getrusage(RUSAGE_SELF, &usage) != -1) {
// Omit the fractional time since the MDRawMiscInfo only wants seconds
info_ptr->process_user_time =
static_cast<u_int32_t>(usage.ru_utime.tv_sec);
static_cast<uint32_t>(usage.ru_utime.tv_sec);
info_ptr->process_kernel_time =
static_cast<u_int32_t>(usage.ru_stime.tv_sec);
static_cast<uint32_t>(usage.ru_stime.tv_sec);
}
int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID,
static_cast<int>(info_ptr->process_id) };
u_int mibsize = static_cast<u_int>(sizeof(mib) / sizeof(mib[0]));
uint mibsize = static_cast<uint>(sizeof(mib) / sizeof(mib[0]));
struct kinfo_proc proc;
size_t size = sizeof(proc);
if (sysctl(mib, mibsize, &proc, &size, NULL, 0) == 0) {
info_ptr->process_create_time =
static_cast<u_int32_t>(proc.kp_proc.p_starttime.tv_sec);
static_cast<uint32_t>(proc.kp_proc.p_starttime.tv_sec);
}
// Speed
@@ -1395,11 +1538,11 @@ bool MinidumpGenerator::WriteMiscInfoStream(MDRawDirectory *misc_info_stream) {
const uint64_t kOneMillion = 1000 * 1000;
size = sizeof(speed);
sysctlbyname("hw.cpufrequency_max", &speed, &size, NULL, 0);
info_ptr->processor_max_mhz = static_cast<u_int32_t>(speed / kOneMillion);
info_ptr->processor_mhz_limit = static_cast<u_int32_t>(speed / kOneMillion);
info_ptr->processor_max_mhz = static_cast<uint32_t>(speed / kOneMillion);
info_ptr->processor_mhz_limit = static_cast<uint32_t>(speed / kOneMillion);
size = sizeof(speed);
sysctlbyname("hw.cpufrequency", &speed, &size, NULL, 0);
info_ptr->processor_current_mhz = static_cast<u_int32_t>(speed / kOneMillion);
info_ptr->processor_current_mhz = static_cast<uint32_t>(speed / kOneMillion);
return true;
}

View File

@@ -37,6 +37,7 @@
#include <string>
#include "client/mac/handler/ucontext_compat.h"
#include "client/minidump_file_writer.h"
#include "common/memory.h"
#include "common/mac/macho_utilities.h"
@@ -45,11 +46,13 @@
#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)
#if !TARGET_OS_IPHONE && (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7)
#define HAS_PPC_SUPPORT
#endif
#if defined(__arm__)
#define HAS_ARM_SUPPORT
#define HAS_ARM_SUPPORT
#elif defined(__arm64__)
#define HAS_ARM64_SUPPORT
#elif defined(__i386__) || defined(__x86_64__)
#define HAS_X86_SUPPORT
#endif
@@ -102,6 +105,11 @@ class MinidumpGenerator {
exception_thread_ = thread_name;
}
// Specify the task context. If |task_context| is not NULL, it will be used
// to retrieve the context of the current thread, instead of using
// |thread_get_state|.
void SetTaskContext(breakpad_ucontext_t *task_context);
// Gather system information. This should be call at least once before using
// the MinidumpGenerator class.
static void GatherSystemInformation();
@@ -125,7 +133,7 @@ class MinidumpGenerator {
bool WriteBreakpadInfoStream(MDRawDirectory *breakpad_info_stream);
// Helpers
u_int64_t CurrentPCForStack(breakpad_thread_state_data_t state);
uint64_t CurrentPCForStack(breakpad_thread_state_data_t state);
bool GetThreadState(thread_act_t target_thread, thread_state_t state,
mach_msg_type_number_t *count);
bool WriteStackFromStartAddress(mach_vm_address_t start_addr,
@@ -146,31 +154,38 @@ class MinidumpGenerator {
MDMemoryDescriptor *stack_location);
bool WriteContextARM(breakpad_thread_state_data_t state,
MDLocationDescriptor *register_location);
u_int64_t CurrentPCForStackARM(breakpad_thread_state_data_t state);
uint64_t CurrentPCForStackARM(breakpad_thread_state_data_t state);
#endif
#ifdef HAS_ARM64_SUPPORT
bool WriteStackARM64(breakpad_thread_state_data_t state,
MDMemoryDescriptor *stack_location);
bool WriteContextARM64(breakpad_thread_state_data_t state,
MDLocationDescriptor *register_location);
uint64_t CurrentPCForStackARM64(breakpad_thread_state_data_t state);
#endif
#ifdef HAS_PPC_SUPPORT
bool WriteStackPPC(breakpad_thread_state_data_t state,
MDMemoryDescriptor *stack_location);
bool WriteContextPPC(breakpad_thread_state_data_t state,
MDLocationDescriptor *register_location);
u_int64_t CurrentPCForStackPPC(breakpad_thread_state_data_t state);
uint64_t CurrentPCForStackPPC(breakpad_thread_state_data_t state);
bool WriteStackPPC64(breakpad_thread_state_data_t state,
MDMemoryDescriptor *stack_location);
bool WriteContextPPC64(breakpad_thread_state_data_t state,
MDLocationDescriptor *register_location);
u_int64_t CurrentPCForStackPPC64(breakpad_thread_state_data_t state);
uint64_t CurrentPCForStackPPC64(breakpad_thread_state_data_t state);
#endif
#ifdef HAS_X86_SUPPORT
bool WriteStackX86(breakpad_thread_state_data_t state,
MDMemoryDescriptor *stack_location);
bool WriteContextX86(breakpad_thread_state_data_t state,
MDLocationDescriptor *register_location);
u_int64_t CurrentPCForStackX86(breakpad_thread_state_data_t state);
uint64_t CurrentPCForStackX86(breakpad_thread_state_data_t state);
bool WriteStackX86_64(breakpad_thread_state_data_t state,
MDMemoryDescriptor *stack_location);
bool WriteContextX86_64(breakpad_thread_state_data_t state,
MDLocationDescriptor *register_location);
u_int64_t CurrentPCForStackX86_64(breakpad_thread_state_data_t state);
uint64_t CurrentPCForStackX86_64(breakpad_thread_state_data_t state);
#endif
// disallow copy ctor and operator=
@@ -199,6 +214,9 @@ class MinidumpGenerator {
static int os_minor_version_;
static int os_build_number_;
// Context of the task to dump.
breakpad_ucontext_t *task_context_;
// Information about dynamically loaded code
DynamicImages *dynamic_images_;

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2010, Google Inc.
// Copyright 2013 Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
@@ -27,22 +27,21 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// The Android NDK doesn't have link.h. Fortunately, the only thing
// that Breakpad uses from it is the ElfW macro, so define it here.
#ifndef CLIENT_MAC_HANDLER_UCONTEXT_COMPAT_H_
#define CLIENT_MAC_HANDLER_UCONTEXT_COMPAT_H_
#ifndef GOOGLE_BREAKPAD_CLIENT_LINUX_ANDROID_LINK_H_
#define GOOGLE_BREAKPAD_CLIENT_LINUX_ANDROID_LINK_H_
#include <sys/ucontext.h>
// TODO(zhenghao): exec_elf.h conflicts with linux/elf.h.
// But we still need ELFSIZE.
//#include <sys/exec_elf.h>
#include <machine/exec.h>
#define ELFSIZE ARCH_ELFSIZE
// The purpose of this file is to work around the fact that ucontext_t's
// uc_mcontext member is an mcontext_t rather than an mcontext64_t on ARM64.
#if defined(__arm64__)
// <sys/ucontext.h> doesn't include the below file.
#include <sys/_types/_ucontext64.h>
typedef ucontext64_t breakpad_ucontext_t;
#define breakpad_uc_mcontext uc_mcontext64
#else
typedef ucontext_t breakpad_ucontext_t;
#define breakpad_uc_mcontext uc_mcontext
#endif // defined(__arm64__)
#ifndef ElfW
#define ElfW(type) _ElfW (Elf, ELFSIZE, type)
#define _ElfW(e,w,t) _ElfW_1 (e, w, _##t)
#define _ElfW_1(e,w,t) e##w##t
#endif
#endif // GOOGLE_BREAKPAD_CLIENT_LINUX_ANDROID_LINK_H_
#endif // CLIENT_MAC_HANDLER_UCONTEXT_COMPAT_H_

View File

@@ -265,11 +265,13 @@ const int kEmailMaxLength = 64;
}
} else {
// Create an alert panel to tell the user something happened
NSPanel* alert = NSGetAlertPanel([self shortDialogMessage],
[self explanatoryDialogText],
NSLocalizedString(@"sendReportButton", @""),
NSLocalizedString(@"cancelButton", @""),
nil);
NSPanel* alert =
NSGetAlertPanel([self shortDialogMessage],
@"%@",
NSLocalizedString(@"sendReportButton", @""),
NSLocalizedString(@"cancelButton", @""),
nil,
[self explanatoryDialogText]);
// Pop the alert with an automatic timeout, and wait for the response
buttonPressed = [self runModalWindow:alert withTimeout:timeout];
@@ -553,13 +555,13 @@ doCommandBySelector:(SEL)commandSelector {
displayName = [[uploader_ parameters] objectForKey:@BREAKPAD_PRODUCT];
if ([self isOnDemand]) {
return [NSString
stringWithFormat:NSLocalizedString(@"noCrashDialogHeader", @""),
displayName];
// Local variable to pacify clang's -Wformat-extra-args.
NSString* format = NSLocalizedString(@"noCrashDialogHeader", @"");
return [NSString stringWithFormat:format, displayName];
} else {
return [NSString
stringWithFormat:NSLocalizedString(@"crashDialogHeader", @""),
displayName];
// Local variable to pacify clang's -Wformat-extra-args.
NSString* format = NSLocalizedString(@"crashDialogHeader", @"");
return [NSString stringWithFormat:format, displayName];
}
}
@@ -574,13 +576,13 @@ doCommandBySelector:(SEL)commandSelector {
vendor = @"unknown vendor";
if ([self isOnDemand]) {
return [NSString
stringWithFormat:NSLocalizedString(@"noCrashDialogMsg", @""),
vendor, displayName];
// Local variable to pacify clang's -Wformat-extra-args.
NSString* format = NSLocalizedString(@"noCrashDialogMsg", @"");
return [NSString stringWithFormat:format, vendor, displayName];
} else {
return [NSString
stringWithFormat:NSLocalizedString(@"crashDialogMsg", @""),
vendor];
// Local variable to pacify clang's -Wformat-extra-args.
NSString* format = NSLocalizedString(@"crashDialogMsg", @"");
return [NSString stringWithFormat:format, vendor];
}
}

View File

@@ -28,7 +28,6 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <fcntl.h>
#import <pwd.h>
#import <sys/stat.h>
#include <TargetConditionals.h>
#import <unistd.h>

View File

@@ -1,243 +0,0 @@
// Copyright (c) 2008, 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.
#import "SimpleStringDictionaryTest.h"
#import "SimpleStringDictionary.h"
using google_breakpad::KeyValueEntry;
using google_breakpad::SimpleStringDictionary;
using google_breakpad::SimpleStringDictionaryIterator;
@implementation SimpleStringDictionaryTest
//==============================================================================
- (void)testKeyValueEntry {
KeyValueEntry entry;
// Verify that initial state is correct
STAssertFalse(entry.IsActive(), @"Initial key value entry is active!");
STAssertEquals(strlen(entry.GetKey()), (size_t)0, @"Empty key value did not "
@"have length 0");
STAssertEquals(strlen(entry.GetValue()), (size_t)0, @"Empty key value did not "
@"have length 0");
// Try setting a key/value and then verify
entry.SetKeyValue("key1", "value1");
STAssertEqualCStrings(entry.GetKey(), "key1", @"key was not equal to key1");
STAssertEqualCStrings(entry.GetValue(), "value1", @"value was not equal");
// Try setting a new value
entry.SetValue("value3");
// Make sure the new value took
STAssertEqualCStrings(entry.GetValue(), "value3", @"value was not equal");
// Make sure the key didn't change
STAssertEqualCStrings(entry.GetKey(), "key1", @"key changed after setting "
@"value!");
// Try setting a new key/value and then verify
entry.SetKeyValue("key2", "value2");
STAssertEqualCStrings(entry.GetKey(), "key2", @"New key was not equal to "
@"key2");
STAssertEqualCStrings(entry.GetValue(), "value2", @"New value was not equal "
@"to value2");
// Clear the entry and verify the key and value are empty strings
entry.Clear();
STAssertFalse(entry.IsActive(), @"Key value clear did not clear object");
STAssertEquals(strlen(entry.GetKey()), (size_t)0, @"Length of cleared key "
@"was not 0");
STAssertEquals(strlen(entry.GetValue()), (size_t)0, @"Length of cleared "
@"value was not 0!");
}
- (void)testEmptyKeyValueCombos {
KeyValueEntry entry;
entry.SetKeyValue(NULL, NULL);
STAssertEqualCStrings(entry.GetKey(), "", @"Setting NULL key did not return "
@"empty key!");
STAssertEqualCStrings(entry.GetValue(), "", @"Setting NULL value did not "
@"set empty string value!");
}
//==============================================================================
- (void)testSimpleStringDictionary {
// Make a new dictionary
SimpleStringDictionary *dict = new SimpleStringDictionary();
STAssertTrue(dict != NULL, nil);
// try passing in NULL for key
//dict->SetKeyValue(NULL, "bad"); // causes assert() to fire
// Set three distinct values on three keys
dict->SetKeyValue("key1", "value1");
dict->SetKeyValue("key2", "value2");
dict->SetKeyValue("key3", "value3");
STAssertTrue(!strcmp(dict->GetValueForKey("key1"), "value1"), nil);
STAssertTrue(!strcmp(dict->GetValueForKey("key2"), "value2"), nil);
STAssertTrue(!strcmp(dict->GetValueForKey("key3"), "value3"), nil);
STAssertEquals(dict->GetCount(), 3, @"GetCount did not return 3");
// try an unknown key
STAssertTrue(dict->GetValueForKey("key4") == NULL, nil);
// try a NULL key
//STAssertTrue(dict->GetValueForKey(NULL) == NULL, nil); // asserts
// Remove a key
dict->RemoveKey("key3");
// Now make sure it's not there anymore
STAssertTrue(dict->GetValueForKey("key3") == NULL, nil);
// Remove a NULL key
//dict->RemoveKey(NULL); // will cause assert() to fire
// Remove by setting value to NULL
dict->SetKeyValue("key2", NULL);
// Now make sure it's not there anymore
STAssertTrue(dict->GetValueForKey("key2") == NULL, nil);
}
//==============================================================================
// The idea behind this test is to add a bunch of values to the dictionary,
// remove some in the middle, then add a few more in. We then create a
// SimpleStringDictionaryIterator and iterate through the dictionary, taking
// note of the key/value pairs we see. We then verify that it iterates
// through exactly the number of key/value pairs we expect, and that they
// match one-for-one with what we would expect. In all cases we're setting
// key value pairs of the form:
//
// key<n>/value<n> (like key0/value0, key17,value17, etc.)
//
- (void)testSimpleStringDictionaryIterator {
SimpleStringDictionary *dict = new SimpleStringDictionary();
STAssertTrue(dict != NULL, nil);
char key[KeyValueEntry::MAX_STRING_STORAGE_SIZE];
char value[KeyValueEntry::MAX_STRING_STORAGE_SIZE];
const int kDictionaryCapacity = SimpleStringDictionary::MAX_NUM_ENTRIES;
const int kPartitionIndex = kDictionaryCapacity - 5;
// We assume at least this size in the tests below
STAssertTrue(kDictionaryCapacity >= 64, nil);
// We'll keep track of the number of key/value pairs we think should
// be in the dictionary
int expectedDictionarySize = 0;
// Set a bunch of key/value pairs like key0/value0, key1/value1, ...
for (int i = 0; i < kPartitionIndex; ++i) {
sprintf(key, "key%d", i);
sprintf(value, "value%d", i);
dict->SetKeyValue(key, value);
}
expectedDictionarySize = kPartitionIndex;
// set a couple of the keys twice (with the same value) - should be nop
dict->SetKeyValue("key2", "value2");
dict->SetKeyValue("key4", "value4");
dict->SetKeyValue("key15", "value15");
// Remove some random elements in the middle
dict->RemoveKey("key7");
dict->RemoveKey("key18");
dict->RemoveKey("key23");
dict->RemoveKey("key31");
expectedDictionarySize -= 4; // we just removed four key/value pairs
// Set some more key/value pairs like key59/value59, key60/value60, ...
for (int i = kPartitionIndex; i < kDictionaryCapacity; ++i) {
sprintf(key, "key%d", i);
sprintf(value, "value%d", i);
dict->SetKeyValue(key, value);
}
expectedDictionarySize += kDictionaryCapacity - kPartitionIndex;
// Now create an iterator on the dictionary
SimpleStringDictionaryIterator iter(*dict);
// We then verify that it iterates through exactly the number of
// key/value pairs we expect, and that they match one-for-one with what we
// would expect. The ordering of the iteration does not matter...
// used to keep track of number of occurrences found for key/value pairs
int count[kDictionaryCapacity];
memset(count, 0, sizeof(count));
int totalCount = 0;
const KeyValueEntry *entry;
while ((entry = iter.Next())) {
totalCount++;
// Extract keyNumber from a string of the form key<keyNumber>
int keyNumber;
sscanf(entry->GetKey(), "key%d", &keyNumber);
// Extract valueNumber from a string of the form value<valueNumber>
int valueNumber;
sscanf(entry->GetValue(), "value%d", &valueNumber);
// The value number should equal the key number since that's how we set them
STAssertTrue(keyNumber == valueNumber, nil);
// Key and value numbers should be in proper range:
// 0 <= keyNumber < kDictionaryCapacity
bool isKeyInGoodRange =
(keyNumber >= 0 && keyNumber < kDictionaryCapacity);
bool isValueInGoodRange =
(valueNumber >= 0 && valueNumber < kDictionaryCapacity);
STAssertTrue(isKeyInGoodRange, nil);
STAssertTrue(isValueInGoodRange, nil);
if (isKeyInGoodRange && isValueInGoodRange) {
++count[keyNumber];
}
}
// Make sure each of the key/value pairs showed up exactly one time, except
// for the ones which we removed.
for (int i = 0; i < kDictionaryCapacity; ++i) {
// Skip over key7, key18, key23, and key31, since we removed them
if (!(i == 7 || i == 18 || i == 23 || i == 31)) {
STAssertTrue(count[i] == 1, nil);
}
}
// Make sure the number of iterations matches the expected dictionary size.
STAssertTrue(totalCount == expectedDictionarySize, nil);
}
@end

View File

@@ -84,12 +84,14 @@ public:
AutoTempDir temp_dir;
// Counter just to ensure that we don't hit the same port again
static int i;
bool filter_callback_called;
void SetUp() {
sprintf(mach_port_name,
"com.google.breakpad.ServerTest.%d.%d", getpid(),
CrashGenerationServerTest::i++);
"com.google.breakpad.ServerTest.%d.%d", getpid(),
CrashGenerationServerTest::i++);
child_pid = (pid_t)-1;
filter_callback_called = false;
}
};
int CrashGenerationServerTest::i = 0;
@@ -97,12 +99,14 @@ int CrashGenerationServerTest::i = 0;
// Test that starting and stopping a server works
TEST_F(CrashGenerationServerTest, testStartStopServer) {
CrashGenerationServer server(mach_port_name,
NULL, // dump callback
NULL, // dump context
NULL, // exit callback
NULL, // exit context
false, // generate dumps
""); // dump path
NULL, // filter callback
NULL, // filter context
NULL, // dump callback
NULL, // dump context
NULL, // exit callback
NULL, // exit context
false, // generate dumps
""); // dump path
ASSERT_TRUE(server.Start());
ASSERT_TRUE(server.Stop());
}
@@ -111,6 +115,8 @@ TEST_F(CrashGenerationServerTest, testStartStopServer) {
// Test without actually dumping
TEST_F(CrashGenerationServerTest, testRequestDumpNoDump) {
CrashGenerationServer server(mach_port_name,
NULL, // filter callback
NULL, // filter context
NULL, // dump callback
NULL, // dump context
NULL, // exit callback
@@ -142,7 +148,7 @@ TEST_F(CrashGenerationServerTest, testRequestDumpNoDump) {
}
void dumpCallback(void *context, const ClientInfo &client_info,
const std::string &file_path) {
const std::string &file_path) {
if (context) {
CrashGenerationServerTest* self =
reinterpret_cast<CrashGenerationServerTest*>(context);
@@ -161,6 +167,8 @@ void *RequestDump(void *context) {
// Test that actually writing a minidump works
TEST_F(CrashGenerationServerTest, testRequestDump) {
CrashGenerationServer server(mach_port_name,
NULL, // filter callback
NULL, // filter context
dumpCallback, // dump callback
this, // dump context
NULL, // exit callback
@@ -209,6 +217,8 @@ static void Crasher() {
// the parent.
TEST_F(CrashGenerationServerTest, testChildProcessCrash) {
CrashGenerationServer server(mach_port_name,
NULL, // filter callback
NULL, // filter context
dumpCallback, // dump callback
this, // dump context
NULL, // exit callback
@@ -270,6 +280,8 @@ TEST_F(CrashGenerationServerTest, testChildProcessCrash) {
// produces a valid minidump.
TEST_F(CrashGenerationServerTest, testChildProcessCrashCrossArchitecture) {
CrashGenerationServer server(mach_port_name,
NULL, // filter callback
NULL, // filter context
dumpCallback, // dump callback
this, // dump context
NULL, // exit callback
@@ -306,7 +318,7 @@ const MDCPUArchitecture kExpectedArchitecture =
MD_CPU_ARCHITECTURE_AMD64
#endif
;
const u_int32_t kExpectedContext =
const uint32_t kExpectedContext =
#if defined(__i386__)
MD_CONTEXT_AMD64
#elif defined(__x86_64__)
@@ -342,4 +354,45 @@ const u_int32_t kExpectedContext =
}
#endif
bool filter_callback(void* context) {
CrashGenerationServerTest* self =
reinterpret_cast<CrashGenerationServerTest*>(context);
self->filter_callback_called = true;
// veto dump generation
return false;
}
// Test that a filter callback can veto minidump writing.
TEST_F(CrashGenerationServerTest, testFilter) {
CrashGenerationServer server(mach_port_name,
filter_callback, // filter callback
this, // filter context
dumpCallback, // dump callback
this, // dump context
NULL, // exit callback
NULL, // exit context
true, // generate dumps
temp_dir.path()); // dump path
ASSERT_TRUE(server.Start());
pid_t pid = fork();
ASSERT_NE(-1, pid);
if (pid == 0) {
// Instantiate an OOP exception handler.
ExceptionHandler eh("", NULL, NULL, NULL, true, mach_port_name);
Crasher();
// not reached
exit(0);
}
int ret;
ASSERT_EQ(pid, waitpid(pid, &ret, 0));
EXPECT_FALSE(WIFEXITED(ret));
EXPECT_TRUE(server.Stop());
// check that no minidump was written
EXPECT_TRUE(last_dump_name.empty());
EXPECT_TRUE(filter_callback_called);
}
} // namespace

View File

@@ -122,10 +122,46 @@ void ExceptionHandlerTest::InProcessCrash(bool aborting) {
char minidump_file[PATH_MAX];
ssize_t nbytes = read(fds[0], minidump_file, sizeof(minidump_file));
ASSERT_NE(0, nbytes);
// Ensure that minidump file exists and is > 0 bytes.
struct stat st;
ASSERT_EQ(0, stat(minidump_file, &st));
ASSERT_LT(0, st.st_size);
Minidump minidump(minidump_file);
ASSERT_TRUE(minidump.Read());
MinidumpException* exception = minidump.GetException();
ASSERT_TRUE(exception);
const MDRawExceptionStream* raw_exception = exception->exception();
ASSERT_TRUE(raw_exception);
if (aborting) {
EXPECT_EQ(MD_EXCEPTION_MAC_SOFTWARE,
raw_exception->exception_record.exception_code);
EXPECT_EQ(MD_EXCEPTION_CODE_MAC_ABORT,
raw_exception->exception_record.exception_flags);
} else {
EXPECT_EQ(MD_EXCEPTION_MAC_BAD_ACCESS,
raw_exception->exception_record.exception_code);
#if defined(__x86_64__)
EXPECT_EQ(MD_EXCEPTION_CODE_MAC_INVALID_ADDRESS,
raw_exception->exception_record.exception_flags);
#elif defined(__i386__)
EXPECT_EQ(MD_EXCEPTION_CODE_MAC_PROTECTION_FAILURE,
raw_exception->exception_record.exception_flags);
#endif
}
const MinidumpContext* context = exception->GetContext();
ASSERT_TRUE(context);
uint64_t instruction_pointer;
ASSERT_TRUE(context->GetInstructionPointer(&instruction_pointer));
// Ideally would like to sanity check that abort() is on the stack
// but that's hard.
MinidumpMemoryList* memory_list = minidump.GetMemoryList();
ASSERT_TRUE(memory_list);
MinidumpMemoryRegion* region =
memory_list->GetMemoryRegionForAddress(instruction_pointer);
EXPECT_TRUE(region);
// Child process should have exited with a zero status.
int ret;
@@ -138,11 +174,9 @@ TEST_F(ExceptionHandlerTest, InProcess) {
InProcessCrash(false);
}
#if TARGET_OS_IPHONE
TEST_F(ExceptionHandlerTest, InProcessAbort) {
InProcessCrash(true);
}
#endif
static bool DumpNameMDCallback(const char *dump_dir, const char *file_name,
void *context, bool success) {
@@ -277,7 +311,7 @@ TEST_F(ExceptionHandlerTest, InstructionPointerMemory) {
// These are defined here so the parent can use them to check the
// data from the minidump afterwards.
const u_int32_t kMemorySize = 256; // bytes
const uint32_t kMemorySize = 256; // bytes
const int kOffset = kMemorySize / 2;
// This crashes with SIGILL on x86/x86-64/arm.
const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff };
@@ -346,32 +380,19 @@ TEST_F(ExceptionHandlerTest, InstructionPointerMemory) {
MinidumpContext* context = exception->GetContext();
ASSERT_TRUE(context);
u_int64_t instruction_pointer;
switch (context->GetContextCPU()) {
case MD_CONTEXT_X86:
instruction_pointer = context->GetContextX86()->eip;
break;
case MD_CONTEXT_AMD64:
instruction_pointer = context->GetContextAMD64()->rip;
break;
case MD_CONTEXT_ARM:
instruction_pointer = context->GetContextARM()->iregs[15];
break;
default:
FAIL() << "Unknown context CPU: " << context->GetContextCPU();
break;
}
uint64_t instruction_pointer;
ASSERT_TRUE(context->GetInstructionPointer(&instruction_pointer));
MinidumpMemoryRegion* region =
memory_list->GetMemoryRegionForAddress(instruction_pointer);
EXPECT_TRUE(region);
EXPECT_EQ(kMemorySize, region->GetSize());
const u_int8_t* bytes = region->GetMemory();
const uint8_t* bytes = region->GetMemory();
ASSERT_TRUE(bytes);
u_int8_t prefix_bytes[kOffset];
u_int8_t suffix_bytes[kMemorySize - kOffset - sizeof(instructions)];
uint8_t prefix_bytes[kOffset];
uint8_t suffix_bytes[kMemorySize - kOffset - sizeof(instructions)];
memset(prefix_bytes, 0, sizeof(prefix_bytes));
memset(suffix_bytes, 0, sizeof(suffix_bytes));
EXPECT_TRUE(memcmp(bytes, prefix_bytes, sizeof(prefix_bytes)) == 0);
@@ -389,7 +410,7 @@ TEST_F(ExceptionHandlerTest, InstructionPointerMemoryMinBound) {
// These are defined here so the parent can use them to check the
// data from the minidump afterwards.
const u_int32_t kMemorySize = 256; // bytes
const uint32_t kMemorySize = 256; // bytes
const int kOffset = 0;
// This crashes with SIGILL on x86/x86-64/arm.
const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff };
@@ -458,31 +479,18 @@ TEST_F(ExceptionHandlerTest, InstructionPointerMemoryMinBound) {
MinidumpContext* context = exception->GetContext();
ASSERT_TRUE(context);
u_int64_t instruction_pointer;
switch (context->GetContextCPU()) {
case MD_CONTEXT_X86:
instruction_pointer = context->GetContextX86()->eip;
break;
case MD_CONTEXT_AMD64:
instruction_pointer = context->GetContextAMD64()->rip;
break;
case MD_CONTEXT_ARM:
instruction_pointer = context->GetContextARM()->iregs[15];
break;
default:
FAIL() << "Unknown context CPU: " << context->GetContextCPU();
break;
}
uint64_t instruction_pointer;
ASSERT_TRUE(context->GetInstructionPointer(&instruction_pointer));
MinidumpMemoryRegion* region =
memory_list->GetMemoryRegionForAddress(instruction_pointer);
EXPECT_TRUE(region);
EXPECT_EQ(kMemorySize / 2, region->GetSize());
const u_int8_t* bytes = region->GetMemory();
const uint8_t* bytes = region->GetMemory();
ASSERT_TRUE(bytes);
u_int8_t suffix_bytes[kMemorySize / 2 - sizeof(instructions)];
uint8_t suffix_bytes[kMemorySize / 2 - sizeof(instructions)];
memset(suffix_bytes, 0, sizeof(suffix_bytes));
EXPECT_TRUE(memcmp(bytes + kOffset, instructions, sizeof(instructions)) == 0);
EXPECT_TRUE(memcmp(bytes + kOffset + sizeof(instructions),
@@ -501,7 +509,7 @@ TEST_F(ExceptionHandlerTest, InstructionPointerMemoryMaxBound) {
// Use 4k here because the OS will hand out a single page even
// if a smaller size is requested, and this test wants to
// test the upper bound of the memory range.
const u_int32_t kMemorySize = 4096; // bytes
const uint32_t kMemorySize = 4096; // bytes
// This crashes with SIGILL on x86/x86-64/arm.
const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff };
const int kOffset = kMemorySize - sizeof(instructions);
@@ -570,21 +578,8 @@ TEST_F(ExceptionHandlerTest, InstructionPointerMemoryMaxBound) {
MinidumpContext* context = exception->GetContext();
ASSERT_TRUE(context);
u_int64_t instruction_pointer;
switch (context->GetContextCPU()) {
case MD_CONTEXT_X86:
instruction_pointer = context->GetContextX86()->eip;
break;
case MD_CONTEXT_AMD64:
instruction_pointer = context->GetContextAMD64()->rip;
break;
case MD_CONTEXT_ARM:
instruction_pointer = context->GetContextARM()->iregs[15];
break;
default:
FAIL() << "Unknown context CPU: " << context->GetContextCPU();
break;
}
uint64_t instruction_pointer;
ASSERT_TRUE(context->GetInstructionPointer(&instruction_pointer));
MinidumpMemoryRegion* region =
memory_list->GetMemoryRegionForAddress(instruction_pointer);
@@ -592,10 +587,10 @@ TEST_F(ExceptionHandlerTest, InstructionPointerMemoryMaxBound) {
const size_t kPrefixSize = 128; // bytes
EXPECT_EQ(kPrefixSize + sizeof(instructions), region->GetSize());
const u_int8_t* bytes = region->GetMemory();
const uint8_t* bytes = region->GetMemory();
ASSERT_TRUE(bytes);
u_int8_t prefix_bytes[kPrefixSize];
uint8_t prefix_bytes[kPrefixSize];
memset(prefix_bytes, 0, sizeof(prefix_bytes));
EXPECT_TRUE(memcmp(bytes, prefix_bytes, sizeof(prefix_bytes)) == 0);
EXPECT_TRUE(memcmp(bytes + kPrefixSize,

View File

@@ -280,7 +280,7 @@ const MDCPUArchitecture kExpectedArchitecture =
MD_CPU_ARCHITECTURE_AMD64
#endif
;
const u_int32_t kExpectedContext =
const uint32_t kExpectedContext =
#if defined(__i386__)
MD_CONTEXT_AMD64
#elif defined(__x86_64__)

View File

@@ -65,7 +65,7 @@ const MDCPUArchitecture kNativeArchitecture =
#endif
;
const u_int32_t kNativeContext =
const uint32_t kNativeContext =
#if defined(__i386__)
MD_CONTEXT_X86
#elif defined(__x86_64__)

View File

@@ -48,11 +48,16 @@ namespace google_breakpad {
const MDRVA MinidumpFileWriter::kInvalidMDRVA = static_cast<MDRVA>(-1);
MinidumpFileWriter::MinidumpFileWriter() : file_(-1), position_(0), size_(0) {
MinidumpFileWriter::MinidumpFileWriter()
: file_(-1),
close_file_when_destroyed_(true),
position_(0),
size_(0) {
}
MinidumpFileWriter::~MinidumpFileWriter() {
Close();
if (close_file_when_destroyed_)
Close();
}
bool MinidumpFileWriter::Open(const char *path) {
@@ -66,6 +71,12 @@ bool MinidumpFileWriter::Open(const char *path) {
return file_ != -1;
}
void MinidumpFileWriter::SetFile(const int file) {
assert(file_ == -1);
file_ = file;
close_file_when_destroyed_ = false;
}
bool MinidumpFileWriter::Close() {
bool result = true;
@@ -88,11 +99,11 @@ bool MinidumpFileWriter::CopyStringToMDString(const wchar_t *str,
unsigned int length,
TypedMDRVA<MDString> *mdstring) {
bool result = true;
if (sizeof(wchar_t) == sizeof(u_int16_t)) {
if (sizeof(wchar_t) == sizeof(uint16_t)) {
// Shortcut if wchar_t is the same size as MDString's buffer
result = mdstring->Copy(str, mdstring->get()->length);
} else {
u_int16_t out[2];
uint16_t out[2];
int out_idx = 0;
// Copy the string character by character
@@ -109,7 +120,7 @@ bool MinidumpFileWriter::CopyStringToMDString(const wchar_t *str,
// zero, but the second one may be zero, depending on the conversion from
// UTF-32.
int out_count = out[1] ? 2 : 1;
size_t out_size = sizeof(u_int16_t) * out_count;
size_t out_size = sizeof(uint16_t) * out_count;
result = mdstring->CopyIndexAfterObject(out_idx, out, out_size);
out_idx += out_count;
}
@@ -121,7 +132,7 @@ bool MinidumpFileWriter::CopyStringToMDString(const char *str,
unsigned int length,
TypedMDRVA<MDString> *mdstring) {
bool result = true;
u_int16_t out[2];
uint16_t out[2];
int out_idx = 0;
// Copy the string character by character
@@ -136,7 +147,7 @@ bool MinidumpFileWriter::CopyStringToMDString(const char *str,
// Append the one or two UTF-16 characters
int out_count = out[1] ? 2 : 1;
size_t out_size = sizeof(u_int16_t) * out_count;
size_t out_size = sizeof(uint16_t) * out_count;
result = mdstring->CopyIndexAfterObject(out_idx, out, out_size);
out_idx += out_count;
}
@@ -159,17 +170,17 @@ bool MinidumpFileWriter::WriteStringCore(const CharType *str,
// Allocate the string buffer
TypedMDRVA<MDString> mdstring(this);
if (!mdstring.AllocateObjectAndArray(mdstring_length + 1, sizeof(u_int16_t)))
if (!mdstring.AllocateObjectAndArray(mdstring_length + 1, sizeof(uint16_t)))
return false;
// Set length excluding the NULL and copy the string
mdstring.get()->length =
static_cast<u_int32_t>(mdstring_length * sizeof(u_int16_t));
static_cast<uint32_t>(mdstring_length * sizeof(uint16_t));
bool result = CopyStringToMDString(str, mdstring_length, &mdstring);
// NULL terminate
if (result) {
u_int16_t ch = 0;
uint16_t ch = 0;
result = mdstring.CopyIndexAfterObject(mdstring_length, &ch, sizeof(ch));
if (result)
@@ -200,7 +211,7 @@ bool MinidumpFileWriter::WriteMemory(const void *src, size_t size,
if (!mem.Copy(src, mem.size()))
return false;
output->start_of_memory_range = reinterpret_cast<u_int64_t>(src);
output->start_of_memory_range = reinterpret_cast<uint64_t>(src);
output->memory = mem.location();
return true;

View File

@@ -55,6 +55,16 @@ template<typename MDType> class TypedMDRVA;
// header->get()->signature = MD_HEADER_SIGNATURE;
// :
// writer.Close();
//
// An alternative is to use SetFile and provide a file descriptor:
// MinidumpFileWriter writer;
// writer.SetFile(minidump_fd);
// TypedMDRVA<MDRawHeader> header(&writer_);
// header.Allocate();
// header->get()->signature = MD_HEADER_SIGNATURE;
// :
// writer.Close();
class MinidumpFileWriter {
public:
// Invalid MDRVA (Minidump Relative Virtual Address)
@@ -66,11 +76,19 @@ public:
// Open |path| as the destination of the minidump data. Any existing file
// will be overwritten.
// Return true on success, or false on failure
// Return true on success, or false on failure.
bool Open(const char *path);
// Close the current file
// Return true on success, or false on failure
// Sets the file descriptor |file| as the destination of the minidump data.
// Can be used as an alternative to Open() when a file descriptor is
// available.
// Note that |fd| is not closed when the instance of MinidumpFileWriter is
// destroyed.
void SetFile(const int file);
// Close the current file (that was either created when Open was called, or
// specified with SetFile).
// Return true on success, or false on failure.
bool Close();
// Copy the contents of |str| to a MDString and write it to the file.
@@ -106,9 +124,12 @@ public:
// unable to allocate the bytes.
MDRVA Allocate(size_t size);
// The file descriptor for the output file
// The file descriptor for the output file.
int file_;
// Whether |file_| should be closed when the instance is destroyed.
bool close_file_when_destroyed_;
// Current position in buffer
MDRVA position_;
@@ -151,7 +172,7 @@ class UntypedMDRVA {
// Return size and position
inline MDLocationDescriptor location() const {
MDLocationDescriptor location = { static_cast<u_int32_t>(size_),
MDLocationDescriptor location = { static_cast<uint32_t>(size_),
position_ };
return location;
}

View File

@@ -86,7 +86,7 @@ static bool WriteFile(const char *path) {
google_breakpad::TypedMDRVA<ArrayStructure> array(&writer);
unsigned int count = 10;
ASSERT_TRUE(array.AllocateArray(count));
for (unsigned int i = 0; i < count; ++i) {
for (unsigned char i = 0; i < count; ++i) {
ArrayStructure local;
local.char_value = i;
local.short_value = i + 1;
@@ -99,7 +99,7 @@ static bool WriteFile(const char *path) {
ASSERT_TRUE(obj_array.AllocateObjectAndArray(count,
sizeof(ArrayStructure)));
obj_array.get()->count = count;
for (unsigned int i = 0; i < count; ++i) {
for (unsigned char i = 0; i < count; ++i) {
ArrayStructure local;
local.char_value = i;
local.short_value = i + 1;

View File

@@ -455,7 +455,7 @@ bool WriteCVRecord(MinidumpFileWriter *minidump_writer,
snprintf(path, sizeof(path), "/proc/self/object/%s", module_name);
size_t module_name_length = strlen(realname);
if (!cv.AllocateObjectAndArray(module_name_length + 1, sizeof(u_int8_t)))
if (!cv.AllocateObjectAndArray(module_name_length + 1, sizeof(uint8_t)))
return false;
if (!cv.CopyIndexAfterObject(0, realname, module_name_length))
return false;
@@ -522,7 +522,7 @@ bool ModuleInfoCallback(const ModuleInfo &module_info, void *context) {
if (!callback_context->minidump_writer->WriteString(realname, 0, &loc))
return false;
module.base_of_image = (u_int64_t)module_info.start_addr;
module.base_of_image = (uint64_t)module_info.start_addr;
module.size_of_image = module_info.size;
module.module_name_rva = loc.rva;

View File

@@ -123,10 +123,10 @@
# Python version.
'python_ver%': '2.5',
# Set ARM-v7 compilation flags
'armv7%': 0,
# Determine ARM compilation flags.
'arm_version%': 7,
# Set Neon compilation flags (only meaningful if armv7==1).
# Set Neon compilation flags (only meaningful if arm_version==7).
'arm_neon%': 1,
# The system root for cross-compiles. Default: none.
@@ -148,7 +148,7 @@
'fastbuild%': '<(fastbuild)',
'linux_fpic%': '<(linux_fpic)',
'python_ver%': '<(python_ver)',
'armv7%': '<(armv7)',
'arm_version%': '<(arm_version)',
'arm_neon%': '<(arm_neon)',
'sysroot%': '<(sysroot)',
'disable_sse2%': '<(disable_sse2)',
@@ -199,14 +199,6 @@
# to ~/.gyp/include.gypi, gclient runhooks --force, and do a release build.
'win_use_allocator_shim%': 1, # 0 = shim allocator via libcmt; 1 = msvcrt
# To do a shared build on linux we need to be able to choose between type
# static_library and shared_library. We default to doing a static build
# but you can override this with "gyp -Dlibrary=shared_library" or you
# can add the following line (without the #) to ~/.gyp/include.gypi
# {'variables': {'library': 'shared_library'}}
# to compile as shared by default
'library%': 'static_library',
# Whether usage of OpenMAX is enabled.
'enable_openmax%': 0,
@@ -262,7 +254,7 @@
# Set Thumb compilation flags.
'arm_thumb%': 0,
# Set ARM fpu compilation flags (only meaningful if armv7==1 and
# Set ARM fpu compilation flags (only meaningful if arm_version==7 and
# arm_neon==0).
'arm_fpu%': 'vfpv3',
@@ -329,9 +321,9 @@
# Whether to use multiple cores to compile with visual studio. This is
# optional because it sometimes causes corruption on VS 2005.
# It is on by default on VS 2008 and off on VS 2005.
['OS=="win"', {
['"<(GENERATOR)" == "msvs"', {
'conditions': [
['MSVS_VERSION=="2005"', {
[ 'MSVS_VERSION=="2005"', {
'msvs_multi_core_compile%': 0,
},{
'msvs_multi_core_compile%': 1,
@@ -348,6 +340,10 @@
# Native Client loader for 64-bit Windows.
'NACL_WIN64',
],
},
{
# XXX: because value is used below...
'msvs_multi_core_compile%': 0,
}],
# Compute based on OS and target architecture whether the GPU
# plugin / process is supported.
@@ -511,12 +507,11 @@
'_CRT_SECURE_NO_DEPRECATE',
'_CRT_NONSTDC_NO_WARNINGS',
'_CRT_NONSTDC_NO_DEPRECATE',
'_SCL_SECURE_NO_DEPRECATE',
],
'msvs_disabled_warnings': [4800],
'msvs_settings': {
'VCCLCompilerTool': {
'WarnAsError': 'false',
'WarnAsError': 'true',
'Detect64BitPortabilityProblems': 'false',
},
},
@@ -832,7 +827,7 @@
'IMPLICIT_COMMAND_DEPENDENCIES': 0,
# -rpath is only used when building with shared libraries.
'conditions': [
[ 'library=="shared_library"', {
[ 'component=="shared_library"', {
'RPATH': '$LIB_DIR',
}],
],
@@ -989,7 +984,7 @@
'-Wa,-mimplicit-it=thumb',
]
}],
['armv7==1', {
['arm_version==7', {
'cflags': [
'-march=armv7-a',
'-mtune=cortex-a8',
@@ -1183,7 +1178,7 @@
'$(VSInstallDir)/VC/atlmfc/include',
],
'msvs_cygwin_dirs': ['<(DEPTH)/third_party/cygwin'],
'msvs_disabled_warnings': [4396, 4503, 4819],
'msvs_disabled_warnings': [4100, 4127, 4396, 4503, 4512, 4702, 4819, 4995],
'msvs_settings': {
'VCCLCompilerTool': {
'MinimalRebuild': 'false',
@@ -1191,7 +1186,7 @@
'BufferSecurityCheck': 'true',
'EnableFunctionLevelLinking': 'true',
'RuntimeTypeInfo': 'false',
'WarningLevel': '3',
'WarningLevel': '4',
'WarnAsError': 'true',
'DebugInformationFormat': '3',
'conditions': [

View File

@@ -43,7 +43,6 @@
'_CRT_SECURE_NO_DEPRECATE',
'_CRT_NONSTDC_NO_WARNINGS',
'_CRT_NONSTDC_NO_DEPRECATE',
'_SCL_SECURE_NO_DEPRECATE',
],
'msvs_disabled_warnings': [4800],
'msvs_settings': {

View File

@@ -30,7 +30,7 @@
#ifndef CLIENT_WINDOWS_COMMON_AUTO_CRITICAL_SECTION_H__
#define CLIENT_WINDOWS_COMMON_AUTO_CRITICAL_SECTION_H__
#include <Windows.h>
#include <windows.h>
namespace google_breakpad {

View File

@@ -85,16 +85,38 @@ bool ClientInfo::Initialize() {
return dump_generated_handle_ != NULL;
}
ClientInfo::~ClientInfo() {
void ClientInfo::UnregisterDumpRequestWaitAndBlockUntilNoPending() {
if (dump_request_wait_handle_) {
// Wait for callbacks that might already be running to finish.
UnregisterWaitEx(dump_request_wait_handle_, INVALID_HANDLE_VALUE);
dump_request_wait_handle_ = NULL;
}
}
void ClientInfo::UnregisterProcessExitWait(bool block_until_no_pending) {
if (process_exit_wait_handle_) {
// Wait for the callback that might already be running to finish.
UnregisterWaitEx(process_exit_wait_handle_, INVALID_HANDLE_VALUE);
if (block_until_no_pending) {
// Wait for the callback that might already be running to finish.
UnregisterWaitEx(process_exit_wait_handle_, INVALID_HANDLE_VALUE);
} else {
UnregisterWait(process_exit_wait_handle_);
}
process_exit_wait_handle_ = NULL;
}
}
ClientInfo::~ClientInfo() {
// Waiting for the callback to finish here is safe because ClientInfo's are
// never destroyed from the dump request handling callback.
UnregisterDumpRequestWaitAndBlockUntilNoPending();
// This is a little tricky because ClientInfo's may be destroyed by the same
// callback (OnClientEnd) and waiting for it to finish will cause a deadlock.
// Regardless of this complication, wait for any running callbacks to finish
// so that the common case is properly handled. In order to avoid deadlocks,
// the OnClientEnd callback must call UnregisterProcessExitWait(false)
// before deleting the ClientInfo.
UnregisterProcessExitWait(true);
if (process_handle_) {
CloseHandle(process_handle_);
@@ -109,18 +131,6 @@ ClientInfo::~ClientInfo() {
}
}
void ClientInfo::UnregisterWaits() {
if (dump_request_wait_handle_) {
UnregisterWait(dump_request_wait_handle_);
dump_request_wait_handle_ = NULL;
}
if (process_exit_wait_handle_) {
UnregisterWait(process_exit_wait_handle_);
process_exit_wait_handle_ = NULL;
}
}
bool ClientInfo::GetClientExceptionInfo(EXCEPTION_POINTERS** ex_info) const {
SIZE_T bytes_count = 0;
if (!ReadProcessMemory(process_handle_,
@@ -164,7 +174,11 @@ void ClientInfo::SetProcessUptime() {
// Convert it to a string.
wchar_t* value = custom_info_entries_.get()[custom_client_info_.count].value;
#ifdef _MSC_VER
_i64tow_s(delay, value, CustomInfoEntry::kValueMaxLength, 10);
#else
_i64tow(delay, value, 10);
#endif
}
bool ClientInfo::PopulateCustomInfo() {
@@ -196,7 +210,7 @@ bool ClientInfo::PopulateCustomInfo() {
}
SetProcessUptime();
return (bytes_count != read_count);
return (bytes_count == read_count);
}
CustomClientInfo ClientInfo::GetCustomInfo() const {

View File

@@ -30,11 +30,11 @@
#ifndef CLIENT_WINDOWS_CRASH_GENERATION_CLIENT_INFO_H__
#define CLIENT_WINDOWS_CRASH_GENERATION_CLIENT_INFO_H__
#include <Windows.h>
#include <DbgHelp.h>
#include <windows.h>
#include <dbghelp.h>
#include "client/windows/common/ipc_protocol.h"
#include "common/scoped_ptr.h"
#include "google_breakpad/common/minidump_format.h"
#include "processor/scoped_ptr.h"
namespace google_breakpad {
@@ -66,25 +66,26 @@ class ClientInfo {
HANDLE dump_requested_handle() const { return dump_requested_handle_; }
HANDLE dump_generated_handle() const { return dump_generated_handle_; }
DWORD crash_id() const { return crash_id_; }
HANDLE dump_request_wait_handle() const {
return dump_request_wait_handle_;
const CustomClientInfo& custom_client_info() const {
return custom_client_info_;
}
void set_dump_request_wait_handle(HANDLE value) {
dump_request_wait_handle_ = value;
}
HANDLE process_exit_wait_handle() const {
return process_exit_wait_handle_;
}
void set_process_exit_wait_handle(HANDLE value) {
process_exit_wait_handle_ = value;
}
// Unregister all waits for the client.
void UnregisterWaits();
// Unregister the dump request wait operation and wait for all callbacks
// that might already be running to complete before returning.
void UnregisterDumpRequestWaitAndBlockUntilNoPending();
// Unregister the process exit wait operation. If block_until_no_pending is
// true, wait for all callbacks that might already be running to complete
// before returning.
void UnregisterProcessExitWait(bool block_until_no_pending);
bool Initialize();
bool GetClientExceptionInfo(EXCEPTION_POINTERS** ex_info) const;

View File

@@ -34,7 +34,7 @@
'targets': [
{
'target_name': 'crash_generation_server',
'type': '<(library)',
'type': 'static_library',
'sources': [
'client_info.cc',
'crash_generation_server.cc',
@@ -50,7 +50,7 @@
},
{
'target_name': 'crash_generation_client',
'type': '<(library)',
'type': 'static_library',
'include_dirs': [
'<(DEPTH)',
],

View File

@@ -95,6 +95,27 @@ CrashGenerationClient::CrashGenerationClient(
MINIDUMP_TYPE dump_type,
const CustomClientInfo* custom_info)
: pipe_name_(pipe_name),
pipe_handle_(NULL),
dump_type_(dump_type),
thread_id_(0),
server_process_id_(0),
crash_event_(NULL),
crash_generated_(NULL),
server_alive_(NULL),
exception_pointers_(NULL),
custom_info_() {
memset(&assert_info_, 0, sizeof(assert_info_));
if (custom_info) {
custom_info_ = *custom_info;
}
}
CrashGenerationClient::CrashGenerationClient(
HANDLE pipe_handle,
MINIDUMP_TYPE dump_type,
const CustomClientInfo* custom_info)
: pipe_name_(),
pipe_handle_(pipe_handle),
dump_type_(dump_type),
thread_id_(0),
server_process_id_(0),
@@ -157,6 +178,10 @@ CrashGenerationClient::~CrashGenerationClient() {
//
// Returns true if the registration is successful; false otherwise.
bool CrashGenerationClient::Register() {
if (IsRegistered()) {
return true;
}
HANDLE pipe = ConnectToServer();
if (!pipe) {
return false;
@@ -248,6 +273,12 @@ bool CrashGenerationClient::RegisterClient(HANDLE pipe) {
HANDLE CrashGenerationClient::ConnectToPipe(const wchar_t* pipe_name,
DWORD pipe_access,
DWORD flags_attrs) {
if (pipe_handle_) {
HANDLE t = pipe_handle_;
pipe_handle_ = NULL;
return t;
}
for (int i = 0; i < kPipeConnectMaxAttempts; ++i) {
HANDLE pipe = CreateFile(pipe_name,
pipe_access,
@@ -342,4 +373,33 @@ bool CrashGenerationClient::SignalCrashEventAndWait() {
return result == WAIT_OBJECT_0;
}
HANDLE CrashGenerationClient::DuplicatePipeToClientProcess(const wchar_t* pipe_name,
HANDLE hProcess) {
for (int i = 0; i < kPipeConnectMaxAttempts; ++i) {
HANDLE local_pipe = CreateFile(pipe_name, kPipeDesiredAccess,
0, NULL, OPEN_EXISTING,
kPipeFlagsAndAttributes, NULL);
if (local_pipe != INVALID_HANDLE_VALUE) {
HANDLE remotePipe = INVALID_HANDLE_VALUE;
if (DuplicateHandle(GetCurrentProcess(), local_pipe,
hProcess, &remotePipe, 0, FALSE,
DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) {
return remotePipe;
} else {
return INVALID_HANDLE_VALUE;
}
}
// Cannot continue retrying if the error wasn't a busy pipe.
if (GetLastError() != ERROR_PIPE_BUSY) {
return INVALID_HANDLE_VALUE;
}
if (!WaitNamedPipe(pipe_name, kPipeBusyWaitTimeoutMs)) {
return INVALID_HANDLE_VALUE;
}
}
return INVALID_HANDLE_VALUE;
}
} // namespace google_breakpad

View File

@@ -35,7 +35,7 @@
#include <string>
#include <utility>
#include "client/windows/common/ipc_protocol.h"
#include "processor/scoped_ptr.h"
#include "common/scoped_ptr.h"
namespace google_breakpad {
@@ -66,6 +66,10 @@ class CrashGenerationClient {
MINIDUMP_TYPE dump_type,
const CustomClientInfo* custom_info);
CrashGenerationClient(HANDLE pipe_handle,
MINIDUMP_TYPE dump_type,
const CustomClientInfo* custom_info);
~CrashGenerationClient();
// Registers the client process with the crash server.
@@ -96,6 +100,14 @@ class CrashGenerationClient {
// false will be returned.
bool RequestDump(MDRawAssertionInfo* assert_info);
// If the crash generation client is running in a sandbox that prevents it
// from opening the named pipe directly, the server process may open the
// handle and duplicate it into the client process with this helper method.
// Returns INVALID_HANDLE_VALUE on failure. The process must have been opened
// with the PROCESS_DUP_HANDLE access right.
static HANDLE DuplicatePipeToClientProcess(const wchar_t* pipe_name,
HANDLE hProcess);
private:
// Connects to the appropriate pipe and sets the pipe handle state.
//
@@ -127,6 +139,10 @@ class CrashGenerationClient {
// Pipe name to use to talk to server.
std::wstring pipe_name_;
// Pipe handle duplicated from server process. Only valid before
// Register is called.
HANDLE pipe_handle_;
// Custom client information
CustomClientInfo custom_info_;

View File

@@ -32,7 +32,7 @@
#include <cassert>
#include <list>
#include "client/windows/common/auto_critical_section.h"
#include "processor/scoped_ptr.h"
#include "common/scoped_ptr.h"
#include "client/windows/crash_generation/client_info.h"
@@ -76,13 +76,6 @@ static const ULONG kPipeIOThreadFlags = WT_EXECUTEINWAITTHREAD;
static const ULONG kDumpRequestThreadFlags = WT_EXECUTEINWAITTHREAD |
WT_EXECUTELONGFUNCTION;
// Maximum delay during server shutdown if some work items
// are still executing.
static const int kShutdownDelayMs = 10000;
// Interval for each sleep during server shutdown.
static const int kShutdownSleepIntervalMs = 5;
static bool IsClientRequestValid(const ProtocolMessage& msg) {
return msg.tag == MESSAGE_TAG_UPLOAD_REQUEST ||
(msg.tag == MESSAGE_TAG_REGISTRATION_REQUEST &&
@@ -92,6 +85,18 @@ static bool IsClientRequestValid(const ProtocolMessage& msg) {
msg.assert_info != NULL);
}
static bool CheckForIOIncomplete(bool success) {
#ifdef _DEBUG
// We should never get an I/O incomplete since we should not execute this
// unless the operation has finished and the overlapped event is signaled. If
// we do get INCOMPLETE, we have a bug in our code.
return success ? false : (GetLastError() == ERROR_IO_INCOMPLETE);
#else
return true;
#endif
}
CrashGenerationServer::CrashGenerationServer(
const std::wstring& pipe_name,
SECURITY_ATTRIBUTES* pipe_sec_attrs,
@@ -119,104 +124,94 @@ CrashGenerationServer::CrashGenerationServer(
upload_request_callback_(upload_request_callback),
upload_context_(upload_context),
generate_dumps_(generate_dumps),
dump_generator_(NULL),
dump_path_(*dump_path),
server_state_(IPC_SERVER_STATE_UNINITIALIZED),
shutting_down_(false),
overlapped_(),
client_info_(NULL),
cleanup_item_count_(0) {
pre_fetch_custom_info_(true) {
InitializeCriticalSection(&sync_);
if (dump_path) {
dump_generator_.reset(new MinidumpGenerator(*dump_path));
}
}
// This should never be called from the OnPipeConnected callback.
// Otherwise the UnregisterWaitEx call below will cause a deadlock.
CrashGenerationServer::~CrashGenerationServer() {
// New scope to release the lock automatically.
{
// Make sure no clients are added or removed beyond this point.
// Before adding or removing any clients, the critical section
// must be entered and the shutting_down_ flag checked. The
// critical section is then exited only after the clients_ list
// modifications are done and the list is in a consistent state.
AutoCriticalSection lock(&sync_);
// Indicate to existing threads that server is shutting down.
shutting_down_ = true;
}
// No one will modify the clients_ list beyond this point -
// not even from another thread.
// Even if there are no current worker threads running, it is possible that
// an I/O request is pending on the pipe right now but not yet done.
// In fact, it's very likely this is the case unless we are in an ERROR
// state. If we don't wait for the pending I/O to be done, then when the I/O
// completes, it may write to invalid memory. AppVerifier will flag this
// problem too. So we disconnect from the pipe and then wait for the server
// to get into error state so that the pending I/O will fail and get
// cleared.
DisconnectNamedPipe(pipe_);
int num_tries = 100;
while (num_tries-- && server_state_ != IPC_SERVER_STATE_ERROR) {
lock.Release();
Sleep(10);
lock.Acquire();
}
// Unregister wait on the pipe.
if (pipe_wait_handle_) {
// Wait for already executing callbacks to finish.
UnregisterWaitEx(pipe_wait_handle_, INVALID_HANDLE_VALUE);
}
// Close the pipe to avoid further client connections.
if (pipe_) {
CloseHandle(pipe_);
}
// Request all ClientInfo objects to unregister all waits.
std::list<ClientInfo*>::iterator iter;
for (iter = clients_.begin(); iter != clients_.end(); ++iter) {
ClientInfo* client_info = *iter;
client_info->UnregisterWaits();
}
// Even if there are no current worker threads running, it is possible that
// an I/O request is pending on the pipe right now but not yet done.
// In fact, it's very likely this is the case unless we are in an ERROR
// state. If we don't wait for the pending I/O to be done, then when the I/O
// completes, it may write to invalid memory. AppVerifier will flag this
// problem too. So we disconnect from the pipe and then wait for the server
// to get into error state so that the pending I/O will fail and get
// cleared.
DisconnectNamedPipe(pipe_);
int num_tries = 100;
while (num_tries-- && server_state_ != IPC_SERVER_STATE_ERROR) {
Sleep(10);
}
// Now that all waits have been unregistered, wait for some time
// for all pending work items to finish.
int total_wait = 0;
while (cleanup_item_count_ > 0) {
Sleep(kShutdownSleepIntervalMs);
total_wait += kShutdownSleepIntervalMs;
if (total_wait >= kShutdownDelayMs) {
break;
}
// Unregister wait on the pipe.
if (pipe_wait_handle_) {
// Wait for already executing callbacks to finish.
UnregisterWaitEx(pipe_wait_handle_, INVALID_HANDLE_VALUE);
}
// New scope to hold the lock for the shortest time.
{
AutoCriticalSection lock(&sync_);
// Close the pipe to avoid further client connections.
if (pipe_) {
CloseHandle(pipe_);
}
// Clean up all the ClientInfo objects.
std::list<ClientInfo*>::iterator iter;
for (iter = clients_.begin(); iter != clients_.end(); ++iter) {
ClientInfo* client_info = *iter;
delete client_info;
}
// Request all ClientInfo objects to unregister all waits.
// No need to enter the critical section because no one is allowed to modify
// the clients_ list once the shutting_down_ flag is set.
std::list<ClientInfo*>::iterator iter;
for (iter = clients_.begin(); iter != clients_.end(); ++iter) {
ClientInfo* client_info = *iter;
// Unregister waits. Wait for already executing callbacks to finish.
// Unregister the client process exit wait first and only then unregister
// the dump request wait. The reason is that the OnClientExit callback
// also unregisters the dump request wait and such a race (doing the same
// unregistration from two threads) is undesirable.
client_info->UnregisterProcessExitWait(true);
client_info->UnregisterDumpRequestWaitAndBlockUntilNoPending();
if (server_alive_handle_) {
// Release the mutex before closing the handle so that clients requesting
// dumps wait for a long time for the server to generate a dump.
ReleaseMutex(server_alive_handle_);
CloseHandle(server_alive_handle_);
}
// Destroying the ClientInfo here is safe because all wait operations for
// this ClientInfo were unregistered and no pending or running callbacks
// for this ClientInfo can possible exist (block_until_no_pending option
// was used).
delete client_info;
}
if (overlapped_.hEvent) {
CloseHandle(overlapped_.hEvent);
}
if (server_alive_handle_) {
// Release the mutex before closing the handle so that clients requesting
// dumps wait for a long time for the server to generate a dump.
ReleaseMutex(server_alive_handle_);
CloseHandle(server_alive_handle_);
}
if (overlapped_.hEvent) {
CloseHandle(overlapped_.hEvent);
}
DeleteCriticalSection(&sync_);
}
bool CrashGenerationServer::Start() {
AutoCriticalSection lock(&sync_);
if (server_state_ != IPC_SERVER_STATE_UNINITIALIZED) {
return false;
}
@@ -231,7 +226,7 @@ bool CrashGenerationServer::Start() {
// Event to signal the client connection and pipe reads and writes.
overlapped_.hEvent = CreateEvent(NULL, // Security descriptor.
TRUE, // Manual reset.
FALSE, // Initially signaled.
FALSE, // Initially nonsignaled.
NULL); // Name.
if (!overlapped_.hEvent) {
return false;
@@ -261,10 +256,13 @@ bool CrashGenerationServer::Start() {
// Kick-start the state machine. This will initiate an asynchronous wait
// for client connections.
HandleInitialState();
if (!SetEvent(overlapped_.hEvent)) {
server_state_ = IPC_SERVER_STATE_ERROR;
return false;
}
// If we are in error state, it's because we failed to start listening.
return server_state_ != IPC_SERVER_STATE_ERROR;
return true;
}
// If the server thread serving clients ever gets into the
@@ -398,18 +396,13 @@ void CrashGenerationServer::HandleReadingState() {
&overlapped_,
&bytes_count,
FALSE) != FALSE;
DWORD error_code = success ? ERROR_SUCCESS : GetLastError();
if (success && bytes_count == sizeof(ProtocolMessage)) {
EnterStateImmediately(IPC_SERVER_STATE_READ_DONE);
} else {
// We should never get an I/O incomplete since we should not execute this
// unless the Read has finished and the overlapped event is signaled. If
// we do get INCOMPLETE, we have a bug in our code.
assert(error_code != ERROR_IO_INCOMPLETE);
EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING);
return;
}
assert(!CheckForIOIncomplete(success));
EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING);
}
// When the server thread serving the client is in the READ_DONE state,
@@ -455,6 +448,7 @@ void CrashGenerationServer::HandleReadDoneState() {
return;
}
// This is only valid as long as it can be found in the clients_ list
client_info_ = client_info.release();
// Note that the asynchronous write issued by RespondToClient function
@@ -477,18 +471,12 @@ void CrashGenerationServer::HandleWritingState() {
&overlapped_,
&bytes_count,
FALSE) != FALSE;
DWORD error_code = success ? ERROR_SUCCESS : GetLastError();
if (success) {
EnterStateImmediately(IPC_SERVER_STATE_WRITE_DONE);
return;
}
// We should never get an I/O incomplete since we should not execute this
// unless the Write has finished and the overlapped event is signaled. If
// we do get INCOMPLETE, we have a bug in our code.
assert(error_code != ERROR_IO_INCOMPLETE);
assert(!CheckForIOIncomplete(success));
EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING);
}
@@ -526,19 +514,39 @@ void CrashGenerationServer::HandleReadingAckState() {
&overlapped_,
&bytes_count,
FALSE) != FALSE;
DWORD error_code = success ? ERROR_SUCCESS : GetLastError();
if (success) {
// The connection handshake with the client is now complete; perform
// the callback.
if (connect_callback_) {
connect_callback_(connect_context_, client_info_);
// Note that there is only a single copy of the ClientInfo of the
// currently connected client. However it is being referenced from
// two different places:
// - the client_info_ member
// - the clients_ list
// The lifetime of this ClientInfo depends on the lifetime of the
// client process - basically it can go away at any time.
// However, as long as it is referenced by the clients_ list it
// is guaranteed to be valid. Enter the critical section and check
// to see whether the client_info_ can be found in the list.
// If found, execute the callback and only then leave the critical
// section.
AutoCriticalSection lock(&sync_);
bool client_is_still_alive = false;
std::list<ClientInfo*>::iterator iter;
for (iter = clients_.begin(); iter != clients_.end(); ++iter) {
if (client_info_ == *iter) {
client_is_still_alive = true;
break;
}
}
if (client_is_still_alive) {
connect_callback_(connect_context_, client_info_);
}
}
} else {
// We should never get an I/O incomplete since we should not execute this
// unless the Read has finished and the overlapped event is signaled. If
// we do get INCOMPLETE, we have a bug in our code.
assert(error_code != ERROR_IO_INCOMPLETE);
assert(!CheckForIOIncomplete(success));
}
EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING);
@@ -605,16 +613,39 @@ bool CrashGenerationServer::PrepareReply(const ClientInfo& client_info,
return true;
}
// Closing of remote handles (belonging to a different process) can
// only be done through DuplicateHandle.
if (reply->dump_request_handle) {
CloseHandle(reply->dump_request_handle);
DuplicateHandle(client_info.process_handle(), // hSourceProcessHandle
reply->dump_request_handle, // hSourceHandle
NULL, // hTargetProcessHandle
0, // lpTargetHandle
0, // dwDesiredAccess
FALSE, // bInheritHandle
DUPLICATE_CLOSE_SOURCE); // dwOptions
reply->dump_request_handle = NULL;
}
if (reply->dump_generated_handle) {
CloseHandle(reply->dump_generated_handle);
DuplicateHandle(client_info.process_handle(), // hSourceProcessHandle
reply->dump_generated_handle, // hSourceHandle
NULL, // hTargetProcessHandle
0, // lpTargetHandle
0, // dwDesiredAccess
FALSE, // bInheritHandle
DUPLICATE_CLOSE_SOURCE); // dwOptions
reply->dump_generated_handle = NULL;
}
if (reply->server_alive_handle) {
CloseHandle(reply->server_alive_handle);
DuplicateHandle(client_info.process_handle(), // hSourceProcessHandle
reply->server_alive_handle, // hSourceHandle
NULL, // hTargetProcessHandle
0, // lpTargetHandle
0, // dwDesiredAccess
FALSE, // bInheritHandle
DUPLICATE_CLOSE_SOURCE); // dwOptions
reply->server_alive_handle = NULL;
}
return false;
@@ -676,21 +707,15 @@ bool CrashGenerationServer::RespondToClient(ClientInfo* client_info) {
// Takes over ownership of client_info. We MUST return true if AddClient
// succeeds.
if (!AddClient(client_info)) {
return false;
}
return true;
return AddClient(client_info);
}
// The server thread servicing the clients runs this method. The method
// implements the state machine described in ReadMe.txt along with the
// helper methods HandleXXXState.
void CrashGenerationServer::HandleConnectionRequest() {
AutoCriticalSection lock(&sync_);
// If we are shutting doen then get into ERROR state, reset the event so more
// workers don't run and return immediately.
// If the server is shutting down, get into ERROR state, reset the event so
// more workers don't run and return immediately.
if (shutting_down_) {
server_state_ = IPC_SERVER_STATE_ERROR;
ResetEvent(overlapped_.hEvent);
@@ -776,6 +801,10 @@ bool CrashGenerationServer::AddClient(ClientInfo* client_info) {
// New scope to hold the lock for the shortest time.
{
AutoCriticalSection lock(&sync_);
if (shutting_down_) {
// If server is shutting down, don't add new clients
return false;
}
clients_.push_back(client_info);
}
@@ -795,10 +824,12 @@ void CALLBACK CrashGenerationServer::OnPipeConnected(void* context, BOOLEAN) {
void CALLBACK CrashGenerationServer::OnDumpRequest(void* context, BOOLEAN) {
assert(context);
ClientInfo* client_info = reinterpret_cast<ClientInfo*>(context);
client_info->PopulateCustomInfo();
CrashGenerationServer* crash_server = client_info->crash_server();
assert(crash_server);
if (crash_server->pre_fetch_custom_info_) {
client_info->PopulateCustomInfo();
}
crash_server->HandleDumpRequest(*client_info);
ResetEvent(client_info->dump_requested_handle());
@@ -812,56 +843,55 @@ void CALLBACK CrashGenerationServer::OnClientEnd(void* context, BOOLEAN) {
CrashGenerationServer* crash_server = client_info->crash_server();
assert(crash_server);
client_info->UnregisterWaits();
InterlockedIncrement(&crash_server->cleanup_item_count_);
if (!QueueUserWorkItem(CleanupClient, context, WT_EXECUTEDEFAULT)) {
InterlockedDecrement(&crash_server->cleanup_item_count_);
}
crash_server->HandleClientProcessExit(client_info);
}
// static
DWORD WINAPI CrashGenerationServer::CleanupClient(void* context) {
assert(context);
ClientInfo* client_info = reinterpret_cast<ClientInfo*>(context);
CrashGenerationServer* crash_server = client_info->crash_server();
assert(crash_server);
if (crash_server->exit_callback_) {
crash_server->exit_callback_(crash_server->exit_context_, client_info);
}
crash_server->DoCleanup(client_info);
InterlockedDecrement(&crash_server->cleanup_item_count_);
return 0;
}
void CrashGenerationServer::DoCleanup(ClientInfo* client_info) {
void CrashGenerationServer::HandleClientProcessExit(ClientInfo* client_info) {
assert(client_info);
// Must unregister the dump request wait operation and wait for any
// dump requests that might be pending to finish before proceeding
// with the client_info cleanup.
client_info->UnregisterDumpRequestWaitAndBlockUntilNoPending();
if (exit_callback_) {
exit_callback_(exit_context_, client_info);
}
// Start a new scope to release lock automatically.
{
AutoCriticalSection lock(&sync_);
if (shutting_down_) {
// The crash generation server is shutting down and as part of the
// shutdown process it will delete all clients from the clients_ list.
return;
}
clients_.remove(client_info);
}
// Explicitly unregister the process exit wait using the non-blocking method.
// Otherwise, the destructor will attempt to unregister it using the blocking
// method which will lead to a deadlock because it is being called from the
// callback of the same wait operation
client_info->UnregisterProcessExitWait(false);
delete client_info;
}
void CrashGenerationServer::HandleDumpRequest(const ClientInfo& client_info) {
bool execute_callback = true;
// Generate the dump only if it's explicitly requested by the
// server application; otherwise the server might want to generate
// dump in the callback.
std::wstring dump_path;
if (generate_dumps_) {
if (!GenerateDump(client_info, &dump_path)) {
return;
// client proccess terminated or some other error
execute_callback = false;
}
}
if (dump_callback_) {
if (dump_callback_ && execute_callback) {
std::wstring* ptr_dump_path = (dump_path == L"") ? NULL : &dump_path;
dump_callback_(dump_context_, &client_info, ptr_dump_path);
}
@@ -886,15 +916,19 @@ bool CrashGenerationServer::GenerateDump(const ClientInfo& client,
return false;
}
return dump_generator_->WriteMinidump(client.process_handle(),
client.pid(),
client_thread_id,
GetCurrentThreadId(),
client_ex_info,
client.assert_info(),
client.dump_type(),
true,
dump_path);
MinidumpGenerator dump_generator(dump_path_,
client.process_handle(),
client.pid(),
client_thread_id,
GetCurrentThreadId(),
client_ex_info,
client.assert_info(),
client.dump_type(),
true);
if (!dump_generator.GenerateDumpFile(dump_path)) {
return false;
}
return dump_generator.WriteMinidump();
}
} // namespace google_breakpad

View File

@@ -34,7 +34,7 @@
#include <string>
#include "client/windows/common/ipc_protocol.h"
#include "client/windows/crash_generation/minidump_generator.h"
#include "processor/scoped_ptr.h"
#include "common/scoped_ptr.h"
namespace google_breakpad {
class ClientInfo;
@@ -102,6 +102,10 @@ class CrashGenerationServer {
// Returns true if initialization is successful; false otherwise.
bool Start();
void pre_fetch_custom_info(bool do_pre_fetch) {
pre_fetch_custom_info_ = do_pre_fetch;
}
private:
// Various states the client can be in during the handshake with
// the server.
@@ -189,11 +193,8 @@ class CrashGenerationServer {
// Callback for client process exit event.
static void CALLBACK OnClientEnd(void* context, BOOLEAN timer_or_wait);
// Releases resources for a client.
static DWORD WINAPI CleanupClient(void* context);
// Cleans up for the given client.
void DoCleanup(ClientInfo* client_info);
// Handles client process exit.
void HandleClientProcessExit(ClientInfo* client_info);
// Adds the given client to the list of registered clients.
bool AddClient(ClientInfo* client_info);
@@ -216,8 +217,7 @@ class CrashGenerationServer {
// asynchronous IO operation.
void EnterStateWhenSignaled(IPCServerState state);
// Sync object for thread-safe access to the shared list of clients and
// the server's state.
// Sync object for thread-safe access to the shared list of clients.
CRITICAL_SECTION sync_;
// List of clients.
@@ -265,8 +265,11 @@ class CrashGenerationServer {
// Whether to generate dumps.
bool generate_dumps_;
// Instance of a mini dump generator.
scoped_ptr<MinidumpGenerator> dump_generator_;
// Wether to populate custom information up-front.
bool pre_fetch_custom_info_;
// The dump path for the server.
const std::wstring dump_path_;
// State of the server in performing the IPC with the client.
// Note that since we restrict the pipe to one instance, we
@@ -286,10 +289,6 @@ class CrashGenerationServer {
// Client Info for the client that's connecting to the server.
ClientInfo* client_info_;
// Count of clean-up work items that are currently running or are
// already queued to run.
volatile LONG cleanup_item_count_;
// Disable copy ctor and operator=.
CrashGenerationServer(const CrashGenerationServer& crash_server);
CrashGenerationServer& operator=(const CrashGenerationServer& crash_server);

View File

@@ -28,18 +28,245 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "client/windows/crash_generation/minidump_generator.h"
#include <cassert>
#include <assert.h>
#include <avrfsdk.h>
#include <algorithm>
#include <iterator>
#include <list>
#include <vector>
#include "client/windows/common/auto_critical_section.h"
#include "common/scoped_ptr.h"
#include "common/windows/guid_string.h"
using std::wstring;
namespace {
// A helper class used to collect handle operations data. Unlike
// |MiniDumpWithHandleData| it records the operations for a single handle value
// only, making it possible to include this information to a minidump.
class HandleTraceData {
public:
HandleTraceData();
~HandleTraceData();
// Collects the handle operations data and formats a user stream to be added
// to the minidump.
bool CollectHandleData(HANDLE process_handle,
EXCEPTION_POINTERS* exception_pointers);
// Fills the user dump entry with a pointer to the collected handle operations
// data. Returns |true| if the entry was initialized successfully, or |false|
// if no trace data is available.
bool GetUserStream(MINIDUMP_USER_STREAM* user_stream);
private:
// Reads the exception code from the client process's address space.
// This routine assumes that the client process's pointer width matches ours.
static bool ReadExceptionCode(HANDLE process_handle,
EXCEPTION_POINTERS* exception_pointers,
DWORD* exception_code);
// Stores handle operations retrieved by VerifierEnumerateResource().
static ULONG CALLBACK RecordHandleOperations(void* resource_description,
void* enumeration_context,
ULONG* enumeration_level);
// Function pointer type for VerifierEnumerateResource, which is looked up
// dynamically.
typedef BOOL (WINAPI* VerifierEnumerateResourceType)(
HANDLE Process,
ULONG Flags,
ULONG ResourceType,
AVRF_RESOURCE_ENUMERATE_CALLBACK ResourceCallback,
PVOID EnumerationContext);
// Handle to dynamically loaded verifier.dll.
HMODULE verifier_module_;
// Pointer to the VerifierEnumerateResource function.
VerifierEnumerateResourceType enumerate_resource_;
// Handle value to look for.
ULONG64 handle_;
// List of handle operations for |handle_|.
std::list<AVRF_HANDLE_OPERATION> operations_;
// Minidump stream data.
std::vector<char> stream_;
};
HandleTraceData::HandleTraceData()
: verifier_module_(NULL),
enumerate_resource_(NULL),
handle_(NULL) {
}
HandleTraceData::~HandleTraceData() {
if (verifier_module_) {
FreeLibrary(verifier_module_);
}
}
bool HandleTraceData::CollectHandleData(
HANDLE process_handle,
EXCEPTION_POINTERS* exception_pointers) {
DWORD exception_code;
if (!ReadExceptionCode(process_handle, exception_pointers, &exception_code)) {
return false;
}
// Verify whether the execption is STATUS_INVALID_HANDLE. Do not record any
// handle information if it is a different exception to keep the minidump
// small.
if (exception_code != STATUS_INVALID_HANDLE) {
return true;
}
// Load verifier!VerifierEnumerateResource() dynamically.
verifier_module_ = LoadLibrary(TEXT("verifier.dll"));
if (!verifier_module_) {
return false;
}
enumerate_resource_ = reinterpret_cast<VerifierEnumerateResourceType>(
GetProcAddress(verifier_module_, "VerifierEnumerateResource"));
if (!enumerate_resource_) {
return false;
}
// STATUS_INVALID_HANDLE does not provide the offending handle value in
// the exception parameters so we have to guess. At the moment we scan
// the handle operations trace looking for the last invalid handle operation
// and record only the operations for that handle value.
if (enumerate_resource_(process_handle,
0,
AvrfResourceHandleTrace,
&RecordHandleOperations,
this) != ERROR_SUCCESS) {
// The handle tracing must have not been enabled.
return true;
}
// Now that |handle_| is initialized, purge all irrelevant operations.
std::list<AVRF_HANDLE_OPERATION>::iterator i = operations_.begin();
std::list<AVRF_HANDLE_OPERATION>::iterator i_end = operations_.end();
while (i != i_end) {
if (i->Handle == handle_) {
++i;
} else {
i = operations_.erase(i);
}
}
// Convert the list of recorded operations to a minidump stream.
stream_.resize(sizeof(MINIDUMP_HANDLE_OPERATION_LIST) +
sizeof(AVRF_HANDLE_OPERATION) * operations_.size());
MINIDUMP_HANDLE_OPERATION_LIST* stream_data =
reinterpret_cast<MINIDUMP_HANDLE_OPERATION_LIST*>(
&stream_.front());
stream_data->SizeOfHeader = sizeof(MINIDUMP_HANDLE_OPERATION_LIST);
stream_data->SizeOfEntry = sizeof(AVRF_HANDLE_OPERATION);
stream_data->NumberOfEntries = static_cast<ULONG32>(operations_.size());
stream_data->Reserved = 0;
AVRF_HANDLE_OPERATION* data_iter = reinterpret_cast<AVRF_HANDLE_OPERATION*>(stream_data + 1);
for (i = operations_.begin(); i != i_end; i++)
*data_iter++ = *i;
return true;
}
bool HandleTraceData::GetUserStream(MINIDUMP_USER_STREAM* user_stream) {
if (stream_.empty()) {
return false;
} else {
user_stream->Type = HandleOperationListStream;
user_stream->BufferSize = static_cast<ULONG>(stream_.size());
user_stream->Buffer = &stream_.front();
return true;
}
}
bool HandleTraceData::ReadExceptionCode(
HANDLE process_handle,
EXCEPTION_POINTERS* exception_pointers,
DWORD* exception_code) {
EXCEPTION_POINTERS pointers;
if (!ReadProcessMemory(process_handle,
exception_pointers,
&pointers,
sizeof(pointers),
NULL)) {
return false;
}
if (!ReadProcessMemory(process_handle,
pointers.ExceptionRecord,
exception_code,
sizeof(*exception_code),
NULL)) {
return false;
}
return true;
}
ULONG CALLBACK HandleTraceData::RecordHandleOperations(
void* resource_description,
void* enumeration_context,
ULONG* enumeration_level) {
AVRF_HANDLE_OPERATION* description =
reinterpret_cast<AVRF_HANDLE_OPERATION*>(resource_description);
HandleTraceData* self =
reinterpret_cast<HandleTraceData*>(enumeration_context);
// Remember the last invalid handle operation.
if (description->OperationType == OperationDbBADREF) {
self->handle_ = description->Handle;
}
// Record all handle operations.
self->operations_.push_back(*description);
*enumeration_level = HeapEnumerationEverything;
return ERROR_SUCCESS;
}
} // namespace
namespace google_breakpad {
MinidumpGenerator::MinidumpGenerator(const wstring& dump_path)
MinidumpGenerator::MinidumpGenerator(
const std::wstring& dump_path,
const HANDLE process_handle,
const DWORD process_id,
const DWORD thread_id,
const DWORD requesting_thread_id,
EXCEPTION_POINTERS* exception_pointers,
MDRawAssertionInfo* assert_info,
const MINIDUMP_TYPE dump_type,
const bool is_client_pointers)
: dbghelp_module_(NULL),
rpcrt4_module_(NULL),
dump_path_(dump_path),
process_handle_(process_handle),
process_id_(process_id),
thread_id_(thread_id),
requesting_thread_id_(requesting_thread_id),
exception_pointers_(exception_pointers),
assert_info_(assert_info),
dump_type_(dump_type),
is_client_pointers_(is_client_pointers),
dump_file_(INVALID_HANDLE_VALUE),
full_dump_file_(INVALID_HANDLE_VALUE),
dump_file_is_internal_(false),
full_dump_file_is_internal_(false),
additional_streams_(NULL),
callback_info_(NULL),
write_dump_(NULL),
create_uuid_(NULL) {
InitializeCriticalSection(&module_load_sync_);
@@ -47,6 +274,14 @@ MinidumpGenerator::MinidumpGenerator(const wstring& dump_path)
}
MinidumpGenerator::~MinidumpGenerator() {
if (dump_file_is_internal_ && dump_file_ != INVALID_HANDLE_VALUE) {
CloseHandle(dump_file_);
}
if (full_dump_file_is_internal_ && full_dump_file_ != INVALID_HANDLE_VALUE) {
CloseHandle(full_dump_file_);
}
if (dbghelp_module_) {
FreeLibrary(dbghelp_module_);
}
@@ -59,91 +294,28 @@ MinidumpGenerator::~MinidumpGenerator() {
DeleteCriticalSection(&module_load_sync_);
}
bool MinidumpGenerator::WriteMinidump(HANDLE process_handle,
DWORD process_id,
DWORD thread_id,
DWORD requesting_thread_id,
EXCEPTION_POINTERS* exception_pointers,
MDRawAssertionInfo* assert_info,
MINIDUMP_TYPE dump_type,
bool is_client_pointers,
wstring* dump_path) {
// Just call the full WriteMinidump with NULL as the full_dump_path.
return this->WriteMinidump(process_handle, process_id, thread_id,
requesting_thread_id, exception_pointers,
assert_info, dump_type, is_client_pointers,
dump_path, NULL);
}
bool MinidumpGenerator::WriteMinidump() {
bool full_memory_dump = (dump_type_ & MiniDumpWithFullMemory) != 0;
if (dump_file_ == INVALID_HANDLE_VALUE ||
(full_memory_dump && full_dump_file_ == INVALID_HANDLE_VALUE)) {
return false;
}
bool MinidumpGenerator::WriteMinidump(HANDLE process_handle,
DWORD process_id,
DWORD thread_id,
DWORD requesting_thread_id,
EXCEPTION_POINTERS* exception_pointers,
MDRawAssertionInfo* assert_info,
MINIDUMP_TYPE dump_type,
bool is_client_pointers,
wstring* dump_path,
wstring* full_dump_path) {
MiniDumpWriteDumpType write_dump = GetWriteDump();
if (!write_dump) {
return false;
}
wstring dump_file_path;
if (!GenerateDumpFilePath(&dump_file_path)) {
return false;
}
// If the client requests a full memory dump, we will write a normal mini
// dump and a full memory dump. Both dump files use the same uuid as file
// name prefix.
bool full_memory_dump = (dump_type & MiniDumpWithFullMemory) != 0;
wstring full_dump_file_path;
if (full_memory_dump) {
full_dump_file_path.assign(dump_file_path);
full_dump_file_path.resize(full_dump_file_path.size() - 4); // strip .dmp
full_dump_file_path.append(TEXT("-full.dmp"));
}
HANDLE dump_file = CreateFile(dump_file_path.c_str(),
GENERIC_WRITE,
0,
NULL,
CREATE_NEW,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (dump_file == INVALID_HANDLE_VALUE) {
return false;
}
HANDLE full_dump_file = INVALID_HANDLE_VALUE;
if (full_memory_dump) {
full_dump_file = CreateFile(full_dump_file_path.c_str(),
GENERIC_WRITE,
0,
NULL,
CREATE_NEW,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (full_dump_file == INVALID_HANDLE_VALUE) {
CloseHandle(dump_file);
return false;
}
}
MINIDUMP_EXCEPTION_INFORMATION* dump_exception_pointers = NULL;
MINIDUMP_EXCEPTION_INFORMATION dump_exception_info;
// Setup the exception information object only if it's a dump
// due to an exception.
if (exception_pointers) {
if (exception_pointers_) {
dump_exception_pointers = &dump_exception_info;
dump_exception_info.ThreadId = thread_id;
dump_exception_info.ExceptionPointers = exception_pointers;
dump_exception_info.ClientPointers = is_client_pointers;
dump_exception_info.ThreadId = thread_id_;
dump_exception_info.ExceptionPointers = exception_pointers_;
dump_exception_info.ClientPointers = is_client_pointers_;
}
// Add an MDRawBreakpadInfo stream to the minidump, to provide additional
@@ -153,48 +325,54 @@ bool MinidumpGenerator::WriteMinidump(HANDLE process_handle,
// can function better with Breakpad-generated dumps when it is present.
// The native debugger is not harmed by the presence of this information.
MDRawBreakpadInfo breakpad_info = {0};
if (!is_client_pointers) {
if (!is_client_pointers_) {
// Set the dump thread id and requesting thread id only in case of
// in-process dump generation.
breakpad_info.validity = MD_BREAKPAD_INFO_VALID_DUMP_THREAD_ID |
MD_BREAKPAD_INFO_VALID_REQUESTING_THREAD_ID;
breakpad_info.dump_thread_id = thread_id;
breakpad_info.requesting_thread_id = requesting_thread_id;
breakpad_info.dump_thread_id = thread_id_;
breakpad_info.requesting_thread_id = requesting_thread_id_;
}
// Leave room in user_stream_array for a possible assertion info stream.
MINIDUMP_USER_STREAM user_stream_array[2];
int additional_streams_count = additional_streams_ ?
additional_streams_->UserStreamCount : 0;
scoped_array<MINIDUMP_USER_STREAM> user_stream_array(
new MINIDUMP_USER_STREAM[3 + additional_streams_count]);
user_stream_array[0].Type = MD_BREAKPAD_INFO_STREAM;
user_stream_array[0].BufferSize = sizeof(breakpad_info);
user_stream_array[0].Buffer = &breakpad_info;
MINIDUMP_USER_STREAM_INFORMATION user_streams;
user_streams.UserStreamCount = 1;
user_streams.UserStreamArray = user_stream_array;
user_streams.UserStreamArray = user_stream_array.get();
MDRawAssertionInfo* actual_assert_info = assert_info;
MDRawAssertionInfo* actual_assert_info = assert_info_;
MDRawAssertionInfo client_assert_info = {0};
if (assert_info) {
if (assert_info_) {
// If the assertion info object lives in the client process,
// read the memory of the client process.
if (is_client_pointers) {
if (is_client_pointers_) {
SIZE_T bytes_read = 0;
if (!ReadProcessMemory(process_handle,
assert_info,
if (!ReadProcessMemory(process_handle_,
assert_info_,
&client_assert_info,
sizeof(client_assert_info),
&bytes_read)) {
CloseHandle(dump_file);
if (full_dump_file != INVALID_HANDLE_VALUE)
CloseHandle(full_dump_file);
if (dump_file_is_internal_)
CloseHandle(dump_file_);
if (full_dump_file_is_internal_ &&
full_dump_file_ != INVALID_HANDLE_VALUE)
CloseHandle(full_dump_file_);
return false;
}
if (bytes_read != sizeof(client_assert_info)) {
CloseHandle(dump_file);
if (full_dump_file != INVALID_HANDLE_VALUE)
CloseHandle(full_dump_file);
if (dump_file_is_internal_)
CloseHandle(dump_file_);
if (full_dump_file_is_internal_ &&
full_dump_file_ != INVALID_HANDLE_VALUE)
CloseHandle(full_dump_file_);
return false;
}
@@ -207,44 +385,128 @@ bool MinidumpGenerator::WriteMinidump(HANDLE process_handle,
++user_streams.UserStreamCount;
}
bool result_minidump = write_dump(
process_handle,
process_id,
dump_file,
static_cast<MINIDUMP_TYPE>((dump_type & (~MiniDumpWithFullMemory))
| MiniDumpNormal),
exception_pointers ? &dump_exception_info : NULL,
&user_streams,
NULL) != FALSE;
if (additional_streams_) {
for (size_t i = 0;
i < additional_streams_->UserStreamCount;
i++, user_streams.UserStreamCount++) {
user_stream_array[user_streams.UserStreamCount].Type =
additional_streams_->UserStreamArray[i].Type;
user_stream_array[user_streams.UserStreamCount].BufferSize =
additional_streams_->UserStreamArray[i].BufferSize;
user_stream_array[user_streams.UserStreamCount].Buffer =
additional_streams_->UserStreamArray[i].Buffer;
}
}
// If the process is terminated by STATUS_INVALID_HANDLE exception store
// the trace of operations for the offending handle value. Do nothing special
// if the client already requested the handle trace to be stored in the dump.
HandleTraceData handle_trace_data;
if (exception_pointers_ && (dump_type_ & MiniDumpWithHandleData) == 0) {
if (!handle_trace_data.CollectHandleData(process_handle_,
exception_pointers_)) {
if (dump_file_is_internal_)
CloseHandle(dump_file_);
if (full_dump_file_is_internal_ &&
full_dump_file_ != INVALID_HANDLE_VALUE)
CloseHandle(full_dump_file_);
return false;
}
}
bool result_full_memory = true;
if (full_memory_dump) {
result_full_memory = write_dump(
process_handle,
process_id,
full_dump_file,
static_cast<MINIDUMP_TYPE>(dump_type & (~MiniDumpNormal)),
exception_pointers ? &dump_exception_info : NULL,
process_handle_,
process_id_,
full_dump_file_,
static_cast<MINIDUMP_TYPE>((dump_type_ & (~MiniDumpNormal))
| MiniDumpWithHandleData),
exception_pointers_ ? &dump_exception_info : NULL,
&user_streams,
NULL) != FALSE;
}
bool result = result_minidump && result_full_memory;
CloseHandle(dump_file);
if (full_dump_file != INVALID_HANDLE_VALUE)
CloseHandle(full_dump_file);
// Store the path of the dump file in the out parameter if dump generation
// succeeded.
if (result && dump_path) {
*dump_path = dump_file_path;
}
if (result && full_memory_dump && full_dump_path) {
*full_dump_path = full_dump_file_path;
// Add handle operations trace stream to the minidump if it was collected.
if (handle_trace_data.GetUserStream(
&user_stream_array[user_streams.UserStreamCount])) {
++user_streams.UserStreamCount;
}
return result;
bool result_minidump = write_dump(
process_handle_,
process_id_,
dump_file_,
static_cast<MINIDUMP_TYPE>((dump_type_ & (~MiniDumpWithFullMemory))
| MiniDumpNormal),
exception_pointers_ ? &dump_exception_info : NULL,
&user_streams,
callback_info_) != FALSE;
return result_minidump && result_full_memory;
}
bool MinidumpGenerator::GenerateDumpFile(wstring* dump_path) {
// The dump file was already set by handle or this function was previously
// called.
if (dump_file_ != INVALID_HANDLE_VALUE) {
return false;
}
wstring dump_file_path;
if (!GenerateDumpFilePath(&dump_file_path)) {
return false;
}
dump_file_ = CreateFile(dump_file_path.c_str(),
GENERIC_WRITE,
0,
NULL,
CREATE_NEW,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (dump_file_ == INVALID_HANDLE_VALUE) {
return false;
}
dump_file_is_internal_ = true;
*dump_path = dump_file_path;
return true;
}
bool MinidumpGenerator::GenerateFullDumpFile(wstring* full_dump_path) {
// A full minidump was not requested.
if ((dump_type_ & MiniDumpWithFullMemory) == 0) {
return false;
}
// The dump file was already set by handle or this function was previously
// called.
if (full_dump_file_ != INVALID_HANDLE_VALUE) {
return false;
}
wstring full_dump_file_path;
if (!GenerateDumpFilePath(&full_dump_file_path)) {
return false;
}
full_dump_file_path.resize(full_dump_file_path.size() - 4); // strip .dmp
full_dump_file_path.append(TEXT("-full.dmp"));
full_dump_file_ = CreateFile(full_dump_file_path.c_str(),
GENERIC_WRITE,
0,
NULL,
CREATE_NEW,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (full_dump_file_ == INVALID_HANDLE_VALUE) {
return false;
}
full_dump_file_is_internal_ = true;
*full_dump_path = full_dump_file_path;
return true;
}
HMODULE MinidumpGenerator::GetDbghelpModule() {

View File

@@ -32,7 +32,9 @@
#include <windows.h>
#include <dbghelp.h>
#include <rpc.h>
#include <list>
#include <string>
#include "google_breakpad/common/minidump_format.h"
namespace google_breakpad {
@@ -43,37 +45,55 @@ namespace google_breakpad {
// the clients to generate minidumps.
class MinidumpGenerator {
public:
// Creates an instance with the given dump path.
explicit MinidumpGenerator(const std::wstring& dump_path);
// Creates an instance with the given parameters.
// is_client_pointers specifies whether the exception_pointers and
// assert_info point into the process that is being dumped.
// Before calling WriteMinidump on the returned instance a dump file muct be
// specified by a call to either SetDumpFile() or GenerateDumpFile().
// If a full dump file will be requested via a subsequent call to either
// SetFullDumpFile or GenerateFullDumpFile() dump_type must include
// MiniDumpWithFullMemory.
MinidumpGenerator(const std::wstring& dump_path,
const HANDLE process_handle,
const DWORD process_id,
const DWORD thread_id,
const DWORD requesting_thread_id,
EXCEPTION_POINTERS* exception_pointers,
MDRawAssertionInfo* assert_info,
const MINIDUMP_TYPE dump_type,
const bool is_client_pointers);
~MinidumpGenerator();
void SetDumpFile(const HANDLE dump_file) { dump_file_ = dump_file; }
void SetFullDumpFile(const HANDLE full_dump_file) {
full_dump_file_ = full_dump_file;
}
// Generate the name for the dump file that will be written to once
// WriteMinidump() is called. Can only be called once and cannot be called
// if the dump file is set via SetDumpFile().
bool GenerateDumpFile(std::wstring* dump_path);
// Generate the name for the full dump file that will be written to once
// WriteMinidump() is called. Cannot be called unless the minidump type
// includes MiniDumpWithFullMemory. Can only be called once and cannot be
// called if the dump file is set via SetFullDumpFile().
bool GenerateFullDumpFile(std::wstring* full_dump_path);
void SetAdditionalStreams(
MINIDUMP_USER_STREAM_INFORMATION* additional_streams) {
additional_streams_ = additional_streams;
}
void SetCallback(MINIDUMP_CALLBACK_INFORMATION* callback_info) {
callback_info_ = callback_info;
}
// Writes the minidump with the given parameters. Stores the
// dump file path in the dump_path parameter if dump generation
// succeeds.
bool WriteMinidump(HANDLE process_handle,
DWORD process_id,
DWORD thread_id,
DWORD requesting_thread_id,
EXCEPTION_POINTERS* exception_pointers,
MDRawAssertionInfo* assert_info,
MINIDUMP_TYPE dump_type,
bool is_client_pointers,
std::wstring* dump_path);
// Writes the minidump with the given parameters. Stores the dump file
// path in the dump_path (and full_dump_path) parameter if dump
// generation succeeds. full_dump_path and dump_path can be NULL.
bool WriteMinidump(HANDLE process_handle,
DWORD process_id,
DWORD thread_id,
DWORD requesting_thread_id,
EXCEPTION_POINTERS* exception_pointers,
MDRawAssertionInfo* assert_info,
MINIDUMP_TYPE dump_type,
bool is_client_pointers,
std::wstring* dump_path,
std::wstring* full_dump_path);
bool WriteMinidump();
private:
// Function pointer type for MiniDumpWriteDump, which is looked up
@@ -119,9 +139,53 @@ class MinidumpGenerator {
// Pointer to the UuidCreate function.
UuidCreateType create_uuid_;
// Handle for the process to dump.
HANDLE process_handle_;
// Process ID for the process to dump.
DWORD process_id_;
// The crashing thread ID.
DWORD thread_id_;
// The thread ID which is requesting the dump.
DWORD requesting_thread_id_;
// Pointer to the exception information for the crash. This may point to an
// address in the crashing process so it should not be dereferenced.
EXCEPTION_POINTERS* exception_pointers_;
// Assertion info for the report.
MDRawAssertionInfo* assert_info_;
// Type of minidump to generate.
MINIDUMP_TYPE dump_type_;
// Specifies whether the exception_pointers_ reference memory in the crashing
// process.
bool is_client_pointers_;
// Folder path to store dump files.
std::wstring dump_path_;
// The file where the dump will be written.
HANDLE dump_file_;
// The file where the full dump will be written.
HANDLE full_dump_file_;
// Tracks whether the dump file handle is managed externally.
bool dump_file_is_internal_;
// Tracks whether the full dump file handle is managed externally.
bool full_dump_file_is_internal_;
// Additional streams to be written to the dump.
MINIDUMP_USER_STREAM_INFORMATION* additional_streams_;
// The user defined callback for the various stages of the dump process.
MINIDUMP_CALLBACK_INFORMATION* callback_info_;
// Critical section to sychronize action of loading modules dynamically.
CRITICAL_SECTION module_load_sync_;

View File

@@ -39,16 +39,20 @@
#include "client/windows/handler/exception_handler.h"
#include "common/windows/guid_string.h"
#include "yvals.h"
namespace google_breakpad {
static const int kWaitForHandlerThreadMs = 60000;
static const int kExceptionHandlerThreadInitialStackSize = 64 * 1024;
// As documented on MSDN, on failure SuspendThread returns (DWORD) -1
static const DWORD kFailedToSuspendThread = static_cast<DWORD>(-1);
// This is passed as the context to the MinidumpWriteDump callback.
typedef struct {
ULONG64 memory_base;
ULONG memory_size;
bool finished;
AppMemoryList::const_iterator iter;
AppMemoryList::const_iterator end;
} MinidumpCallbackContext;
vector<ExceptionHandler*>* ExceptionHandler::handler_stack_ = NULL;
@@ -71,9 +75,52 @@ ExceptionHandler::ExceptionHandler(const wstring& dump_path,
handler_types,
dump_type,
pipe_name,
NULL, // pipe_handle
NULL, // crash_generation_client
custom_info);
}
ExceptionHandler::ExceptionHandler(const wstring& dump_path,
FilterCallback filter,
MinidumpCallback callback,
void* callback_context,
int handler_types,
MINIDUMP_TYPE dump_type,
HANDLE pipe_handle,
const CustomClientInfo* custom_info) {
Initialize(dump_path,
filter,
callback,
callback_context,
handler_types,
dump_type,
NULL, // pipe_name
pipe_handle,
NULL, // crash_generation_client
custom_info);
}
ExceptionHandler::ExceptionHandler(
const wstring& dump_path,
FilterCallback filter,
MinidumpCallback callback,
void* callback_context,
int handler_types,
CrashGenerationClient* crash_generation_client) {
// The dump_type, pipe_name and custom_info that are passed in to Initialize()
// are not used. The ones set in crash_generation_client are used instead.
Initialize(dump_path,
filter,
callback,
callback_context,
handler_types,
MiniDumpNormal, // dump_type - not used
NULL, // pipe_name - not used
NULL, // pipe_handle
crash_generation_client,
NULL); // custom_info - not used
}
ExceptionHandler::ExceptionHandler(const wstring &dump_path,
FilterCallback filter,
MinidumpCallback callback,
@@ -85,18 +132,23 @@ ExceptionHandler::ExceptionHandler(const wstring &dump_path,
callback_context,
handler_types,
MiniDumpNormal,
NULL,
NULL);
NULL, // pipe_name
NULL, // pipe_handle
NULL, // crash_generation_client
NULL); // custom_info
}
void ExceptionHandler::Initialize(const wstring& dump_path,
FilterCallback filter,
MinidumpCallback callback,
void* callback_context,
int handler_types,
MINIDUMP_TYPE dump_type,
const wchar_t* pipe_name,
const CustomClientInfo* custom_info) {
void ExceptionHandler::Initialize(
const wstring& dump_path,
FilterCallback filter,
MinidumpCallback callback,
void* callback_context,
int handler_types,
MINIDUMP_TYPE dump_type,
const wchar_t* pipe_name,
HANDLE pipe_handle,
CrashGenerationClient* crash_generation_client,
const CustomClientInfo* custom_info) {
LONG instance_count = InterlockedIncrement(&instance_count_);
filter_ = filter;
callback_ = callback;
@@ -111,9 +163,9 @@ void ExceptionHandler::Initialize(const wstring& dump_path,
uuid_create_ = NULL;
handler_types_ = handler_types;
previous_filter_ = NULL;
#if _MSC_VER >= 1400 // MSVC 2005/8
//#if _MSC_VER >= 1400 // MSVC 2005/8
previous_iph_ = NULL;
#endif // _MSC_VER >= 1400
//#endif // _MSC_VER >= 1400
previous_pch_ = NULL;
handler_thread_ = NULL;
is_shutdown_ = false;
@@ -125,13 +177,20 @@ void ExceptionHandler::Initialize(const wstring& dump_path,
handler_return_value_ = false;
handle_debug_exceptions_ = false;
// Attempt to use out-of-process if user has specified pipe name.
if (pipe_name != NULL) {
scoped_ptr<CrashGenerationClient> client(
new CrashGenerationClient(pipe_name,
dump_type_,
custom_info));
// Attempt to use out-of-process if user has specified a pipe or a
// crash generation client.
scoped_ptr<CrashGenerationClient> client;
if (crash_generation_client) {
client.reset(crash_generation_client);
} else if (pipe_name) {
client.reset(
new CrashGenerationClient(pipe_name, dump_type_, custom_info));
} else if (pipe_handle) {
client.reset(
new CrashGenerationClient(pipe_handle, dump_type_, custom_info));
}
if (client.get() != NULL) {
// If successful in registering with the monitoring process,
// there is no need to setup in-process crash generation.
if (client->Register()) {
@@ -188,6 +247,12 @@ void ExceptionHandler::Initialize(const wstring& dump_path,
set_dump_path(dump_path);
}
// Reserve one element for the instruction memory
AppMemory instruction_memory;
instruction_memory.ptr = NULL;
instruction_memory.length = 0;
app_memory_info_.push_back(instruction_memory);
// There is a race condition here. If the first instance has not yet
// initialized the critical section, the second (and later) instances may
// try to use uninitialized critical section object. The feature of multiple
@@ -216,13 +281,13 @@ void ExceptionHandler::Initialize(const wstring& dump_path,
if (handler_types & HANDLER_EXCEPTION)
previous_filter_ = SetUnhandledExceptionFilter(HandleException);
#if _MSC_VER >= 1400 // MSVC 2005/8
//#if _MSC_VER >= 1400 // MSVC 2005/8
if (handler_types & HANDLER_INVALID_PARAMETER)
previous_iph_ = _set_invalid_parameter_handler(HandleInvalidParameter);
#endif // _MSC_VER >= 1400
//#endif // _MSC_VER >= 1400
// if (handler_types & HANDLER_PURECALL)
// previous_pch_ = _set_purecall_handler(HandlePureVirtualCall);
if (handler_types & HANDLER_PURECALL)
previous_pch_ = _set_purecall_handler(HandlePureVirtualCall);
LeaveCriticalSection(&handler_stack_critical_section_);
}
@@ -243,13 +308,13 @@ ExceptionHandler::~ExceptionHandler() {
if (handler_types_ & HANDLER_EXCEPTION)
SetUnhandledExceptionFilter(previous_filter_);
#if _MSC_VER >= 1400 // MSVC 2005/8
//#if _MSC_VER >= 1400 // MSVC 2005/8
if (handler_types_ & HANDLER_INVALID_PARAMETER)
_set_invalid_parameter_handler(previous_iph_);
#endif // _MSC_VER >= 1400
//#endif // _MSC_VER >= 1400
// if (handler_types_ & HANDLER_PURECALL)
// _set_purecall_handler(previous_pch_);
if (handler_types_ & HANDLER_PURECALL)
_set_purecall_handler(previous_pch_);
if (handler_stack_->back() == this) {
handler_stack_->pop_back();
@@ -379,10 +444,10 @@ class AutoExceptionHandler {
// In case another exception occurs while this handler is doing its thing,
// it should be delivered to the previous filter.
SetUnhandledExceptionFilter(handler_->previous_filter_);
#if _MSC_VER >= 1400 // MSVC 2005/8
//#if _MSC_VER >= 1400 // MSVC 2005/8
_set_invalid_parameter_handler(handler_->previous_iph_);
#endif // _MSC_VER >= 1400
// _set_purecall_handler(handler_->previous_pch_);
//#endif // _MSC_VER >= 1400
_set_purecall_handler(handler_->previous_pch_);
}
~AutoExceptionHandler() {
@@ -391,7 +456,7 @@ class AutoExceptionHandler {
#if _MSC_VER >= 1400 // MSVC 2005/8
_set_invalid_parameter_handler(ExceptionHandler::HandleInvalidParameter);
#endif // _MSC_VER >= 1400
// _set_purecall_handler(ExceptionHandler::HandlePureVirtualCall);
_set_purecall_handler(ExceptionHandler::HandlePureVirtualCall);
--ExceptionHandler::handler_stack_index_;
LeaveCriticalSection(&ExceptionHandler::handler_stack_critical_section_);
@@ -466,7 +531,7 @@ LONG ExceptionHandler::HandleException(EXCEPTION_POINTERS* exinfo) {
return action;
}
#if _MSC_VER >= 1400 // MSVC 2005/8
//#if _MSC_VER >= 1400 // MSVC 2005/8
// static
void ExceptionHandler::HandleInvalidParameter(const wchar_t* expression,
const wchar_t* function,
@@ -549,7 +614,7 @@ void ExceptionHandler::HandleInvalidParameter(const wchar_t* expression,
#ifdef _DEBUG
_invalid_parameter(expression, function, file, line, reserved);
#else // _DEBUG
_invalid_parameter_noinfo();
// _invalid_parameter_noinfo();
#endif // _DEBUG
}
}
@@ -559,7 +624,7 @@ void ExceptionHandler::HandleInvalidParameter(const wchar_t* expression,
// the behavior of "swallowing" exceptions.
exit(0);
}
#endif // _MSC_VER >= 1400
//#endif // _MSC_VER >= 1400
// static
void ExceptionHandler::HandlePureVirtualCall() {
@@ -701,6 +766,62 @@ bool ExceptionHandler::WriteMinidump(const wstring &dump_path,
return handler.WriteMinidump();
}
// static
bool ExceptionHandler::WriteMinidumpForChild(HANDLE child,
DWORD child_blamed_thread,
const wstring& dump_path,
MinidumpCallback callback,
void* callback_context) {
EXCEPTION_RECORD ex;
CONTEXT ctx;
EXCEPTION_POINTERS exinfo = { NULL, NULL };
DWORD last_suspend_count = kFailedToSuspendThread;
HANDLE child_thread_handle = OpenThread(THREAD_GET_CONTEXT |
THREAD_QUERY_INFORMATION |
THREAD_SUSPEND_RESUME,
FALSE,
child_blamed_thread);
// This thread may have died already, so not opening the handle is a
// non-fatal error.
if (child_thread_handle != NULL) {
last_suspend_count = SuspendThread(child_thread_handle);
if (last_suspend_count != kFailedToSuspendThread) {
ctx.ContextFlags = CONTEXT_ALL;
if (GetThreadContext(child_thread_handle, &ctx)) {
memset(&ex, 0, sizeof(ex));
ex.ExceptionCode = EXCEPTION_BREAKPOINT;
#if defined(_M_IX86)
ex.ExceptionAddress = reinterpret_cast<PVOID>(ctx.Eip);
#elif defined(_M_X64)
ex.ExceptionAddress = reinterpret_cast<PVOID>(ctx.Rip);
#endif
exinfo.ExceptionRecord = &ex;
exinfo.ContextRecord = &ctx;
}
}
}
ExceptionHandler handler(dump_path, NULL, callback, callback_context,
HANDLER_NONE);
bool success = handler.WriteMinidumpWithExceptionForProcess(
child_blamed_thread,
exinfo.ExceptionRecord ? &exinfo : NULL,
NULL, child, false);
if (last_suspend_count != kFailedToSuspendThread) {
ResumeThread(child_thread_handle);
}
CloseHandle(child_thread_handle);
if (callback) {
success = callback(handler.dump_path_c_, handler.next_minidump_id_c_,
callback_context, NULL, NULL, success);
}
return success;
}
bool ExceptionHandler::WriteMinidumpWithException(
DWORD requesting_thread_id,
EXCEPTION_POINTERS* exinfo,
@@ -719,106 +840,11 @@ bool ExceptionHandler::WriteMinidumpWithException(
if (IsOutOfProcess()) {
success = crash_generation_client_->RequestDump(exinfo, assertion);
} else {
if (minidump_write_dump_) {
HANDLE dump_file = CreateFile(next_minidump_path_c_,
GENERIC_WRITE,
0, // no sharing
NULL,
CREATE_NEW, // fail if exists
FILE_ATTRIBUTE_NORMAL,
NULL);
if (dump_file != INVALID_HANDLE_VALUE) {
MINIDUMP_EXCEPTION_INFORMATION except_info;
except_info.ThreadId = requesting_thread_id;
except_info.ExceptionPointers = exinfo;
except_info.ClientPointers = FALSE;
// Add an MDRawBreakpadInfo stream to the minidump, to provide
// additional information about the exception handler to the Breakpad
// processor. The information will help the processor determine which
// threads are relevant. The Breakpad processor does not require this
// information but can function better with Breakpad-generated dumps
// when it is present. The native debugger is not harmed by the
// presence of this information.
MDRawBreakpadInfo breakpad_info;
breakpad_info.validity = MD_BREAKPAD_INFO_VALID_DUMP_THREAD_ID |
MD_BREAKPAD_INFO_VALID_REQUESTING_THREAD_ID;
breakpad_info.dump_thread_id = GetCurrentThreadId();
breakpad_info.requesting_thread_id = requesting_thread_id;
// Leave room in user_stream_array for a possible assertion info stream.
MINIDUMP_USER_STREAM user_stream_array[2];
user_stream_array[0].Type = MD_BREAKPAD_INFO_STREAM;
user_stream_array[0].BufferSize = sizeof(breakpad_info);
user_stream_array[0].Buffer = &breakpad_info;
MINIDUMP_USER_STREAM_INFORMATION user_streams;
user_streams.UserStreamCount = 1;
user_streams.UserStreamArray = user_stream_array;
if (assertion) {
user_stream_array[1].Type = MD_ASSERTION_INFO_STREAM;
user_stream_array[1].BufferSize = sizeof(MDRawAssertionInfo);
user_stream_array[1].Buffer = assertion;
++user_streams.UserStreamCount;
}
MINIDUMP_CALLBACK_INFORMATION callback;
MinidumpCallbackContext context;
MINIDUMP_CALLBACK_INFORMATION* callback_pointer = NULL;
// Older versions of DbgHelp.dll don't correctly put the memory around
// the faulting instruction pointer into the minidump. This
// callback will ensure that it gets included.
if (exinfo) {
// Find a memory region of 256 bytes centered on the
// faulting instruction pointer.
const ULONG64 instruction_pointer =
#if defined(_M_IX86)
exinfo->ContextRecord->Eip;
#elif defined(_M_AMD64)
exinfo->ContextRecord->Rip;
#else
#error Unsupported platform
#endif
MEMORY_BASIC_INFORMATION info;
if (VirtualQuery(reinterpret_cast<LPCVOID>(instruction_pointer),
&info,
sizeof(MEMORY_BASIC_INFORMATION)) != 0 &&
info.State == MEM_COMMIT) {
// Attempt to get 128 bytes before and after the instruction
// pointer, but settle for whatever's available up to the
// boundaries of the memory region.
const ULONG64 kIPMemorySize = 256;
context.memory_base =
(std::max)(reinterpret_cast<ULONG64>(info.BaseAddress),
instruction_pointer - (kIPMemorySize / 2));
ULONG64 end_of_range =
(std::min)(instruction_pointer + (kIPMemorySize / 2),
reinterpret_cast<ULONG64>(info.BaseAddress)
+ info.RegionSize);
context.memory_size =
static_cast<ULONG>(end_of_range - context.memory_base);
context.finished = false;
callback.CallbackRoutine = MinidumpWriteDumpCallback;
callback.CallbackParam = reinterpret_cast<void*>(&context);
callback_pointer = &callback;
}
}
// The explicit comparison to TRUE avoids a warning (C4800).
success = (minidump_write_dump_(GetCurrentProcess(),
GetCurrentProcessId(),
dump_file,
dump_type_,
exinfo ? &except_info : NULL,
&user_streams,
callback_pointer) == TRUE);
CloseHandle(dump_file);
}
}
success = WriteMinidumpWithExceptionForProcess(requesting_thread_id,
exinfo,
assertion,
GetCurrentProcess(),
true);
}
if (callback_) {
@@ -842,13 +868,13 @@ BOOL CALLBACK ExceptionHandler::MinidumpWriteDumpCallback(
case MemoryCallback: {
MinidumpCallbackContext* callback_context =
reinterpret_cast<MinidumpCallbackContext*>(context);
if (callback_context->finished)
if (callback_context->iter == callback_context->end)
return FALSE;
// Include the specified memory region.
callback_output->MemoryBase = callback_context->memory_base;
callback_output->MemorySize = callback_context->memory_size;
callback_context->finished = true;
callback_output->MemoryBase = callback_context->iter->ptr;
callback_output->MemorySize = callback_context->iter->length;
callback_context->iter++;
return TRUE;
}
@@ -872,6 +898,132 @@ BOOL CALLBACK ExceptionHandler::MinidumpWriteDumpCallback(
return FALSE;
}
bool ExceptionHandler::WriteMinidumpWithExceptionForProcess(
DWORD requesting_thread_id,
EXCEPTION_POINTERS* exinfo,
MDRawAssertionInfo* assertion,
HANDLE process,
bool write_requester_stream) {
bool success = false;
if (minidump_write_dump_) {
HANDLE dump_file = CreateFile(next_minidump_path_c_,
GENERIC_WRITE,
0, // no sharing
NULL,
CREATE_NEW, // fail if exists
FILE_ATTRIBUTE_NORMAL,
NULL);
if (dump_file != INVALID_HANDLE_VALUE) {
MINIDUMP_EXCEPTION_INFORMATION except_info;
except_info.ThreadId = requesting_thread_id;
except_info.ExceptionPointers = exinfo;
except_info.ClientPointers = FALSE;
// Leave room in user_stream_array for possible breakpad and
// assertion info streams.
MINIDUMP_USER_STREAM user_stream_array[2];
MINIDUMP_USER_STREAM_INFORMATION user_streams;
user_streams.UserStreamCount = 0;
user_streams.UserStreamArray = user_stream_array;
if (write_requester_stream) {
// Add an MDRawBreakpadInfo stream to the minidump, to provide
// additional information about the exception handler to the Breakpad
// processor. The information will help the processor determine which
// threads are relevant. The Breakpad processor does not require this
// information but can function better with Breakpad-generated dumps
// when it is present. The native debugger is not harmed by the
// presence of this information.
MDRawBreakpadInfo breakpad_info;
breakpad_info.validity = MD_BREAKPAD_INFO_VALID_DUMP_THREAD_ID |
MD_BREAKPAD_INFO_VALID_REQUESTING_THREAD_ID;
breakpad_info.dump_thread_id = GetCurrentThreadId();
breakpad_info.requesting_thread_id = requesting_thread_id;
int index = user_streams.UserStreamCount;
user_stream_array[index].Type = MD_BREAKPAD_INFO_STREAM;
user_stream_array[index].BufferSize = sizeof(breakpad_info);
user_stream_array[index].Buffer = &breakpad_info;
++user_streams.UserStreamCount;
}
if (assertion) {
int index = user_streams.UserStreamCount;
user_stream_array[index].Type = MD_ASSERTION_INFO_STREAM;
user_stream_array[index].BufferSize = sizeof(MDRawAssertionInfo);
user_stream_array[index].Buffer = assertion;
++user_streams.UserStreamCount;
}
// Older versions of DbgHelp.dll don't correctly put the memory around
// the faulting instruction pointer into the minidump. This
// callback will ensure that it gets included.
if (exinfo) {
// Find a memory region of 256 bytes centered on the
// faulting instruction pointer.
const ULONG64 instruction_pointer =
#if defined(_M_IX86)
exinfo->ContextRecord->Eip;
#elif defined(_M_AMD64)
exinfo->ContextRecord->Rip;
#else
#error Unsupported platform
#endif
MEMORY_BASIC_INFORMATION info;
if (VirtualQueryEx(process,
reinterpret_cast<LPCVOID>(instruction_pointer),
&info,
sizeof(MEMORY_BASIC_INFORMATION)) != 0 &&
info.State == MEM_COMMIT) {
// Attempt to get 128 bytes before and after the instruction
// pointer, but settle for whatever's available up to the
// boundaries of the memory region.
const ULONG64 kIPMemorySize = 256;
ULONG64 base =
(std::max)(reinterpret_cast<ULONG64>(info.BaseAddress),
instruction_pointer - (kIPMemorySize / 2));
ULONG64 end_of_range =
(std::min)(instruction_pointer + (kIPMemorySize / 2),
reinterpret_cast<ULONG64>(info.BaseAddress)
+ info.RegionSize);
ULONG size = static_cast<ULONG>(end_of_range - base);
AppMemory& elt = app_memory_info_.front();
elt.ptr = base;
elt.length = size;
}
}
MinidumpCallbackContext context;
context.iter = app_memory_info_.begin();
context.end = app_memory_info_.end();
// Skip the reserved element if there was no instruction memory
if (context.iter->ptr == 0) {
context.iter++;
}
MINIDUMP_CALLBACK_INFORMATION callback;
callback.CallbackRoutine = MinidumpWriteDumpCallback;
callback.CallbackParam = reinterpret_cast<void*>(&context);
// The explicit comparison to TRUE avoids a warning (C4800).
success = (minidump_write_dump_(process,
GetProcessId(process),
dump_file,
dump_type_,
exinfo ? &except_info : NULL,
&user_streams,
&callback) == TRUE);
CloseHandle(dump_file);
}
}
return success;
}
void ExceptionHandler::UpdateNextID() {
assert(uuid_create_);
UUID id = {0};
@@ -892,4 +1044,26 @@ void ExceptionHandler::UpdateNextID() {
next_minidump_path_c_ = next_minidump_path_.c_str();
}
void ExceptionHandler::RegisterAppMemory(void* ptr, size_t length) {
AppMemoryList::iterator iter =
std::find(app_memory_info_.begin(), app_memory_info_.end(), ptr);
if (iter != app_memory_info_.end()) {
// Don't allow registering the same pointer twice.
return;
}
AppMemory app_memory;
app_memory.ptr = reinterpret_cast<ULONG64>(ptr);
app_memory.length = static_cast<ULONG>(length);
app_memory_info_.push_back(app_memory);
}
void ExceptionHandler::UnregisterAppMemory(void* ptr) {
AppMemoryList::iterator iter =
std::find(app_memory_info_.begin(), app_memory_info_.end(), ptr);
if (iter != app_memory_info_.end()) {
app_memory_info_.erase(iter);
}
}
} // namespace google_breakpad

View File

@@ -34,7 +34,7 @@
'targets': [
{
'target_name': 'exception_handler',
'type': '<(library)',
'type': 'static_library',
'sources': [
"exception_handler.cc",
"exception_handler.h",

View File

@@ -61,23 +61,40 @@
#include <dbghelp.h>
#include <rpc.h>
#pragma warning( push )
#pragma warning(push)
// Disable exception handler warnings.
#pragma warning( disable : 4530 )
#pragma warning(disable:4530)
#include <list>
#include <string>
#include <vector>
#include "client/windows/common/ipc_protocol.h"
#include "client/windows/crash_generation/crash_generation_client.h"
#include "common/scoped_ptr.h"
#include "google_breakpad/common/minidump_format.h"
#include "processor/scoped_ptr.h"
namespace google_breakpad {
using std::vector;
using std::wstring;
// These entries store a list of memory regions that the client wants included
// in the minidump.
struct AppMemory {
ULONG64 ptr;
ULONG length;
bool operator==(const struct AppMemory& other) const {
return ptr == other.ptr;
}
bool operator==(const void* other) const {
return ptr == reinterpret_cast<ULONG64>(other);
}
};
typedef std::list<AppMemory> AppMemoryList;
class ExceptionHandler {
public:
// A callback function to run before Breakpad performs any substantial
@@ -153,7 +170,7 @@ class ExceptionHandler {
void* callback_context,
int handler_types);
// Creates a new ExcetpionHandler instance that can attempt to perform
// Creates a new ExceptionHandler instance that can attempt to perform
// out-of-process dump generation if pipe_name is not NULL. If pipe_name is
// NULL, or if out-of-process dump generation registration step fails,
// in-process dump generation will be used. This also allows specifying
@@ -167,6 +184,36 @@ class ExceptionHandler {
const wchar_t* pipe_name,
const CustomClientInfo* custom_info);
// As above, creates a new ExceptionHandler instance to perform
// out-of-process dump generation if the given pipe_handle is not NULL.
ExceptionHandler(const wstring& dump_path,
FilterCallback filter,
MinidumpCallback callback,
void* callback_context,
int handler_types,
MINIDUMP_TYPE dump_type,
HANDLE pipe_handle,
const CustomClientInfo* custom_info);
// ExceptionHandler that ENSURES out-of-process dump generation. Expects a
// crash generation client that is already registered with a crash generation
// server. Takes ownership of the passed-in crash_generation_client.
//
// Usage example:
// crash_generation_client = new CrashGenerationClient(..);
// if (crash_generation_client->Register()) {
// // Registration with the crash generation server succeeded.
// // Out-of-process dump generation is guaranteed.
// g_handler = new ExceptionHandler(.., crash_generation_client, ..);
// return true;
// }
ExceptionHandler(const wstring& dump_path,
FilterCallback filter,
MinidumpCallback callback,
void* callback_context,
int handler_types,
CrashGenerationClient* crash_generation_client);
~ExceptionHandler();
// Get and set the minidump path.
@@ -193,6 +240,17 @@ class ExceptionHandler {
static bool WriteMinidump(const wstring &dump_path,
MinidumpCallback callback, void* callback_context);
// Write a minidump of |child| immediately. This can be used to
// capture the execution state of |child| independently of a crash.
// Pass a meaningful |child_blamed_thread| to make that thread in
// the child process the one from which a crash signature is
// extracted.
static bool WriteMinidumpForChild(HANDLE child,
DWORD child_blamed_thread,
const wstring& dump_path,
MinidumpCallback callback,
void* callback_context);
// Get the thread ID of the thread requesting the dump (either the exception
// thread or any other thread that called WriteMinidump directly). This
// may be useful if you want to include additional thread state in your
@@ -208,6 +266,11 @@ class ExceptionHandler {
// Returns whether out-of-process dump generation is used or not.
bool IsOutOfProcess() const { return crash_generation_client_.get() != NULL; }
// Calling RegisterAppMemory(p, len) causes len bytes starting
// at address p to be copied to the minidump when a crash happens.
void RegisterAppMemory(void* ptr, size_t length);
void UnregisterAppMemory(void* ptr);
private:
friend class AutoExceptionHandler;
@@ -219,6 +282,8 @@ class ExceptionHandler {
int handler_types,
MINIDUMP_TYPE dump_type,
const wchar_t* pipe_name,
HANDLE pipe_handle,
CrashGenerationClient* crash_generation_client,
const CustomClientInfo* custom_info);
// Function pointer type for MiniDumpWriteDump, which is looked up
@@ -242,7 +307,7 @@ class ExceptionHandler {
// Signals the exception handler thread to handle the exception.
static LONG WINAPI HandleException(EXCEPTION_POINTERS* exinfo);
#if _MSC_VER >= 1400 // MSVC 2005/8
// #if _MSC_VER >= 1400 // MSVC 2005/8
// This function will be called by some CRT functions when they detect
// that they were passed an invalid parameter. Note that in _DEBUG builds,
// the CRT may display an assertion dialog before calling this function,
@@ -253,7 +318,7 @@ class ExceptionHandler {
const wchar_t* file,
unsigned int line,
uintptr_t reserved);
#endif // _MSC_VER >= 1400
// #endif // _MSC_VER >= 1400
// This function will be called by the CRT when a pure virtual
// function is called.
@@ -271,8 +336,9 @@ class ExceptionHandler {
bool WriteMinidumpOnHandlerThread(EXCEPTION_POINTERS* exinfo,
MDRawAssertionInfo* assertion);
// This function does the actual writing of a minidump. It is called
// on the handler thread. requesting_thread_id is the ID of the thread
// This function is called on the handler thread. It calls into
// WriteMinidumpWithExceptionForProcess() with a handle to the
// current process. requesting_thread_id is the ID of the thread
// that requested the dump. If the dump is requested as a result of
// an exception, exinfo contains exception information, otherwise,
// it is NULL.
@@ -287,6 +353,20 @@ class ExceptionHandler {
const PMINIDUMP_CALLBACK_INPUT callback_input,
PMINIDUMP_CALLBACK_OUTPUT callback_output);
// This function does the actual writing of a minidump. It is
// called on the handler thread. requesting_thread_id is the ID of
// the thread that requested the dump, if that information is
// meaningful. If the dump is requested as a result of an
// exception, exinfo contains exception information, otherwise, it
// is NULL. process is the one that will be dumped. If
// requesting_thread_id is meaningful and should be added to the
// minidump, write_requester_stream is |true|.
bool WriteMinidumpWithExceptionForProcess(DWORD requesting_thread_id,
EXCEPTION_POINTERS* exinfo,
MDRawAssertionInfo* assertion,
HANDLE process,
bool write_requester_stream);
// 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();
@@ -335,13 +415,13 @@ class ExceptionHandler {
// that there is no previous unhandled exception filter.
LPTOP_LEVEL_EXCEPTION_FILTER previous_filter_;
#if _MSC_VER >= 1400 // MSVC 2005/8
//#if _MSC_VER >= 1400 // MSVC 2005/8
// Beginning in VC 8, the CRT provides an invalid parameter handler that will
// be called when some CRT functions are passed invalid parameters. In
// earlier CRTs, the same conditions would cause unexpected behavior or
// crashes.
_invalid_parameter_handler previous_iph_;
#endif // _MSC_VER >= 1400
//#endif // _MSC_VER >= 1400
// The CRT allows you to override the default handler for pure
// virtual function calls.
@@ -392,6 +472,10 @@ class ExceptionHandler {
// to not interfere with debuggers.
bool handle_debug_exceptions_;
// Callers can request additional memory regions to be included in
// the dump.
AppMemoryList app_memory_info_;
// A stack of ExceptionHandler objects that have installed unhandled
// exception filters. This vector is used by HandleException to determine
// which ExceptionHandler object to route an exception to. When an
@@ -411,7 +495,7 @@ class ExceptionHandler {
static CRITICAL_SECTION handler_stack_critical_section_;
// The number of instances of this class.
volatile static LONG instance_count_;
static volatile LONG instance_count_;
// disallow copy ctor and operator=
explicit ExceptionHandler(const ExceptionHandler &);
@@ -420,6 +504,6 @@ class ExceptionHandler {
} // namespace google_breakpad
#pragma warning( pop )
#pragma warning(pop)
#endif // CLIENT_WINDOWS_HANDLER_EXCEPTION_HANDLER_H__

View File

@@ -34,7 +34,7 @@
'targets': [
{
'target_name': 'crash_report_sender',
'type': '<(library)',
'type': 'static_library',
'sources': [
'crash_report_sender.cc',
'crash_report_sender.h',

View File

@@ -42,6 +42,10 @@
#include "client/windows/tests/crash_generation_app/abstract_class.h"
#ifdef __MINGW32__
#define swprintf_s swprintf
#endif
namespace google_breakpad {
const int kMaxLoadString = 100;
@@ -283,6 +287,12 @@ void CrashServerStart() {
}
std::wstring dump_path = L"C:\\Dumps\\";
if (_wmkdir(dump_path.c_str()) && (errno != EEXIST)) {
MessageBoxW(NULL, L"Unable to create dump directory", L"Dumper", MB_OK);
return;
}
crash_server = new CrashGenerationServer(kPipeName,
NULL,
ShowClientConnected,
@@ -345,9 +355,9 @@ void CleanUp() {
// Processes messages for the main window.
//
// WM_COMMAND - process the application menu.
// WM_PAINT - Paint the main window.
// WM_DESTROY - post a quit message and return.
// WM_COMMAND - process the application menu.
// WM_PAINT - Paint the main window.
// WM_DESTROY - post a quit message and return.
LRESULT CALLBACK WndProc(HWND wnd,
UINT message,
WPARAM w_param,
@@ -357,13 +367,7 @@ LRESULT CALLBACK WndProc(HWND wnd,
PAINTSTRUCT ps;
HDC hdc;
#pragma warning(push)
#pragma warning(disable:4312)
// Disable warning C4312: 'type cast' : conversion from 'LONG' to
// 'HINSTANCE' of greater size.
// The value returned by GetwindowLong in the case below returns unsigned.
HINSTANCE instance = (HINSTANCE)GetWindowLong(wnd, GWL_HINSTANCE);
#pragma warning(pop)
HINSTANCE instance = (HINSTANCE)GetWindowLongPtr(wnd, GWLP_HINSTANCE);
switch (message) {
case WM_COMMAND:
@@ -480,9 +484,11 @@ int APIENTRY _tWinMain(HINSTANCE instance,
CustomClientInfo custom_info = {kCustomInfoEntries, kCustomInfoCount};
CrashServerStart();
#ifdef _MSC_VER
// This is needed for CRT to not show dialog for invalid param
// failures and instead let the code handle it.
_CrtSetReportMode(_CRT_ASSERT, 0);
#endif
handler = new ExceptionHandler(L"C:\\dumps\\",
NULL,
google_breakpad::ShowDumpResults,
@@ -501,7 +507,7 @@ int APIENTRY _tWinMain(HINSTANCE instance,
MyRegisterClass(instance);
// Perform application initialization.
if (!InitInstance (instance, command_show)) {
if (!InitInstance(instance, command_show)) {
return FALSE;
}
@@ -518,5 +524,5 @@ int APIENTRY _tWinMain(HINSTANCE instance,
}
}
return (int)msg.wParam;
return static_cast<int>(msg.wParam);
}

View File

@@ -41,7 +41,7 @@
'crash_generation_app.cc',
'crash_generation_app.h',
'crash_generation_app.ico',
'crash_generation_app.rc',
'resource.rc',
'resource.h',
'small.ico',
],
@@ -51,6 +51,30 @@
'../../crash_generation/crash_generation.gyp:crash_generation_client',
'../../handler/exception_handler.gyp:exception_handler',
],
'conditions': [
[ '"<(GENERATOR)" == "make"', {
'ldflags': [
'-Wl,--subsystem=2', '-municode'
],
'rules': [
{ 'rule_name': 'windres',
'extension': 'rc',
'inputs' : [ ],
'outputs' : [ '$(builddir)/<(RULE_INPUT_ROOT).o' ],
'action' : [ '$(RC)', '--input=<(RULE_INPUT_PATH)', '--output=$(builddir)/<(RULE_INPUT_ROOT).o', '--input-format=rc', '--output-format=coff', '-v', '--use-temp-file' ],
'message' : 'Compiling Windows resources',
'process_outputs_as_sources' : 1,
},
],
}
],
[ '"<(GENERATOR)" == "msvs"', {
'libraries': [
'user32.lib',
],
}
]
],
'msvs_settings': {
'VCLinkerTool': {
'SubSystem': '2', # Windows Subsystem as opposed to a console app

View File

@@ -36,8 +36,10 @@
'target_name': 'client_tests',
'type': 'executable',
'sources': [
'exception_handler_test.h',
'exception_handler_test.cc',
'exception_handler_death_test.cc',
'exception_handler_nesting_test.cc',
'minidump_test.cc',
'dump_analysis.cc',
'dump_analysis.h',
@@ -50,8 +52,16 @@
'../crash_generation/crash_generation.gyp:crash_generation_server',
'../crash_generation/crash_generation.gyp:crash_generation_client',
'../handler/exception_handler.gyp:exception_handler',
'processor_bits',
]
'processor_bits',
],
'conditions': [
[ '"<(GENERATOR)" == "make"', {
'libraries': [
'-ldbghelp', '-lversion', '-lpthread',
],
},
],
],
},
{
'target_name': 'processor_bits',

View File

@@ -31,8 +31,8 @@
#include <objbase.h>
#include <dbghelp.h>
#include "dump_analysis.h" // NOLINT
#include "gtest/gtest.h"
#include "client/windows/unittests/dump_analysis.h" // NOLINT
#include "testing/gtest/include/gtest/gtest.h"
DumpAnalysis::~DumpAnalysis() {
if (dump_file_view_ != NULL) {

View File

@@ -30,7 +30,7 @@
#ifndef CLIENT_WINDOWS_UNITTESTS_DUMP_ANALYSIS_H_
#define CLIENT_WINDOWS_UNITTESTS_DUMP_ANALYSIS_H_
#include "../crash_generation/minidump_generator.h"
#include "client/windows/crash_generation/minidump_generator.h"
// Convenience to get to the PEB pointer in a TEB.
struct FakeTEB {

Some files were not shown because too many files have changed in this diff Show More