Android APP Initial Commit

serial
Matthew 2 years ago
parent 743189f8d4
commit 00433db12f

1
app/.gitignore vendored

@ -0,0 +1 @@
/build

@ -0,0 +1,62 @@
plugins {
id 'com.android.application'
}
android {
namespace 'com.xinyingpower.microphoto'
compileSdk 33
defaultConfig {
applicationId "com.xinyingpower.microphoto"
minSdk 26
targetSdk 33
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags '-std=c++17'
abiFilters 'arm64-v8a'
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
externalNativeBuild {
cmake {
path file('src/main/cpp/CMakeLists.txt')
version '3.22.1'
}
}
buildFeatures {
viewBinding true
}
}
dependencies {
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:2.0.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'com.googlecode.mp4parser:isoparser:1.1.21'
}

Binary file not shown.

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

@ -0,0 +1,20 @@
{
"version": 3,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "com.xinyingpower.microphoto",
"variantName": "release",
"elements": [
{
"type": "SINGLE",
"filters": [],
"attributes": [],
"versionCode": 1,
"versionName": "1.0",
"outputFile": "app-release.apk"
}
],
"elementType": "File"
}

@ -0,0 +1,25 @@
package com.xinyingpower.microphoto;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.xinyingpower.microphoto", appContext.getPackageName());
}
}

@ -0,0 +1,121 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission
android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"
tools:ignore="ProtectedPermissions" />
<uses-permission
android:name="android.permission.SET_TIME"
tools:ignore="ProtectedPermissions" />
<uses-permission
android:name="android.permission.CHANGE_CONFIGURATION"
tools:ignore="ProtectedPermissions" />
<uses-permission
android:name="android.permission.SET_TIME_ZONE"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission
android:name="android.permission.REBOOT"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission
android:name="android.permission.WRITE_SETTINGS"
tools:ignore="ProtectedPermissions" />
<uses-permission
android:name="android.permission.INSTALL_PACKAGES"
tools:ignore="ProtectedPermissions" /> <!-- 关机权限 -->
<uses-permission
android:name="android.permission.RECOVERY"
tools:ignore="ProtectedPermissions" />
<uses-permission
android:name="android.permission.SHUTDOWN"
tools:ignore="ProtectedPermissions" /> <!-- 添加访问手机状态的权限 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" /> <!-- 添加手机关机的权限 -->
<uses-permission
android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.RECEIVE_SMS" /> <!-- 接收短信权限 -->
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission
android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"
tools:ignore="ProtectedPermissions" />
<uses-permission
android:name="android.permission.SET_TIME"
tools:ignore="ProtectedPermissions" />
<uses-permission
android:name="android.permission.CHANGE_CONFIGURATION"
tools:ignore="ProtectedPermissions" />
<uses-permission
android:name="android.permission.SET_TIME_ZONE"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.REBOOT" tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.INSTALL_PACKAGES" tools:ignore="ProtectedPermissions" /> <!-- 关机权限 -->
<uses-permission android:name="android.permission.RECOVERY" tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.SHUTDOWN" tools:ignore="ProtectedPermissions" /> <!-- 添加访问手机状态的权限 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" /> <!-- 添加手机关机的权限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.RECEIVE_SMS" /> <!-- 接收短信权限 -->
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission
android:name="android.permission.DEVICE_POWER"
tools:ignore="ProtectedPermissions" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.MicroPhoto"
tools:targetApi="31">
<service
android:name=".MicroPhotoService"
android:enabled="true"
android:exported="true"
android:process="com.xytech.xymp"></service>
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

@ -0,0 +1,176 @@
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.22.1)
add_definitions(-DTERMINAL_CLIENT)
add_definitions(-DKEEP_FRAME_TYPE_ON_REVERSE)
add_definitions(-DBOOST_ALL_NO_LIB)
# Declares and names the project.
project("microphoto")
set(TERM_CORE_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../xymp/Core)
set(BREAKPAD_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/breakpad)
include_directories(${BREAKPAD_ROOT} ${BREAKPAD_ROOT}/common/android/include)
file(GLOB BREAKPAD_SOURCES_COMMON
native-lib.cpp
${BREAKPAD_ROOT}/client/linux/crash_generation/crash_generation_client.cc
${BREAKPAD_ROOT}/client/linux/dump_writer_common/thread_info.cc
${BREAKPAD_ROOT}/client/linux/dump_writer_common/ucontext_reader.cc
${BREAKPAD_ROOT}/client/linux/handler/exception_handler.cc
${BREAKPAD_ROOT}/client/linux/handler/minidump_descriptor.cc
${BREAKPAD_ROOT}/client/linux/log/log.cc
${BREAKPAD_ROOT}/client/linux/microdump_writer/microdump_writer.cc
${BREAKPAD_ROOT}/client/linux/minidump_writer/linux_dumper.cc
${BREAKPAD_ROOT}/client/linux/minidump_writer/linux_ptrace_dumper.cc
${BREAKPAD_ROOT}/client/linux/minidump_writer/minidump_writer.cc
${BREAKPAD_ROOT}/client/linux/minidump_writer/pe_file.cc
${BREAKPAD_ROOT}/client/minidump_file_writer.cc
${BREAKPAD_ROOT}/common/convert_UTF.cc
${BREAKPAD_ROOT}/common/md5.cc
${BREAKPAD_ROOT}/common/string_conversion.cc
${BREAKPAD_ROOT}/common/linux/elfutils.cc
${BREAKPAD_ROOT}/common/linux/file_id.cc
${BREAKPAD_ROOT}/common/linux/guid_creator.cc
${BREAKPAD_ROOT}/common/linux/linux_libc_support.cc
${BREAKPAD_ROOT}/common/linux/memory_mapped_file.cc
${BREAKPAD_ROOT}/common/linux/safe_readlink.cc
)
file(GLOB BREAKPAD_ASM_SOURCE ${BREAKPAD_ROOT}/common/linux/breakpad_getcontext.S)
set_source_files_properties(${BREAKPAD_ASM_SOURCE} PROPERTIES LANGUAGE C)
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
breakpad
# Sets the library as a shared library.
STATIC
# Provides a relative path to your source file(s).
${BREAKPAD_SOURCES_COMMON} ${BREAKPAD_ASM_SOURCE} )
SET(JSONCPP_INCLUDE_DIR jsoncpp/include)
INCLUDE_DIRECTORIES(${JSONCPP_INCLUDE_DIR})
SET(PUBLIC_HEADERS
${JSONCPP_INCLUDE_DIR}/json/config.h
${JSONCPP_INCLUDE_DIR}/json/forwards.h
${JSONCPP_INCLUDE_DIR}/json/features.h
${JSONCPP_INCLUDE_DIR}/json/value.h
${JSONCPP_INCLUDE_DIR}/json/reader.h
${JSONCPP_INCLUDE_DIR}/json/writer.h
${JSONCPP_INCLUDE_DIR}/json/assertions.h
${JSONCPP_INCLUDE_DIR}/json/version.h
)
SET(jsoncpp_sources
jsoncpp/src/lib_json/json_tool.h
jsoncpp/src/lib_json/json_reader.cpp
jsoncpp/src/lib_json/json_valueiterator.inl
jsoncpp/src/lib_json/json_value.cpp
jsoncpp/src/lib_json/json_writer.cpp
jsoncpp/version.in)
include_directories(${TERM_CORE_ROOT})
# include_directories(${PROJECT_SOURCE_DIR}/../../../../../libs/inc/)
IF (CMAKE_HOST_SYSTEM_NAME MATCHES "Windows")
# set(BOOST_ROOT C:/ProgramData/boost_1_82_0/)
# set(BOOST_INCLUDEDIR C:/ProgramData/boost_1_82_0/)
include_directories(C:/ProgramData/boost_1_82_0/)
ELSE()
# find_package(Boost 1.58.0 COMPONENTS)
find_package(Boost 1.58.0)
if(Boost_FOUND)
include_directories(${Boost_INCLUDE_DIRS})
else()
message(FATAL_ERROR "Boost Not Found")
endif()
endif()
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
jsoncpp
# Sets the library as a shared library.
STATIC
# Provides a relative path to your source file(s).
${jsoncpp_sources}
)
add_library( # Sets the name of the library.
microphoto
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
MicroPhoto.cpp
TerminalDevice.cpp
${TERM_CORE_ROOT}/Factory.cpp
${TERM_CORE_ROOT}/FilePoster.cpp
${TERM_CORE_ROOT}/LogThread.cpp
${TERM_CORE_ROOT}/SpecData.cpp
${TERM_CORE_ROOT}/SpecData_I1.cpp
${TERM_CORE_ROOT}/SpecData_I1_AH.cpp
${TERM_CORE_ROOT}/SpecData_I1_HN.cpp
${TERM_CORE_ROOT}/SpecData_XY.cpp
${TERM_CORE_ROOT}/SpecData_ZJ.cpp
${TERM_CORE_ROOT}/TermClient.cpp
${TERM_CORE_ROOT}/Timer.cpp
${TERM_CORE_ROOT}/TimerThread.cpp
${TERM_CORE_ROOT}/Utils.cpp
${TERM_CORE_ROOT}/Client/Terminal.cpp
${TERM_CORE_ROOT}/Client/Terminal_HN.cpp
${TERM_CORE_ROOT}/Client/UpgradeReceiver.cpp
)
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log)
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
microphoto
jsoncpp
#breakpad
# Links the target library to the log library
# included in the NDK.
${log-lib}
)

@ -0,0 +1,55 @@
#include <jni.h>
#include <string>
#include <TermClient.h>
#include "TerminalDevice.h"
// #include "client/linux/handler/exception_handler.h"
// #include "client/linux/handler/minidump_descriptor.h"
extern "C" JNIEXPORT jstring JNICALL
Java_com_xinyingpower_microphoto_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
extern "C" JNIEXPORT jboolean JNICALL
Java_com_xinyingpower_microphoto_MicroPhotoService_init(
JNIEnv* env,
jobject pThis, jstring appPath, jstring ip, jint port, jstring cmdid) {
/*
google_breakpad::MinidumpDescriptor descriptor(".");
google_breakpad::ExceptionHandler eh(descriptor, NULL, NULL,
NULL, true, -1);
*/
const char *appPathStr = env->GetStringUTFChars(appPath, 0);
const char *ipStr = env->GetStringUTFChars(ip, 0);
const char *cmdidStr = env->GetStringUTFChars(cmdid, 0);
JavaVM* vm = NULL;
jint ret = env->GetJavaVM(&vm);
// const string& appPath, const string& termId, const string& server, unsigned short port, const string& bindIp
CTermClient& service = CTermClient::GetService();
CTerminalDevice* device = new CTerminalDevice(vm, pThis);
bool res = service.InitService(appPathStr, cmdidStr, ipStr, (unsigned short)port, "", device);
env->ReleaseStringUTFChars(appPath, appPathStr);
env->ReleaseStringUTFChars(ip, ipStr);
env->ReleaseStringUTFChars(cmdid, cmdidStr);
return res ? JNI_TRUE : JNI_FALSE;
}
extern "C" JNIEXPORT jboolean JNICALL
Java_com_xinyingpower_microphoto_MicroPhotoService_uninit(
JNIEnv* env,
jobject pThis) {
CTermClient::GetService().ExitService();
return JNI_TRUE;
}

@ -0,0 +1,72 @@
#include "TerminalDevice.h"
#include <dlfcn.h>
typedef jbyteArray (*TakePhotoFunc)(int, int, int, int);
bool GetJniEnv(JavaVM *vm, JNIEnv **env)
{
bool did_attach_thread = false;
*env = nullptr;
// Check if the current thread is attached to the VM
auto get_env_result = vm->GetEnv((void**)env, JNI_VERSION_1_6);
if (get_env_result == JNI_EDETACHED)
{
if (vm->AttachCurrentThread(env, NULL) == JNI_OK) {
did_attach_thread = true;
} else {
// Failed to attach thread. Throw an exception if you want to.
}
} else if (get_env_result == JNI_EVERSION)
{
// Unsupported JNI version. Throw an exception if you want to.
}
return did_attach_thread;
}
CTerminalDevice::CTerminalDevice(JavaVM* vm, jobject service)
{
m_vm = vm;
JNIEnv* env = NULL;
bool attached = GetJniEnv(m_vm, &env);
m_javaService = env->NewGlobalRef(service);
if (attached)
{
vm->DetachCurrentThread();
}
}
CTerminalDevice::~CTerminalDevice()
{
JNIEnv* env = NULL;
bool attached = GetJniEnv(m_vm, &env);
env->DeleteGlobalRef(m_javaService);
if (attached)
{
m_vm->DetachCurrentThread();
}
m_javaService = NULL;
}
bool CTerminalDevice::TakePhoto(unsigned char channel, unsigned char preset, const string& path, bool photo)
{
JNIEnv* env = NULL;
bool attached = GetJniEnv(m_vm, &env);
jclass serviceClass = env->GetObjectClass(m_javaService);
jmethodID mid = env->GetMethodID(serviceClass, "takePhoto", "(SSLjava/lang/String;)Z");
jstring str = env->NewStringUTF(path.c_str());
jboolean res = env->CallBooleanMethod (m_javaService, mid, (jint)channel, (jint)preset, str);
env->ReleaseStringUTFChars(str, path.c_str());
env->DeleteLocalRef(serviceClass);
if (!res)
{
int aa = 1;
}
if (attached)
{
m_vm->DetachCurrentThread();
}
return res == JNI_TRUE;
}

@ -0,0 +1,21 @@
#ifndef __TERMINAL_DEVICE_H__
#define __TERMINAL_DEVICE_H__
#include <Client/Device.h>
#include <jni.h>
class CTerminalDevice : public IDevice
{
public:
CTerminalDevice(JavaVM* vm, jobject service);
~CTerminalDevice();
virtual bool TakePhoto(unsigned char channel, unsigned char preset, const string& path, bool photo);
private:
JavaVM* m_vm;
jobject m_javaService;
};
#endif // __TERMINAL_DEVICE_H__

@ -0,0 +1,56 @@
// Copyright 2009 Google LLC
//
// 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 LLC 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 BREAKPAD_GOOGLETEST_INCLUDES_H__
#define BREAKPAD_GOOGLETEST_INCLUDES_H__
#include "gtest/gtest.h"
#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 // BREAKPAD_GOOGLETEST_INCLUDES_H__

@ -0,0 +1,72 @@
// Copyright 2011 Google LLC
//
// 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 LLC 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.
// Keys for configuration file
#define kReporterMinidumpDirectoryKey "MinidumpDir"
#define kReporterMinidumpIDKey "MinidumpID"
// Filename for recording uploaded IDs
#define kReporterLogFilename "uploads.log"
// The default subdirectory of the Library to put crash dumps in
// The subdirectory is
// ~/Library/<kDefaultLibrarySubdirectory>/<GoogleBreakpadProduct>
#define kDefaultLibrarySubdirectory "Breakpad"
// Specify some special keys to be used in the configuration file that is
// generated by Breakpad and consumed by the crash_sender.
#define BREAKPAD_PRODUCT "BreakpadProduct"
#define BREAKPAD_PRODUCT_DISPLAY "BreakpadProductDisplay"
#define BREAKPAD_VERSION "BreakpadVersion"
#define BREAKPAD_VENDOR "BreakpadVendor"
#define BREAKPAD_URL "BreakpadURL"
#define BREAKPAD_REPORT_INTERVAL "BreakpadReportInterval"
#define BREAKPAD_SKIP_CONFIRM "BreakpadSkipConfirm"
#define BREAKPAD_CONFIRM_TIMEOUT "BreakpadConfirmTimeout"
#define BREAKPAD_SEND_AND_EXIT "BreakpadSendAndExit"
#define BREAKPAD_DUMP_DIRECTORY "BreakpadMinidumpLocation"
#define BREAKPAD_INSPECTOR_LOCATION "BreakpadInspectorLocation"
#define BREAKPAD_REPORTER_EXE_LOCATION \
"BreakpadReporterExeLocation"
#define BREAKPAD_LOGFILES "BreakpadLogFiles"
#define BREAKPAD_LOGFILE_UPLOAD_SIZE "BreakpadLogFileTailSize"
#define BREAKPAD_REQUEST_COMMENTS "BreakpadRequestComments"
#define BREAKPAD_COMMENTS "BreakpadComments"
#define BREAKPAD_REQUEST_EMAIL "BreakpadRequestEmail"
#define BREAKPAD_EMAIL "BreakpadEmail"
#define BREAKPAD_SERVER_TYPE "BreakpadServerType"
#define BREAKPAD_SERVER_PARAMETER_DICT "BreakpadServerParameters"
#define BREAKPAD_IN_PROCESS "BreakpadInProcess"
// The keys below are NOT user supplied, and are used internally.
#define BREAKPAD_PROCESS_START_TIME "BreakpadProcStartTime"
#define BREAKPAD_PROCESS_UP_TIME "BreakpadProcessUpTime"
#define BREAKPAD_PROCESS_CRASH_TIME "BreakpadProcessCrashTime"
#define BREAKPAD_LOGFILE_KEY_PREFIX "BreakpadAppLogFile"
#define BREAKPAD_SERVER_PARAMETER_PREFIX "BreakpadServerParameterPrefix_"
#define BREAKPAD_ON_DEMAND "BreakpadOnDemand"

@ -0,0 +1,259 @@
// Copyright 2011 Google LLC
//
// 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 LLC 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);
// Optional user-defined function that will be called after a network upload
// of a crash report.
// |report_id| will be the id returned by the server, or "ERR" if an error
// occurred.
// |error| will contain the error, or nil if no error occured.
typedef void (*BreakpadUploadCompletionCallback)(NSString* report_id,
NSError* error);
// 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);
// Returns the next upload configuration. The report file is deleted.
NSDictionary* BreakpadGetNextReportConfiguration(BreakpadRef ref);
// Returns the date of the most recent crash report.
NSDate* BreakpadGetDateOfMostRecentCrashReport(BreakpadRef ref);
// Upload next report to the server.
void BreakpadUploadNextReport(BreakpadRef ref);
// Upload next report to the server.
// |server_parameters| is additional server parameters to send.
void BreakpadUploadNextReportWithParameters(
BreakpadRef ref,
NSDictionary* server_parameters,
BreakpadUploadCompletionCallback callback);
// Upload a report to the server.
// |server_parameters| is additional server parameters to send.
// |configuration| is the configuration of the breakpad report to send.
void BreakpadUploadReportWithParametersAndConfiguration(
BreakpadRef ref,
NSDictionary* server_parameters,
NSDictionary* configuration,
BreakpadUploadCompletionCallback callback);
// Handles the network response of a breakpad upload. This function is needed if
// the actual upload is done by the Breakpad client.
// |configuration| is the configuration of the upload. It must contain the same
// fields as the configuration passed to
// BreakpadUploadReportWithParametersAndConfiguration.
// |data| and |error| contain the network response.
void BreakpadHandleNetworkResponse(BreakpadRef ref,
NSDictionary* configuration,
NSData* data,
NSError* error);
// 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

@ -0,0 +1,986 @@
// Copyright 2011 Google LLC
//
// 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 LLC 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>
#include <TargetConditionals.h>
#include <string>
#import "client/ios/handler/ios_exception_minidump_generator.h"
#import "client/mac/crash_generation/ConfigFile.h"
#import "client/mac/handler/minidump_generator.h"
#import "client/mac/handler/protected_memory_allocator.h"
#import "client/mac/sender/uploader.h"
#import "common/long_string_dictionary.h"
#if !TARGET_OS_TV && !TARGET_OS_WATCH
#import "client/mac/handler/exception_handler.h"
#else
#import "client/ios/exception_handler_no_mach.h"
#endif // !TARGET_OS_TV && !TARGET_OS_WATCH
#if !defined(__EXCEPTIONS) || (__clang__ && !__has_feature(cxx_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::LongStringDictionary;
//=============================================================================
// 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
__attribute__((unused)) 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
__attribute__((unused)) 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();
NSDictionary* NextCrashReportConfiguration();
NSDictionary* FixedUpCrashReportConfiguration(NSDictionary* configuration);
NSDate* DateOfMostRecentCrashReport();
void UploadNextReport(NSDictionary* server_parameters);
void UploadReportWithConfiguration(NSDictionary* configuration,
NSDictionary* server_parameters,
BreakpadUploadCompletionCallback callback);
void UploadData(NSData* data, NSString* name,
NSDictionary* server_parameters);
void HandleNetworkResponse(NSDictionary* configuration,
NSData* data,
NSError* error);
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)
LongStringDictionary* 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);
BreakpadRelease(current_breakpad_);
}
}
//=============================================================================
#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_->~LongStringDictionary();
}
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.length) // Default nil or empty string to CFBundleVersion
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(LongStringDictionary)))
LongStringDictionary();
LongStringDictionary& 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 std::string value = config_params_->GetValueForKey([key UTF8String]);
return value.empty() ? nil : [NSString stringWithUTF8String:value.c_str()];
}
//=============================================================================
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];
}
//=============================================================================
NSDictionary* Breakpad::NextCrashReportConfiguration() {
NSDictionary* configuration = [Uploader readConfigurationDataFromFile:NextCrashReportToUpload()];
return FixedUpCrashReportConfiguration(configuration);
}
//=============================================================================
NSDictionary* Breakpad::FixedUpCrashReportConfiguration(NSDictionary* configuration) {
NSMutableDictionary* fixedConfiguration = [[configuration mutableCopy] autorelease];
// kReporterMinidumpDirectoryKey can become stale because the app's data container path includes
// an UUID that is not guaranteed to stay the same over time.
[fixedConfiguration setObject:KeyValue(@BREAKPAD_DUMP_DIRECTORY)
forKey:@kReporterMinidumpDirectoryKey];
return fixedConfiguration;
}
//=============================================================================
NSDate* Breakpad::DateOfMostRecentCrashReport() {
NSString* directory = KeyValue(@BREAKPAD_DUMP_DIRECTORY);
if (!directory) {
return nil;
}
NSFileManager* fileManager = [NSFileManager defaultManager];
NSArray* dirContents = [fileManager contentsOfDirectoryAtPath:directory error:nil];
NSArray* dumps = [dirContents filteredArrayUsingPredicate:[NSPredicate
predicateWithFormat:@"self ENDSWITH '.dmp'"]];
NSDate* mostRecentCrashReportDate = nil;
for (NSString* dump in dumps) {
NSString* filePath = [directory stringByAppendingPathComponent:dump];
NSDate* crashReportDate =
[[fileManager attributesOfItemAtPath:filePath error:nil] fileCreationDate];
if (!mostRecentCrashReportDate) {
mostRecentCrashReportDate = crashReportDate;
} else if (crashReportDate) {
mostRecentCrashReportDate = [mostRecentCrashReportDate laterDate:crashReportDate];
}
}
return mostRecentCrashReportDate;
}
//=============================================================================
void Breakpad::HandleNetworkResponse(NSDictionary* configuration,
NSData* data,
NSError* error) {
Uploader* uploader = [[[Uploader alloc]
initWithConfig:configuration] autorelease];
[uploader handleNetworkResponse:data withError:error];
}
//=============================================================================
void Breakpad::UploadReportWithConfiguration(
NSDictionary* configuration,
NSDictionary* server_parameters,
BreakpadUploadCompletionCallback callback) {
Uploader* uploader = [[[Uploader alloc]
initWithConfig:configuration] autorelease];
if (!uploader)
return;
for (NSString* key in server_parameters) {
[uploader addServerParameter:[server_parameters objectForKey:key]
forKey:key];
}
if (callback) {
[uploader setUploadCompletionBlock:^(NSString* report_id, NSError* error) {
dispatch_async(dispatch_get_main_queue(), ^{
callback(report_id, error);
});
}];
}
[uploader report];
}
//=============================================================================
void Breakpad::UploadNextReport(NSDictionary* server_parameters) {
NSDictionary* configuration = NextCrashReportConfiguration();
if (configuration) {
return UploadReportWithConfiguration(configuration, server_parameters,
nullptr);
}
}
//=============================================================================
void Breakpad::UploadData(NSData* data, NSString* name,
NSDictionary* server_parameters) {
NSMutableDictionary* config = [NSMutableDictionary dictionary];
LongStringDictionary::Iterator it(*config_params_);
while (const LongStringDictionary::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;
LongStringDictionary params = *config_params_;
for (NSString* key in server_parameters) {
params.SetKeyValue([key UTF8String],
[[server_parameters objectForKey:key] UTF8String]);
}
ConfigFile config_file;
config_file.WriteFile(dumpDir, &params, dumpDir, dumpId.c_str());
// Handle results.
NSMutableDictionary* result = [NSMutableDictionary dictionary];
NSString* dumpFullPath = [NSString stringWithUTF8String:dumpFilename.c_str()];
[result setValue:dumpFullPath
forKey:@BREAKPAD_OUTPUT_DUMP_FILE];
[result setValue:[NSString stringWithUTF8String:config_file.GetFilePath()]
forKey:@BREAKPAD_OUTPUT_CONFIG_FILE];
return result;
}
//=============================================================================
bool Breakpad::HandleMinidump(const char* dump_dir,
const char* minidump_id) {
config_file_.WriteFile(dump_dir,
config_params_,
dump_dir,
minidump_id);
// Return true here to indicate that we've processed things as much as we
// want.
return true;
}
//=============================================================================
void Breakpad::HandleUncaughtException(NSException* exception) {
// Generate the minidump.
google_breakpad::IosExceptionMinidumpGenerator generator(exception);
const std::string 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.
LongStringDictionary 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.c_str(),
&params,
minidump_path.c_str(),
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(LongStringDictionary));
// Create a mutex for use in accessing the LongStringDictionary
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 static_cast<int>([breakpad->CrashReportsToUpload() count]);
}
} catch(...) { // don't let exceptions leave this C API
fprintf(stderr, "BreakpadGetCrashReportCount() : error\n");
}
return false;
}
//=============================================================================
void BreakpadUploadNextReport(BreakpadRef ref) {
BreakpadUploadNextReportWithParameters(ref, nil, nullptr);
}
//=============================================================================
NSDictionary* BreakpadGetNextReportConfiguration(BreakpadRef ref) {
try {
Breakpad* breakpad = (Breakpad*)ref;
if (breakpad)
return breakpad->NextCrashReportConfiguration();
} catch(...) { // don't let exceptions leave this C API
fprintf(stderr, "BreakpadGetNextReportConfiguration() : error\n");
}
return nil;
}
//=============================================================================
NSDate* BreakpadGetDateOfMostRecentCrashReport(BreakpadRef ref) {
try {
Breakpad* breakpad = (Breakpad*)ref;
if (breakpad) {
return breakpad->DateOfMostRecentCrashReport();
}
} catch (...) { // don't let exceptions leave this C API
fprintf(stderr, "BreakpadGetDateOfMostRecentCrashReport() : error\n");
}
return nil;
}
//=============================================================================
void BreakpadUploadReportWithParametersAndConfiguration(
BreakpadRef ref,
NSDictionary* server_parameters,
NSDictionary* configuration,
BreakpadUploadCompletionCallback callback) {
try {
Breakpad* breakpad = (Breakpad*)ref;
if (!breakpad || !configuration)
return;
breakpad->UploadReportWithConfiguration(configuration, server_parameters,
callback);
} catch(...) { // don't let exceptions leave this C API
fprintf(stderr,
"BreakpadUploadReportWithParametersAndConfiguration() : error\n");
}
}
//=============================================================================
void BreakpadUploadNextReportWithParameters(
BreakpadRef ref,
NSDictionary* server_parameters,
BreakpadUploadCompletionCallback callback) {
try {
Breakpad* breakpad = (Breakpad*)ref;
if (!breakpad)
return;
NSDictionary* configuration = breakpad->NextCrashReportConfiguration();
if (!configuration)
return;
return BreakpadUploadReportWithParametersAndConfiguration(
ref, server_parameters, configuration, callback);
} catch(...) { // don't let exceptions leave this C API
fprintf(stderr, "BreakpadUploadNextReportWithParameters() : error\n");
}
}
void BreakpadHandleNetworkResponse(BreakpadRef ref,
NSDictionary* configuration,
NSData* data,
NSError* error) {
try {
// Not called at exception time
Breakpad* breakpad = (Breakpad*)ref;
if (breakpad && configuration)
breakpad->HandleNetworkResponse(configuration,data, error);
} catch(...) { // don't let exceptions leave this C API
fprintf(stderr, "BreakpadHandleNetworkResponse() : 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;
}
}

@ -0,0 +1,604 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
06D561E62700974500F9F2E8 /* encoding_util.h in Headers */ = {isa = PBXBuildFile; fileRef = 06D561E42700974500F9F2E8 /* encoding_util.h */; };
06D561E72700974500F9F2E8 /* encoding_util.m in Sources */ = {isa = PBXBuildFile; fileRef = 06D561E52700974500F9F2E8 /* encoding_util.m */; };
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.cc in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CC4A147D4A4300776EAD /* convert_UTF.cc */; };
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 */; };
CF6D547D1F9E6FFE00E95174 /* long_string_dictionary.cc in Sources */ = {isa = PBXBuildFile; fileRef = CF6D547C1F9E6FFE00E95174 /* long_string_dictionary.cc */; };
CF706DC11F7C6EFB002C54C7 /* long_string_dictionary.h in Headers */ = {isa = PBXBuildFile; fileRef = CF706DC01F7C6EFB002C54C7 /* long_string_dictionary.h */; };
E69213D8265202570071B04F /* HTTPRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = E69213D6265202570071B04F /* HTTPRequest.h */; };
E69213D9265202570071B04F /* HTTPRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = E69213D7265202570071B04F /* HTTPRequest.m */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
06D561E42700974500F9F2E8 /* encoding_util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = encoding_util.h; sourceTree = "<group>"; };
06D561E52700974500F9F2E8 /* encoding_util.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = encoding_util.m; sourceTree = "<group>"; };
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.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = convert_UTF.cc; 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; };
CF6D547C1F9E6FFE00E95174 /* long_string_dictionary.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = long_string_dictionary.cc; sourceTree = "<group>"; };
CF706DC01F7C6EFB002C54C7 /* long_string_dictionary.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = long_string_dictionary.h; sourceTree = "<group>"; };
D2AAC07E0554694100DB518D /* libBreakpad.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libBreakpad.a; sourceTree = BUILT_PRODUCTS_DIR; };
E69213D6265202570071B04F /* HTTPRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPRequest.h; sourceTree = "<group>"; };
E69213D7265202570071B04F /* HTTPRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPRequest.m; sourceTree = "<group>"; };
/* 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 = (
CF706DC01F7C6EFB002C54C7 /* long_string_dictionary.h */,
CF6D547C1F9E6FFE00E95174 /* long_string_dictionary.cc */,
1EEEB60C1720821900F7E689 /* simple_string_dictionary.cc */,
1EEEB60D1720821900F7E689 /* simple_string_dictionary.h */,
16C7CC4A147D4A4300776EAD /* convert_UTF.cc */,
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 = (
06D561E42700974500F9F2E8 /* encoding_util.h */,
06D561E52700974500F9F2E8 /* encoding_util.m */,
16C7CC88147D4A4300776EAD /* GTMLogger.h */,
16C7CC89147D4A4300776EAD /* GTMLogger.m */,
E69213D6265202570071B04F /* HTTPRequest.h */,
E69213D7265202570071B04F /* HTTPRequest.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 */,
06D561E62700974500F9F2E8 /* encoding_util.h in Headers */,
16C7CE41147D4A4300776EAD /* convert_UTF.h in Headers */,
16C7CE78147D4A4300776EAD /* GTMLogger.h in Headers */,
E69213D8265202570071B04F /* HTTPRequest.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 */,
CF706DC11F7C6EFB002C54C7 /* long_string_dictionary.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 = {
LastUpgradeCheck = 0510;
};
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 */,
E69213D9265202570071B04F /* HTTPRequest.m 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 */,
CF6D547D1F9E6FFE00E95174 /* long_string_dictionary.cc in Sources */,
16C7CE19147D4A4300776EAD /* minidump_file_writer.cc in Sources */,
16C7CE40147D4A4300776EAD /* convert_UTF.cc in Sources */,
16C7CE79147D4A4300776EAD /* GTMLogger.m in Sources */,
06D561E72700974500F9F2E8 /* encoding_util.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;
CLANG_CXX_LIBRARY = "libc++";
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;
CLANG_CXX_LIBRARY = "libc++";
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 = {
CLANG_CXX_LANGUAGE_STANDARD = "c++0x";
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,
);
IPHONEOS_DEPLOYMENT_TARGET = 5.0;
ONLY_ACTIVE_ARCH = YES;
OTHER_LDFLAGS = "-ObjC";
SDKROOT = iphoneos;
WARNING_CFLAGS = "-Wundef";
};
name = Debug;
};
1DEB922408733DC00010E9CD /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "c++0x";
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,
);
IPHONEOS_DEPLOYMENT_TARGET = 5.0;
OTHER_LDFLAGS = "-ObjC";
SDKROOT = iphoneos;
WARNING_CFLAGS = "-Wundef";
};
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 */;
}

@ -0,0 +1,153 @@
// Copyright 2012 Google LLC
//
// 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 LLC 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_;
// The callback to call on report upload completion.
BreakpadUploadCompletionCallback uploadCompleteCallback_;
}
// 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;
// Sets the callback to be called after uploading a crash report to the server.
// Only the latest callback registered will be called.
- (void)setUploadCallback:(BreakpadUploadCompletionCallback)callback;
// 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;
// Returns whether or not the controller is started.
- (BOOL)isStarted;
// 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;
// Get the next report to upload.
// - If upload is disabled, callback will be called with (nil, -1).
// - If a delay is to be waited before sending, callback will be called with
// (nil, n), with n (> 0) being the number of seconds to wait.
// - if no delay is needed, callback will be called with (0, configuration),
// configuration being next report to upload, or nil if none is pending.
- (void)getNextReportConfigurationOrSendDelay:
(void(^)(NSDictionary*, int))callback;
// Get the date of the most recent crash report.
- (void)getDateOfMostRecentCrashReport:(void(^)(NSDate *))callback;
// Sends synchronously the report specified by |configuration|. This method is
// NOT thread safe and must be called from the breakpad thread.
- (void)threadUnsafeSendReportWithConfiguration:(NSDictionary*)configuration
withBreakpadRef:(BreakpadRef)ref;
@end
#endif // CLIENT_IOS_HANDLER_IOS_BREAKPAD_CONTROLLER_H_

@ -0,0 +1,373 @@
// Copyright 2012 Google LLC
//
// 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 LLC 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 {
static dispatch_once_t onceToken;
static BreakpadController* sharedInstance ;
dispatch_once(&onceToken, ^{
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;
}
});
}
- (BOOL)isStarted {
return started_;
}
// This method must be called from the breakpad queue.
- (void)threadUnsafeSendReportWithConfiguration:(NSDictionary*)configuration
withBreakpadRef:(BreakpadRef)ref {
NSAssert(started_, @"The controller must be started before "
"threadUnsafeSendReportWithConfiguration is called");
if (breakpadRef_) {
BreakpadUploadReportWithParametersAndConfiguration(
breakpadRef_, uploadTimeParameters_, configuration,
uploadCompleteCallback_);
}
}
- (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 {
// disable the enableUpload_ flag.
// sendDelay checks this flag and disables the upload of logs by sendStoredCrashReports
enableUploads_ = NO;
}
});
}
- (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)setUploadCallback:(BreakpadUploadCompletionCallback)callback {
NSAssert(started_,
@"The controller must not be started before setUploadCallback is "
"called");
dispatch_async(queue_, ^{
uploadCompleteCallback_ = callback;
});
}
- (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 {
dispatch_async(queue_, ^{
callback(started_ ? breakpadRef_ : NULL);
});
}
- (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);
});
}
- (void)getNextReportConfigurationOrSendDelay:
(void(^)(NSDictionary*, int))callback {
NSAssert(started_, @"The controller must be started before "
"getNextReportConfigurationOrSendDelay is called");
dispatch_async(queue_, ^{
if (!breakpadRef_) {
callback(nil, -1);
return;
}
int delay = [self sendDelay];
if (delay != 0) {
callback(nil, delay);
return;
}
[self reportWillBeSent];
callback(BreakpadGetNextReportConfiguration(breakpadRef_), 0);
});
}
- (void)getDateOfMostRecentCrashReport:(void(^)(NSDate *))callback {
NSAssert(started_, @"The controller must be started before "
"getDateOfMostRecentCrashReport is called");
dispatch_async(queue_, ^{
if (!breakpadRef_) {
callback(nil);
return;
}
callback(BreakpadGetDateOfMostRecentCrashReport(breakpadRef_));
});
}
#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];
}
// This method must be called from the breakpad queue.
- (void)sendStoredCrashReports {
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];
BreakpadUploadNextReportWithParameters(breakpadRef_, uploadTimeParameters_,
uploadCompleteCallback_);
// 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) {
dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeToWait * NSEC_PER_SEC));
dispatch_after(delay, queue_, ^{
[self sendStoredCrashReports];
});
}
}
@end

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

@ -0,0 +1,261 @@
// Copyright 2006 Google LLC
//
// 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 LLC 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 <signal.h>
#include <TargetConditionals.h>
#include "client/mac/handler/minidump_generator.h"
#include "client/ios/exception_handler_no_mach.h"
#ifndef USE_PROTECTED_ALLOCATIONS
#if TARGET_OS_TV
#define USE_PROTECTED_ALLOCATIONS 1
#else
#define USE_PROTECTED_ALLOCATIONS 0
#endif
#endif
// If USE_PROTECTED_ALLOCATIONS is activated then the
// gBreakpadAllocator needs to be setup in other code
// ahead of time. Please see ProtectedMemoryAllocator.h
// for more details.
#if USE_PROTECTED_ALLOCATIONS
#include "client/mac/handler/protected_memory_allocator.h"
extern ProtectedMemoryAllocator* gBreakpadAllocator;
#endif
namespace google_breakpad {
const int kExceptionSignals[] = {
// Core-generating signals.
SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGQUIT, SIGSEGV, SIGSYS, SIGTRAP, SIGEMT,
SIGXCPU, SIGXFSZ,
// Non-core-generating but terminating signals.
SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGPROF, SIGTERM, SIGUSR1, SIGUSR2,
SIGVTALRM, SIGXCPU, SIGXFSZ, SIGIO,
};
const int kNumHandledSignals =
sizeof(kExceptionSignals) / sizeof(kExceptionSignals[0]);
struct scoped_ptr<struct sigaction> old_handlers[kNumHandledSignals];
static union {
#if USE_PROTECTED_ALLOCATIONS
#if defined PAGE_MAX_SIZE
char protected_buffer[PAGE_MAX_SIZE] __attribute__((aligned(PAGE_MAX_SIZE)));
#else
char protected_buffer[PAGE_SIZE] __attribute__((aligned(PAGE_SIZE)));
#endif // defined PAGE_MAX_SIZE
#endif // USE_PROTECTED_ALLOCATIONS
google_breakpad::ExceptionHandler* handler;
} gProtectedData;
ExceptionHandler::ExceptionHandler(const string& dump_path,
FilterCallback filter,
MinidumpCallback callback,
void* callback_context,
bool install_handler,
const char* port_name)
: dump_path_(),
filter_(filter),
callback_(callback),
callback_context_(callback_context),
directCallback_(NULL),
installed_exception_handler_(false),
is_in_teardown_(false) {
// This will update to the ID and C-string pointers
set_dump_path(dump_path);
MinidumpGenerator::GatherSystemInformation();
Setup();
}
// 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,
bool install_handler)
: dump_path_(),
filter_(NULL),
callback_(NULL),
callback_context_(callback_context),
directCallback_(callback),
installed_exception_handler_(false),
is_in_teardown_(false) {
MinidumpGenerator::GatherSystemInformation();
Setup();
}
ExceptionHandler::~ExceptionHandler() {
Teardown();
}
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 !TARGET_OS_TV
exit_after_write = false;
#endif // !TARGET_OS_TV
if (directCallback_) {
if (directCallback_(callback_context_,
exception_type,
exception_code,
exception_subcode,
thread_name) ) {
if (exit_after_write)
_exit(exception_type);
}
} else {
string minidump_id;
// Putting the MinidumpGenerator in its own context will ensure that the
// destructor is executed, closing the newly created minidump file.
if (!dump_path_.empty()) {
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.
if (filter_ && !filter_(callback_context_))
return false;
md.SetExceptionInformation(exception_type, exception_code,
exception_subcode, thread_name);
}
result = md.Write(next_minidump_path_c_);
}
// Call user specified callback (if any)
if (callback_) {
// If the user callback returned true and we're handling an exception
// (rather than just writing out the file), then we should exit without
// forwarding the exception to the next handler.
if (callback_(dump_path_c_, next_minidump_id_c_, callback_context_,
result)) {
if (exit_after_write)
_exit(exception_type);
}
}
}
return result;
}
// static
void ExceptionHandler::SignalHandler(int sig, siginfo_t* info, void* uc) {
#if USE_PROTECTED_ALLOCATIONS
if (gBreakpadAllocator)
gBreakpadAllocator->Unprotect();
#endif
gProtectedData.handler->WriteMinidumpWithException(
EXC_SOFTWARE,
MD_EXCEPTION_CODE_MAC_ABORT,
0,
static_cast<breakpad_ucontext_t*>(uc),
mach_thread_self(),
true,
true);
#if USE_PROTECTED_ALLOCATIONS
if (gBreakpadAllocator)
gBreakpadAllocator->Protect();
#endif
}
bool ExceptionHandler::InstallHandlers() {
// If a handler is already installed, something is really wrong.
if (gProtectedData.handler != NULL)
return false;
for (int i = 0; i < kNumHandledSignals; ++i) {
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask, kExceptionSignals[i]);
sa.sa_sigaction = ExceptionHandler::SignalHandler;
sa.sa_flags = SA_ONSTACK | SA_SIGINFO;
if (sigaction(kExceptionSignals[i], &sa, old_handlers[i].get()) == -1) {
return false;
}
}
gProtectedData.handler = this;
#if USE_PROTECTED_ALLOCATIONS
assert(((size_t)(gProtectedData.protected_buffer) & PAGE_MASK) == 0);
mprotect(gProtectedData.protected_buffer, PAGE_SIZE, PROT_READ);
#endif // USE_PROTECTED_ALLOCATIONS
installed_exception_handler_ = true;
return true;
}
bool ExceptionHandler::UninstallHandlers() {
for (int i = 0; i < kNumHandledSignals; ++i) {
if (old_handlers[i].get()) {
sigaction(kExceptionSignals[i], old_handlers[i].get(), NULL);
old_handlers[i].reset();
}
}
#if USE_PROTECTED_ALLOCATIONS
mprotect(gProtectedData.protected_buffer, PAGE_SIZE, PROT_READ | PROT_WRITE);
#endif // USE_PROTECTED_ALLOCATIONS
gProtectedData.handler = NULL;
installed_exception_handler_ = false;
return true;
}
bool ExceptionHandler::Setup() {
if (!InstallHandlers())
return false;
return true;
}
bool ExceptionHandler::Teardown() {
is_in_teardown_ = true;
if (!UninstallHandlers())
return false;
return true;
}
void ExceptionHandler::UpdateNextID() {
next_minidump_path_ =
(MinidumpGenerator::UniqueNameInDirectory(dump_path_, &next_minidump_id_));
next_minidump_path_c_ = next_minidump_path_.c_str();
next_minidump_id_c_ = next_minidump_id_.c_str();
}
} // namespace google_breakpad

@ -0,0 +1,178 @@
// Copyright 2006 Google LLC
//
// 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 LLC 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_EXCEPTION_HANDLER_NO_MACH_H__
#define CLIENT_IOS_HANDLER_EXCEPTION_HANDLER_NO_MACH_H__
#include <mach/mach.h>
#include <TargetConditionals.h>
#include <string>
#include "client/mac/handler/ucontext_compat.h"
#include "common/scoped_ptr.h"
namespace google_breakpad {
using std::string;
class ExceptionHandler {
public:
// A callback function to run before Breakpad performs any substantial
// processing of an exception. A FilterCallback is called before writing
// a minidump. context is the parameter supplied by the user as
// callback_context when the handler was created.
//
// If a FilterCallback returns true, Breakpad will continue processing,
// attempting to write a minidump. If a FilterCallback returns false, Breakpad
// will immediately report the exception as unhandled without writing a
// minidump, allowing another handler the opportunity to handle it.
typedef bool (*FilterCallback)(void* context);
// A callback function to run after the minidump has been written.
// |minidump_id| is a unique id for the dump, so the minidump
// file is <dump_dir>/<minidump_id>.dmp.
// |context| is the value passed into the constructor.
// |succeeded| indicates whether a minidump file was successfully written.
// Return true if the exception was fully handled and breakpad should exit.
// Return false to allow any other exception handlers to process the
// exception.
typedef bool (*MinidumpCallback)(const char* dump_dir,
const char* minidump_id,
void* context, bool succeeded);
// A callback function which will be called directly if an exception occurs.
// This bypasses the minidump file writing and simply gives the client
// the exception information.
typedef bool (*DirectCallback)(void* context,
int exception_type,
int exception_code,
int exception_subcode,
mach_port_t thread_name);
// Creates a new ExceptionHandler instance to handle writing minidumps.
// Minidump files will be written to dump_path, and the optional callback
// is called after writing the dump file, as described above.
// If install_handler is true, then a minidump will be written whenever
// an unhandled exception occurs. If it is false, minidumps will only
// be written when WriteMinidump is called.
// If port_name is non-NULL, attempt to perform out-of-process dump generation
// If port_name is NULL, in-process dump generation will be used.
ExceptionHandler(const string& dump_path,
FilterCallback filter, MinidumpCallback callback,
void* callback_context, bool install_handler,
const char* port_name);
// A special constructor if we want to bypass minidump writing and
// simply get a callback with the exception information.
ExceptionHandler(DirectCallback callback,
void* callback_context,
bool install_handler);
~ExceptionHandler();
// Get and set the minidump path.
string dump_path() const { return dump_path_; }
void set_dump_path(const string& dump_path) {
dump_path_ = dump_path;
dump_path_c_ = dump_path_.c_str();
UpdateNextID(); // Necessary to put dump_path_ in next_minidump_path_.
}
private:
// Install the SIG exception handlers.
bool InstallHandlers();
// Uninstall the SIG exception handlers.
bool UninstallHandlers();
// Setup the handler thread, and if |install_handler| is true, install the
// mach exception port handler
bool Setup();
// Uninstall the mach exception handler (if any) and terminate the helper
// thread
bool Teardown();
// 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);
// Signal handler for SIG exceptions.
static void SignalHandler(int sig, siginfo_t* info, void* uc);
// disallow copy ctor and operator=
explicit ExceptionHandler(const ExceptionHandler&);
void operator=(const ExceptionHandler&);
// 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();
// The destination directory for the minidump
string dump_path_;
// The basename of the next minidump w/o extension
string next_minidump_id_;
// The full path to the next minidump to be written, including extension
string next_minidump_path_;
// Pointers to the UTF-8 versions of above
const char* dump_path_c_;
const char* next_minidump_id_c_;
const char* next_minidump_path_c_;
// The callback function and pointer to be passed back after the minidump
// has been written
FilterCallback filter_;
MinidumpCallback callback_;
void* callback_context_;
// The callback function to be passed back when we don't want a minidump
// file to be written
DirectCallback directCallback_;
// True, if we've installed the exception handler
bool installed_exception_handler_;
// True, if we're in the process of uninstalling the exception handler and
// the thread.
bool is_in_teardown_;
};
} // namespace google_breakpad
#endif // CLIENT_IOS_HANDLER_EXCEPTION_HANDLER_NO_MACH_H__

@ -0,0 +1,73 @@
// Copyright 2012 Google LLC
//
// 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 LLC 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_

@ -0,0 +1,209 @@
// Copyright 2012 Google LLC
//
// 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 LLC 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_Old> context(&writer_);
if (!context.Allocate())
return false;
*register_location = context.location();
MDRawContextARM64_Old* context_ptr = context.get();
memset(context_ptr, 0, sizeof(*context_ptr));
context_ptr->context_flags = MD_CONTEXT_ARM64_FULL_OLD;
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];
if (frame_count == 0)
return false;
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 (size_t 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

@ -0,0 +1,52 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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_CRASH_GENERATION_CLIENT_INFO_H_
#define CLIENT_LINUX_CRASH_GENERATION_CLIENT_INFO_H_
namespace google_breakpad {
class CrashGenerationServer;
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_;
};
}
#endif // CLIENT_LINUX_CRASH_GENERATION_CLIENT_INFO_H_

@ -0,0 +1,104 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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/linux/crash_generation/crash_generation_client.h"
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <algorithm>
#include "common/linux/eintr_wrapper.h"
#include "common/linux/ignore_ret.h"
#include "third_party/lss/linux_syscall_support.h"
namespace google_breakpad {
namespace {
class CrashGenerationClientImpl : public CrashGenerationClient {
public:
explicit CrashGenerationClientImpl(int server_fd) : server_fd_(server_fd) {}
virtual ~CrashGenerationClientImpl() {}
virtual bool RequestDump(const void* blob, size_t blob_size) {
int fds[2];
if (sys_pipe(fds) < 0)
return false;
static const unsigned kControlMsgSize = CMSG_SPACE(sizeof(int));
struct kernel_iovec iov;
iov.iov_base = const_cast<void*>(blob);
iov.iov_len = blob_size;
struct kernel_msghdr msg = { 0 };
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
char cmsg[kControlMsgSize] = "";
msg.msg_control = cmsg;
msg.msg_controllen = sizeof(cmsg);
struct cmsghdr* hdr = CMSG_FIRSTHDR(&msg);
hdr->cmsg_level = SOL_SOCKET;
hdr->cmsg_type = SCM_RIGHTS;
hdr->cmsg_len = CMSG_LEN(sizeof(int));
int* p = reinterpret_cast<int*>(CMSG_DATA(hdr));
*p = fds[1];
ssize_t ret = HANDLE_EINTR(sys_sendmsg(server_fd_, &msg, 0));
sys_close(fds[1]);
if (ret < 0) {
sys_close(fds[0]);
return false;
}
// Wait for an ACK from the server.
char b;
IGNORE_RET(HANDLE_EINTR(sys_read(fds[0], &b, 1)));
sys_close(fds[0]);
return true;
}
private:
int server_fd_;
DISALLOW_COPY_AND_ASSIGN(CrashGenerationClientImpl);
};
} // namespace
// static
CrashGenerationClient* CrashGenerationClient::TryCreate(int server_fd) {
if (server_fd < 0)
return NULL;
return new CrashGenerationClientImpl(server_fd);
}
} // namespace google_breakpad

@ -0,0 +1,64 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_
#define CLIENT_LINUX_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_
#include "common/basictypes.h"
#include <stddef.h>
namespace google_breakpad {
// CrashGenerationClient is an interface for implementing out-of-process crash
// dumping. The default implementation, accessed via the TryCreate() factory,
// works in conjunction with the CrashGenerationServer to generate a minidump
// via a remote process.
class CrashGenerationClient {
public:
CrashGenerationClient() {}
virtual ~CrashGenerationClient() {}
// Request the crash server to generate a dump. |blob| is an opaque
// CrashContext pointer from exception_handler.h.
// Returns true if the dump was successful; false otherwise.
virtual bool RequestDump(const void* blob, size_t blob_size) = 0;
// Returns a new CrashGenerationClient if |server_fd| is valid and
// connects to a CrashGenerationServer. Otherwise, return NULL.
// The returned CrashGenerationClient* is owned by the caller of
// this function.
static CrashGenerationClient* TryCreate(int server_fd);
private:
DISALLOW_COPY_AND_ASSIGN(CrashGenerationClient);
};
} // namespace google_breakpad
#endif // CLIENT_LINUX_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_

@ -0,0 +1,332 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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 <assert.h>
#include <dirent.h>
#include <fcntl.h>
#include <limits.h>
#include <poll.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <vector>
#include "client/linux/crash_generation/crash_generation_server.h"
#include "client/linux/crash_generation/client_info.h"
#include "client/linux/handler/exception_handler.h"
#include "client/linux/minidump_writer/minidump_writer.h"
#include "common/linux/eintr_wrapper.h"
#include "common/linux/guid_creator.h"
#include "common/linux/safe_readlink.h"
static const char kCommandQuit = 'x';
namespace google_breakpad {
CrashGenerationServer::CrashGenerationServer(
const int listen_fd,
OnClientDumpRequestCallback dump_callback,
void* dump_context,
OnClientExitingCallback exit_callback,
void* exit_context,
bool generate_dumps,
const string* dump_path) :
server_fd_(listen_fd),
dump_callback_(dump_callback),
dump_context_(dump_context),
exit_callback_(exit_callback),
exit_context_(exit_context),
generate_dumps_(generate_dumps),
started_(false)
{
if (dump_path)
dump_dir_ = *dump_path;
else
dump_dir_ = "/tmp";
}
CrashGenerationServer::~CrashGenerationServer()
{
if (started_)
Stop();
}
bool
CrashGenerationServer::Start()
{
if (started_ || 0 > server_fd_)
return false;
int control_pipe[2];
if (pipe(control_pipe))
return false;
if (fcntl(control_pipe[0], F_SETFD, FD_CLOEXEC))
return false;
if (fcntl(control_pipe[1], F_SETFD, FD_CLOEXEC))
return false;
if (fcntl(control_pipe[0], F_SETFL, O_NONBLOCK))
return false;
control_pipe_in_ = control_pipe[0];
control_pipe_out_ = control_pipe[1];
if (pthread_create(&thread_, NULL,
ThreadMain, reinterpret_cast<void*>(this)))
return false;
started_ = true;
return true;
}
void
CrashGenerationServer::Stop()
{
assert(pthread_self() != thread_);
if (!started_)
return;
HANDLE_EINTR(write(control_pipe_out_, &kCommandQuit, 1));
void* dummy;
pthread_join(thread_, &dummy);
close(control_pipe_in_);
close(control_pipe_out_);
started_ = false;
}
//static
bool
CrashGenerationServer::CreateReportChannel(int* server_fd, int* client_fd)
{
int fds[2];
if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds))
return false;
static const int on = 1;
// Enable passcred on the server end of the socket
if (setsockopt(fds[1], SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)))
return false;
if (fcntl(fds[1], F_SETFL, O_NONBLOCK))
return false;
if (fcntl(fds[1], F_SETFD, FD_CLOEXEC))
return false;
*client_fd = fds[0];
*server_fd = fds[1];
return true;
}
// The following methods/functions execute on the server thread
void
CrashGenerationServer::Run()
{
struct pollfd pollfds[2];
memset(&pollfds, 0, sizeof(pollfds));
pollfds[0].fd = server_fd_;
pollfds[0].events = POLLIN;
pollfds[1].fd = control_pipe_in_;
pollfds[1].events = POLLIN;
while (true) {
// infinite timeout
int nevents = poll(pollfds, sizeof(pollfds)/sizeof(pollfds[0]), -1);
if (-1 == nevents) {
if (EINTR == errno) {
continue;
} else {
return;
}
}
if (pollfds[0].revents && !ClientEvent(pollfds[0].revents))
return;
if (pollfds[1].revents && !ControlEvent(pollfds[1].revents))
return;
}
}
bool
CrashGenerationServer::ClientEvent(short revents)
{
if (POLLHUP & revents)
return false;
assert(POLLIN & revents);
// A process has crashed and has signaled us by writing a datagram
// to the death signal socket. The datagram contains the crash context needed
// for writing the minidump as well as a file descriptor and a credentials
// block so that they can't lie about their pid.
// The length of the control message:
static const unsigned kControlMsgSize =
CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(struct ucred));
// The length of the regular payload:
static const unsigned kCrashContextSize =
sizeof(google_breakpad::ExceptionHandler::CrashContext);
struct msghdr msg = {0};
struct iovec iov[1];
char crash_context[kCrashContextSize];
char control[kControlMsgSize];
const ssize_t expected_msg_size = sizeof(crash_context);
iov[0].iov_base = crash_context;
iov[0].iov_len = sizeof(crash_context);
msg.msg_iov = iov;
msg.msg_iovlen = sizeof(iov)/sizeof(iov[0]);
msg.msg_control = control;
msg.msg_controllen = kControlMsgSize;
const ssize_t msg_size = HANDLE_EINTR(recvmsg(server_fd_, &msg, 0));
if (msg_size != expected_msg_size)
return true;
if (msg.msg_controllen != kControlMsgSize ||
msg.msg_flags & ~MSG_TRUNC)
return true;
// Walk the control payload and extract the file descriptor and validated pid.
pid_t crashing_pid = -1;
int signal_fd = -1;
for (struct cmsghdr* hdr = CMSG_FIRSTHDR(&msg); hdr;
hdr = CMSG_NXTHDR(&msg, hdr)) {
if (hdr->cmsg_level != SOL_SOCKET)
continue;
if (hdr->cmsg_type == SCM_RIGHTS) {
const unsigned len = hdr->cmsg_len -
(((uint8_t*)CMSG_DATA(hdr)) - (uint8_t*)hdr);
assert(len % sizeof(int) == 0u);
const unsigned num_fds = len / sizeof(int);
if (num_fds > 1 || num_fds == 0) {
// A nasty process could try and send us too many descriptors and
// force a leak.
for (unsigned i = 0; i < num_fds; ++i)
close(reinterpret_cast<int*>(CMSG_DATA(hdr))[i]);
return true;
} else {
signal_fd = reinterpret_cast<int*>(CMSG_DATA(hdr))[0];
}
} else if (hdr->cmsg_type == SCM_CREDENTIALS) {
const struct ucred* cred =
reinterpret_cast<struct ucred*>(CMSG_DATA(hdr));
crashing_pid = cred->pid;
}
}
if (crashing_pid == -1 || signal_fd == -1) {
if (signal_fd != -1)
close(signal_fd);
return true;
}
string minidump_filename;
if (!MakeMinidumpFilename(minidump_filename))
return true;
if (!google_breakpad::WriteMinidump(minidump_filename.c_str(),
crashing_pid, crash_context,
kCrashContextSize)) {
close(signal_fd);
return true;
}
if (dump_callback_) {
ClientInfo info(crashing_pid, this);
dump_callback_(dump_context_, &info, &minidump_filename);
}
// Send the done signal to the process: it can exit now.
// (Closing this will make the child's sys_read unblock and return 0.)
close(signal_fd);
return true;
}
bool
CrashGenerationServer::ControlEvent(short revents)
{
if (POLLHUP & revents)
return false;
assert(POLLIN & revents);
char command;
if (read(control_pipe_in_, &command, 1))
return false;
switch (command) {
case kCommandQuit:
return false;
default:
assert(0);
}
return true;
}
bool
CrashGenerationServer::MakeMinidumpFilename(string& outFilename)
{
GUID guid;
char guidString[kGUIDStringLength+1];
if (!(CreateGUID(&guid)
&& GUIDToString(&guid, guidString, sizeof(guidString))))
return false;
char path[PATH_MAX];
snprintf(path, sizeof(path), "%s/%s.dmp", dump_dir_.c_str(), guidString);
outFilename = path;
return true;
}
// static
void*
CrashGenerationServer::ThreadMain(void* arg)
{
reinterpret_cast<CrashGenerationServer*>(arg)->Run();
return NULL;
}
} // namespace google_breakpad

@ -0,0 +1,134 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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_CRASH_GENERATION_CRASH_GENERATION_SERVER_H_
#define CLIENT_LINUX_CRASH_GENERATION_CRASH_GENERATION_SERVER_H_
#include <pthread.h>
#include <string>
#include "common/using_std_string.h"
namespace google_breakpad {
class ClientInfo;
class CrashGenerationServer {
public:
// WARNING: callbacks may be invoked on a different thread
// than that which creates the CrashGenerationServer. They must
// be thread safe.
typedef void (*OnClientDumpRequestCallback)(void* context,
const ClientInfo* client_info,
const string* file_path);
typedef void (*OnClientExitingCallback)(void* context,
const ClientInfo* client_info);
// Create an instance with the given parameters.
//
// Parameter listen_fd: The server fd created by CreateReportChannel().
// Parameter dump_callback: Callback for a client crash dump request.
// Parameter dump_context: Context for client crash dump request callback.
// Parameter exit_callback: Callback for client process exit.
// Parameter exit_context: Context for client exit callback.
// Parameter generate_dumps: Whether to automatically generate dumps.
// Client code of this class might want to generate dumps explicitly
// in the crash dump request callback. In that case, false can be
// passed for this parameter.
// Parameter dump_path: Path for generating dumps; required only if true is
// passed for generateDumps parameter; NULL can be passed otherwise.
CrashGenerationServer(const int listen_fd,
OnClientDumpRequestCallback dump_callback,
void* dump_context,
OnClientExitingCallback exit_callback,
void* exit_context,
bool generate_dumps,
const string* dump_path);
~CrashGenerationServer();
// Perform initialization steps needed to start listening to clients.
//
// Return true if initialization is successful; false otherwise.
bool Start();
// Stop the server.
void Stop();
// Create a "channel" that can be used by clients to report crashes
// to a CrashGenerationServer. |*server_fd| should be passed to
// this class's constructor, and |*client_fd| should be passed to
// the ExceptionHandler constructor in the client process.
static bool CreateReportChannel(int* server_fd, int* client_fd);
private:
// Run the server's event loop
void Run();
// Invoked when an child process (client) event occurs
// Returning true => "keep running", false => "exit loop"
bool ClientEvent(short revents);
// Invoked when the controlling thread (main) event occurs
// Returning true => "keep running", false => "exit loop"
bool ControlEvent(short revents);
// Return a unique filename at which a minidump can be written
bool MakeMinidumpFilename(string& outFilename);
// Trampoline to |Run()|
static void* ThreadMain(void* arg);
int server_fd_;
OnClientDumpRequestCallback dump_callback_;
void* dump_context_;
OnClientExitingCallback exit_callback_;
void* exit_context_;
bool generate_dumps_;
string dump_dir_;
bool started_;
pthread_t thread_;
int control_pipe_in_;
int control_pipe_out_;
// disable these
CrashGenerationServer(const CrashGenerationServer&);
CrashGenerationServer& operator=(const CrashGenerationServer&);
};
} // namespace google_breakpad
#endif // CLIENT_LINUX_CRASH_GENERATION_CRASH_GENERATION_SERVER_H_

@ -0,0 +1,3 @@
MODULE Linux x86 B8CFDE93002D54DA1900A40AA1BD67690 linux-gate.so
PUBLIC 400 0 __kernel_vsyscall
STACK WIN 4 400 100 1 1 0 0 0 0 0 1

@ -0,0 +1,3 @@
MODULE Linux x86 4FBDA58B5A1DF5A379E3CF19A235EA090 linux-gate.so
PUBLIC 400 0 __kernel_vsyscall
STACK WIN 4 400 200 3 3 0 0 0 0 0 1

@ -0,0 +1,73 @@
// Copyright 2014 Google LLC
//
// 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 LLC 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_DUMP_WRITER_COMMON_MAPPING_INFO_H_
#define CLIENT_LINUX_DUMP_WRITER_COMMON_MAPPING_INFO_H_
#include <limits.h>
#include <list>
#include <stdint.h>
#include "google_breakpad/common/minidump_format.h"
namespace google_breakpad {
// One of these is produced for each mapping in the process (i.e. line in
// /proc/$x/maps).
struct MappingInfo {
// On Android, relocation packing can mean that the reported start
// address of the mapping must be adjusted by a bias in order to
// compensate for the compression of the relocation section. The
// following two members hold (after LateInit) the adjusted mapping
// range. See crbug.com/606972 for more information.
uintptr_t start_addr;
size_t size;
// When Android relocation packing causes |start_addr| and |size| to
// be modified with a load bias, we need to remember the unbiased
// address range. The following structure holds the original mapping
// address range as reported by the operating system.
struct {
uintptr_t start_addr;
uintptr_t end_addr;
} system_mapping_info;
size_t offset; // offset into the backed file.
bool exec; // true if the mapping has the execute bit set.
char name[NAME_MAX];
};
struct MappingEntry {
MappingInfo first;
uint8_t second[sizeof(MDGUID)];
};
// A list of <MappingInfo, GUID>
typedef std::list<MappingEntry> MappingList;
} // namespace google_breakpad
#endif // CLIENT_LINUX_DUMP_WRITER_COMMON_MAPPING_INFO_H_

@ -0,0 +1,60 @@
// Copyright 2014 Google LLC
//
// 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 LLC 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_DUMP_WRITER_COMMON_RAW_CONTEXT_CPU_H
#define CLIENT_LINUX_DUMP_WRITER_COMMON_RAW_CONTEXT_CPU_H
#include "google_breakpad/common/minidump_format.h"
namespace google_breakpad {
#if defined(__i386__)
typedef MDRawContextX86 RawContextCPU;
#elif defined(__x86_64)
typedef MDRawContextAMD64 RawContextCPU;
#elif defined(__ARM_EABI__)
typedef MDRawContextARM RawContextCPU;
#elif defined(__aarch64__)
typedef MDRawContextARM64_Old RawContextCPU;
#elif defined(__mips__)
typedef MDRawContextMIPS RawContextCPU;
#elif defined(__riscv)
# if __riscv_xlen == 32
typedef MDRawContextRISCV RawContextCPU;
# elif __riscv_xlen == 64
typedef MDRawContextRISCV64 RawContextCPU;
# else
# error "Unexpected __riscv_xlen"
# endif
#else
#error "This code has not been ported to your platform yet."
#endif
} // namespace google_breakpad
#endif // CLIENT_LINUX_DUMP_WRITER_COMMON_RAW_CONTEXT_CPU_H

@ -0,0 +1,395 @@
// Copyright 2014 Google LLC
//
// 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 LLC 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/linux/dump_writer_common/thread_info.h"
#include <string.h>
#include <assert.h>
#include "common/linux/linux_libc_support.h"
#include "google_breakpad/common/minidump_format.h"
namespace {
#if defined(__i386__)
// Write a uint16_t to memory
// out: memory location to write to
// v: value to write.
void U16(void* out, uint16_t v) {
my_memcpy(out, &v, sizeof(v));
}
// Write a uint32_t to memory
// out: memory location to write to
// v: value to write.
void U32(void* out, uint32_t v) {
my_memcpy(out, &v, sizeof(v));
}
#endif
}
namespace google_breakpad {
#if defined(__i386__)
uintptr_t ThreadInfo::GetInstructionPointer() const {
return regs.eip;
}
void ThreadInfo::FillCPUContext(RawContextCPU* out) const {
out->context_flags = MD_CONTEXT_X86_ALL;
out->dr0 = dregs[0];
out->dr1 = dregs[1];
out->dr2 = dregs[2];
out->dr3 = dregs[3];
// 4 and 5 deliberatly omitted because they aren't included in the minidump
// format.
out->dr6 = dregs[6];
out->dr7 = dregs[7];
out->gs = regs.xgs;
out->fs = regs.xfs;
out->es = regs.xes;
out->ds = regs.xds;
out->edi = regs.edi;
out->esi = regs.esi;
out->ebx = regs.ebx;
out->edx = regs.edx;
out->ecx = regs.ecx;
out->eax = regs.eax;
out->ebp = regs.ebp;
out->eip = regs.eip;
out->cs = regs.xcs;
out->eflags = regs.eflags;
out->esp = regs.esp;
out->ss = regs.xss;
out->float_save.control_word = fpregs.cwd;
out->float_save.status_word = fpregs.swd;
out->float_save.tag_word = fpregs.twd;
out->float_save.error_offset = fpregs.fip;
out->float_save.error_selector = fpregs.fcs;
out->float_save.data_offset = fpregs.foo;
out->float_save.data_selector = fpregs.fos;
// 8 registers * 10 bytes per register.
my_memcpy(out->float_save.register_area, fpregs.st_space, 10 * 8);
// This matches the Intel fpsave format.
U16(out->extended_registers + 0, fpregs.cwd);
U16(out->extended_registers + 2, fpregs.swd);
U16(out->extended_registers + 4, fpregs.twd);
U16(out->extended_registers + 6, fpxregs.fop);
U32(out->extended_registers + 8, fpxregs.fip);
U16(out->extended_registers + 12, fpxregs.fcs);
U32(out->extended_registers + 16, fpregs.foo);
U16(out->extended_registers + 20, fpregs.fos);
U32(out->extended_registers + 24, fpxregs.mxcsr);
my_memcpy(out->extended_registers + 32, &fpxregs.st_space, 128);
my_memcpy(out->extended_registers + 160, &fpxregs.xmm_space, 128);
}
#elif defined(__x86_64)
uintptr_t ThreadInfo::GetInstructionPointer() const {
return regs.rip;
}
void ThreadInfo::FillCPUContext(RawContextCPU* out) const {
out->context_flags = MD_CONTEXT_AMD64_FULL |
MD_CONTEXT_AMD64_SEGMENTS;
out->cs = regs.cs;
out->ds = regs.ds;
out->es = regs.es;
out->fs = regs.fs;
out->gs = regs.gs;
out->ss = regs.ss;
out->eflags = regs.eflags;
out->dr0 = dregs[0];
out->dr1 = dregs[1];
out->dr2 = dregs[2];
out->dr3 = dregs[3];
// 4 and 5 deliberatly omitted because they aren't included in the minidump
// format.
out->dr6 = dregs[6];
out->dr7 = dregs[7];
out->rax = regs.rax;
out->rcx = regs.rcx;
out->rdx = regs.rdx;
out->rbx = regs.rbx;
out->rsp = regs.rsp;
out->rbp = regs.rbp;
out->rsi = regs.rsi;
out->rdi = regs.rdi;
out->r8 = regs.r8;
out->r9 = regs.r9;
out->r10 = regs.r10;
out->r11 = regs.r11;
out->r12 = regs.r12;
out->r13 = regs.r13;
out->r14 = regs.r14;
out->r15 = regs.r15;
out->rip = regs.rip;
out->flt_save.control_word = fpregs.cwd;
out->flt_save.status_word = fpregs.swd;
out->flt_save.tag_word = fpregs.ftw;
out->flt_save.error_opcode = fpregs.fop;
out->flt_save.error_offset = fpregs.rip;
out->flt_save.error_selector = 0; // We don't have this.
out->flt_save.data_offset = fpregs.rdp;
out->flt_save.data_selector = 0; // We don't have this.
out->flt_save.mx_csr = fpregs.mxcsr;
out->flt_save.mx_csr_mask = fpregs.mxcr_mask;
my_memcpy(&out->flt_save.float_registers, &fpregs.st_space, 8 * 16);
my_memcpy(&out->flt_save.xmm_registers, &fpregs.xmm_space, 16 * 16);
}
#elif defined(__ARM_EABI__)
uintptr_t ThreadInfo::GetInstructionPointer() const {
return regs.uregs[15];
}
void ThreadInfo::FillCPUContext(RawContextCPU* out) const {
out->context_flags = MD_CONTEXT_ARM_FULL;
for (int i = 0; i < MD_CONTEXT_ARM_GPR_COUNT; ++i)
out->iregs[i] = regs.uregs[i];
// No CPSR register in ThreadInfo(it's not accessible via ptrace)
out->cpsr = 0;
#if !defined(__ANDROID__)
out->float_save.fpscr = fpregs.fpsr |
(static_cast<uint64_t>(fpregs.fpcr) << 32);
// TODO: sort this out, actually collect floating point registers
my_memset(&out->float_save.regs, 0, sizeof(out->float_save.regs));
my_memset(&out->float_save.extra, 0, sizeof(out->float_save.extra));
#endif
}
#elif defined(__aarch64__)
uintptr_t ThreadInfo::GetInstructionPointer() const {
return regs.pc;
}
void ThreadInfo::FillCPUContext(RawContextCPU* out) const {
out->context_flags = MD_CONTEXT_ARM64_FULL_OLD;
out->cpsr = static_cast<uint32_t>(regs.pstate);
for (int i = 0; i < MD_CONTEXT_ARM64_REG_SP; ++i)
out->iregs[i] = regs.regs[i];
out->iregs[MD_CONTEXT_ARM64_REG_SP] = regs.sp;
out->iregs[MD_CONTEXT_ARM64_REG_PC] = regs.pc;
out->float_save.fpsr = fpregs.fpsr;
out->float_save.fpcr = fpregs.fpcr;
my_memcpy(&out->float_save.regs, &fpregs.vregs,
MD_FLOATINGSAVEAREA_ARM64_FPR_COUNT * 16);
}
#elif defined(__mips__)
uintptr_t ThreadInfo::GetInstructionPointer() const {
return mcontext.pc;
}
void ThreadInfo::FillCPUContext(RawContextCPU* out) const {
#if _MIPS_SIM == _ABI64
out->context_flags = MD_CONTEXT_MIPS64_FULL;
#elif _MIPS_SIM == _ABIO32
out->context_flags = MD_CONTEXT_MIPS_FULL;
#else
# error "This mips ABI is currently not supported (n32)"
#endif
for (int i = 0; i < MD_CONTEXT_MIPS_GPR_COUNT; ++i)
out->iregs[i] = mcontext.gregs[i];
out->mdhi = mcontext.mdhi;
out->mdlo = mcontext.mdlo;
out->dsp_control = mcontext.dsp;
out->hi[0] = mcontext.hi1;
out->lo[0] = mcontext.lo1;
out->hi[1] = mcontext.hi2;
out->lo[1] = mcontext.lo2;
out->hi[2] = mcontext.hi3;
out->lo[2] = mcontext.lo3;
out->epc = mcontext.pc;
out->badvaddr = 0; // Not stored in mcontext
out->status = 0; // Not stored in mcontext
out->cause = 0; // Not stored in mcontext
for (int i = 0; i < MD_FLOATINGSAVEAREA_MIPS_FPR_COUNT; ++i)
out->float_save.regs[i] = mcontext.fpregs.fp_r.fp_fregs[i]._fp_fregs;
out->float_save.fpcsr = mcontext.fpc_csr;
#if _MIPS_SIM == _ABIO32
out->float_save.fir = mcontext.fpc_eir;
#endif
}
#elif defined(__riscv)
uintptr_t ThreadInfo::GetInstructionPointer() const {
return mcontext.__gregs[0];
}
void ThreadInfo::FillCPUContext(RawContextCPU* out) const {
# if __riscv__xlen == 32
out->context_flags = MD_CONTEXT_RISCV_FULL;
# elif __riscv_xlen == 64
out->context_flags = MD_CONTEXT_RISCV64_FULL;
# else
# error "Unexpected __riscv_xlen"
# endif
out->pc = mcontext.__gregs[0];
out->ra = mcontext.__gregs[1];
out->sp = mcontext.__gregs[2];
out->gp = mcontext.__gregs[3];
out->tp = mcontext.__gregs[4];
out->t0 = mcontext.__gregs[5];
out->t1 = mcontext.__gregs[6];
out->t2 = mcontext.__gregs[7];
out->s0 = mcontext.__gregs[8];
out->s1 = mcontext.__gregs[9];
out->a0 = mcontext.__gregs[10];
out->a1 = mcontext.__gregs[11];
out->a2 = mcontext.__gregs[12];
out->a3 = mcontext.__gregs[13];
out->a4 = mcontext.__gregs[14];
out->a5 = mcontext.__gregs[15];
out->a6 = mcontext.__gregs[16];
out->a7 = mcontext.__gregs[17];
out->s2 = mcontext.__gregs[18];
out->s3 = mcontext.__gregs[19];
out->s4 = mcontext.__gregs[20];
out->s5 = mcontext.__gregs[21];
out->s6 = mcontext.__gregs[22];
out->s7 = mcontext.__gregs[23];
out->s8 = mcontext.__gregs[24];
out->s9 = mcontext.__gregs[25];
out->s10 = mcontext.__gregs[26];
out->s11 = mcontext.__gregs[27];
out->t3 = mcontext.__gregs[28];
out->t4 = mcontext.__gregs[29];
out->t5 = mcontext.__gregs[30];
out->t6 = mcontext.__gregs[31];
# if __riscv_flen == 32
for(int i = 0; i < MD_FLOATINGSAVEAREA_RISCV_FPR_COUNT; i++)
out->float_save.regs[i] = mcontext.__fpregs.__f.__f[i];
out->float_save.fpcsr = mcontext.__fpregs.__f.__fcsr;
# elif __riscv_flen == 64
for(int i = 0; i < MD_FLOATINGSAVEAREA_RISCV_FPR_COUNT; i++)
out->float_save.regs[i] = mcontext.__fpregs.__d.__f[i];
out->float_save.fpcsr = mcontext.__fpregs.__d.__fcsr;
# elif __riscv_flen == 128
for(int i = 0; i < MD_FLOATINGSAVEAREA_RISCV_FPR_COUNT; i++) {
out->float_save.regs[i].high = mcontext.__fpregs.__q.__f[2*i];
out->float_save.regs[i].low = mcontext.__fpregs.__q.__f[2*i+1];
}
out->float_save.fpcsr = mcontext.__fpregs.__q.__fcsr;
# else
# error "Unexpected __riscv_flen"
# endif
}
#endif // __riscv
void ThreadInfo::GetGeneralPurposeRegisters(void** gp_regs, size_t* size) {
assert(gp_regs || size);
#if defined(__mips__)
if (gp_regs)
*gp_regs = mcontext.gregs;
if (size)
*size = sizeof(mcontext.gregs);
#elif defined(__riscv)
if (gp_regs)
*gp_regs = mcontext.__gregs;
if (size)
*size = sizeof(mcontext.__gregs);
#else
if (gp_regs)
*gp_regs = &regs;
if (size)
*size = sizeof(regs);
#endif
}
void ThreadInfo::GetFloatingPointRegisters(void** fp_regs, size_t* size) {
assert(fp_regs || size);
#if defined(__mips__)
if (fp_regs)
*fp_regs = &mcontext.fpregs;
if (size)
*size = sizeof(mcontext.fpregs);
#elif defined(__riscv)
# if __riscv_flen == 32
if (fp_regs)
*fp_regs = &mcontext.__fpregs.__f.__f;
if (size)
*size = sizeof(mcontext.__fpregs.__f.__f);
# elif __riscv_flen == 64
if (fp_regs)
*fp_regs = &mcontext.__fpregs.__d.__f;
if (size)
*size = sizeof(mcontext.__fpregs.__d.__f);
# elif __riscv_flen == 128
if (fp_regs)
*fp_regs = &mcontext.__fpregs.__q.__f;
if (size)
*size = sizeof(mcontext.__fpregs.__q.__f);
# else
# error "Unexpected __riscv_flen"
# endif
#else
if (fp_regs)
*fp_regs = &fpregs;
if (size)
*size = sizeof(fpregs);
#endif
}
} // namespace google_breakpad

@ -0,0 +1,90 @@
// Copyright 2014 Google LLC
//
// 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 LLC 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_DUMP_WRITER_COMMON_THREAD_INFO_H_
#define CLIENT_LINUX_DUMP_WRITER_COMMON_THREAD_INFO_H_
#include <sys/ucontext.h>
#include <sys/user.h>
#include "client/linux/dump_writer_common/raw_context_cpu.h"
#include "common/memory_allocator.h"
#include "google_breakpad/common/minidump_format.h"
namespace google_breakpad {
#if defined(__i386) || defined(__x86_64)
typedef __typeof__(((struct user*) 0)->u_debugreg[0]) debugreg_t;
#endif
// We produce one of these structures for each thread in the crashed process.
struct ThreadInfo {
pid_t tgid; // thread group id
pid_t ppid; // parent process
uintptr_t stack_pointer; // thread stack pointer
#if defined(__i386) || defined(__x86_64)
user_regs_struct regs;
user_fpregs_struct fpregs;
static const unsigned kNumDebugRegisters = 8;
debugreg_t dregs[8];
#if defined(__i386)
user_fpxregs_struct fpxregs;
#endif // defined(__i386)
#elif defined(__ARM_EABI__)
// Mimicking how strace does this(see syscall.c, search for GETREGS)
struct user_regs regs;
struct user_fpregs fpregs;
#elif defined(__aarch64__)
// Use the structures defined in <sys/user.h>
struct user_regs_struct regs;
struct user_fpsimd_struct fpregs;
#elif defined(__mips__) || defined(__riscv)
// Use the structure defined in <sys/ucontext.h>.
mcontext_t mcontext;
#endif
// Returns the instruction pointer (platform-dependent impl.).
uintptr_t GetInstructionPointer() const;
// Fills a RawContextCPU using the context in the ThreadInfo object.
void FillCPUContext(RawContextCPU* out) const;
// Returns the pointer and size of general purpose register area.
void GetGeneralPurposeRegisters(void** gp_regs, size_t* size);
// Returns the pointer and size of float point register area.
void GetFloatingPointRegisters(void** fp_regs, size_t* size);
};
} // namespace google_breakpad
#endif // CLIENT_LINUX_DUMP_WRITER_COMMON_THREAD_INFO_H_

@ -0,0 +1,327 @@
// Copyright 2014 Google LLC
//
// 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 LLC 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/linux/dump_writer_common/ucontext_reader.h"
#include "common/linux/linux_libc_support.h"
#include "google_breakpad/common/minidump_format.h"
namespace google_breakpad {
// Minidump defines register structures which are different from the raw
// structures which we get from the kernel. These are platform specific
// functions to juggle the ucontext_t and user structures into minidump format.
#if defined(__i386__)
uintptr_t UContextReader::GetStackPointer(const ucontext_t* uc) {
return uc->uc_mcontext.gregs[REG_ESP];
}
uintptr_t UContextReader::GetInstructionPointer(const ucontext_t* uc) {
return uc->uc_mcontext.gregs[REG_EIP];
}
void UContextReader::FillCPUContext(RawContextCPU* out, const ucontext_t* uc,
const fpstate_t* fp) {
const greg_t* regs = uc->uc_mcontext.gregs;
out->context_flags = MD_CONTEXT_X86_FULL |
MD_CONTEXT_X86_FLOATING_POINT;
out->gs = regs[REG_GS];
out->fs = regs[REG_FS];
out->es = regs[REG_ES];
out->ds = regs[REG_DS];
out->edi = regs[REG_EDI];
out->esi = regs[REG_ESI];
out->ebx = regs[REG_EBX];
out->edx = regs[REG_EDX];
out->ecx = regs[REG_ECX];
out->eax = regs[REG_EAX];
out->ebp = regs[REG_EBP];
out->eip = regs[REG_EIP];
out->cs = regs[REG_CS];
out->eflags = regs[REG_EFL];
out->esp = regs[REG_UESP];
out->ss = regs[REG_SS];
out->float_save.control_word = fp->cw;
out->float_save.status_word = fp->sw;
out->float_save.tag_word = fp->tag;
out->float_save.error_offset = fp->ipoff;
out->float_save.error_selector = fp->cssel;
out->float_save.data_offset = fp->dataoff;
out->float_save.data_selector = fp->datasel;
// 8 registers * 10 bytes per register.
my_memcpy(out->float_save.register_area, fp->_st, 10 * 8);
}
#elif defined(__x86_64)
uintptr_t UContextReader::GetStackPointer(const ucontext_t* uc) {
return uc->uc_mcontext.gregs[REG_RSP];
}
uintptr_t UContextReader::GetInstructionPointer(const ucontext_t* uc) {
return uc->uc_mcontext.gregs[REG_RIP];
}
void UContextReader::FillCPUContext(RawContextCPU* out, const ucontext_t* uc,
const fpstate_t* fpregs) {
const greg_t* regs = uc->uc_mcontext.gregs;
out->context_flags = MD_CONTEXT_AMD64_FULL;
out->cs = regs[REG_CSGSFS] & 0xffff;
out->fs = (regs[REG_CSGSFS] >> 32) & 0xffff;
out->gs = (regs[REG_CSGSFS] >> 16) & 0xffff;
out->eflags = regs[REG_EFL];
out->rax = regs[REG_RAX];
out->rcx = regs[REG_RCX];
out->rdx = regs[REG_RDX];
out->rbx = regs[REG_RBX];
out->rsp = regs[REG_RSP];
out->rbp = regs[REG_RBP];
out->rsi = regs[REG_RSI];
out->rdi = regs[REG_RDI];
out->r8 = regs[REG_R8];
out->r9 = regs[REG_R9];
out->r10 = regs[REG_R10];
out->r11 = regs[REG_R11];
out->r12 = regs[REG_R12];
out->r13 = regs[REG_R13];
out->r14 = regs[REG_R14];
out->r15 = regs[REG_R15];
out->rip = regs[REG_RIP];
out->flt_save.control_word = fpregs->cwd;
out->flt_save.status_word = fpregs->swd;
out->flt_save.tag_word = fpregs->ftw;
out->flt_save.error_opcode = fpregs->fop;
out->flt_save.error_offset = fpregs->rip;
out->flt_save.data_offset = fpregs->rdp;
out->flt_save.error_selector = 0; // We don't have this.
out->flt_save.data_selector = 0; // We don't have this.
out->flt_save.mx_csr = fpregs->mxcsr;
out->flt_save.mx_csr_mask = fpregs->mxcr_mask;
my_memcpy(&out->flt_save.float_registers, &fpregs->_st, 8 * 16);
my_memcpy(&out->flt_save.xmm_registers, &fpregs->_xmm, 16 * 16);
}
#elif defined(__ARM_EABI__)
uintptr_t UContextReader::GetStackPointer(const ucontext_t* uc) {
return uc->uc_mcontext.arm_sp;
}
uintptr_t UContextReader::GetInstructionPointer(const ucontext_t* uc) {
return uc->uc_mcontext.arm_pc;
}
void UContextReader::FillCPUContext(RawContextCPU* out, const ucontext_t* uc) {
out->context_flags = MD_CONTEXT_ARM_FULL;
out->iregs[0] = uc->uc_mcontext.arm_r0;
out->iregs[1] = uc->uc_mcontext.arm_r1;
out->iregs[2] = uc->uc_mcontext.arm_r2;
out->iregs[3] = uc->uc_mcontext.arm_r3;
out->iregs[4] = uc->uc_mcontext.arm_r4;
out->iregs[5] = uc->uc_mcontext.arm_r5;
out->iregs[6] = uc->uc_mcontext.arm_r6;
out->iregs[7] = uc->uc_mcontext.arm_r7;
out->iregs[8] = uc->uc_mcontext.arm_r8;
out->iregs[9] = uc->uc_mcontext.arm_r9;
out->iregs[10] = uc->uc_mcontext.arm_r10;
out->iregs[11] = uc->uc_mcontext.arm_fp;
out->iregs[12] = uc->uc_mcontext.arm_ip;
out->iregs[13] = uc->uc_mcontext.arm_sp;
out->iregs[14] = uc->uc_mcontext.arm_lr;
out->iregs[15] = uc->uc_mcontext.arm_pc;
out->cpsr = uc->uc_mcontext.arm_cpsr;
// TODO: fix this after fixing ExceptionHandler
out->float_save.fpscr = 0;
my_memset(&out->float_save.regs, 0, sizeof(out->float_save.regs));
my_memset(&out->float_save.extra, 0, sizeof(out->float_save.extra));
}
#elif defined(__aarch64__)
uintptr_t UContextReader::GetStackPointer(const ucontext_t* uc) {
return uc->uc_mcontext.sp;
}
uintptr_t UContextReader::GetInstructionPointer(const ucontext_t* uc) {
return uc->uc_mcontext.pc;
}
void UContextReader::FillCPUContext(RawContextCPU* out, const ucontext_t* uc,
const struct fpsimd_context* fpregs) {
out->context_flags = MD_CONTEXT_ARM64_FULL_OLD;
out->cpsr = static_cast<uint32_t>(uc->uc_mcontext.pstate);
for (int i = 0; i < MD_CONTEXT_ARM64_REG_SP; ++i)
out->iregs[i] = uc->uc_mcontext.regs[i];
out->iregs[MD_CONTEXT_ARM64_REG_SP] = uc->uc_mcontext.sp;
out->iregs[MD_CONTEXT_ARM64_REG_PC] = uc->uc_mcontext.pc;
out->float_save.fpsr = fpregs->fpsr;
out->float_save.fpcr = fpregs->fpcr;
my_memcpy(&out->float_save.regs, &fpregs->vregs,
MD_FLOATINGSAVEAREA_ARM64_FPR_COUNT * 16);
}
#elif defined(__mips__)
uintptr_t UContextReader::GetStackPointer(const ucontext_t* uc) {
return uc->uc_mcontext.gregs[MD_CONTEXT_MIPS_REG_SP];
}
uintptr_t UContextReader::GetInstructionPointer(const ucontext_t* uc) {
return uc->uc_mcontext.pc;
}
void UContextReader::FillCPUContext(RawContextCPU* out, const ucontext_t* uc) {
#if _MIPS_SIM == _ABI64
out->context_flags = MD_CONTEXT_MIPS64_FULL;
#elif _MIPS_SIM == _ABIO32
out->context_flags = MD_CONTEXT_MIPS_FULL;
#else
#error "This mips ABI is currently not supported (n32)"
#endif
for (int i = 0; i < MD_CONTEXT_MIPS_GPR_COUNT; ++i)
out->iregs[i] = uc->uc_mcontext.gregs[i];
out->mdhi = uc->uc_mcontext.mdhi;
out->mdlo = uc->uc_mcontext.mdlo;
out->hi[0] = uc->uc_mcontext.hi1;
out->hi[1] = uc->uc_mcontext.hi2;
out->hi[2] = uc->uc_mcontext.hi3;
out->lo[0] = uc->uc_mcontext.lo1;
out->lo[1] = uc->uc_mcontext.lo2;
out->lo[2] = uc->uc_mcontext.lo3;
out->dsp_control = uc->uc_mcontext.dsp;
out->epc = uc->uc_mcontext.pc;
out->badvaddr = 0; // Not reported in signal context.
out->status = 0; // Not reported in signal context.
out->cause = 0; // Not reported in signal context.
for (int i = 0; i < MD_FLOATINGSAVEAREA_MIPS_FPR_COUNT; ++i)
out->float_save.regs[i] = uc->uc_mcontext.fpregs.fp_r.fp_dregs[i];
out->float_save.fpcsr = uc->uc_mcontext.fpc_csr;
#if _MIPS_SIM == _ABIO32
out->float_save.fir = uc->uc_mcontext.fpc_eir; // Unused.
#endif
}
#elif defined(__riscv)
uintptr_t UContextReader::GetStackPointer(const ucontext_t* uc) {
return uc->uc_mcontext.__gregs[MD_CONTEXT_RISCV_REG_SP];
}
uintptr_t UContextReader::GetInstructionPointer(const ucontext_t* uc) {
return uc->uc_mcontext.__gregs[MD_CONTEXT_RISCV_REG_PC];
}
void UContextReader::FillCPUContext(RawContextCPU* out, const ucontext_t* uc) {
# if __riscv__xlen == 32
out->context_flags = MD_CONTEXT_RISCV_FULL;
# elif __riscv_xlen == 64
out->context_flags = MD_CONTEXT_RISCV64_FULL;
# else
# error "Unexpected __riscv_xlen"
# endif
out->pc = uc->uc_mcontext.__gregs[0];
out->ra = uc->uc_mcontext.__gregs[1];
out->sp = uc->uc_mcontext.__gregs[2];
out->gp = uc->uc_mcontext.__gregs[3];
out->tp = uc->uc_mcontext.__gregs[4];
out->t0 = uc->uc_mcontext.__gregs[5];
out->t1 = uc->uc_mcontext.__gregs[6];
out->t2 = uc->uc_mcontext.__gregs[7];
out->s0 = uc->uc_mcontext.__gregs[8];
out->s1 = uc->uc_mcontext.__gregs[9];
out->a0 = uc->uc_mcontext.__gregs[10];
out->a1 = uc->uc_mcontext.__gregs[11];
out->a2 = uc->uc_mcontext.__gregs[12];
out->a3 = uc->uc_mcontext.__gregs[13];
out->a4 = uc->uc_mcontext.__gregs[14];
out->a5 = uc->uc_mcontext.__gregs[15];
out->a6 = uc->uc_mcontext.__gregs[16];
out->a7 = uc->uc_mcontext.__gregs[17];
out->s2 = uc->uc_mcontext.__gregs[18];
out->s3 = uc->uc_mcontext.__gregs[19];
out->s4 = uc->uc_mcontext.__gregs[20];
out->s5 = uc->uc_mcontext.__gregs[21];
out->s6 = uc->uc_mcontext.__gregs[22];
out->s7 = uc->uc_mcontext.__gregs[23];
out->s8 = uc->uc_mcontext.__gregs[24];
out->s9 = uc->uc_mcontext.__gregs[25];
out->s10 = uc->uc_mcontext.__gregs[26];
out->s11 = uc->uc_mcontext.__gregs[27];
out->t3 = uc->uc_mcontext.__gregs[28];
out->t4 = uc->uc_mcontext.__gregs[29];
out->t5 = uc->uc_mcontext.__gregs[30];
out->t6 = uc->uc_mcontext.__gregs[31];
# if __riscv_flen == 32
for(int i = 0; i < MD_FLOATINGSAVEAREA_RISCV_FPR_COUNT; i++)
out->float_save.regs[i] = uc->uc_mcontext.__fpregs.__f.__f[i];
out->float_save.fpcsr = uc->uc_mcontext.__fpregs.__f.__fcsr;
# elif __riscv_flen == 64
for(int i = 0; i < MD_FLOATINGSAVEAREA_RISCV_FPR_COUNT; i++)
out->float_save.regs[i] = uc->uc_mcontext.__fpregs.__d.__f[i];
out->float_save.fpcsr = uc->uc_mcontext.__fpregs.__d.__fcsr;
# elif __riscv_flen == 128
for(int i = 0; i < MD_FLOATINGSAVEAREA_RISCV_FPR_COUNT; i++) {
out->float_save.regs[i].high = uc->uc_mcontext.__fpregs.__q.__f[2*i];
out->float_save.regs[i].low = uc->uc_mcontext.__fpregs.__q.__f[2*i+1];
}
out->float_save.fpcsr = uc->uc_mcontext.__fpregs.__q.__fcsr;
# endif
}
#endif
} // namespace google_breakpad

@ -0,0 +1,64 @@
// Copyright 2014 Google LLC
//
// 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 LLC 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_DUMP_WRITER_COMMON_UCONTEXT_READER_H
#define CLIENT_LINUX_DUMP_WRITER_COMMON_UCONTEXT_READER_H
#include <sys/ucontext.h>
#include <sys/user.h>
#include "client/linux/dump_writer_common/raw_context_cpu.h"
#include "client/linux/minidump_writer/minidump_writer.h"
#include "common/memory_allocator.h"
#include "google_breakpad/common/minidump_format.h"
namespace google_breakpad {
// Wraps platform-dependent implementations of accessors to ucontext_t structs.
struct UContextReader {
static uintptr_t GetStackPointer(const ucontext_t* uc);
static uintptr_t GetInstructionPointer(const ucontext_t* uc);
// Juggle a arch-specific ucontext_t into a minidump format
// out: the minidump structure
// info: the collection of register structures.
#if defined(__i386__) || defined(__x86_64)
static void FillCPUContext(RawContextCPU* out, const ucontext_t* uc,
const fpstate_t* fp);
#elif defined(__aarch64__)
static void FillCPUContext(RawContextCPU* out, const ucontext_t* uc,
const struct fpsimd_context* fpregs);
#else
static void FillCPUContext(RawContextCPU* out, const ucontext_t* uc);
#endif
};
} // namespace google_breakpad
#endif // CLIENT_LINUX_DUMP_WRITER_COMMON_UCONTEXT_READER_H

@ -0,0 +1,795 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
// The ExceptionHandler object installs signal handlers for a number of
// signals. We rely on the signal handler running on the thread which crashed
// in order to identify it. This is true of the synchronous signals (SEGV etc),
// but not true of ABRT. Thus, if you send ABRT to yourself in a program which
// uses ExceptionHandler, you need to use tgkill to direct it to the current
// thread.
//
// The signal flow looks like this:
//
// SignalHandler (uses a global stack of ExceptionHandler objects to find
// | one to handle the signal. If the first rejects it, try
// | the second etc...)
// V
// HandleSignal ----------------------------| (clones a new process which
// | | shares an address space with
// (wait for cloned | the crashed process. This
// process) | allows us to ptrace the crashed
// | | process)
// V V
// (set signal handler to ThreadEntry (static function to bounce
// SIG_DFL and rethrow, | back into the object)
// killing the crashed |
// process) V
// DoDump (writes minidump)
// |
// V
// sys_exit
//
// This code is a little fragmented. Different functions of the ExceptionHandler
// class run in a number of different contexts. Some of them run in a normal
// context and are easy to code, others run in a compromised context and the
// restrictions at the top of minidump_writer.cc apply: no libc and use the
// alternative malloc. Each function should have comment above it detailing the
// context which it runs in.
#include "client/linux/handler/exception_handler.h"
#include <errno.h>
#include <fcntl.h>
#include <linux/limits.h>
#include <pthread.h>
#include <sched.h>
#include <signal.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/ucontext.h>
#include <sys/user.h>
#include <ucontext.h>
#include <algorithm>
#include <utility>
#include <vector>
#include "common/basictypes.h"
#include "common/linux/breakpad_getcontext.h"
#include "common/linux/linux_libc_support.h"
#include "common/memory_allocator.h"
#include "client/linux/log/log.h"
#include "client/linux/microdump_writer/microdump_writer.h"
#include "client/linux/minidump_writer/linux_dumper.h"
#include "client/linux/minidump_writer/minidump_writer.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
#endif
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.
const int kExceptionSignals[] = {
SIGSEGV, SIGABRT, SIGFPE, SIGILL, SIGBUS, SIGTRAP
};
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.
const unsigned kSigStackSize = std::max<unsigned>(16384, 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 = calloc(1, kSigStackSize);
new_stack.ss_size = kSigStackSize;
if (sys_sigaltstack(&new_stack, NULL) == -1) {
free(new_stack.ss_sp);
return;
}
stack_installed = true;
}
}
// Runs before crashing: normal context.
void RestoreAlternateStackLocked() {
if (!stack_installed)
return;
stack_t current_stack;
if (sys_sigaltstack(NULL, &current_stack) == -1)
return;
// Only restore the old_stack if the current alternative stack is the one
// installed by the call to InstallAlternateStackLocked.
if (current_stack.ss_sp == new_stack.ss_sp) {
if (old_stack.ss_sp) {
if (sys_sigaltstack(&old_stack, NULL) == -1)
return;
} else {
stack_t disable_stack;
disable_stack.ss_flags = SS_DISABLE;
if (sys_sigaltstack(&disable_stack, NULL) == -1)
return;
}
}
free(new_stack.ss_sp);
stack_installed = false;
}
void InstallDefaultHandler(int sig) {
#if defined(__ANDROID__)
// Android L+ expose signal and sigaction symbols that override the system
// ones. There is a bug in these functions where a request to set the handler
// to SIG_DFL is ignored. In that case, an infinite loop is entered as the
// signal is repeatedly sent to breakpad's signal handler.
// To work around this, directly call the system's sigaction.
struct kernel_sigaction sa;
memset(&sa, 0, sizeof(sa));
sys_sigemptyset(&sa.sa_mask);
sa.sa_handler_ = SIG_DFL;
sa.sa_flags = SA_RESTART;
sys_rt_sigaction(sig, &sa, NULL, sizeof(kernel_sigset_t));
#else
signal(sig, SIG_DFL);
#endif
}
// The global exception handler stack. This is needed because there may exist
// multiple ExceptionHandler instances in a process. Each will have itself
// registered in this stack.
std::vector<ExceptionHandler*>* g_handler_stack_ = NULL;
pthread_mutex_t g_handler_stack_mutex_ = PTHREAD_MUTEX_INITIALIZER;
// sizeof(CrashContext) can be too big w.r.t the size of alternatate stack
// for SignalHandler(). Keep the crash context as a .bss field. Exception
// handlers are serialized by the |g_handler_stack_mutex_| and at most one at a
// time can use |g_crash_context_|.
ExceptionHandler::CrashContext g_crash_context_;
FirstChanceHandler g_first_chance_handler_ = nullptr;
} // namespace
// Runs before crashing: normal context.
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),
minidump_descriptor_(descriptor),
crash_handler_(NULL) {
if (server_fd >= 0)
crash_generation_client_.reset(CrashGenerationClient::TryCreate(server_fd));
if (!IsOutOfProcess() && !minidump_descriptor_.IsFD() &&
!minidump_descriptor_.IsMicrodumpOnConsole())
minidump_descriptor_.UpdatePath();
#if defined(__ANDROID__)
if (minidump_descriptor_.IsMicrodumpOnConsole())
logger::initializeCrashLogWriter();
#endif
pthread_mutex_lock(&g_handler_stack_mutex_);
// Pre-fault the crash context struct. This is to avoid failing due to OOM
// if handling an exception when the process ran out of virtual memory.
memset(&g_crash_context_, 0, sizeof(g_crash_context_));
if (!g_handler_stack_)
g_handler_stack_ = new std::vector<ExceptionHandler*>;
if (install_handler) {
InstallAlternateStackLocked();
InstallHandlersLocked();
}
g_handler_stack_->push_back(this);
pthread_mutex_unlock(&g_handler_stack_mutex_);
}
// Runs before crashing: normal context.
ExceptionHandler::~ExceptionHandler() {
pthread_mutex_lock(&g_handler_stack_mutex_);
std::vector<ExceptionHandler*>::iterator handler =
std::find(g_handler_stack_->begin(), g_handler_stack_->end(), this);
g_handler_stack_->erase(handler);
if (g_handler_stack_->empty()) {
delete g_handler_stack_;
g_handler_stack_ = NULL;
RestoreAlternateStackLocked();
RestoreHandlersLocked();
}
pthread_mutex_unlock(&g_handler_stack_mutex_);
}
// Runs before crashing: normal context.
// static
bool ExceptionHandler::InstallHandlersLocked() {
if (handlers_installed)
return false;
// 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;
}
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sigemptyset(&sa.sa_mask);
// 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 (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;
}
// 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) {
InstallDefaultHandler(kExceptionSignals[i]);
}
}
handlers_installed = false;
}
// void ExceptionHandler::set_crash_handler(HandlerCallback callback) {
// crash_handler_ = callback;
// }
// This function runs in a compromised context: see the top of the file.
// Runs on the crashing thread.
// static
void ExceptionHandler::SignalHandler(int sig, siginfo_t* info, void* uc) {
// Give the first chance handler a chance to recover from this signal
//
// This is primarily used by V8. V8 uses guard regions to guarantee memory
// safety in WebAssembly. This means some signals might be expected if they
// originate from Wasm code while accessing the guard region. We give V8 the
// chance to handle and recover from these signals first.
if (g_first_chance_handler_ != nullptr &&
g_first_chance_handler_(sig, info, uc)) {
return;
}
// All the exception signals are blocked at this point.
pthread_mutex_lock(&g_handler_stack_mutex_);
// 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_sigaction == SignalHandler &&
(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.
InstallDefaultHandler(sig);
}
pthread_mutex_unlock(&g_handler_stack_mutex_);
return;
}
bool handled = false;
for (int i = g_handler_stack_->size() - 1; !handled && i >= 0; --i) {
handled = (*g_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) {
InstallDefaultHandler(sig);
} else {
RestoreHandlersLocked();
}
pthread_mutex_unlock(&g_handler_stack_mutex_);
// info->si_code <= 0 iff SI_FROMUSER (SI_FROMKERNEL otherwise).
if (info->si_code <= 0 || 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. The special case (si_pid == 0 && sig == SIGABRT) is
// due to the kernel sending a SIGABRT from a user request via SysRQ.
if (sys_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
// result in an incorrect exit code.
_exit(1);
}
} else {
// This was a synchronous signal triggered by a hard fault (e.g. SIGSEGV).
// No need to reissue the signal. It will automatically trigger again,
// when we return from the signal handler.
}
}
struct ThreadArgument {
pid_t pid; // the crashing process
const MinidumpDescriptor* minidump_descriptor;
ExceptionHandler* handler;
const void* context; // a CrashContext structure
size_t context_size;
};
// This is the entry function for the cloned process. We are in a compromised
// context here: see the top of the file.
// static
int ExceptionHandler::ThreadEntry(void* arg) {
const ThreadArgument* thread_arg = reinterpret_cast<ThreadArgument*>(arg);
// Close the write end of the pipe. This allows us to fail if the parent dies
// while waiting for the continue signal.
sys_close(thread_arg->handler->fdes[1]);
// Block here until the crashing process unblocks us when
// we're allowed to use ptrace
thread_arg->handler->WaitForContinueSignal();
sys_close(thread_arg->handler->fdes[0]);
return thread_arg->handler->DoDump(thread_arg->pid, thread_arg->context,
thread_arg->context_size) == false;
}
// This function runs in a compromised context: see the top of the file.
// Runs on the crashing thread.
bool ExceptionHandler::HandleSignal(int /*sig*/, siginfo_t* info, void* uc) {
if (filter_ && !filter_(callback_context_))
return false;
// Allow ourselves to be dumped if the signal is trusted.
bool signal_trusted = info->si_code > 0;
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, 0, 0, 0);
}
// Fill in all the holes in the struct to make Valgrind happy.
memset(&g_crash_context_, 0, sizeof(g_crash_context_));
memcpy(&g_crash_context_.siginfo, info, sizeof(siginfo_t));
memcpy(&g_crash_context_.context, uc, sizeof(ucontext_t));
#if defined(__aarch64__)
ucontext_t* uc_ptr = (ucontext_t*)uc;
struct fpsimd_context* fp_ptr =
(struct fpsimd_context*)&uc_ptr->uc_mcontext.__reserved;
if (fp_ptr->head.magic == FPSIMD_MAGIC) {
memcpy(&g_crash_context_.float_state, fp_ptr,
sizeof(g_crash_context_.float_state));
}
#elif GOOGLE_BREAKPAD_CRASH_CONTEXT_HAS_FLOAT_STATE
ucontext_t* uc_ptr = (ucontext_t*)uc;
if (uc_ptr->uc_mcontext.fpregs) {
memcpy(&g_crash_context_.float_state, uc_ptr->uc_mcontext.fpregs,
sizeof(g_crash_context_.float_state));
}
#endif
g_crash_context_.tid = syscall(__NR_gettid);
if (crash_handler_ != NULL) {
if (crash_handler_(&g_crash_context_, sizeof(g_crash_context_),
callback_context_)) {
return true;
}
}
return GenerateDump(&g_crash_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();
ucontext_t 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));
// 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 = reinterpret_cast<uint8_t*>(allocator.Alloc(kChildStackSize));
if (!stack)
return false;
// clone() needs the top-most address. (scrub just to be safe)
stack += kChildStackSize;
my_memset(stack - 16, 0, 16);
ThreadArgument thread_arg;
thread_arg.handler = this;
thread_arg.minidump_descriptor = &minidump_descriptor_;
thread_arg.pid = getpid();
thread_arg.context = context;
thread_arg.context_size = sizeof(*context);
// 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. Create a pipe here which we can use to block the
// cloned process after creating it, until we have explicitly enabled ptrace
if (sys_pipe(fdes) == -1) {
// Creating the pipe failed. We'll log an error but carry on anyway,
// as we'll probably still get a useful crash report. All that will happen
// is the write() and read() calls will fail with EBADF
static const char no_pipe_msg[] = "ExceptionHandler::GenerateDump "
"sys_pipe failed:";
logger::write(no_pipe_msg, sizeof(no_pipe_msg) - 1);
logger::write(strerror(errno), strlen(strerror(errno)));
logger::write("\n", 1);
// Ensure fdes[0] and fdes[1] are invalid file descriptors.
fdes[0] = fdes[1] = -1;
}
const pid_t child = sys_clone(
ThreadEntry, stack, CLONE_FS | CLONE_UNTRACED, &thread_arg, NULL, NULL,
NULL);
if (child == -1) {
sys_close(fdes[0]);
sys_close(fdes[1]);
return false;
}
// Close the read end of the pipe.
sys_close(fdes[0]);
// Allow the child to ptrace us
sys_prctl(PR_SET_PTRACER, child, 0, 0, 0);
SendContinueSignalToChild();
int status = 0;
const int r = HANDLE_EINTR(sys_waitpid(child, &status, __WALL));
sys_close(fdes[1]);
if (r == -1) {
static const char msg[] = "ExceptionHandler::GenerateDump waitpid failed:";
logger::write(msg, sizeof(msg) - 1);
logger::write(strerror(errno), strlen(strerror(errno)));
logger::write("\n", 1);
}
bool success = r != -1 && WIFEXITED(status) && WEXITSTATUS(status) == 0;
if (callback_)
success = callback_(minidump_descriptor_, callback_context_, success);
return success;
}
// This function runs in a compromised context: see the top of the file.
void ExceptionHandler::SendContinueSignalToChild() {
static const char okToContinueMessage = 'a';
int r;
r = HANDLE_EINTR(sys_write(fdes[1], &okToContinueMessage, sizeof(char)));
if (r == -1) {
static const char msg[] = "ExceptionHandler::SendContinueSignalToChild "
"sys_write failed:";
logger::write(msg, sizeof(msg) - 1);
logger::write(strerror(errno), strlen(strerror(errno)));
logger::write("\n", 1);
}
}
// This function runs in a compromised context: see the top of the file.
// Runs on the cloned process.
void ExceptionHandler::WaitForContinueSignal() {
int r;
char receivedMessage;
r = HANDLE_EINTR(sys_read(fdes[0], &receivedMessage, sizeof(char)));
if (r == -1) {
static const char msg[] = "ExceptionHandler::WaitForContinueSignal "
"sys_read failed:";
logger::write(msg, sizeof(msg) - 1);
logger::write(strerror(errno), strlen(strerror(errno)));
logger::write("\n", 1);
}
}
// This function runs in a compromised context: see the top of the file.
// Runs on the cloned process.
bool ExceptionHandler::DoDump(pid_t crashing_process, const void* context,
size_t context_size) {
const bool may_skip_dump =
minidump_descriptor_.skip_dump_if_principal_mapping_not_referenced();
const uintptr_t principal_mapping_address =
minidump_descriptor_.address_within_principal_mapping();
const bool sanitize_stacks = minidump_descriptor_.sanitize_stacks();
if (minidump_descriptor_.IsMicrodumpOnConsole()) {
return google_breakpad::WriteMicrodump(
crashing_process,
context,
context_size,
mapping_list_,
may_skip_dump,
principal_mapping_address,
sanitize_stacks,
*minidump_descriptor_.microdump_extra_info());
}
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_,
may_skip_dump,
principal_mapping_address,
sanitize_stacks);
}
return google_breakpad::WriteMinidump(minidump_descriptor_.path(),
minidump_descriptor_.size_limit(),
crashing_process,
context,
context_size,
mapping_list_,
app_memory_list_,
may_skip_dump,
principal_mapping_address,
sanitize_stacks);
}
// static
bool ExceptionHandler::WriteMinidump(const string& dump_path,
MinidumpCallback callback,
void* callback_context) {
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 (!IsOutOfProcess() && !minidump_descriptor_.IsFD() &&
!minidump_descriptor_.IsMicrodumpOnConsole()) {
// 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 GOOGLE_BREAKPAD_CRASH_CONTEXT_HAS_FLOAT_STATE && !defined(__aarch64__)
memcpy(&context.float_state, context.context.uc_mcontext.fpregs,
sizeof(context.float_state));
#endif
context.tid = sys_gettid();
// 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(__aarch64__)
context.siginfo.si_addr =
reinterpret_cast<void*>(context.context.uc_mcontext.pc);
#elif defined(__mips__)
context.siginfo.si_addr =
reinterpret_cast<void*>(context.context.uc_mcontext.pc);
#elif defined(__riscv)
context.siginfo.si_addr =
reinterpret_cast<void*>(context.context.uc_mcontext.__gregs[REG_PC]);
#else
# error "This code has not been ported to your platform yet."
#endif
return GenerateDump(&context);
}
void ExceptionHandler::AddMappingInfo(const string& name,
const uint8_t identifier[sizeof(MDGUID)],
uintptr_t start_address,
size_t mapping_size,
size_t file_offset) {
MappingInfo info;
info.start_addr = start_address;
info.size = mapping_size;
info.offset = file_offset;
strncpy(info.name, name.c_str(), sizeof(info.name) - 1);
info.name[sizeof(info.name) - 1] = '\0';
MappingEntry mapping;
mapping.first = info;
memcpy(mapping.second, identifier, sizeof(MDGUID));
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;
}
void SetFirstChanceExceptionHandler(FirstChanceHandler callback) {
g_first_chance_handler_ = callback;
}
} // namespace google_breakpad

@ -0,0 +1,286 @@
// Copyright 2010 Google LLC
//
// 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 LLC nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_
#define CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/ucontext.h>
#include <string>
#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"
#if !defined(__ARM_EABI__) && !defined(__mips__) && !defined(__riscv)
// FP state is not part of user ABI for Linux ARM.
// In case of MIPS and RISCV Linux FP state is already part of ucontext_t
// so 'float_state' is not required.
# define GOOGLE_BREAKPAD_CRASH_CONTEXT_HAS_FLOAT_STATE 1
#else
# define GOOGLE_BREAKPAD_CRASH_CONTEXT_HAS_FLOAT_STATE 0
#endif
namespace google_breakpad {
// ExceptionHandler
//
// ExceptionHandler can write a minidump file when an exception occurs,
// or when WriteMinidump() is called explicitly by your program.
//
// To have the exception handler write minidumps when an uncaught exception
// (crash) occurs, you should create an instance early in the execution
// of your program, and keep it around for the entire time you want to
// have crash handling active (typically, until shutdown).
// (NOTE): There should be only be one this kind of exception handler
// object per process.
//
// If you want to write minidumps without installing the exception handler,
// you can create an ExceptionHandler with install_handler set to false,
// then call WriteMinidump. You can also use this technique if you want to
// use different minidump callbacks for different call sites.
//
// In either case, a callback function is called when a minidump is written,
// which receives the 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
// callback_context when the handler was created.
//
// If a FilterCallback returns true, Breakpad will continue processing,
// attempting to write a minidump. If a FilterCallback returns false,
// Breakpad will immediately report the exception as unhandled without
// writing a minidump, allowing another handler the opportunity to handle it.
typedef bool (*FilterCallback)(void* context);
// A callback function to run after the minidump has been written.
// |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
// being notified of the exception. If the callback returns false, Breakpad
// will treat the exception as unhandled, and allow another handler to handle
// it. If there are no other handlers, Breakpad will report the exception to
// the system as unhandled, allowing a debugger or native crash dialog the
// opportunity to handle the exception. Most callback implementations
// should normally return the value of |succeeded|, or when they wish to
// not report an exception of handled, false. Callbacks will rarely want to
// return true directly (unless |succeeded| is true).
typedef bool (*MinidumpCallback)(const MinidumpDescriptor& descriptor,
void* context,
bool succeeded);
// In certain cases, a user may wish to handle the generation of the minidump
// themselves. In this case, they can install a handler callback which is
// called when a crash has occurred. If this function returns true, no other
// processing of occurs and the process will shortly be crashed. If this
// returns false, the normal processing continues.
typedef bool (*HandlerCallback)(const void* crash_context,
size_t crash_context_size,
void* context);
// Creates a new ExceptionHandler instance to handle writing minidumps.
// Before writing a minidump, the optional |filter| callback will be called.
// Its return value determines whether or not Breakpad should write a
// 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.
// 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,
const int server_fd);
~ExceptionHandler();
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;
}
void set_crash_generation_client(CrashGenerationClient* client) {
crash_generation_client_.reset(client);
}
// 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 string& 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.
//
// 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.
struct CrashContext {
siginfo_t siginfo;
pid_t tid; // the crashing thread.
ucontext_t context;
#if GOOGLE_BREAKPAD_CRASH_CONTEXT_HAS_FLOAT_STATE
fpstate_t float_state;
#endif
};
// Returns whether out-of-process dump generation is used or not.
bool IsOutOfProcess() const {
return crash_generation_client_.get() != NULL;
}
// 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 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:
// 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();
static void SignalHandler(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);
const FilterCallback filter_;
const MinidumpCallback callback_;
void* const callback_context_;
scoped_ptr<CrashGenerationClient> crash_generation_client_;
MinidumpDescriptor minidump_descriptor_;
// Must be volatile. The compiler is unaware of the code which runs in
// the signal handler which reads this variable. Without volatile the
// compiler is free to optimise away writes to this variable which it
// believes are never read.
volatile HandlerCallback crash_handler_;
// 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
// cloned process after creating it, until we have explicitly enabled
// ptrace. This is used to store the file descriptors for the pipe
int fdes[2] = {-1, -1};
// 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_;
};
typedef bool (*FirstChanceHandler)(int, siginfo_t*, void*);
void SetFirstChanceExceptionHandler(FirstChanceHandler callback);
} // namespace google_breakpad
#endif // CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_

@ -0,0 +1,51 @@
// Copyright 2015 Google LLC
//
// 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 LLC 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_MICRODUMP_EXTRA_INFO_H_
#define CLIENT_LINUX_HANDLER_MICRODUMP_EXTRA_INFO_H_
namespace google_breakpad {
struct MicrodumpExtraInfo {
// Strings pointed to by this struct are not copied, and are
// expected to remain valid for the lifetime of the process.
const char* build_fingerprint;
const char* product_info;
const char* gpu_fingerprint;
const char* process_type;
MicrodumpExtraInfo()
: build_fingerprint(NULL),
product_info(NULL),
gpu_fingerprint(NULL),
process_type(NULL) {}
};
}
#endif // CLIENT_LINUX_HANDLER_MICRODUMP_EXTRA_INFO_H_

@ -0,0 +1,96 @@
// Copyright 2012 Google LLC
//
// 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 LLC 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 <stdio.h>
#include "client/linux/handler/minidump_descriptor.h"
#include "common/linux/guid_creator.h"
namespace google_breakpad {
//static
const MinidumpDescriptor::MicrodumpOnConsole
MinidumpDescriptor::kMicrodumpOnConsole = {};
MinidumpDescriptor::MinidumpDescriptor(const MinidumpDescriptor& descriptor)
: mode_(descriptor.mode_),
fd_(descriptor.fd_),
directory_(descriptor.directory_),
c_path_(NULL),
size_limit_(descriptor.size_limit_),
address_within_principal_mapping_(
descriptor.address_within_principal_mapping_),
skip_dump_if_principal_mapping_not_referenced_(
descriptor.skip_dump_if_principal_mapping_not_referenced_),
sanitize_stacks_(descriptor.sanitize_stacks_),
microdump_extra_info_(descriptor.microdump_extra_info_) {
// 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());
}
MinidumpDescriptor& MinidumpDescriptor::operator=(
const MinidumpDescriptor& descriptor) {
assert(descriptor.path_.empty());
mode_ = descriptor.mode_;
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_;
address_within_principal_mapping_ =
descriptor.address_within_principal_mapping_;
skip_dump_if_principal_mapping_not_referenced_ =
descriptor.skip_dump_if_principal_mapping_not_referenced_;
sanitize_stacks_ = descriptor.sanitize_stacks_;
microdump_extra_info_ = descriptor.microdump_extra_info_;
return *this;
}
void MinidumpDescriptor::UpdatePath() {
assert(mode_ == kWriteMinidumpToFile && !directory_.empty());
GUID guid;
char guid_str[kGUIDStringLength + 1];
if (!CreateGUID(&guid) || !GUIDToString(&guid, guid_str, sizeof(guid_str))) {
assert(false);
}
path_.clear();
path_ = directory_ + "/" + guid_str + ".dmp";
c_path_ = path_.c_str();
}
} // namespace google_breakpad

@ -0,0 +1,199 @@
// Copyright 2012 Google LLC
//
// 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 LLC 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 <cstdint>
#include <string>
#include "client/linux/handler/microdump_extra_info.h"
#include "common/using_std_string.h"
// This class describes how a crash dump should be generated, either:
// - Writing a full minidump to a file in a given directory (the actual path,
// inside the directory, is determined by this class).
// - Writing a full minidump to a given fd.
// - Writing a reduced microdump to the console (logcat on Android).
namespace google_breakpad {
class MinidumpDescriptor {
public:
struct MicrodumpOnConsole {};
static const MicrodumpOnConsole kMicrodumpOnConsole;
MinidumpDescriptor()
: mode_(kUninitialized),
fd_(-1),
size_limit_(-1),
address_within_principal_mapping_(0),
skip_dump_if_principal_mapping_not_referenced_(false) {}
explicit MinidumpDescriptor(const string& directory)
: mode_(kWriteMinidumpToFile),
fd_(-1),
directory_(directory),
c_path_(NULL),
size_limit_(-1),
address_within_principal_mapping_(0),
skip_dump_if_principal_mapping_not_referenced_(false),
sanitize_stacks_(false) {
assert(!directory.empty());
}
explicit MinidumpDescriptor(int fd)
: mode_(kWriteMinidumpToFd),
fd_(fd),
c_path_(NULL),
size_limit_(-1),
address_within_principal_mapping_(0),
skip_dump_if_principal_mapping_not_referenced_(false),
sanitize_stacks_(false) {
assert(fd != -1);
}
explicit MinidumpDescriptor(const MicrodumpOnConsole&)
: mode_(kWriteMicrodumpToConsole),
fd_(-1),
size_limit_(-1),
address_within_principal_mapping_(0),
skip_dump_if_principal_mapping_not_referenced_(false),
sanitize_stacks_(false) {}
explicit MinidumpDescriptor(const MinidumpDescriptor& descriptor);
MinidumpDescriptor& operator=(const MinidumpDescriptor& descriptor);
static MinidumpDescriptor getMicrodumpDescriptor();
bool IsFD() const { return mode_ == kWriteMinidumpToFd; }
int fd() const { return fd_; }
string directory() const { return directory_; }
const char* path() const { return c_path_; }
bool IsMicrodumpOnConsole() const {
return mode_ == kWriteMicrodumpToConsole;
}
// 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; }
uintptr_t address_within_principal_mapping() const {
return address_within_principal_mapping_;
}
void set_address_within_principal_mapping(
uintptr_t address_within_principal_mapping) {
address_within_principal_mapping_ = address_within_principal_mapping;
}
bool skip_dump_if_principal_mapping_not_referenced() {
return skip_dump_if_principal_mapping_not_referenced_;
}
void set_skip_dump_if_principal_mapping_not_referenced(
bool skip_dump_if_principal_mapping_not_referenced) {
skip_dump_if_principal_mapping_not_referenced_ =
skip_dump_if_principal_mapping_not_referenced;
}
bool sanitize_stacks() const { return sanitize_stacks_; }
void set_sanitize_stacks(bool sanitize_stacks) {
sanitize_stacks_ = sanitize_stacks;
}
MicrodumpExtraInfo* microdump_extra_info() {
assert(IsMicrodumpOnConsole());
return &microdump_extra_info_;
}
private:
enum DumpMode {
kUninitialized = 0,
kWriteMinidumpToFile,
kWriteMinidumpToFd,
kWriteMicrodumpToConsole
};
// Specifies the dump mode (see DumpMode).
DumpMode mode_;
// 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_;
// This member points somewhere into the main module for this
// process (the module that is considerered interesting for the
// purposes of debugging crashes).
uintptr_t address_within_principal_mapping_;
// If set, threads that do not reference the address range
// associated with |address_within_principal_mapping_| will not have their
// stacks logged.
bool skip_dump_if_principal_mapping_not_referenced_;
// If set, stacks are sanitized to remove PII. This involves
// overwriting any pointer-aligned words that are not either
// pointers into a process mapping or small integers (+/-4096). This
// leaves enough information to unwind stacks, and preserve some
// register values, but elides strings and other program data.
bool sanitize_stacks_;
// The extra microdump data (e.g. product name/version, build
// fingerprint, gpu fingerprint) that should be appended to the dump
// (microdump only). Microdumps don't have the ability of appending
// extra metadata after the dump is generated (as opposite to
// minidumps MIME fields), therefore the extra data must be provided
// upfront. Any memory pointed to by members of the
// MicrodumpExtraInfo struct must be valid for the lifetime of the
// process (read: the caller has to guarantee that it is stored in
// global static storage.)
MicrodumpExtraInfo microdump_extra_info_;
};
} // namespace google_breakpad
#endif // CLIENT_LINUX_HANDLER_MINIDUMP_DESCRIPTOR_H_

@ -0,0 +1,83 @@
// Copyright 2012 Google LLC
//
// 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 LLC 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/linux/log/log.h"
#if defined(__ANDROID__)
#include <android/log.h>
#include <dlfcn.h>
#else
#include "third_party/lss/linux_syscall_support.h"
#endif
namespace logger {
#if defined(__ANDROID__)
namespace {
// __android_log_buf_write() is not exported in the NDK and is being used by
// dynamic runtime linking. Its declaration is taken from Android's
// system/core/include/log/log.h.
using AndroidLogBufferWriteFunc = int (*)(int bufID, int prio, const char* tag,
const char* text);
const int kAndroidCrashLogId = 4; // From LOG_ID_CRASH in log.h.
const char kAndroidLogTag[] = "google-breakpad";
bool g_crash_log_initialized = false;
AndroidLogBufferWriteFunc g_android_log_buf_write = nullptr;
} // namespace
void initializeCrashLogWriter() {
if (g_crash_log_initialized)
return;
g_android_log_buf_write = reinterpret_cast<AndroidLogBufferWriteFunc>(
dlsym(RTLD_DEFAULT, "__android_log_buf_write"));
g_crash_log_initialized = true;
}
int writeToCrashLog(const char* buf) {
// Try writing to the crash log ring buffer. If not available, fall back to
// the standard log buffer.
if (g_android_log_buf_write) {
return g_android_log_buf_write(kAndroidCrashLogId, ANDROID_LOG_FATAL,
kAndroidLogTag, buf);
}
return __android_log_write(ANDROID_LOG_FATAL, kAndroidLogTag, buf);
}
#endif
int write(const char* buf, size_t nbytes) {
#if defined(__ANDROID__)
return __android_log_write(ANDROID_LOG_WARN, kAndroidLogTag, buf);
#else
return sys_write(2, buf, nbytes);
#endif
}
} // namespace logger

@ -0,0 +1,54 @@
// Copyright 2012 Google LLC
//
// 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 LLC 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_LOG_LOG_H_
#define CLIENT_LINUX_LOG_LOG_H_
#include <stddef.h>
namespace logger {
int write(const char* buf, size_t nbytes);
// In the case of Android the log can be written to the default system log
// (default behavior of write() above, or to the crash log (see
// writeToCrashLog() below).
#if defined(__ANDROID__)
// The logger must be initialized in a non-compromised context.
void initializeCrashLogWriter();
// Once initialized, writeToCrashLog is safe to use in a compromised context,
// even if the initialization failed, in which case this will silently fall
// back on write().
int writeToCrashLog(const char* buf);
#endif
} // namespace logger
#endif // CLIENT_LINUX_LOG_LOG_H_

@ -0,0 +1,671 @@
// Copyright 2014 Google LLC
//
// 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 LLC nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// This translation unit generates microdumps into the console (logcat on
// Android). See crbug.com/410294 for more info and design docs.
#include "client/linux/microdump_writer/microdump_writer.h"
#include <limits>
#include <sys/utsname.h>
#include "client/linux/dump_writer_common/thread_info.h"
#include "client/linux/dump_writer_common/ucontext_reader.h"
#include "client/linux/handler/exception_handler.h"
#include "client/linux/handler/microdump_extra_info.h"
#include "client/linux/log/log.h"
#include "client/linux/minidump_writer/linux_ptrace_dumper.h"
#include "common/linux/file_id.h"
#include "common/linux/linux_libc_support.h"
#include "common/memory_allocator.h"
namespace {
using google_breakpad::auto_wasteful_vector;
using google_breakpad::elf::kDefaultBuildIdSize;
using google_breakpad::ExceptionHandler;
using google_breakpad::LinuxDumper;
using google_breakpad::LinuxPtraceDumper;
using google_breakpad::MappingInfo;
using google_breakpad::MappingList;
using google_breakpad::MicrodumpExtraInfo;
using google_breakpad::RawContextCPU;
using google_breakpad::ThreadInfo;
using google_breakpad::UContextReader;
const size_t kLineBufferSize = 2048;
#if !defined(__LP64__)
// The following are only used by DumpFreeSpace, so need to be compiled
// in conditionally in the same way.
template <typename Dst, typename Src>
Dst saturated_cast(Src src) {
if (src >= std::numeric_limits<Dst>::max())
return std::numeric_limits<Dst>::max();
if (src <= std::numeric_limits<Dst>::min())
return std::numeric_limits<Dst>::min();
return static_cast<Dst>(src);
}
int Log2Floor(uint64_t n) {
// Copied from chromium src/base/bits.h
if (n == 0)
return -1;
int log = 0;
uint64_t value = n;
for (int i = 5; i >= 0; --i) {
int shift = (1 << i);
uint64_t x = value >> shift;
if (x != 0) {
value = x;
log += shift;
}
}
assert(value == 1u);
return log;
}
bool MappingsAreAdjacent(const MappingInfo& a, const MappingInfo& b) {
// Because of load biasing, we can end up with a situation where two
// mappings actually overlap. So we will define adjacency to also include a
// b start address that lies within a's address range (including starting
// immediately after a).
// Because load biasing only ever moves the start address backwards, the end
// address should still increase.
return a.start_addr <= b.start_addr && a.start_addr + a.size >= b.start_addr;
}
bool MappingLessThan(const MappingInfo* a, const MappingInfo* b) {
// Return true if mapping a is before mapping b.
// For the same reason (load biasing) we compare end addresses, which - unlike
// start addresses - will not have been modified.
return a->start_addr + a->size < b->start_addr + b->size;
}
size_t NextOrderedMapping(
const google_breakpad::wasteful_vector<MappingInfo*>& mappings,
size_t curr) {
// Find the mapping that directly follows mappings[curr].
// If no such mapping exists, return |invalid| to indicate this.
const size_t invalid = std::numeric_limits<size_t>::max();
size_t best = invalid;
for (size_t next = 0; next < mappings.size(); ++next) {
if (MappingLessThan(mappings[curr], mappings[next]) &&
(best == invalid || MappingLessThan(mappings[next], mappings[best]))) {
best = next;
}
}
return best;
}
#endif // !__LP64__
class MicrodumpWriter {
public:
MicrodumpWriter(const ExceptionHandler::CrashContext* context,
const MappingList& mappings,
bool skip_dump_if_principal_mapping_not_referenced,
uintptr_t address_within_principal_mapping,
bool sanitize_stack,
const MicrodumpExtraInfo& microdump_extra_info,
LinuxDumper* dumper)
: ucontext_(context ? &context->context : NULL),
#if GOOGLE_BREAKPAD_CRASH_CONTEXT_HAS_FLOAT_STATE
float_state_(context ? &context->float_state : NULL),
#endif
dumper_(dumper),
mapping_list_(mappings),
skip_dump_if_principal_mapping_not_referenced_(
skip_dump_if_principal_mapping_not_referenced),
address_within_principal_mapping_(address_within_principal_mapping),
sanitize_stack_(sanitize_stack),
microdump_extra_info_(microdump_extra_info),
log_line_(NULL),
stack_copy_(NULL),
stack_len_(0),
stack_lower_bound_(0),
stack_pointer_(0) {
log_line_ = reinterpret_cast<char*>(Alloc(kLineBufferSize));
if (log_line_)
log_line_[0] = '\0'; // Clear out the log line buffer.
}
~MicrodumpWriter() { dumper_->ThreadsResume(); }
bool Init() {
// In the exceptional case where the system was out of memory and there
// wasn't even room to allocate the line buffer, bail out. There is nothing
// useful we can possibly achieve without the ability to Log. At least let's
// try to not crash.
if (!dumper_->Init() || !log_line_)
return false;
return dumper_->ThreadsSuspend() && dumper_->LateInit();
}
void Dump() {
CaptureResult stack_capture_result = CaptureCrashingThreadStack(-1);
if (stack_capture_result == CAPTURE_UNINTERESTING) {
LogLine("Microdump skipped (uninteresting)");
return;
}
LogLine("-----BEGIN BREAKPAD MICRODUMP-----");
DumpProductInformation();
DumpOSInformation();
DumpProcessType();
DumpCrashReason();
DumpGPUInformation();
#if !defined(__LP64__)
DumpFreeSpace();
#endif
if (stack_capture_result == CAPTURE_OK)
DumpThreadStack();
DumpCPUState();
DumpMappings();
LogLine("-----END BREAKPAD MICRODUMP-----");
}
private:
enum CaptureResult { CAPTURE_OK, CAPTURE_FAILED, CAPTURE_UNINTERESTING };
// Writes one line to the system log.
void LogLine(const char* msg) {
#if defined(__ANDROID__)
logger::writeToCrashLog(msg);
#else
logger::write(msg, my_strlen(msg));
logger::write("\n", 1);
#endif
}
// Stages the given string in the current line buffer.
void LogAppend(const char* str) {
my_strlcat(log_line_, str, kLineBufferSize);
}
// As above (required to take precedence over template specialization below).
void LogAppend(char* str) {
LogAppend(const_cast<const char*>(str));
}
// Stages the hex repr. of the given int type in the current line buffer.
template<typename T>
void LogAppend(T value) {
// Make enough room to hex encode the largest int type + NUL.
static const char HEX[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F'};
char hexstr[sizeof(T) * 2 + 1];
for (int i = sizeof(T) * 2 - 1; i >= 0; --i, value >>= 4)
hexstr[i] = HEX[static_cast<uint8_t>(value) & 0x0F];
hexstr[sizeof(T) * 2] = '\0';
LogAppend(hexstr);
}
// Stages the buffer content hex-encoded in the current line buffer.
void LogAppend(const void* buf, size_t length) {
const uint8_t* ptr = reinterpret_cast<const uint8_t*>(buf);
for (size_t i = 0; i < length; ++i, ++ptr)
LogAppend(*ptr);
}
// Writes out the current line buffer on the system log.
void LogCommitLine() {
LogLine(log_line_);
log_line_[0] = 0;
}
CaptureResult CaptureCrashingThreadStack(int max_stack_len) {
stack_pointer_ = UContextReader::GetStackPointer(ucontext_);
if (!dumper_->GetStackInfo(reinterpret_cast<const void**>(&stack_lower_bound_),
&stack_len_, stack_pointer_)) {
return CAPTURE_FAILED;
}
if (max_stack_len >= 0 &&
stack_len_ > static_cast<size_t>(max_stack_len)) {
stack_len_ = max_stack_len;
}
stack_copy_ = reinterpret_cast<uint8_t*>(Alloc(stack_len_));
dumper_->CopyFromProcess(stack_copy_, dumper_->crash_thread(),
reinterpret_cast<const void*>(stack_lower_bound_),
stack_len_);
if (!skip_dump_if_principal_mapping_not_referenced_) return CAPTURE_OK;
const MappingInfo* principal_mapping =
dumper_->FindMappingNoBias(address_within_principal_mapping_);
if (!principal_mapping) return CAPTURE_UNINTERESTING;
uintptr_t low_addr = principal_mapping->system_mapping_info.start_addr;
uintptr_t high_addr = principal_mapping->system_mapping_info.end_addr;
uintptr_t pc = UContextReader::GetInstructionPointer(ucontext_);
if (low_addr <= pc && pc <= high_addr) return CAPTURE_OK;
if (dumper_->StackHasPointerToMapping(stack_copy_, stack_len_,
stack_pointer_ - stack_lower_bound_,
*principal_mapping)) {
return CAPTURE_OK;
}
return CAPTURE_UNINTERESTING;
}
void DumpProductInformation() {
LogAppend("V ");
if (microdump_extra_info_.product_info) {
LogAppend(microdump_extra_info_.product_info);
} else {
LogAppend("UNKNOWN:0.0.0.0");
}
LogCommitLine();
}
void DumpProcessType() {
LogAppend("P ");
if (microdump_extra_info_.process_type) {
LogAppend(microdump_extra_info_.process_type);
} else {
LogAppend("UNKNOWN");
}
LogCommitLine();
}
void DumpCrashReason() {
LogAppend("R ");
LogAppend(dumper_->crash_signal());
LogAppend(" ");
LogAppend(dumper_->GetCrashSignalString());
LogAppend(" ");
LogAppend(dumper_->crash_address());
LogCommitLine();
}
void DumpOSInformation() {
const uint8_t n_cpus = static_cast<uint8_t>(sysconf(_SC_NPROCESSORS_CONF));
#if defined(__ANDROID__)
const char kOSId[] = "A";
#else
const char kOSId[] = "L";
#endif
// Dump the runtime architecture. On multiarch devices it might not match the
// hw architecture (the one returned by uname()), for instance in the case of
// a 32-bit app running on a aarch64 device.
#if defined(__aarch64__)
const char kArch[] = "arm64";
#elif defined(__ARMEL__)
const char kArch[] = "arm";
#elif defined(__x86_64__)
const char kArch[] = "x86_64";
#elif defined(__i386__)
const char kArch[] = "x86";
#elif defined(__mips__)
# if _MIPS_SIM == _ABIO32
const char kArch[] = "mips";
# elif _MIPS_SIM == _ABI64
const char kArch[] = "mips64";
# else
# error "This mips ABI is currently not supported (n32)"
# endif
#elif defined(__riscv)
# if __riscv_xlen == 32
const char kArch[] = "riscv32";
# elif __riscv_xlen == 64
const char kArch[] = "riscv64";
# else
# error "Unexpected __riscv_xlen"
# endif
#else
# error "This code has not been ported to your platform yet"
#endif
LogAppend("O ");
LogAppend(kOSId);
LogAppend(" ");
LogAppend(kArch);
LogAppend(" ");
LogAppend(n_cpus);
LogAppend(" ");
// Dump the HW architecture (e.g., armv7l, aarch64).
struct utsname uts;
const bool has_uts_info = (uname(&uts) == 0);
const char* hwArch = has_uts_info ? uts.machine : "unknown_hw_arch";
LogAppend(hwArch);
LogAppend(" ");
// If the client has attached a build fingerprint to the MinidumpDescriptor
// use that one. Otherwise try to get some basic info from uname().
if (microdump_extra_info_.build_fingerprint) {
LogAppend(microdump_extra_info_.build_fingerprint);
} else if (has_uts_info) {
LogAppend(uts.release);
LogAppend(" ");
LogAppend(uts.version);
} else {
LogAppend("no build fingerprint available");
}
LogCommitLine();
}
void DumpGPUInformation() {
LogAppend("G ");
if (microdump_extra_info_.gpu_fingerprint) {
LogAppend(microdump_extra_info_.gpu_fingerprint);
} else {
LogAppend("UNKNOWN");
}
LogCommitLine();
}
void DumpThreadStack() {
if (sanitize_stack_) {
dumper_->SanitizeStackCopy(stack_copy_, stack_len_, stack_pointer_,
stack_pointer_ - stack_lower_bound_);
}
LogAppend("S 0 ");
LogAppend(stack_pointer_);
LogAppend(" ");
LogAppend(stack_lower_bound_);
LogAppend(" ");
LogAppend(stack_len_);
LogCommitLine();
const size_t STACK_DUMP_CHUNK_SIZE = 384;
for (size_t stack_off = 0; stack_off < stack_len_;
stack_off += STACK_DUMP_CHUNK_SIZE) {
LogAppend("S ");
LogAppend(stack_lower_bound_ + stack_off);
LogAppend(" ");
LogAppend(stack_copy_ + stack_off,
std::min(STACK_DUMP_CHUNK_SIZE, stack_len_ - stack_off));
LogCommitLine();
}
}
void DumpCPUState() {
RawContextCPU cpu;
my_memset(&cpu, 0, sizeof(RawContextCPU));
#if GOOGLE_BREAKPAD_CRASH_CONTEXT_HAS_FLOAT_STATE
UContextReader::FillCPUContext(&cpu, ucontext_, float_state_);
#else
UContextReader::FillCPUContext(&cpu, ucontext_);
#endif
LogAppend("C ");
LogAppend(&cpu, sizeof(cpu));
LogCommitLine();
}
// If there is caller-provided information about this mapping
// in the mapping_list_ list, return true. Otherwise, return false.
bool HaveMappingInfo(const MappingInfo& mapping) {
for (MappingList::const_iterator iter = mapping_list_.begin();
iter != mapping_list_.end();
++iter) {
// Ignore any mappings that are wholly contained within
// mappings in the mapping_info_ list.
if (mapping.start_addr >= iter->first.start_addr &&
(mapping.start_addr + mapping.size) <=
(iter->first.start_addr + iter->first.size)) {
return true;
}
}
return false;
}
// Dump information about the provided |mapping|. If |identifier| is non-NULL,
// use it instead of calculating a file ID from the mapping.
void DumpModule(const MappingInfo& mapping,
bool member,
unsigned int mapping_id,
const uint8_t* identifier) {
auto_wasteful_vector<uint8_t, kDefaultBuildIdSize> identifier_bytes(
dumper_->allocator());
if (identifier) {
// GUID was provided by caller.
identifier_bytes.insert(identifier_bytes.end(),
identifier,
identifier + sizeof(MDGUID));
} else {
dumper_->ElfFileIdentifierForMapping(
mapping,
member,
mapping_id,
identifier_bytes);
}
// Copy as many bytes of |identifier| as will fit into a MDGUID
MDGUID module_identifier = {0};
memcpy(&module_identifier, &identifier_bytes[0],
std::min(sizeof(MDGUID), identifier_bytes.size()));
char file_name[NAME_MAX];
char file_path[NAME_MAX];
dumper_->GetMappingEffectiveNameAndPath(
mapping, file_path, sizeof(file_path), file_name, sizeof(file_name));
LogAppend("M ");
LogAppend(static_cast<uintptr_t>(mapping.start_addr));
LogAppend(" ");
LogAppend(mapping.offset);
LogAppend(" ");
LogAppend(mapping.size);
LogAppend(" ");
LogAppend(module_identifier.data1);
LogAppend(module_identifier.data2);
LogAppend(module_identifier.data3);
LogAppend(module_identifier.data4[0]);
LogAppend(module_identifier.data4[1]);
LogAppend(module_identifier.data4[2]);
LogAppend(module_identifier.data4[3]);
LogAppend(module_identifier.data4[4]);
LogAppend(module_identifier.data4[5]);
LogAppend(module_identifier.data4[6]);
LogAppend(module_identifier.data4[7]);
LogAppend("0 "); // Age is always 0 on Linux.
LogAppend(file_name);
LogCommitLine();
}
#if !defined(__LP64__)
void DumpFreeSpace() {
const MappingInfo* stack_mapping = nullptr;
ThreadInfo info;
if (dumper_->GetThreadInfoByIndex(dumper_->GetMainThreadIndex(), &info)) {
stack_mapping = dumper_->FindMappingNoBias(info.stack_pointer);
}
const google_breakpad::wasteful_vector<MappingInfo*>& mappings =
dumper_->mappings();
if (mappings.size() == 0) return;
// This is complicated by the fact that mappings is not in order. It should
// be mostly in order, however the mapping that contains the entry point for
// the process is always at the front of the vector.
static const int HBITS = sizeof(size_t) * 8;
size_t hole_histogram[HBITS];
my_memset(hole_histogram, 0, sizeof(hole_histogram));
// Find the lowest address mapping.
size_t curr = 0;
for (size_t i = 1; i < mappings.size(); ++i) {
if (mappings[i]->start_addr < mappings[curr]->start_addr) curr = i;
}
uintptr_t lo_addr = mappings[curr]->start_addr;
size_t hole_cnt = 0;
size_t hole_max = 0;
size_t hole_sum = 0;
while (true) {
// Skip to the end of an adjacent run of mappings. This is an optimization
// for the fact that mappings is mostly sorted.
while (curr != mappings.size() - 1 &&
MappingsAreAdjacent(*mappings[curr], *mappings[curr + 1])) {
++curr;
}
if (mappings[curr] == stack_mapping) {
// Because we can't determine the top of userspace mappable
// memory we treat the start of the process stack as the top
// of the allocatable address space. Once we reach
// |stack_mapping| we are done scanning for free space regions.
break;
}
size_t next = NextOrderedMapping(mappings, curr);
if (next == std::numeric_limits<size_t>::max())
break;
uintptr_t hole_lo = mappings[curr]->start_addr + mappings[curr]->size;
uintptr_t hole_hi = mappings[next]->start_addr;
if (hole_hi > hole_lo) {
size_t hole_sz = hole_hi - hole_lo;
hole_sum += hole_sz;
hole_max = std::max(hole_sz, hole_max);
++hole_cnt;
++hole_histogram[Log2Floor(hole_sz)];
}
curr = next;
}
uintptr_t hi_addr = mappings[curr]->start_addr + mappings[curr]->size;
LogAppend("H ");
LogAppend(lo_addr);
LogAppend(" ");
LogAppend(hi_addr);
LogAppend(" ");
LogAppend(saturated_cast<uint16_t>(hole_cnt));
LogAppend(" ");
LogAppend(hole_max);
LogAppend(" ");
LogAppend(hole_sum);
for (unsigned int i = 0; i < HBITS; ++i) {
if (!hole_histogram[i]) continue;
LogAppend(" ");
LogAppend(saturated_cast<uint8_t>(i));
LogAppend(":");
LogAppend(saturated_cast<uint8_t>(hole_histogram[i]));
}
LogCommitLine();
}
#endif
// Write information about the mappings in effect.
void DumpMappings() {
// First write all the mappings from the dumper
for (unsigned i = 0; i < dumper_->mappings().size(); ++i) {
const MappingInfo& mapping = *dumper_->mappings()[i];
if (mapping.name[0] == 0 || // only want modules with filenames.
!mapping.exec || // only want executable mappings.
mapping.size < 4096 || // too small to get a signature for.
HaveMappingInfo(mapping)) {
continue;
}
DumpModule(mapping, true, i, NULL);
}
// Next write all the mappings provided by the caller
for (MappingList::const_iterator iter = mapping_list_.begin();
iter != mapping_list_.end();
++iter) {
DumpModule(iter->first, false, 0, iter->second);
}
}
void* Alloc(unsigned bytes) { return dumper_->allocator()->Alloc(bytes); }
const ucontext_t* const ucontext_;
#if GOOGLE_BREAKPAD_CRASH_CONTEXT_HAS_FLOAT_STATE
const google_breakpad::fpstate_t* const float_state_;
#endif
LinuxDumper* dumper_;
const MappingList& mapping_list_;
bool skip_dump_if_principal_mapping_not_referenced_;
uintptr_t address_within_principal_mapping_;
bool sanitize_stack_;
const MicrodumpExtraInfo microdump_extra_info_;
char* log_line_;
// The local copy of crashed process stack memory, beginning at
// |stack_lower_bound_|.
uint8_t* stack_copy_;
// The length of crashed process stack copy.
size_t stack_len_;
// The address of the page containing the stack pointer in the
// crashed process. |stack_lower_bound_| <= |stack_pointer_|
uintptr_t stack_lower_bound_;
// The stack pointer of the crashed thread.
uintptr_t stack_pointer_;
};
} // namespace
namespace google_breakpad {
bool WriteMicrodump(pid_t crashing_process,
const void* blob,
size_t blob_size,
const MappingList& mappings,
bool skip_dump_if_principal_mapping_not_referenced,
uintptr_t address_within_principal_mapping,
bool sanitize_stack,
const MicrodumpExtraInfo& microdump_extra_info) {
LinuxPtraceDumper dumper(crashing_process);
const ExceptionHandler::CrashContext* context = NULL;
if (blob) {
if (blob_size != sizeof(ExceptionHandler::CrashContext))
return false;
context = reinterpret_cast<const ExceptionHandler::CrashContext*>(blob);
dumper.SetCrashInfoFromSigInfo(context->siginfo);
dumper.set_crash_thread(context->tid);
}
MicrodumpWriter writer(context, mappings,
skip_dump_if_principal_mapping_not_referenced,
address_within_principal_mapping, sanitize_stack,
microdump_extra_info, &dumper);
if (!writer.Init())
return false;
writer.Dump();
return true;
}
} // namespace google_breakpad

@ -0,0 +1,67 @@
// Copyright 2014 Google LLC
//
// 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 LLC 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_MICRODUMP_WRITER_H_
#define CLIENT_LINUX_MINIDUMP_WRITER_MICRODUMP_WRITER_H_
#include <stdint.h>
#include <sys/types.h>
#include "client/linux/dump_writer_common/mapping_info.h"
namespace google_breakpad {
struct MicrodumpExtraInfo;
// Writes a microdump (a reduced dump containing only the state of the crashing
// thread) on the console (logcat on Android). 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.
// Args:
// 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.
// mappings: a list of additional mappings provided by the application.
// build_fingerprint: a (optional) C string which determines the OS
// build fingerprint (e.g., aosp/occam/mako:5.1.1/LMY47W/1234:eng/dev-keys).
// product_info: a (optional) C string which determines the product name and
// version (e.g., WebView:42.0.2311.136).
//
// Returns true iff successful.
bool WriteMicrodump(pid_t crashing_process,
const void* blob,
size_t blob_size,
const MappingList& mappings,
bool skip_dump_if_main_module_not_referenced,
uintptr_t address_within_main_module,
bool sanitize_stack,
const MicrodumpExtraInfo& microdump_extra_info);
} // namespace google_breakpad
#endif // CLIENT_LINUX_MINIDUMP_WRITER_MICRODUMP_WRITER_H_

@ -0,0 +1,420 @@
// Copyright 2014 Google LLC
//
// 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 LLC 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 <ctype.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#include <ucontext.h>
#include <sstream>
#include <string>
#include "breakpad_googletest_includes.h"
#include "client/linux/handler/exception_handler.h"
#include "client/linux/handler/microdump_extra_info.h"
#include "client/linux/microdump_writer/microdump_writer.h"
#include "common/linux/breakpad_getcontext.h"
#include "common/linux/eintr_wrapper.h"
#include "common/linux/ignore_ret.h"
#include "common/scoped_ptr.h"
#include "common/tests/auto_tempdir.h"
#include "common/using_std_string.h"
using namespace google_breakpad;
extern "C" {
extern char __executable_start;
extern char __etext;
}
namespace {
typedef testing::Test MicrodumpWriterTest;
MicrodumpExtraInfo MakeMicrodumpExtraInfo(
const char* build_fingerprint,
const char* product_info,
const char* gpu_fingerprint) {
MicrodumpExtraInfo info;
info.build_fingerprint = build_fingerprint;
info.product_info = product_info;
info.gpu_fingerprint = gpu_fingerprint;
info.process_type = "Browser";
return info;
}
bool ContainsMicrodump(const std::string& buf) {
return std::string::npos != buf.find("-----BEGIN BREAKPAD MICRODUMP-----") &&
std::string::npos != buf.find("-----END BREAKPAD MICRODUMP-----");
}
const char kIdentifiableString[] = "_IDENTIFIABLE_";
const uintptr_t kCrashAddress = 0xdeaddeadu;
void CrashAndGetMicrodump(const MappingList& mappings,
const MicrodumpExtraInfo& microdump_extra_info,
std::string* microdump,
bool skip_dump_if_principal_mapping_not_referenced = false,
uintptr_t address_within_principal_mapping = 0,
bool sanitize_stack = false) {
int fds[2];
ASSERT_NE(-1, pipe(fds));
AutoTempDir temp_dir;
string stderr_file = temp_dir.path() + "/stderr.log";
int err_fd = open(stderr_file.c_str(), O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
ASSERT_NE(-1, err_fd);
char identifiable_string[sizeof(kIdentifiableString)];
// This string should not appear in the resulting microdump if it
// has been sanitized.
strcpy(identifiable_string, kIdentifiableString);
// Force the strcpy to not be optimized away.
IGNORE_RET(write(STDOUT_FILENO, identifiable_string, 0));
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));
// Pretend the current context is the child context (which is
// approximately right) so that we have a valid stack pointer, and
// can fetch child stack data via ptrace.
getcontext(&context.context);
// Set a non-zero tid to avoid tripping asserts.
context.tid = child;
context.siginfo.si_signo = MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED;
context.siginfo.si_addr = reinterpret_cast<void*>(kCrashAddress);
// Redirect temporarily stderr to the stderr.log file.
int save_err = dup(STDERR_FILENO);
ASSERT_NE(-1, save_err);
ASSERT_NE(-1, dup2(err_fd, STDERR_FILENO));
ASSERT_TRUE(WriteMicrodump(child, &context, sizeof(context), mappings,
skip_dump_if_principal_mapping_not_referenced,
address_within_principal_mapping, sanitize_stack,
microdump_extra_info));
// Revert stderr back to the console.
dup2(save_err, STDERR_FILENO);
close(save_err);
// Read back the stderr file and check for the microdump marker.
fsync(err_fd);
lseek(err_fd, 0, SEEK_SET);
microdump->clear();
char buf[1024];
while (true) {
int bytes_read = IGNORE_EINTR(read(err_fd, buf, 1024));
if (bytes_read <= 0) break;
microdump->append(buf, buf + bytes_read);
}
close(err_fd);
close(fds[1]);
}
void ExtractMicrodumpStackContents(const string& microdump_content,
string* result) {
std::istringstream iss(microdump_content);
result->clear();
for (string line; std::getline(iss, line);) {
if (line.find("S ") == 0) {
std::istringstream stack_data(line);
std::string key;
std::string addr;
std::string data;
stack_data >> key >> addr >> data;
EXPECT_TRUE((data.size() & 1u) == 0u);
result->reserve(result->size() + data.size() / 2);
for (size_t i = 0; i < data.size(); i += 2) {
std::string byte = data.substr(i, 2);
result->push_back(static_cast<char>(strtoul(byte.c_str(), NULL, 16)));
}
}
}
}
void CheckMicrodumpContents(const string& microdump_content,
const MicrodumpExtraInfo& expected_info) {
std::istringstream iss(microdump_content);
bool did_find_os_info = false;
bool did_find_product_info = false;
bool did_find_process_type = false;
bool did_find_crash_reason = false;
bool did_find_gpu_info = false;
for (string line; std::getline(iss, line);) {
if (line.find("O ") == 0) {
std::istringstream os_info_tokens(line);
string token;
os_info_tokens.ignore(2); // Ignore the "O " preamble.
// Check the OS descriptor char (L=Linux, A=Android).
os_info_tokens >> token;
ASSERT_TRUE(token == "L" || token == "A");
os_info_tokens >> token; // HW architecture.
os_info_tokens >> token; // Number of cpus.
for (size_t i = 0; i < token.size(); ++i)
ASSERT_TRUE(isxdigit(token[i]));
os_info_tokens >> token; // SW architecture.
// Check that the build fingerprint is in the right place.
os_info_tokens >> token;
ASSERT_FALSE(os_info_tokens.fail());
if (expected_info.build_fingerprint)
ASSERT_EQ(expected_info.build_fingerprint, token);
did_find_os_info = true;
} else if (line.find("P ") == 0) {
if (expected_info.process_type)
ASSERT_EQ(string("P ") + expected_info.process_type, line);
did_find_process_type = true;
} else if (line.find("R ") == 0) {
std::istringstream crash_reason_tokens(line);
string token;
unsigned crash_reason;
string crash_reason_str;
uintptr_t crash_address;
crash_reason_tokens.ignore(2); // Ignore the "R " preamble.
crash_reason_tokens >> std::hex >> crash_reason >> crash_reason_str >>
crash_address;
ASSERT_FALSE(crash_reason_tokens.fail());
ASSERT_EQ(MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED, crash_reason);
ASSERT_EQ("DUMP_REQUESTED", crash_reason_str);
ASSERT_EQ(kCrashAddress, crash_address);
did_find_crash_reason = true;
} else if (line.find("V ") == 0) {
if (expected_info.product_info)
ASSERT_EQ(string("V ") + expected_info.product_info, line);
did_find_product_info = true;
} else if (line.find("G ") == 0) {
if (expected_info.gpu_fingerprint)
ASSERT_EQ(string("G ") + expected_info.gpu_fingerprint, line);
did_find_gpu_info = true;
}
}
ASSERT_TRUE(did_find_os_info);
ASSERT_TRUE(did_find_product_info);
ASSERT_TRUE(did_find_process_type);
ASSERT_TRUE(did_find_crash_reason);
ASSERT_TRUE(did_find_gpu_info);
}
bool MicrodumpStackContains(const string& microdump_content,
const string& expected_content) {
string result;
ExtractMicrodumpStackContents(microdump_content, &result);
return result.find(kIdentifiableString) != string::npos;
}
void CheckMicrodumpContents(const string& microdump_content,
const string& expected_fingerprint,
const string& expected_product_info,
const string& expected_gpu_fingerprint) {
CheckMicrodumpContents(
microdump_content,
MakeMicrodumpExtraInfo(expected_fingerprint.c_str(),
expected_product_info.c_str(),
expected_gpu_fingerprint.c_str()));
}
TEST(MicrodumpWriterTest, BasicWithMappings) {
// Push some extra mapping to check the MappingList logic.
const uint32_t memory_size = sysconf(_SC_PAGESIZE);
const char* kMemoryName = "libfoo.so";
const uint8_t kModuleGUID[sizeof(MDGUID)] = {
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
};
MappingInfo info;
info.start_addr = memory_size;
info.size = memory_size;
info.offset = 42;
strcpy(info.name, kMemoryName);
MappingList mappings;
MappingEntry mapping;
mapping.first = info;
memcpy(mapping.second, kModuleGUID, sizeof(MDGUID));
mappings.push_back(mapping);
std::string buf;
CrashAndGetMicrodump(mappings, MicrodumpExtraInfo(), &buf);
ASSERT_TRUE(ContainsMicrodump(buf));
#ifdef __LP64__
ASSERT_NE(std::string::npos,
buf.find("M 0000000000001000 000000000000002A 0000000000001000 "
"33221100554477668899AABBCCDDEEFF0 libfoo.so"));
#else
ASSERT_NE(std::string::npos,
buf.find("M 00001000 0000002A 00001000 "
"33221100554477668899AABBCCDDEEFF0 libfoo.so"));
#endif
// In absence of a product info in the minidump, the writer should just write
// an unknown marker.
ASSERT_NE(std::string::npos, buf.find("V UNKNOWN:0.0.0.0"));
}
// Ensure that no output occurs if the interest region is set, but
// doesn't overlap anything on the stack.
TEST(MicrodumpWriterTest, NoOutputIfUninteresting) {
const char kProductInfo[] = "MockProduct:42.0.2311.99";
const char kBuildFingerprint[] =
"aosp/occam/mako:5.1.1/LMY47W/12345678:userdegbug/dev-keys";
const char kGPUFingerprint[] =
"Qualcomm;Adreno (TM) 330;OpenGL ES 3.0 V@104.0 AU@ (GIT@Id3510ff6dc)";
const MicrodumpExtraInfo kMicrodumpExtraInfo(
MakeMicrodumpExtraInfo(kBuildFingerprint, kProductInfo, kGPUFingerprint));
std::string buf;
MappingList no_mappings;
CrashAndGetMicrodump(no_mappings, kMicrodumpExtraInfo, &buf, true, 0);
ASSERT_FALSE(ContainsMicrodump(buf));
}
// Ensure that stack content does not contain an identifiable string if the
// stack is sanitized.
TEST(MicrodumpWriterTest, StringRemovedBySanitization) {
const char kProductInfo[] = "MockProduct:42.0.2311.99";
const char kBuildFingerprint[] =
"aosp/occam/mako:5.1.1/LMY47W/12345678:userdegbug/dev-keys";
const char kGPUFingerprint[] =
"Qualcomm;Adreno (TM) 330;OpenGL ES 3.0 V@104.0 AU@ (GIT@Id3510ff6dc)";
const MicrodumpExtraInfo kMicrodumpExtraInfo(
MakeMicrodumpExtraInfo(kBuildFingerprint, kProductInfo, kGPUFingerprint));
std::string buf;
MappingList no_mappings;
CrashAndGetMicrodump(no_mappings, kMicrodumpExtraInfo, &buf, false, 0u, true);
ASSERT_TRUE(ContainsMicrodump(buf));
ASSERT_FALSE(MicrodumpStackContains(buf, kIdentifiableString));
}
// Ensure that stack content does contain an identifiable string if the
// stack is not sanitized.
TEST(MicrodumpWriterTest, StringPresentIfNotSanitized) {
const char kProductInfo[] = "MockProduct:42.0.2311.99";
const char kBuildFingerprint[] =
"aosp/occam/mako:5.1.1/LMY47W/12345678:userdegbug/dev-keys";
const char kGPUFingerprint[] =
"Qualcomm;Adreno (TM) 330;OpenGL ES 3.0 V@104.0 AU@ (GIT@Id3510ff6dc)";
const MicrodumpExtraInfo kMicrodumpExtraInfo(
MakeMicrodumpExtraInfo(kBuildFingerprint, kProductInfo, kGPUFingerprint));
std::string buf;
MappingList no_mappings;
CrashAndGetMicrodump(no_mappings, kMicrodumpExtraInfo, &buf, false, 0u, false);
ASSERT_TRUE(ContainsMicrodump(buf));
ASSERT_TRUE(MicrodumpStackContains(buf, kIdentifiableString));
}
// Ensure that output occurs if the interest region is set, and
// does overlap something on the stack.
TEST(MicrodumpWriterTest, OutputIfInteresting) {
const char kProductInfo[] = "MockProduct:42.0.2311.99";
const char kBuildFingerprint[] =
"aosp/occam/mako:5.1.1/LMY47W/12345678:userdegbug/dev-keys";
const char kGPUFingerprint[] =
"Qualcomm;Adreno (TM) 330;OpenGL ES 3.0 V@104.0 AU@ (GIT@Id3510ff6dc)";
const MicrodumpExtraInfo kMicrodumpExtraInfo(
MakeMicrodumpExtraInfo(kBuildFingerprint, kProductInfo, kGPUFingerprint));
std::string buf;
MappingList no_mappings;
CrashAndGetMicrodump(no_mappings, kMicrodumpExtraInfo, &buf, true,
reinterpret_cast<uintptr_t>(CrashAndGetMicrodump));
ASSERT_TRUE(ContainsMicrodump(buf));
}
// Ensure that the product info and build fingerprint metadata show up in the
// final microdump if present.
TEST(MicrodumpWriterTest, BuildFingerprintAndProductInfo) {
const char kProductInfo[] = "MockProduct:42.0.2311.99";
const char kBuildFingerprint[] =
"aosp/occam/mako:5.1.1/LMY47W/12345678:userdegbug/dev-keys";
const char kGPUFingerprint[] =
"Qualcomm;Adreno (TM) 330;OpenGL ES 3.0 V@104.0 AU@ (GIT@Id3510ff6dc)";
const MicrodumpExtraInfo kMicrodumpExtraInfo(
MakeMicrodumpExtraInfo(kBuildFingerprint, kProductInfo, kGPUFingerprint));
std::string buf;
MappingList no_mappings;
CrashAndGetMicrodump(no_mappings, kMicrodumpExtraInfo, &buf);
ASSERT_TRUE(ContainsMicrodump(buf));
CheckMicrodumpContents(buf, kMicrodumpExtraInfo);
}
TEST(MicrodumpWriterTest, NoProductInfo) {
const char kBuildFingerprint[] = "foobar";
const char kGPUFingerprint[] = "bazqux";
std::string buf;
MappingList no_mappings;
const MicrodumpExtraInfo kMicrodumpExtraInfoNoProductInfo(
MakeMicrodumpExtraInfo(kBuildFingerprint, NULL, kGPUFingerprint));
CrashAndGetMicrodump(no_mappings, kMicrodumpExtraInfoNoProductInfo, &buf);
ASSERT_TRUE(ContainsMicrodump(buf));
CheckMicrodumpContents(buf, kBuildFingerprint, "UNKNOWN:0.0.0.0",
kGPUFingerprint);
}
TEST(MicrodumpWriterTest, NoGPUInfo) {
const char kProductInfo[] = "bazqux";
const char kBuildFingerprint[] = "foobar";
std::string buf;
MappingList no_mappings;
const MicrodumpExtraInfo kMicrodumpExtraInfoNoGPUInfo(
MakeMicrodumpExtraInfo(kBuildFingerprint, kProductInfo, NULL));
CrashAndGetMicrodump(no_mappings, kMicrodumpExtraInfoNoGPUInfo, &buf);
ASSERT_TRUE(ContainsMicrodump(buf));
CheckMicrodumpContents(buf, kBuildFingerprint, kProductInfo, "UNKNOWN");
}
} // namespace

@ -0,0 +1,143 @@
// Copyright 2013 Google LLC
//
// 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 LLC 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_

@ -0,0 +1,163 @@
// Copyright 2013 Google LLC
//
// 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 LLC 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());
}

@ -0,0 +1,105 @@
// Copyright 2009 Google LLC
//
// 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 LLC nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef CLIENT_LINUX_MINIDUMP_WRITER_DIRECTORY_READER_H_
#define CLIENT_LINUX_MINIDUMP_WRITER_DIRECTORY_READER_H_
#include <stdint.h>
#include <unistd.h>
#include <limits.h>
#include <assert.h>
#include <errno.h>
#include <string.h>
#include "common/linux/linux_libc_support.h"
#include "third_party/lss/linux_syscall_support.h"
namespace google_breakpad {
// A class for enumerating a directory without using diropen/readdir or other
// functions which may allocate memory.
class DirectoryReader {
public:
DirectoryReader(int fd)
: fd_(fd),
buf_used_(0) {
}
// Return the next entry from the directory
// name: (output) the NUL terminated entry name
//
// Returns true iff successful (false on EOF).
//
// After calling this, one must call |PopEntry| otherwise you'll get the same
// entry over and over.
bool GetNextEntry(const char** name) {
struct kernel_dirent* const dent =
reinterpret_cast<kernel_dirent*>(buf_);
if (buf_used_ == 0) {
// need to read more entries.
const int n = sys_getdents(fd_, dent, sizeof(buf_));
if (n < 0) {
return false;
} else if (n == 0) {
hit_eof_ = true;
} else {
buf_used_ += n;
}
}
if (buf_used_ == 0 && hit_eof_)
return false;
assert(buf_used_ > 0);
*name = dent->d_name;
return true;
}
void PopEntry() {
if (!buf_used_)
return;
const struct kernel_dirent* const dent =
reinterpret_cast<kernel_dirent*>(buf_);
buf_used_ -= dent->d_reclen;
my_memmove(buf_, buf_ + dent->d_reclen, buf_used_);
}
private:
const int fd_;
bool hit_eof_;
unsigned buf_used_;
uint8_t buf_[sizeof(struct kernel_dirent) + NAME_MAX + 1];
};
} // namespace google_breakpad
#endif // CLIENT_LINUX_MINIDUMP_WRITER_DIRECTORY_READER_H_

@ -0,0 +1,77 @@
// Copyright 2009 Google LLC
//
// 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 LLC nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <set>
#include <string>
#include <dirent.h>
#include <fcntl.h>
#include <sys/types.h>
#include "client/linux/minidump_writer/directory_reader.h"
#include "common/using_std_string.h"
#include "breakpad_googletest_includes.h"
using namespace google_breakpad;
namespace {
typedef testing::Test DirectoryReaderTest;
}
TEST(DirectoryReaderTest, CompareResults) {
std::set<string> dent_set;
DIR* const dir = opendir("/proc/self");
ASSERT_TRUE(dir != NULL);
struct dirent* dent;
while ((dent = readdir(dir)))
dent_set.insert(dent->d_name);
closedir(dir);
const int fd = open("/proc/self", O_DIRECTORY | O_RDONLY);
ASSERT_GE(fd, 0);
DirectoryReader dir_reader(fd);
unsigned seen = 0;
const char* name;
while (dir_reader.GetNextEntry(&name)) {
ASSERT_TRUE(dent_set.find(name) != dent_set.end());
seen++;
dir_reader.PopEntry();
}
ASSERT_TRUE(dent_set.find("status") != dent_set.end());
ASSERT_TRUE(dent_set.find("stat") != dent_set.end());
ASSERT_TRUE(dent_set.find("cmdline") != dent_set.end());
ASSERT_EQ(dent_set.size(), seen);
close(fd);
}

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

@ -0,0 +1,168 @@
// Copyright 2009 Google LLC
//
// 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 LLC nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include "client/linux/minidump_writer/line_reader.h"
#include "breakpad_googletest_includes.h"
#include "common/linux/tests/auto_testfile.h"
using namespace google_breakpad;
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) {
ScopedTestFile file("");
ASSERT_TRUE(file.IsOk());
LineReader reader(file.GetFd());
const char* line;
unsigned len;
ASSERT_FALSE(reader.GetNextLine(&line, &len));
}
TEST(LineReaderTest, OneLineTerminated) {
ScopedTestFile file("a\n");
ASSERT_TRUE(file.IsOk());
LineReader reader(file.GetFd());
const char* line;
unsigned int len;
ASSERT_TRUE(reader.GetNextLine(&line, &len));
ASSERT_EQ((unsigned int)1, len);
ASSERT_EQ('a', line[0]);
ASSERT_EQ('\0', line[1]);
reader.PopLine(len);
ASSERT_FALSE(reader.GetNextLine(&line, &len));
}
TEST(LineReaderTest, OneLine) {
ScopedTestFile file("a");
ASSERT_TRUE(file.IsOk());
LineReader reader(file.GetFd());
const char* line;
unsigned len;
ASSERT_TRUE(reader.GetNextLine(&line, &len));
ASSERT_EQ((unsigned)1, len);
ASSERT_EQ('a', line[0]);
ASSERT_EQ('\0', line[1]);
reader.PopLine(len);
ASSERT_FALSE(reader.GetNextLine(&line, &len));
}
TEST(LineReaderTest, TwoLinesTerminated) {
ScopedTestFile file("a\nb\n");
ASSERT_TRUE(file.IsOk());
LineReader reader(file.GetFd());
const char* line;
unsigned len;
ASSERT_TRUE(reader.GetNextLine(&line, &len));
ASSERT_EQ((unsigned)1, len);
ASSERT_EQ('a', line[0]);
ASSERT_EQ('\0', line[1]);
reader.PopLine(len);
ASSERT_TRUE(reader.GetNextLine(&line, &len));
ASSERT_EQ((unsigned)1, len);
ASSERT_EQ('b', line[0]);
ASSERT_EQ('\0', line[1]);
reader.PopLine(len);
ASSERT_FALSE(reader.GetNextLine(&line, &len));
}
TEST(LineReaderTest, TwoLines) {
ScopedTestFile file("a\nb");
ASSERT_TRUE(file.IsOk());
LineReader reader(file.GetFd());
const char* line;
unsigned len;
ASSERT_TRUE(reader.GetNextLine(&line, &len));
ASSERT_EQ((unsigned)1, len);
ASSERT_EQ('a', line[0]);
ASSERT_EQ('\0', line[1]);
reader.PopLine(len);
ASSERT_TRUE(reader.GetNextLine(&line, &len));
ASSERT_EQ((unsigned)1, len);
ASSERT_EQ('b', line[0]);
ASSERT_EQ('\0', line[1]);
reader.PopLine(len);
ASSERT_FALSE(reader.GetNextLine(&line, &len));
}
TEST(LineReaderTest, MaxLength) {
char l[LineReader::kMaxLineLen-1];
memset(l, 'a', sizeof(l));
ScopedTestFile file(l, sizeof(l));
ASSERT_TRUE(file.IsOk());
LineReader reader(file.GetFd());
const char* line;
unsigned len;
ASSERT_TRUE(reader.GetNextLine(&line, &len));
ASSERT_EQ(sizeof(l), len);
ASSERT_TRUE(memcmp(l, line, sizeof(l)) == 0);
ASSERT_EQ('\0', line[len]);
}
TEST(LineReaderTest, TooLong) {
// Note: this writes kMaxLineLen 'a' chars in the test file.
char l[LineReader::kMaxLineLen];
memset(l, 'a', sizeof(l));
ScopedTestFile file(l, sizeof(l));
ASSERT_TRUE(file.IsOk());
LineReader reader(file.GetFd());
const char* line;
unsigned len;
ASSERT_FALSE(reader.GetNextLine(&line, &len));
}

@ -0,0 +1,317 @@
// Copyright 2012 Google LLC
//
// 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 LLC 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.
// linux_core_dumper.cc: Implement google_breakpad::LinuxCoreDumper.
// See linux_core_dumper.h for details.
#include "client/linux/minidump_writer/linux_core_dumper.h"
#include <asm/ptrace.h>
#include <assert.h>
#include <elf.h>
#include <stdio.h>
#include <string.h>
#include <sys/procfs.h>
#if defined(__mips__) && defined(__ANDROID__)
// To get register definitions.
#include <asm/reg.h>
#endif
#include "common/linux/elf_gnu_compat.h"
#include "common/linux/linux_libc_support.h"
namespace google_breakpad {
LinuxCoreDumper::LinuxCoreDumper(pid_t pid,
const char* core_path,
const char* procfs_path,
const char* root_prefix)
: LinuxDumper(pid, root_prefix),
core_path_(core_path),
procfs_path_(procfs_path),
thread_infos_(&allocator_, 8) {
assert(core_path_);
}
bool LinuxCoreDumper::BuildProcPath(char* path, pid_t pid,
const char* node) const {
if (!path || !node)
return false;
size_t node_len = my_strlen(node);
if (node_len == 0)
return false;
size_t procfs_path_len = my_strlen(procfs_path_);
size_t total_length = procfs_path_len + 1 + node_len;
if (total_length >= NAME_MAX)
return false;
memcpy(path, procfs_path_, procfs_path_len);
path[procfs_path_len] = '/';
memcpy(path + procfs_path_len + 1, node, node_len);
path[total_length] = '\0';
return true;
}
bool LinuxCoreDumper::CopyFromProcess(void* dest, pid_t child,
const void* src, size_t length) {
ElfCoreDump::Addr virtual_address = reinterpret_cast<ElfCoreDump::Addr>(src);
// TODO(benchan): Investigate whether the data to be copied could span
// across multiple segments in the core dump file. ElfCoreDump::CopyData
// and this method do not handle that case yet.
if (!core_.CopyData(dest, virtual_address, length)) {
// If the data segment is not found in the core dump, fill the result
// with marker characters.
memset(dest, 0xab, length);
return false;
}
return true;
}
bool LinuxCoreDumper::GetThreadInfoByIndex(size_t index, ThreadInfo* info) {
if (index >= thread_infos_.size())
return false;
*info = thread_infos_[index];
const uint8_t* stack_pointer;
#if defined(__i386)
memcpy(&stack_pointer, &info->regs.esp, sizeof(info->regs.esp));
#elif defined(__x86_64)
memcpy(&stack_pointer, &info->regs.rsp, sizeof(info->regs.rsp));
#elif defined(__ARM_EABI__)
memcpy(&stack_pointer, &info->regs.ARM_sp, sizeof(info->regs.ARM_sp));
#elif defined(__aarch64__)
memcpy(&stack_pointer, &info->regs.sp, sizeof(info->regs.sp));
#elif defined(__mips__)
stack_pointer =
reinterpret_cast<uint8_t*>(info->mcontext.gregs[MD_CONTEXT_MIPS_REG_SP]);
#elif defined(__riscv)
stack_pointer = reinterpret_cast<uint8_t*>(
info->mcontext.__gregs[MD_CONTEXT_RISCV_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 true;
}
bool LinuxCoreDumper::IsPostMortem() const {
return true;
}
bool LinuxCoreDumper::ThreadsSuspend() {
return true;
}
bool LinuxCoreDumper::ThreadsResume() {
return true;
}
bool LinuxCoreDumper::EnumerateThreads() {
if (!mapped_core_file_.Map(core_path_, 0)) {
fprintf(stderr, "Could not map core dump file into memory\n");
return false;
}
char proc_mem_path[NAME_MAX];
if (BuildProcPath(proc_mem_path, pid_, "mem")) {
int fd = open(proc_mem_path, O_RDONLY | O_LARGEFILE | O_CLOEXEC);
if (fd != -1) {
core_.SetProcMem(fd);
} else {
fprintf(stderr, "Cannot open %s (%s)\n", proc_mem_path, strerror(errno));
}
}
core_.SetContent(mapped_core_file_.content());
if (!core_.IsValid()) {
fprintf(stderr, "Invalid core dump file\n");
return false;
}
ElfCoreDump::Note note = core_.GetFirstNote();
if (!note.IsValid()) {
fprintf(stderr, "PT_NOTE section not found\n");
return false;
}
bool first_thread = true;
do {
ElfCoreDump::Word type = note.GetType();
MemoryRange name = note.GetName();
MemoryRange description = note.GetDescription();
if (type == 0 || name.IsEmpty() || description.IsEmpty()) {
fprintf(stderr, "Could not found a valid PT_NOTE.\n");
return false;
}
// Based on write_note_info() in linux/kernel/fs/binfmt_elf.c, notes are
// ordered as follows (NT_PRXFPREG and NT_386_TLS are i386 specific):
// Thread Name Type
// -------------------------------------------------------------------
// 1st thread CORE NT_PRSTATUS
// process-wide CORE NT_PRPSINFO
// process-wide CORE NT_SIGINFO
// process-wide CORE NT_AUXV
// 1st thread CORE NT_FPREGSET
// 1st thread LINUX NT_PRXFPREG
// 1st thread LINUX NT_386_TLS
//
// 2nd thread CORE NT_PRSTATUS
// 2nd thread CORE NT_FPREGSET
// 2nd thread LINUX NT_PRXFPREG
// 2nd thread LINUX NT_386_TLS
//
// 3rd thread CORE NT_PRSTATUS
// 3rd thread CORE NT_FPREGSET
// 3rd thread LINUX NT_PRXFPREG
// 3rd thread LINUX NT_386_TLS
//
// The following code only works if notes are ordered as expected.
switch (type) {
case NT_PRSTATUS: {
if (description.length() != sizeof(elf_prstatus)) {
fprintf(stderr, "Found NT_PRSTATUS descriptor of unexpected size\n");
return false;
}
const elf_prstatus* status =
reinterpret_cast<const elf_prstatus*>(description.data());
pid_t pid = status->pr_pid;
ThreadInfo info;
memset(&info, 0, sizeof(ThreadInfo));
info.tgid = status->pr_pgrp;
info.ppid = status->pr_ppid;
#if defined(__mips__)
# if defined(__ANDROID__)
for (int i = EF_R0; i <= EF_R31; i++)
info.mcontext.gregs[i - EF_R0] = status->pr_reg[i];
# else // __ANDROID__
for (int i = EF_REG0; i <= EF_REG31; i++)
info.mcontext.gregs[i - EF_REG0] = status->pr_reg[i];
# endif // __ANDROID__
info.mcontext.mdlo = status->pr_reg[EF_LO];
info.mcontext.mdhi = status->pr_reg[EF_HI];
info.mcontext.pc = status->pr_reg[EF_CP0_EPC];
#elif defined(__riscv)
memcpy(&info.mcontext.__gregs, status->pr_reg,
sizeof(info.mcontext.__gregs));
#else // __riscv
memcpy(&info.regs, status->pr_reg, sizeof(info.regs));
#endif
if (first_thread) {
crash_thread_ = pid;
crash_signal_ = status->pr_info.si_signo;
crash_signal_code_ = status->pr_info.si_code;
}
first_thread = false;
threads_.push_back(pid);
thread_infos_.push_back(info);
break;
}
case NT_SIGINFO: {
if (description.length() != sizeof(siginfo_t)) {
fprintf(stderr, "Found NT_SIGINFO descriptor of unexpected size\n");
return false;
}
const siginfo_t* info =
reinterpret_cast<const siginfo_t*>(description.data());
// Set crash_address when si_addr is valid for the signal.
switch (info->si_signo) {
case MD_EXCEPTION_CODE_LIN_SIGBUS:
case MD_EXCEPTION_CODE_LIN_SIGFPE:
case MD_EXCEPTION_CODE_LIN_SIGILL:
case MD_EXCEPTION_CODE_LIN_SIGSEGV:
case MD_EXCEPTION_CODE_LIN_SIGSYS:
case MD_EXCEPTION_CODE_LIN_SIGTRAP:
crash_address_ = reinterpret_cast<uintptr_t>(info->si_addr);
break;
}
// Set crash_exception_info for common signals. Since exception info is
// unsigned, but some of these fields might be signed, we always cast.
switch (info->si_signo) {
case MD_EXCEPTION_CODE_LIN_SIGKILL:
set_crash_exception_info({
static_cast<uint64_t>(info->si_pid),
static_cast<uint64_t>(info->si_uid),
});
break;
case MD_EXCEPTION_CODE_LIN_SIGSYS:
#ifdef si_syscall
set_crash_exception_info({
static_cast<uint64_t>(info->si_syscall),
static_cast<uint64_t>(info->si_arch),
});
#endif
break;
}
break;
}
#if defined(__i386) || defined(__x86_64)
case NT_FPREGSET: {
if (thread_infos_.empty())
return false;
ThreadInfo* info = &thread_infos_.back();
if (description.length() != sizeof(info->fpregs)) {
fprintf(stderr, "Found NT_FPREGSET descriptor of unexpected size\n");
return false;
}
memcpy(&info->fpregs, description.data(), sizeof(info->fpregs));
break;
}
#endif
#if defined(__i386)
case NT_PRXFPREG: {
if (thread_infos_.empty())
return false;
ThreadInfo* info = &thread_infos_.back();
if (description.length() != sizeof(info->fpxregs)) {
fprintf(stderr, "Found NT_PRXFPREG descriptor of unexpected size\n");
return false;
}
memcpy(&info->fpxregs, description.data(), sizeof(info->fpxregs));
break;
}
#endif
}
note = note.GetNextNote();
} while (note.IsValid());
return true;
}
} // namespace google_breakpad

@ -0,0 +1,124 @@
// Copyright 2012 Google LLC
//
// 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 LLC 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.
// linux_core_dumper.h: Define the google_breakpad::LinuxCoreDumper
// class, which is derived from google_breakpad::LinuxDumper to extract
// information from a crashed process via its core dump and proc files.
#ifndef CLIENT_LINUX_MINIDUMP_WRITER_LINUX_CORE_DUMPER_H_
#define CLIENT_LINUX_MINIDUMP_WRITER_LINUX_CORE_DUMPER_H_
#include "client/linux/minidump_writer/linux_dumper.h"
#include "common/linux/elf_core_dump.h"
#include "common/linux/memory_mapped_file.h"
namespace google_breakpad {
class LinuxCoreDumper : public LinuxDumper {
public:
// Constructs a dumper for extracting information of a given process
// with a process ID of |pid| via its core dump file at |core_path| and
// its proc files at |procfs_path|. If |procfs_path| is a copy of
// /proc/<pid>, it should contain the following files:
// auxv, cmdline, environ, exe, maps, status
// See LinuxDumper for the purpose of |root_prefix|.
LinuxCoreDumper(pid_t pid, const char* core_path, const char* procfs_path,
const char* root_prefix = "");
// Implements LinuxDumper::BuildProcPath().
// Builds a proc path for a certain pid for a node (/proc/<pid>/<node>).
// |path| is a character array of at least NAME_MAX bytes to return the
// result.|node| is the final node without any slashes. Return true on
// success.
//
// As this dumper performs a post-mortem dump and makes use of a copy
// of the proc files of the crashed process, this derived method does
// not actually make use of |pid| and always returns a subpath of
// |procfs_path_| regardless of whether |pid| corresponds to the main
// process or a thread of the process, i.e. assuming both the main process
// and its threads have the following proc files with the same content:
// auxv, cmdline, environ, exe, maps, status
virtual bool BuildProcPath(char* path, pid_t pid, const char* node) const;
// Implements LinuxDumper::CopyFromProcess().
// Copies content of |length| bytes from a given process |child|,
// starting from |src|, into |dest|. This method extracts the content
// the core dump and fills |dest| with a sequence of marker bytes
// if the expected data is not found in the core dump. Returns true if
// the expected data is found in the core dump.
virtual bool CopyFromProcess(void* dest, pid_t child, const void* src,
size_t length);
// Implements LinuxDumper::GetThreadInfoByIndex().
// Reads information about the |index|-th thread of |threads_|.
// Returns true on success. One must have called |ThreadsSuspend| first.
virtual bool GetThreadInfoByIndex(size_t index, ThreadInfo* info);
// Implements LinuxDumper::IsPostMortem().
// Always returns true to indicate that this dumper performs a
// post-mortem dump of a crashed process via a core dump file.
virtual bool IsPostMortem() const;
// Implements LinuxDumper::ThreadsSuspend().
// As the dumper performs a post-mortem dump via a core dump file,
// there is no threads to suspend. This method does nothing and
// always returns true.
virtual bool ThreadsSuspend();
// Implements LinuxDumper::ThreadsResume().
// As the dumper performs a post-mortem dump via a core dump file,
// there is no threads to resume. This method does nothing and
// always returns true.
virtual bool ThreadsResume();
protected:
// Implements LinuxDumper::EnumerateThreads().
// Enumerates all threads of the given process into |threads_|.
virtual bool EnumerateThreads();
private:
// Path of the core dump file.
const char* core_path_;
// Path of the directory containing the proc files of the given process,
// which is usually a copy of /proc/<pid>.
const char* procfs_path_;
// Memory-mapped core dump file at |core_path_|.
MemoryMappedFile mapped_core_file_;
// Content of the core dump file.
ElfCoreDump core_;
// Thread info found in the core dump file.
wasteful_vector<ThreadInfo> thread_infos_;
};
} // namespace google_breakpad
#endif // CLIENT_LINUX_HANDLER_LINUX_CORE_DUMPER_H_

@ -0,0 +1,191 @@
// Copyright 2012 Google LLC
//
// 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 LLC 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.
// 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 namespace google_breakpad;
TEST(LinuxCoreDumperTest, GetMappingAbsolutePath) {
const LinuxCoreDumper dumper(getpid(), "core", "/tmp", "/mnt/root");
const MappingInfo mapping = {0, 0, {0, 0}, 0, false, "/usr/lib/libc.so"};
char path[PATH_MAX];
dumper.GetMappingAbsolutePath(mapping, path);
EXPECT_STREQ("/mnt/root/usr/lib/libc.so", path);
}
TEST(LinuxCoreDumperTest, BuildProcPath) {
const pid_t pid = getpid();
const char procfs_path[] = "/procfs_copy";
LinuxCoreDumper dumper(getpid(), "core_file", procfs_path);
char maps_path[NAME_MAX] = "";
char maps_path_expected[NAME_MAX];
snprintf(maps_path_expected, sizeof(maps_path_expected),
"%s/maps", procfs_path);
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, pid, ""));
EXPECT_FALSE(dumper.BuildProcPath(maps_path, pid, NULL));
char long_node[NAME_MAX];
size_t long_node_len = NAME_MAX - strlen(procfs_path) - 1;
memset(long_node, 'a', long_node_len);
long_node[long_node_len] = '\0';
EXPECT_FALSE(dumper.BuildProcPath(maps_path, pid, long_node));
}
TEST(LinuxCoreDumperTest, VerifyDumpWithMultipleThreads) {
CrashGenerator crash_generator;
if (!crash_generator.HasDefaultCorePattern()) {
fprintf(stderr, "LinuxCoreDumperTest.VerifyDumpWithMultipleThreads test "
"is skipped due to non-default core pattern\n");
return;
}
const unsigned kNumOfThreads = 3;
const unsigned kCrashThread = 1;
const int kCrashSignal = SIGABRT;
pid_t child_pid;
ASSERT_TRUE(crash_generator.CreateChildCrash(kNumOfThreads, kCrashThread,
kCrashSignal, &child_pid));
const string core_file = crash_generator.GetCoreFilePath();
const string procfs_path = crash_generator.GetDirectoryOfProcFilesCopy();
#if defined(__ANDROID__)
struct stat st;
if (stat(core_file.c_str(), &st) != 0) {
fprintf(stderr, "LinuxCoreDumperTest.VerifyDumpWithMultipleThreads test is "
"skipped due to no core file being generated\n");
return;
}
#endif
LinuxCoreDumper dumper(child_pid, core_file.c_str(), procfs_path.c_str());
EXPECT_TRUE(dumper.Init());
EXPECT_TRUE(dumper.IsPostMortem());
// These are no-ops and should always return true.
EXPECT_TRUE(dumper.ThreadsSuspend());
EXPECT_TRUE(dumper.ThreadsResume());
// Linux does not set the crash address with SIGABRT, so make sure it always
// sets the crash address to 0.
EXPECT_EQ(0U, dumper.crash_address());
EXPECT_EQ(kCrashSignal, dumper.crash_signal());
EXPECT_EQ(crash_generator.GetThreadId(kCrashThread),
dumper.crash_thread());
#if defined(THREAD_SANITIZER)
EXPECT_GE(dumper.threads().size(), kNumOfThreads);
#else
EXPECT_EQ(dumper.threads().size(), kNumOfThreads);
#endif
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);
}
}
TEST(LinuxCoreDumperTest, VerifyExceptionDetails) {
CrashGenerator crash_generator;
if (!crash_generator.HasDefaultCorePattern()) {
fprintf(stderr, "LinuxCoreDumperTest.VerifyDumpWithMultipleThreads test "
"is skipped due to non-default core pattern\n");
return;
}
#ifndef si_syscall
fprintf(stderr, "LinuxCoreDumperTest.VerifyDumpWithMultipleThreads test is "
"skipped due to old kernel/C library headers\n");
return;
#endif
const unsigned kNumOfThreads = 2;
const unsigned kCrashThread = 1;
const int kCrashSignal = SIGSYS;
pid_t child_pid;
ASSERT_TRUE(crash_generator.CreateChildCrash(kNumOfThreads, kCrashThread,
kCrashSignal, &child_pid));
const string core_file = crash_generator.GetCoreFilePath();
const string procfs_path = crash_generator.GetDirectoryOfProcFilesCopy();
#if defined(__ANDROID__)
struct stat st;
if (stat(core_file.c_str(), &st) != 0) {
fprintf(stderr, "LinuxCoreDumperTest.VerifyExceptionDetails test is "
"skipped due to no core file being generated\n");
return;
}
#endif
LinuxCoreDumper dumper(child_pid, core_file.c_str(), procfs_path.c_str());
EXPECT_TRUE(dumper.Init());
EXPECT_TRUE(dumper.IsPostMortem());
#if defined(__ANDROID__)
// TODO: For some reason, Android doesn't seem to pass this.
if (!dumper.crash_address()) {
fprintf(stderr, "LinuxCoreDumperTest.VerifyExceptionDetails test is "
"skipped due to missing signal details on Android\n");
return;
}
#endif
// Check the exception details.
EXPECT_NE(0U, dumper.crash_address());
EXPECT_EQ(kCrashSignal, dumper.crash_signal());
EXPECT_EQ(crash_generator.GetThreadId(kCrashThread),
dumper.crash_thread());
// We check the length, but not the actual fields. We sent SIGSYS ourselves
// instead of the kernel, so the extended fields are garbage.
const std::vector<uint64_t> info(dumper.crash_exception_info());
EXPECT_EQ(2U, info.size());
}

@ -0,0 +1,970 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
// linux_dumper.cc: Implement google_breakpad::LinuxDumper.
// See linux_dumper.h for details.
// This code deals with the mechanics of getting information about a crashed
// process. Since this code may run in a compromised address space, the same
// rules apply as detailed at the top of minidump_writer.h: no libc calls and
// use the alternative allocator.
#include "client/linux/minidump_writer/linux_dumper.h"
#include <assert.h>
#include <elf.h>
#include <fcntl.h>
#include <limits.h>
#include <stddef.h>
#include <string.h>
#include "client/linux/minidump_writer/line_reader.h"
#include "common/linux/elfutils.h"
#include "common/linux/file_id.h"
#include "common/linux/linux_libc_support.h"
#include "common/linux/memory_mapped_file.h"
#include "common/linux/safe_readlink.h"
#include "google_breakpad/common/minidump_exception_linux.h"
#include "third_party/lss/linux_syscall_support.h"
using google_breakpad::elf::FileID;
#if defined(__ANDROID__)
// Android packed relocations definitions are not yet available from the
// NDK header files, so we have to provide them manually here.
#ifndef DT_LOOS
#define DT_LOOS 0x6000000d
#endif
#ifndef DT_ANDROID_REL
static const int DT_ANDROID_REL = DT_LOOS + 2;
#endif
#ifndef DT_ANDROID_RELA
static const int DT_ANDROID_RELA = DT_LOOS + 4;
#endif
#endif // __ANDROID __
static const char kMappedFileUnsafePrefix[] = "/dev/";
static const char kDeletedSuffix[] = " (deleted)";
inline static bool IsMappedFileOpenUnsafe(
const google_breakpad::MappingInfo& mapping) {
// It is unsafe to attempt to open a mapped file that lives under /dev,
// because the semantics of the open may be driver-specific so we'd risk
// hanging the crash dumper. And a file in /dev/ almost certainly has no
// ELF file identifier anyways.
return my_strncmp(mapping.name,
kMappedFileUnsafePrefix,
sizeof(kMappedFileUnsafePrefix) - 1) == 0;
}
namespace google_breakpad {
namespace {
bool MappingContainsAddress(const MappingInfo& mapping, uintptr_t address) {
return mapping.system_mapping_info.start_addr <= address &&
address < mapping.system_mapping_info.end_addr;
}
#if defined(__CHROMEOS__)
// Recover memory mappings before writing dump on ChromeOS
//
// On Linux, breakpad relies on /proc/[pid]/maps to associate symbols from
// addresses. ChromeOS' hugepage implementation replaces some segments with
// anonymous private pages, which is a restriction of current implementation
// in Linux kernel at the time of writing. Thus, breakpad can no longer
// symbolize addresses from those text segments replaced with hugepages.
//
// This postprocess tries to recover the mappings. Because hugepages are always
// inserted in between some .text sections, it tries to infer the names and
// offsets of the segments, by looking at segments immediately precede and
// succeed them.
//
// For example, a text segment before hugepage optimization
// 02001000-03002000 r-xp /opt/google/chrome/chrome
//
// can be broken into
// 02001000-02200000 r-xp /opt/google/chrome/chrome
// 02200000-03000000 r-xp
// 03000000-03002000 r-xp /opt/google/chrome/chrome
//
// For more details, see:
// crbug.com/628040 ChromeOS' use of hugepages confuses crash symbolization
// Copied from CrOS' hugepage implementation, which is unlikely to change.
// The hugepage size is 2M.
const unsigned int kHpageShift = 21;
const size_t kHpageSize = (1 << kHpageShift);
const size_t kHpageMask = (~(kHpageSize - 1));
// Find and merge anonymous r-xp segments with surrounding named segments.
// There are two cases:
// Case 1: curr, next
// curr is anonymous
// curr is r-xp
// curr.size >= 2M
// curr.size is a multiple of 2M.
// next is backed by some file.
// curr and next are contiguous.
// offset(next) == sizeof(curr)
void TryRecoverMappings(MappingInfo* curr, MappingInfo* next) {
// Merged segments are marked with size = 0.
if (curr->size == 0 || next->size == 0)
return;
if (curr->size >= kHpageSize &&
curr->exec &&
(curr->size & kHpageMask) == curr->size &&
(curr->start_addr & kHpageMask) == curr->start_addr &&
curr->name[0] == '\0' &&
next->name[0] != '\0' &&
curr->start_addr + curr->size == next->start_addr &&
curr->size == next->offset) {
// matched
my_strlcpy(curr->name, next->name, NAME_MAX);
if (next->exec) {
// (curr, next)
curr->size += next->size;
next->size = 0;
}
}
}
// Case 2: prev, curr, next
// curr is anonymous
// curr is r-xp
// curr.size >= 2M
// curr.size is a multiple of 2M.
// next and prev are backed by the same file.
// prev, curr and next are contiguous.
// offset(next) == offset(prev) + sizeof(prev) + sizeof(curr)
void TryRecoverMappings(MappingInfo* prev, MappingInfo* curr,
MappingInfo* next) {
// Merged segments are marked with size = 0.
if (prev->size == 0 || curr->size == 0 || next->size == 0)
return;
if (curr->size >= kHpageSize &&
curr->exec &&
(curr->size & kHpageMask) == curr->size &&
(curr->start_addr & kHpageMask) == curr->start_addr &&
curr->name[0] == '\0' &&
next->name[0] != '\0' &&
curr->start_addr + curr->size == next->start_addr &&
prev->start_addr + prev->size == curr->start_addr &&
my_strncmp(prev->name, next->name, NAME_MAX) == 0 &&
next->offset == prev->offset + prev->size + curr->size) {
// matched
my_strlcpy(curr->name, prev->name, NAME_MAX);
if (prev->exec) {
curr->offset = prev->offset;
curr->start_addr = prev->start_addr;
if (next->exec) {
// (prev, curr, next)
curr->size += prev->size + next->size;
prev->size = 0;
next->size = 0;
} else {
// (prev, curr), next
curr->size += prev->size;
prev->size = 0;
}
} else {
curr->offset = prev->offset + prev->size;
if (next->exec) {
// prev, (curr, next)
curr->size += next->size;
next->size = 0;
} else {
// prev, curr, next
}
}
}
}
// mappings_ is sorted excepted for the first entry.
// This function tries to merge segemnts into the first entry,
// then check for other sorted entries.
// See LinuxDumper::EnumerateMappings().
void CrOSPostProcessMappings(wasteful_vector<MappingInfo*>& mappings) {
// Find the candidate "next" to first segment, which is the only one that
// could be out-of-order.
size_t l = 1;
size_t r = mappings.size();
size_t next = mappings.size();
while (l < r) {
int m = (l + r) / 2;
if (mappings[m]->start_addr > mappings[0]->start_addr)
r = next = m;
else
l = m + 1;
}
// Shows the range that contains the entry point is
// [first_start_addr, first_end_addr)
size_t first_start_addr = mappings[0]->start_addr;
size_t first_end_addr = mappings[0]->start_addr + mappings[0]->size;
// Put the out-of-order segment in order.
std::rotate(mappings.begin(), mappings.begin() + 1, mappings.begin() + next);
// Iterate through normal, sorted cases.
// Normal case 1.
for (size_t i = 0; i < mappings.size() - 1; i++)
TryRecoverMappings(mappings[i], mappings[i + 1]);
// Normal case 2.
for (size_t i = 0; i < mappings.size() - 2; i++)
TryRecoverMappings(mappings[i], mappings[i + 1], mappings[i + 2]);
// Collect merged (size == 0) segments.
size_t f, e;
for (f = e = 0; e < mappings.size(); e++)
if (mappings[e]->size > 0)
mappings[f++] = mappings[e];
mappings.resize(f);
// The entry point is in the first mapping. We want to find the location
// of the entry point after merging segment. To do this, we want to find
// the mapping that covers the first mapping from the original mapping list.
// If the mapping is not in the beginning, we move it to the begining via
// a right rotate by using reverse iterators.
for (l = 0; l < mappings.size(); l++) {
if (mappings[l]->start_addr <= first_start_addr
&& (mappings[l]->start_addr + mappings[l]->size >= first_end_addr))
break;
}
if (l > 0) {
r = mappings.size();
std::rotate(mappings.rbegin() + r - l - 1, mappings.rbegin() + r - l,
mappings.rend());
}
}
#endif // __CHROMEOS__
} // namespace
// All interesting auvx entry types are below AT_SYSINFO_EHDR
#define AT_MAX AT_SYSINFO_EHDR
LinuxDumper::LinuxDumper(pid_t pid, const char* root_prefix)
: pid_(pid),
root_prefix_(root_prefix),
crash_address_(0),
crash_signal_(0),
crash_signal_code_(0),
crash_thread_(pid),
threads_(&allocator_, 8),
mappings_(&allocator_),
auxv_(&allocator_, AT_MAX + 1) {
assert(root_prefix_ && my_strlen(root_prefix_) < PATH_MAX);
// 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 ReadAuxv() && EnumerateThreads() && EnumerateMappings();
}
bool LinuxDumper::LateInit() {
#if defined(__ANDROID__)
LatePostprocessMappings();
#endif
#if defined(__CHROMEOS__)
CrOSPostProcessMappings(mappings_);
#endif
return true;
}
bool
LinuxDumper::ElfFileIdentifierForMapping(const MappingInfo& mapping,
bool member,
unsigned int mapping_id,
wasteful_vector<uint8_t>& identifier) {
assert(!member || mapping_id < mappings_.size());
if (IsMappedFileOpenUnsafe(mapping))
return false;
// Special-case linux-gate because it's not a real file.
if (my_strcmp(mapping.name, kLinuxGateLibraryName) == 0) {
void* linux_gate = NULL;
if (pid_ == sys_getpid()) {
linux_gate = reinterpret_cast<void*>(mapping.start_addr);
} else {
linux_gate = allocator_.Alloc(mapping.size);
CopyFromProcess(linux_gate, pid_,
reinterpret_cast<const void*>(mapping.start_addr),
mapping.size);
}
return FileID::ElfFileIdentifierFromMappedFile(linux_gate, identifier);
}
char filename[PATH_MAX];
if (!GetMappingAbsolutePath(mapping, filename))
return false;
bool filename_modified = HandleDeletedFileInMapping(filename);
MemoryMappedFile mapped_file(filename, mapping.offset);
if (!mapped_file.data() || mapped_file.size() < SELFMAG)
return false;
bool success =
FileID::ElfFileIdentifierFromMappedFile(mapped_file.data(), identifier);
if (success && member && filename_modified) {
mappings_[mapping_id]->name[my_strlen(mapping.name) -
sizeof(kDeletedSuffix) + 1] = '\0';
}
return success;
}
void LinuxDumper::SetCrashInfoFromSigInfo(const siginfo_t& siginfo) {
set_crash_address(reinterpret_cast<uintptr_t>(siginfo.si_addr));
set_crash_signal(siginfo.si_signo);
set_crash_signal_code(siginfo.si_code);
}
const char* LinuxDumper::GetCrashSignalString() const {
switch (static_cast<unsigned int>(crash_signal_)) {
case MD_EXCEPTION_CODE_LIN_SIGHUP:
return "SIGHUP";
case MD_EXCEPTION_CODE_LIN_SIGINT:
return "SIGINT";
case MD_EXCEPTION_CODE_LIN_SIGQUIT:
return "SIGQUIT";
case MD_EXCEPTION_CODE_LIN_SIGILL:
return "SIGILL";
case MD_EXCEPTION_CODE_LIN_SIGTRAP:
return "SIGTRAP";
case MD_EXCEPTION_CODE_LIN_SIGABRT:
return "SIGABRT";
case MD_EXCEPTION_CODE_LIN_SIGBUS:
return "SIGBUS";
case MD_EXCEPTION_CODE_LIN_SIGFPE:
return "SIGFPE";
case MD_EXCEPTION_CODE_LIN_SIGKILL:
return "SIGKILL";
case MD_EXCEPTION_CODE_LIN_SIGUSR1:
return "SIGUSR1";
case MD_EXCEPTION_CODE_LIN_SIGSEGV:
return "SIGSEGV";
case MD_EXCEPTION_CODE_LIN_SIGUSR2:
return "SIGUSR2";
case MD_EXCEPTION_CODE_LIN_SIGPIPE:
return "SIGPIPE";
case MD_EXCEPTION_CODE_LIN_SIGALRM:
return "SIGALRM";
case MD_EXCEPTION_CODE_LIN_SIGTERM:
return "SIGTERM";
case MD_EXCEPTION_CODE_LIN_SIGSTKFLT:
return "SIGSTKFLT";
case MD_EXCEPTION_CODE_LIN_SIGCHLD:
return "SIGCHLD";
case MD_EXCEPTION_CODE_LIN_SIGCONT:
return "SIGCONT";
case MD_EXCEPTION_CODE_LIN_SIGSTOP:
return "SIGSTOP";
case MD_EXCEPTION_CODE_LIN_SIGTSTP:
return "SIGTSTP";
case MD_EXCEPTION_CODE_LIN_SIGTTIN:
return "SIGTTIN";
case MD_EXCEPTION_CODE_LIN_SIGTTOU:
return "SIGTTOU";
case MD_EXCEPTION_CODE_LIN_SIGURG:
return "SIGURG";
case MD_EXCEPTION_CODE_LIN_SIGXCPU:
return "SIGXCPU";
case MD_EXCEPTION_CODE_LIN_SIGXFSZ:
return "SIGXFSZ";
case MD_EXCEPTION_CODE_LIN_SIGVTALRM:
return "SIGVTALRM";
case MD_EXCEPTION_CODE_LIN_SIGPROF:
return "SIGPROF";
case MD_EXCEPTION_CODE_LIN_SIGWINCH:
return "SIGWINCH";
case MD_EXCEPTION_CODE_LIN_SIGIO:
return "SIGIO";
case MD_EXCEPTION_CODE_LIN_SIGPWR:
return "SIGPWR";
case MD_EXCEPTION_CODE_LIN_SIGSYS:
return "SIGSYS";
case MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED:
return "DUMP_REQUESTED";
default:
return "UNKNOWN";
}
}
bool LinuxDumper::GetMappingAbsolutePath(const MappingInfo& mapping,
char path[PATH_MAX]) const {
return my_strlcpy(path, root_prefix_, PATH_MAX) < PATH_MAX &&
my_strlcat(path, mapping.name, PATH_MAX) < PATH_MAX;
}
namespace {
// Find the shared object name (SONAME) by examining the ELF information
// for |mapping|. If the SONAME is found copy it into the passed buffer
// |soname| and return true. The size of the buffer is |soname_size|.
// The SONAME will be truncated if it is too long to fit in the buffer.
bool ElfFileSoName(const LinuxDumper& dumper,
const MappingInfo& mapping, char* soname, size_t soname_size) {
if (IsMappedFileOpenUnsafe(mapping)) {
// Not safe
return false;
}
char filename[PATH_MAX];
if (!dumper.GetMappingAbsolutePath(mapping, filename))
return false;
MemoryMappedFile mapped_file(filename, mapping.offset);
if (!mapped_file.data() || mapped_file.size() < SELFMAG) {
// mmap failed
return false;
}
return ElfFileSoNameFromMappedFile(mapped_file.data(), soname, soname_size);
}
} // namespace
void LinuxDumper::GetMappingEffectiveNameAndPath(const MappingInfo& mapping,
char* file_path,
size_t file_path_size,
char* file_name,
size_t file_name_size) {
my_strlcpy(file_path, mapping.name, file_path_size);
// Tools such as minidump_stackwalk use the name of the module to look up
// symbols produced by dump_syms. dump_syms will prefer to use a module's
// DT_SONAME as the module name, if one exists, and will fall back to the
// filesystem name of the module.
// Just use the filesystem name if no SONAME is present.
if (!ElfFileSoName(*this, mapping, file_name, file_name_size)) {
// file_path := /path/to/libname.so
// file_name := libname.so
const char* basename = my_strrchr(file_path, '/');
basename = basename == NULL ? file_path : (basename + 1);
my_strlcpy(file_name, basename, file_name_size);
return;
}
if (mapping.exec && mapping.offset != 0) {
// If an executable is mapped from a non-zero offset, this is likely because
// the executable was loaded directly from inside an archive file (e.g., an
// apk on Android).
// In this case, we append the file_name to the mapped archive path:
// file_name := libname.so
// file_path := /path/to/ARCHIVE.APK/libname.so
if (my_strlen(file_path) + 1 + my_strlen(file_name) < file_path_size) {
my_strlcat(file_path, "/", file_path_size);
my_strlcat(file_path, file_name, file_path_size);
}
} else {
// Otherwise, replace the basename with the SONAME.
char* basename = const_cast<char*>(my_strrchr(file_path, '/'));
if (basename) {
my_strlcpy(basename + 1, file_name,
file_path_size - my_strlen(file_path) +
my_strlen(basename + 1));
} else {
my_strlcpy(file_path, file_name, file_path_size);
}
}
}
bool LinuxDumper::ReadAuxv() {
char auxv_path[NAME_MAX];
if (!BuildProcPath(auxv_path, pid_, "auxv")) {
return false;
}
int fd = sys_open(auxv_path, O_RDONLY, 0);
if (fd < 0) {
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_MAX) {
auxv_[one_aux_entry.a_type] = one_aux_entry.a_un.a_val;
res = true;
}
}
sys_close(fd);
return res;
}
bool LinuxDumper::EnumerateMappings() {
char maps_path[NAME_MAX];
if (!BuildProcPath(maps_path, pid_, "maps"))
return false;
// 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, 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 = reinterpret_cast<void*>(auxv_[AT_ENTRY]);
const int fd = sys_open(maps_path, O_RDONLY, 0);
if (fd < 0)
return false;
LineReader* const line_reader = new(allocator_) LineReader(fd);
const char* line;
unsigned line_len;
while (line_reader->GetNextLine(&line, &line_len)) {
uintptr_t start_addr, end_addr, offset;
const char* i1 = my_read_hex_ptr(&start_addr, line);
if (*i1 == '-') {
const char* i2 = my_read_hex_ptr(&end_addr, i1 + 1);
if (*i2 == ' ') {
bool exec = (*(i2 + 3) == 'x');
const char* i3 = my_read_hex_ptr(&offset, i2 + 6 /* skip ' rwxp ' */);
if (*i3 == ' ') {
const char* name = NULL;
// Only copy name if the name is a valid path name, or if
// it's the VDSO image.
if (((name = my_strchr(line, '/')) == NULL) &&
linux_gate_loc &&
reinterpret_cast<void*>(start_addr) == linux_gate_loc) {
name = kLinuxGateLibraryName;
offset = 0;
}
// Merge adjacent mappings into one module, assuming they're a single
// library mapped by the dynamic linker. Do this only if their name
// matches and either they have the same +x protection flag, or if the
// previous mapping is not executable and the new one is, to handle
// lld's output (see crbug.com/716484).
if (name && !mappings_.empty()) {
MappingInfo* module = mappings_.back();
if ((start_addr == module->start_addr + module->size) &&
(my_strlen(name) == my_strlen(module->name)) &&
(my_strncmp(name, module->name, my_strlen(name)) == 0) &&
((exec == module->exec) || (!module->exec && exec))) {
module->system_mapping_info.end_addr = end_addr;
module->size = end_addr - module->start_addr;
module->exec |= exec;
line_reader->PopLine(line_len);
continue;
}
}
MappingInfo* const module = new(allocator_) MappingInfo;
mappings_.push_back(module);
my_memset(module, 0, sizeof(MappingInfo));
module->system_mapping_info.start_addr = start_addr;
module->system_mapping_info.end_addr = end_addr;
module->start_addr = start_addr;
module->size = end_addr - start_addr;
module->offset = offset;
module->exec = exec;
if (name != NULL) {
const unsigned l = my_strlen(name);
if (l < sizeof(module->name))
my_memcpy(module->name, name, l);
}
}
}
}
line_reader->PopLine(line_len);
}
if (entry_point_loc) {
for (size_t i = 0; i < mappings_.size(); ++i) {
MappingInfo* module = mappings_[i];
// If this module contains the entry-point, and it's not already the first
// one, then we need to make it be first. This is because the minidump
// format assumes the first module is the one that corresponds to the main
// executable (as codified in
// processor/minidump.cc:MinidumpModuleList::GetMainModule()).
if ((entry_point_loc >= reinterpret_cast<void*>(module->start_addr)) &&
(entry_point_loc <
reinterpret_cast<void*>(module->start_addr + module->size))) {
for (size_t j = i; j > 0; j--)
mappings_[j] = mappings_[j - 1];
mappings_[0] = module;
break;
}
}
}
sys_close(fd);
return !mappings_.empty();
}
#if defined(__ANDROID__)
bool LinuxDumper::GetLoadedElfHeader(uintptr_t start_addr, ElfW(Ehdr)* ehdr) {
CopyFromProcess(ehdr, pid_,
reinterpret_cast<const void*>(start_addr),
sizeof(*ehdr));
return my_memcmp(&ehdr->e_ident, ELFMAG, SELFMAG) == 0;
}
void LinuxDumper::ParseLoadedElfProgramHeaders(ElfW(Ehdr)* ehdr,
uintptr_t start_addr,
uintptr_t* min_vaddr_ptr,
uintptr_t* dyn_vaddr_ptr,
size_t* dyn_count_ptr) {
uintptr_t phdr_addr = start_addr + ehdr->e_phoff;
const uintptr_t max_addr = UINTPTR_MAX;
uintptr_t min_vaddr = max_addr;
uintptr_t dyn_vaddr = 0;
size_t dyn_count = 0;
for (size_t i = 0; i < ehdr->e_phnum; ++i) {
ElfW(Phdr) phdr;
CopyFromProcess(&phdr, pid_,
reinterpret_cast<const void*>(phdr_addr),
sizeof(phdr));
if (phdr.p_type == PT_LOAD && phdr.p_vaddr < min_vaddr) {
min_vaddr = phdr.p_vaddr;
}
if (phdr.p_type == PT_DYNAMIC) {
dyn_vaddr = phdr.p_vaddr;
dyn_count = phdr.p_memsz / sizeof(ElfW(Dyn));
}
phdr_addr += sizeof(phdr);
}
*min_vaddr_ptr = min_vaddr;
*dyn_vaddr_ptr = dyn_vaddr;
*dyn_count_ptr = dyn_count;
}
bool LinuxDumper::HasAndroidPackedRelocations(uintptr_t load_bias,
uintptr_t dyn_vaddr,
size_t dyn_count) {
uintptr_t dyn_addr = load_bias + dyn_vaddr;
for (size_t i = 0; i < dyn_count; ++i) {
ElfW(Dyn) dyn;
CopyFromProcess(&dyn, pid_,
reinterpret_cast<const void*>(dyn_addr),
sizeof(dyn));
if (dyn.d_tag == DT_ANDROID_REL || dyn.d_tag == DT_ANDROID_RELA) {
return true;
}
dyn_addr += sizeof(dyn);
}
return false;
}
uintptr_t LinuxDumper::GetEffectiveLoadBias(ElfW(Ehdr)* ehdr,
uintptr_t start_addr) {
uintptr_t min_vaddr = 0;
uintptr_t dyn_vaddr = 0;
size_t dyn_count = 0;
ParseLoadedElfProgramHeaders(ehdr, start_addr,
&min_vaddr, &dyn_vaddr, &dyn_count);
// If |min_vaddr| is non-zero and we find Android packed relocation tags,
// return the effective load bias.
if (min_vaddr != 0) {
const uintptr_t load_bias = start_addr - min_vaddr;
if (HasAndroidPackedRelocations(load_bias, dyn_vaddr, dyn_count)) {
return load_bias;
}
}
// Either |min_vaddr| is zero, or it is non-zero but we did not find the
// expected Android packed relocations tags.
return start_addr;
}
void LinuxDumper::LatePostprocessMappings() {
for (size_t i = 0; i < mappings_.size(); ++i) {
// Only consider exec mappings that indicate a file path was mapped, and
// where the ELF header indicates a mapped shared library.
MappingInfo* mapping = mappings_[i];
if (!(mapping->exec && mapping->name[0] == '/')) {
continue;
}
ElfW(Ehdr) ehdr;
if (!GetLoadedElfHeader(mapping->start_addr, &ehdr)) {
continue;
}
if (ehdr.e_type == ET_DYN) {
// Compute the effective load bias for this mapped library, and update
// the mapping to hold that rather than |start_addr|, at the same time
// adjusting |size| to account for the change in |start_addr|. Where
// the library does not contain Android packed relocations,
// GetEffectiveLoadBias() returns |start_addr| and the mapping entry
// is not changed.
const uintptr_t load_bias = GetEffectiveLoadBias(&ehdr,
mapping->start_addr);
mapping->size += mapping->start_addr - load_bias;
mapping->start_addr = load_bias;
}
}
}
#endif // __ANDROID__
// Get information about the stack, given the stack pointer. We don't try to
// walk the stack since we might not have all the information needed to do
// unwind. So we just grab, up to, 32k of stack.
bool LinuxDumper::GetStackInfo(const void** stack, size_t* stack_len,
uintptr_t int_stack_pointer) {
// Move the stack pointer to the bottom of the page that it's in.
const uintptr_t page_size = getpagesize();
uint8_t* const stack_pointer =
reinterpret_cast<uint8_t*>(int_stack_pointer & ~(page_size - 1));
// The number of bytes of stack which we try to capture.
static const ptrdiff_t kStackToCapture = 32 * 1024;
const MappingInfo* mapping = FindMapping(stack_pointer);
if (!mapping)
return false;
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 ?
kStackToCapture : distance_to_end;
*stack = stack_pointer;
return true;
}
void LinuxDumper::SanitizeStackCopy(uint8_t* stack_copy, size_t stack_len,
uintptr_t stack_pointer,
uintptr_t sp_offset) {
// We optimize the search for containing mappings in three ways:
// 1) We expect that pointers into the stack mapping will be common, so
// we cache that address range.
// 2) The last referenced mapping is a reasonable predictor for the next
// referenced mapping, so we test that first.
// 3) We precompute a bitfield based upon bits 32:32-n of the start and
// stop addresses, and use that to short circuit any values that can
// not be pointers. (n=11)
const uintptr_t defaced =
#if defined(__LP64__)
0x0defaced0defaced;
#else
0x0defaced;
#endif
// the bitfield length is 2^test_bits long.
const unsigned int test_bits = 11;
// byte length of the corresponding array.
const unsigned int array_size = 1 << (test_bits - 3);
const unsigned int array_mask = array_size - 1;
// The amount to right shift pointers by. This captures the top bits
// on 32 bit architectures. On 64 bit architectures this would be
// uninformative so we take the same range of bits.
const unsigned int shift = 32 - 11;
const MappingInfo* last_hit_mapping = nullptr;
const MappingInfo* hit_mapping = nullptr;
const MappingInfo* stack_mapping = FindMappingNoBias(stack_pointer);
// The magnitude below which integers are considered to be to be
// 'small', and not constitute a PII risk. These are included to
// avoid eliding useful register values.
const ssize_t small_int_magnitude = 4096;
char could_hit_mapping[array_size];
my_memset(could_hit_mapping, 0, array_size);
// Initialize the bitfield such that if the (pointer >> shift)'th
// bit, modulo the bitfield size, is not set then there does not
// exist a mapping in mappings_ that would contain that pointer.
for (size_t i = 0; i < mappings_.size(); ++i) {
if (!mappings_[i]->exec) continue;
// For each mapping, work out the (unmodulo'ed) range of bits to
// set.
uintptr_t start = mappings_[i]->start_addr;
uintptr_t end = start + mappings_[i]->size;
start >>= shift;
end >>= shift;
for (size_t bit = start; bit <= end; ++bit) {
// Set each bit in the range, applying the modulus.
could_hit_mapping[(bit >> 3) & array_mask] |= 1 << (bit & 7);
}
}
// Zero memory that is below the current stack pointer.
const uintptr_t offset =
(sp_offset + sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1);
if (offset) {
my_memset(stack_copy, 0, offset);
}
// Apply sanitization to each complete pointer-aligned word in the
// stack.
uint8_t* sp;
for (sp = stack_copy + offset;
sp <= stack_copy + stack_len - sizeof(uintptr_t);
sp += sizeof(uintptr_t)) {
uintptr_t addr;
my_memcpy(&addr, sp, sizeof(uintptr_t));
if (static_cast<intptr_t>(addr) <= small_int_magnitude &&
static_cast<intptr_t>(addr) >= -small_int_magnitude) {
continue;
}
if (stack_mapping && MappingContainsAddress(*stack_mapping, addr)) {
continue;
}
if (last_hit_mapping && MappingContainsAddress(*last_hit_mapping, addr)) {
continue;
}
uintptr_t test = addr >> shift;
if (could_hit_mapping[(test >> 3) & array_mask] & (1 << (test & 7)) &&
(hit_mapping = FindMappingNoBias(addr)) != nullptr &&
hit_mapping->exec) {
last_hit_mapping = hit_mapping;
continue;
}
my_memcpy(sp, &defaced, sizeof(uintptr_t));
}
// Zero any partial word at the top of the stack, if alignment is
// such that that is required.
if (sp < stack_copy + stack_len) {
my_memset(sp, 0, stack_copy + stack_len - sp);
}
}
bool LinuxDumper::StackHasPointerToMapping(const uint8_t* stack_copy,
size_t stack_len,
uintptr_t sp_offset,
const MappingInfo& mapping) {
// Loop over all stack words that would have been on the stack in
// the target process (i.e. are word aligned, and at addresses >=
// the stack pointer). Regardless of the alignment of |stack_copy|,
// the memory starting at |stack_copy| + |offset| represents an
// aligned word in the target process.
const uintptr_t low_addr = mapping.system_mapping_info.start_addr;
const uintptr_t high_addr = mapping.system_mapping_info.end_addr;
const uintptr_t offset =
(sp_offset + sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1);
for (const uint8_t* sp = stack_copy + offset;
sp <= stack_copy + stack_len - sizeof(uintptr_t);
sp += sizeof(uintptr_t)) {
uintptr_t addr;
my_memcpy(&addr, sp, sizeof(uintptr_t));
if (low_addr <= addr && addr <= high_addr)
return true;
}
return false;
}
// Find the mapping which the given memory address falls in.
const MappingInfo* LinuxDumper::FindMapping(const void* address) const {
const uintptr_t addr = (uintptr_t) address;
for (size_t i = 0; i < mappings_.size(); ++i) {
const uintptr_t start = static_cast<uintptr_t>(mappings_[i]->start_addr);
if (addr >= start && addr - start < mappings_[i]->size)
return mappings_[i];
}
return NULL;
}
// Find the mapping which the given memory address falls in. Uses the
// unadjusted mapping address range from the kernel, rather than the
// biased range.
const MappingInfo* LinuxDumper::FindMappingNoBias(uintptr_t address) const {
for (size_t i = 0; i < mappings_.size(); ++i) {
if (address >= mappings_[i]->system_mapping_info.start_addr &&
address < mappings_[i]->system_mapping_info.end_addr) {
return mappings_[i];
}
}
return NULL;
}
bool LinuxDumper::HandleDeletedFileInMapping(char* path) const {
static const size_t kDeletedSuffixLen = sizeof(kDeletedSuffix) - 1;
// Check for ' (deleted)' in |path|.
// |path| has to be at least as long as "/x (deleted)".
const size_t path_len = my_strlen(path);
if (path_len < kDeletedSuffixLen + 2)
return false;
if (my_strncmp(path + path_len - kDeletedSuffixLen, kDeletedSuffix,
kDeletedSuffixLen) != 0) {
return false;
}
// Check |path| against the /proc/pid/exe 'symlink'.
char exe_link[NAME_MAX];
if (!BuildProcPath(exe_link, pid_, "exe"))
return false;
MappingInfo new_mapping = {0};
if (!SafeReadLink(exe_link, new_mapping.name))
return false;
char new_path[PATH_MAX];
if (!GetMappingAbsolutePath(new_mapping, new_path))
return false;
if (my_strcmp(path, new_path) != 0)
return false;
// Check to see if someone actually named their executable 'foo (deleted)'.
struct kernel_stat exe_stat;
struct kernel_stat new_path_stat;
if (sys_stat(exe_link, &exe_stat) == 0 &&
sys_stat(new_path, &new_path_stat) == 0 &&
exe_stat.st_dev == new_path_stat.st_dev &&
exe_stat.st_ino == new_path_stat.st_ino) {
return false;
}
my_memcpy(path, exe_link, NAME_MAX);
return true;
}
} // namespace google_breakpad

@ -0,0 +1,327 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
// linux_dumper.h: Define the google_breakpad::LinuxDumper class, which
// is a base class for extracting information of a crashed process. It
// was originally a complete implementation using the ptrace API, but
// has been refactored to allow derived implementations supporting both
// ptrace and core dump. A portion of the original implementation is now
// in google_breakpad::LinuxPtraceDumper (see linux_ptrace_dumper.h for
// details).
#ifndef CLIENT_LINUX_MINIDUMP_WRITER_LINUX_DUMPER_H_
#define CLIENT_LINUX_MINIDUMP_WRITER_LINUX_DUMPER_H_
#include <assert.h>
#include <elf.h>
#if defined(__ANDROID__)
#include <link.h>
#endif
#include <linux/limits.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/user.h>
#include <vector>
#include "client/linux/dump_writer_common/mapping_info.h"
#include "client/linux/dump_writer_common/thread_info.h"
#include "common/linux/file_id.h"
#include "common/memory_allocator.h"
#include "google_breakpad/common/minidump_format.h"
namespace google_breakpad {
// Typedef for our parsing of the auxv variables in /proc/pid/auxv.
#if defined(__i386) || defined(__ARM_EABI__) || \
(defined(__mips__) && _MIPS_SIM == _ABIO32) || \
(defined(__riscv) && __riscv_xlen == 32)
typedef Elf32_auxv_t elf_aux_entry;
#elif defined(__x86_64) || defined(__aarch64__) || \
(defined(__mips__) && _MIPS_SIM != _ABIO32) || \
(defined(__riscv) && __riscv_xlen == 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!
const char kLinuxGateLibraryName[] = "linux-gate.so";
class LinuxDumper {
public:
// The |root_prefix| is prepended to mapping paths before opening them, which
// is useful if the crash originates from a chroot.
explicit LinuxDumper(pid_t pid, const char* root_prefix = "");
virtual ~LinuxDumper();
// Parse the data for |threads| and |mappings|.
virtual bool Init();
// Take any actions that could not be taken in Init(). LateInit() is
// called after all other caller's initialization is complete, and in
// particular after it has called ThreadsSuspend(), so that ptrace is
// available.
virtual bool LateInit();
// Return true if the dumper performs a post-mortem dump.
virtual bool IsPostMortem() const = 0;
// Suspend/resume all threads in the given process.
virtual bool ThreadsSuspend() = 0;
virtual bool ThreadsResume() = 0;
// Read information about the |index|-th thread of |threads_|.
// Returns true on success. One must have called |ThreadsSuspend| first.
virtual bool GetThreadInfoByIndex(size_t index, ThreadInfo* info) = 0;
size_t GetMainThreadIndex() const {
for (size_t i = 0; i < threads_.size(); ++i) {
if (threads_[i] == pid_) return i;
}
return -1u;
}
// These are only valid after a call to |Init|.
const wasteful_vector<pid_t>& threads() { return threads_; }
const wasteful_vector<MappingInfo*>& mappings() { return mappings_; }
const MappingInfo* FindMapping(const void* address) const;
// Find the mapping which the given memory address falls in. Unlike
// FindMapping, this method uses the unadjusted mapping address
// ranges from the kernel, rather than the ranges that have had the
// load bias applied.
const MappingInfo* FindMappingNoBias(uintptr_t 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
// stack_len: (output) the length of the memory area
// stack_top: the current top of the stack
bool GetStackInfo(const void** stack, size_t* stack_len, uintptr_t stack_top);
// Sanitize a copy of the stack by overwriting words that are not
// pointers with a sentinel (0x0defaced).
// stack_copy: a copy of the stack to sanitize. |stack_copy| might
// not be word aligned, but it represents word aligned
// data copied from another location.
// stack_len: the length of the allocation pointed to by |stack_copy|.
// stack_pointer: the address of the stack pointer (used to locate
// the stack mapping, as an optimization).
// sp_offset: the offset relative to stack_copy that reflects the
// current value of the stack pointer.
void SanitizeStackCopy(uint8_t* stack_copy, size_t stack_len,
uintptr_t stack_pointer, uintptr_t sp_offset);
// Test whether |stack_copy| contains a pointer-aligned word that
// could be an address within a given mapping.
// stack_copy: a copy of the stack to check. |stack_copy| might
// not be word aligned, but it represents word aligned
// data copied from another location.
// stack_len: the length of the allocation pointed to by |stack_copy|.
// sp_offset: the offset relative to stack_copy that reflects the
// current value of the stack pointer.
// mapping: the mapping against which to test stack words.
bool StackHasPointerToMapping(const uint8_t* stack_copy, size_t stack_len,
uintptr_t sp_offset,
const MappingInfo& mapping);
PageAllocator* allocator() { return &allocator_; }
// Copy content of |length| bytes from a given process |child|,
// starting from |src|, into |dest|. Returns true on success.
virtual bool CopyFromProcess(void* dest, pid_t child, const void* src,
size_t length) = 0;
// Builds a proc path for a certain pid for a node (/proc/<pid>/<node>).
// |path| is a character array of at least NAME_MAX bytes to return the
// result.|node| is the final node without any slashes. Returns true on
// success.
virtual bool BuildProcPath(char* path, pid_t pid, const char* node) const = 0;
// Generate a File ID from the .text section of a mapped entry.
// If not a member, mapping_id is ignored. This method can also manipulate the
// |mapping|.name to truncate "(deleted)" from the file name if necessary.
bool ElfFileIdentifierForMapping(const MappingInfo& mapping,
bool member,
unsigned int mapping_id,
wasteful_vector<uint8_t>& identifier);
void SetCrashInfoFromSigInfo(const siginfo_t& siginfo);
uintptr_t crash_address() const { return crash_address_; }
void set_crash_address(uintptr_t crash_address) {
crash_address_ = crash_address;
}
int crash_signal() const { return crash_signal_; }
void set_crash_signal(int crash_signal) { crash_signal_ = crash_signal; }
const char* GetCrashSignalString() const;
void set_crash_signal_code(int code) { crash_signal_code_ = code; }
int crash_signal_code() const { return crash_signal_code_; }
void set_crash_exception_info(const std::vector<uint64_t>& exception_info) {
assert(exception_info.size() <= MD_EXCEPTION_MAXIMUM_PARAMETERS);
crash_exception_info_ = exception_info;
}
const std::vector<uint64_t>& crash_exception_info() const {
return crash_exception_info_;
}
pid_t crash_thread() const { return crash_thread_; }
void set_crash_thread(pid_t crash_thread) { crash_thread_ = crash_thread; }
// Concatenates the |root_prefix_| and |mapping| path. Writes into |path| and
// returns true unless the string is too long.
bool GetMappingAbsolutePath(const MappingInfo& mapping,
char path[PATH_MAX]) const;
// Extracts the effective path and file name of from |mapping|. In most cases
// the effective name/path are just the mapping's path and basename. In some
// other cases, however, a library can be mapped from an archive (e.g., when
// loading .so libs from an apk on Android) and this method is able to
// reconstruct the original file name.
void GetMappingEffectiveNameAndPath(const MappingInfo& mapping,
char* file_path,
size_t file_path_size,
char* file_name,
size_t file_name_size);
protected:
bool ReadAuxv();
virtual bool EnumerateMappings();
virtual bool EnumerateThreads() = 0;
// For the case where a running program has been deleted, it'll show up in
// /proc/pid/maps as "/path/to/program (deleted)". If this is the case, then
// see if '/path/to/program (deleted)' matches /proc/pid/exe and return
// /proc/pid/exe in |path| so ELF identifier generation works correctly. This
// also checks to see if '/path/to/program (deleted)' exists, so it does not
// get fooled by a poorly named binary.
// For programs that don't end with ' (deleted)', this is a no-op.
// This assumes |path| is a buffer with length NAME_MAX.
// Returns true if |path| is modified.
bool HandleDeletedFileInMapping(char* path) const;
// ID of the crashed process.
const pid_t pid_;
// Path of the root directory to which mapping paths are relative.
const char* const root_prefix_;
// Virtual address at which the process crashed.
uintptr_t crash_address_;
// Signal that terminated the crashed process.
int crash_signal_;
// The code associated with |crash_signal_|.
int crash_signal_code_;
// The additional fields associated with |crash_signal_|.
std::vector<uint64_t> crash_exception_info_;
// ID of the crashed thread.
pid_t crash_thread_;
mutable PageAllocator allocator_;
// IDs of all the threads.
wasteful_vector<pid_t> threads_;
// Info from /proc/<pid>/maps.
wasteful_vector<MappingInfo*> mappings_;
// Info from /proc/<pid>/auxv
wasteful_vector<elf_aux_val_t> auxv_;
#if defined(__ANDROID__)
private:
// Android M and later support packed ELF relocations in shared libraries.
// Packing relocations changes the vaddr of the LOAD segments, such that
// the effective load bias is no longer the same as the start address of
// the memory mapping containing the executable parts of the library. The
// packing is applied to the stripped library run on the target, but not to
// any other library, and in particular not to the library used to generate
// breakpad symbols. As a result, we need to adjust the |start_addr| for
// any mapping that results from a shared library that contains Android
// packed relocations, so that it properly represents the effective library
// load bias. The following functions support this adjustment.
// Check that a given mapping at |start_addr| is for an ELF shared library.
// If it is, place the ELF header in |ehdr| and return true.
// The first LOAD segment in an ELF shared library has offset zero, so the
// ELF file header is at the start of this map entry, and in already mapped
// memory.
bool GetLoadedElfHeader(uintptr_t start_addr, ElfW(Ehdr)* ehdr);
// For the ELF file mapped at |start_addr|, iterate ELF program headers to
// find the min vaddr of all program header LOAD segments, the vaddr for
// the DYNAMIC segment, and a count of DYNAMIC entries. Return values in
// |min_vaddr_ptr|, |dyn_vaddr_ptr|, and |dyn_count_ptr|.
// The program header table is also in already mapped memory.
void ParseLoadedElfProgramHeaders(ElfW(Ehdr)* ehdr,
uintptr_t start_addr,
uintptr_t* min_vaddr_ptr,
uintptr_t* dyn_vaddr_ptr,
size_t* dyn_count_ptr);
// Search the DYNAMIC tags for the ELF file with the given |load_bias|, and
// return true if the tags indicate that the file contains Android packed
// relocations. Dynamic tags are found at |dyn_vaddr| past the |load_bias|.
bool HasAndroidPackedRelocations(uintptr_t load_bias,
uintptr_t dyn_vaddr,
size_t dyn_count);
// If the ELF file mapped at |start_addr| contained Android packed
// relocations, return the load bias that the system linker (or Chromium
// crazy linker) will have used. If the file did not contain Android
// packed relocations, returns |start_addr|, indicating that no adjustment
// is necessary.
// The effective load bias is |start_addr| adjusted downwards by the
// min vaddr in the library LOAD segments.
uintptr_t GetEffectiveLoadBias(ElfW(Ehdr)* ehdr, uintptr_t start_addr);
// Called from LateInit(). Iterates |mappings_| and rewrites the |start_addr|
// field of any that represent ELF shared libraries with Android packed
// relocations, so that |start_addr| is the load bias that the system linker
// (or Chromium crazy linker) used. This value matches the addresses produced
// when the non-relocation-packed library is used for breakpad symbol
// generation.
void LatePostprocessMappings();
#endif // __ANDROID__
};
} // namespace google_breakpad
#endif // CLIENT_LINUX_HANDLER_LINUX_DUMPER_H_

@ -0,0 +1,96 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
//
// Helper program for the linux_dumper class, which creates a bunch of
// threads. The first word of each thread's stack is set to the thread
// id.
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/syscall.h>
#include <unistd.h>
#include "common/scoped_ptr.h"
#include "third_party/lss/linux_syscall_support.h"
#if defined(__ARM_EABI__)
#define TID_PTR_REGISTER "r3"
#elif defined(__aarch64__)
#define TID_PTR_REGISTER "x3"
#elif defined(__i386)
#define TID_PTR_REGISTER "ecx"
#elif defined(__x86_64)
#define TID_PTR_REGISTER "rcx"
#elif defined(__mips__)
#define TID_PTR_REGISTER "$1"
#elif defined(__riscv)
#define TID_PTR_REGISTER "x4"
#else
#error This test has not been ported to this platform.
#endif
void* thread_function(void* data) {
int pipefd = *static_cast<int*>(data);
volatile pid_t* thread_id = new pid_t;
*thread_id = syscall(__NR_gettid);
// Signal parent that a thread has started.
uint8_t byte = 1;
if (write(pipefd, &byte, sizeof(byte)) != sizeof(byte)) {
perror("ERROR: parent notification failed");
return NULL;
}
register volatile pid_t* thread_id_ptr asm(TID_PTR_REGISTER) = thread_id;
while (true)
asm volatile ("" : : "r" (thread_id_ptr));
return NULL;
}
int main(int argc, char* argv[]) {
if (argc < 3) {
fprintf(stderr,
"usage: linux_dumper_unittest_helper <pipe fd> <# of threads>\n");
return 1;
}
int pipefd = atoi(argv[1]);
int num_threads = atoi(argv[2]);
if (num_threads < 1) {
fprintf(stderr, "ERROR: number of threads is 0");
return 1;
}
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);
for (int i = 1; i < num_threads; i++) {
pthread_create(&threads[i], &thread_attributes, &thread_function, &pipefd);
}
thread_function(&pipefd);
return 0;
}

@ -0,0 +1,378 @@
// Copyright 2012 Google LLC
//
// 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 LLC 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.
// linux_ptrace_dumper.cc: Implement google_breakpad::LinuxPtraceDumper.
// See linux_ptrace_dumper.h for detals.
// This class was originally splitted from google_breakpad::LinuxDumper.
// This code deals with the mechanics of getting information about a crashed
// process. Since this code may run in a compromised address space, the same
// rules apply as detailed at the top of minidump_writer.h: no libc calls and
// use the alternative allocator.
#include "client/linux/minidump_writer/linux_ptrace_dumper.h"
#include <asm/ptrace.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ptrace.h>
#include <sys/uio.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"
#include "third_party/lss/linux_syscall_support.h"
// Suspends a thread by attaching to it.
static bool SuspendThread(pid_t pid) {
// This may fail if the thread has just died or debugged.
errno = 0;
if (sys_ptrace(PTRACE_ATTACH, pid, NULL, NULL) != 0 &&
errno != 0) {
return false;
}
while (sys_waitpid(pid, NULL, __WALL) < 0) {
if (errno != EINTR) {
sys_ptrace(PTRACE_DETACH, pid, NULL, NULL);
return false;
}
}
#if defined(__i386) || defined(__x86_64)
// On x86, the stack pointer is NULL or -1, when executing trusted code in
// the seccomp sandbox. Not only does this cause difficulties down the line
// when trying to dump the thread's stack, it also results in the minidumps
// containing information about the trusted threads. This information is
// generally completely meaningless and just pollutes the minidumps.
// We thus test the stack pointer and exclude any threads that are part of
// the seccomp sandbox's trusted code.
user_regs_struct regs;
if (sys_ptrace(PTRACE_GETREGS, pid, NULL, &regs) == -1 ||
#if defined(__i386)
!regs.esp
#elif defined(__x86_64)
!regs.rsp
#endif
) {
sys_ptrace(PTRACE_DETACH, pid, NULL, NULL);
return false;
}
#endif
return true;
}
// Resumes a thread by detaching from it.
static bool ResumeThread(pid_t pid) {
return sys_ptrace(PTRACE_DETACH, pid, NULL, NULL) >= 0;
}
namespace google_breakpad {
LinuxPtraceDumper::LinuxPtraceDumper(pid_t pid)
: LinuxDumper(pid),
threads_suspended_(false) {
}
bool LinuxPtraceDumper::BuildProcPath(char* path, pid_t pid,
const char* node) const {
if (!path || !node || pid <= 0)
return false;
size_t node_len = my_strlen(node);
if (node_len == 0)
return false;
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;
my_memcpy(path, "/proc/", 6);
my_uitos(path + 6, pid, pid_len);
path[6 + pid_len] = '/';
my_memcpy(path + 6 + pid_len + 1, node, node_len);
path[total_length] = '\0';
return true;
}
bool LinuxPtraceDumper::CopyFromProcess(void* dest, pid_t child,
const void* src, size_t length) {
unsigned long tmp = 55;
size_t done = 0;
static const size_t word_size = sizeof(tmp);
uint8_t* const local = (uint8_t*) dest;
uint8_t* const remote = (uint8_t*) src;
while (done < length) {
const size_t l = (length - done > word_size) ? word_size : (length - done);
if (sys_ptrace(PTRACE_PEEKDATA, child, remote + done, &tmp) == -1) {
tmp = 0;
}
my_memcpy(local + done, &tmp, l);
done += l;
}
return true;
}
bool LinuxPtraceDumper::ReadRegisterSet(ThreadInfo* info, pid_t tid)
{
#ifdef PTRACE_GETREGSET
struct iovec io;
info->GetGeneralPurposeRegisters(&io.iov_base, &io.iov_len);
if (sys_ptrace(PTRACE_GETREGSET, tid, (void*)NT_PRSTATUS, (void*)&io) == -1) {
return false;
}
info->GetFloatingPointRegisters(&io.iov_base, &io.iov_len);
if (sys_ptrace(PTRACE_GETREGSET, tid, (void*)NT_FPREGSET, (void*)&io) == -1) {
return false;
}
return true;
#else
return false;
#endif
}
bool LinuxPtraceDumper::ReadRegisters(ThreadInfo* info, pid_t tid) {
#ifdef PTRACE_GETREGS
void* gp_addr;
info->GetGeneralPurposeRegisters(&gp_addr, NULL);
if (sys_ptrace(PTRACE_GETREGS, tid, NULL, gp_addr) == -1) {
return false;
}
#if !(defined(__ANDROID__) && defined(__ARM_EABI__))
// When running an arm build on an arm64 device, attempting to get the
// floating point registers fails. On Android, the floating point registers
// aren't written to the cpu context anyway, so just don't get them here.
// See http://crbug.com/508324
void* fp_addr;
info->GetFloatingPointRegisters(&fp_addr, NULL);
if (sys_ptrace(PTRACE_GETFPREGS, tid, NULL, fp_addr) == -1) {
return false;
}
#endif // !(defined(__ANDROID__) && defined(__ARM_EABI__))
return true;
#else // PTRACE_GETREGS
return false;
#endif
}
// Read thread info from /proc/$pid/status.
// Fill out the |tgid|, |ppid| and |pid| members of |info|. If unavailable,
// these members are set to -1. Returns true iff all three members are
// available.
bool LinuxPtraceDumper::GetThreadInfoByIndex(size_t index, ThreadInfo* info) {
if (index >= threads_.size())
return false;
pid_t tid = threads_[index];
assert(info != NULL);
char status_path[NAME_MAX];
if (!BuildProcPath(status_path, tid, "status"))
return false;
const int fd = sys_open(status_path, O_RDONLY, 0);
if (fd < 0)
return false;
LineReader* const line_reader = new(allocator_) LineReader(fd);
const char* line;
unsigned line_len;
info->ppid = info->tgid = -1;
while (line_reader->GetNextLine(&line, &line_len)) {
if (my_strncmp("Tgid:\t", line, 6) == 0) {
my_strtoui(&info->tgid, line + 6);
} else if (my_strncmp("PPid:\t", line, 6) == 0) {
my_strtoui(&info->ppid, line + 6);
}
line_reader->PopLine(line_len);
}
sys_close(fd);
if (info->ppid == -1 || info->tgid == -1)
return false;
if (!ReadRegisterSet(info, tid)) {
if (!ReadRegisters(info, tid)) {
return false;
}
}
#if defined(__i386)
#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) {
if (sys_ptrace(
PTRACE_PEEKUSER, tid,
reinterpret_cast<void*> (offsetof(struct user,
u_debugreg[0]) + i *
sizeof(debugreg_t)),
&info->dregs[i]) == -1) {
return false;
}
}
#endif
#if defined(__mips__)
sys_ptrace(PTRACE_PEEKUSER, tid,
reinterpret_cast<void*>(PC), &info->mcontext.pc);
sys_ptrace(PTRACE_PEEKUSER, tid,
reinterpret_cast<void*>(DSP_BASE), &info->mcontext.hi1);
sys_ptrace(PTRACE_PEEKUSER, tid,
reinterpret_cast<void*>(DSP_BASE + 1), &info->mcontext.lo1);
sys_ptrace(PTRACE_PEEKUSER, tid,
reinterpret_cast<void*>(DSP_BASE + 2), &info->mcontext.hi2);
sys_ptrace(PTRACE_PEEKUSER, tid,
reinterpret_cast<void*>(DSP_BASE + 3), &info->mcontext.lo2);
sys_ptrace(PTRACE_PEEKUSER, tid,
reinterpret_cast<void*>(DSP_BASE + 4), &info->mcontext.hi3);
sys_ptrace(PTRACE_PEEKUSER, tid,
reinterpret_cast<void*>(DSP_BASE + 5), &info->mcontext.lo3);
sys_ptrace(PTRACE_PEEKUSER, tid,
reinterpret_cast<void*>(DSP_CONTROL), &info->mcontext.dsp);
#endif
const uint8_t* stack_pointer;
#if defined(__i386)
my_memcpy(&stack_pointer, &info->regs.esp, sizeof(info->regs.esp));
#elif defined(__x86_64)
my_memcpy(&stack_pointer, &info->regs.rsp, sizeof(info->regs.rsp));
#elif defined(__ARM_EABI__)
my_memcpy(&stack_pointer, &info->regs.ARM_sp, sizeof(info->regs.ARM_sp));
#elif defined(__aarch64__)
my_memcpy(&stack_pointer, &info->regs.sp, sizeof(info->regs.sp));
#elif defined(__mips__)
stack_pointer =
reinterpret_cast<uint8_t*>(info->mcontext.gregs[MD_CONTEXT_MIPS_REG_SP]);
#elif defined(__riscv)
stack_pointer = reinterpret_cast<uint8_t*>(
info->mcontext.__gregs[MD_CONTEXT_RISCV_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 true;
}
bool LinuxPtraceDumper::IsPostMortem() const {
return false;
}
bool LinuxPtraceDumper::ThreadsSuspend() {
if (threads_suspended_)
return true;
for (size_t i = 0; i < threads_.size(); ++i) {
if (!SuspendThread(threads_[i])) {
// 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.
if (i < threads_.size() - 1) {
my_memmove(&threads_[i], &threads_[i + 1],
(threads_.size() - i - 1) * sizeof(threads_[i]));
}
threads_.resize(threads_.size() - 1);
--i;
}
}
threads_suspended_ = true;
return threads_.size() > 0;
}
bool LinuxPtraceDumper::ThreadsResume() {
if (!threads_suspended_)
return false;
bool good = true;
for (size_t i = 0; i < threads_.size(); ++i)
good &= ResumeThread(threads_[i]);
threads_suspended_ = false;
return good;
}
// Parse /proc/$pid/task to list all the threads of the process identified by
// pid.
bool LinuxPtraceDumper::EnumerateThreads() {
char task_path[NAME_MAX];
if (!BuildProcPath(task_path, pid_, "task"))
return false;
const int fd = sys_open(task_path, O_RDONLY | O_DIRECTORY, 0);
if (fd < 0)
return false;
DirectoryReader* dir_reader = new(allocator_) DirectoryReader(fd);
// The directory may contain duplicate entries which we filter by assuming
// that they are consecutive.
int last_tid = -1;
const char* dent_name;
while (dir_reader->GetNextEntry(&dent_name)) {
if (my_strcmp(dent_name, ".") &&
my_strcmp(dent_name, "..")) {
int tid = 0;
if (my_strtoui(&tid, dent_name) &&
last_tid != tid) {
last_tid = tid;
threads_.push_back(tid);
}
}
dir_reader->PopEntry();
}
sys_close(fd);
return true;
}
} // namespace google_breakpad

@ -0,0 +1,100 @@
// Copyright 2012 Google LLC
//
// 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 LLC 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.
// linux_ptrace_dumper.h: Define the google_breakpad::LinuxPtraceDumper
// class, which is derived from google_breakpad::LinuxDumper to extract
// information from a crashed process via ptrace.
// This class was originally splitted from google_breakpad::LinuxDumper.
#ifndef CLIENT_LINUX_MINIDUMP_WRITER_LINUX_PTRACE_DUMPER_H_
#define CLIENT_LINUX_MINIDUMP_WRITER_LINUX_PTRACE_DUMPER_H_
#include "client/linux/minidump_writer/linux_dumper.h"
namespace google_breakpad {
class LinuxPtraceDumper : public LinuxDumper {
public:
// Constructs a dumper for extracting information of a given process
// with a process ID of |pid|.
explicit LinuxPtraceDumper(pid_t pid);
// Implements LinuxDumper::BuildProcPath().
// Builds a proc path for a certain pid for a node (/proc/<pid>/<node>).
// |path| is a character array of at least NAME_MAX bytes to return the
// result. |node| is the final node without any slashes. Returns true on
// success.
virtual bool BuildProcPath(char* path, pid_t pid, const char* node) const;
// Implements LinuxDumper::CopyFromProcess().
// Copies content of |length| bytes from a given process |child|,
// starting from |src|, into |dest|. This method uses ptrace to extract
// the content from the target process. Always returns true.
virtual bool CopyFromProcess(void* dest, pid_t child, const void* src,
size_t length);
// Implements LinuxDumper::GetThreadInfoByIndex().
// Reads information about the |index|-th thread of |threads_|.
// Returns true on success. One must have called |ThreadsSuspend| first.
virtual bool GetThreadInfoByIndex(size_t index, ThreadInfo* info);
// Implements LinuxDumper::IsPostMortem().
// Always returns false to indicate this dumper performs a dump of
// a crashed process via ptrace.
virtual bool IsPostMortem() const;
// Implements LinuxDumper::ThreadsSuspend().
// Suspends all threads in the given process. Returns true on success.
virtual bool ThreadsSuspend();
// Implements LinuxDumper::ThreadsResume().
// Resumes all threads in the given process. Returns true on success.
virtual bool ThreadsResume();
protected:
// Implements LinuxDumper::EnumerateThreads().
// Enumerates all threads of the given process into |threads_|.
virtual bool EnumerateThreads();
private:
// Set to true if all threads of the crashed process are suspended.
bool threads_suspended_;
// Read the tracee's registers on kernel with PTRACE_GETREGSET support.
// Returns false if PTRACE_GETREGSET is not defined.
// Returns true on success.
bool ReadRegisterSet(ThreadInfo* info, pid_t tid);
// Read the tracee's registers on kernel with PTRACE_GETREGS support.
// Returns true on success.
bool ReadRegisters(ThreadInfo* info, pid_t tid);
};
} // namespace google_breakpad
#endif // CLIENT_LINUX_HANDLER_LINUX_PTRACE_DUMPER_H_

@ -0,0 +1,586 @@
// Copyright 2009 Google LLC
//
// 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 LLC 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.
// linux_ptrace_dumper_unittest.cc:
// 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 <poll.h>
#include <unistd.h>
#include <signal.h>
#include <stdint.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <string>
#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_allocator.h"
#include "common/using_std_string.h"
#ifndef PR_SET_PTRACER
#define PR_SET_PTRACER 0x59616d61
#endif
using namespace google_breakpad;
using google_breakpad::elf::FileID;
using google_breakpad::elf::kDefaultBuildIdSize;
namespace {
pid_t SetupChildProcess(int number_of_threads) {
char kNumberOfThreadsArgument[2];
sprintf(kNumberOfThreadsArgument, "%d", number_of_threads);
int fds[2];
EXPECT_NE(-1, pipe(fds));
pid_t child_pid = fork();
if (child_pid == 0) {
// In child process.
close(fds[0]);
string helper_path(GetHelperBinary());
if (helper_path.empty()) {
fprintf(stderr, "Couldn't find helper binary\n");
_exit(1);
}
// 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(),
"linux_dumper_unittest_helper",
pipe_fd_string,
kNumberOfThreadsArgument,
NULL);
// Kill if we get here.
printf("Errno from exec: %d", errno);
std::string err_str = "Exec of " + helper_path + " failed";
perror(err_str.c_str());
_exit(1);
}
close(fds[1]);
// Wait for all child threads to indicate that they have started
for (int threads = 0; threads < number_of_threads; 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));
EXPECT_EQ(1, r);
EXPECT_TRUE(pfd.revents & POLLIN);
uint8_t junk;
EXPECT_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);
return child_pid;
}
typedef wasteful_vector<uint8_t> id_vector;
typedef testing::Test LinuxPtraceDumperTest;
/* 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
}
/* 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;
id_vector make_vector() {
return id_vector(&allocator, kDefaultBuildIdSize);
}
private:
static const int kFatalFailure = 1;
static const int kNonFatalFailure = 2;
pid_t child_pid_;
PageAllocator allocator;
};
} // namespace
/* Replace TestBody declarations within TEST*() with RealTestBody
* declarations */
#define TestBody RealTestBody
TEST_F(LinuxPtraceDumperChildTest, Setup) {
LinuxPtraceDumper dumper(getppid());
}
TEST_F(LinuxPtraceDumperChildTest, FindMappings) {
LinuxPtraceDumper dumper(getppid());
ASSERT_TRUE(dumper.Init());
ASSERT_TRUE(dumper.FindMapping(reinterpret_cast<void*>(getpid)));
ASSERT_TRUE(dumper.FindMapping(reinterpret_cast<void*>(printf)));
ASSERT_FALSE(dumper.FindMapping(NULL));
}
TEST_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] == getppid()) {
ASSERT_FALSE(found);
found = true;
}
}
ASSERT_TRUE(found);
}
// Helper stack class to close a file descriptor and unmap
// a mmap'ed mapping.
class StackHelper {
public:
StackHelper()
: fd_(-1), mapping_(NULL), size_(0) {}
~StackHelper() {
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_;
char* mapping_;
size_t size_;
};
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.
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,
PROT_READ,
MAP_SHARED,
fd,
0));
ASSERT_TRUE(mapping);
// Ensure that things get cleaned up.
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 * page_size_,
page_size_,
PROT_NONE,
MAP_SHARED | MAP_FIXED,
fd,
// Map a different offset just to
// better test real-world conditions.
page_size_));
ASSERT_TRUE(inside_mapping);
LinuxPtraceDumperChildTest::SetUp();
}
TEST_F(LinuxPtraceDumperMappingsTest, MergedMappings) {
// Now check that LinuxPtraceDumper interpreted the mappings properly.
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, this->helper_path_.c_str()) == 0) {
// This mapping should encompass the entire original mapped
// range.
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());
id_vector identifier(make_vector());
ASSERT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[index],
true,
index,
identifier));
id_vector empty_identifier(make_vector());
empty_identifier.resize(kDefaultBuildIdSize, 0);
EXPECT_NE(empty_identifier, 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);
id_vector identifier1(make_vector());
id_vector identifier2(make_vector());
EXPECT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[i], true, i,
identifier1));
FileID fileid(exe_name);
EXPECT_TRUE(fileid.ElfFileIdentifier(identifier2));
string identifier_string1 =
FileID::ConvertIdentifierToUUIDString(identifier1);
string identifier_string2 =
FileID::ConvertIdentifierToUUIDString(identifier2);
EXPECT_EQ(identifier_string1, identifier_string2);
}
/* Get back to normal behavior of TEST*() macros wrt TestBody. */
#undef TestBody
TEST(LinuxPtraceDumperTest, VerifyStackReadWithMultipleThreads) {
static const size_t kNumberOfThreadsInHelperProgram = 5;
pid_t child_pid = SetupChildProcess(kNumberOfThreadsInHelperProgram);
ASSERT_NE(child_pid, -1);
// Children are ready now.
LinuxPtraceDumper dumper(child_pid);
ASSERT_TRUE(dumper.Init());
#if defined(THREAD_SANITIZER)
EXPECT_GE(dumper.threads().size(), (size_t)kNumberOfThreadsInHelperProgram);
#else
EXPECT_EQ(dumper.threads().size(), (size_t)kNumberOfThreadsInHelperProgram);
#endif
EXPECT_TRUE(dumper.ThreadsSuspend());
ThreadInfo one_thread;
size_t matching_threads = 0;
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]);
#elif defined(__aarch64__)
pid_t* process_tid_location = (pid_t*)(one_thread.regs.regs[3]);
#elif defined(__i386)
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);
#elif defined(__mips__)
pid_t* process_tid_location =
reinterpret_cast<pid_t*>(one_thread.mcontext.gregs[1]);
#elif defined(__riscv)
pid_t* process_tid_location =
reinterpret_cast<pid_t*>(one_thread.mcontext.__gregs[4]);
#else
#error This test has not been ported to this platform.
#endif
pid_t one_thread_id;
dumper.CopyFromProcess(&one_thread_id,
dumper.threads()[i],
process_tid_location,
4);
matching_threads += (dumper.threads()[i] == one_thread_id) ? 1 : 0;
}
EXPECT_EQ(matching_threads, kNumberOfThreadsInHelperProgram);
EXPECT_TRUE(dumper.ThreadsResume());
kill(child_pid, SIGKILL);
// Reap child
int status;
ASSERT_NE(-1, HANDLE_EINTR(waitpid(child_pid, &status, 0)));
ASSERT_TRUE(WIFSIGNALED(status));
ASSERT_EQ(SIGKILL, WTERMSIG(status));
}
TEST_F(LinuxPtraceDumperTest, SanitizeStackCopy) {
static const size_t kNumberOfThreadsInHelperProgram = 1;
pid_t child_pid = SetupChildProcess(kNumberOfThreadsInHelperProgram);
ASSERT_NE(child_pid, -1);
LinuxPtraceDumper dumper(child_pid);
ASSERT_TRUE(dumper.Init());
EXPECT_TRUE(dumper.ThreadsSuspend());
ThreadInfo thread_info;
EXPECT_TRUE(dumper.GetThreadInfoByIndex(0, &thread_info));
const uintptr_t defaced =
#if defined(__LP64__)
0x0defaced0defaced;
#else
0x0defaced;
#endif
uintptr_t simulated_stack[2];
// Pointers into the stack shouldn't be sanitized.
memset(simulated_stack, 0xff, sizeof(simulated_stack));
simulated_stack[1] = thread_info.stack_pointer;
dumper.SanitizeStackCopy(reinterpret_cast<uint8_t*>(&simulated_stack),
sizeof(simulated_stack), thread_info.stack_pointer,
sizeof(uintptr_t));
ASSERT_NE(simulated_stack[1], defaced);
// Memory prior to the stack pointer should be cleared.
ASSERT_EQ(simulated_stack[0], 0u);
// Small integers should not be sanitized.
for (int i = -4096; i <= 4096; ++i) {
memset(simulated_stack, 0, sizeof(simulated_stack));
simulated_stack[0] = static_cast<uintptr_t>(i);
dumper.SanitizeStackCopy(reinterpret_cast<uint8_t*>(&simulated_stack),
sizeof(simulated_stack), thread_info.stack_pointer,
0u);
ASSERT_NE(simulated_stack[0], defaced);
}
// The instruction pointer definitely should point into an executable mapping.
const MappingInfo* mapping_info = dumper.FindMappingNoBias(
reinterpret_cast<uintptr_t>(thread_info.GetInstructionPointer()));
ASSERT_NE(mapping_info, nullptr);
ASSERT_TRUE(mapping_info->exec);
// Pointers to code shouldn't be sanitized.
memset(simulated_stack, 0, sizeof(simulated_stack));
simulated_stack[1] = thread_info.GetInstructionPointer();
dumper.SanitizeStackCopy(reinterpret_cast<uint8_t*>(&simulated_stack),
sizeof(simulated_stack), thread_info.stack_pointer,
0u);
ASSERT_NE(simulated_stack[0], defaced);
// String fragments should be sanitized.
memcpy(simulated_stack, "abcdefghijklmnop", sizeof(simulated_stack));
dumper.SanitizeStackCopy(reinterpret_cast<uint8_t*>(&simulated_stack),
sizeof(simulated_stack), thread_info.stack_pointer,
0u);
ASSERT_EQ(simulated_stack[0], defaced);
ASSERT_EQ(simulated_stack[1], defaced);
// Heap pointers should be sanititzed.
#if defined(__ARM_EABI__)
uintptr_t heap_addr = thread_info.regs.uregs[3];
#elif defined(__aarch64__)
uintptr_t heap_addr = thread_info.regs.regs[3];
#elif defined(__i386)
uintptr_t heap_addr = thread_info.regs.ecx;
#elif defined(__x86_64)
uintptr_t heap_addr = thread_info.regs.rcx;
#elif defined(__mips__)
uintptr_t heap_addr = thread_info.mcontext.gregs[1];
#elif defined(__riscv)
uintptr_t heap_addr = thread_info.mcontext.__gregs[4];
#else
#error This test has not been ported to this platform.
#endif
memset(simulated_stack, 0, sizeof(simulated_stack));
simulated_stack[0] = heap_addr;
dumper.SanitizeStackCopy(reinterpret_cast<uint8_t*>(&simulated_stack),
sizeof(simulated_stack), thread_info.stack_pointer,
0u);
ASSERT_EQ(simulated_stack[0], defaced);
EXPECT_TRUE(dumper.ThreadsResume());
kill(child_pid, SIGKILL);
// Reap child.
int status;
ASSERT_NE(-1, HANDLE_EINTR(waitpid(child_pid, &status, 0)));
ASSERT_TRUE(WIFSIGNALED(status));
ASSERT_EQ(SIGKILL, WTERMSIG(status));
}

@ -0,0 +1,142 @@
// Copyright 2009 Google LLC
//
// 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 LLC 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_MINIDUMP_WRITER_H_
#define CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_
#include <stdint.h>
#include <sys/types.h>
#include <sys/ucontext.h>
#include <unistd.h>
#include <list>
#include <type_traits>
#include <utility>
#include "client/linux/minidump_writer/linux_dumper.h"
#include "google_breakpad/common/minidump_format.h"
namespace google_breakpad {
class ExceptionHandler;
#if defined(__aarch64__)
typedef struct fpsimd_context fpstate_t;
#elif !defined(__ARM_EABI__) && !defined(__mips__)
typedef std::remove_pointer<fpregset_t>::type fpstate_t;
#endif
// 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.
// 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* minidump_path, pid_t crashing_process,
const void* blob, size_t blob_size,
bool skip_stacks_if_mapping_unreferenced = false,
uintptr_t principal_mapping_address = 0,
bool sanitize_stacks = false);
// 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,
bool skip_stacks_if_mapping_unreferenced = false,
uintptr_t principal_mapping_address = 0,
bool sanitize_stacks = false);
// 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 AppMemoryList& appdata,
bool skip_stacks_if_mapping_unreferenced = false,
uintptr_t principal_mapping_address = 0,
bool sanitize_stacks = false);
bool WriteMinidump(int minidump_fd, pid_t crashing_process,
const void* blob, size_t blob_size,
const MappingList& mappings,
const AppMemoryList& appdata,
bool skip_stacks_if_mapping_unreferenced = false,
uintptr_t principal_mapping_address = 0,
bool sanitize_stacks = false);
// 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 skip_stacks_if_mapping_unreferenced = false,
uintptr_t principal_mapping_address = 0,
bool sanitize_stacks = false);
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 skip_stacks_if_mapping_unreferenced = false,
uintptr_t principal_mapping_address = 0,
bool sanitize_stacks = false);
bool WriteMinidump(const char* filename,
const MappingList& mappings,
const AppMemoryList& appdata,
LinuxDumper* dumper);
} // namespace google_breakpad
#endif // CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_

@ -0,0 +1,938 @@
// Copyright 2011 Google LLC
//
// 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 LLC nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <fcntl.h>
#include <poll.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <ucontext.h>
#include <unistd.h>
#include <string>
#include "breakpad_googletest_includes.h"
#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/breakpad_getcontext.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;
using google_breakpad::elf::FileID;
using google_breakpad::elf::kDefaultBuildIdSize;
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_group);
}
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]);
IGNORE_EINTR(waitpid(child, nullptr, 0));
}
TEST(MinidumpWriterTest, SetupWithFD) {
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_group);
}
close(fds[0]);
ExceptionHandler::CrashContext context;
memset(&context, 0, sizeof(context));
AutoTempDir temp_dir;
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 = child;
ASSERT_TRUE(WriteMinidump(fd, child, &context, sizeof(context)));
struct stat st;
ASSERT_EQ(0, stat(templ.c_str(), &st));
ASSERT_GT(st.st_size, 0);
close(fds[1]);
IGNORE_EINTR(waitpid(child, nullptr, 0));
}
// Test that mapping info can be specified when writing a minidump,
// and that it ends up in the module list of the minidump.
TEST(MinidumpWriterTest, MappingInfo) {
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 memory_size = sysconf(_SC_PAGESIZE);
const char* kMemoryName = "a fake module";
const uint8_t kModuleGUID[sizeof(MDGUID)] = {
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
};
const string module_identifier = "33221100554477668899AABBCCDDEEFF0";
// Get some memory.
char* memory =
reinterpret_cast<char*>(mmap(NULL,
memory_size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON,
-1,
0));
const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory);
ASSERT_TRUE(memory);
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_group);
}
close(fds[0]);
ExceptionHandler::CrashContext context;
memset(&context, 0, sizeof(context));
ASSERT_EQ(0, getcontext(&context.context));
context.tid = child;
AutoTempDir temp_dir;
string templ = temp_dir.path() + kMDWriterUnitTestFileName;
// Add information about the mapped memory.
MappingInfo info;
info.start_addr = kMemoryAddress;
info.size = memory_size;
info.offset = 0;
info.exec = false;
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, memory_list, false, 0, false));
// 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);
ASSERT_TRUE(minidump.Read());
MinidumpModuleList* module_list = minidump.GetModuleList();
ASSERT_TRUE(module_list);
const MinidumpModule* module =
module_list->GetModuleForAddress(kMemoryAddress);
ASSERT_TRUE(module);
EXPECT_EQ(kMemoryAddress, module->base_address());
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]);
IGNORE_EINTR(waitpid(child, nullptr, 0));
}
// Test that minidumping is skipped while writing minidumps if principal mapping
// is not referenced.
TEST(MinidumpWriterTest, MinidumpSkippedIfRequested) {
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_group);
}
close(fds[0]);
ExceptionHandler::CrashContext context;
memset(&context, 0, sizeof(context));
ASSERT_EQ(0, getcontext(&context.context));
context.tid = child;
AutoTempDir temp_dir;
string templ = temp_dir.path() + kMDWriterUnitTestFileName;
// pass an invalid principal mapping address, which will force
// WriteMinidump to not write a minidump.
ASSERT_FALSE(WriteMinidump(templ.c_str(), child, &context, sizeof(context),
true, static_cast<uintptr_t>(0x0102030405060708ull),
false));
close(fds[1]);
IGNORE_EINTR(waitpid(child, nullptr, 0));
}
// Test that minidumping is skipped while writing minidumps if principal mapping
// is not referenced.
TEST(MinidumpWriterTest, MinidumpStacksSkippedIfRequested) {
int fds[2];
ASSERT_NE(-1, pipe(fds));
const pid_t child = fork();
if (child == 0) {
close(fds[1]);
// Create a thread that does not return, and only references libc (not the
// current executable). This thread should not be captured in the minidump.
pthread_t thread;
pthread_attr_t thread_attributes;
pthread_attr_init(&thread_attributes);
pthread_attr_setdetachstate(&thread_attributes, PTHREAD_CREATE_DETACHED);
sigset_t sigset;
sigemptyset(&sigset);
pthread_create(&thread, &thread_attributes,
reinterpret_cast<void* (*)(void*)>(&sigsuspend), &sigset);
char b;
IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b))));
close(fds[0]);
syscall(__NR_exit_group);
}
close(fds[0]);
ExceptionHandler::CrashContext context;
memset(&context, 0, sizeof(context));
ASSERT_EQ(0, getcontext(&context.context));
context.tid = child;
AutoTempDir temp_dir;
string templ = temp_dir.path() + kMDWriterUnitTestFileName;
// Pass an invalid principal mapping address, which will force
// WriteMinidump to not dump any thread stacks.
ASSERT_TRUE(WriteMinidump(
templ.c_str(), child, &context, sizeof(context), true,
reinterpret_cast<uintptr_t>(google_breakpad::WriteFile), false));
// Read the minidump. And ensure that thread memory was dumped only for the
// main thread.
Minidump minidump(templ);
ASSERT_TRUE(minidump.Read());
MinidumpThreadList* threads = minidump.GetThreadList();
int threads_with_stacks = 0;
for (unsigned int i = 0; i < threads->thread_count(); ++i) {
MinidumpThread* thread = threads->GetThreadAtIndex(i);
if (thread->GetMemory()) {
++threads_with_stacks;
}
}
#if defined(THREAD_SANITIZER) || defined(ADDRESS_SANITIZER)
ASSERT_GE(threads_with_stacks, 1);
#else
ASSERT_EQ(threads_with_stacks, 1);
#endif
close(fds[1]);
IGNORE_EINTR(waitpid(child, nullptr, 0));
}
// Test that stacks can be sanitized while writing minidumps.
TEST(MinidumpWriterTest, StacksAreSanitizedIfRequested) {
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_group);
}
close(fds[0]);
ExceptionHandler::CrashContext context;
memset(&context, 0, sizeof(context));
ASSERT_EQ(0, getcontext(&context.context));
context.tid = child;
AutoTempDir temp_dir;
string templ = temp_dir.path() + kMDWriterUnitTestFileName;
// pass an invalid principal mapping address, which will force
// WriteMinidump to not dump any thread stacks.
ASSERT_TRUE(WriteMinidump(templ.c_str(), child, &context, sizeof(context),
false, 0, true));
// Read the minidump. And ensure that thread memory contains a defaced value.
Minidump minidump(templ);
ASSERT_TRUE(minidump.Read());
const uintptr_t defaced =
#if defined(__LP64__)
0x0defaced0defaced;
#else
0x0defaced;
#endif
MinidumpThreadList* threads = minidump.GetThreadList();
for (unsigned int i = 0; i < threads->thread_count(); ++i) {
MinidumpThread* thread = threads->GetThreadAtIndex(i);
MinidumpMemoryRegion* mem = thread->GetMemory();
ASSERT_TRUE(mem != nullptr);
uint32_t sz = mem->GetSize();
const uint8_t* data = mem->GetMemory();
ASSERT_TRUE(memmem(data, sz, &defaced, sizeof(defaced)) != nullptr);
}
close(fds[1]);
IGNORE_EINTR(waitpid(child, nullptr, 0));
}
// Test that a binary with a longer-than-usual build id note
// makes its way all the way through to the minidump unscathed.
// The linux_client_unittest is linked with an explicit --build-id
// in Makefile.am.
TEST(MinidumpWriterTest, BuildIDLong) {
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_group);
}
close(fds[0]);
ExceptionHandler::CrashContext context;
memset(&context, 0, sizeof(context));
ASSERT_EQ(0, getcontext(&context.context));
context.tid = child;
AutoTempDir temp_dir;
const string dump_path = temp_dir.path() + kMDWriterUnitTestFileName;
EXPECT_TRUE(WriteMinidump(dump_path.c_str(),
child, &context, sizeof(context)));
close(fds[1]);
// Read the minidump. Load the module list, and ensure that
// the main module has the correct debug id and code id.
Minidump minidump(dump_path);
ASSERT_TRUE(minidump.Read());
MinidumpModuleList* module_list = minidump.GetModuleList();
ASSERT_TRUE(module_list);
const MinidumpModule* module = module_list->GetMainModule();
ASSERT_TRUE(module);
const string module_identifier = "030201000504070608090A0B0C0D0E0F0";
// This is passed explicitly to the linker in Makefile.am
const string build_id =
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f";
EXPECT_EQ(module_identifier, module->debug_identifier());
EXPECT_EQ(build_id, module->code_identifier());
IGNORE_EINTR(waitpid(child, nullptr, 0));
}
// Test that mapping info can be specified, and that it overrides
// existing mappings that are wholly contained within the specified
// range.
TEST(MinidumpWriterTest, MappingInfoContained) {
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 int32_t memory_size = sysconf(_SC_PAGESIZE);
const char* kMemoryName = "a fake module";
const uint8_t kModuleGUID[sizeof(MDGUID)] = {
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
};
const string module_identifier = "33221100554477668899AABBCCDDEEFF0";
// mmap a file
AutoTempDir temp_dir;
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
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,
memory_size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE,
fd,
0));
const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory);
ASSERT_TRUE(memory);
close(fd);
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_group);
}
close(fds[0]);
ExceptionHandler::CrashContext context;
memset(&context, 0, sizeof(context));
context.tid = 1;
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 - memory_size;
info.size = memory_size * 3;
info.offset = 0;
info.exec = false;
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, 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);
ASSERT_TRUE(minidump.Read());
MinidumpModuleList* module_list = minidump.GetModuleList();
ASSERT_TRUE(module_list);
const MinidumpModule* module =
module_list->GetModuleForAddress(kMemoryAddress);
ASSERT_TRUE(module);
EXPECT_EQ(info.start_addr, module->base_address());
EXPECT_EQ(info.size, module->size());
EXPECT_EQ(kMemoryName, module->code_file());
EXPECT_EQ(module_identifier, module->debug_identifier());
close(fds[1]);
IGNORE_EINTR(waitpid(child, nullptr, 0));
}
TEST(MinidumpWriterTest, DeletedBinary) {
const string kNumberOfThreadsArgument = "1";
const string helper_path(GetHelperBinary());
if (helper_path.empty()) {
FAIL() << "Couldn't find helper binary";
exit(1);
}
// Copy binary to a temp file.
AutoTempDir temp_dir;
string binpath = temp_dir.path() + "/linux-dumper-unittest-helper";
ASSERT_TRUE(CopyFile(helper_path, binpath))
<< "Failed to copy " << helper_path << " to " << binpath;
ASSERT_EQ(0, chmod(binpath.c_str(), 0755));
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(binpath.c_str(),
binpath.c_str(),
pipe_fd_string,
kNumberOfThreadsArgument.c_str(),
NULL);
}
close(fds[1]);
// Wait for the child process to signal that it's ready.
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;
const int nr = HANDLE_EINTR(read(fds[0], &junk, sizeof(junk)));
ASSERT_EQ(static_cast<ssize_t>(sizeof(junk)), nr);
close(fds[0]);
// Child is ready now.
// Unlink the test binary.
unlink(binpath.c_str());
ExceptionHandler::CrashContext context;
memset(&context, 0, sizeof(context));
string templ = temp_dir.path() + kMDWriterUnitTestFileName;
// Set a non-zero tid to avoid tripping asserts.
context.tid = child_pid;
ASSERT_TRUE(WriteMinidump(templ.c_str(), child_pid, &context,
sizeof(context)));
kill(child_pid, SIGKILL);
struct stat st;
ASSERT_EQ(0, stat(templ.c_str(), &st));
ASSERT_GT(st.st_size, 0);
Minidump minidump(templ);
ASSERT_TRUE(minidump.Read());
// Check that the main module filename is correct.
MinidumpModuleList* module_list = minidump.GetModuleList();
ASSERT_TRUE(module_list);
const MinidumpModule* module = module_list->GetMainModule();
EXPECT_STREQ(binpath.c_str(), module->code_file().c_str());
// Check that the file ID is correct.
FileID fileid(helper_path.c_str());
PageAllocator allocator;
wasteful_vector<uint8_t> identifier(&allocator, kDefaultBuildIdSize);
EXPECT_TRUE(fileid.ElfFileIdentifier(identifier));
string identifier_string = FileID::ConvertIdentifierToUUIDString(identifier);
string module_identifier(identifier_string);
// Strip out dashes
size_t pos;
while ((pos = module_identifier.find('-')) != string::npos) {
module_identifier.erase(pos, 1);
}
// And append a zero, because module IDs include an "age" field
// which is always zero on Linux.
module_identifier += "0";
EXPECT_EQ(module_identifier, module->debug_identifier());
IGNORE_EINTR(waitpid(child_pid, nullptr, 0));
}
// 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_group);
}
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]);
IGNORE_EINTR(waitpid(child, nullptr, 0));
}
// 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_group);
}
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).
// Try 1MB below the current stack.
uintptr_t invalid_stack_pointer =
reinterpret_cast<uintptr_t>(&context) - 1024*1024;
#if defined(__i386)
context.context.uc_mcontext.gregs[REG_ESP] = invalid_stack_pointer;
#elif defined(__x86_64)
context.context.uc_mcontext.gregs[REG_RSP] = invalid_stack_pointer;
#elif defined(__ARM_EABI__)
context.context.uc_mcontext.arm_sp = invalid_stack_pointer;
#elif defined(__aarch64__)
context.context.uc_mcontext.sp = invalid_stack_pointer;
#elif defined(__mips__)
context.context.uc_mcontext.gregs[MD_CONTEXT_MIPS_REG_SP] =
invalid_stack_pointer;
#elif defined(__riscv)
context.context.uc_mcontext.__gregs[MD_CONTEXT_RISCV_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]);
IGNORE_EINTR(waitpid(child, nullptr, 0));
}
// 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);
IGNORE_EINTR(waitpid(child_pid, nullptr, 0));
}
} // namespace

@ -0,0 +1,65 @@
// Copyright 2011 Google LLC
//
// 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 LLC 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.
// minidump_writer_unittest_utils.cc:
// Shared routines used by unittests under client/linux/minidump_writer.
#include <limits.h>
#include <stdlib.h>
#include "client/linux/minidump_writer/minidump_writer_unittest_utils.h"
#include "common/linux/safe_readlink.h"
#include "common/using_std_string.h"
namespace google_breakpad {
string GetHelperBinary() {
string helper_path;
char* bindir = getenv("bindir");
if (bindir) {
helper_path = string(bindir) + "/";
} else {
// 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;
}
} // namespace google_breakpad

@ -0,0 +1,48 @@
// Copyright 2012 Google LLC
//
// 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 LLC 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.
// minidump_writer_unittest_utils.h:
// Shared routines used by unittests under client/linux/minidump_writer.
#ifndef CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_UNITTEST_UTILS_H_
#define CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_UNITTEST_UTILS_H_
#include <string>
#include "common/using_std_string.h"
namespace google_breakpad {
// 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_

@ -0,0 +1,147 @@
// Copyright 2022 Google LLC
//
// 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 LLC nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <string.h>
#include "client/linux/minidump_writer/pe_file.h"
#include "client/linux/minidump_writer/pe_structs.h"
#include "common/linux/memory_mapped_file.h"
namespace google_breakpad {
PEFileFormat PEFile::TryGetDebugInfo(const char* filename,
PRSDS_DEBUG_FORMAT debug_info) {
MemoryMappedFile mapped_file(filename, 0);
if (!mapped_file.data())
return PEFileFormat::notPeCoff;
const void* base = mapped_file.data();
const size_t file_size = mapped_file.size();
const IMAGE_DOS_HEADER* header =
TryReadStruct<IMAGE_DOS_HEADER>(base, 0, file_size);
if (!header || (header->e_magic != IMAGE_DOS_SIGNATURE)) {
return PEFileFormat::notPeCoff;
}
// NTHeader is at position 'e_lfanew'.
DWORD nt_header_offset = header->e_lfanew;
// First, read a common IMAGE_NT_HEADERS structure. It should contain a
// special flag marking whether PE module is x64 (OptionalHeader.Magic)
// and so-called NT_SIGNATURE in Signature field.
const IMAGE_NT_HEADERS* nt_header =
TryReadStruct<IMAGE_NT_HEADERS>(base, nt_header_offset, file_size);
if (!nt_header || (nt_header->Signature != IMAGE_NT_SIGNATURE)
|| ((nt_header->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC)
&& (nt_header->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC)))
return PEFileFormat::notPeCoff;
bool x64 = nt_header->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC;
WORD sections_number = nt_header->FileHeader.NumberOfSections;
DWORD debug_offset;
DWORD debug_size;
DWORD section_offset;
if (x64) {
const IMAGE_NT_HEADERS64* header_64 =
TryReadStruct<IMAGE_NT_HEADERS64>(base, nt_header_offset, file_size);
if (!header_64)
return PEFileFormat::peWithoutBuildId;
debug_offset =
header_64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG]
.VirtualAddress;
debug_size =
header_64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG]
.Size;
section_offset = nt_header_offset + sizeof(IMAGE_NT_HEADERS64);
} else {
const IMAGE_NT_HEADERS32* header_32 =
TryReadStruct<IMAGE_NT_HEADERS32>(base, nt_header_offset, file_size);
if (!header_32)
return PEFileFormat::peWithoutBuildId;
debug_offset =
header_32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG]
.VirtualAddress;
debug_size =
header_32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG]
.Size;
section_offset = nt_header_offset + sizeof(IMAGE_NT_HEADERS32);
}
DWORD debug_end_pos = debug_offset + debug_size;
while (debug_offset < debug_end_pos) {
for (WORD i = 0; i < sections_number; ++i) {
// Section headers are placed sequentially after the NT_HEADER (32/64).
const IMAGE_SECTION_HEADER* section =
TryReadStruct<IMAGE_SECTION_HEADER>(base, section_offset, file_size);
if (!section)
return PEFileFormat::peWithoutBuildId;
section_offset += sizeof(IMAGE_SECTION_HEADER);
// Current `debug_offset` should be inside a section, stop if we find
// a suitable one (we don't consider any malformed sections here).
if ((section->VirtualAddress <= debug_offset) &&
(debug_offset < section->VirtualAddress + section->SizeOfRawData)) {
DWORD offset =
section->PointerToRawData + debug_offset - section->VirtualAddress;
// Go to the position of current ImageDebugDirectory (offset).
const IMAGE_DEBUG_DIRECTORY* debug_directory =
TryReadStruct<IMAGE_DEBUG_DIRECTORY>(base, offset, file_size);
if (!debug_directory)
return PEFileFormat::peWithoutBuildId;
// Process ImageDebugDirectory with CodeViewRecord type and skip
// all others.
if (debug_directory->Type == IMAGE_DEBUG_TYPE_CODEVIEW) {
DWORD debug_directory_size = debug_directory->SizeOfData;
if (debug_directory_size < sizeof(RSDS_DEBUG_FORMAT))
// RSDS section is malformed.
return PEFileFormat::peWithoutBuildId;
// Go to the position of current ImageDebugDirectory Raw Data
// (debug_directory->PointerToRawData) and read the RSDS section.
const RSDS_DEBUG_FORMAT* rsds =
TryReadStruct<RSDS_DEBUG_FORMAT>(
base, debug_directory->PointerToRawData, file_size);
if (!rsds)
return PEFileFormat::peWithoutBuildId;
memcpy(debug_info->guid, rsds->guid, sizeof(rsds->guid));
memcpy(debug_info->age, rsds->age, sizeof(rsds->age));
return PEFileFormat::peWithBuildId;
}
break;
}
}
debug_offset += sizeof(IMAGE_DEBUG_DIRECTORY);
}
return PEFileFormat::peWithoutBuildId;
}
} // namespace google_breakpad

@ -0,0 +1,76 @@
// Copyright 2022 Google LLC
//
// 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 LLC 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_PE_FILE_H_
#define CLIENT_LINUX_MINIDUMP_WRITER_PE_FILE_H_
#include "client/linux/minidump_writer/pe_structs.h"
namespace google_breakpad {
typedef enum {
notPeCoff = 0,
peWithoutBuildId = 1,
peWithBuildId = 2
} PEFileFormat;
class PEFile {
public:
/**
* Attempts to parse RSDS_DEBUG_FORMAT record from a PE (Portable
* Executable) file. To do this we check whether the loaded file is a PE
* file, and if it is - try to find IMAGE_DEBUG_DIRECTORY structure with
* its type set to IMAGE_DEBUG_TYPE_CODEVIEW.
*
* @param filename Filename for the module to parse.
* @param debug_info RSDS_DEBUG_FORMAT struct to be populated with PE debug
* info (GUID and age).
* @return
* notPeCoff: not PE/COFF file;
* peWithoutBuildId: a PE/COFF file but build-id is not set;
* peWithBuildId: a PE/COFF file and build-id is set.
*/
static PEFileFormat TryGetDebugInfo(const char* filename,
PRSDS_DEBUG_FORMAT debug_info);
private:
template <class TStruct>
static const TStruct* TryReadStruct(const void* base,
const DWORD position,
const size_t file_size) {
if (position + sizeof(TStruct) >= file_size){
return nullptr;
}
const void* ptr = static_cast<const char*>(base) + position;
return reinterpret_cast<const TStruct*>(ptr);
}
};
} // namespace google_breakpad
#endif // CLIENT_LINUX_MINIDUMP_WRITER_PE_FILE_H_

@ -0,0 +1,225 @@
// Copyright 2022 Google LLC
//
// 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 LLC 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_PE_STRUCTS_H_
#define CLIENT_LINUX_MINIDUMP_WRITER_PE_STRUCTS_H_
#include <cstdint>
namespace google_breakpad {
typedef uint8_t BYTE;
typedef uint16_t WORD;
typedef uint32_t DWORD;
typedef uint64_t ULONGLONG;
#define IMAGE_NT_OPTIONAL_HDR32_MAGIC 0x10b
#define IMAGE_NT_OPTIONAL_HDR64_MAGIC 0x20b
#define IMAGE_DEBUG_TYPE_CODEVIEW 2
#define IMAGE_DOS_SIGNATURE 0x5A4D // MZ
#define IMAGE_NT_SIGNATURE 0x00004550 // PE00
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
#define IMAGE_DIRECTORY_ENTRY_DEBUG 6
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header
WORD e_magic; // Magic number
WORD e_cblp; // Bytes on last page of file
WORD e_cp; // Pages in file
WORD e_crlc; // Relocations
WORD e_cparhdr; // Size of header in paragraphs
WORD e_minalloc; // Minimum extra paragraphs needed
WORD e_maxalloc; // Maximum extra paragraphs needed
WORD e_ss; // Initial (relative) SS value
WORD e_sp; // Initial SP value
WORD e_csum; // Checksum
WORD e_ip; // Initial IP value
WORD e_cs; // Initial (relative) CS value
WORD e_lfarlc; // File address of relocation table
WORD e_ovno; // Overlay number
WORD e_res[4]; // Reserved words
WORD e_oemid; // OEM identifier (for e_oeminfo)
WORD e_oeminfo; // OEM information; e_oemid specific
WORD e_res2[10]; // Reserved words
DWORD e_lfanew; // File address of new exe header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
typedef struct _IMAGE_FILE_HEADER {
WORD Machine;
WORD NumberOfSections;
DWORD TimeDateStamp;
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader;
WORD Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
typedef struct _IMAGE_DEBUG_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Type;
DWORD SizeOfData;
DWORD AddressOfRawData;
DWORD PointerToRawData;
} IMAGE_DEBUG_DIRECTORY, *PIMAGE_DEBUG_DIRECTORY;
typedef struct _IMAGE_OPTIONAL_HEADER64 {
//
// Standard fields - Magic.
//
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
//
// NT additional fields.
//
ULONGLONG ImageBase;
DWORD SectionAlignment;
DWORD FileAlignment;
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage;
DWORD SizeOfHeaders;
DWORD CheckSum;
WORD Subsystem;
WORD DllCharacteristics;
ULONGLONG SizeOfStackReserve;
ULONGLONG SizeOfStackCommit;
ULONGLONG SizeOfHeapReserve;
ULONGLONG SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;
typedef struct _IMAGE_OPTIONAL_HEADER {
//
// Standard fields.
//
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD BaseOfData;
//
// NT additional fields.
//
DWORD ImageBase;
DWORD SectionAlignment;
DWORD FileAlignment;
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage;
DWORD SizeOfHeaders;
DWORD CheckSum;
WORD Subsystem;
WORD DllCharacteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
typedef struct _IMAGE_NT_HEADERS64 {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;
typedef struct _IMAGE_NT_HEADERS32 {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS;
#define IMAGE_SIZEOF_SHORT_NAME 8
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
union {
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc;
DWORD VirtualAddress;
DWORD SizeOfRawData;
DWORD PointerToRawData;
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
typedef struct _RSDS_DEBUG_FORMAT {
DWORD signature;
BYTE guid[16];
BYTE age[4];
char pdbpath[1];
} RSDS_DEBUG_FORMAT, *PRSDS_DEBUG_FORMAT;
} // namespace google_breakpad
#endif // CLIENT_LINUX_MINIDUMP_WRITER_PE_STRUCTS_H_

@ -0,0 +1,129 @@
// Copyright 2013 Google LLC
//
// 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 LLC 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_

@ -0,0 +1,198 @@
// Copyright 2013 Google LLC
//
// 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 LLC 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));
}

@ -0,0 +1,104 @@
// Copyright 2009 Google LLC
//
// 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 LLC 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 "common/linux/google_crashdump_uploader.h"
#include <string>
#include <iostream>
#include <gflags/gflags.h>
#include <glog/logging.h>
#include "common/using_std_string.h"
DEFINE_string(crash_server, "https://clients2.google.com/cr",
"The crash server to upload minidumps to.");
DEFINE_string(product_name, "",
"The product name that the minidump corresponds to.");
DEFINE_string(product_version, "",
"The version of the product that produced the minidump.");
DEFINE_string(client_id, "",
"The client GUID");
DEFINE_string(minidump_path, "",
"The path of the minidump file.");
DEFINE_string(ptime, "",
"The process uptime in milliseconds.");
DEFINE_string(ctime, "",
"The cumulative process uptime in milliseconds.");
DEFINE_string(email, "",
"The user's email address.");
DEFINE_string(comments, "",
"Extra user comments");
DEFINE_string(proxy_host, "",
"Proxy host");
DEFINE_string(proxy_userpasswd, "",
"Proxy username/password in user:pass format.");
bool CheckForRequiredFlagsOrDie() {
string error_text = "";
if (FLAGS_product_name.empty()) {
error_text.append("\nProduct name must be specified.");
}
if (FLAGS_product_version.empty()) {
error_text.append("\nProduct version must be specified.");
}
if (FLAGS_client_id.empty()) {
error_text.append("\nClient ID must be specified.");
}
if (FLAGS_minidump_path.empty()) {
error_text.append("\nMinidump pathname must be specified.");
}
if (!error_text.empty()) {
std::cout << error_text;
return false;
}
return true;
}
int main(int argc, char* argv[]) {
google::InitGoogleLogging(argv[0]);
google::ParseCommandLineFlags(&argc, &argv, true);
if (!CheckForRequiredFlagsOrDie()) {
return 1;
}
google_breakpad::GoogleCrashdumpUploader g(FLAGS_product_name,
FLAGS_product_version,
FLAGS_client_id,
FLAGS_ptime,
FLAGS_ctime,
FLAGS_email,
FLAGS_comments,
FLAGS_minidump_path,
FLAGS_crash_server,
FLAGS_proxy_host,
FLAGS_proxy_userpasswd);
g.Upload(NULL, NULL, NULL);
}

@ -0,0 +1,284 @@
// Copyright 2006 Google LLC
//
// 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 LLC 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. Prompt the user (using CFUserNotification)
// 3. Invoke a command line reporting tool to send the minidump to a
// server
//
// By specifying parameters to the BreakpadCreate function, you can
// modify the default behavior to suit your needs and wants and
// desires.
// A service name associated with the original bootstrap parent port, saved in
// OnDemandServer and restored in Inspector.
#define BREAKPAD_BOOTSTRAP_PARENT_PORT "com.Breakpad.BootstrapParent"
typedef void* BreakpadRef;
#ifdef __cplusplus
extern "C" {
#endif
#include <CoreFoundation/CoreFoundation.h>
#include <Foundation/Foundation.h>
#include "BreakpadDefines.h"
// Optional user-defined function to dec 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_REPORT_INTERVAL # of seconds between sending
// reports. If an additional report is
// generated within this time, it will
// be ignored. Default: 3600sec.
// Specify 0 to send all reports.
//
// BREAKPAD_SKIP_CONFIRM If true, the reporter will send the report
// without any user intervention.
// Defaults to NO
//
// BREAKPAD_CONFIRM_TIMEOUT Number of seconds before the upload
// confirmation dialog will be automatically
// dismissed (cancelling the upload).
// Default: 300 seconds (min of 60).
// Specify 0 to prevent timeout.
//
// BREAKPAD_SEND_AND_EXIT If true, the handler will exit after sending.
// This will prevent any other handler (e.g.,
// CrashReporter) from getting the crash.
// Defaults TO YES
//
// BREAKPAD_DUMP_DIRECTORY The directory to store crash-dumps
// in. By default, we use
// ~/Library/Breakpad/<BREAKPAD_PRODUCT>
// The path you specify here is tilde-expanded.
//
// BREAKPAD_INSPECTOR_LOCATION The full path to the Inspector executable.
// Defaults to <Framework resources>/Inspector
//
// BREAKPAD_REPORTER_EXE_LOCATION The full path to the Reporter/sender
// executable.
// Default:
// <Framework Resources>/crash_report_sender.app
//
// BREAKPAD_LOGFILES Indicates an array of log file paths that
// should be uploaded at crash time.
//
// BREAKPAD_REQUEST_COMMENTS If true, the message dialog will have a
// text box for the user to enter comments.
// Default: NO
//
// BREAKPAD_REQUEST_EMAIL If true and BREAKPAD_REQUEST_COMMENTS is also
// true, the message dialog will have a text
// box for the user to enter their email address.
// Default: NO
//
// 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.
//
// BREAKPAD_IN_PROCESS A boolean NSNumber value. If YES, Breakpad
// will write the dump file in-process and then
// launch the reporter executable as a child
// process.
//=============================================================================
// 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 BREAKPAD_SKIP_CONFIRM and BREAKPAD_SEND_AND_EXIT default
// values are NO and YES. However, they can be controlled by setting their
// values in a user or global plist.
//
// It's easiest to use Breakpad via the Framework, but if you're compiling the
// code in directly, BREAKPAD_INSPECTOR_LOCATION and
// BREAKPAD_REPORTER_EXE_LOCATION allow you to specify custom paths
// to the helper executables.
//
//=============================================================================
// 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_LOGFILE_KEY_PREFIX Used to find out which parameters in the
// parameter dictionary correspond to log
// file paths.
//
// 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.
//
// BREAKPAD_ON_DEMAND Used internally to indicate to the
// Reporter that we're sending on-demand,
// not as result of a crash.
//
// BREAKPAD_COMMENTS The text the user provided as comments.
// Only used in crash_report_sender.
// 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);
// Clients may set an optional callback which gets called when a crash
// occurs. The callback function should return |true| if we should
// handle the crash, generate a crash report, etc. or |false| if we
// should ignore it and forward the crash (normally to CrashReporter).
// Context is a pointer to arbitrary data to make the callback with.
void BreakpadSetFilterCallback(BreakpadRef ref,
BreakpadFilterCallback callback,
void* context);
// 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);
// Add a log file for Breakpad to read and send upon crash dump
void BreakpadAddLogFile(BreakpadRef ref, NSString* logPathname);
// Generate a minidump and send
void BreakpadGenerateAndSendReport(BreakpadRef ref);
#ifdef __cplusplus
}
#endif

File diff suppressed because it is too large Load Diff

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

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>com.googlecode.google-breakpad</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>

@ -0,0 +1,144 @@
// Copyright 2007 Google LLC
//
// 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 LLC 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 <mach/mach.h>
#include <servers/bootstrap.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
//==============================================================================
// class OnDemandServer :
// A basic on-demand server launcher supporting a single named service port
//
// Example Usage :
//
// kern_return_t result;
// OnDemandServer* server = OnDemandServer::Create("/tmp/myserver",
// "com.MyCompany.MyServiceName",
// true,
// &result);
//
// if (server) {
// server->LaunchOnDemand();
// mach_port_t service_port = GetServicePort();
//
// // Send a mach message to service_port and "myserver" will be launched
// }
//
//
// ---- Now in the server code ----
//
// // "myserver" should get the service port and read the message which
// // launched it:
// mach_port_t service_rcv_port_;
// kern_return_t kr = bootstrap_check_in(bootstrap_port,
// "com.MyCompany.MyServiceName",
// &service_rcv_port_);
// // mach_msg() read service_rcv_port_ ....
//
// ....
//
// // Later "myserver" may want to unregister the service if it doesn't
// // want its bootstrap service to stick around after it exits.
//
// // DO NOT use mach_port_deallocate() here -- it will fail and the
// // following bootstrap_register() will also fail leaving our service
// // name hanging around forever (until reboot)
// kern_return_t kr = mach_port_destroy(mach_task_self(), service_rcv_port_);
//
// kr = bootstrap_register(bootstrap_port,
// "com.MyCompany.MyServiceName",
// MACH_PORT_NULL);
class OnDemandServer {
public:
// must call Initialize() to be useful
OnDemandServer()
: server_port_(MACH_PORT_NULL),
service_port_(MACH_PORT_NULL),
unregister_on_cleanup_(true) {
}
// Creates the bootstrap server and service
kern_return_t Initialize(const char* server_command,
const char* service_name,
bool unregister_on_cleanup);
// Returns an OnDemandServer object if successful, or NULL if there's
// an error. The error result will be returned in out_result.
//
// server_command : the full path name including optional command-line
// arguments to the executable representing the server
//
// service_name : represents service name
// something like "com.company.ServiceName"
//
// unregister_on_cleanup : if true, unregisters the service name
// when the OnDemandServer is deleted -- unregistering will
// ONLY be possible if LaunchOnDemand() has NOT been called.
// If false, then the service will continue to be registered
// even after the current process quits.
//
// out_result : if non-NULL, returns the result
// this value will be KERN_SUCCESS if Create() returns non-NULL
//
static OnDemandServer* Create(const char *server_command,
const char* service_name,
bool unregister_on_cleanup,
kern_return_t* out_result);
// Cleans up and if LaunchOnDemand() has not yet been called then
// the bootstrap service will be unregistered.
~OnDemandServer();
// This must be called if we intend to commit to launching the server
// by sending a mach message to our service port. Do not call it otherwise
// or it will be difficult (impossible?) to unregister the service name.
void LaunchOnDemand();
// This is the port we need to send a mach message to after calling
// LaunchOnDemand(). Sending a message causing an immediate launch
// of the server
mach_port_t GetServicePort() { return service_port_; }
private:
// Disallow copy constructor
OnDemandServer(const OnDemandServer&);
// Cleans up and if LaunchOnDemand() has not yet been called then
// the bootstrap service will be unregistered.
void Unregister();
name_t service_name_;
mach_port_t server_port_;
mach_port_t service_port_;
bool unregister_on_cleanup_;
};

@ -0,0 +1,189 @@
// Copyright 2007 Google LLC
//
// 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 LLC 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 "OnDemandServer.h"
#import "Breakpad.h"
#include "common/mac/bootstrap_compat.h"
#if DEBUG
#define PRINT_MACH_RESULT(result_, message_) \
printf(message_"%s (%d)\n", mach_error_string(result_), result_ );
#if defined(MAC_OS_X_VERSION_10_5) && \
MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
#define PRINT_BOOTSTRAP_RESULT(result_, message_) \
printf(message_"%s (%d)\n", bootstrap_strerror(result_), result_ );
#else
#define PRINT_BOOTSTRAP_RESULT(result_, message_) \
PRINT_MACH_RESULT(result_, message_)
#endif
#else
#define PRINT_MACH_RESULT(result_, message_)
#define PRINT_BOOTSTRAP_RESULT(result_, message_)
#endif
//==============================================================================
OnDemandServer* OnDemandServer::Create(const char* server_command,
const char* service_name,
bool unregister_on_cleanup,
kern_return_t* out_result) {
OnDemandServer* server = new OnDemandServer();
if (!server) return NULL;
kern_return_t result = server->Initialize(server_command,
service_name,
unregister_on_cleanup);
if (out_result) {
*out_result = result;
}
if (result == KERN_SUCCESS) {
return server;
}
delete server;
return NULL;
}
//==============================================================================
kern_return_t OnDemandServer::Initialize(const char* server_command,
const char* service_name,
bool unregister_on_cleanup) {
unregister_on_cleanup_ = unregister_on_cleanup;
mach_port_t self_task = mach_task_self();
mach_port_t self_bootstrap_port;
kern_return_t kr = task_get_bootstrap_port(self_task, &self_bootstrap_port);
if (kr != KERN_SUCCESS) {
PRINT_MACH_RESULT(kr, "task_get_bootstrap_port(): ");
return kr;
}
mach_port_t bootstrap_subset_port;
kr = bootstrap_subset(self_bootstrap_port, self_task, &bootstrap_subset_port);
if (kr != BOOTSTRAP_SUCCESS) {
PRINT_BOOTSTRAP_RESULT(kr, "bootstrap_subset(): ");
return kr;
}
// The inspector will be invoked with its bootstrap port set to the subset,
// but the sender will need access to the original bootstrap port. Although
// the original port is the subset's parent, bootstrap_parent can't be used
// because it requires extra privileges. Stash the original bootstrap port
// in the subset by registering it under a known name. The inspector will
// recover this port and set it as its own bootstrap port in Inspector.mm
// Inspector::ResetBootstrapPort.
kr = breakpad::BootstrapRegister(
bootstrap_subset_port,
const_cast<char*>(BREAKPAD_BOOTSTRAP_PARENT_PORT),
self_bootstrap_port);
if (kr != BOOTSTRAP_SUCCESS) {
PRINT_BOOTSTRAP_RESULT(kr, "bootstrap_register(): ");
return kr;
}
kr = bootstrap_create_server(bootstrap_subset_port,
const_cast<char*>(server_command),
geteuid(), // server uid
true,
&server_port_);
if (kr != BOOTSTRAP_SUCCESS) {
PRINT_BOOTSTRAP_RESULT(kr, "bootstrap_create_server(): ");
return kr;
}
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(): ");
// perhaps the service has already been created - try to look it up
kr = bootstrap_look_up(self_bootstrap_port, (char*)service_name,
&service_port_);
if (kr != BOOTSTRAP_SUCCESS) {
PRINT_BOOTSTRAP_RESULT(kr, "bootstrap_look_up(): ");
Unregister(); // clean up server port
return kr;
}
}
return KERN_SUCCESS;
}
//==============================================================================
OnDemandServer::~OnDemandServer() {
if (unregister_on_cleanup_) {
Unregister();
}
}
//==============================================================================
void OnDemandServer::LaunchOnDemand() {
// We need to do this, since the launched server is another process
// and holding on to this port delays launching until the current process
// exits!
mach_port_deallocate(mach_task_self(), server_port_);
server_port_ = MACH_PORT_DEAD;
// Now, the service is still registered and all we need to do is send
// a mach message to the service port in order to launch the server.
}
//==============================================================================
void OnDemandServer::Unregister() {
if (service_port_ != MACH_PORT_NULL) {
mach_port_deallocate(mach_task_self(), service_port_);
service_port_ = MACH_PORT_NULL;
}
if (server_port_ != MACH_PORT_NULL) {
// unregister the service
kern_return_t kr = breakpad::BootstrapRegister(server_port_,
service_name_,
MACH_PORT_NULL);
if (kr != KERN_SUCCESS) {
PRINT_MACH_RESULT(kr, "Breakpad UNREGISTER : bootstrap_register() : ");
}
mach_port_deallocate(mach_task_self(), server_port_);
server_port_ = MACH_PORT_NULL;
}
}

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>com.yourcompany.${PRODUCT_NAME:identifier}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
</dict>
</plist>

@ -0,0 +1,82 @@
// Copyright 2011 Google LLC
//
// 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 LLC 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.
//
// Utility class that can persist a SimpleStringDictionary to disk.
#import <Foundation/Foundation.h>
#include "common/simple_string_dictionary.h"
namespace google_breakpad {
BOOL EnsureDirectoryPathExists(NSString* dirPath);
//=============================================================================
class ConfigFile {
public:
ConfigFile() {
config_file_ = -1;
config_file_path_[0] = 0;
has_created_file_ = false;
}
~ConfigFile() {
}
void WriteFile(const char* directory,
const SimpleStringDictionary* configurationParameters,
const char* dump_dir,
const char* minidump_id);
const char* GetFilePath() { return config_file_path_; }
void Unlink() {
if (config_file_ != -1)
unlink(config_file_path_);
config_file_ = -1;
}
private:
BOOL WriteData(const void* data, size_t length);
BOOL AppendConfigData(const char* key,
const void* data,
size_t length);
BOOL AppendConfigString(const char* key,
const char* value);
BOOL AppendCrashTimeParameters(const char* processStartTimeString);
int config_file_; // descriptor for config file
char config_file_path_[PATH_MAX]; // Path to configuration file
bool has_created_file_;
};
} // namespace google_breakpad

@ -0,0 +1,167 @@
// Copyright 2011 Google LLC
//
// 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 LLC 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.
//
// Utility class that can persist a SimpleStringDictionary to disk.
#import "client/mac/crash_generation/ConfigFile.h"
#import <Foundation/Foundation.h>
#include <stdio.h>
#include <sys/time.h>
#import "client/apple/Framework/BreakpadDefines.h"
#import "common/mac/GTMDefines.h"
namespace google_breakpad {
//=============================================================================
BOOL EnsureDirectoryPathExists(NSString* dirPath) {
NSFileManager* mgr = [NSFileManager defaultManager];
NSDictionary* attrs =
[NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedLong:0750]
forKey:NSFilePosixPermissions];
return [mgr createDirectoryAtPath:dirPath
withIntermediateDirectories:YES
attributes:attrs
error:nil];
}
//=============================================================================
BOOL ConfigFile::WriteData(const void* data, size_t length) {
size_t result = write(config_file_, data, length);
return result == length;
}
//=============================================================================
BOOL ConfigFile::AppendConfigData(const char* key,
const void* data, size_t length) {
assert(config_file_ != -1);
if (!key) {
return NO;
}
if (!data) {
return NO;
}
// Write the key, \n, length of data (ascii integer), \n, data
char buffer[16];
char nl = '\n';
BOOL result = WriteData(key, strlen(key));
snprintf(buffer, sizeof(buffer) - 1, "\n%lu\n", length);
result &= WriteData(buffer, strlen(buffer));
result &= WriteData(data, length);
result &= WriteData(&nl, 1);
return result;
}
//=============================================================================
BOOL ConfigFile::AppendConfigString(const char* key,
const char* value) {
return AppendConfigData(key, value, strlen(value));
}
//=============================================================================
BOOL ConfigFile::AppendCrashTimeParameters(const char* processStartTimeString) {
// Set process uptime parameter
struct timeval tv;
gettimeofday(&tv, NULL);
char processUptimeString[32], processCrashtimeString[32];
// Set up time if we've received the start time.
if (processStartTimeString) {
time_t processStartTime = strtol(processStartTimeString, NULL, 10);
time_t processUptime = tv.tv_sec - processStartTime;
// Store the uptime in milliseconds.
snprintf(processUptimeString, sizeof(processUptimeString), "%llu",
static_cast<unsigned long long int>(processUptime) * 1000);
if (!AppendConfigString(BREAKPAD_PROCESS_UP_TIME, processUptimeString))
return false;
}
snprintf(processCrashtimeString, sizeof(processCrashtimeString), "%llu",
static_cast<unsigned long long int>(tv.tv_sec));
return AppendConfigString(BREAKPAD_PROCESS_CRASH_TIME,
processCrashtimeString);
}
//=============================================================================
void ConfigFile::WriteFile(const char* directory,
const SimpleStringDictionary* configurationParameters,
const char* dump_dir,
const char* minidump_id) {
assert(config_file_ == -1);
// Open and write out configuration file preamble
if (directory) {
snprintf(config_file_path_, sizeof(config_file_path_), "%s/Config-XXXXXX",
directory);
} else {
strlcpy(config_file_path_, "/tmp/Config-XXXXXX",
sizeof(config_file_path_));
}
config_file_ = mkstemp(config_file_path_);
if (config_file_ == -1) {
return;
}
has_created_file_ = true;
// Add the minidump dir
AppendConfigString(kReporterMinidumpDirectoryKey, dump_dir);
AppendConfigString(kReporterMinidumpIDKey, minidump_id);
// Write out the configuration parameters
BOOL result = YES;
const SimpleStringDictionary& dictionary = *configurationParameters;
const SimpleStringDictionary::Entry* entry = NULL;
SimpleStringDictionary::Iterator iter(dictionary);
while ((entry = iter.Next())) {
result = AppendConfigString(entry->key, entry->value);
if (!result)
break;
}
AppendCrashTimeParameters(
configurationParameters->GetValueForKey(BREAKPAD_PROCESS_START_TIME));
close(config_file_);
config_file_ = -1;
}
} // namespace google_breakpad

@ -0,0 +1,161 @@
// Copyright 2007 Google LLC
//
// 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 LLC 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.
//
// Interface file between the Breakpad.framework and
// the Inspector process.
#include "common/simple_string_dictionary.h"
#import <Foundation/Foundation.h>
#include <mach/mach.h>
#import "client/mac/crash_generation/ConfigFile.h"
#import "client/mac/handler/minidump_generator.h"
// Types of mach messsages (message IDs)
enum {
kMsgType_InspectorInitialInfo = 0, // data is InspectorInfo
kMsgType_InspectorKeyValuePair = 1, // data is KeyValueMessageData
kMsgType_InspectorAcknowledgement = 2 // no data sent
};
// Initial information sent from the crashed process by
// Breakpad.framework to the Inspector process
// The mach message with this struct as data will also include
// several descriptors for sending mach port rights to the crashed
// task, etc.
struct InspectorInfo {
int exception_type;
int exception_code;
int exception_subcode;
unsigned int parameter_count; // key-value pairs
};
// Key/value message data to be sent to the Inspector
struct KeyValueMessageData {
public:
KeyValueMessageData() {}
explicit KeyValueMessageData(
const google_breakpad::SimpleStringDictionary::Entry& inEntry) {
strlcpy(key, inEntry.key, sizeof(key) );
strlcpy(value, inEntry.value, sizeof(value) );
}
char key[google_breakpad::SimpleStringDictionary::key_size];
char value[google_breakpad::SimpleStringDictionary::value_size];
};
using std::string;
using google_breakpad::MinidumpGenerator;
namespace google_breakpad {
//=============================================================================
class MinidumpLocation {
public:
MinidumpLocation(NSString* minidumpDir) {
// Ensure that the path exists. Fallback to /tmp if unable to locate path.
assert(minidumpDir);
if (!EnsureDirectoryPathExists(minidumpDir)) {
minidumpDir = @"/tmp";
}
strlcpy(minidump_dir_path_, [minidumpDir fileSystemRepresentation],
sizeof(minidump_dir_path_));
// now generate a unique ID
string dump_path(minidump_dir_path_);
string next_minidump_id;
string next_minidump_path_ =
(MinidumpGenerator::UniqueNameInDirectory(dump_path, &next_minidump_id));
strlcpy(minidump_id_, next_minidump_id.c_str(), sizeof(minidump_id_));
}
const char* GetPath() { return minidump_dir_path_; }
const char* GetID() { return minidump_id_; }
private:
char minidump_dir_path_[PATH_MAX]; // Path to minidump directory
char minidump_id_[128];
};
//=============================================================================
class Inspector {
public:
Inspector() {}
// given a bootstrap service name, receives mach messages
// from a crashed process, then inspects it, creates a minidump file
// and asks the user if he wants to upload it to a server.
void Inspect(const char* receive_port_name);
private:
// The Inspector is invoked with its bootstrap port set to the bootstrap
// subset established in OnDemandServer.mm OnDemandServer::Initialize.
// For proper communication with the system, the sender (which will inherit
// the Inspector's bootstrap port) needs the per-session bootstrap namespace
// available directly in its bootstrap port. OnDemandServer stashed this
// port into the subset namespace under a special name. ResetBootstrapPort
// recovers this port and switches this task to use it as its own bootstrap
// (ensuring that children like the sender will inherit it), and saves the
// subset in bootstrap_subset_port_ for use by ServiceCheckIn and
// ServiceCheckOut.
kern_return_t ResetBootstrapPort();
kern_return_t ServiceCheckIn(const char* receive_port_name);
kern_return_t ServiceCheckOut(const char* receive_port_name);
kern_return_t ReadMessages();
bool InspectTask();
kern_return_t SendAcknowledgement();
// The bootstrap port in which the inspector is registered and into which it
// must check in.
mach_port_t bootstrap_subset_port_;
mach_port_t service_rcv_port_;
int exception_type_;
int exception_code_;
int exception_subcode_;
mach_port_t remote_task_;
mach_port_t crashing_thread_;
mach_port_t handler_thread_;
mach_port_t ack_port_;
SimpleStringDictionary config_params_;
ConfigFile config_file_;
};
} // namespace google_breakpad

@ -0,0 +1,361 @@
// Copyright 2007 Google LLC
//
// 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 LLC 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.
//
// Utility that can inspect another process and write a crash dump
#include <cstdio>
#include <iostream>
#include <servers/bootstrap.h>
#include <stdio.h>
#include <string.h>
#include <string>
#import "client/mac/crash_generation/Inspector.h"
#import "client/mac/Framework/Breakpad.h"
#import "client/mac/handler/minidump_generator.h"
#import "common/mac/MachIPC.h"
#include "common/mac/bootstrap_compat.h"
#include "common/mac/launch_reporter.h"
#import "GTMDefines.h"
#import <Foundation/Foundation.h>
namespace google_breakpad {
//=============================================================================
void Inspector::Inspect(const char* receive_port_name) {
kern_return_t result = ResetBootstrapPort();
if (result != KERN_SUCCESS) {
return;
}
result = ServiceCheckIn(receive_port_name);
if (result == KERN_SUCCESS) {
result = ReadMessages();
if (result == KERN_SUCCESS) {
// Inspect the task and write a minidump file.
bool wrote_minidump = InspectTask();
// Send acknowledgement to the crashed process that the inspection
// has finished. It will then be able to cleanly exit.
// The return value is ignored because failure isn't fatal. If the process
// didn't get the message there's nothing we can do, and we still want to
// send the report.
SendAcknowledgement();
if (wrote_minidump) {
// Ask the user if he wants to upload the crash report to a server,
// and do so if he agrees.
LaunchReporter(
config_params_.GetValueForKey(BREAKPAD_REPORTER_EXE_LOCATION),
config_file_.GetFilePath());
} else {
fprintf(stderr, "Inspection of crashed process failed\n");
}
// Now that we're done reading messages, cleanup the service, but only
// if there was an actual exception
// Otherwise, it means the dump was generated on demand and the process
// lives on, and we might be needed again in the future.
if (exception_code_) {
ServiceCheckOut(receive_port_name);
}
} else {
PRINT_MACH_RESULT(result, "Inspector: WaitForMessage()");
}
}
}
//=============================================================================
kern_return_t Inspector::ResetBootstrapPort() {
// A reasonable default, in case anything fails.
bootstrap_subset_port_ = bootstrap_port;
mach_port_t self_task = mach_task_self();
kern_return_t kr = task_get_bootstrap_port(self_task,
&bootstrap_subset_port_);
if (kr != KERN_SUCCESS) {
NSLog(@"ResetBootstrapPort: task_get_bootstrap_port failed: %s (%d)",
mach_error_string(kr), kr);
return kr;
}
mach_port_t bootstrap_parent_port;
kr = bootstrap_look_up(bootstrap_subset_port_,
const_cast<char*>(BREAKPAD_BOOTSTRAP_PARENT_PORT),
&bootstrap_parent_port);
if (kr != BOOTSTRAP_SUCCESS) {
NSLog(@"ResetBootstrapPort: bootstrap_look_up failed: %s (%d)",
#if defined(MAC_OS_X_VERSION_10_5) && \
MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
bootstrap_strerror(kr),
#else
mach_error_string(kr),
#endif
kr);
return kr;
}
kr = task_set_bootstrap_port(self_task, bootstrap_parent_port);
if (kr != KERN_SUCCESS) {
NSLog(@"ResetBootstrapPort: task_set_bootstrap_port failed: %s (%d)",
mach_error_string(kr), kr);
return kr;
}
// Some things access the bootstrap port through this global variable
// instead of calling task_get_bootstrap_port.
bootstrap_port = bootstrap_parent_port;
return KERN_SUCCESS;
}
//=============================================================================
kern_return_t Inspector::ServiceCheckIn(const char* receive_port_name) {
// We need to get the mach port representing this service, so we can
// get information from the crashed process.
kern_return_t kr = bootstrap_check_in(bootstrap_subset_port_,
(char*)receive_port_name,
&service_rcv_port_);
if (kr != KERN_SUCCESS) {
#if VERBOSE
PRINT_MACH_RESULT(kr, "Inspector: bootstrap_check_in()");
#endif
}
return kr;
}
//=============================================================================
kern_return_t Inspector::ServiceCheckOut(const char* receive_port_name) {
// We're done receiving mach messages from the crashed process,
// so clean up a bit.
kern_return_t kr;
// DO NOT use mach_port_deallocate() here -- it will fail and the
// following bootstrap_register() will also fail leaving our service
// name hanging around forever (until reboot)
kr = mach_port_destroy(mach_task_self(), service_rcv_port_);
if (kr != KERN_SUCCESS) {
PRINT_MACH_RESULT(kr,
"Inspector: UNREGISTERING: service_rcv_port mach_port_deallocate()");
return kr;
}
// Unregister the service associated with the receive port.
kr = breakpad::BootstrapRegister(bootstrap_subset_port_,
(char*)receive_port_name,
MACH_PORT_NULL);
if (kr != KERN_SUCCESS) {
PRINT_MACH_RESULT(kr, "Inspector: UNREGISTERING: bootstrap_register()");
}
return kr;
}
//=============================================================================
kern_return_t Inspector::ReadMessages() {
// Wait for an initial message from the crashed process containing basic
// information about the crash.
ReceivePort receive_port(service_rcv_port_);
MachReceiveMessage message;
kern_return_t result = receive_port.WaitForMessage(&message, 1000);
if (result == KERN_SUCCESS) {
InspectorInfo& info = (InspectorInfo&)*message.GetData();
exception_type_ = info.exception_type;
exception_code_ = info.exception_code;
exception_subcode_ = info.exception_subcode;
#if VERBOSE
printf("message ID = %d\n", message.GetMessageID());
#endif
remote_task_ = message.GetTranslatedPort(0);
crashing_thread_ = message.GetTranslatedPort(1);
handler_thread_ = message.GetTranslatedPort(2);
ack_port_ = message.GetTranslatedPort(3);
#if VERBOSE
printf("exception_type = %d\n", exception_type_);
printf("exception_code = %d\n", exception_code_);
printf("exception_subcode = %d\n", exception_subcode_);
printf("remote_task = %d\n", remote_task_);
printf("crashing_thread = %d\n", crashing_thread_);
printf("handler_thread = %d\n", handler_thread_);
printf("ack_port_ = %d\n", ack_port_);
printf("parameter count = %d\n", info.parameter_count);
#endif
// In certain situations where multiple crash requests come
// through quickly, we can end up with the mach IPC messages not
// coming through correctly. Since we don't know what parameters
// we've missed, we can't do much besides abort the crash dump
// situation in this case.
unsigned int parameters_read = 0;
// The initial message contains the number of key value pairs that
// we are expected to read.
// Read each key/value pair, one mach message per key/value pair.
for (unsigned int i = 0; i < info.parameter_count; ++i) {
MachReceiveMessage parameter_message;
result = receive_port.WaitForMessage(&parameter_message, 1000);
if(result == KERN_SUCCESS) {
KeyValueMessageData& key_value_data =
(KeyValueMessageData&)*parameter_message.GetData();
// If we get a blank key, make sure we don't increment the
// parameter count; in some cases (notably on-demand generation
// many times in a short period of time) caused the Mach IPC
// messages to not come through correctly.
if (strlen(key_value_data.key) == 0) {
continue;
}
parameters_read++;
config_params_.SetKeyValue(key_value_data.key, key_value_data.value);
} else {
PRINT_MACH_RESULT(result, "Inspector: key/value message");
break;
}
}
if (parameters_read != info.parameter_count) {
return KERN_FAILURE;
}
}
return result;
}
//=============================================================================
bool Inspector::InspectTask() {
// keep the task quiet while we're looking at it
task_suspend(remote_task_);
NSString* minidumpDir;
const char* minidumpDirectory =
config_params_.GetValueForKey(BREAKPAD_DUMP_DIRECTORY);
// If the client app has not specified a minidump directory,
// use a default of Library/<kDefaultLibrarySubdirectory>/<Product Name>
if (!minidumpDirectory || 0 == strlen(minidumpDirectory)) {
NSArray* libraryDirectories =
NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
NSUserDomainMask,
YES);
NSString* applicationSupportDirectory =
[libraryDirectories objectAtIndex:0];
NSString* library_subdirectory = [NSString
stringWithUTF8String:kDefaultLibrarySubdirectory];
NSString* breakpad_product = [NSString
stringWithUTF8String:config_params_.GetValueForKey(BREAKPAD_PRODUCT)];
NSArray* path_components = [NSArray
arrayWithObjects:applicationSupportDirectory,
library_subdirectory,
breakpad_product,
nil];
minidumpDir = [NSString pathWithComponents:path_components];
} else {
minidumpDir = [[NSString stringWithUTF8String:minidumpDirectory]
stringByExpandingTildeInPath];
}
MinidumpLocation minidumpLocation(minidumpDir);
// Obscure bug alert:
// Don't use [NSString stringWithFormat] to build up the path here since it
// assumes system encoding and in RTL locales will prepend an LTR override
// character for paths beginning with '/' which fileSystemRepresentation does
// not remove. Filed as rdar://6889706 .
NSString* path_ns = [NSString
stringWithUTF8String:minidumpLocation.GetPath()];
NSString* pathid_ns = [NSString
stringWithUTF8String:minidumpLocation.GetID()];
NSString* minidumpPath = [path_ns stringByAppendingPathComponent:pathid_ns];
minidumpPath = [minidumpPath
stringByAppendingPathExtension:@"dmp"];
config_file_.WriteFile( 0,
&config_params_,
minidumpLocation.GetPath(),
minidumpLocation.GetID());
MinidumpGenerator generator(remote_task_, handler_thread_);
if (exception_type_ && exception_code_) {
generator.SetExceptionInformation(exception_type_,
exception_code_,
exception_subcode_,
crashing_thread_);
}
bool result = generator.Write([minidumpPath fileSystemRepresentation]);
// let the task continue
task_resume(remote_task_);
return result;
}
//=============================================================================
// The crashed task needs to be told that the inspection has finished.
// It will wait on a mach port (with timeout) until we send acknowledgement.
kern_return_t Inspector::SendAcknowledgement() {
if (ack_port_ != MACH_PORT_DEAD) {
MachPortSender sender(ack_port_);
MachSendMessage ack_message(kMsgType_InspectorAcknowledgement);
kern_return_t result = sender.SendMessage(ack_message, 2000);
#if VERBOSE
PRINT_MACH_RESULT(result, "Inspector: sent acknowledgement");
#endif
return result;
}
return KERN_INVALID_NAME;
}
} // namespace google_breakpad

@ -0,0 +1,64 @@
// Copyright 2007 Google LLC
//
// 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 LLC 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.
//
// Main driver for Inspector
#import "client/mac/crash_generation/Inspector.h"
#import <Cocoa/Cocoa.h>
namespace google_breakpad {
//=============================================================================
extern "C" {
int main(int argc, char *const argv[]) {
#if DEBUG
// Since we're launched on-demand, this is necessary to see debugging
// output in the console window.
freopen("/dev/console", "w", stdout);
freopen("/dev/console", "w", stderr);
#endif
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
if (argc != 2) {
exit(0);
}
// Our first command-line argument contains the name of the service
// that we're providing.
google_breakpad::Inspector inspector;
inspector.Inspect(argv[1]);
[pool release];
return 0;
}
} // extern "C"
} // namespace google_breakpad

@ -0,0 +1,46 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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_MAC_CRASH_GENERATION_CLIENT_INFO_H_
#define CLIENT_MAC_CRASH_GENERATION_CLIENT_INFO_H_
namespace google_breakpad {
class ClientInfo {
public:
explicit ClientInfo(pid_t pid) : pid_(pid) {}
pid_t pid() const { return pid_; }
private:
pid_t pid_;
};
} // namespace google_breakpad
#endif // CLIENT_MAC_CRASH_GENERATION_CLIENT_INFO_H_

@ -0,0 +1,71 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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/mac/crash_generation/crash_generation_client.h"
#include "client/mac/crash_generation/crash_generation_server.h"
#include "common/mac/MachIPC.h"
namespace google_breakpad {
bool CrashGenerationClient::RequestDumpForException(
int exception_type,
int exception_code,
int exception_subcode,
mach_port_t crashing_thread) {
// The server will send a message to this port indicating that it
// has finished its work.
ReceivePort acknowledge_port;
MachSendMessage message(kDumpRequestMessage);
message.AddDescriptor(mach_task_self()); // this task
message.AddDescriptor(crashing_thread); // crashing thread
message.AddDescriptor(mach_thread_self()); // handler thread
message.AddDescriptor(acknowledge_port.GetPort()); // message receive port
ExceptionInfo info;
info.exception_type = exception_type;
info.exception_code = exception_code;
info.exception_subcode = exception_subcode;
message.SetData(&info, sizeof(info));
const mach_msg_timeout_t kSendTimeoutMs = 2 * 1000;
kern_return_t result = sender_.SendMessage(message, kSendTimeoutMs);
if (result != KERN_SUCCESS)
return false;
// Give the server slightly longer to reply since it has to
// inspect this task and write the minidump.
const mach_msg_timeout_t kReceiveTimeoutMs = 5 * 1000;
MachReceiveMessage acknowledge_message;
result = acknowledge_port.WaitForMessage(&acknowledge_message,
kReceiveTimeoutMs);
return result == KERN_SUCCESS;
}
} // namespace google_breakpad

@ -0,0 +1,64 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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 GOOGLE_BREAKPAD_CLIENT_MAC_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_
#define GOOGLE_BREAKPAD_CLIENT_MAC_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_
#include "common/mac/MachIPC.h"
namespace google_breakpad {
class CrashGenerationClient {
public:
explicit CrashGenerationClient(const char* mach_port_name)
: sender_(mach_port_name) {
}
// Request the crash server to generate a dump.
//
// Return true if the dump was successful; false otherwise.
bool RequestDumpForException(int exception_type,
int exception_code,
int exception_subcode,
mach_port_t crashing_thread);
bool RequestDump() {
return RequestDumpForException(0, 0, 0, MACH_PORT_NULL);
}
private:
MachPortSender sender_;
// Prevent copy construction and assignment.
CrashGenerationClient(const CrashGenerationClient&);
CrashGenerationClient& operator=(const CrashGenerationClient&);
};
} // namespace google_breakpad
#endif // GOOGLE_BREAKPAD_CLIENT_MAC_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_

@ -0,0 +1,165 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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/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"
namespace google_breakpad {
CrashGenerationServer::CrashGenerationServer(
const char* mach_port_name,
FilterCallback filter,
void* filter_context,
OnClientDumpRequestCallback dump_callback,
void* dump_context,
OnClientExitingCallback exit_callback,
void* exit_context,
bool generate_dumps,
const std::string& dump_path)
: filter_(filter),
filter_context_(filter_context),
dump_callback_(dump_callback),
dump_context_(dump_context),
exit_callback_(exit_callback),
exit_context_(exit_context),
generate_dumps_(generate_dumps),
dump_dir_(dump_path.empty() ? "/tmp" : dump_path),
started_(false),
receive_port_(mach_port_name),
mach_port_name_(mach_port_name) {
}
CrashGenerationServer::~CrashGenerationServer() {
if (started_)
Stop();
}
bool CrashGenerationServer::Start() {
int thread_create_result = pthread_create(&server_thread_, NULL,
&WaitForMessages, this);
started_ = thread_create_result == 0;
return started_;
}
bool CrashGenerationServer::Stop() {
if (!started_)
return false;
// Send a quit message to the background thread, and then join it.
MachPortSender sender(mach_port_name_.c_str());
MachSendMessage quit_message(kQuitMessage);
const mach_msg_timeout_t kSendTimeoutMs = 2 * 1000;
kern_return_t result = sender.SendMessage(quit_message, kSendTimeoutMs);
if (result == KERN_SUCCESS) {
int thread_join_result = pthread_join(server_thread_, NULL);
started_ = thread_join_result != 0;
}
return !started_;
}
// static
void* CrashGenerationServer::WaitForMessages(void* server) {
CrashGenerationServer* self =
reinterpret_cast<CrashGenerationServer*>(server);
while (self->WaitForOneMessage()) {}
return NULL;
}
bool CrashGenerationServer::WaitForOneMessage() {
MachReceiveMessage message;
kern_return_t result = receive_port_.WaitForMessage(&message,
MACH_MSG_TIMEOUT_NONE);
if (result == KERN_SUCCESS) {
switch (message.GetMessageID()) {
case kDumpRequestMessage: {
ExceptionInfo& info = (ExceptionInfo&)*message.GetData();
mach_port_t remote_task = message.GetTranslatedPort(0);
mach_port_t crashing_thread = message.GetTranslatedPort(1);
mach_port_t handler_thread = message.GetTranslatedPort(2);
mach_port_t ack_port = message.GetTranslatedPort(3);
pid_t remote_pid = -1;
pid_for_task(remote_task, &remote_pid);
ClientInfo client(remote_pid);
bool result;
std::string dump_path;
if (generate_dumps_ && (!filter_ || filter_(filter_context_))) {
ScopedTaskSuspend suspend(remote_task);
MinidumpGenerator generator(remote_task, handler_thread);
dump_path = generator.UniqueNameInDirectory(dump_dir_, NULL);
if (info.exception_type && info.exception_code) {
generator.SetExceptionInformation(info.exception_type,
info.exception_code,
info.exception_subcode,
crashing_thread);
}
result = generator.Write(dump_path.c_str());
} else {
result = true;
}
if (result && dump_callback_) {
dump_callback_(dump_context_, client, dump_path);
}
// TODO(ted): support a way for the client to send additional data,
// perhaps with a callback so users of the server can read the data
// themselves?
if (ack_port != MACH_PORT_DEAD && ack_port != MACH_PORT_NULL) {
MachPortSender sender(ack_port);
MachSendMessage ack_message(kAcknowledgementMessage);
const mach_msg_timeout_t kSendTimeoutMs = 2 * 1000;
sender.SendMessage(ack_message, kSendTimeoutMs);
}
if (exit_callback_) {
exit_callback_(exit_context_, client);
}
break;
}
case kQuitMessage:
return false;
}
} else { // result != KERN_SUCCESS
return false;
}
return true;
}
} // namespace google_breakpad

@ -0,0 +1,149 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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 GOOGLE_BREAKPAD_CLIENT_MAC_CRASH_GENERATION_CRASH_GENERATION_SERVER_H_
#define GOOGLE_BREAKPAD_CLIENT_MAC_CRASH_GENERATION_CRASH_GENERATION_SERVER_H_
#include <stdint.h>
#include <string>
#include "common/mac/MachIPC.h"
namespace google_breakpad {
class ClientInfo;
// Messages the server can read via its mach port
enum {
kDumpRequestMessage = 1,
kAcknowledgementMessage = 2,
kQuitMessage = 3
};
// Exception details sent by the client when requesting a dump.
struct ExceptionInfo {
int32_t exception_type;
int32_t exception_code;
int32_t exception_subcode;
};
class CrashGenerationServer {
public:
// WARNING: callbacks may be invoked on a different thread
// than that which creates the CrashGenerationServer. They must
// be thread safe.
typedef void (*OnClientDumpRequestCallback)(void* context,
const ClientInfo& client_info,
const std::string& file_path);
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.
// exit_context: Context for client exit callback.
// generate_dumps: Whether to automatically generate dumps.
// Client code of this class might want to generate dumps explicitly
// in the crash dump request callback. In that case, false can be
// passed for this parameter.
// 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,
void* exit_context,
bool generate_dumps,
const std::string& dump_path);
~CrashGenerationServer();
// Perform initialization steps needed to start listening to clients.
//
// Return true if initialization is successful; false otherwise.
bool Start();
// Stop the server.
bool Stop();
private:
// Return a unique filename at which a minidump can be written.
bool MakeMinidumpFilename(std::string& outFilename);
// Loop reading client messages and responding to them until
// a quit message is received.
static void* WaitForMessages(void* server);
// Wait for a single client message and respond to it. Returns false
// if a quit message was received or if an error occurred.
bool WaitForOneMessage();
FilterCallback filter_;
void* filter_context_;
OnClientDumpRequestCallback dump_callback_;
void* dump_context_;
OnClientExitingCallback exit_callback_;
void* exit_context_;
bool generate_dumps_;
std::string dump_dir_;
bool started_;
// The mach port that receives requests to dump from child processes.
ReceivePort receive_port_;
// The name of the mach port. Stored so the Stop method can message
// the background thread to shut it down.
std::string mach_port_name_;
// The thread that waits on the receive port.
pthread_t server_thread_;
// Disable copy constructor and operator=.
CrashGenerationServer(const CrashGenerationServer&);
CrashGenerationServer& operator=(const CrashGenerationServer&);
};
} // namespace google_breakpad
#endif // GOOGLE_BREAKPAD_CLIENT_MAC_CRASH_GENERATION_CRASH_GENERATION_SERVER_H_

@ -0,0 +1,399 @@
/*
* Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_LICENSE_HEADER_END@
*/
/*
* Copyright (c) 1989, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* This file was copied from libc/gen/nlist.c from Darwin's source code
* The version of nlist used as a base is from 10.5.2, libc-498
* http://www.opensource.apple.com/darwinsource/10.5.2/Libc-498/gen/nlist.c
*
* The full tarball is at:
* http://www.opensource.apple.com/darwinsource/tarballs/apsl/Libc-498.tar.gz
*
* I've modified it to be compatible with 64-bit images.
*/
#include "breakpad_nlist_64.h"
#include <CoreFoundation/CoreFoundation.h>
#include <fcntl.h>
#include <mach-o/nlist.h>
#include <mach-o/loader.h>
#include <mach-o/fat.h>
#include <mach/mach.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <TargetConditionals.h>
#include <unistd.h>
/* Stuff lifted from <a.out.h> and <sys/exec.h> since they are gone */
/*
* Header prepended to each a.out file.
*/
struct exec {
unsigned short a_machtype; /* machine type */
unsigned short a_magic; /* magic number */
unsigned long a_text; /* size of text segment */
unsigned long a_data; /* size of initialized data */
unsigned long a_bss; /* size of uninitialized data */
unsigned long a_syms; /* size of symbol table */
unsigned long a_entry; /* entry point */
unsigned long a_trsize; /* size of text relocation */
unsigned long a_drsize; /* size of data relocation */
};
#define OMAGIC 0407 /* old impure format */
#define NMAGIC 0410 /* read-only text */
#define ZMAGIC 0413 /* demand load format */
#define N_BADMAG(x) \
(((x).a_magic)!=OMAGIC && ((x).a_magic)!=NMAGIC && ((x).a_magic)!=ZMAGIC)
#define N_TXTOFF(x) \
((x).a_magic==ZMAGIC ? 0 : sizeof (struct exec))
#define N_SYMOFF(x) \
(N_TXTOFF(x) + (x).a_text+(x).a_data + (x).a_trsize+(x).a_drsize)
// Traits structs for specializing function templates to handle
// 32-bit/64-bit Mach-O files.
template<typename T>
struct MachBits {};
typedef struct nlist nlist32;
typedef struct nlist_64 nlist64;
template<>
struct MachBits<nlist32> {
typedef mach_header mach_header_type;
typedef uint32_t word_type;
static const uint32_t magic = MH_MAGIC;
};
template<>
struct MachBits<nlist64> {
typedef mach_header_64 mach_header_type;
typedef uint64_t word_type;
static const uint32_t magic = MH_MAGIC_64;
};
template<typename nlist_type>
int
__breakpad_fdnlist(int fd, nlist_type* list, const char** symbolNames,
cpu_type_t cpu_type);
/*
* nlist - retreive attributes from name list (string table version)
*/
template <typename nlist_type>
int breakpad_nlist_common(const char* name,
nlist_type* list,
const char** symbolNames,
cpu_type_t cpu_type) {
int fd = open(name, O_RDONLY, 0);
if (fd < 0)
return -1;
int n = __breakpad_fdnlist(fd, list, symbolNames, cpu_type);
close(fd);
return n;
}
int breakpad_nlist(const char* name,
struct nlist* list,
const char** symbolNames,
cpu_type_t cpu_type) {
return breakpad_nlist_common(name, list, symbolNames, cpu_type);
}
int breakpad_nlist(const char* name,
struct nlist_64* list,
const char** symbolNames,
cpu_type_t cpu_type) {
return breakpad_nlist_common(name, list, symbolNames, cpu_type);
}
/* Note: __fdnlist() is called from kvm_nlist in libkvm's kvm.c */
template<typename nlist_type>
int __breakpad_fdnlist(int fd, nlist_type* list, const char** symbolNames,
cpu_type_t cpu_type) {
typedef typename MachBits<nlist_type>::mach_header_type mach_header_type;
typedef typename MachBits<nlist_type>::word_type word_type;
const uint32_t magic = MachBits<nlist_type>::magic;
int maxlen = 500;
int nreq = 0;
for (nlist_type* q = list;
symbolNames[q-list] && symbolNames[q-list][0];
q++, nreq++) {
q->n_type = 0;
q->n_value = 0;
q->n_desc = 0;
q->n_sect = 0;
q->n_un.n_strx = 0;
}
struct exec buf;
if (read(fd, (char*)&buf, sizeof(buf)) != sizeof(buf) ||
(N_BADMAG(buf) && *((uint32_t*)&buf) != magic &&
CFSwapInt32BigToHost(*((uint32_t*)&buf)) != FAT_MAGIC &&
/* The following is the big-endian ppc64 check */
(*((uint32_t*)&buf)) != FAT_MAGIC)) {
return -1;
}
/* Deal with fat file if necessary */
unsigned arch_offset = 0;
if (CFSwapInt32BigToHost(*((uint32_t*)&buf)) == FAT_MAGIC ||
/* The following is the big-endian ppc64 check */
*((unsigned int*)&buf) == FAT_MAGIC) {
/* Read in the fat header */
struct fat_header fh;
if (lseek(fd, 0, SEEK_SET) == -1) {
return -1;
}
if (read(fd, (char*)&fh, sizeof(fh)) != sizeof(fh)) {
return -1;
}
/* Convert fat_narchs to host byte order */
fh.nfat_arch = CFSwapInt32BigToHost(fh.nfat_arch);
/* Read in the fat archs */
struct fat_arch* fat_archs =
(struct fat_arch*)malloc(fh.nfat_arch * sizeof(struct fat_arch));
if (fat_archs == NULL) {
return -1;
}
if (read(fd, (char*)fat_archs,
sizeof(struct fat_arch) * fh.nfat_arch) !=
(ssize_t)(sizeof(struct fat_arch) * fh.nfat_arch)) {
free(fat_archs);
return -1;
}
/*
* Convert archs to host byte ordering (a constraint of
* cpusubtype_getbestarch()
*/
for (unsigned i = 0; i < fh.nfat_arch; i++) {
fat_archs[i].cputype =
CFSwapInt32BigToHost(fat_archs[i].cputype);
fat_archs[i].cpusubtype =
CFSwapInt32BigToHost(fat_archs[i].cpusubtype);
fat_archs[i].offset =
CFSwapInt32BigToHost(fat_archs[i].offset);
fat_archs[i].size =
CFSwapInt32BigToHost(fat_archs[i].size);
fat_archs[i].align =
CFSwapInt32BigToHost(fat_archs[i].align);
}
struct fat_arch* fap = NULL;
for (unsigned i = 0; i < fh.nfat_arch; i++) {
if (fat_archs[i].cputype == cpu_type) {
fap = &fat_archs[i];
break;
}
}
if (!fap) {
free(fat_archs);
return -1;
}
arch_offset = fap->offset;
free(fat_archs);
/* Read in the beginning of the architecture-specific file */
if (lseek(fd, arch_offset, SEEK_SET) == -1) {
return -1;
}
if (read(fd, (char*)&buf, sizeof(buf)) != sizeof(buf)) {
return -1;
}
}
off_t sa; /* symbol address */
off_t ss; /* start of strings */
register_t n;
if (*((unsigned int*)&buf) == magic) {
if (lseek(fd, arch_offset, SEEK_SET) == -1) {
return -1;
}
mach_header_type mh;
if (read(fd, (char*)&mh, sizeof(mh)) != sizeof(mh)) {
return -1;
}
struct load_command* load_commands =
(struct load_command*)malloc(mh.sizeofcmds);
if (load_commands == NULL) {
return -1;
}
if (read(fd, (char*)load_commands, mh.sizeofcmds) !=
(ssize_t)mh.sizeofcmds) {
free(load_commands);
return -1;
}
struct symtab_command* stp = NULL;
struct load_command* lcp = load_commands;
// iterate through all load commands, looking for
// LC_SYMTAB load command
for (uint32_t i = 0; i < mh.ncmds; i++) {
if (lcp->cmdsize % sizeof(word_type) != 0 ||
lcp->cmdsize <= 0 ||
(char*)lcp + lcp->cmdsize > (char*)load_commands + mh.sizeofcmds) {
free(load_commands);
return -1;
}
if (lcp->cmd == LC_SYMTAB) {
if (lcp->cmdsize != sizeof(struct symtab_command)) {
free(load_commands);
return -1;
}
stp = (struct symtab_command*)lcp;
break;
}
lcp = (struct load_command*)((char*)lcp + lcp->cmdsize);
}
if (stp == NULL) {
free(load_commands);
return -1;
}
// sa points to the beginning of the symbol table
sa = stp->symoff + arch_offset;
// ss points to the beginning of the string table
ss = stp->stroff + arch_offset;
// n is the number of bytes in the symbol table
// each symbol table entry is an nlist structure
n = stp->nsyms * sizeof(nlist_type);
free(load_commands);
} else {
sa = N_SYMOFF(buf) + arch_offset;
ss = sa + buf.a_syms + arch_offset;
n = buf.a_syms;
}
if (lseek(fd, sa, SEEK_SET) == -1) {
return -1;
}
// the algorithm here is to read the nlist entries in m-sized
// chunks into q. q is then iterated over. for each entry in q,
// use the string table index(q->n_un.n_strx) to read the symbol
// name, then scan the nlist entries passed in by the user(via p),
// and look for a match
while (n) {
nlist_type space[BUFSIZ/sizeof (nlist_type)];
register_t m = sizeof (space);
if (n < m)
m = n;
if (read(fd, (char*)space, m) != m)
break;
n -= m;
off_t savpos = lseek(fd, 0, SEEK_CUR);
if (savpos == -1) {
return -1;
}
for (nlist_type* q = space; (m -= sizeof(nlist_type)) >= 0; q++) {
char nambuf[BUFSIZ];
if (q->n_un.n_strx == 0 || q->n_type & N_STAB)
continue;
// seek to the location in the binary where the symbol
// name is stored & read it into memory
if (lseek(fd, ss+q->n_un.n_strx, SEEK_SET) == -1) {
return -1;
}
if (read(fd, nambuf, maxlen+1) == -1) {
return -1;
}
const char* s2 = nambuf;
for (nlist_type* p = list;
symbolNames[p-list] && symbolNames[p-list][0];
p++) {
// get the symbol name the user has passed in that
// corresponds to the nlist entry that we're looking at
const char* s1 = symbolNames[p - list];
while (*s1) {
if (*s1++ != *s2++)
goto cont;
}
if (*s2)
goto cont;
p->n_value = q->n_value;
p->n_type = q->n_type;
p->n_desc = q->n_desc;
p->n_sect = q->n_sect;
p->n_un.n_strx = q->n_un.n_strx;
if (--nreq == 0)
return nreq;
break;
cont: ;
}
}
if (lseek(fd, savpos, SEEK_SET) == -1) {
return -1;
}
}
return nreq;
}

@ -0,0 +1,47 @@
// Copyright 2008 Google LLC
// 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 LLC 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.
// breakpad_nlist.h
//
// This file is meant to provide a header for clients of the modified
// nlist function implemented to work on 64-bit.
#ifndef CLIENT_MAC_HANDLER_BREAKPAD_NLIST_H__
#define CLIENT_MAC_HANDLER_BREAKPAD_NLIST_H__
#include <mach/machine.h>
int breakpad_nlist(const char* name,
struct nlist* list,
const char** symbolNames,
cpu_type_t cpu_type);
int breakpad_nlist(const char* name,
struct nlist_64* list,
const char** symbolNames,
cpu_type_t cpu_type);
#endif /* CLIENT_MAC_HANDLER_BREAKPAD_NLIST_H__ */

@ -0,0 +1,572 @@
// Copyright 2007 Google LLC
//
// 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 LLC 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/mac/handler/dynamic_images.h"
extern "C" { // needed to compile on Leopard
#include <mach-o/nlist.h>
#include <stdlib.h>
#include <stdio.h>
}
#include <assert.h>
#include <AvailabilityMacros.h>
#include <dlfcn.h>
#include <mach/task_info.h>
#include <sys/sysctl.h>
#include <TargetConditionals.h>
#include <unistd.h>
#include <algorithm>
#include <string>
#include <vector>
#include "breakpad_nlist_64.h"
#if !TARGET_OS_IPHONE
#include <CoreServices/CoreServices.h>
#ifndef MAC_OS_X_VERSION_10_6
#define MAC_OS_X_VERSION_10_6 1060
#endif
#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6
// Fallback declarations for TASK_DYLD_INFO and friends, introduced in
// <mach/task_info.h> in the Mac OS X 10.6 SDK.
#define TASK_DYLD_INFO 17
struct task_dyld_info {
mach_vm_address_t all_image_info_addr;
mach_vm_size_t all_image_info_size;
};
typedef struct task_dyld_info task_dyld_info_data_t;
typedef struct task_dyld_info* task_dyld_info_t;
#define TASK_DYLD_INFO_COUNT (sizeof(task_dyld_info_data_t) / sizeof(natural_t))
#endif
#endif // !TARGET_OS_IPHONE
namespace google_breakpad {
using std::string;
using std::vector;
//==============================================================================
// Returns the size of the memory region containing |address| and the
// number of bytes from |address| to the end of the region.
// We potentially, will extend the size of the original
// region by the size of the following region if it's contiguous with the
// first in order to handle cases when we're reading strings and they
// straddle two vm regions.
//
static mach_vm_size_t GetMemoryRegionSize(task_port_t target_task,
const uint64_t address,
mach_vm_size_t* size_to_end) {
mach_vm_address_t region_base = (mach_vm_address_t)address;
mach_vm_size_t region_size;
natural_t nesting_level = 0;
vm_region_submap_info_64 submap_info;
mach_msg_type_number_t info_count = VM_REGION_SUBMAP_INFO_COUNT_64;
// Get information about the vm region containing |address|
vm_region_recurse_info_t region_info;
region_info = reinterpret_cast<vm_region_recurse_info_t>(&submap_info);
kern_return_t result =
mach_vm_region_recurse(target_task,
&region_base,
&region_size,
&nesting_level,
region_info,
&info_count);
if (result == KERN_SUCCESS) {
// Get distance from |address| to the end of this region
*size_to_end = region_base + region_size -(mach_vm_address_t)address;
// If we want to handle strings as long as 4096 characters we may need
// to check if there's a vm region immediately following the first one.
// If so, we need to extend |*size_to_end| to go all the way to the end
// of the second region.
if (*size_to_end < 4096) {
// Second region starts where the first one ends
mach_vm_address_t region_base2 =
(mach_vm_address_t)(region_base + region_size);
mach_vm_size_t region_size2;
// Get information about the following vm region
result =
mach_vm_region_recurse(target_task,
&region_base2,
&region_size2,
&nesting_level,
region_info,
&info_count);
// Extend region_size to go all the way to the end of the 2nd region
if (result == KERN_SUCCESS
&& region_base2 == region_base + region_size) {
region_size += region_size2;
}
}
*size_to_end = region_base + region_size -(mach_vm_address_t)address;
} else {
region_size = 0;
*size_to_end = 0;
}
return region_size;
}
#define kMaxStringLength 8192
//==============================================================================
// Reads a NULL-terminated string from another task.
//
// Warning! This will not read any strings longer than kMaxStringLength-1
//
static string ReadTaskString(task_port_t target_task,
const uint64_t address) {
// The problem is we don't know how much to read until we know how long
// the string is. And we don't know how long the string is, until we've read
// the memory! So, we'll try to read kMaxStringLength bytes
// (or as many bytes as we can until we reach the end of the vm region).
mach_vm_size_t size_to_end;
GetMemoryRegionSize(target_task, address, &size_to_end);
if (size_to_end > 0) {
mach_vm_size_t size_to_read =
size_to_end > kMaxStringLength ? kMaxStringLength : size_to_end;
vector<uint8_t> bytes;
if (ReadTaskMemory(target_task, address, (size_t)size_to_read, bytes) !=
KERN_SUCCESS)
return string();
return string(reinterpret_cast<const char*>(&bytes[0]));
}
return string();
}
//==============================================================================
// Reads an address range from another task. The bytes read will be returned
// in bytes, which will be resized as necessary.
kern_return_t ReadTaskMemory(task_port_t target_task,
const uint64_t address,
size_t length,
vector<uint8_t>& bytes) {
int systemPageSize = getpagesize();
// use the negative of the page size for the mask to find the page address
mach_vm_address_t page_address = address & (-systemPageSize);
mach_vm_address_t last_page_address =
(address + length + (systemPageSize - 1)) & (-systemPageSize);
mach_vm_size_t page_size = last_page_address - page_address;
uint8_t* local_start;
uint32_t local_length;
kern_return_t r = mach_vm_read(target_task,
page_address,
page_size,
reinterpret_cast<vm_offset_t*>(&local_start),
&local_length);
if (r != KERN_SUCCESS)
return r;
bytes.resize(length);
memcpy(&bytes[0],
&local_start[(mach_vm_address_t)address - page_address],
length);
mach_vm_deallocate(mach_task_self(), (uintptr_t)local_start, local_length);
return KERN_SUCCESS;
}
#pragma mark -
//==============================================================================
// Traits structs for specializing function templates to handle
// 32-bit/64-bit Mach-O files.
struct MachO32 {
typedef mach_header mach_header_type;
typedef segment_command mach_segment_command_type;
typedef dyld_image_info32 dyld_image_info;
typedef dyld_all_image_infos32 dyld_all_image_infos;
typedef struct nlist nlist_type;
static const uint32_t magic = MH_MAGIC;
static const uint32_t segment_load_command = LC_SEGMENT;
};
struct MachO64 {
typedef mach_header_64 mach_header_type;
typedef segment_command_64 mach_segment_command_type;
typedef dyld_image_info64 dyld_image_info;
typedef dyld_all_image_infos64 dyld_all_image_infos;
typedef struct nlist_64 nlist_type;
static const uint32_t magic = MH_MAGIC_64;
static const uint32_t segment_load_command = LC_SEGMENT_64;
};
template<typename MachBits>
bool FindTextSection(DynamicImage& image) {
typedef typename MachBits::mach_header_type mach_header_type;
typedef typename MachBits::mach_segment_command_type
mach_segment_command_type;
const mach_header_type* header =
reinterpret_cast<const mach_header_type*>(&image.header_[0]);
if(header->magic != MachBits::magic) {
return false;
}
const struct load_command* cmd =
reinterpret_cast<const struct load_command*>(header + 1);
bool found_text_section = false;
bool found_dylib_id_command = false;
for (unsigned int i = 0; cmd && (i < header->ncmds); ++i) {
if (!found_text_section) {
if (cmd->cmd == MachBits::segment_load_command) {
const mach_segment_command_type* seg =
reinterpret_cast<const mach_segment_command_type*>(cmd);
if (!strcmp(seg->segname, "__TEXT")) {
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) {
image.slide_ =
(uintptr_t)image.GetLoadAddress() - (uintptr_t)seg->vmaddr;
}
found_text_section = true;
}
}
}
if (!found_dylib_id_command) {
if (cmd->cmd == LC_ID_DYLIB) {
const struct dylib_command* dc =
reinterpret_cast<const struct dylib_command*>(cmd);
image.version_ = dc->dylib.current_version;
found_dylib_id_command = true;
}
}
if (found_dylib_id_command && found_text_section) {
return true;
}
cmd = reinterpret_cast<const struct load_command*>
(reinterpret_cast<const char*>(cmd) + cmd->cmdsize);
}
return false;
}
//==============================================================================
// Initializes vmaddr_, vmsize_, and slide_
void DynamicImage::CalculateMemoryAndVersionInfo() {
// unless we can process the header, ensure that calls to
// IsValid() will return false
vmaddr_ = 0;
vmsize_ = 0;
slide_ = 0;
version_ = 0;
// The function template above does all the real work.
if (Is64Bit())
FindTextSection<MachO64>(*this);
else
FindTextSection<MachO32>(*this);
}
//==============================================================================
// The helper function template abstracts the 32/64-bit differences.
template<typename MachBits>
uint32_t GetFileTypeFromHeader(DynamicImage& image) {
typedef typename MachBits::mach_header_type mach_header_type;
const mach_header_type* header =
reinterpret_cast<const mach_header_type*>(&image.header_[0]);
return header->filetype;
}
uint32_t DynamicImage::GetFileType() {
if (Is64Bit())
return GetFileTypeFromHeader<MachO64>(*this);
return GetFileTypeFromHeader<MachO32>(*this);
}
#pragma mark -
//==============================================================================
// Loads information about dynamically loaded code in the given task.
DynamicImages::DynamicImages(mach_port_t task)
: task_(task),
cpu_type_(DetermineTaskCPUType(task)),
image_list_() {
ReadImageInfoForTask();
}
template<typename MachBits>
static uint64_t LookupSymbol(const char* symbol_name,
const char* filename,
cpu_type_t cpu_type) {
typedef typename MachBits::nlist_type nlist_type;
nlist_type symbol_info[8] = {};
const char* symbolNames[2] = { symbol_name, "\0" };
nlist_type& list = symbol_info[0];
int invalidEntriesCount = breakpad_nlist(filename,
&list,
symbolNames,
cpu_type);
if(invalidEntriesCount != 0) {
return 0;
}
assert(list.n_value);
return list.n_value;
}
#if TARGET_OS_IPHONE || MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6
static bool HasTaskDyldInfo() {
return true;
}
#else
static SInt32 GetOSVersionInternal() {
SInt32 os_version = 0;
Gestalt(gestaltSystemVersion, &os_version);
return os_version;
}
static SInt32 GetOSVersion() {
static SInt32 os_version = GetOSVersionInternal();
return os_version;
}
static bool HasTaskDyldInfo() {
return GetOSVersion() >= 0x1060;
}
#endif // TARGET_OS_IPHONE || MAC_OS_X_VERSION_MIN_REQUIRED >= 10_6
uint64_t DynamicImages::GetDyldAllImageInfosPointer() {
if (HasTaskDyldInfo()) {
task_dyld_info_data_t task_dyld_info;
mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
if (task_info(task_, TASK_DYLD_INFO, (task_info_t)&task_dyld_info,
&count) != KERN_SUCCESS) {
return 0;
}
return (uint64_t)task_dyld_info.all_image_info_addr;
} else {
const char* imageSymbolName = "_dyld_all_image_infos";
const char* dyldPath = "/usr/lib/dyld";
if (Is64Bit())
return LookupSymbol<MachO64>(imageSymbolName, dyldPath, cpu_type_);
return LookupSymbol<MachO32>(imageSymbolName, dyldPath, cpu_type_);
}
}
//==============================================================================
// This code was written using dyld_debug.c (from Darwin) as a guide.
template<typename MachBits>
void ReadImageInfo(DynamicImages& images,
uint64_t image_list_address) {
typedef typename MachBits::dyld_image_info dyld_image_info;
typedef typename MachBits::dyld_all_image_infos dyld_all_image_infos;
typedef typename MachBits::mach_header_type mach_header_type;
// Read the structure inside of dyld that contains information about
// loaded images. We're reading from the desired task's address space.
// Here we make the assumption that dyld loaded at the same address in
// the crashed process vs. this one. This is an assumption made in
// "dyld_debug.c" and is said to be nearly always valid.
vector<uint8_t> dyld_all_info_bytes;
if (ReadTaskMemory(images.task_,
image_list_address,
sizeof(dyld_all_image_infos),
dyld_all_info_bytes) != KERN_SUCCESS)
return;
dyld_all_image_infos* dyldInfo =
reinterpret_cast<dyld_all_image_infos*>(&dyld_all_info_bytes[0]);
// number of loaded images
int count = dyldInfo->infoArrayCount;
// Read an array of dyld_image_info structures each containing
// information about a loaded image.
vector<uint8_t> dyld_info_array_bytes;
if (ReadTaskMemory(images.task_,
dyldInfo->infoArray,
count * sizeof(dyld_image_info),
dyld_info_array_bytes) != KERN_SUCCESS)
return;
dyld_image_info* infoArray =
reinterpret_cast<dyld_image_info*>(&dyld_info_array_bytes[0]);
images.image_list_.reserve(count);
for (int i = 0; i < count; ++i) {
dyld_image_info& info = infoArray[i];
// First read just the mach_header from the image in the task.
vector<uint8_t> mach_header_bytes;
if (ReadTaskMemory(images.task_,
info.load_address_,
sizeof(mach_header_type),
mach_header_bytes) != KERN_SUCCESS)
continue; // bail on this dynamic image
mach_header_type* header =
reinterpret_cast<mach_header_type*>(&mach_header_bytes[0]);
// Now determine the total amount necessary to read the header
// plus all of the load commands.
size_t header_size =
sizeof(mach_header_type) + header->sizeofcmds;
if (ReadTaskMemory(images.task_,
info.load_address_,
header_size,
mach_header_bytes) != KERN_SUCCESS)
continue;
// Read the file name from the task's memory space.
string file_path;
if (info.file_path_) {
// Although we're reading kMaxStringLength bytes, it's copied in the
// the DynamicImage constructor below with the correct string length,
// so it's not really wasting memory.
file_path = ReadTaskString(images.task_, info.file_path_);
}
// Create an object representing this image and add it to our list.
DynamicImage* new_image;
new_image = new DynamicImage(&mach_header_bytes[0],
header_size,
info.load_address_,
file_path,
static_cast<uintptr_t>(info.file_mod_date_),
images.task_,
images.cpu_type_);
if (new_image->IsValid()) {
images.image_list_.push_back(DynamicImageRef(new_image));
} else {
delete new_image;
}
}
// sorts based on loading address
sort(images.image_list_.begin(), images.image_list_.end());
// remove duplicates - this happens in certain strange cases
// You can see it in DashboardClient when Google Gadgets plugin
// is installed. Apple's crash reporter log and gdb "info shared"
// both show the same library multiple times at the same address
vector<DynamicImageRef>::iterator it = unique(images.image_list_.begin(),
images.image_list_.end());
images.image_list_.erase(it, images.image_list_.end());
}
void DynamicImages::ReadImageInfoForTask() {
uint64_t imageList = GetDyldAllImageInfosPointer();
if (imageList) {
if (Is64Bit())
ReadImageInfo<MachO64>(*this, imageList);
else
ReadImageInfo<MachO32>(*this, imageList);
}
}
//==============================================================================
DynamicImage* DynamicImages::GetExecutableImage() {
int executable_index = GetExecutableImageIndex();
if (executable_index >= 0) {
return GetImage(executable_index);
}
return NULL;
}
//==============================================================================
// returns -1 if failure to find executable
int DynamicImages::GetExecutableImageIndex() {
int image_count = GetImageCount();
for (int i = 0; i < image_count; ++i) {
DynamicImage* image = GetImage(i);
if (image->GetFileType() == MH_EXECUTE) {
return i;
}
}
return -1;
}
//==============================================================================
// static
cpu_type_t DynamicImages::DetermineTaskCPUType(task_t task) {
if (task == mach_task_self())
return GetNativeCPUType();
int mib[CTL_MAXNAME];
size_t mibLen = CTL_MAXNAME;
int err = sysctlnametomib("sysctl.proc_cputype", mib, &mibLen);
if (err == 0) {
assert(mibLen < CTL_MAXNAME);
pid_for_task(task, &mib[mibLen]);
mibLen += 1;
cpu_type_t cpu_type;
size_t cpuTypeSize = sizeof(cpu_type);
sysctl(mib, static_cast<u_int>(mibLen), &cpu_type, &cpuTypeSize, 0, 0);
return cpu_type;
}
return GetNativeCPUType();
}
} // namespace google_breakpad

@ -0,0 +1,319 @@
// Copyright 2007 Google LLC
//
// 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 LLC 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.
// dynamic_images.h
//
// Implements most of the function of the dyld API, but allowing an
// arbitrary task to be introspected, unlike the dyld API which
// only allows operation on the current task. The current implementation
// is limited to use by 32-bit tasks.
#ifndef CLIENT_MAC_HANDLER_DYNAMIC_IMAGES_H__
#define CLIENT_MAC_HANDLER_DYNAMIC_IMAGES_H__
#include <mach/mach.h>
#include <mach-o/dyld.h>
#include <mach-o/loader.h>
#include <sys/types.h>
#include <string>
#include <vector>
#include "mach_vm_compat.h"
namespace google_breakpad {
using std::string;
using std::vector;
//==============================================================================
// The memory layout of this struct matches the dyld_image_info struct
// defined in "dyld_gdb.h" in the darwin source.
typedef struct dyld_image_info32 {
uint32_t load_address_; // struct mach_header*
uint32_t file_path_; // char*
uint32_t file_mod_date_;
} dyld_image_info32;
typedef struct dyld_image_info64 {
uint64_t load_address_; // struct mach_header*
uint64_t file_path_; // char*
uint64_t file_mod_date_;
} dyld_image_info64;
//==============================================================================
// This is as defined in "dyld_gdb.h" in the darwin source.
// _dyld_all_image_infos (in dyld) is a structure of this type
// which will be used to determine which dynamic code has been loaded.
typedef struct dyld_all_image_infos32 {
uint32_t version; // == 1 in Mac OS X 10.4
uint32_t infoArrayCount;
uint32_t infoArray; // const struct dyld_image_info*
uint32_t notification;
bool processDetachedFromSharedRegion;
} dyld_all_image_infos32;
typedef struct dyld_all_image_infos64 {
uint32_t version; // == 1 in Mac OS X 10.4
uint32_t infoArrayCount;
uint64_t infoArray; // const struct dyld_image_info*
uint64_t notification;
bool processDetachedFromSharedRegion;
} dyld_all_image_infos64;
// some typedefs to isolate 64/32 bit differences
#ifdef __LP64__
typedef mach_header_64 breakpad_mach_header;
typedef segment_command_64 breakpad_mach_segment_command;
#else
typedef mach_header breakpad_mach_header;
typedef segment_command breakpad_mach_segment_command;
#endif
// Helper functions to deal with 32-bit/64-bit Mach-O differences.
class DynamicImage;
template<typename MachBits>
bool FindTextSection(DynamicImage& image);
template<typename MachBits>
uint32_t GetFileTypeFromHeader(DynamicImage& image);
//==============================================================================
// Represents a single dynamically loaded mach-o image
class DynamicImage {
public:
DynamicImage(uint8_t* header, // data is copied
size_t header_size, // includes load commands
uint64_t load_address,
string file_path,
uintptr_t image_mod_date,
mach_port_t task,
cpu_type_t cpu_type)
: header_(header, header + header_size),
header_size_(header_size),
load_address_(load_address),
vmaddr_(0),
vmsize_(0),
slide_(0),
version_(0),
file_path_(file_path),
file_mod_date_(image_mod_date),
task_(task),
cpu_type_(cpu_type) {
CalculateMemoryAndVersionInfo();
}
// Size of mach_header plus load commands
size_t GetHeaderSize() const {return header_.size();}
// Full path to mach-o binary
string GetFilePath() {return file_path_;}
uint64_t GetModDate() const {return file_mod_date_;}
// Actual address where the image was loaded
uint64_t GetLoadAddress() const {return load_address_;}
// Address where the image should be loaded
mach_vm_address_t GetVMAddr() const {return vmaddr_;}
// Difference between GetLoadAddress() and GetVMAddr()
ptrdiff_t GetVMAddrSlide() const {return slide_;}
// Size of the image
mach_vm_size_t GetVMSize() const {return vmsize_;}
// Task owning this loaded image
mach_port_t GetTask() {return task_;}
// CPU type of the task
cpu_type_t GetCPUType() {return cpu_type_;}
// filetype from the Mach-O header.
uint32_t GetFileType();
// Return true if the task is a 64-bit architecture.
bool Is64Bit() { return (GetCPUType() & CPU_ARCH_ABI64) == CPU_ARCH_ABI64; }
uint32_t GetVersion() {return version_;}
// For sorting
bool operator<(const DynamicImage& inInfo) {
return GetLoadAddress() < inInfo.GetLoadAddress();
}
// Sanity checking
bool IsValid() {return GetVMSize() != 0;}
private:
DynamicImage(const DynamicImage&);
DynamicImage& operator=(const DynamicImage&);
friend class DynamicImages;
template<typename MachBits>
friend bool FindTextSection(DynamicImage& image);
template<typename MachBits>
friend uint32_t GetFileTypeFromHeader(DynamicImage& image);
// Initializes vmaddr_, vmsize_, and slide_
void CalculateMemoryAndVersionInfo();
const vector<uint8_t> header_; // our local copy of the header
size_t header_size_; // mach_header plus load commands
uint64_t load_address_; // base address image is mapped into
mach_vm_address_t vmaddr_;
mach_vm_size_t vmsize_;
ptrdiff_t slide_;
uint32_t version_; // Dylib version
string file_path_; // path dyld used to load the image
uintptr_t file_mod_date_; // time_t of image file
mach_port_t task_;
cpu_type_t cpu_type_; // CPU type of task_
};
//==============================================================================
// DynamicImageRef is just a simple wrapper for a pointer to
// DynamicImage. The reason we use it instead of a simple typedef is so
// that we can use stl::sort() on a vector of DynamicImageRefs
// and simple class pointers can't implement operator<().
//
class DynamicImageRef {
public:
explicit DynamicImageRef(DynamicImage* inP) : p(inP) {}
// The copy constructor is required by STL
DynamicImageRef(const DynamicImageRef& inRef) = default;
DynamicImageRef& operator=(const DynamicImageRef& inRef) = default;
bool operator<(const DynamicImageRef& inRef) const {
return (*const_cast<DynamicImageRef*>(this)->p)
< (*const_cast<DynamicImageRef&>(inRef).p);
}
bool operator==(const DynamicImageRef& inInfo) const {
return (*const_cast<DynamicImageRef*>(this)->p).GetLoadAddress() ==
(*const_cast<DynamicImageRef&>(inInfo)).GetLoadAddress();
}
// Be just like DynamicImage*
DynamicImage* operator->() {return p;}
operator DynamicImage*() {return p;}
private:
DynamicImage* p;
};
// Helper function to deal with 32-bit/64-bit Mach-O differences.
class DynamicImages;
template<typename MachBits>
void ReadImageInfo(DynamicImages& images, uint64_t image_list_address);
//==============================================================================
// An object of type DynamicImages may be created to allow introspection of
// an arbitrary task's dynamically loaded mach-o binaries. This makes the
// assumption that the current task has send rights to the target task.
class DynamicImages {
public:
explicit DynamicImages(mach_port_t task);
~DynamicImages() {
for (int i = 0; i < GetImageCount(); ++i) {
delete image_list_[i];
}
}
// Returns the number of dynamically loaded mach-o images.
int GetImageCount() const {return static_cast<int>(image_list_.size());}
// Returns an individual image.
DynamicImage* GetImage(int i) {
if (i < (int)image_list_.size()) {
return image_list_[i];
}
return NULL;
}
// Returns the image corresponding to the main executable.
DynamicImage* GetExecutableImage();
int GetExecutableImageIndex();
// Returns the task which we're looking at.
mach_port_t GetTask() const {return task_;}
// CPU type of the task
cpu_type_t GetCPUType() {return cpu_type_;}
// Return true if the task is a 64-bit architecture.
bool Is64Bit() { return (GetCPUType() & CPU_ARCH_ABI64) == CPU_ARCH_ABI64; }
// Determine the CPU type of the task being dumped.
static cpu_type_t DetermineTaskCPUType(task_t task);
// Get the native CPU type of this task.
static cpu_type_t GetNativeCPUType() {
#if defined(__i386__)
return CPU_TYPE_I386;
#elif defined(__x86_64__)
return CPU_TYPE_X86_64;
#elif defined(__ppc__)
return CPU_TYPE_POWERPC;
#elif defined(__ppc64__)
return CPU_TYPE_POWERPC64;
#elif defined(__arm__)
return CPU_TYPE_ARM;
#elif defined(__aarch64__)
return CPU_TYPE_ARM64;
#else
#error "GetNativeCPUType not implemented for this architecture"
#endif
}
private:
template<typename MachBits>
friend void ReadImageInfo(DynamicImages& images, uint64_t image_list_address);
bool IsOurTask() {return task_ == mach_task_self();}
// Initialization
void ReadImageInfoForTask();
uint64_t GetDyldAllImageInfosPointer();
mach_port_t task_;
cpu_type_t cpu_type_; // CPU type of task_
vector<DynamicImageRef> image_list_;
};
// Fill bytes with the contents of memory at a particular
// location in another task.
kern_return_t ReadTaskMemory(task_port_t target_task,
const uint64_t address,
size_t length,
vector<uint8_t>& bytes);
} // namespace google_breakpad
#endif // CLIENT_MAC_HANDLER_DYNAMIC_IMAGES_H__

@ -0,0 +1,859 @@
// Copyright 2006 Google LLC
//
// 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 LLC 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 <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
#else
#define USE_PROTECTED_ALLOCATIONS 0
#endif
#endif
// If USE_PROTECTED_ALLOCATIONS is activated then the
// gBreakpadAllocator needs to be setup in other code
// ahead of time. Please see ProtectedMemoryAllocator.h
// for more details.
#if USE_PROTECTED_ALLOCATIONS
#include "protected_memory_allocator.h"
extern ProtectedMemoryAllocator *gBreakpadAllocator;
#endif
namespace google_breakpad {
static union {
#if USE_PROTECTED_ALLOCATIONS
#if defined PAGE_MAX_SIZE
char protected_buffer[PAGE_MAX_SIZE] __attribute__((aligned(PAGE_MAX_SIZE)));
#else
char protected_buffer[PAGE_SIZE] __attribute__((aligned(PAGE_SIZE)));
#endif // defined PAGE_MAX_SIZE
#endif // USE_PROTECTED_ALLOCATIONS
google_breakpad::ExceptionHandler *handler;
} gProtectedData;
using std::map;
// These structures and techniques are illustrated in
// Mac OS X Internals, Amit Singh, ch 9.7
struct ExceptionMessage {
mach_msg_header_t header;
mach_msg_body_t body;
mach_msg_port_descriptor_t thread;
mach_msg_port_descriptor_t task;
NDR_record_t ndr;
exception_type_t exception;
mach_msg_type_number_t code_count;
integer_t code[EXCEPTION_CODE_MAX];
char padding[512];
};
struct ExceptionParameters {
ExceptionParameters() : count(0) {}
mach_msg_type_number_t count;
exception_mask_t masks[EXC_TYPES_COUNT];
mach_port_t ports[EXC_TYPES_COUNT];
exception_behavior_t behaviors[EXC_TYPES_COUNT];
thread_state_flavor_t flavors[EXC_TYPES_COUNT];
};
struct ExceptionReplyMessage {
mach_msg_header_t header;
NDR_record_t ndr;
kern_return_t return_code;
};
// Only catch these three exceptions. The other ones are nebulously defined
// and may result in treating a non-fatal exception as fatal.
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" {
// Forward declarations for functions that need "C" style compilation
boolean_t exc_server(mach_msg_header_t* request,
mach_msg_header_t* reply);
// This symbol must be visible to dlsym() - see
// https://bugs.chromium.org/p/google-breakpad/issues/detail?id=345 for details.
kern_return_t catch_exception_raise(mach_port_t target_port,
mach_port_t failed_thread,
mach_port_t task,
exception_type_t exception,
exception_data_t code,
mach_msg_type_number_t code_count)
__attribute__((visibility("default")));
}
#endif
kern_return_t ForwardException(mach_port_t task,
mach_port_t failed_thread,
exception_type_t exception,
exception_data_t code,
mach_msg_type_number_t code_count);
#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;
if (InHeadP->msgh_id != 2401) {
((mig_reply_error_t*)OutHeadP)->NDR = NDR_record;
((mig_reply_error_t*)OutHeadP)->RetCode = MIG_BAD_ID;
return FALSE;
}
#ifdef __MigPackStructs
#pragma pack(4)
#endif
typedef struct {
mach_msg_header_t Head;
/* start of the kernel processed data */
mach_msg_body_t msgh_body;
mach_msg_port_descriptor_t thread;
mach_msg_port_descriptor_t task;
/* end of the kernel processed data */
NDR_record_t NDR;
exception_type_t exception;
mach_msg_type_number_t codeCnt;
integer_t code[2];
mach_msg_trailer_t trailer;
} Request;
typedef struct {
mach_msg_header_t Head;
NDR_record_t NDR;
kern_return_t RetCode;
} Reply;
#ifdef __MigPackStructs
#pragma pack()
#endif
Request* In0P = (Request*)InHeadP;
Reply* OutP = (Reply*)OutHeadP;
if (In0P->task.name != mach_task_self()) {
return FALSE;
}
OutP->RetCode = ForwardException(In0P->task.name,
In0P->thread.name,
In0P->exception,
In0P->code,
In0P->codeCnt);
OutP->NDR = NDR_record;
return TRUE;
}
#else
boolean_t breakpad_exc_server(mach_msg_header_t* request,
mach_msg_header_t* reply) {
return exc_server(request, reply);
}
// Callback from exc_server()
kern_return_t catch_exception_raise(mach_port_t port, mach_port_t failed_thread,
mach_port_t task,
exception_type_t exception,
exception_data_t code,
mach_msg_type_number_t code_count) {
if (task != mach_task_self()) {
return KERN_FAILURE;
}
return ForwardException(task, failed_thread, exception, code, code_count);
}
#endif
ExceptionHandler::ExceptionHandler(const string& dump_path,
FilterCallback filter,
MinidumpCallback callback,
void* callback_context,
bool install_handler,
const char* port_name)
: dump_path_(),
filter_(filter),
callback_(callback),
callback_context_(callback_context),
directCallback_(NULL),
handler_thread_(NULL),
handler_port_(MACH_PORT_NULL),
previous_(NULL),
installed_exception_handler_(false),
is_in_teardown_(false),
last_minidump_write_result_(false),
use_minidump_write_mutex_(false) {
// This will update to the ID and C-string pointers
set_dump_path(dump_path);
MinidumpGenerator::GatherSystemInformation();
#if !TARGET_OS_IPHONE
if (port_name)
crash_generation_client_.reset(new CrashGenerationClient(port_name));
#endif
Setup(install_handler);
}
// 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,
bool install_handler)
: dump_path_(),
filter_(NULL),
callback_(NULL),
callback_context_(callback_context),
directCallback_(callback),
handler_thread_(NULL),
handler_port_(MACH_PORT_NULL),
previous_(NULL),
installed_exception_handler_(false),
is_in_teardown_(false),
last_minidump_write_result_(false),
use_minidump_write_mutex_(false) {
MinidumpGenerator::GatherSystemInformation();
Setup(install_handler);
}
ExceptionHandler::~ExceptionHandler() {
Teardown();
}
bool ExceptionHandler::WriteMinidump(bool write_exception_stream) {
// If we're currently writing, just return
if (use_minidump_write_mutex_)
return false;
use_minidump_write_mutex_ = true;
last_minidump_write_result_ = false;
// Lock the mutex. Since we just created it, this will return immediately.
if (pthread_mutex_lock(&minidump_write_mutex_) == 0) {
// Send an empty message to the handle port so that a minidump will
// be written
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
pthread_mutex_lock(&minidump_write_mutex_);
}
use_minidump_write_mutex_ = false;
UpdateNextID();
return last_minidump_write_result_;
}
// static
bool ExceptionHandler::WriteMinidump(const string& dump_path,
bool write_exception_stream,
MinidumpCallback callback,
void* callback_context) {
ExceptionHandler handler(dump_path, NULL, callback, callback_context, false,
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) {
ScopedTaskSuspend suspend(child);
MinidumpGenerator generator(child, MACH_PORT_NULL);
string dump_id;
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__) || defined(__aarch64__)
EXC_ARM_BREAKPOINT,
#else
#error architecture not supported
#endif
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);
}
return result;
}
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 TARGET_OS_IPHONE
// _exit() should never be called on iOS.
exit_after_write = false;
#endif
if (directCallback_) {
if (directCallback_(callback_context_,
exception_type,
exception_code,
exception_subcode,
thread_name) ) {
if (exit_after_write)
_exit(exception_type);
}
#if !TARGET_OS_IPHONE
} else if (IsOutOfProcess()) {
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.
if (filter_ && !filter_(callback_context_))
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 {
string minidump_id;
// Putting the MinidumpGenerator in its own context will ensure that the
// destructor is executed, closing the newly created minidump file.
if (!dump_path_.empty()) {
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.
if (filter_ && !filter_(callback_context_))
return false;
md.SetExceptionInformation(exception_type, exception_code,
exception_subcode, thread_name);
}
result = md.Write(next_minidump_path_c_);
}
// Call user specified callback (if any)
if (callback_) {
// If the user callback returned true and we're handling an exception
// (rather than just writing out the file), then we should exit without
// forwarding the exception to the next handler.
if (callback_(dump_path_c_, next_minidump_id_c_, callback_context_,
result)) {
if (exit_after_write)
_exit(exception_type);
}
}
}
return result;
}
kern_return_t ForwardException(mach_port_t task, mach_port_t failed_thread,
exception_type_t exception,
exception_data_t code,
mach_msg_type_number_t code_count) {
// At this time, we should have called Uninstall() on the exception handler
// so that the current exception ports are the ones that we should be
// forwarding to.
ExceptionParameters current;
current.count = EXC_TYPES_COUNT;
mach_port_t current_task = mach_task_self();
task_get_exception_ports(current_task,
s_exception_mask,
current.masks,
&current.count,
current.ports,
current.behaviors,
current.flavors);
// Find the first exception handler that matches the exception
unsigned int found;
for (found = 0; found < current.count; ++found) {
if (current.masks[found] & (1 << exception)) {
break;
}
}
// Nothing to forward
if (found == current.count) {
fprintf(stderr, "** No previous ports for forwarding!! \n");
exit(KERN_FAILURE);
}
mach_port_t target_port = current.ports[found];
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://bugs.chromium.org/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;
break;
}
return result;
}
// static
void* ExceptionHandler::WaitForMessage(void* exception_handler_class) {
ExceptionHandler* self =
reinterpret_cast<ExceptionHandler*>(exception_handler_class);
ExceptionMessage receive;
// Wait for the exception info
while (1) {
receive.header.msgh_local_port = self->handler_port_;
receive.header.msgh_size = static_cast<mach_msg_size_t>(sizeof(receive));
kern_return_t result = mach_msg(&(receive.header),
MACH_RCV_MSG | MACH_RCV_LARGE, 0,
receive.header.msgh_size,
self->handler_port_,
MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
if (result == KERN_SUCCESS) {
// Uninstall our handler so that we don't get in a loop if the process of
// writing out a minidump causes an exception. However, if the exception
// was caused by a fork'd process, don't uninstall things
// If the actual exception code is zero, then we're calling this handler
// in a way that indicates that we want to either exit this thread or
// generate a minidump
//
// While reporting, all threads (except this one) must be suspended
// to avoid misleading stacks. If appropriate they will be resumed
// afterwards.
if (!receive.exception) {
// Don't touch self, since this message could have been sent
// from its destructor.
if (receive.header.msgh_id == kShutdownMessage)
return NULL;
self->SuspendThreads();
#if USE_PROTECTED_ALLOCATIONS
if (gBreakpadAllocator)
gBreakpadAllocator->Unprotect();
#endif
mach_port_t thread = MACH_PORT_NULL;
int exception_type = 0;
int exception_code = 0;
if (receive.header.msgh_id == kWriteDumpWithExceptionMessage) {
thread = receive.thread.name;
exception_type = EXC_BREAKPOINT;
#if defined(__i386__) || defined(__x86_64__)
exception_code = EXC_I386_BPT;
#elif defined(__ppc__) || defined(__ppc64__)
exception_code = EXC_PPC_BREAKPOINT;
#elif defined(__arm__) || defined(__aarch64__)
exception_code = EXC_ARM_BREAKPOINT;
#else
#error architecture not supported
#endif
}
// Write out the dump and save the result for later retrieval
self->last_minidump_write_result_ =
self->WriteMinidumpWithException(exception_type, exception_code,
0, NULL, thread,
false, false);
#if USE_PROTECTED_ALLOCATIONS
if (gBreakpadAllocator)
gBreakpadAllocator->Protect();
#endif
self->ResumeThreads();
if (self->use_minidump_write_mutex_)
pthread_mutex_unlock(&self->minidump_write_mutex_);
} else {
// When forking a child process with the exception handler installed,
// if the child crashes, it will send the exception back to the parent
// process. The check for task == self_task() ensures that only
// exceptions that occur in the parent process are caught and
// processed. If the exception was not caused by this task, we
// still need to call into the exception server and have it return
// KERN_FAILURE (see catch_exception_raise) in order for the kernel
// to move onto the host exception handler for the child task
if (receive.task.name == mach_task_self()) {
self->SuspendThreads();
#if USE_PROTECTED_ALLOCATIONS
if (gBreakpadAllocator)
gBreakpadAllocator->Unprotect();
#endif
int subcode = 0;
if (receive.exception == EXC_BAD_ACCESS && receive.code_count > 1)
subcode = receive.code[1];
// Generate the minidump with the exception data.
self->WriteMinidumpWithException(receive.exception, receive.code[0],
subcode, NULL, receive.thread.name,
true, false);
#if USE_PROTECTED_ALLOCATIONS
// This may have become protected again within
// WriteMinidumpWithException, but it needs to be unprotected for
// UninstallHandler.
if (gBreakpadAllocator)
gBreakpadAllocator->Unprotect();
#endif
self->UninstallHandler(true);
#if USE_PROTECTED_ALLOCATIONS
if (gBreakpadAllocator)
gBreakpadAllocator->Protect();
#endif
}
// Pass along the exception to the server, which will setup the
// message and call catch_exception_raise() and put the return
// code into the reply.
ExceptionReplyMessage reply;
if (!breakpad_exc_server(&receive.header, &reply.header))
exit(1);
// Send a reply and exit
mach_msg(&(reply.header), MACH_SEND_MSG,
reply.header.msgh_size, 0, MACH_PORT_NULL,
MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
}
}
}
return NULL;
}
// static
void ExceptionHandler::SignalHandler(int sig, siginfo_t* info, void* uc) {
#if USE_PROTECTED_ALLOCATIONS
if (gBreakpadAllocator)
gBreakpadAllocator->Unprotect();
#endif
gProtectedData.handler->WriteMinidumpWithException(
EXC_SOFTWARE,
MD_EXCEPTION_CODE_MAC_ABORT,
0,
static_cast<breakpad_ucontext_t*>(uc),
mach_thread_self(),
true,
true);
#if USE_PROTECTED_ALLOCATIONS
if (gBreakpadAllocator)
gBreakpadAllocator->Protect();
#endif
}
bool ExceptionHandler::InstallHandler() {
// If a handler is already installed, something is really wrong.
if (gProtectedData.handler != NULL) {
return false;
}
if (!IsOutOfProcess()) {
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask, SIGABRT);
sa.sa_sigaction = ExceptionHandler::SignalHandler;
sa.sa_flags = SA_SIGINFO;
scoped_ptr<struct sigaction> old(new struct sigaction);
if (sigaction(SIGABRT, &sa, old.get()) == -1) {
return false;
}
old_handler_.swap(old);
gProtectedData.handler = this;
#if USE_PROTECTED_ALLOCATIONS
assert(((size_t)(gProtectedData.protected_buffer) & PAGE_MASK) == 0);
mprotect(gProtectedData.protected_buffer, PAGE_SIZE, PROT_READ);
#endif
}
try {
#if USE_PROTECTED_ALLOCATIONS
previous_ = new (gBreakpadAllocator->Allocate(sizeof(ExceptionParameters)) )
ExceptionParameters();
#else
previous_ = new ExceptionParameters();
#endif
}
catch (std::bad_alloc) {
return false;
}
// Save the current exception ports so that we can forward to them
previous_->count = EXC_TYPES_COUNT;
mach_port_t current_task = mach_task_self();
kern_return_t result = task_get_exception_ports(current_task,
s_exception_mask,
previous_->masks,
&previous_->count,
previous_->ports,
previous_->behaviors,
previous_->flavors);
// Setup the exception ports on this task
if (result == KERN_SUCCESS)
result = task_set_exception_ports(current_task, s_exception_mask,
handler_port_, EXCEPTION_DEFAULT,
THREAD_STATE_NONE);
installed_exception_handler_ = (result == KERN_SUCCESS);
return installed_exception_handler_;
}
bool ExceptionHandler::UninstallHandler(bool in_exception) {
kern_return_t result = KERN_SUCCESS;
if (old_handler_.get()) {
sigaction(SIGABRT, old_handler_.get(), NULL);
#if USE_PROTECTED_ALLOCATIONS
mprotect(gProtectedData.protected_buffer, PAGE_SIZE,
PROT_READ | PROT_WRITE);
#endif
old_handler_.reset();
gProtectedData.handler = NULL;
}
if (installed_exception_handler_) {
mach_port_t current_task = mach_task_self();
// Restore the previous ports
for (unsigned int i = 0; i < previous_->count; ++i) {
result = task_set_exception_ports(current_task, previous_->masks[i],
previous_->ports[i],
previous_->behaviors[i],
previous_->flavors[i]);
if (result != KERN_SUCCESS)
return false;
}
// this delete should NOT happen if an exception just occurred!
if (!in_exception) {
#if USE_PROTECTED_ALLOCATIONS
previous_->~ExceptionParameters();
#else
delete previous_;
#endif
}
previous_ = NULL;
installed_exception_handler_ = false;
}
return result == KERN_SUCCESS;
}
bool ExceptionHandler::Setup(bool install_handler) {
if (pthread_mutex_init(&minidump_write_mutex_, NULL))
return false;
// Create a receive right
mach_port_t current_task = mach_task_self();
kern_return_t result = mach_port_allocate(current_task,
MACH_PORT_RIGHT_RECEIVE,
&handler_port_);
// Add send right
if (result == KERN_SUCCESS)
result = mach_port_insert_right(current_task, handler_port_, handler_port_,
MACH_MSG_TYPE_MAKE_SEND);
if (install_handler && result == KERN_SUCCESS)
if (!InstallHandler())
return false;
if (result == KERN_SUCCESS) {
// Install the handler in its own thread, detached as we won't be joining.
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
int thread_create_result = pthread_create(&handler_thread_, &attr,
&WaitForMessage, this);
pthread_attr_destroy(&attr);
result = thread_create_result ? KERN_FAILURE : KERN_SUCCESS;
}
return result == KERN_SUCCESS;
}
bool ExceptionHandler::Teardown() {
kern_return_t result = KERN_SUCCESS;
is_in_teardown_ = true;
if (!UninstallHandler(false))
return false;
// Send an empty message so that the handler_thread exits
if (SendMessageToHandlerThread(kShutdownMessage)) {
mach_port_t current_task = mach_task_self();
result = mach_port_deallocate(current_task, handler_port_);
if (result != KERN_SUCCESS)
return false;
} else {
return false;
}
handler_thread_ = NULL;
handler_port_ = MACH_PORT_NULL;
pthread_mutex_destroy(&minidump_write_mutex_);
return result == KERN_SUCCESS;
}
bool ExceptionHandler::SendMessageToHandlerThread(
HandlerThreadMessage message_id) {
ExceptionMessage msg;
memset(&msg, 0, sizeof(msg));
msg.header.msgh_id = message_id;
if (message_id == kWriteDumpMessage ||
message_id == kWriteDumpWithExceptionMessage) {
// Include this thread's port.
msg.thread.name = mach_thread_self();
msg.thread.disposition = MACH_MSG_TYPE_PORT_SEND;
msg.thread.type = MACH_MSG_PORT_DESCRIPTOR;
}
msg.header.msgh_size = sizeof(msg) - sizeof(msg.padding);
msg.header.msgh_remote_port = handler_port_;
msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND,
MACH_MSG_TYPE_MAKE_SEND_ONCE);
kern_return_t result = mach_msg(&(msg.header),
MACH_SEND_MSG | MACH_SEND_TIMEOUT,
msg.header.msgh_size, 0, 0,
MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
return result == KERN_SUCCESS;
}
void ExceptionHandler::UpdateNextID() {
next_minidump_path_ =
(MinidumpGenerator::UniqueNameInDirectory(dump_path_, &next_minidump_id_));
next_minidump_path_c_ = next_minidump_path_.c_str();
next_minidump_id_c_ = next_minidump_id_.c_str();
}
bool ExceptionHandler::SuspendThreads() {
thread_act_port_array_t threads_for_task;
mach_msg_type_number_t thread_count;
if (task_threads(mach_task_self(), &threads_for_task, &thread_count))
return false;
// suspend all of the threads except for this one
for (unsigned int i = 0; i < thread_count; ++i) {
if (threads_for_task[i] != mach_thread_self()) {
if (thread_suspend(threads_for_task[i]))
return false;
}
}
return true;
}
bool ExceptionHandler::ResumeThreads() {
thread_act_port_array_t threads_for_task;
mach_msg_type_number_t thread_count;
if (task_threads(mach_task_self(), &threads_for_task, &thread_count))
return false;
// resume all of the threads except for this one
for (unsigned int i = 0; i < thread_count; ++i) {
if (threads_for_task[i] != mach_thread_self()) {
if (thread_resume(threads_for_task[i]))
return false;
}
}
return true;
}
} // namespace google_breakpad

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

Loading…
Cancel
Save