Android APP Initial Commit
parent
743189f8d4
commit
00433db12f
@ -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.
Binary file not shown.
Binary file not shown.
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, ¶ms, dumpDir, dumpId.c_str());
|
||||
|
||||
// Handle results.
|
||||
NSMutableDictionary* result = [NSMutableDictionary dictionary];
|
||||
NSString* dumpFullPath = [NSString stringWithUTF8String:dumpFilename.c_str()];
|
||||
[result setValue:dumpFullPath
|
||||
forKey:@BREAKPAD_OUTPUT_DUMP_FILE];
|
||||
[result setValue:[NSString stringWithUTF8String:config_file.GetFilePath()]
|
||||
forKey:@BREAKPAD_OUTPUT_CONFIG_FILE];
|
||||
return result;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
bool Breakpad::HandleMinidump(const char* dump_dir,
|
||||
const char* minidump_id) {
|
||||
config_file_.WriteFile(dump_dir,
|
||||
config_params_,
|
||||
dump_dir,
|
||||
minidump_id);
|
||||
|
||||
// Return true here to indicate that we've processed things as much as we
|
||||
// want.
|
||||
return true;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
void Breakpad::HandleUncaughtException(NSException* exception) {
|
||||
// Generate the minidump.
|
||||
google_breakpad::IosExceptionMinidumpGenerator generator(exception);
|
||||
const 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(),
|
||||
¶ms,
|
||||
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 = ®s;
|
||||
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, ¤t_stack) == -1)
|
||||
return;
|
||||
|
||||
// Only restore the old_stack if the current alternative stack is the one
|
||||
// installed by the call to InstallAlternateStackLocked.
|
||||
if (current_stack.ss_sp == new_stack.ss_sp) {
|
||||
if (old_stack.ss_sp) {
|
||||
if (sys_sigaltstack(&old_stack, NULL) == -1)
|
||||
return;
|
||||
} else {
|
||||
stack_t disable_stack;
|
||||
disable_stack.ss_flags = SS_DISABLE;
|
||||
if (sys_sigaltstack(&disable_stack, NULL) == -1)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
free(new_stack.ss_sp);
|
||||
stack_installed = false;
|
||||
}
|
||||
|
||||
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_
|
File diff suppressed because it is too large
Load Diff
@ -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 µdump_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, ®s) == -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));
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -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);
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -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(¶meter_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_
|
Binary file not shown.
@ -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,
|
||||
®ion_base,
|
||||
®ion_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,
|
||||
®ion_base2,
|
||||
®ion_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,
|
||||
¤t.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…
Reference in New Issue