mirror of
https://github.com/tomahawk-player/tomahawk.git
synced 2025-09-06 04:02:54 +02:00
Compare commits
3 Commits
libfuzzyma
...
breakpad_u
Author | SHA1 | Date | |
---|---|---|---|
|
c823ab0cdf | ||
|
8ffa6634b8 | ||
|
b4f05b0831 |
@@ -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 )
|
||||
|
||||
|
@@ -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";
|
||||
|
@@ -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;
|
||||
|
@@ -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();
|
||||
|
@@ -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)
|
||||
|
||||
|
@@ -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 )
|
||||
|
@@ -1,5 +1,3 @@
|
||||
SET( CMAKE_BUILD_TYPE "Release" )
|
||||
|
||||
ADD_DEFINITIONS( /DNOMINMAX )
|
||||
ADD_DEFINITIONS( /DWIN32_LEAN_AND_MEAN )
|
||||
ADD_DEFINITIONS( -static-libgcc )
|
||||
|
@@ -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;
|
||||
|
39
thirdparty/breakpad/CMakeLists.txt
vendored
39
thirdparty/breakpad/CMakeLists.txt
vendored
@@ -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()
|
||||
|
@@ -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__
|
221
thirdparty/breakpad/client/ios/Breakpad.h
vendored
Normal file
221
thirdparty/breakpad/client/ios/Breakpad.h
vendored
Normal 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
|
837
thirdparty/breakpad/client/ios/Breakpad.mm
vendored
Normal file
837
thirdparty/breakpad/client/ios/Breakpad.mm
vendored
Normal 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, ¶ms, 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,
|
||||
¶ms,
|
||||
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;
|
||||
}
|
||||
}
|
574
thirdparty/breakpad/client/ios/Breakpad.xcodeproj/project.pbxproj
vendored
Normal file
574
thirdparty/breakpad/client/ios/Breakpad.xcodeproj/project.pbxproj
vendored
Normal 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 */;
|
||||
}
|
127
thirdparty/breakpad/client/ios/BreakpadController.h
vendored
Normal file
127
thirdparty/breakpad/client/ios/BreakpadController.h
vendored
Normal 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_
|
318
thirdparty/breakpad/client/ios/BreakpadController.mm
vendored
Normal file
318
thirdparty/breakpad/client/ios/BreakpadController.mm
vendored
Normal 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
|
7
thirdparty/breakpad/client/ios/Breakpad_Prefix.pch
vendored
Normal file
7
thirdparty/breakpad/client/ios/Breakpad_Prefix.pch
vendored
Normal 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
|
74
thirdparty/breakpad/client/ios/handler/ios_exception_minidump_generator.h
vendored
Normal file
74
thirdparty/breakpad/client/ios/handler/ios_exception_minidump_generator.h
vendored
Normal 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_
|
208
thirdparty/breakpad/client/ios/handler/ios_exception_minidump_generator.mm
vendored
Normal file
208
thirdparty/breakpad/client/ios/handler/ios_exception_minidump_generator.mm
vendored
Normal 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
|
@@ -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_;
|
||||
};
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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];
|
||||
|
@@ -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_;
|
||||
|
||||
|
@@ -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, ¤t_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
|
||||
|
@@ -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
@@ -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
|
100
thirdparty/breakpad/client/linux/handler/minidump_descriptor.h
vendored
Normal file
100
thirdparty/breakpad/client/linux/handler/minidump_descriptor.h
vendored
Normal 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_
|
144
thirdparty/breakpad/client/linux/minidump_writer/cpu_set.h
vendored
Normal file
144
thirdparty/breakpad/client/linux/minidump_writer/cpu_set.h
vendored
Normal 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_
|
164
thirdparty/breakpad/client/linux/minidump_writer/cpu_set_unittest.cc
vendored
Normal file
164
thirdparty/breakpad/client/linux/minidump_writer/cpu_set_unittest.cc
vendored
Normal 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());
|
||||
}
|
||||
|
@@ -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:
|
||||
|
@@ -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);
|
||||
|
@@ -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:
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
@@ -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_
|
130
thirdparty/breakpad/client/linux/minidump_writer/proc_cpuinfo_reader.h
vendored
Normal file
130
thirdparty/breakpad/client/linux/minidump_writer/proc_cpuinfo_reader.h
vendored
Normal 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_
|
199
thirdparty/breakpad/client/linux/minidump_writer/proc_cpuinfo_reader_unittest.cc
vendored
Normal file
199
thirdparty/breakpad/client/linux/minidump_writer/proc_cpuinfo_reader_unittest.cc
vendored
Normal 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));
|
||||
}
|
@@ -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.");
|
||||
}
|
||||
|
@@ -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;
|
||||
};
|
||||
|
@@ -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);
|
||||
|
@@ -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 :
|
||||
|
@@ -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(): ");
|
||||
|
||||
|
@@ -31,7 +31,7 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "common/mac/SimpleStringDictionary.h"
|
||||
#include "common/simple_string_dictionary.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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";
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
|
||||
@@ -283,11 +279,11 @@ bool Inspector::InspectTask() {
|
||||
|
||||
NSString *applicationSupportDirectory =
|
||||
[libraryDirectories objectAtIndex:0];
|
||||
NSString *library_subdirectory = [NSString
|
||||
NSString *library_subdirectory = [NSString
|
||||
stringWithUTF8String:kDefaultLibrarySubdirectory];
|
||||
NSString *breakpad_product = [NSString
|
||||
NSString *breakpad_product = [NSString
|
||||
stringWithUTF8String:config_params_.GetValueForKey(BREAKPAD_PRODUCT)];
|
||||
|
||||
|
||||
NSArray *path_components = [NSArray
|
||||
arrayWithObjects:applicationSupportDirectory,
|
||||
library_subdirectory,
|
||||
@@ -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);
|
||||
|
||||
@@ -315,13 +308,8 @@ bool Inspector::InspectTask() {
|
||||
NSString *pathid_ns = [NSString
|
||||
stringWithUTF8String:minidumpLocation.GetID()];
|
||||
NSString *minidumpPath = [path_ns stringByAppendingPathComponent:pathid_ns];
|
||||
minidumpPath = [minidumpPath
|
||||
minidumpPath = [minidumpPath
|
||||
stringByAppendingPathExtension:@"dmp"];
|
||||
|
||||
DEBUGLOG(stderr,
|
||||
"minidump path (%s)\n",
|
||||
[minidumpPath UTF8String]);
|
||||
|
||||
|
||||
config_file_.WriteFile( 0,
|
||||
&config_params_,
|
||||
@@ -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
|
||||
|
@@ -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);
|
||||
|
@@ -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_;
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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_);
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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() {
|
||||
|
@@ -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);
|
||||
|
@@ -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>
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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=
|
||||
@@ -198,7 +213,10 @@ class MinidumpGenerator {
|
||||
static int os_major_version_;
|
||||
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_;
|
||||
|
||||
|
@@ -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_
|
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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>
|
||||
|
@@ -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
|
@@ -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
|
||||
|
@@ -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,
|
||||
|
@@ -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__)
|
||||
|
@@ -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__)
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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': [
|
||||
|
@@ -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': {
|
||||
|
@@ -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 {
|
||||
|
||||
|
@@ -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 {
|
||||
|
@@ -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;
|
||||
|
@@ -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)',
|
||||
],
|
||||
|
@@ -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
|
||||
|
@@ -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_;
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
|
@@ -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() {
|
||||
|
@@ -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_;
|
||||
|
||||
|
@@ -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,16 +868,16 @@ 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;
|
||||
}
|
||||
|
||||
|
||||
// Include all modules.
|
||||
case IncludeModuleCallback:
|
||||
case ModuleCallback:
|
||||
@@ -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
|
||||
|
@@ -34,7 +34,7 @@
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'exception_handler',
|
||||
'type': '<(library)',
|
||||
'type': 'static_library',
|
||||
'sources': [
|
||||
"exception_handler.cc",
|
||||
"exception_handler.h",
|
||||
|
@@ -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__
|
||||
|
@@ -34,7 +34,7 @@
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'crash_report_sender',
|
||||
'type': '<(library)',
|
||||
'type': 'static_library',
|
||||
'sources': [
|
||||
'crash_report_sender.cc',
|
||||
'crash_report_sender.h',
|
||||
|
@@ -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:
|
||||
@@ -415,16 +419,16 @@ LRESULT CALLBACK WndProc(HWND wnd,
|
||||
instance,
|
||||
NULL);
|
||||
break;
|
||||
case WM_SIZE:
|
||||
// Make the edit control the size of the window's client area.
|
||||
MoveWindow(client_status_edit_box,
|
||||
case WM_SIZE:
|
||||
// Make the edit control the size of the window's client area.
|
||||
MoveWindow(client_status_edit_box,
|
||||
0,
|
||||
0,
|
||||
LOWORD(l_param), // width of client area.
|
||||
HIWORD(l_param), // height of client area.
|
||||
TRUE); // repaint window.
|
||||
break;
|
||||
case WM_SETFOCUS:
|
||||
case WM_SETFOCUS:
|
||||
SetFocus(client_status_edit_box);
|
||||
break;
|
||||
case WM_PAINT:
|
||||
@@ -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);
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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',
|
||||
|
@@ -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) {
|
||||
|
@@ -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
Reference in New Issue
Block a user