Update sentry sdk
This commit is contained in:
parent
2ee6f67892
commit
96446cba1d
|
@ -1,5 +1,98 @@
|
|||
# Changelog
|
||||
|
||||
## 0.5.0
|
||||
|
||||
**Features**
|
||||
|
||||
- Provide `on_crash()` callback to allow clients to act on detected crashes.
|
||||
Users often inquired about distinguishing between crashes and "normal" events in the `before_send()` hook.
|
||||
`on_crash()` can be considered a replacement for `before_send()` for crash events, where the goal is to use
|
||||
`before_send()` only for normal events, while `on_crash()` is only invoked for crashes. This change is backward
|
||||
compatible for current users of `before_send()` and allows gradual migration to `on_crash()`
|
||||
([see the docs for details](https://docs.sentry.io/platforms/native/configuration/filtering/)).
|
||||
([#724](https://github.com/getsentry/sentry-native/pull/724),
|
||||
[#734](https://github.com/getsentry/sentry-native/pull/734))
|
||||
|
||||
**Fixes**
|
||||
|
||||
- Make Windows ModuleFinder more resilient to missing Debug Info
|
||||
([#732](https://github.com/getsentry/sentry-native/pull/732))
|
||||
- Aligned pre-send event processing in `sentry_capture_event()` with the
|
||||
[cross-SDK session filter order](https://develop.sentry.dev/sdk/sessions/#filter-order)
|
||||
([#729](https://github.com/getsentry/sentry-native/pull/729))
|
||||
- Align the default value initialization for the `environment` payload attribute with the
|
||||
[developer documentation](https://develop.sentry.dev/sdk/event-payloads/#optional-attribute)
|
||||
([#739](https://github.com/getsentry/sentry-native/pull/739))
|
||||
- Iterate all debug directory entries when parsing PE modules for a valid CodeView record
|
||||
([#740](https://github.com/getsentry/sentry-native/pull/740))
|
||||
|
||||
**Thank you**:
|
||||
|
||||
Features, fixes and improvements in this release have been contributed by:
|
||||
|
||||
- [@espkk](https://github.com/espkk)
|
||||
|
||||
## 0.4.18
|
||||
|
||||
**Features**:
|
||||
|
||||
- The crashpad backend now captures thread names. ([#725](https://github.com/getsentry/sentry-native/pull/725))
|
||||
- The inproc backend now captures the context registers. ([#714](https://github.com/getsentry/sentry-native/pull/714))
|
||||
- A new set of APIs to get the sentry SDK version at runtime. ([#726](https://github.com/getsentry/sentry-native/pull/726))
|
||||
- Add more convenient APIs to attach stack traces to exception or thread values. ([#723](https://github.com/getsentry/sentry-native/pull/723))
|
||||
- Allow disabling the crash reporting backend at runtime. ([#717](https://github.com/getsentry/sentry-native/pull/717))
|
||||
|
||||
**Fixes**:
|
||||
|
||||
- Improved heuristics flagging sessions as "crashed". ([#719](https://github.com/getsentry/sentry-native/pull/719))
|
||||
|
||||
**Internal**:
|
||||
|
||||
- Updated Breakpad and Crashpad backends to 2022-06-14. ([#725](https://github.com/getsentry/sentry-native/pull/725))
|
||||
|
||||
**Thank you**:
|
||||
|
||||
Features, fixes and improvements in this release have been contributed by:
|
||||
|
||||
- [@olback](https://github.com/olback)
|
||||
|
||||
## 0.4.17
|
||||
|
||||
**Fixes**:
|
||||
|
||||
- sentry-native now successfully builds when examples aren't included. ([#702](https://github.com/getsentry/sentry-native/pull/702))
|
||||
|
||||
**Thank you**:
|
||||
|
||||
Features, fixes and improvements in this release have been contributed by:
|
||||
|
||||
- [@AenBleidd](https://github.com/AenBleidd)
|
||||
|
||||
## 0.4.16
|
||||
|
||||
**Features**:
|
||||
|
||||
- Removed the `SENTRY_PERFORMANCE_MONITORING` compile flag requirement to access performance monitoring in the Sentry SDK. Performance monitoring is now available to everybody who has opted into the experimental API.
|
||||
- New API to check whether the application has crashed in the previous run: `sentry_get_crashed_last_run()` and `sentry_clear_crashed_last_run()` ([#685](https://github.com/getsentry/sentry-native/pull/685)).
|
||||
- Allow overriding the SDK name at build time - set the `SENTRY_SDK_NAME` CMake cache variable.
|
||||
- More aggressively prune the Crashpad database. ([#698](https://github.com/getsentry/sentry-native/pull/698))
|
||||
|
||||
**Internal**:
|
||||
|
||||
- Project IDs are now treated as opaque strings instead of integer values. ([#690](https://github.com/getsentry/sentry-native/pull/690))
|
||||
- Updated Breakpad and Crashpad backends to 2022-04-12. ([#696](https://github.com/getsentry/sentry-native/pull/696))
|
||||
|
||||
**Fixes**:
|
||||
|
||||
- Updated CI as well as list of supported platforms to reflect Windows Server 2016, and therefore MSVC 2017 losing active support.
|
||||
- Correctly free Windows Mutexes in Crashpad backend.
|
||||
|
||||
**Thank you**:
|
||||
|
||||
Features, fixes and improvements in this release have been contributed by:
|
||||
|
||||
- [@zhaowq32](https://github.com/zhaowq32)
|
||||
|
||||
## 0.4.15
|
||||
|
||||
**Fixes**:
|
||||
|
|
|
@ -30,7 +30,7 @@ if(NOT CMAKE_C_STANDARD)
|
|||
endif()
|
||||
|
||||
if(NOT CMAKE_CXX_STANDARD)
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
endif()
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
@ -151,9 +151,12 @@ if(SENTRY_BACKEND_CRASHPAD AND ANDROID)
|
|||
message(FATAL_ERROR "The Crashpad backend is not currently supported on Android")
|
||||
endif()
|
||||
|
||||
set(SENTRY_SDK_NAME "" CACHE STRING "The SDK name to report when sending events.")
|
||||
|
||||
message(STATUS "SENTRY_TRANSPORT=${SENTRY_TRANSPORT}")
|
||||
message(STATUS "SENTRY_BACKEND=${SENTRY_BACKEND}")
|
||||
message(STATUS "SENTRY_LIBRARY_TYPE=${SENTRY_LIBRARY_TYPE}")
|
||||
message(STATUS "SENTRY_SDK_NAME=${SENTRY_SDK_NAME}")
|
||||
|
||||
if(ANDROID)
|
||||
set(SENTRY_WITH_LIBUNWINDSTACK TRUE)
|
||||
|
@ -221,6 +224,10 @@ target_sources(sentry PRIVATE "${PROJECT_SOURCE_DIR}/include/sentry.h")
|
|||
add_library(sentry::sentry ALIAS sentry)
|
||||
add_subdirectory(src)
|
||||
|
||||
if (NOT SENTRY_SDK_NAME STREQUAL "")
|
||||
target_compile_definitions(sentry PRIVATE SENTRY_SDK_NAME="${SENTRY_SDK_NAME}")
|
||||
endif()
|
||||
|
||||
# we do not need this on android, only linux
|
||||
if(LINUX)
|
||||
target_sources(sentry PRIVATE
|
||||
|
@ -559,8 +566,6 @@ if(SENTRY_BUILD_EXAMPLES)
|
|||
add_executable(sentry_example examples/example.c)
|
||||
target_link_libraries(sentry_example PRIVATE sentry)
|
||||
|
||||
target_compile_definitions(sentry_example PRIVATE SENTRY_PERFORMANCE_MONITORING)
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(sentry_example PRIVATE $<BUILD_INTERFACE:/wd5105>)
|
||||
endif()
|
||||
|
|
|
@ -12,6 +12,7 @@ Building and testing `sentry-native` currently requires the following tools:
|
|||
- **CMake** and a supported C/C++ compiler, to actually build the code.
|
||||
- **python** and **pytest**, to run integration tests.
|
||||
- **clang-format** and **black**, to format the C/C++ and python code respectively.
|
||||
- **curl** and **zlib** libraries (e.g. on Ubuntu: libcurl4-openssl-dev, libz-dev)
|
||||
|
||||
`pytest` and `black` are installed as virtualenv dependencies automatically.
|
||||
|
||||
|
@ -138,3 +139,8 @@ The example currently supports the following commends:
|
|||
- `capture-multiple`: Captures a number of events.
|
||||
- `sleep`: Introduces a 10 second sleep.
|
||||
- `add-stacktrace`: Adds the current thread stacktrace to the captured event.
|
||||
- `disable-backend`: Disables the build-configured crash-handler backend.
|
||||
- `before-send`: Installs a `before_send()` callback that retains the event.
|
||||
- `discarding-before-send`: Installs a `before_send()` callback that retains the event.
|
||||
- `on-crash`: Installs an `on_crash()` callback that retains the crash event.
|
||||
- `discarding-on-crash`: Installs an `on_crash()` callback that discards the crash event.
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
<p align="center">
|
||||
<a href="https://sentry.io" target="_blank" align="center">
|
||||
<img src="https://sentry-brand.storage.googleapis.com/sentry-logo-black.png" width="280">
|
||||
<a href="https://sentry.io/?utm_source=github&utm_medium=logo" target="_blank">
|
||||
<picture>
|
||||
<source srcset="https://sentry-brand.storage.googleapis.com/sentry-logo-white.png" media="(prefers-color-scheme: dark)" />
|
||||
<source srcset="https://sentry-brand.storage.googleapis.com/sentry-logo-black.png" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" />
|
||||
<img src="https://sentry-brand.storage.googleapis.com/sentry-logo-black.png" alt="Sentry" width="280">
|
||||
</picture>
|
||||
</a>
|
||||
<br />
|
||||
</p>
|
||||
|
||||
# Official Sentry SDK for C/C++ <!-- omit in toc -->
|
||||
|
@ -58,8 +61,8 @@ The SDK currently supports and is tested on the following OS/Compiler variations
|
|||
- 64bit Linux with GCC 9
|
||||
- 64bit Linux with clang 9
|
||||
- 32bit Linux with GCC 7 (cross compiled from 64bit host)
|
||||
- 64bit Windows with MSVC 2019
|
||||
- 32bit Windows with MSVC 2017
|
||||
- 32bit Windows with MSVC 2019
|
||||
- 64bit Windows with MSVC 2022
|
||||
- macOS Catalina with most recent Compiler toolchain
|
||||
- Android API29 built by NDK21 toolchain
|
||||
- Android API16 built by NDK19 toolchain
|
||||
|
@ -271,6 +274,10 @@ Legend:
|
|||
and the result will be submitted to Sentry attached to the generated minidump.
|
||||
Note that this feature is still experimental.
|
||||
|
||||
- `SENTRY_SDK_NAME` (Default: sentry.native or sentry.native.android):
|
||||
Sets the SDK name that should be included in the reported events. If you're overriding this, make sure to also define
|
||||
the same value using `target_compile_definitions()` on your own targets that include `sentry.h`.
|
||||
|
||||
### Build Targets
|
||||
|
||||
- `sentry`: This is the main library and the only default build target.
|
||||
|
|
|
@ -23,6 +23,54 @@
|
|||
# define sleep_s(SECONDS) sleep(SECONDS)
|
||||
#endif
|
||||
|
||||
static sentry_value_t
|
||||
before_send_callback(sentry_value_t event, void *hint, void *closure)
|
||||
{
|
||||
(void)hint;
|
||||
(void)closure;
|
||||
|
||||
// make our mark on the event
|
||||
sentry_value_set_by_key(
|
||||
event, "adapted_by", sentry_value_new_string("before_send"));
|
||||
|
||||
// tell the backend to proceed with the event
|
||||
return event;
|
||||
}
|
||||
|
||||
static sentry_value_t
|
||||
discarding_before_send_callback(sentry_value_t event, void *hint, void *closure)
|
||||
{
|
||||
(void)hint;
|
||||
(void)closure;
|
||||
|
||||
// discard event and signal backend to stop further processing
|
||||
sentry_value_decref(event);
|
||||
return sentry_value_new_null();
|
||||
}
|
||||
|
||||
static sentry_value_t
|
||||
discarding_on_crash_callback(
|
||||
const sentry_ucontext_t *uctx, sentry_value_t event, void *closure)
|
||||
{
|
||||
(void)uctx;
|
||||
(void)closure;
|
||||
|
||||
// discard event and signal backend to stop further processing
|
||||
sentry_value_decref(event);
|
||||
return sentry_value_new_null();
|
||||
}
|
||||
|
||||
static sentry_value_t
|
||||
on_crash_callback(
|
||||
const sentry_ucontext_t *uctx, sentry_value_t event, void *closure)
|
||||
{
|
||||
(void)uctx;
|
||||
(void)closure;
|
||||
|
||||
// tell the backend to retain the event
|
||||
return event;
|
||||
}
|
||||
|
||||
static void
|
||||
print_envelope(sentry_envelope_t *envelope, void *unused_state)
|
||||
{
|
||||
|
@ -65,6 +113,10 @@ main(int argc, char **argv)
|
|||
{
|
||||
sentry_options_t *options = sentry_options_new();
|
||||
|
||||
if (has_arg(argc, argv, "disable-backend")) {
|
||||
sentry_options_set_backend(options, NULL);
|
||||
}
|
||||
|
||||
// this is an example. for real usage, make sure to set this explicitly to
|
||||
// an app specific cache location.
|
||||
sentry_options_set_database_path(options, ".sentry-native");
|
||||
|
@ -93,7 +145,6 @@ main(int argc, char **argv)
|
|||
options, sentry_transport_new(print_envelope));
|
||||
}
|
||||
|
||||
#ifdef SENTRY_PERFORMANCE_MONITORING
|
||||
if (has_arg(argc, argv, "capture-transaction")) {
|
||||
sentry_options_set_traces_sample_rate(options, 1.0);
|
||||
}
|
||||
|
@ -101,7 +152,24 @@ main(int argc, char **argv)
|
|||
if (has_arg(argc, argv, "child-spans")) {
|
||||
sentry_options_set_max_spans(options, 5);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (has_arg(argc, argv, "before-send")) {
|
||||
sentry_options_set_before_send(options, before_send_callback, NULL);
|
||||
}
|
||||
|
||||
if (has_arg(argc, argv, "discarding-before-send")) {
|
||||
sentry_options_set_before_send(
|
||||
options, discarding_before_send_callback, NULL);
|
||||
}
|
||||
|
||||
if (has_arg(argc, argv, "on-crash")) {
|
||||
sentry_options_set_on_crash(options, on_crash_callback, NULL);
|
||||
}
|
||||
|
||||
if (has_arg(argc, argv, "discarding-on-crash")) {
|
||||
sentry_options_set_on_crash(
|
||||
options, discarding_on_crash_callback, NULL);
|
||||
}
|
||||
|
||||
sentry_init(options);
|
||||
|
||||
|
@ -209,8 +277,7 @@ main(int argc, char **argv)
|
|||
sentry_value_t exc = sentry_value_new_exception(
|
||||
"ParseIntError", "invalid digit found in string");
|
||||
if (has_arg(argc, argv, "add-stacktrace")) {
|
||||
sentry_value_t stacktrace = sentry_value_new_stacktrace(NULL, 0);
|
||||
sentry_value_set_by_key(exc, "stacktrace", stacktrace);
|
||||
sentry_value_set_stacktrace(exc, NULL, 0);
|
||||
}
|
||||
sentry_value_t event = sentry_value_new_event();
|
||||
sentry_event_add_exception(event, exc);
|
||||
|
@ -218,7 +285,6 @@ main(int argc, char **argv)
|
|||
sentry_capture_event(event);
|
||||
}
|
||||
|
||||
#ifdef SENTRY_PERFORMANCE_MONITORING
|
||||
if (has_arg(argc, argv, "capture-transaction")) {
|
||||
sentry_transaction_context_t *tx_ctx
|
||||
= sentry_transaction_context_new("little.teapot",
|
||||
|
@ -253,7 +319,6 @@ main(int argc, char **argv)
|
|||
|
||||
sentry_transaction_finish(tx);
|
||||
}
|
||||
#endif
|
||||
|
||||
// make sure everything flushes
|
||||
sentry_close();
|
||||
|
|
|
@ -74,6 +74,9 @@ set(BREAKPAD_SOURCES_CLIENT_LINUX
|
|||
breakpad/src/client/linux/minidump_writer/linux_dumper.cc
|
||||
breakpad/src/client/linux/minidump_writer/linux_ptrace_dumper.cc
|
||||
breakpad/src/client/linux/minidump_writer/minidump_writer.cc
|
||||
breakpad/src/client/linux/minidump_writer/pe_file.cc
|
||||
breakpad/src/client/linux/minidump_writer/pe_file.h
|
||||
breakpad/src/client/linux/minidump_writer/pe_structs.h
|
||||
)
|
||||
|
||||
set(BREAKPAD_SOURCES_CLIENT_WINDOWS
|
||||
|
@ -91,13 +94,13 @@ set(BREAKPAD_SOURCES_CLIENT_APPLE
|
|||
breakpad/src/client/mac/handler/breakpad_nlist_64.h
|
||||
breakpad/src/client/mac/handler/dynamic_images.cc
|
||||
breakpad/src/client/mac/handler/dynamic_images.h
|
||||
breakpad/src/client/mac/handler/exception_handler.cc
|
||||
breakpad/src/client/mac/handler/exception_handler.h
|
||||
breakpad/src/client/mac/handler/minidump_generator.cc
|
||||
breakpad/src/client/mac/handler/minidump_generator.h
|
||||
)
|
||||
|
||||
set(BREAKPAD_SOURCES_CLIENT_MAC
|
||||
breakpad/src/client/mac/handler/exception_handler.cc
|
||||
breakpad/src/client/mac/handler/exception_handler.h
|
||||
breakpad/src/client/mac/crash_generation/crash_generation_client.cc
|
||||
breakpad/src/client/mac/crash_generation/crash_generation_client.h
|
||||
)
|
||||
|
@ -115,12 +118,12 @@ set(BREAKPAD_SOURCES_CLIENT_IOS
|
|||
breakpad/src/client/mac/handler/ucontext_compat.h
|
||||
)
|
||||
|
||||
|
||||
add_library(breakpad_client STATIC)
|
||||
target_sources(breakpad_client PRIVATE ${BREAKPAD_SOURCES_COMMON})
|
||||
|
||||
if(LINUX OR ANDROID)
|
||||
target_sources(breakpad_client PRIVATE ${BREAKPAD_SOURCES_COMMON_LINUX} ${BREAKPAD_SOURCES_CLIENT_LINUX})
|
||||
|
||||
if(ANDROID)
|
||||
target_sources(breakpad_client PRIVATE ${BREAKPAD_SOURCES_COMMON_ANDROID})
|
||||
target_include_directories(breakpad_client PRIVATE breakpad/src/common/android/include)
|
||||
|
@ -128,6 +131,7 @@ if(LINUX OR ANDROID)
|
|||
|
||||
include(CheckFunctionExists)
|
||||
check_function_exists(getcontext HAVE_GETCONTEXT)
|
||||
|
||||
if(HAVE_GETCONTEXT)
|
||||
target_compile_definitions(breakpad_client PRIVATE HAVE_GETCONTEXT)
|
||||
else()
|
||||
|
@ -141,6 +145,7 @@ if(APPLE)
|
|||
target_sources(breakpad_client PRIVATE
|
||||
${BREAKPAD_SOURCES_COMMON_APPLE}
|
||||
${BREAKPAD_SOURCES_CLIENT_APPLE})
|
||||
|
||||
if(NOT IOS)
|
||||
target_sources(breakpad_client PRIVATE
|
||||
${BREAKPAD_SOURCES_COMMON_MAC}
|
||||
|
@ -167,8 +172,8 @@ endif()
|
|||
# which are being resolved correctly when we add the current directory to
|
||||
# the include directories. A giant hack, yes, but it works
|
||||
target_include_directories(breakpad_client
|
||||
PRIVATE
|
||||
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/breakpad/src/>"
|
||||
PUBLIC
|
||||
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>"
|
||||
PRIVATE
|
||||
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/breakpad/src/>"
|
||||
PUBLIC
|
||||
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>"
|
||||
)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# GitHub actions workflow.
|
||||
# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions
|
||||
|
||||
# https://scan.coverity.com/projects/gentoo-pax-utils
|
||||
# https://scan.coverity.com/projects/google-breakpad
|
||||
name: Coverity Scan
|
||||
|
||||
on:
|
||||
|
@ -36,9 +36,8 @@ jobs:
|
|||
- run: ./configure --disable-silent-rules
|
||||
working-directory: src
|
||||
|
||||
- uses: vapier/coverity-scan-action@v0
|
||||
- uses: vapier/coverity-scan-action@v1
|
||||
with:
|
||||
project: google%2Fbreakpad
|
||||
command: make -C src -O -j$(getconf _NPROCESSORS_CONF)
|
||||
email: google-breakpad-dev@googlegroups.com
|
||||
email: ${{ secrets.COVERITY_SCAN_EMAIL }}
|
||||
token: ${{ secrets.COVERITY_SCAN_TOKEN }}
|
||||
|
|
|
@ -168,6 +168,7 @@ src_client_linux_libbreakpad_client_a_SOURCES = \
|
|||
src/client/linux/minidump_writer/linux_dumper.cc \
|
||||
src/client/linux/minidump_writer/linux_ptrace_dumper.cc \
|
||||
src/client/linux/minidump_writer/minidump_writer.cc \
|
||||
src/client/linux/minidump_writer/pe_file.cc \
|
||||
src/client/minidump_file_writer-inl.h \
|
||||
src/client/minidump_file_writer.cc \
|
||||
src/client/minidump_file_writer.h \
|
||||
|
@ -491,6 +492,7 @@ src_client_linux_linux_client_unittest_shlib_SOURCES = \
|
|||
src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc \
|
||||
src/client/linux/minidump_writer/minidump_writer_unittest.cc \
|
||||
src/client/linux/minidump_writer/minidump_writer_unittest_utils.cc \
|
||||
src/client/linux/minidump_writer/pe_file.cc \
|
||||
src/client/linux/minidump_writer/proc_cpuinfo_reader_unittest.cc \
|
||||
src/common/linux/elf_core_dump.cc \
|
||||
src/common/linux/linux_libc_support_unittest.cc \
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Makefile.in generated by automake 1.16.3 from Makefile.am.
|
||||
# Makefile.in generated by automake 1.16.5 from Makefile.am.
|
||||
# @configure_input@
|
||||
|
||||
# Copyright (C) 1994-2020 Free Software Foundation, Inc.
|
||||
# Copyright (C) 1994-2021 Free Software Foundation, Inc.
|
||||
|
||||
# This Makefile.in is free software; the Free Software Foundation
|
||||
# gives unlimited permission to copy and/or distribute it,
|
||||
|
@ -360,6 +360,7 @@ am__src_client_linux_libbreakpad_client_a_SOURCES_DIST = \
|
|||
src/client/linux/minidump_writer/linux_dumper.cc \
|
||||
src/client/linux/minidump_writer/linux_ptrace_dumper.cc \
|
||||
src/client/linux/minidump_writer/minidump_writer.cc \
|
||||
src/client/linux/minidump_writer/pe_file.cc \
|
||||
src/client/minidump_file_writer-inl.h \
|
||||
src/client/minidump_file_writer.cc \
|
||||
src/client/minidump_file_writer.h src/common/convert_UTF.cc \
|
||||
|
@ -387,6 +388,7 @@ am__dirstamp = $(am__leading_dot)dirstamp
|
|||
@LINUX_HOST_TRUE@ src/client/linux/minidump_writer/linux_dumper.$(OBJEXT) \
|
||||
@LINUX_HOST_TRUE@ src/client/linux/minidump_writer/linux_ptrace_dumper.$(OBJEXT) \
|
||||
@LINUX_HOST_TRUE@ src/client/linux/minidump_writer/minidump_writer.$(OBJEXT) \
|
||||
@LINUX_HOST_TRUE@ src/client/linux/minidump_writer/pe_file.$(OBJEXT) \
|
||||
@LINUX_HOST_TRUE@ src/client/minidump_file_writer.$(OBJEXT) \
|
||||
@LINUX_HOST_TRUE@ src/common/convert_UTF.$(OBJEXT) \
|
||||
@LINUX_HOST_TRUE@ src/common/md5.$(OBJEXT) \
|
||||
|
@ -632,6 +634,7 @@ am__src_client_linux_linux_client_unittest_shlib_SOURCES_DIST = \
|
|||
src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc \
|
||||
src/client/linux/minidump_writer/minidump_writer_unittest.cc \
|
||||
src/client/linux/minidump_writer/minidump_writer_unittest_utils.cc \
|
||||
src/client/linux/minidump_writer/pe_file.cc \
|
||||
src/client/linux/minidump_writer/proc_cpuinfo_reader_unittest.cc \
|
||||
src/common/linux/elf_core_dump.cc \
|
||||
src/common/linux/linux_libc_support_unittest.cc \
|
||||
|
@ -665,6 +668,7 @@ am__src_client_linux_linux_client_unittest_shlib_SOURCES_DIST = \
|
|||
@LINUX_HOST_TRUE@ src/client/linux/minidump_writer/linux_client_unittest_shlib-linux_ptrace_dumper_unittest.$(OBJEXT) \
|
||||
@LINUX_HOST_TRUE@ src/client/linux/minidump_writer/linux_client_unittest_shlib-minidump_writer_unittest.$(OBJEXT) \
|
||||
@LINUX_HOST_TRUE@ src/client/linux/minidump_writer/linux_client_unittest_shlib-minidump_writer_unittest_utils.$(OBJEXT) \
|
||||
@LINUX_HOST_TRUE@ src/client/linux/minidump_writer/linux_client_unittest_shlib-pe_file.$(OBJEXT) \
|
||||
@LINUX_HOST_TRUE@ src/client/linux/minidump_writer/linux_client_unittest_shlib-proc_cpuinfo_reader_unittest.$(OBJEXT) \
|
||||
@LINUX_HOST_TRUE@ src/common/linux/client_linux_linux_client_unittest_shlib-elf_core_dump.$(OBJEXT) \
|
||||
@LINUX_HOST_TRUE@ src/common/linux/client_linux_linux_client_unittest_shlib-linux_libc_support_unittest.$(OBJEXT) \
|
||||
|
@ -1645,12 +1649,14 @@ am__depfiles_remade = src/client/$(DEPDIR)/minidump_file_writer.Po \
|
|||
src/client/linux/minidump_writer/$(DEPDIR)/linux_client_unittest_shlib-linux_ptrace_dumper_unittest.Po \
|
||||
src/client/linux/minidump_writer/$(DEPDIR)/linux_client_unittest_shlib-minidump_writer_unittest.Po \
|
||||
src/client/linux/minidump_writer/$(DEPDIR)/linux_client_unittest_shlib-minidump_writer_unittest_utils.Po \
|
||||
src/client/linux/minidump_writer/$(DEPDIR)/linux_client_unittest_shlib-pe_file.Po \
|
||||
src/client/linux/minidump_writer/$(DEPDIR)/linux_client_unittest_shlib-proc_cpuinfo_reader_unittest.Po \
|
||||
src/client/linux/minidump_writer/$(DEPDIR)/linux_core_dumper.Po \
|
||||
src/client/linux/minidump_writer/$(DEPDIR)/linux_dumper.Po \
|
||||
src/client/linux/minidump_writer/$(DEPDIR)/linux_dumper_unittest_helper-linux_dumper_unittest_helper.Po \
|
||||
src/client/linux/minidump_writer/$(DEPDIR)/linux_ptrace_dumper.Po \
|
||||
src/client/linux/minidump_writer/$(DEPDIR)/minidump_writer.Po \
|
||||
src/client/linux/minidump_writer/$(DEPDIR)/pe_file.Po \
|
||||
src/common/$(DEPDIR)/client_linux_linux_client_unittest_shlib-memory_allocator_unittest.Po \
|
||||
src/common/$(DEPDIR)/convert_UTF.Po \
|
||||
src/common/$(DEPDIR)/dumper_unittest-byte_cursor_unittest.Po \
|
||||
|
@ -2101,9 +2107,6 @@ am__define_uniq_tagged_files = \
|
|||
unique=`for i in $$list; do \
|
||||
if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
|
||||
done | $(am__uniquify_input)`
|
||||
ETAGS = etags
|
||||
CTAGS = ctags
|
||||
CSCOPE = cscope
|
||||
AM_RECURSIVE_TARGETS = cscope check recheck
|
||||
am__tty_colors_dummy = \
|
||||
mgn= red= grn= lgn= blu= brg= std=; \
|
||||
|
@ -2291,9 +2294,9 @@ am__DIST_COMMON = $(srcdir)/Makefile.in \
|
|||
$(top_srcdir)/autotools/missing \
|
||||
$(top_srcdir)/autotools/test-driver \
|
||||
$(top_srcdir)/src/config.h.in AUTHORS ChangeLog INSTALL NEWS \
|
||||
autotools/ar-lib autotools/compile autotools/config.guess \
|
||||
autotools/config.sub autotools/depcomp autotools/install-sh \
|
||||
autotools/ltmain.sh autotools/missing
|
||||
README.md autotools/ar-lib autotools/compile \
|
||||
autotools/config.guess autotools/config.sub autotools/depcomp \
|
||||
autotools/install-sh autotools/ltmain.sh autotools/missing
|
||||
DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
|
||||
distdir = $(PACKAGE)-$(VERSION)
|
||||
top_distdir = $(distdir)
|
||||
|
@ -2329,6 +2332,8 @@ CCDEPMODE = @CCDEPMODE@
|
|||
CFLAGS = @CFLAGS@
|
||||
CPP = @CPP@
|
||||
CPPFLAGS = @CPPFLAGS@
|
||||
CSCOPE = @CSCOPE@
|
||||
CTAGS = @CTAGS@
|
||||
CXX = @CXX@
|
||||
CXXDEPMODE = @CXXDEPMODE@
|
||||
CXXFLAGS = @CXXFLAGS@
|
||||
|
@ -2339,6 +2344,7 @@ ECHO_C = @ECHO_C@
|
|||
ECHO_N = @ECHO_N@
|
||||
ECHO_T = @ECHO_T@
|
||||
EGREP = @EGREP@
|
||||
ETAGS = @ETAGS@
|
||||
EXEEXT = @EXEEXT@
|
||||
GMOCK_CFLAGS = @GMOCK_CFLAGS@
|
||||
GMOCK_LIBS = @GMOCK_LIBS@
|
||||
|
@ -2513,6 +2519,7 @@ CLEANFILES = $(am__append_12)
|
|||
@LINUX_HOST_TRUE@ src/client/linux/minidump_writer/linux_dumper.cc \
|
||||
@LINUX_HOST_TRUE@ src/client/linux/minidump_writer/linux_ptrace_dumper.cc \
|
||||
@LINUX_HOST_TRUE@ src/client/linux/minidump_writer/minidump_writer.cc \
|
||||
@LINUX_HOST_TRUE@ src/client/linux/minidump_writer/pe_file.cc \
|
||||
@LINUX_HOST_TRUE@ src/client/minidump_file_writer-inl.h \
|
||||
@LINUX_HOST_TRUE@ src/client/minidump_file_writer.cc \
|
||||
@LINUX_HOST_TRUE@ src/client/minidump_file_writer.h \
|
||||
|
@ -2718,6 +2725,7 @@ TESTS = $(check_PROGRAMS) $(check_SCRIPTS)
|
|||
@LINUX_HOST_TRUE@ src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc \
|
||||
@LINUX_HOST_TRUE@ src/client/linux/minidump_writer/minidump_writer_unittest.cc \
|
||||
@LINUX_HOST_TRUE@ src/client/linux/minidump_writer/minidump_writer_unittest_utils.cc \
|
||||
@LINUX_HOST_TRUE@ src/client/linux/minidump_writer/pe_file.cc \
|
||||
@LINUX_HOST_TRUE@ src/client/linux/minidump_writer/proc_cpuinfo_reader_unittest.cc \
|
||||
@LINUX_HOST_TRUE@ src/common/linux/elf_core_dump.cc \
|
||||
@LINUX_HOST_TRUE@ src/common/linux/linux_libc_support_unittest.cc \
|
||||
|
@ -4151,6 +4159,9 @@ src/client/linux/minidump_writer/linux_ptrace_dumper.$(OBJEXT): \
|
|||
src/client/linux/minidump_writer/minidump_writer.$(OBJEXT): \
|
||||
src/client/linux/minidump_writer/$(am__dirstamp) \
|
||||
src/client/linux/minidump_writer/$(DEPDIR)/$(am__dirstamp)
|
||||
src/client/linux/minidump_writer/pe_file.$(OBJEXT): \
|
||||
src/client/linux/minidump_writer/$(am__dirstamp) \
|
||||
src/client/linux/minidump_writer/$(DEPDIR)/$(am__dirstamp)
|
||||
src/client/$(am__dirstamp):
|
||||
@$(MKDIR_P) src/client
|
||||
@: > src/client/$(am__dirstamp)
|
||||
|
@ -4452,6 +4463,9 @@ src/client/linux/minidump_writer/linux_client_unittest_shlib-minidump_writer_uni
|
|||
src/client/linux/minidump_writer/linux_client_unittest_shlib-minidump_writer_unittest_utils.$(OBJEXT): \
|
||||
src/client/linux/minidump_writer/$(am__dirstamp) \
|
||||
src/client/linux/minidump_writer/$(DEPDIR)/$(am__dirstamp)
|
||||
src/client/linux/minidump_writer/linux_client_unittest_shlib-pe_file.$(OBJEXT): \
|
||||
src/client/linux/minidump_writer/$(am__dirstamp) \
|
||||
src/client/linux/minidump_writer/$(DEPDIR)/$(am__dirstamp)
|
||||
src/client/linux/minidump_writer/linux_client_unittest_shlib-proc_cpuinfo_reader_unittest.$(OBJEXT): \
|
||||
src/client/linux/minidump_writer/$(am__dirstamp) \
|
||||
src/client/linux/minidump_writer/$(DEPDIR)/$(am__dirstamp)
|
||||
|
@ -5357,12 +5371,14 @@ distclean-compile:
|
|||
@AMDEP_TRUE@@am__include@ @am__quote@src/client/linux/minidump_writer/$(DEPDIR)/linux_client_unittest_shlib-linux_ptrace_dumper_unittest.Po@am__quote@ # am--include-marker
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/client/linux/minidump_writer/$(DEPDIR)/linux_client_unittest_shlib-minidump_writer_unittest.Po@am__quote@ # am--include-marker
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/client/linux/minidump_writer/$(DEPDIR)/linux_client_unittest_shlib-minidump_writer_unittest_utils.Po@am__quote@ # am--include-marker
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/client/linux/minidump_writer/$(DEPDIR)/linux_client_unittest_shlib-pe_file.Po@am__quote@ # am--include-marker
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/client/linux/minidump_writer/$(DEPDIR)/linux_client_unittest_shlib-proc_cpuinfo_reader_unittest.Po@am__quote@ # am--include-marker
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/client/linux/minidump_writer/$(DEPDIR)/linux_core_dumper.Po@am__quote@ # am--include-marker
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/client/linux/minidump_writer/$(DEPDIR)/linux_dumper.Po@am__quote@ # am--include-marker
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/client/linux/minidump_writer/$(DEPDIR)/linux_dumper_unittest_helper-linux_dumper_unittest_helper.Po@am__quote@ # am--include-marker
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/client/linux/minidump_writer/$(DEPDIR)/linux_ptrace_dumper.Po@am__quote@ # am--include-marker
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/client/linux/minidump_writer/$(DEPDIR)/minidump_writer.Po@am__quote@ # am--include-marker
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/client/linux/minidump_writer/$(DEPDIR)/pe_file.Po@am__quote@ # am--include-marker
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/common/$(DEPDIR)/client_linux_linux_client_unittest_shlib-memory_allocator_unittest.Po@am__quote@ # am--include-marker
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/common/$(DEPDIR)/convert_UTF.Po@am__quote@ # am--include-marker
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/common/$(DEPDIR)/dumper_unittest-byte_cursor_unittest.Po@am__quote@ # am--include-marker
|
||||
|
@ -5921,6 +5937,20 @@ src/client/linux/minidump_writer/linux_client_unittest_shlib-minidump_writer_uni
|
|||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
|
||||
@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_shlib_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o src/client/linux/minidump_writer/linux_client_unittest_shlib-minidump_writer_unittest_utils.obj `if test -f 'src/client/linux/minidump_writer/minidump_writer_unittest_utils.cc'; then $(CYGPATH_W) 'src/client/linux/minidump_writer/minidump_writer_unittest_utils.cc'; else $(CYGPATH_W) '$(srcdir)/src/client/linux/minidump_writer/minidump_writer_unittest_utils.cc'; fi`
|
||||
|
||||
src/client/linux/minidump_writer/linux_client_unittest_shlib-pe_file.o: src/client/linux/minidump_writer/pe_file.cc
|
||||
@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_shlib_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT src/client/linux/minidump_writer/linux_client_unittest_shlib-pe_file.o -MD -MP -MF src/client/linux/minidump_writer/$(DEPDIR)/linux_client_unittest_shlib-pe_file.Tpo -c -o src/client/linux/minidump_writer/linux_client_unittest_shlib-pe_file.o `test -f 'src/client/linux/minidump_writer/pe_file.cc' || echo '$(srcdir)/'`src/client/linux/minidump_writer/pe_file.cc
|
||||
@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) src/client/linux/minidump_writer/$(DEPDIR)/linux_client_unittest_shlib-pe_file.Tpo src/client/linux/minidump_writer/$(DEPDIR)/linux_client_unittest_shlib-pe_file.Po
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='src/client/linux/minidump_writer/pe_file.cc' object='src/client/linux/minidump_writer/linux_client_unittest_shlib-pe_file.o' libtool=no @AMDEPBACKSLASH@
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
|
||||
@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_shlib_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o src/client/linux/minidump_writer/linux_client_unittest_shlib-pe_file.o `test -f 'src/client/linux/minidump_writer/pe_file.cc' || echo '$(srcdir)/'`src/client/linux/minidump_writer/pe_file.cc
|
||||
|
||||
src/client/linux/minidump_writer/linux_client_unittest_shlib-pe_file.obj: src/client/linux/minidump_writer/pe_file.cc
|
||||
@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_shlib_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT src/client/linux/minidump_writer/linux_client_unittest_shlib-pe_file.obj -MD -MP -MF src/client/linux/minidump_writer/$(DEPDIR)/linux_client_unittest_shlib-pe_file.Tpo -c -o src/client/linux/minidump_writer/linux_client_unittest_shlib-pe_file.obj `if test -f 'src/client/linux/minidump_writer/pe_file.cc'; then $(CYGPATH_W) 'src/client/linux/minidump_writer/pe_file.cc'; else $(CYGPATH_W) '$(srcdir)/src/client/linux/minidump_writer/pe_file.cc'; fi`
|
||||
@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) src/client/linux/minidump_writer/$(DEPDIR)/linux_client_unittest_shlib-pe_file.Tpo src/client/linux/minidump_writer/$(DEPDIR)/linux_client_unittest_shlib-pe_file.Po
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='src/client/linux/minidump_writer/pe_file.cc' object='src/client/linux/minidump_writer/linux_client_unittest_shlib-pe_file.obj' libtool=no @AMDEPBACKSLASH@
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
|
||||
@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_shlib_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o src/client/linux/minidump_writer/linux_client_unittest_shlib-pe_file.obj `if test -f 'src/client/linux/minidump_writer/pe_file.cc'; then $(CYGPATH_W) 'src/client/linux/minidump_writer/pe_file.cc'; else $(CYGPATH_W) '$(srcdir)/src/client/linux/minidump_writer/pe_file.cc'; fi`
|
||||
|
||||
src/client/linux/minidump_writer/linux_client_unittest_shlib-proc_cpuinfo_reader_unittest.o: src/client/linux/minidump_writer/proc_cpuinfo_reader_unittest.cc
|
||||
@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_shlib_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT src/client/linux/minidump_writer/linux_client_unittest_shlib-proc_cpuinfo_reader_unittest.o -MD -MP -MF src/client/linux/minidump_writer/$(DEPDIR)/linux_client_unittest_shlib-proc_cpuinfo_reader_unittest.Tpo -c -o src/client/linux/minidump_writer/linux_client_unittest_shlib-proc_cpuinfo_reader_unittest.o `test -f 'src/client/linux/minidump_writer/proc_cpuinfo_reader_unittest.cc' || echo '$(srcdir)/'`src/client/linux/minidump_writer/proc_cpuinfo_reader_unittest.cc
|
||||
@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) src/client/linux/minidump_writer/$(DEPDIR)/linux_client_unittest_shlib-proc_cpuinfo_reader_unittest.Tpo src/client/linux/minidump_writer/$(DEPDIR)/linux_client_unittest_shlib-proc_cpuinfo_reader_unittest.Po
|
||||
|
@ -9131,7 +9161,6 @@ src/processor/minidump_stackwalk_machine_readable_test.log: src/processor/minidu
|
|||
@am__EXEEXT_TRUE@ --log-file $$b.log --trs-file $$b.trs \
|
||||
@am__EXEEXT_TRUE@ $(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \
|
||||
@am__EXEEXT_TRUE@ "$$tst" $(AM_TESTS_FD_REDIRECT)
|
||||
|
||||
distdir: $(BUILT_SOURCES)
|
||||
$(MAKE) $(AM_MAKEFLAGS) distdir-am
|
||||
|
||||
|
@ -9427,12 +9456,14 @@ distclean: distclean-am
|
|||
-rm -f src/client/linux/minidump_writer/$(DEPDIR)/linux_client_unittest_shlib-linux_ptrace_dumper_unittest.Po
|
||||
-rm -f src/client/linux/minidump_writer/$(DEPDIR)/linux_client_unittest_shlib-minidump_writer_unittest.Po
|
||||
-rm -f src/client/linux/minidump_writer/$(DEPDIR)/linux_client_unittest_shlib-minidump_writer_unittest_utils.Po
|
||||
-rm -f src/client/linux/minidump_writer/$(DEPDIR)/linux_client_unittest_shlib-pe_file.Po
|
||||
-rm -f src/client/linux/minidump_writer/$(DEPDIR)/linux_client_unittest_shlib-proc_cpuinfo_reader_unittest.Po
|
||||
-rm -f src/client/linux/minidump_writer/$(DEPDIR)/linux_core_dumper.Po
|
||||
-rm -f src/client/linux/minidump_writer/$(DEPDIR)/linux_dumper.Po
|
||||
-rm -f src/client/linux/minidump_writer/$(DEPDIR)/linux_dumper_unittest_helper-linux_dumper_unittest_helper.Po
|
||||
-rm -f src/client/linux/minidump_writer/$(DEPDIR)/linux_ptrace_dumper.Po
|
||||
-rm -f src/client/linux/minidump_writer/$(DEPDIR)/minidump_writer.Po
|
||||
-rm -f src/client/linux/minidump_writer/$(DEPDIR)/pe_file.Po
|
||||
-rm -f src/common/$(DEPDIR)/client_linux_linux_client_unittest_shlib-memory_allocator_unittest.Po
|
||||
-rm -f src/common/$(DEPDIR)/convert_UTF.Po
|
||||
-rm -f src/common/$(DEPDIR)/dumper_unittest-byte_cursor_unittest.Po
|
||||
|
@ -9770,12 +9801,14 @@ maintainer-clean: maintainer-clean-am
|
|||
-rm -f src/client/linux/minidump_writer/$(DEPDIR)/linux_client_unittest_shlib-linux_ptrace_dumper_unittest.Po
|
||||
-rm -f src/client/linux/minidump_writer/$(DEPDIR)/linux_client_unittest_shlib-minidump_writer_unittest.Po
|
||||
-rm -f src/client/linux/minidump_writer/$(DEPDIR)/linux_client_unittest_shlib-minidump_writer_unittest_utils.Po
|
||||
-rm -f src/client/linux/minidump_writer/$(DEPDIR)/linux_client_unittest_shlib-pe_file.Po
|
||||
-rm -f src/client/linux/minidump_writer/$(DEPDIR)/linux_client_unittest_shlib-proc_cpuinfo_reader_unittest.Po
|
||||
-rm -f src/client/linux/minidump_writer/$(DEPDIR)/linux_core_dumper.Po
|
||||
-rm -f src/client/linux/minidump_writer/$(DEPDIR)/linux_dumper.Po
|
||||
-rm -f src/client/linux/minidump_writer/$(DEPDIR)/linux_dumper_unittest_helper-linux_dumper_unittest_helper.Po
|
||||
-rm -f src/client/linux/minidump_writer/$(DEPDIR)/linux_ptrace_dumper.Po
|
||||
-rm -f src/client/linux/minidump_writer/$(DEPDIR)/minidump_writer.Po
|
||||
-rm -f src/client/linux/minidump_writer/$(DEPDIR)/pe_file.Po
|
||||
-rm -f src/common/$(DEPDIR)/client_linux_linux_client_unittest_shlib-memory_allocator_unittest.Po
|
||||
-rm -f src/common/$(DEPDIR)/convert_UTF.Po
|
||||
-rm -f src/common/$(DEPDIR)/dumper_unittest-byte_cursor_unittest.Po
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# generated automatically by aclocal 1.16.3 -*- Autoconf -*-
|
||||
# generated automatically by aclocal 1.16.5 -*- Autoconf -*-
|
||||
|
||||
# Copyright (C) 1996-2020 Free Software Foundation, Inc.
|
||||
# Copyright (C) 1996-2021 Free Software Foundation, Inc.
|
||||
|
||||
# This file is free software; the Free Software Foundation
|
||||
# gives unlimited permission to copy and/or distribute it,
|
||||
|
@ -14,13 +14,13 @@
|
|||
m4_ifndef([AC_CONFIG_MACRO_DIRS], [m4_defun([_AM_CONFIG_MACRO_DIRS], [])m4_defun([AC_CONFIG_MACRO_DIRS], [_AM_CONFIG_MACRO_DIRS($@)])])
|
||||
m4_ifndef([AC_AUTOCONF_VERSION],
|
||||
[m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl
|
||||
m4_if(m4_defn([AC_AUTOCONF_VERSION]), [2.69],,
|
||||
[m4_warning([this file was generated for autoconf 2.69.
|
||||
m4_if(m4_defn([AC_AUTOCONF_VERSION]), [2.71],,
|
||||
[m4_warning([this file was generated for autoconf 2.71.
|
||||
You have another version of autoconf. It may work, but is not guaranteed to.
|
||||
If you have problems, you may need to regenerate the build system entirely.
|
||||
To do so, use the procedure documented by the package, typically 'autoreconf'.])])
|
||||
|
||||
# Copyright (C) 2002-2020 Free Software Foundation, Inc.
|
||||
# Copyright (C) 2002-2021 Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is free software; the Free Software Foundation
|
||||
# gives unlimited permission to copy and/or distribute it,
|
||||
|
@ -35,7 +35,7 @@ AC_DEFUN([AM_AUTOMAKE_VERSION],
|
|||
[am__api_version='1.16'
|
||||
dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to
|
||||
dnl require some minimum version. Point them to the right macro.
|
||||
m4_if([$1], [1.16.3], [],
|
||||
m4_if([$1], [1.16.5], [],
|
||||
[AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl
|
||||
])
|
||||
|
||||
|
@ -51,12 +51,12 @@ m4_define([_AM_AUTOCONF_VERSION], [])
|
|||
# Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced.
|
||||
# This function is AC_REQUIREd by AM_INIT_AUTOMAKE.
|
||||
AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION],
|
||||
[AM_AUTOMAKE_VERSION([1.16.3])dnl
|
||||
[AM_AUTOMAKE_VERSION([1.16.5])dnl
|
||||
m4_ifndef([AC_AUTOCONF_VERSION],
|
||||
[m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl
|
||||
_AM_AUTOCONF_VERSION(m4_defn([AC_AUTOCONF_VERSION]))])
|
||||
|
||||
# Copyright (C) 2011-2020 Free Software Foundation, Inc.
|
||||
# Copyright (C) 2011-2021 Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is free software; the Free Software Foundation
|
||||
# gives unlimited permission to copy and/or distribute it,
|
||||
|
@ -118,7 +118,7 @@ AC_SUBST([AR])dnl
|
|||
|
||||
# Figure out how to run the assembler. -*- Autoconf -*-
|
||||
|
||||
# Copyright (C) 2001-2020 Free Software Foundation, Inc.
|
||||
# Copyright (C) 2001-2021 Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is free software; the Free Software Foundation
|
||||
# gives unlimited permission to copy and/or distribute it,
|
||||
|
@ -138,7 +138,7 @@ _AM_IF_OPTION([no-dependencies],, [_AM_DEPENDENCIES([CCAS])])dnl
|
|||
|
||||
# AM_AUX_DIR_EXPAND -*- Autoconf -*-
|
||||
|
||||
# Copyright (C) 2001-2020 Free Software Foundation, Inc.
|
||||
# Copyright (C) 2001-2021 Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is free software; the Free Software Foundation
|
||||
# gives unlimited permission to copy and/or distribute it,
|
||||
|
@ -190,7 +190,7 @@ am_aux_dir=`cd "$ac_aux_dir" && pwd`
|
|||
|
||||
# AM_CONDITIONAL -*- Autoconf -*-
|
||||
|
||||
# Copyright (C) 1997-2020 Free Software Foundation, Inc.
|
||||
# Copyright (C) 1997-2021 Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is free software; the Free Software Foundation
|
||||
# gives unlimited permission to copy and/or distribute it,
|
||||
|
@ -221,7 +221,7 @@ AC_CONFIG_COMMANDS_PRE(
|
|||
Usually this means the macro was only invoked conditionally.]])
|
||||
fi])])
|
||||
|
||||
# Copyright (C) 1999-2020 Free Software Foundation, Inc.
|
||||
# Copyright (C) 1999-2021 Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is free software; the Free Software Foundation
|
||||
# gives unlimited permission to copy and/or distribute it,
|
||||
|
@ -412,7 +412,7 @@ _AM_SUBST_NOTMAKE([am__nodep])dnl
|
|||
|
||||
# Generate code to set up dependency tracking. -*- Autoconf -*-
|
||||
|
||||
# Copyright (C) 1999-2020 Free Software Foundation, Inc.
|
||||
# Copyright (C) 1999-2021 Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is free software; the Free Software Foundation
|
||||
# gives unlimited permission to copy and/or distribute it,
|
||||
|
@ -480,7 +480,7 @@ AC_DEFUN([AM_OUTPUT_DEPENDENCY_COMMANDS],
|
|||
|
||||
# Do all the work for Automake. -*- Autoconf -*-
|
||||
|
||||
# Copyright (C) 1996-2020 Free Software Foundation, Inc.
|
||||
# Copyright (C) 1996-2021 Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is free software; the Free Software Foundation
|
||||
# gives unlimited permission to copy and/or distribute it,
|
||||
|
@ -508,6 +508,10 @@ m4_defn([AC_PROG_CC])
|
|||
# release and drop the old call support.
|
||||
AC_DEFUN([AM_INIT_AUTOMAKE],
|
||||
[AC_PREREQ([2.65])dnl
|
||||
m4_ifdef([_$0_ALREADY_INIT],
|
||||
[m4_fatal([$0 expanded multiple times
|
||||
]m4_defn([_$0_ALREADY_INIT]))],
|
||||
[m4_define([_$0_ALREADY_INIT], m4_expansion_stack)])dnl
|
||||
dnl Autoconf wants to disallow AM_ names. We explicitly allow
|
||||
dnl the ones we care about.
|
||||
m4_pattern_allow([^AM_[A-Z]+FLAGS$])dnl
|
||||
|
@ -544,7 +548,7 @@ m4_ifval([$3], [_AM_SET_OPTION([no-define])])dnl
|
|||
[_AM_SET_OPTIONS([$1])dnl
|
||||
dnl Diagnose old-style AC_INIT with new-style AM_AUTOMAKE_INIT.
|
||||
m4_if(
|
||||
m4_ifdef([AC_PACKAGE_NAME], [ok]):m4_ifdef([AC_PACKAGE_VERSION], [ok]),
|
||||
m4_ifset([AC_PACKAGE_NAME], [ok]):m4_ifset([AC_PACKAGE_VERSION], [ok]),
|
||||
[ok:ok],,
|
||||
[m4_fatal([AC_INIT should be called with package and version arguments])])dnl
|
||||
AC_SUBST([PACKAGE], ['AC_PACKAGE_TARNAME'])dnl
|
||||
|
@ -596,6 +600,20 @@ AC_PROVIDE_IFELSE([AC_PROG_OBJCXX],
|
|||
[m4_define([AC_PROG_OBJCXX],
|
||||
m4_defn([AC_PROG_OBJCXX])[_AM_DEPENDENCIES([OBJCXX])])])dnl
|
||||
])
|
||||
# Variables for tags utilities; see am/tags.am
|
||||
if test -z "$CTAGS"; then
|
||||
CTAGS=ctags
|
||||
fi
|
||||
AC_SUBST([CTAGS])
|
||||
if test -z "$ETAGS"; then
|
||||
ETAGS=etags
|
||||
fi
|
||||
AC_SUBST([ETAGS])
|
||||
if test -z "$CSCOPE"; then
|
||||
CSCOPE=cscope
|
||||
fi
|
||||
AC_SUBST([CSCOPE])
|
||||
|
||||
AC_REQUIRE([AM_SILENT_RULES])dnl
|
||||
dnl The testsuite driver may need to know about EXEEXT, so add the
|
||||
dnl 'am__EXEEXT' conditional if _AM_COMPILER_EXEEXT was seen. This
|
||||
|
@ -677,7 +695,7 @@ for _am_header in $config_headers :; do
|
|||
done
|
||||
echo "timestamp for $_am_arg" >`AS_DIRNAME(["$_am_arg"])`/stamp-h[]$_am_stamp_count])
|
||||
|
||||
# Copyright (C) 2001-2020 Free Software Foundation, Inc.
|
||||
# Copyright (C) 2001-2021 Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is free software; the Free Software Foundation
|
||||
# gives unlimited permission to copy and/or distribute it,
|
||||
|
@ -698,7 +716,7 @@ if test x"${install_sh+set}" != xset; then
|
|||
fi
|
||||
AC_SUBST([install_sh])])
|
||||
|
||||
# Copyright (C) 2003-2020 Free Software Foundation, Inc.
|
||||
# Copyright (C) 2003-2021 Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is free software; the Free Software Foundation
|
||||
# gives unlimited permission to copy and/or distribute it,
|
||||
|
@ -720,7 +738,7 @@ AC_SUBST([am__leading_dot])])
|
|||
# Add --enable-maintainer-mode option to configure. -*- Autoconf -*-
|
||||
# From Jim Meyering
|
||||
|
||||
# Copyright (C) 1996-2020 Free Software Foundation, Inc.
|
||||
# Copyright (C) 1996-2021 Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is free software; the Free Software Foundation
|
||||
# gives unlimited permission to copy and/or distribute it,
|
||||
|
@ -755,7 +773,7 @@ AC_MSG_CHECKING([whether to enable maintainer-specific portions of Makefiles])
|
|||
|
||||
# Check to see how 'make' treats includes. -*- Autoconf -*-
|
||||
|
||||
# Copyright (C) 2001-2020 Free Software Foundation, Inc.
|
||||
# Copyright (C) 2001-2021 Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is free software; the Free Software Foundation
|
||||
# gives unlimited permission to copy and/or distribute it,
|
||||
|
@ -798,7 +816,7 @@ AC_SUBST([am__quote])])
|
|||
|
||||
# Fake the existence of programs that GNU maintainers use. -*- Autoconf -*-
|
||||
|
||||
# Copyright (C) 1997-2020 Free Software Foundation, Inc.
|
||||
# Copyright (C) 1997-2021 Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is free software; the Free Software Foundation
|
||||
# gives unlimited permission to copy and/or distribute it,
|
||||
|
@ -834,7 +852,7 @@ fi
|
|||
# Obsolete and "removed" macros, that must however still report explicit
|
||||
# error messages when used, to smooth transition.
|
||||
#
|
||||
# Copyright (C) 1996-2020 Free Software Foundation, Inc.
|
||||
# Copyright (C) 1996-2021 Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is free software; the Free Software Foundation
|
||||
# gives unlimited permission to copy and/or distribute it,
|
||||
|
@ -861,7 +879,7 @@ AU_DEFUN([fp_C_PROTOTYPES], [AM_C_PROTOTYPES])
|
|||
|
||||
# Helper functions for option handling. -*- Autoconf -*-
|
||||
|
||||
# Copyright (C) 2001-2020 Free Software Foundation, Inc.
|
||||
# Copyright (C) 2001-2021 Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is free software; the Free Software Foundation
|
||||
# gives unlimited permission to copy and/or distribute it,
|
||||
|
@ -890,7 +908,7 @@ AC_DEFUN([_AM_SET_OPTIONS],
|
|||
AC_DEFUN([_AM_IF_OPTION],
|
||||
[m4_ifset(_AM_MANGLE_OPTION([$1]), [$2], [$3])])
|
||||
|
||||
# Copyright (C) 1999-2020 Free Software Foundation, Inc.
|
||||
# Copyright (C) 1999-2021 Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is free software; the Free Software Foundation
|
||||
# gives unlimited permission to copy and/or distribute it,
|
||||
|
@ -937,7 +955,7 @@ AC_LANG_POP([C])])
|
|||
# For backward compatibility.
|
||||
AC_DEFUN_ONCE([AM_PROG_CC_C_O], [AC_REQUIRE([AC_PROG_CC])])
|
||||
|
||||
# Copyright (C) 2001-2020 Free Software Foundation, Inc.
|
||||
# Copyright (C) 2001-2021 Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is free software; the Free Software Foundation
|
||||
# gives unlimited permission to copy and/or distribute it,
|
||||
|
@ -956,7 +974,7 @@ AC_DEFUN([AM_RUN_LOG],
|
|||
|
||||
# Check to make sure that the build environment is sane. -*- Autoconf -*-
|
||||
|
||||
# Copyright (C) 1996-2020 Free Software Foundation, Inc.
|
||||
# Copyright (C) 1996-2021 Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is free software; the Free Software Foundation
|
||||
# gives unlimited permission to copy and/or distribute it,
|
||||
|
@ -1037,7 +1055,7 @@ AC_CONFIG_COMMANDS_PRE(
|
|||
rm -f conftest.file
|
||||
])
|
||||
|
||||
# Copyright (C) 2009-2020 Free Software Foundation, Inc.
|
||||
# Copyright (C) 2009-2021 Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is free software; the Free Software Foundation
|
||||
# gives unlimited permission to copy and/or distribute it,
|
||||
|
@ -1097,7 +1115,7 @@ AC_SUBST([AM_BACKSLASH])dnl
|
|||
_AM_SUBST_NOTMAKE([AM_BACKSLASH])dnl
|
||||
])
|
||||
|
||||
# Copyright (C) 2001-2020 Free Software Foundation, Inc.
|
||||
# Copyright (C) 2001-2021 Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is free software; the Free Software Foundation
|
||||
# gives unlimited permission to copy and/or distribute it,
|
||||
|
@ -1125,7 +1143,7 @@ fi
|
|||
INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s"
|
||||
AC_SUBST([INSTALL_STRIP_PROGRAM])])
|
||||
|
||||
# Copyright (C) 2006-2020 Free Software Foundation, Inc.
|
||||
# Copyright (C) 2006-2021 Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is free software; the Free Software Foundation
|
||||
# gives unlimited permission to copy and/or distribute it,
|
||||
|
@ -1144,7 +1162,7 @@ AC_DEFUN([AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE($@)])
|
|||
|
||||
# Check how to create a tarball. -*- Autoconf -*-
|
||||
|
||||
# Copyright (C) 2004-2020 Free Software Foundation, Inc.
|
||||
# Copyright (C) 2004-2021 Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is free software; the Free Software Foundation
|
||||
# gives unlimited permission to copy and/or distribute it,
|
||||
|
|
|
@ -33,8 +33,15 @@ restrictions, these may appear in any order.
|
|||
* A `FILE` record gives a source file name, and assigns it a number by which
|
||||
other records can refer to it.
|
||||
|
||||
* An `INLINE_ORIGIN` record holds an inline function name for `INLINE` records
|
||||
to refer to.
|
||||
|
||||
* A `FUNC` record describes a function present in the source code.
|
||||
|
||||
* An `INLINE` record describes the inline function's nest level, call site
|
||||
line and call site source file to which the given ranges of machine code
|
||||
should be attributed.
|
||||
|
||||
* A line record indicates to which source file and line a given range of
|
||||
machine code should be attributed. The line is attributed to the function
|
||||
defined by the most recent `FUNC` record.
|
||||
|
@ -62,16 +69,25 @@ for _name_.
|
|||
|
||||
* The _operatingsystem_ field names the operating system on which the
|
||||
executable or shared library was intended to run. This field should have one
|
||||
of the following values: | **Value** | **Meaning** |
|
||||
|:----------|:--------------------| | Linux | Linux | | mac | Macintosh OSX
|
||||
| | windows | Microsoft Windows |
|
||||
of the following values:
|
||||
|
||||
| **Value** | **Meaning** |
|
||||
|:----------|:--------------------|
|
||||
| Linux | Linux |
|
||||
| mac | Macintosh OSX |
|
||||
| windows | Microsoft Windows |
|
||||
|
||||
* The _architecture_ field indicates what processor architecture the
|
||||
executable or shared library contains machine code for. This field should
|
||||
have one of the following values: | **Value** | **Instruction Set
|
||||
Architecture** | |:----------|:---------------------------------| | x86 |
|
||||
Intel IA-32 | | x86\_64 | AMD64/Intel 64 | | ppc | 32-bit PowerPC | | ppc64
|
||||
| 64-bit PowerPC | | unknown | unknown |
|
||||
have one of the following values:
|
||||
|
||||
| **Value** | **Instruction Set Architecture** |
|
||||
|:----------|:---------------------------------|
|
||||
| x86 | Intel IA-32 |
|
||||
| x86\_64 | AMD64/Intel 64 |
|
||||
| ppc | 32-bit PowerPC |
|
||||
| ppc64 | 64-bit PowerPC |
|
||||
| unknown | unknown |
|
||||
|
||||
* The _id_ field is a sequence of hexadecimal digits that identifies the exact
|
||||
executable or library whose contents the symbol file describes. The way in
|
||||
|
@ -96,6 +112,22 @@ which other records (line records, in particular) can use to refer to that file
|
|||
name. The _number_ field is a decimal number. The _name_ field is the name of
|
||||
the file; it may contain spaces.
|
||||
|
||||
# `INLINE_ORIGIN` records
|
||||
|
||||
An `INLINE_ORIGIN` record holds an inline function name for `INLINE` records to
|
||||
refer to. It has the form:
|
||||
|
||||
> `INLINE_ORIGIN` _number_ _name_
|
||||
|
||||
For example: `INLINE_ORIGIN 2 nsQueryInterfaceWithError::operator()(nsID const&,
|
||||
void**) const
|
||||
`
|
||||
|
||||
An `INLINE_ORIGIN` record provides the name of an inline function, and assigns
|
||||
it a number which other records (`INLINE` records, in particular) can use to
|
||||
refer to that function name. The _number_ field is a decimal number. The _name_
|
||||
field is the name of the inline function; it may contain spaces.
|
||||
|
||||
# `FUNC` records
|
||||
|
||||
A `FUNC` record describes a source-language function. It has the form:
|
||||
|
@ -127,6 +159,49 @@ The _name_ field is the name of the function. In languages that use linker
|
|||
symbol name mangling like C++, this should be the source language name (the
|
||||
"unmangled" form). This field may contain spaces.
|
||||
|
||||
# `INLINE` records
|
||||
|
||||
An `INLINE` record describes the inline function's nest level, call site line
|
||||
and call site source file to which the given ranges of machine code should be
|
||||
attributed. It has the form:
|
||||
|
||||
> `INLINE` _inline_nest_level_ _call_site_line_ _call_site_file_num_
|
||||
> _origin_num_ [_address_ _size_]+
|
||||
|
||||
For example: `INLINE 0 10 3 4 d30 2a fa1 b
|
||||
`
|
||||
|
||||
The _inline_nest_level_ field is a decimal number that means it's inlined at the
|
||||
function described by a previous `INLINE` record which has _inline_nest_level_
|
||||
one less than its. In the example below, first and third `INLINE` records have
|
||||
_inline_nest_level_ 0, which means they are inlined inside the function
|
||||
described by the `FUNC` record. The second `INLINE` record has
|
||||
_inline_nest_level_ 1 means that it's inlined at the inline function described
|
||||
by first `INLINE` record.
|
||||
```
|
||||
FUNC ...
|
||||
INLINE 0 ...
|
||||
INLINE 1 ...
|
||||
INLINE 0 ...
|
||||
```
|
||||
|
||||
The _call_site_line_ and _call_site_file_num_ fields are decimal numbers
|
||||
indicating where this inline function being called at.
|
||||
|
||||
The _origin_num_ field refers to an `INLINE_ORIGIN` record that has the name
|
||||
of the inline function.
|
||||
|
||||
The _address_ and _size_ fields are hexadecimal numbers indicating the start
|
||||
address and length in bytes of the machine code. The address is relative to the
|
||||
module's load address. There could be more than one [_address_ _size_] range
|
||||
pair, since inline functions could have discontinuous address ranges. The ranges
|
||||
of an `INLINE` record are always inside the ranges described by its parent
|
||||
record (a `FUNC` record or an `INLINE` record).
|
||||
|
||||
The `INLINE` record is assumed to belong to the function described by the last
|
||||
preceding `FUNC` record. `INLINE` records may not appear before the first `FUNC`
|
||||
record.
|
||||
|
||||
# Line records
|
||||
|
||||
A line record describes the source file and line number to which a given range
|
||||
|
@ -396,14 +471,14 @@ func+22: pc = *sp; sp += 4 ; pop return address and jump to it
|
|||
|
||||
The following table would describe the function above:
|
||||
|
||||
**code address** | **.cfa** | **r0 (on Google Code)** | **r1 (on Google Code)** | ... | **.ra**
|
||||
:--------------- | :------- | :---------------------- | :---------------------- | :-- | :-------
|
||||
func+0 | sp | | | | `cfa[0]`
|
||||
func+1 | sp+16 | | | | `cfa[0]`
|
||||
func+2 | sp+16 | `cfa[-4]` | | | `cfa[0]`
|
||||
func+11 | sp+20 | `cfa[-4]` | | | `cfa[0]`
|
||||
func+21 | sp+20 | | | | `cfa[0]`
|
||||
func+22 | sp | | | | `cfa[0]`
|
||||
| **code address** | **.cfa** | **r0 (on Google Code)** | **r1 (on Google Code)** | ... | **.ra** |
|
||||
|:-----------------|:---------|:------------------------|:------------------------|:----|:---------|
|
||||
| func+0 | sp | | | | `cfa[0]` |
|
||||
| func+1 | sp+16 | | | | `cfa[0]` |
|
||||
| func+2 | sp+16 | `cfa[-4]` | | | `cfa[0]` |
|
||||
| func+11 | sp+20 | `cfa[-4]` | | | `cfa[0]` |
|
||||
| func+21 | sp+20 | | | | `cfa[0]` |
|
||||
| func+22 | sp | | | | `cfa[0]` |
|
||||
|
||||
Some things to note here:
|
||||
|
||||
|
@ -438,14 +513,14 @@ To save space, the most common type of CFI record only mentions the table
|
|||
entries at which changes take place. So for the above, the CFI data would only
|
||||
actually mention the non-blank entries here:
|
||||
|
||||
**insn** | **cfa** | **r0 (on Google Code)** | **r1 (on Google Code)** | ... | **ra**
|
||||
:------- | :------ | :---------------------- | :---------------------- | :-- | :-------
|
||||
func+0 | sp | | | | `cfa[0]`
|
||||
func+1 | sp+16 | | | |
|
||||
func+2 | | `cfa[-4]` | | |
|
||||
func+11 | sp+20 | | | |
|
||||
func+21 | | r0 (on Google Code) | | |
|
||||
func+22 | sp | | | |
|
||||
| **insn** | **cfa** | **r0 (on Google Code)** | **r1 (on Google Code)** | ... | **ra** |
|
||||
|:---------|:--------|:------------------------|:------------------------|:----|:---------|
|
||||
| func+0 | sp | | | | `cfa[0]` |
|
||||
| func+1 | sp+16 | | | | |
|
||||
| func+2 | | `cfa[-4]` | | | |
|
||||
| func+11 | sp+20 | | | | |
|
||||
| func+21 | | r0 (on Google Code) | | | |
|
||||
| func+22 | sp | | | | |
|
||||
|
||||
A `STACK CFI INIT` record indicates that, at the machine instruction at
|
||||
_address_, belonging to some function, the value that _register<sub>n</sub>_ had
|
||||
|
|
|
@ -138,7 +138,7 @@ void InstallAlternateStackLocked() {
|
|||
// SIGSTKSZ may be too small to prevent the signal handlers from overrunning
|
||||
// the alternative stack. Ensure that the size of the alternative stack is
|
||||
// large enough.
|
||||
static const unsigned kSigStackSize = std::max(16384, (int)SIGSTKSZ);
|
||||
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.
|
||||
|
|
|
@ -49,8 +49,8 @@
|
|||
namespace {
|
||||
|
||||
using google_breakpad::auto_wasteful_vector;
|
||||
using google_breakpad::elf::kDefaultBuildIdSize;
|
||||
using google_breakpad::ExceptionHandler;
|
||||
using google_breakpad::kDefaultBuildIdSize;
|
||||
using google_breakpad::LinuxDumper;
|
||||
using google_breakpad::LinuxPtraceDumper;
|
||||
using google_breakpad::MappingInfo;
|
||||
|
|
|
@ -53,6 +53,8 @@
|
|||
#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
|
||||
|
|
|
@ -63,6 +63,8 @@
|
|||
#endif
|
||||
|
||||
using namespace google_breakpad;
|
||||
using google_breakpad::elf::FileID;
|
||||
using google_breakpad::elf::kDefaultBuildIdSize;
|
||||
|
||||
namespace {
|
||||
|
||||
|
|
|
@ -71,6 +71,8 @@
|
|||
#include "client/linux/minidump_writer/line_reader.h"
|
||||
#include "client/linux/minidump_writer/linux_dumper.h"
|
||||
#include "client/linux/minidump_writer/linux_ptrace_dumper.h"
|
||||
#include "client/linux/minidump_writer/pe_file.h"
|
||||
#include "client/linux/minidump_writer/pe_structs.h"
|
||||
#include "client/linux/minidump_writer/proc_cpuinfo_reader.h"
|
||||
#include "client/minidump_file_writer.h"
|
||||
#include "common/linux/file_id.h"
|
||||
|
@ -83,9 +85,9 @@ namespace {
|
|||
|
||||
using google_breakpad::AppMemoryList;
|
||||
using google_breakpad::auto_wasteful_vector;
|
||||
using google_breakpad::elf::kDefaultBuildIdSize;
|
||||
using google_breakpad::ExceptionHandler;
|
||||
using google_breakpad::CpuSet;
|
||||
using google_breakpad::kDefaultBuildIdSize;
|
||||
using google_breakpad::LineReader;
|
||||
using google_breakpad::LinuxDumper;
|
||||
using google_breakpad::LinuxPtraceDumper;
|
||||
|
@ -95,8 +97,11 @@ using google_breakpad::MappingInfo;
|
|||
using google_breakpad::MappingList;
|
||||
using google_breakpad::MinidumpFileWriter;
|
||||
using google_breakpad::PageAllocator;
|
||||
using google_breakpad::PEFile;
|
||||
using google_breakpad::PEFileFormat;
|
||||
using google_breakpad::ProcCpuInfoReader;
|
||||
using google_breakpad::RawContextCPU;
|
||||
using google_breakpad::RSDS_DEBUG_FORMAT;
|
||||
using google_breakpad::ThreadInfo;
|
||||
using google_breakpad::TypedMDRVA;
|
||||
using google_breakpad::UContextReader;
|
||||
|
@ -632,40 +637,88 @@ class MinidumpWriter {
|
|||
mod->base_of_image = mapping.start_addr;
|
||||
mod->size_of_image = mapping.size;
|
||||
|
||||
auto_wasteful_vector<uint8_t, kDefaultBuildIdSize> identifier_bytes(
|
||||
dumper_->allocator());
|
||||
char file_name[NAME_MAX];
|
||||
char file_path[NAME_MAX];
|
||||
|
||||
if (identifier) {
|
||||
// GUID was provided by caller.
|
||||
identifier_bytes.insert(identifier_bytes.end(),
|
||||
identifier,
|
||||
identifier + sizeof(MDGUID));
|
||||
dumper_->GetMappingEffectiveNameAndPath(mapping, file_path,
|
||||
sizeof(file_path), file_name,
|
||||
sizeof(file_name));
|
||||
|
||||
RSDS_DEBUG_FORMAT rsds;
|
||||
PEFileFormat file_format = PEFile::TryGetDebugInfo(file_path, &rsds);
|
||||
|
||||
if (file_format == PEFileFormat::notPeCoff) {
|
||||
// The module is not a PE/COFF file, process as an ELF.
|
||||
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 {
|
||||
// Note: ElfFileIdentifierForMapping() can manipulate the
|
||||
// |mapping.name|, that is why we need to call the method
|
||||
// GetMappingEffectiveNameAndPath again.
|
||||
dumper_->ElfFileIdentifierForMapping(mapping, member, mapping_id,
|
||||
identifier_bytes);
|
||||
dumper_->GetMappingEffectiveNameAndPath(mapping, file_path,
|
||||
sizeof(file_path), file_name,
|
||||
sizeof(file_name));
|
||||
}
|
||||
|
||||
if (!identifier_bytes.empty()) {
|
||||
UntypedMDRVA cv(&minidump_writer_);
|
||||
if (!cv.Allocate(MDCVInfoELF_minsize + identifier_bytes.size()))
|
||||
return false;
|
||||
|
||||
const uint32_t cv_signature = MD_CVINFOELF_SIGNATURE;
|
||||
cv.Copy(&cv_signature, sizeof(cv_signature));
|
||||
cv.Copy(cv.position() + sizeof(cv_signature), &identifier_bytes[0],
|
||||
identifier_bytes.size());
|
||||
|
||||
mod->cv_record = cv.location();
|
||||
}
|
||||
} else {
|
||||
// Note: ElfFileIdentifierForMapping() can manipulate the |mapping.name|.
|
||||
dumper_->ElfFileIdentifierForMapping(mapping,
|
||||
member,
|
||||
mapping_id,
|
||||
identifier_bytes);
|
||||
}
|
||||
|
||||
if (!identifier_bytes.empty()) {
|
||||
UntypedMDRVA cv(&minidump_writer_);
|
||||
if (!cv.Allocate(MDCVInfoELF_minsize + identifier_bytes.size()))
|
||||
// The module is a PE/COFF file. Create MDCVInfoPDB70 struct for it.
|
||||
size_t file_name_length = strlen(file_name);
|
||||
TypedMDRVA<MDCVInfoPDB70> cv(&minidump_writer_);
|
||||
if (!cv.AllocateObjectAndArray(file_name_length + 1, sizeof(uint8_t)))
|
||||
return false;
|
||||
|
||||
const uint32_t cv_signature = MD_CVINFOELF_SIGNATURE;
|
||||
cv.Copy(&cv_signature, sizeof(cv_signature));
|
||||
cv.Copy(cv.position() + sizeof(cv_signature), &identifier_bytes[0],
|
||||
identifier_bytes.size());
|
||||
if (!cv.CopyIndexAfterObject(0, file_name, file_name_length))
|
||||
return false;
|
||||
MDCVInfoPDB70* cv_ptr = cv.get();
|
||||
cv_ptr->cv_signature = MD_CVINFOPDB70_SIGNATURE;
|
||||
if (file_format == PEFileFormat::peWithBuildId) {
|
||||
// Populate BuildId and age using RSDS instance.
|
||||
cv_ptr->signature.data1 = static_cast<uint32_t>(rsds.guid[0]) << 24 |
|
||||
static_cast<uint32_t>(rsds.guid[1]) << 16 |
|
||||
static_cast<uint32_t>(rsds.guid[2]) << 8 |
|
||||
static_cast<uint32_t>(rsds.guid[3]);
|
||||
cv_ptr->signature.data2 =
|
||||
static_cast<uint16_t>(rsds.guid[4]) << 8 | rsds.guid[5];
|
||||
cv_ptr->signature.data3 =
|
||||
static_cast<uint16_t>(rsds.guid[6]) << 8 | rsds.guid[7];
|
||||
cv_ptr->signature.data4[0] = rsds.guid[8];
|
||||
cv_ptr->signature.data4[1] = rsds.guid[9];
|
||||
cv_ptr->signature.data4[2] = rsds.guid[10];
|
||||
cv_ptr->signature.data4[3] = rsds.guid[11];
|
||||
cv_ptr->signature.data4[4] = rsds.guid[12];
|
||||
cv_ptr->signature.data4[5] = rsds.guid[13];
|
||||
cv_ptr->signature.data4[6] = rsds.guid[14];
|
||||
cv_ptr->signature.data4[7] = rsds.guid[15];
|
||||
// The Age field should be reverted as well.
|
||||
cv_ptr->age = static_cast<uint32_t>(rsds.age[0]) << 24 |
|
||||
static_cast<uint32_t>(rsds.age[1]) << 16 |
|
||||
static_cast<uint32_t>(rsds.age[2]) << 8 |
|
||||
static_cast<uint32_t>(rsds.age[3]);
|
||||
} else {
|
||||
cv_ptr->age = 0;
|
||||
}
|
||||
|
||||
mod->cv_record = cv.location();
|
||||
}
|
||||
|
||||
char file_name[NAME_MAX];
|
||||
char file_path[NAME_MAX];
|
||||
dumper_->GetMappingEffectiveNameAndPath(
|
||||
mapping, file_path, sizeof(file_path), file_name, sizeof(file_name));
|
||||
|
||||
MDLocationDescriptor ld;
|
||||
if (!minidump_writer_.WriteString(file_path, my_strlen(file_path), &ld))
|
||||
return false;
|
||||
|
|
|
@ -54,6 +54,8 @@
|
|||
#include "google_breakpad/processor/minidump.h"
|
||||
|
||||
using namespace google_breakpad;
|
||||
using google_breakpad::elf::FileID;
|
||||
using google_breakpad::elf::kDefaultBuildIdSize;
|
||||
|
||||
namespace {
|
||||
|
||||
|
|
148
thirdparty/sentry-native/external/breakpad/src/client/linux/minidump_writer/pe_file.cc
vendored
Normal file
148
thirdparty/sentry-native/external/breakpad/src/client/linux/minidump_writer/pe_file.cc
vendored
Normal file
|
@ -0,0 +1,148 @@
|
|||
// Copyright (c) 2022, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include <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
|
77
thirdparty/sentry-native/external/breakpad/src/client/linux/minidump_writer/pe_file.h
vendored
Normal file
77
thirdparty/sentry-native/external/breakpad/src/client/linux/minidump_writer/pe_file.h
vendored
Normal file
|
@ -0,0 +1,77 @@
|
|||
// Copyright (c) 2022, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef CLIENT_LINUX_MINIDUMP_WRITER_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_
|
226
thirdparty/sentry-native/external/breakpad/src/client/linux/minidump_writer/pe_structs.h
vendored
Normal file
226
thirdparty/sentry-native/external/breakpad/src/client/linux/minidump_writer/pe_structs.h
vendored
Normal file
|
@ -0,0 +1,226 @@
|
|||
// Copyright (c) 2022, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef CLIENT_LINUX_MINIDUMP_WRITER_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_
|
|
@ -62,6 +62,8 @@ using MacStringUtils::IntegerValueAtIndex;
|
|||
|
||||
namespace google_breakpad {
|
||||
|
||||
using mach_o::FileID;
|
||||
|
||||
#if defined(__LP64__) && __LP64__
|
||||
#define LC_SEGMENT_ARCH LC_SEGMENT_64
|
||||
#else
|
||||
|
@ -1449,7 +1451,7 @@ bool MinidumpGenerator::WriteCVRecord(MDRawModule* module, int cpu_type,
|
|||
unsigned char identifier[16];
|
||||
bool result = false;
|
||||
if (in_memory) {
|
||||
MacFileUtilities::MachoID macho(module_path,
|
||||
MacFileUtilities::MachoID macho(
|
||||
reinterpret_cast<void*>(module->base_of_image),
|
||||
static_cast<size_t>(module->size_of_image));
|
||||
result = macho.UUIDCommand(cpu_type, CPU_SUBTYPE_MULTIPLE, identifier);
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
namespace {
|
||||
|
||||
using namespace google_breakpad;
|
||||
using namespace google_breakpad::elf::FileID;
|
||||
|
||||
// Argument for the writer function.
|
||||
struct WriterArgument {
|
||||
|
|
|
@ -1793,7 +1793,7 @@ bool RangeListReader::ReadRanges(enum DwarfForm form, uint64_t data) {
|
|||
}
|
||||
} else if (form == DW_FORM_rnglistx) {
|
||||
offset_array_ = cu_info_->ranges_base_;
|
||||
uint64_t index_offset = reader_->AddressSize() * data;
|
||||
uint64_t index_offset = reader_->OffsetSize() * data;
|
||||
uint64_t range_list_offset =
|
||||
reader_->ReadOffset(cu_info_->buffer_ + offset_array_ + index_offset);
|
||||
|
||||
|
|
|
@ -184,9 +184,7 @@ string DwarfCFIToModule::RegisterName(int i) {
|
|||
return register_names_[reg];
|
||||
|
||||
reporter_->UnnamedRegister(entry_offset_, reg);
|
||||
char buf[30];
|
||||
sprintf(buf, "unnamed_register%u", reg);
|
||||
return buf;
|
||||
return string("unnamed_register") + std::to_string(reg);
|
||||
}
|
||||
|
||||
void DwarfCFIToModule::Record(Module::Address address, int reg,
|
||||
|
|
|
@ -254,9 +254,6 @@ struct DwarfCUToModule::CUContext {
|
|||
|
||||
// A map of function pointers to the its forward specification DIE's offset.
|
||||
map<Module::Function*, uint64_t> spec_function_offsets;
|
||||
|
||||
// From file index to vector of subprogram's offset in this CU.
|
||||
map<uint64_t, vector<uint64_t>> inline_origins;
|
||||
};
|
||||
|
||||
// Information about the context of a particular DIE. This is for
|
||||
|
@ -289,6 +286,7 @@ class DwarfCUToModule::GenericDIEHandler: public DIEHandler {
|
|||
offset_(offset),
|
||||
declaration_(false),
|
||||
specification_(NULL),
|
||||
no_specification(false),
|
||||
abstract_origin_(NULL),
|
||||
forward_ref_die_offset_(0), specification_offset_(0) { }
|
||||
|
||||
|
@ -334,6 +332,10 @@ class DwarfCUToModule::GenericDIEHandler: public DIEHandler {
|
|||
// Otherwise, this is NULL.
|
||||
Specification* specification_;
|
||||
|
||||
// If this DIE has DW_AT_specification with offset smaller than this DIE and
|
||||
// we can't find that in the specification map.
|
||||
bool no_specification;
|
||||
|
||||
// If this DIE has a DW_AT_abstract_origin attribute, this is the
|
||||
// AbstractOrigin structure for the DIE the attribute refers to.
|
||||
// Otherwise, this is NULL.
|
||||
|
@ -396,7 +398,7 @@ void DwarfCUToModule::GenericDIEHandler::ProcessAttributeReference(
|
|||
} else if (data > offset_) {
|
||||
forward_ref_die_offset_ = data;
|
||||
} else {
|
||||
cu_context_->reporter->UnknownSpecification(offset_, data);
|
||||
no_specification = true;
|
||||
}
|
||||
specification_offset_ = data;
|
||||
break;
|
||||
|
@ -482,7 +484,7 @@ StringView DwarfCUToModule::GenericDIEHandler::ComputeQualifiedName() {
|
|||
// counts; otherwise, use this DIE's context.
|
||||
if (specification_) {
|
||||
enclosing_name = &specification_->enclosing_name;
|
||||
} else {
|
||||
} else if (parent_context_) {
|
||||
enclosing_name = &parent_context_->name;
|
||||
}
|
||||
}
|
||||
|
@ -561,7 +563,8 @@ class DwarfCUToModule::InlineHandler : public GenericDIEHandler {
|
|||
DwarfForm high_pc_form_; // DW_AT_high_pc can be length or address.
|
||||
DwarfForm ranges_form_; // DW_FORM_sec_offset or DW_FORM_rnglistx
|
||||
uint64_t ranges_data_; // DW_AT_ranges
|
||||
int call_site_line_;
|
||||
int call_site_line_; // DW_AT_call_line
|
||||
int call_site_file_id_; // DW_AT_call_file
|
||||
int inline_nest_level_;
|
||||
// A vector of inlines in the same nest level. It's owned by its parent
|
||||
// function/inline. At Finish(), add this inline into the vector.
|
||||
|
@ -589,6 +592,9 @@ void DwarfCUToModule::InlineHandler::ProcessAttributeUnsigned(
|
|||
case DW_AT_call_line:
|
||||
call_site_line_ = data;
|
||||
break;
|
||||
case DW_AT_call_file:
|
||||
call_site_file_id_ = data;
|
||||
break;
|
||||
default:
|
||||
GenericDIEHandler::ProcessAttributeUnsigned(attr, form, data);
|
||||
break;
|
||||
|
@ -600,7 +606,7 @@ DIEHandler* DwarfCUToModule::InlineHandler::FindChildHandler(
|
|||
enum DwarfTag tag) {
|
||||
switch (tag) {
|
||||
case DW_TAG_inlined_subroutine:
|
||||
return new InlineHandler(cu_context_, new DIEContext(), offset,
|
||||
return new InlineHandler(cu_context_, nullptr, offset,
|
||||
inline_nest_level_ + 1, child_inlines_);
|
||||
default:
|
||||
return NULL;
|
||||
|
@ -652,6 +658,11 @@ void DwarfCUToModule::InlineHandler::Finish() {
|
|||
}
|
||||
}
|
||||
|
||||
// Ignore DW_TAG_inlined_subroutine with empty range.
|
||||
if (ranges.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Every DW_TAG_inlined_subroutine should have a DW_AT_abstract_origin.
|
||||
assert(specification_offset_ != 0);
|
||||
|
||||
|
@ -661,11 +672,29 @@ void DwarfCUToModule::InlineHandler::Finish() {
|
|||
cu_context_->file_context->module_->inline_origin_map
|
||||
.GetOrCreateInlineOrigin(specification_offset_, name_);
|
||||
unique_ptr<Module::Inline> in(
|
||||
new Module::Inline(origin, ranges, call_site_line_, inline_nest_level_,
|
||||
std::move(child_inlines_)));
|
||||
new Module::Inline(origin, ranges, call_site_line_, call_site_file_id_,
|
||||
inline_nest_level_, std::move(child_inlines_)));
|
||||
inlines_.push_back(std::move(in));
|
||||
}
|
||||
|
||||
// A handler for DIEs that contain functions and contribute a
|
||||
// component to their names: namespaces, classes, etc.
|
||||
class DwarfCUToModule::NamedScopeHandler: public GenericDIEHandler {
|
||||
public:
|
||||
NamedScopeHandler(CUContext* cu_context,
|
||||
DIEContext* parent_context,
|
||||
uint64_t offset,
|
||||
bool handle_inline)
|
||||
: GenericDIEHandler(cu_context, parent_context, offset),
|
||||
handle_inline_(handle_inline) {}
|
||||
bool EndAttributes();
|
||||
DIEHandler* FindChildHandler(uint64_t offset, enum DwarfTag tag);
|
||||
|
||||
private:
|
||||
DIEContext child_context_; // A context for our children.
|
||||
bool handle_inline_;
|
||||
};
|
||||
|
||||
// A handler class for DW_TAG_subprogram DIEs.
|
||||
class DwarfCUToModule::FuncHandler: public GenericDIEHandler {
|
||||
public:
|
||||
|
@ -679,7 +708,6 @@ class DwarfCUToModule::FuncHandler: public GenericDIEHandler {
|
|||
high_pc_form_(DW_FORM_addr),
|
||||
ranges_form_(DW_FORM_sec_offset),
|
||||
ranges_data_(0),
|
||||
decl_file_data_(UINT64_MAX),
|
||||
inline_(false),
|
||||
handle_inline_(handle_inline) {}
|
||||
|
||||
|
@ -700,12 +728,11 @@ class DwarfCUToModule::FuncHandler: public GenericDIEHandler {
|
|||
uint64_t low_pc_, high_pc_; // DW_AT_low_pc, DW_AT_high_pc
|
||||
DwarfForm high_pc_form_; // DW_AT_high_pc can be length or address.
|
||||
DwarfForm ranges_form_; // DW_FORM_sec_offset or DW_FORM_rnglistx
|
||||
uint64_t ranges_data_; // DW_AT_ranges
|
||||
// DW_AT_decl_file, value of UINT64_MAX means undefined.
|
||||
uint64_t decl_file_data_;
|
||||
uint64_t ranges_data_; // DW_AT_ranges
|
||||
bool inline_;
|
||||
vector<unique_ptr<Module::Inline>> child_inlines_;
|
||||
bool handle_inline_;
|
||||
DIEContext child_context_; // A context for our children.
|
||||
};
|
||||
|
||||
void DwarfCUToModule::FuncHandler::ProcessAttributeUnsigned(
|
||||
|
@ -727,9 +754,6 @@ void DwarfCUToModule::FuncHandler::ProcessAttributeUnsigned(
|
|||
ranges_data_ = data;
|
||||
ranges_form_ = form;
|
||||
break;
|
||||
case DW_AT_decl_file:
|
||||
decl_file_data_ = data;
|
||||
break;
|
||||
default:
|
||||
GenericDIEHandler::ProcessAttributeUnsigned(attr, form, data);
|
||||
break;
|
||||
|
@ -757,8 +781,13 @@ DIEHandler* DwarfCUToModule::FuncHandler::FindChildHandler(
|
|||
switch (tag) {
|
||||
case DW_TAG_inlined_subroutine:
|
||||
if (handle_inline_)
|
||||
return new InlineHandler(cu_context_, new DIEContext(), offset, 0,
|
||||
return new InlineHandler(cu_context_, nullptr, offset, 0,
|
||||
child_inlines_);
|
||||
case DW_TAG_class_type:
|
||||
case DW_TAG_structure_type:
|
||||
case DW_TAG_union_type:
|
||||
return new NamedScopeHandler(cu_context_, &child_context_, offset,
|
||||
handle_inline_);
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
|
@ -770,6 +799,10 @@ bool DwarfCUToModule::FuncHandler::EndAttributes() {
|
|||
if (name_.empty() && abstract_origin_) {
|
||||
name_ = abstract_origin_->name;
|
||||
}
|
||||
child_context_.name = name_;
|
||||
if (name_.empty() && no_specification) {
|
||||
cu_context_->reporter->UnknownSpecification(offset_, specification_offset_);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -857,8 +890,7 @@ void DwarfCUToModule::FuncHandler::Finish() {
|
|||
|
||||
// Only keep track of DW_TAG_subprogram which have the attributes we are
|
||||
// interested.
|
||||
if (handle_inline_ &&
|
||||
(!empty_range || inline_ || decl_file_data_ != UINT64_MAX)) {
|
||||
if (handle_inline_ && (!empty_range || inline_)) {
|
||||
StringView name = name_.empty() ? name_omitted : name_;
|
||||
uint64_t offset =
|
||||
specification_offset_ != 0 ? specification_offset_ : offset_;
|
||||
|
@ -866,29 +898,14 @@ void DwarfCUToModule::FuncHandler::Finish() {
|
|||
offset);
|
||||
cu_context_->file_context->module_->inline_origin_map
|
||||
.GetOrCreateInlineOrigin(offset_, name);
|
||||
if (decl_file_data_ != UINT64_MAX)
|
||||
cu_context_->inline_origins[decl_file_data_].push_back(offset_);
|
||||
}
|
||||
}
|
||||
|
||||
// A handler for DIEs that contain functions and contribute a
|
||||
// component to their names: namespaces, classes, etc.
|
||||
class DwarfCUToModule::NamedScopeHandler: public GenericDIEHandler {
|
||||
public:
|
||||
NamedScopeHandler(CUContext* cu_context, DIEContext* parent_context,
|
||||
uint64_t offset, bool handle_inline)
|
||||
: GenericDIEHandler(cu_context, parent_context, offset),
|
||||
handle_inline_(handle_inline) { }
|
||||
bool EndAttributes();
|
||||
DIEHandler* FindChildHandler(uint64_t offset, enum DwarfTag tag);
|
||||
|
||||
private:
|
||||
DIEContext child_context_; // A context for our children.
|
||||
bool handle_inline_;
|
||||
};
|
||||
|
||||
bool DwarfCUToModule::NamedScopeHandler::EndAttributes() {
|
||||
child_context_.name = ComputeQualifiedName();
|
||||
if (child_context_.name.empty() && no_specification) {
|
||||
cu_context_->reporter->UnknownSpecification(offset_, specification_offset_);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1450,10 +1467,12 @@ void DwarfCUToModule::AssignLinesToFunctions() {
|
|||
}
|
||||
|
||||
void DwarfCUToModule::AssignFilesToInlines() {
|
||||
for (auto iter : files_) {
|
||||
cu_context_->file_context->module_->inline_origin_map
|
||||
.AssignFilesToInlineOrigins(cu_context_->inline_origins[iter.first],
|
||||
iter.second);
|
||||
// Assign File* to Inlines inside this CU.
|
||||
auto assignFile = [this](unique_ptr<Module::Inline>& in) {
|
||||
in->call_site_file = files_[in->call_site_file_id];
|
||||
};
|
||||
for (auto func : cu_context_->functions) {
|
||||
Module::Inline::InlineDFS(func->inlines, assignFile);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -87,11 +87,11 @@ using google_breakpad::DwarfRangeListHandler;
|
|||
using google_breakpad::ElfClass;
|
||||
using google_breakpad::ElfClass32;
|
||||
using google_breakpad::ElfClass64;
|
||||
using google_breakpad::FileID;
|
||||
using google_breakpad::elf::FileID;
|
||||
using google_breakpad::FindElfSectionByName;
|
||||
using google_breakpad::GetOffset;
|
||||
using google_breakpad::IsValidElf;
|
||||
using google_breakpad::kDefaultBuildIdSize;
|
||||
using google_breakpad::elf::kDefaultBuildIdSize;
|
||||
using google_breakpad::Module;
|
||||
using google_breakpad::PageAllocator;
|
||||
#ifndef NO_STABS_SUPPORT
|
||||
|
|
|
@ -49,6 +49,7 @@
|
|||
#include "third_party/lss/linux_syscall_support.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
namespace elf {
|
||||
|
||||
// Used in a few places for backwards-compatibility.
|
||||
const size_t kMDGUIDSize = sizeof(MDGUID);
|
||||
|
@ -198,4 +199,5 @@ string FileID::ConvertIdentifierToString(
|
|||
return bytes_to_hex_string(&identifier[0], identifier.size());
|
||||
}
|
||||
|
||||
} // elf
|
||||
} // namespace google_breakpad
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
#include "common/using_std_string.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
namespace elf {
|
||||
|
||||
// GNU binutils' ld defaults to 'sha1', which is 160 bits == 20 bytes,
|
||||
// so this is enough to fit that, which most binaries will use.
|
||||
|
@ -83,6 +84,7 @@ class FileID {
|
|||
string path_;
|
||||
};
|
||||
|
||||
} // namespace elf
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // COMMON_LINUX_FILE_ID_H__
|
||||
|
|
|
@ -50,6 +50,8 @@
|
|||
#include "breakpad_googletest_includes.h"
|
||||
|
||||
using namespace google_breakpad;
|
||||
using google_breakpad::elf::FileID;
|
||||
using google_breakpad::elf::kDefaultBuildIdSize;
|
||||
using google_breakpad::synth_elf::ELF;
|
||||
using google_breakpad::synth_elf::Notes;
|
||||
using google_breakpad::test_assembler::kLittleEndian;
|
||||
|
|
|
@ -96,7 +96,8 @@ typedef NS_ENUM(NSInteger, SymbolStatus) {
|
|||
withUploadKey:(NSString*)uploadKey
|
||||
withDebugFile:(NSString*)debugFile
|
||||
withDebugID:(NSString*)debugID
|
||||
withType:(NSString*)type;
|
||||
withType:(NSString*)type
|
||||
withProductName:(NSString*)productName;
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -190,18 +190,22 @@
|
|||
withUploadKey:(NSString*)uploadKey
|
||||
withDebugFile:(NSString*)debugFile
|
||||
withDebugID:(NSString*)debugID
|
||||
withType:(NSString*)type {
|
||||
withType:(NSString*)type
|
||||
withProductName:(NSString*)productName {
|
||||
NSURL* URL = [NSURL
|
||||
URLWithString:[NSString
|
||||
stringWithFormat:@"%@/v1/uploads/%@:complete?key=%@",
|
||||
APIURL, uploadKey, APIKey]];
|
||||
|
||||
NSDictionary* symbolIdDictionary =
|
||||
[NSDictionary dictionaryWithObjectsAndKeys:debugFile, @"debug_file",
|
||||
debugID, @"debug_id", nil];
|
||||
NSDictionary* jsonDictionary = [NSDictionary
|
||||
dictionaryWithObjectsAndKeys:symbolIdDictionary, @"symbol_id", type,
|
||||
@"symbol_upload_type", nil];
|
||||
NSMutableDictionary* jsonDictionary = [@{
|
||||
@"symbol_id" : @{@"debug_file" : debugFile, @"debug_id" : debugID},
|
||||
@"symbol_upload_type" : type, @"use_async_processing" : @"true"
|
||||
} mutableCopy];
|
||||
|
||||
if (productName != nil) {
|
||||
jsonDictionary[@"metadata"] = @{@"product_name": productName};
|
||||
}
|
||||
|
||||
NSError* error = nil;
|
||||
NSData* jsonData =
|
||||
[NSJSONSerialization dataWithJSONObject:jsonDictionary
|
||||
|
|
|
@ -78,8 +78,8 @@ using google_breakpad::ByteReader;
|
|||
using google_breakpad::DwarfCUToModule;
|
||||
using google_breakpad::DwarfLineToModule;
|
||||
using google_breakpad::DwarfRangeListHandler;
|
||||
using google_breakpad::FileID;
|
||||
using google_breakpad::mach_o::FatReader;
|
||||
using google_breakpad::mach_o::FileID;
|
||||
using google_breakpad::mach_o::Section;
|
||||
using google_breakpad::mach_o::Segment;
|
||||
using google_breakpad::Module;
|
||||
|
@ -128,10 +128,11 @@ bool DumpSymbols::Read(const string& filename) {
|
|||
return false;
|
||||
}
|
||||
|
||||
input_pathname_ = filename;
|
||||
from_disk_ = true;
|
||||
|
||||
// Does this filename refer to a dSYM bundle?
|
||||
string contents_path = input_pathname_ + "/Contents/Resources/DWARF";
|
||||
string contents_path = filename + "/Contents/Resources/DWARF";
|
||||
string object_filename;
|
||||
if (S_ISDIR(st.st_mode) &&
|
||||
access(contents_path.c_str(), F_OK) == 0) {
|
||||
// If there's one file under Contents/Resources/DWARF then use that,
|
||||
|
@ -139,30 +140,31 @@ bool DumpSymbols::Read(const string& filename) {
|
|||
const vector<string> entries = list_directory(contents_path);
|
||||
if (entries.size() == 0) {
|
||||
fprintf(stderr, "Unable to find DWARF-bearing file in bundle: %s\n",
|
||||
input_pathname_.c_str());
|
||||
filename.c_str());
|
||||
return false;
|
||||
}
|
||||
if (entries.size() > 1) {
|
||||
fprintf(stderr, "Too many DWARF files in bundle: %s\n",
|
||||
input_pathname_.c_str());
|
||||
filename.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
object_filename_ = entries[0];
|
||||
object_filename = entries[0];
|
||||
} else {
|
||||
object_filename_ = input_pathname_;
|
||||
object_filename = filename;
|
||||
}
|
||||
|
||||
// Read the file's contents into memory.
|
||||
bool read_ok = true;
|
||||
string error;
|
||||
if (stat(object_filename_.c_str(), &st) != -1) {
|
||||
FILE* f = fopen(object_filename_.c_str(), "rb");
|
||||
scoped_array<uint8_t> contents;
|
||||
off_t total = 0;
|
||||
if (stat(object_filename.c_str(), &st) != -1) {
|
||||
FILE* f = fopen(object_filename.c_str(), "rb");
|
||||
if (f) {
|
||||
contents_.reset(new uint8_t[st.st_size]);
|
||||
off_t total = 0;
|
||||
contents.reset(new uint8_t[st.st_size]);
|
||||
while (total < st.st_size && !feof(f)) {
|
||||
size_t read = fread(&contents_[0] + total, 1, st.st_size - total, f);
|
||||
size_t read = fread(&contents[0] + total, 1, st.st_size - total, f);
|
||||
if (read == 0) {
|
||||
if (ferror(f)) {
|
||||
read_ok = false;
|
||||
|
@ -180,16 +182,22 @@ bool DumpSymbols::Read(const string& filename) {
|
|||
|
||||
if (!read_ok) {
|
||||
fprintf(stderr, "Error reading object file: %s: %s\n",
|
||||
object_filename_.c_str(),
|
||||
error.c_str());
|
||||
object_filename.c_str(), error.c_str());
|
||||
return false;
|
||||
}
|
||||
return ReadData(contents.release(), total, object_filename);
|
||||
}
|
||||
|
||||
bool DumpSymbols::ReadData(uint8_t* contents, size_t size,
|
||||
const std::string& filename) {
|
||||
contents_.reset(contents);
|
||||
size_ = size;
|
||||
object_filename_ = filename;
|
||||
|
||||
// Get the list of object files present in the file.
|
||||
FatReader::Reporter fat_reporter(object_filename_);
|
||||
FatReader fat_reader(&fat_reporter);
|
||||
if (!fat_reader.Read(&contents_[0],
|
||||
st.st_size)) {
|
||||
if (!fat_reader.Read(contents_.get(), size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -283,7 +291,13 @@ SuperFatArch* DumpSymbols::FindBestMatchForArchitecture(
|
|||
}
|
||||
|
||||
string DumpSymbols::Identifier() {
|
||||
FileID file_id(object_filename_.c_str());
|
||||
scoped_ptr<FileID> file_id;
|
||||
|
||||
if (from_disk_) {
|
||||
file_id.reset(new FileID(object_filename_.c_str()));
|
||||
} else {
|
||||
file_id.reset(new FileID(contents_.get(), size_));
|
||||
}
|
||||
unsigned char identifier_bytes[16];
|
||||
scoped_ptr<Module> module;
|
||||
if (!selected_object_file_) {
|
||||
|
@ -292,7 +306,7 @@ string DumpSymbols::Identifier() {
|
|||
}
|
||||
cpu_type_t cpu_type = selected_object_file_->cputype;
|
||||
cpu_subtype_t cpu_subtype = selected_object_file_->cpusubtype;
|
||||
if (!file_id.MachoIdentifier(cpu_type, cpu_subtype, identifier_bytes)) {
|
||||
if (!file_id->MachoIdentifier(cpu_type, cpu_subtype, identifier_bytes)) {
|
||||
fprintf(stderr, "Unable to calculate UUID of mach-o binary %s!\n",
|
||||
object_filename_.c_str());
|
||||
return "";
|
||||
|
@ -399,6 +413,13 @@ bool DumpSymbols::CreateEmptyModule(scoped_ptr<Module>& module) {
|
|||
google_breakpad::BreakpadGetArchInfoFromCpuType(
|
||||
selected_object_file_->cputype, selected_object_file_->cpusubtype);
|
||||
|
||||
// In certain cases, it is possible that architecture info can't be reliably
|
||||
// determined, e.g. new architectures that breakpad is unware of. In that
|
||||
// case, avoid crashing and return false instead.
|
||||
if (selected_arch_info == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* selected_arch_name = selected_arch_info->name;
|
||||
if (strcmp(selected_arch_name, "i386") == 0)
|
||||
selected_arch_name = "x86";
|
||||
|
|
|
@ -57,21 +57,30 @@ class DumpSymbols {
|
|||
DumpSymbols(SymbolData symbol_data, bool handle_inter_cu_refs)
|
||||
: symbol_data_(symbol_data),
|
||||
handle_inter_cu_refs_(handle_inter_cu_refs),
|
||||
input_pathname_(),
|
||||
object_filename_(),
|
||||
contents_(),
|
||||
size_(0),
|
||||
from_disk_(false),
|
||||
object_files_(),
|
||||
selected_object_file_(),
|
||||
selected_object_name_() { }
|
||||
selected_object_name_() {}
|
||||
~DumpSymbols() {
|
||||
}
|
||||
|
||||
// Prepare to read debugging information from |filename|. |filename| may be
|
||||
// the name of a universal binary, a Mach-O file, or a dSYM bundle
|
||||
// containing either of the above. On success, return true; if there is a
|
||||
// problem reading |filename|, report it and return false.
|
||||
// the name of a fat file, a Mach-O file, or a dSYM bundle containing either
|
||||
// of the above. On success, return true; if there is a problem reading
|
||||
// |filename|, report it and return false.
|
||||
bool Read(const std::string& filename);
|
||||
|
||||
// Prepare to read debugging information from |contents|. |contents| is
|
||||
// expected to be the data obtained from reading a fat file, or a Mach-O file.
|
||||
// |filename| is used to determine the object filename in the generated
|
||||
// output; there will not be an attempt to open this file as the data
|
||||
// is already expected to be in memory. On success, return true; if there is a
|
||||
// problem reading |contents|, report it and return false.
|
||||
bool ReadData(uint8_t* contents, size_t size, const std::string& filename);
|
||||
|
||||
// If this dumper's file includes an object file for |cpu_type| and
|
||||
// |cpu_subtype|, then select that object file for dumping, and return
|
||||
// true. Otherwise, return false, and leave this dumper's selected
|
||||
|
@ -162,19 +171,22 @@ class DumpSymbols {
|
|||
// Whether to handle references between compilation units.
|
||||
const bool handle_inter_cu_refs_;
|
||||
|
||||
// The name of the file or bundle whose symbols this will dump.
|
||||
// This is the path given to Read, for use in error messages.
|
||||
std::string input_pathname_;
|
||||
|
||||
// The name of the file this DumpSymbols will actually read debugging
|
||||
// information from. Normally, this is the same as input_pathname_, but if
|
||||
// filename refers to a dSYM bundle, then this is the resource file
|
||||
// within that bundle.
|
||||
// information from. If the filename passed to Read refers to a dSYM bundle,
|
||||
// then this is the resource file within that bundle.
|
||||
std::string object_filename_;
|
||||
|
||||
// The complete contents of object_filename_, mapped into memory.
|
||||
scoped_array<uint8_t> contents_;
|
||||
|
||||
// The size of contents_.
|
||||
size_t size_;
|
||||
|
||||
// Indicates which entry point to DumpSymbols was used, i.e. Read vs ReadData.
|
||||
// This is used to indicate that downstream code paths can/should also read
|
||||
// from disk or not.
|
||||
bool from_disk_;
|
||||
|
||||
// A vector of SuperFatArch structures describing the object files
|
||||
// object_filename_ contains. If object_filename_ refers to a fat binary,
|
||||
// this may have more than one element; if it refers to a Mach-O file, this
|
||||
|
|
|
@ -33,53 +33,41 @@
|
|||
//
|
||||
// Author: Dan Waylonis
|
||||
|
||||
#include "common/mac/file_id.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "common/mac/file_id.h"
|
||||
#include "common/mac/macho_id.h"
|
||||
#include "common/scoped_ptr.h"
|
||||
|
||||
using MacFileUtilities::MachoID;
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
FileID::FileID(const char *path) {
|
||||
namespace mach_o {
|
||||
// Constructs a FileID given a path to a file
|
||||
FileID::FileID(const char* path) : memory_(nullptr), size_(0) {
|
||||
snprintf(path_, sizeof(path_), "%s", path);
|
||||
}
|
||||
|
||||
bool FileID::FileIdentifier(unsigned char identifier[16]) {
|
||||
int fd = open(path_, O_RDONLY);
|
||||
if (fd == -1)
|
||||
return false;
|
||||
|
||||
MD5Context md5;
|
||||
MD5Init(&md5);
|
||||
|
||||
// Read 4k x 2 bytes at a time. This is faster than just 4k bytes, but
|
||||
// doesn't seem to be an unreasonable size for the stack.
|
||||
unsigned char buffer[4096 * 2];
|
||||
size_t buffer_size = sizeof(buffer);
|
||||
while ((buffer_size = read(fd, buffer, buffer_size) > 0)) {
|
||||
MD5Update(&md5, buffer, static_cast<unsigned>(buffer_size));
|
||||
}
|
||||
|
||||
close(fd);
|
||||
MD5Final(identifier, &md5);
|
||||
|
||||
return true;
|
||||
}
|
||||
// Constructs a FileID given the contents of a file and its size
|
||||
FileID::FileID(void* memory, size_t size)
|
||||
: path_(), memory_(memory), size_(size) {}
|
||||
|
||||
bool FileID::MachoIdentifier(cpu_type_t cpu_type,
|
||||
cpu_subtype_t cpu_subtype,
|
||||
unsigned char identifier[16]) {
|
||||
MachoID macho(path_);
|
||||
|
||||
if (macho.UUIDCommand(cpu_type, cpu_subtype, identifier))
|
||||
scoped_ptr<MachoID> macho;
|
||||
if (memory_) {
|
||||
macho.reset(new MachoID(memory_, size_));
|
||||
} else {
|
||||
macho.reset(new MachoID(path_));
|
||||
}
|
||||
if (macho->UUIDCommand(cpu_type, cpu_subtype, identifier))
|
||||
return true;
|
||||
|
||||
return macho.MD5(cpu_type, cpu_subtype, identifier);
|
||||
return macho->MD5(cpu_type, cpu_subtype, identifier);
|
||||
}
|
||||
|
||||
// static
|
||||
|
@ -103,4 +91,5 @@ void FileID::ConvertIdentifierToString(const unsigned char identifier[16],
|
|||
buffer[(buffer_idx < buffer_length) ? buffer_idx : buffer_idx - 1] = 0;
|
||||
}
|
||||
|
||||
} // namespace mach_o
|
||||
} // namespace google_breakpad
|
||||
|
|
|
@ -36,19 +36,19 @@
|
|||
|
||||
#include <limits.h>
|
||||
#include <mach/machine.h>
|
||||
#include <stddef.h>
|
||||
|
||||
namespace google_breakpad {
|
||||
namespace mach_o {
|
||||
|
||||
class FileID {
|
||||
public:
|
||||
FileID(const char *path);
|
||||
~FileID() {}
|
||||
// Constructs a FileID given a path to a file
|
||||
FileID(const char* path);
|
||||
|
||||
// Load the identifier for the file path specified in the constructor into
|
||||
// |identifier|. Return false if the identifier could not be created for the
|
||||
// file.
|
||||
// The current implementation will return the MD5 hash of the file's bytes.
|
||||
bool FileIdentifier(unsigned char identifier[16]);
|
||||
// Constructs a FileID given the contents of a file and its size.
|
||||
FileID(void* memory, size_t size);
|
||||
~FileID() {}
|
||||
|
||||
// Treat the file as a mach-o file that will contain one or more archicture.
|
||||
// Accepted values for |cpu_type| and |cpu_subtype| (e.g., CPU_TYPE_X86 or
|
||||
|
@ -74,8 +74,19 @@ class FileID {
|
|||
private:
|
||||
// Storage for the path specified
|
||||
char path_[PATH_MAX];
|
||||
|
||||
// Storage for contents of a file if this instance is used to operate on in
|
||||
// memory file data rather than directly from a filesystem. If memory_ is
|
||||
// null, the file represented by path_ will be opened/read. If memory_ is
|
||||
// non-null, it is assumed to contain valid data, and no file operations will
|
||||
// occur.
|
||||
void* memory_;
|
||||
|
||||
// Size of memory_
|
||||
size_t size_;
|
||||
};
|
||||
|
||||
} // namespace mach_o
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // COMMON_MAC_FILE_ID_H__
|
||||
|
|
|
@ -37,11 +37,7 @@
|
|||
#include <fcntl.h>
|
||||
#include <mach-o/loader.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "common/mac/macho_id.h"
|
||||
#include "common/mac/macho_walker.h"
|
||||
|
@ -54,73 +50,18 @@ using google_breakpad::MD5Update;
|
|||
using google_breakpad::MD5Final;
|
||||
|
||||
MachoID::MachoID(const char* path)
|
||||
: memory_(0),
|
||||
memory_size_(0),
|
||||
crc_(0),
|
||||
md5_context_(),
|
||||
update_function_(NULL) {
|
||||
: memory_(0), memory_size_(0), md5_context_(), update_function_(NULL) {
|
||||
snprintf(path_, sizeof(path_), "%s", path);
|
||||
}
|
||||
|
||||
MachoID::MachoID(const char* path, void* memory, size_t size)
|
||||
: memory_(memory),
|
||||
memory_size_(size),
|
||||
crc_(0),
|
||||
md5_context_(),
|
||||
update_function_(NULL) {
|
||||
snprintf(path_, sizeof(path_), "%s", path);
|
||||
}
|
||||
MachoID::MachoID(void* memory, size_t size)
|
||||
: path_(),
|
||||
memory_(memory),
|
||||
memory_size_(size),
|
||||
md5_context_(),
|
||||
update_function_(NULL) {}
|
||||
|
||||
MachoID::~MachoID() {
|
||||
}
|
||||
|
||||
// The CRC info is from http://en.wikipedia.org/wiki/Adler-32
|
||||
// With optimizations from http://www.zlib.net/
|
||||
|
||||
// The largest prime smaller than 65536
|
||||
#define MOD_ADLER 65521
|
||||
// MAX_BLOCK is the largest n such that 255n(n+1)/2 + (n+1)(MAX_BLOCK-1) <= 2^32-1
|
||||
#define MAX_BLOCK 5552
|
||||
|
||||
void MachoID::UpdateCRC(unsigned char* bytes, size_t size) {
|
||||
// Unrolled loops for summing
|
||||
#define DO1(buf,i) {sum1 += (buf)[i]; sum2 += sum1;}
|
||||
#define DO2(buf,i) DO1(buf,i); DO1(buf,i+1);
|
||||
#define DO4(buf,i) DO2(buf,i); DO2(buf,i+2);
|
||||
#define DO8(buf,i) DO4(buf,i); DO4(buf,i+4);
|
||||
#define DO16(buf) DO8(buf,0); DO8(buf,8);
|
||||
// Split up the crc
|
||||
uint32_t sum1 = crc_ & 0xFFFF;
|
||||
uint32_t sum2 = (crc_ >> 16) & 0xFFFF;
|
||||
|
||||
// Do large blocks
|
||||
while (size >= MAX_BLOCK) {
|
||||
size -= MAX_BLOCK;
|
||||
int block_count = MAX_BLOCK / 16;
|
||||
do {
|
||||
DO16(bytes);
|
||||
bytes += 16;
|
||||
} while (--block_count);
|
||||
sum1 %= MOD_ADLER;
|
||||
sum2 %= MOD_ADLER;
|
||||
}
|
||||
|
||||
// Do remaining bytes
|
||||
if (size) {
|
||||
while (size >= 16) {
|
||||
size -= 16;
|
||||
DO16(bytes);
|
||||
bytes += 16;
|
||||
}
|
||||
while (size--) {
|
||||
sum1 += *bytes++;
|
||||
sum2 += sum1;
|
||||
}
|
||||
sum1 %= MOD_ADLER;
|
||||
sum2 %= MOD_ADLER;
|
||||
crc_ = (sum2 << 16) | sum1;
|
||||
}
|
||||
}
|
||||
MachoID::~MachoID() {}
|
||||
|
||||
void MachoID::UpdateMD5(unsigned char* bytes, size_t size) {
|
||||
MD5Update(&md5_context_, bytes, static_cast<unsigned>(size));
|
||||
|
@ -169,59 +110,6 @@ bool MachoID::UUIDCommand(cpu_type_t cpu_type,
|
|||
return false;
|
||||
}
|
||||
|
||||
bool MachoID::IDCommand(cpu_type_t cpu_type,
|
||||
cpu_subtype_t cpu_subtype,
|
||||
unsigned char identifier[16]) {
|
||||
struct dylib_command dylib_cmd;
|
||||
dylib_cmd.cmd = 0;
|
||||
if (!WalkHeader(cpu_type, cpu_subtype, IDWalkerCB, &dylib_cmd))
|
||||
return false;
|
||||
|
||||
// If we found the command, we'll have initialized the dylib_command
|
||||
// structure
|
||||
if (dylib_cmd.cmd == LC_ID_DYLIB) {
|
||||
// Take the hashed filename, version, and compatability version bytes
|
||||
// to form the first 12 bytes, pad the rest with zeros
|
||||
|
||||
// create a crude hash of the filename to generate the first 4 bytes
|
||||
identifier[0] = 0;
|
||||
identifier[1] = 0;
|
||||
identifier[2] = 0;
|
||||
identifier[3] = 0;
|
||||
|
||||
for (int j = 0, i = (int)strlen(path_)-1; i>=0 && path_[i]!='/'; ++j, --i) {
|
||||
identifier[j%4] += path_[i];
|
||||
}
|
||||
|
||||
identifier[4] = (dylib_cmd.dylib.current_version >> 24) & 0xFF;
|
||||
identifier[5] = (dylib_cmd.dylib.current_version >> 16) & 0xFF;
|
||||
identifier[6] = (dylib_cmd.dylib.current_version >> 8) & 0xFF;
|
||||
identifier[7] = dylib_cmd.dylib.current_version & 0xFF;
|
||||
identifier[8] = (dylib_cmd.dylib.compatibility_version >> 24) & 0xFF;
|
||||
identifier[9] = (dylib_cmd.dylib.compatibility_version >> 16) & 0xFF;
|
||||
identifier[10] = (dylib_cmd.dylib.compatibility_version >> 8) & 0xFF;
|
||||
identifier[11] = dylib_cmd.dylib.compatibility_version & 0xFF;
|
||||
identifier[12] = (cpu_type >> 24) & 0xFF;
|
||||
identifier[13] = (cpu_type >> 16) & 0xFF;
|
||||
identifier[14] = (cpu_type >> 8) & 0xFF;
|
||||
identifier[15] = cpu_type & 0xFF;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t MachoID::Adler32(cpu_type_t cpu_type, cpu_subtype_t cpu_subtype) {
|
||||
update_function_ = &MachoID::UpdateCRC;
|
||||
crc_ = 0;
|
||||
|
||||
if (!WalkHeader(cpu_type, cpu_subtype, WalkerCB, this))
|
||||
return 0;
|
||||
|
||||
return crc_;
|
||||
}
|
||||
|
||||
bool MachoID::MD5(cpu_type_t cpu_type, cpu_subtype_t cpu_subtype, unsigned char identifier[16]) {
|
||||
update_function_ = &MachoID::UpdateMD5;
|
||||
|
||||
|
@ -346,24 +234,4 @@ bool MachoID::UUIDWalkerCB(MachoWalker* walker, load_command* cmd, off_t offset,
|
|||
// Continue processing
|
||||
return true;
|
||||
}
|
||||
|
||||
// static
|
||||
bool MachoID::IDWalkerCB(MachoWalker* walker, load_command* cmd, off_t offset,
|
||||
bool swap, void* context) {
|
||||
if (cmd->cmd == LC_ID_DYLIB) {
|
||||
struct dylib_command* dylib_cmd = (struct dylib_command*)context;
|
||||
|
||||
if (!walker->ReadBytes(dylib_cmd, sizeof(struct dylib_command), offset))
|
||||
return false;
|
||||
|
||||
if (swap)
|
||||
breakpad_swap_dylib_command(dylib_cmd);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Continue processing
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace MacFileUtilities
|
||||
|
|
|
@ -46,7 +46,7 @@ namespace MacFileUtilities {
|
|||
class MachoID {
|
||||
public:
|
||||
MachoID(const char* path);
|
||||
MachoID(const char* path, void* memory, size_t size);
|
||||
MachoID(void* memory, size_t size);
|
||||
~MachoID();
|
||||
|
||||
// For the given |cpu_type| and |cpu_subtype|, return a UUID from the LC_UUID
|
||||
|
@ -56,19 +56,6 @@ class MachoID {
|
|||
cpu_subtype_t cpu_subtype,
|
||||
unsigned char identifier[16]);
|
||||
|
||||
// For the given |cpu_type| and |cpu_subtype|, return a UUID from the
|
||||
// LC_ID_DYLIB command.
|
||||
// Return false if there isn't a LC_ID_DYLIB command.
|
||||
bool IDCommand(cpu_type_t cpu_type,
|
||||
cpu_subtype_t cpu_subtype,
|
||||
unsigned char identifier[16]);
|
||||
|
||||
// For the given |cpu_type| and |cpu_subtype|, return the Adler32 CRC for the
|
||||
// mach-o data segment(s).
|
||||
// Return 0 on error (e.g., if the file is not a mach-o file)
|
||||
uint32_t Adler32(cpu_type_t cpu_type,
|
||||
cpu_subtype_t cpu_subtype);
|
||||
|
||||
// For the given |cpu_type|, and |cpu_subtype| return the MD5 for the mach-o
|
||||
// data segment(s).
|
||||
// Return true on success, false otherwise
|
||||
|
@ -80,10 +67,6 @@ class MachoID {
|
|||
// Signature of class member function to be called with data read from file
|
||||
typedef void (MachoID::*UpdateFunction)(unsigned char* bytes, size_t size);
|
||||
|
||||
// Update the CRC value by examining |size| |bytes| and applying the algorithm
|
||||
// to each byte.
|
||||
void UpdateCRC(unsigned char* bytes, size_t size);
|
||||
|
||||
// Update the MD5 value by examining |size| |bytes| and applying the algorithm
|
||||
// to each byte.
|
||||
void UpdateMD5(unsigned char* bytes, size_t size);
|
||||
|
@ -103,10 +86,6 @@ class MachoID {
|
|||
static bool UUIDWalkerCB(MachoWalker* walker, load_command* cmd, off_t offset,
|
||||
bool swap, void* context);
|
||||
|
||||
// The callback from the MachoWalker for LC_ID_DYLIB
|
||||
static bool IDWalkerCB(MachoWalker* walker, load_command* cmd, off_t offset,
|
||||
bool swap, void* context);
|
||||
|
||||
// File path
|
||||
char path_[PATH_MAX];
|
||||
|
||||
|
@ -116,9 +95,6 @@ class MachoID {
|
|||
// Size of the memory region
|
||||
size_t memory_size_;
|
||||
|
||||
// The current crc value
|
||||
uint32_t crc_;
|
||||
|
||||
// The MD5 context
|
||||
google_breakpad::MD5Context md5_context_;
|
||||
|
||||
|
|
|
@ -97,17 +97,6 @@ void Module::InlineOriginMap::SetReference(uint64_t offset,
|
|||
references_[offset] = specification_offset;
|
||||
}
|
||||
|
||||
void Module::InlineOriginMap::AssignFilesToInlineOrigins(
|
||||
const vector<uint64_t>& inline_origin_offsets,
|
||||
Module::File* file) {
|
||||
for (uint64_t offset : inline_origin_offsets)
|
||||
if (references_.find(offset) != references_.end()) {
|
||||
auto origin = inline_origins_.find(references_[offset]);
|
||||
if (origin != inline_origins_.end())
|
||||
origin->second->file = file;
|
||||
}
|
||||
}
|
||||
|
||||
Module::Module(const string& name, const string& os,
|
||||
const string& architecture, const string& id,
|
||||
const string& code_id /* = "" */) :
|
||||
|
@ -276,13 +265,19 @@ void Module::AssignSourceIds(
|
|||
line_it != func->lines.end(); ++line_it)
|
||||
line_it->file->source_id = 0;
|
||||
}
|
||||
// Also mark all files cited by inline functions by setting each one's source
|
||||
|
||||
// Also mark all files cited by inline callsite by setting each one's source
|
||||
// id to zero.
|
||||
for (InlineOrigin* origin : inline_origins)
|
||||
auto markInlineFiles = [](unique_ptr<Inline>& in) {
|
||||
// There are some artificial inline functions which don't belong to
|
||||
// any file. Those will have file id -1.
|
||||
if (origin->file)
|
||||
origin->file->source_id = 0;
|
||||
if (in->call_site_file) {
|
||||
in->call_site_file->source_id = 0;
|
||||
}
|
||||
};
|
||||
for (auto func : functions_) {
|
||||
Inline::InlineDFS(func->inlines, markInlineFiles);
|
||||
}
|
||||
|
||||
// Finally, assign source ids to those files that have been marked.
|
||||
// We could have just assigned source id numbers while traversing
|
||||
|
@ -296,15 +291,6 @@ void Module::AssignSourceIds(
|
|||
}
|
||||
}
|
||||
|
||||
static void InlineDFS(
|
||||
vector<unique_ptr<Module::Inline>>& inlines,
|
||||
std::function<void(unique_ptr<Module::Inline>&)> const& forEach) {
|
||||
for (unique_ptr<Module::Inline>& in : inlines) {
|
||||
forEach(in);
|
||||
InlineDFS(in->child_inlines, forEach);
|
||||
}
|
||||
}
|
||||
|
||||
void Module::CreateInlineOrigins(
|
||||
set<InlineOrigin*, InlineOriginCompare>& inline_origins) {
|
||||
// Only add origins that have file and deduplicate origins with same name and
|
||||
|
@ -317,7 +303,7 @@ void Module::CreateInlineOrigins(
|
|||
in->origin = *it;
|
||||
};
|
||||
for (Function* func : functions_)
|
||||
InlineDFS(func->inlines, addInlineOrigins);
|
||||
Module::Inline::InlineDFS(func->inlines, addInlineOrigins);
|
||||
int next_id = 0;
|
||||
for (InlineOrigin* origin : inline_origins) {
|
||||
origin->id = next_id++;
|
||||
|
@ -381,8 +367,7 @@ bool Module::Write(std::ostream& stream, SymbolData symbol_data) {
|
|||
}
|
||||
// Write out inline origins.
|
||||
for (InlineOrigin* origin : inline_origins) {
|
||||
stream << "INLINE_ORIGIN " << origin->id << " " << origin->getFileID()
|
||||
<< " " << origin->name << "\n";
|
||||
stream << "INLINE_ORIGIN " << origin->id << " " << origin->name << "\n";
|
||||
if (!stream.good())
|
||||
return ReportError();
|
||||
}
|
||||
|
@ -407,12 +392,12 @@ bool Module::Write(std::ostream& stream, SymbolData symbol_data) {
|
|||
auto write_inline = [&](unique_ptr<Inline>& in) {
|
||||
stream << "INLINE ";
|
||||
stream << in->inline_nest_level << " " << in->call_site_line << " "
|
||||
<< in->origin->id << hex;
|
||||
<< in->getCallSiteFileID() << " " << in->origin->id << hex;
|
||||
for (const Range& r : in->ranges)
|
||||
stream << " " << (r.address - load_address_) << " " << r.size;
|
||||
stream << dec << "\n";
|
||||
};
|
||||
InlineDFS(func->inlines, write_inline);
|
||||
Module::Inline::InlineDFS(func->inlines, write_inline);
|
||||
if (!stream.good())
|
||||
return ReportError();
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
#ifndef COMMON_LINUX_MODULE_H__
|
||||
#define COMMON_LINUX_MODULE_H__
|
||||
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
|
@ -131,7 +132,7 @@ class Module {
|
|||
};
|
||||
|
||||
struct InlineOrigin {
|
||||
explicit InlineOrigin(StringView name): id(-1), name(name), file(nullptr) {}
|
||||
explicit InlineOrigin(StringView name) : id(-1), name(name) {}
|
||||
|
||||
// A unique id for each InlineOrigin object. INLINE records use the id to
|
||||
// refer to its INLINE_ORIGIN record.
|
||||
|
@ -150,11 +151,14 @@ class Module {
|
|||
Inline(InlineOrigin* origin,
|
||||
const vector<Range>& ranges,
|
||||
int call_site_line,
|
||||
int call_site_file_id,
|
||||
int inline_nest_level,
|
||||
vector<std::unique_ptr<Inline>> child_inlines)
|
||||
: origin(origin),
|
||||
ranges(ranges),
|
||||
call_site_line(call_site_line),
|
||||
call_site_file_id(call_site_file_id),
|
||||
call_site_file(nullptr),
|
||||
inline_nest_level(inline_nest_level),
|
||||
child_inlines(std::move(child_inlines)) {}
|
||||
|
||||
|
@ -165,10 +169,29 @@ class Module {
|
|||
|
||||
int call_site_line;
|
||||
|
||||
// The id is only meanful inside a CU. It's only used for looking up real
|
||||
// File* after scanning a CU.
|
||||
int call_site_file_id;
|
||||
|
||||
File* call_site_file;
|
||||
|
||||
int inline_nest_level;
|
||||
|
||||
// A list of inlines which are children of this inline.
|
||||
vector<std::unique_ptr<Inline>> child_inlines;
|
||||
|
||||
int getCallSiteFileID() const {
|
||||
return call_site_file ? call_site_file->source_id : -1;
|
||||
}
|
||||
|
||||
static void InlineDFS(
|
||||
vector<std::unique_ptr<Module::Inline>>& inlines,
|
||||
std::function<void(std::unique_ptr<Module::Inline>&)> const& forEach) {
|
||||
for (std::unique_ptr<Module::Inline>& in : inlines) {
|
||||
forEach(in);
|
||||
InlineDFS(in->child_inlines, forEach);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
typedef map<uint64_t, InlineOrigin*> InlineOriginByOffset;
|
||||
|
@ -182,9 +205,7 @@ class Module {
|
|||
// value of its DW_AT_specification or equals to offset if
|
||||
// DW_AT_specification doesn't exist in that DIE.
|
||||
void SetReference(uint64_t offset, uint64_t specification_offset);
|
||||
void AssignFilesToInlineOrigins(
|
||||
const vector<uint64_t>& inline_origin_offsets,
|
||||
File* file);
|
||||
|
||||
~InlineOriginMap() {
|
||||
for (const auto& iter : inline_origins_) {
|
||||
delete iter.second;
|
||||
|
@ -261,10 +282,8 @@ class Module {
|
|||
};
|
||||
|
||||
struct InlineOriginCompare {
|
||||
bool operator() (const InlineOrigin* lhs, const InlineOrigin* rhs) const {
|
||||
if (lhs->getFileID() == rhs->getFileID())
|
||||
return lhs->name < rhs->name;
|
||||
return lhs->getFileID() < rhs->getFileID();
|
||||
bool operator()(const InlineOrigin* lhs, const InlineOrigin* rhs) const {
|
||||
return lhs->name < rhs->name;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -492,7 +492,7 @@ bool WriteModuleInfo(int fd, GElf_Half arch, const std::string& obj_file) {
|
|||
}
|
||||
|
||||
unsigned char identifier[16];
|
||||
google_breakpad::FileID file_id(obj_file.c_str());
|
||||
google_breakpad::elf::FileID file_id(obj_file.c_str());
|
||||
if (file_id.ElfFileIdentifier(identifier)) {
|
||||
char identifier_str[40];
|
||||
file_id.ConvertIdentifierToString(identifier,
|
||||
|
|
|
@ -128,10 +128,6 @@ static bool FindElfTextSection(int fd, const void* elf_base,
|
|||
return false;
|
||||
}
|
||||
|
||||
FileID::FileID(const char* path) {
|
||||
strcpy(path_, path);
|
||||
}
|
||||
|
||||
class AutoCloser {
|
||||
public:
|
||||
AutoCloser(int fd) : fd_(fd) {}
|
||||
|
@ -140,6 +136,12 @@ class AutoCloser {
|
|||
int fd_;
|
||||
};
|
||||
|
||||
namespace elf {
|
||||
|
||||
FileID::FileID(const char* path) {
|
||||
strcpy(path_, path);
|
||||
}
|
||||
|
||||
bool FileID::ElfFileIdentifier(unsigned char identifier[16]) {
|
||||
int fd = 0;
|
||||
if ((fd = open(path_, O_RDONLY)) < 0)
|
||||
|
@ -194,4 +196,5 @@ bool FileID::ConvertIdentifierToString(const unsigned char identifier[16],
|
|||
return true;
|
||||
}
|
||||
|
||||
} // elf
|
||||
} // namespace google_breakpad
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
#include <limits.h>
|
||||
|
||||
namespace google_breakpad {
|
||||
namespace elf {
|
||||
|
||||
class FileID {
|
||||
public:
|
||||
|
@ -61,6 +62,7 @@ class FileID {
|
|||
char path_[PATH_MAX];
|
||||
};
|
||||
|
||||
} // elf
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // COMMON_SOLARIS_FILE_ID_H__
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
|
||||
|
@ -58,6 +59,8 @@ namespace google_breakpad {
|
|||
|
||||
namespace {
|
||||
|
||||
using std::set;
|
||||
using std::unique_ptr;
|
||||
using std::vector;
|
||||
|
||||
// The symbol (among possibly many) selected to represent an rva.
|
||||
|
@ -208,9 +211,162 @@ void StripLlvmSuffixAndUndecorate(BSTR* name) {
|
|||
|
||||
} // namespace
|
||||
|
||||
PDBSourceLineWriter::PDBSourceLineWriter() : output_(NULL) {
|
||||
PDBSourceLineWriter::Inline::Inline(int inline_nest_level)
|
||||
: inline_nest_level_(inline_nest_level) {}
|
||||
|
||||
void PDBSourceLineWriter::Inline::SetOriginId(int origin_id) {
|
||||
origin_id_ = origin_id;
|
||||
}
|
||||
|
||||
void PDBSourceLineWriter::Inline::ExtendRanges(const Line& line) {
|
||||
if (ranges_.empty()) {
|
||||
ranges_[line.rva] = line.length;
|
||||
return;
|
||||
}
|
||||
auto iter = ranges_.lower_bound(line.rva);
|
||||
// There is no overlap if this function is called with inlinee lines from
|
||||
// the same callsite.
|
||||
if (iter == ranges_.begin()) {
|
||||
return;
|
||||
}
|
||||
if (line.rva + line.length == iter->first) {
|
||||
// If they are connected, merge their ranges into one.
|
||||
DWORD length = line.length + iter->second;
|
||||
ranges_.erase(iter);
|
||||
ranges_[line.rva] = length;
|
||||
} else {
|
||||
--iter;
|
||||
if (iter->first + iter->second == line.rva) {
|
||||
ranges_[iter->first] = iter->second + line.length;
|
||||
} else {
|
||||
ranges_[line.rva] = line.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PDBSourceLineWriter::Inline::SetCallSiteLine(DWORD call_site_line) {
|
||||
call_site_line_ = call_site_line;
|
||||
}
|
||||
|
||||
void PDBSourceLineWriter::Inline::SetCallSiteFileId(DWORD call_site_file_id) {
|
||||
call_site_file_id_ = call_site_file_id;
|
||||
}
|
||||
|
||||
void PDBSourceLineWriter::Inline::SetChildInlines(
|
||||
vector<unique_ptr<Inline>> child_inlines) {
|
||||
child_inlines_ = std::move(child_inlines);
|
||||
}
|
||||
|
||||
void PDBSourceLineWriter::Inline::Print(FILE* output) const {
|
||||
// Ignore INLINE record that doesn't have any range.
|
||||
if (ranges_.empty())
|
||||
return;
|
||||
fprintf(output, "INLINE %d %lu %lu %d", inline_nest_level_, call_site_line_,
|
||||
call_site_file_id_, origin_id_);
|
||||
for (const auto& r : ranges_) {
|
||||
fprintf(output, " %lx %lx", r.first, r.second);
|
||||
}
|
||||
fprintf(output, "\n");
|
||||
for (const unique_ptr<Inline>& in : child_inlines_) {
|
||||
in->Print(output);
|
||||
}
|
||||
}
|
||||
|
||||
const PDBSourceLineWriter::Line* PDBSourceLineWriter::Lines::GetLine(
|
||||
DWORD rva) const {
|
||||
auto iter = line_map_.find(rva);
|
||||
if (iter == line_map_.end()) {
|
||||
// If not found exact rva, check if it's within any range.
|
||||
iter = line_map_.lower_bound(rva);
|
||||
if (iter == line_map_.begin())
|
||||
return nullptr;
|
||||
--iter;
|
||||
auto l = iter->second;
|
||||
// This happens when there is no top level lines cover this rva (e.g. empty
|
||||
// lines found for the function). Then we don't know the call site line
|
||||
// number for this inlined function.
|
||||
if (rva >= l.rva + l.length)
|
||||
return nullptr;
|
||||
}
|
||||
return &iter->second;
|
||||
}
|
||||
|
||||
DWORD PDBSourceLineWriter::Lines::GetLineNum(DWORD rva) const {
|
||||
const Line* line = GetLine(rva);
|
||||
return line ? line->line_num : 0;
|
||||
}
|
||||
|
||||
DWORD PDBSourceLineWriter::Lines::GetFileId(DWORD rva) const {
|
||||
const Line* line = GetLine(rva);
|
||||
return line ? line->file_id : 0;
|
||||
}
|
||||
|
||||
void PDBSourceLineWriter::Lines::AddLine(const Line& line) {
|
||||
if (line_map_.empty()) {
|
||||
line_map_[line.rva] = line;
|
||||
return;
|
||||
}
|
||||
|
||||
// Given an existing line in line_map_, remove it from line_map_ if it
|
||||
// overlaps with the line and add a new line for the non-overlap range. Return
|
||||
// true if there is an overlap.
|
||||
auto intercept = [&](Line old_line) {
|
||||
DWORD end = old_line.rva + old_line.length;
|
||||
// No overlap.
|
||||
if (old_line.rva >= line.rva + line.length || line.rva >= end)
|
||||
return false;
|
||||
// old_line is within the line.
|
||||
if (old_line.rva >= line.rva && end <= line.rva + line.length) {
|
||||
line_map_.erase(old_line.rva);
|
||||
return true;
|
||||
}
|
||||
// Then there is a overlap.
|
||||
if (old_line.rva < line.rva) {
|
||||
old_line.length -= end - line.rva;
|
||||
if (end > line.rva + line.length) {
|
||||
Line new_line = old_line;
|
||||
new_line.rva = line.rva + line.length;
|
||||
new_line.length = end - new_line.rva;
|
||||
line_map_[new_line.rva] = new_line;
|
||||
}
|
||||
} else {
|
||||
line_map_.erase(old_line.rva);
|
||||
old_line.length -= line.rva + line.length - old_line.rva;
|
||||
old_line.rva = line.rva + line.length;
|
||||
}
|
||||
line_map_[old_line.rva] = old_line;
|
||||
return true;
|
||||
};
|
||||
|
||||
bool is_intercept;
|
||||
// Use a loop in cases that there are multiple lines within the given line.
|
||||
do {
|
||||
auto iter = line_map_.lower_bound(line.rva);
|
||||
if (iter == line_map_.end()) {
|
||||
if (!line_map_.empty()) {
|
||||
--iter;
|
||||
intercept(iter->second);
|
||||
}
|
||||
break;
|
||||
}
|
||||
is_intercept = false;
|
||||
if (iter != line_map_.begin()) {
|
||||
// Check if the given line overlaps a line with smaller in the map.
|
||||
auto prev = line_map_.lower_bound(line.rva);
|
||||
--prev;
|
||||
is_intercept = intercept(prev->second);
|
||||
}
|
||||
// Check if the given line overlaps a line with greater or equal rva in the
|
||||
// map. Using operator |= here since it's possible that there are multiple
|
||||
// lines with greater rva in the map overlap with the given line.
|
||||
is_intercept |= intercept(iter->second);
|
||||
} while (is_intercept);
|
||||
line_map_[line.rva] = line;
|
||||
}
|
||||
|
||||
PDBSourceLineWriter::PDBSourceLineWriter(bool handle_inline)
|
||||
: output_(NULL), handle_inline_(handle_inline) {}
|
||||
|
||||
PDBSourceLineWriter::~PDBSourceLineWriter() {
|
||||
Close();
|
||||
}
|
||||
|
@ -280,50 +436,63 @@ bool PDBSourceLineWriter::Open(const wstring& file, FileFormat format) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool PDBSourceLineWriter::PrintLines(IDiaEnumLineNumbers* lines) {
|
||||
// The line number format is:
|
||||
// <rva> <line number> <source file id>
|
||||
bool PDBSourceLineWriter::GetLine(IDiaLineNumber* dia_line, Line* line) const {
|
||||
if (FAILED(dia_line->get_relativeVirtualAddress(&line->rva))) {
|
||||
fprintf(stderr, "failed to get line rva\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (FAILED(dia_line->get_length(&line->length))) {
|
||||
fprintf(stderr, "failed to get line code length\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD dia_source_id;
|
||||
if (FAILED(dia_line->get_sourceFileId(&dia_source_id))) {
|
||||
fprintf(stderr, "failed to get line source file id\n");
|
||||
return false;
|
||||
}
|
||||
// duplicate file names are coalesced to share one ID
|
||||
line->file_id = GetRealFileID(dia_source_id);
|
||||
|
||||
if (FAILED(dia_line->get_lineNumber(&line->line_num))) {
|
||||
fprintf(stderr, "failed to get line number\n");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PDBSourceLineWriter::GetLines(IDiaEnumLineNumbers* lines,
|
||||
Lines* line_list) const {
|
||||
CComPtr<IDiaLineNumber> line;
|
||||
ULONG count;
|
||||
|
||||
while (SUCCEEDED(lines->Next(1, &line, &count)) && count == 1) {
|
||||
DWORD rva;
|
||||
if (FAILED(line->get_relativeVirtualAddress(&rva))) {
|
||||
fprintf(stderr, "failed to get line rva\n");
|
||||
Line l;
|
||||
if (!GetLine(line, &l))
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD length;
|
||||
if (FAILED(line->get_length(&length))) {
|
||||
fprintf(stderr, "failed to get line code length\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD dia_source_id;
|
||||
if (FAILED(line->get_sourceFileId(&dia_source_id))) {
|
||||
fprintf(stderr, "failed to get line source file id\n");
|
||||
return false;
|
||||
}
|
||||
// duplicate file names are coalesced to share one ID
|
||||
DWORD source_id = GetRealFileID(dia_source_id);
|
||||
|
||||
DWORD line_num;
|
||||
if (FAILED(line->get_lineNumber(&line_num))) {
|
||||
fprintf(stderr, "failed to get line number\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
AddressRangeVector ranges;
|
||||
MapAddressRange(image_map_, AddressRange(rva, length), &ranges);
|
||||
for (size_t i = 0; i < ranges.size(); ++i) {
|
||||
fprintf(output_, "%lx %lx %lu %lu\n", ranges[i].rva, ranges[i].length,
|
||||
line_num, source_id);
|
||||
}
|
||||
// Silently ignore zero-length lines.
|
||||
if (l.length != 0)
|
||||
line_list->AddLine(l);
|
||||
line.Release();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void PDBSourceLineWriter::PrintLines(const Lines& lines) const {
|
||||
// The line number format is:
|
||||
// <rva> <line number> <source file id>
|
||||
for (const auto& kv : lines.GetLineMap()) {
|
||||
const Line& l = kv.second;
|
||||
AddressRangeVector ranges;
|
||||
MapAddressRange(image_map_, AddressRange(l.rva, l.length), &ranges);
|
||||
for (auto& range : ranges) {
|
||||
fprintf(output_, "%lx %lx %lu %lu\n", range.rva, range.length, l.line_num,
|
||||
l.file_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool PDBSourceLineWriter::PrintFunction(IDiaSymbol* function,
|
||||
IDiaSymbol* block,
|
||||
bool has_multiple_symbols) {
|
||||
|
@ -372,9 +541,20 @@ bool PDBSourceLineWriter::PrintFunction(IDiaSymbol* function,
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!PrintLines(lines)) {
|
||||
// Get top level lines first, which later may be split into multiple smaller
|
||||
// lines if any inline exists in their ranges if we want to handle inline.
|
||||
Lines line_list;
|
||||
if (!GetLines(lines, &line_list)) {
|
||||
return false;
|
||||
}
|
||||
if (handle_inline_) {
|
||||
vector<unique_ptr<Inline>> inlines;
|
||||
if (!GetInlines(block, &line_list, 0, &inlines)) {
|
||||
return false;
|
||||
}
|
||||
PrintInlines(inlines);
|
||||
}
|
||||
PrintLines(line_list);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -392,6 +572,10 @@ bool PDBSourceLineWriter::PrintSourceFiles() {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Print a dummy file with id equals 0 to represent unknown file, because
|
||||
// inline records might have unknown call site.
|
||||
fwprintf(output_, L"FILE %d unknown file\n", 0);
|
||||
|
||||
CComPtr<IDiaSymbol> compiland;
|
||||
ULONG count;
|
||||
while (SUCCEEDED(compilands->Next(1, &compiland, &count)) && count == 1) {
|
||||
|
@ -555,6 +739,97 @@ bool PDBSourceLineWriter::PrintFunctions() {
|
|||
return true;
|
||||
}
|
||||
|
||||
void PDBSourceLineWriter::PrintInlineOrigins() const {
|
||||
struct OriginCompare {
|
||||
bool operator()(const InlineOrigin lhs, const InlineOrigin rhs) const {
|
||||
return lhs.id < rhs.id;
|
||||
}
|
||||
};
|
||||
set<InlineOrigin, OriginCompare> origins;
|
||||
// Sort by origin id.
|
||||
for (auto const& origin : inline_origins_)
|
||||
origins.insert(origin.second);
|
||||
for (auto o : origins) {
|
||||
fprintf(output_, "INLINE_ORIGIN %d %ls\n", o.id, o.name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
bool PDBSourceLineWriter::GetInlines(IDiaSymbol* block,
|
||||
Lines* line_list,
|
||||
int inline_nest_level,
|
||||
vector<unique_ptr<Inline>>* inlines) {
|
||||
CComPtr<IDiaEnumSymbols> inline_callsites;
|
||||
if (FAILED(block->findChildrenEx(SymTagInlineSite, nullptr, nsNone,
|
||||
&inline_callsites))) {
|
||||
return false;
|
||||
}
|
||||
ULONG count;
|
||||
CComPtr<IDiaSymbol> callsite;
|
||||
while (SUCCEEDED(inline_callsites->Next(1, &callsite, &count)) &&
|
||||
count == 1) {
|
||||
unique_ptr<Inline> new_inline(new Inline(inline_nest_level));
|
||||
CComPtr<IDiaEnumLineNumbers> lines;
|
||||
// All inlinee lines have the same file id.
|
||||
DWORD file_id = 0;
|
||||
DWORD call_site_line = 0;
|
||||
if (FAILED(session_->findInlineeLines(callsite, &lines))) {
|
||||
return false;
|
||||
}
|
||||
CComPtr<IDiaLineNumber> dia_line;
|
||||
while (SUCCEEDED(lines->Next(1, &dia_line, &count)) && count == 1) {
|
||||
Line line;
|
||||
if (!GetLine(dia_line, &line)) {
|
||||
return false;
|
||||
}
|
||||
// Silently ignore zero-length lines.
|
||||
if (line.length != 0) {
|
||||
// Use the first line num and file id at rva as this inline's call site
|
||||
// line number, because after adding lines it may be changed to inner
|
||||
// line number and inner file id.
|
||||
if (call_site_line == 0)
|
||||
call_site_line = line_list->GetLineNum(line.rva);
|
||||
if (file_id == 0)
|
||||
file_id = line_list->GetFileId(line.rva);
|
||||
line_list->AddLine(line);
|
||||
new_inline->ExtendRanges(line);
|
||||
}
|
||||
dia_line.Release();
|
||||
}
|
||||
BSTR name;
|
||||
callsite->get_name(&name);
|
||||
if (SysStringLen(name) == 0) {
|
||||
name = SysAllocString(L"<name omitted>");
|
||||
}
|
||||
auto iter = inline_origins_.find(name);
|
||||
if (iter == inline_origins_.end()) {
|
||||
InlineOrigin origin;
|
||||
origin.id = inline_origins_.size();
|
||||
origin.name = name;
|
||||
inline_origins_[name] = origin;
|
||||
}
|
||||
new_inline->SetOriginId(inline_origins_[name].id);
|
||||
new_inline->SetCallSiteLine(call_site_line);
|
||||
new_inline->SetCallSiteFileId(file_id);
|
||||
// Go to next level.
|
||||
vector<unique_ptr<Inline>> child_inlines;
|
||||
if (!GetInlines(callsite, line_list, inline_nest_level + 1,
|
||||
&child_inlines)) {
|
||||
return false;
|
||||
}
|
||||
new_inline->SetChildInlines(std::move(child_inlines));
|
||||
inlines->push_back(std::move(new_inline));
|
||||
callsite.Release();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void PDBSourceLineWriter::PrintInlines(
|
||||
const vector<unique_ptr<Inline>>& inlines) const {
|
||||
for (const unique_ptr<Inline>& in : inlines) {
|
||||
in->Print(output_);
|
||||
}
|
||||
}
|
||||
|
||||
#undef max
|
||||
|
||||
bool PDBSourceLineWriter::PrintFrameDataUsingPDB() {
|
||||
|
@ -1105,10 +1380,8 @@ bool PDBSourceLineWriter::WriteSymbols(FILE* symbol_file) {
|
|||
bool ret = PrintPDBInfo();
|
||||
// This is not a critical piece of the symbol file.
|
||||
PrintPEInfo();
|
||||
ret = ret &&
|
||||
PrintSourceFiles() &&
|
||||
PrintFunctions() &&
|
||||
PrintFrameData();
|
||||
ret = ret && PrintSourceFiles() && PrintFunctions() && PrintFrameData();
|
||||
PrintInlineOrigins();
|
||||
|
||||
output_ = NULL;
|
||||
return ret;
|
||||
|
|
|
@ -35,8 +35,11 @@
|
|||
|
||||
#include <atlcomcli.h>
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "common/windows/module_info.h"
|
||||
#include "common/windows/omap.h"
|
||||
|
@ -47,6 +50,8 @@ struct IDiaSymbol;
|
|||
|
||||
namespace google_breakpad {
|
||||
|
||||
using std::map;
|
||||
using std::vector;
|
||||
using std::wstring;
|
||||
using std::unordered_map;
|
||||
|
||||
|
@ -58,7 +63,7 @@ class PDBSourceLineWriter {
|
|||
ANY_FILE // try PDB_FILE and then EXE_FILE
|
||||
};
|
||||
|
||||
explicit PDBSourceLineWriter();
|
||||
explicit PDBSourceLineWriter(bool handle_inline = false);
|
||||
~PDBSourceLineWriter();
|
||||
|
||||
// Opens the given file. For executable files, the corresponding pdb
|
||||
|
@ -99,9 +104,110 @@ class PDBSourceLineWriter {
|
|||
bool UsesGUID(bool *uses_guid);
|
||||
|
||||
private:
|
||||
// Outputs the line/address pairs for each line in the enumerator.
|
||||
// InlineOrigin represents INLINE_ORIGIN record in a symbol file. It's an
|
||||
// inlined function.
|
||||
struct InlineOrigin {
|
||||
// The unique id for an InlineOrigin.
|
||||
int id;
|
||||
// The name of the inlined function.
|
||||
wstring name;
|
||||
};
|
||||
|
||||
// Line represents LINE record in a symbol file. It represents a source code
|
||||
// line.
|
||||
struct Line {
|
||||
// The relative address of a line.
|
||||
DWORD rva;
|
||||
// The number bytes this line has.
|
||||
DWORD length;
|
||||
// The source line number.
|
||||
DWORD line_num;
|
||||
// The source file id where the source line is located at.
|
||||
DWORD file_id;
|
||||
};
|
||||
|
||||
// Inline represents INLINE record in a symbol file.
|
||||
class Inline {
|
||||
public:
|
||||
explicit Inline(int inline_nest_level);
|
||||
|
||||
void SetOriginId(int origin_id);
|
||||
|
||||
// Adding inlinee line's range into ranges. If line is adjacent with any
|
||||
// existing lines, extend the range. Otherwise, add line as a new range.
|
||||
void ExtendRanges(const Line& line);
|
||||
|
||||
void SetCallSiteLine(DWORD call_site_line);
|
||||
|
||||
void SetCallSiteFileId(DWORD call_site_file_id);
|
||||
|
||||
void SetChildInlines(std::vector<std::unique_ptr<Inline>> child_inlines);
|
||||
|
||||
void Print(FILE* output) const;
|
||||
|
||||
private:
|
||||
// The nest level of this inline record.
|
||||
int inline_nest_level_;
|
||||
// The source line number at where this inlined function is called.
|
||||
DWORD call_site_line_ = 0;
|
||||
// The call site file id at where this inlined function is called.
|
||||
DWORD call_site_file_id_ = 0;
|
||||
// The id used for referring to an InlineOrigin.
|
||||
int origin_id_ = 0;
|
||||
// A map from rva to length. This is the address ranges covered by this
|
||||
// Inline.
|
||||
map<DWORD, DWORD> ranges_;
|
||||
// The list of direct Inlines inlined inside this Inline.
|
||||
vector<std::unique_ptr<Inline>> child_inlines_;
|
||||
};
|
||||
|
||||
// Lines represents a map of lines inside a function with rva as the key.
|
||||
// AddLine function adds a line into the map and ensures that there is no
|
||||
// overlap between any two lines in the map.
|
||||
class Lines {
|
||||
public:
|
||||
const map<DWORD, Line>& GetLineMap() const { return line_map_; }
|
||||
|
||||
// Finds the line from line_map_ that contains the given rva returns its
|
||||
// line_num. If not found, return 0.
|
||||
DWORD GetLineNum(DWORD rva) const;
|
||||
|
||||
// Finds the line from line_map_ that contains the given rva returns its
|
||||
// file_id. If not found, return 0.
|
||||
DWORD GetFileId(DWORD rva) const;
|
||||
|
||||
// Add the `line` into line_map_. If the `line` overlaps with existing
|
||||
// lines, truncate the existing lines and add the given line. It ensures
|
||||
// that all lines in line_map_ do not overlap with each other. For example,
|
||||
// suppose there is a line A in the map and we call AddLine with Line B.
|
||||
// Line A: rva: 100, length: 20, line_num: 10, file_id: 1
|
||||
// Line B: rva: 105, length: 10, line_num: 4, file_id: 2
|
||||
// After calling AddLine with Line B, we will have the following lines:
|
||||
// Line 1: rva: 100, length: 5, line_num: 10, file_id: 1
|
||||
// Line 2: rva: 105, length: 10, line_num: 4, file_id: 2
|
||||
// Line 3: rva: 115, length: 5, line_num: 10, file_id: 1
|
||||
void AddLine(const Line& line);
|
||||
|
||||
private:
|
||||
// Finds the line from line_map_ that contains the given rva. If not found,
|
||||
// return nullptr.
|
||||
const Line* GetLine(DWORD rva) const;
|
||||
// The key is rva. AddLine function ensures that any two lines in the map do
|
||||
// not overlap.
|
||||
map<DWORD, Line> line_map_;
|
||||
};
|
||||
|
||||
// Construct Line from IDiaLineNumber. The output Line is stored at line.
|
||||
// Return true on success.
|
||||
bool GetLine(IDiaLineNumber* dia_line, Line* line) const;
|
||||
|
||||
// Construct Lines from IDiaEnumLineNumbers. The list of Lines are stored at
|
||||
// line_list.
|
||||
// Returns true on success.
|
||||
bool PrintLines(IDiaEnumLineNumbers *lines);
|
||||
bool GetLines(IDiaEnumLineNumbers* lines, Lines* line_list) const;
|
||||
|
||||
// Outputs the line/address pairs for each line in the enumerator.
|
||||
void PrintLines(const Lines& lines) const;
|
||||
|
||||
// Outputs a function address and name, followed by its source line list.
|
||||
// block can be the same object as function, or it can be a reference to a
|
||||
|
@ -118,6 +224,25 @@ class PDBSourceLineWriter {
|
|||
// Returns true on success.
|
||||
bool PrintSourceFiles();
|
||||
|
||||
// Output all inline origins.
|
||||
void PrintInlineOrigins() const;
|
||||
|
||||
// Retrieve inlines inside the given block. It also adds inlinee lines to
|
||||
// `line_list` since inner lines are more precise source location. If the
|
||||
// block has children wih SymTagInlineSite Tag, it will recursively (DFS) call
|
||||
// itself with each child as first argument. Returns true on success.
|
||||
// `block`: the IDiaSymbol that may have inline sites.
|
||||
// `line_list`: the list of lines inside current function.
|
||||
// `inline_nest_level`: the nest level of block's Inlines.
|
||||
// `inlines`: the vector to store the list of inlines for the block.
|
||||
bool GetInlines(IDiaSymbol* block,
|
||||
Lines* line_list,
|
||||
int inline_nest_level,
|
||||
vector<std::unique_ptr<Inline>>* inlines);
|
||||
|
||||
// Outputs all inlines.
|
||||
void PrintInlines(const vector<std::unique_ptr<Inline>>& inlines) const;
|
||||
|
||||
// Outputs all of the frame information necessary to construct stack
|
||||
// backtraces in the absence of frame pointers. For x86 data stored in
|
||||
// .pdb files. Returns true on success.
|
||||
|
@ -172,8 +297,8 @@ class PDBSourceLineWriter {
|
|||
// reference it. There may be multiple files with identical filenames
|
||||
// but different unique IDs. The cache attempts to coalesce these into
|
||||
// one ID per unique filename.
|
||||
DWORD GetRealFileID(DWORD id) {
|
||||
unordered_map<DWORD, DWORD>::iterator iter = file_ids_.find(id);
|
||||
DWORD GetRealFileID(DWORD id) const {
|
||||
unordered_map<DWORD, DWORD>::const_iterator iter = file_ids_.find(id);
|
||||
if (iter == file_ids_.end())
|
||||
return id;
|
||||
return iter->second;
|
||||
|
@ -213,9 +338,15 @@ class PDBSourceLineWriter {
|
|||
// This maps unique filenames to file IDs.
|
||||
unordered_map<wstring, DWORD> unique_files_;
|
||||
|
||||
// The INLINE_ORIGINS records. The key is the function name.
|
||||
std::map<wstring, InlineOrigin> inline_origins_;
|
||||
|
||||
// This is used for calculating post-transform symbol addresses and lengths.
|
||||
ImageMap image_map_;
|
||||
|
||||
// If we should output INLINE/INLINE_ORIGIN records
|
||||
bool handle_inline_;
|
||||
|
||||
// Disallow copy ctor and operator=
|
||||
PDBSourceLineWriter(const PDBSourceLineWriter&);
|
||||
void operator=(const PDBSourceLineWriter&);
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
#include <ImageHlp.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include "common/windows/string_utils-inl.h"
|
||||
#include "common/windows/guid_string.h"
|
||||
|
@ -42,15 +43,19 @@
|
|||
namespace {
|
||||
|
||||
/*
|
||||
* Not defined in WinNT.h for some reason. Definitions taken from:
|
||||
* http://uninformed.org/index.cgi?v=4&a=1&p=13
|
||||
* Not defined in WinNT.h prior to SDK 10.0.20348.0 for some reason.
|
||||
* Definitions taken from: http://uninformed.org/index.cgi?v=4&a=1&p=13
|
||||
*
|
||||
*/
|
||||
typedef unsigned char UBYTE;
|
||||
|
||||
#if !defined(_WIN64)
|
||||
#if !defined(UNW_FLAG_EHANDLER)
|
||||
#define UNW_FLAG_EHANDLER 0x01
|
||||
#endif
|
||||
#if !defined(UNW_FLAG_UHANDLER)
|
||||
#define UNW_FLAG_UHANDLER 0x02
|
||||
#endif
|
||||
#if !defined(UNW_FLAG_CHAININFO)
|
||||
#define UNW_FLAG_CHAININFO 0x04
|
||||
#endif
|
||||
|
||||
|
|
88
thirdparty/sentry-native/external/breakpad/src/common/windows/sym_upload_v2_protocol.cc
vendored
Normal file
88
thirdparty/sentry-native/external/breakpad/src/common/windows/sym_upload_v2_protocol.cc
vendored
Normal file
|
@ -0,0 +1,88 @@
|
|||
#include "common/windows/sym_upload_v2_protocol.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#include "common/windows/http_upload.h"
|
||||
#include "common/windows/symbol_collector_client.h"
|
||||
|
||||
using google_breakpad::CompleteUploadResult;
|
||||
using google_breakpad::HTTPUpload;
|
||||
using google_breakpad::SymbolCollectorClient;
|
||||
using google_breakpad::SymbolStatus;
|
||||
using google_breakpad::UploadUrlResponse;
|
||||
using std::wstring;
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
static bool SymUploadV2ProtocolSend(const wchar_t* api_url,
|
||||
const wchar_t* api_key,
|
||||
int* timeout_ms,
|
||||
const wstring& debug_file,
|
||||
const wstring& debug_id,
|
||||
const wstring& symbol_filename,
|
||||
const wstring& symbol_type,
|
||||
bool force) {
|
||||
wstring url(api_url);
|
||||
wstring key(api_key);
|
||||
|
||||
if (!force) {
|
||||
SymbolStatus symbolStatus = SymbolCollectorClient::CheckSymbolStatus(
|
||||
url, key, timeout_ms, debug_file, debug_id);
|
||||
if (symbolStatus == SymbolStatus::Found) {
|
||||
wprintf(
|
||||
L"Symbol file already exists, upload aborted."
|
||||
L" Use \"-f\" to overwrite.\n");
|
||||
return true;
|
||||
} else if (symbolStatus == SymbolStatus::Unknown) {
|
||||
wprintf(L"Failed to get check for existing symbol.\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
UploadUrlResponse uploadUrlResponse;
|
||||
if (!SymbolCollectorClient::CreateUploadUrl(url, key, timeout_ms,
|
||||
&uploadUrlResponse)) {
|
||||
wprintf(L"Failed to create upload URL.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
wstring signed_url = uploadUrlResponse.upload_url;
|
||||
wstring upload_key = uploadUrlResponse.upload_key;
|
||||
wstring response;
|
||||
int response_code;
|
||||
bool success = HTTPUpload::SendPutRequest(
|
||||
signed_url, symbol_filename, timeout_ms, &response, &response_code);
|
||||
if (!success) {
|
||||
wprintf(L"Failed to send symbol file.\n");
|
||||
wprintf(L"Response code: %ld\n", response_code);
|
||||
wprintf(L"Response:\n");
|
||||
wprintf(L"%s\n", response.c_str());
|
||||
return false;
|
||||
} else if (response_code == 0) {
|
||||
wprintf(L"Failed to send symbol file: No response code\n");
|
||||
return false;
|
||||
} else if (response_code != 200) {
|
||||
wprintf(L"Failed to send symbol file: Response code %ld\n", response_code);
|
||||
wprintf(L"Response:\n");
|
||||
wprintf(L"%s\n", response.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
CompleteUploadResult completeUploadResult =
|
||||
SymbolCollectorClient::CompleteUpload(url, key, timeout_ms, upload_key,
|
||||
debug_file, debug_id, symbol_type);
|
||||
if (completeUploadResult == CompleteUploadResult::Error) {
|
||||
wprintf(L"Failed to complete upload.\n");
|
||||
return false;
|
||||
} else if (completeUploadResult == CompleteUploadResult::DuplicateData) {
|
||||
wprintf(
|
||||
L"Uploaded file checksum matched existing file checksum,"
|
||||
L" no change necessary.\n");
|
||||
} else {
|
||||
wprintf(L"Successfully sent the symbol file.\n");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
64
thirdparty/sentry-native/external/breakpad/src/common/windows/sym_upload_v2_protocol.h
vendored
Normal file
64
thirdparty/sentry-native/external/breakpad/src/common/windows/sym_upload_v2_protocol.h
vendored
Normal file
|
@ -0,0 +1,64 @@
|
|||
// Copyright (c) 2022, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef COMMON_WINDOWS_SYM_UPLOAD_V2_PROTOCOL_H_
|
||||
#define COMMON_WINDOWS_SYM_UPLOAD_V2_PROTOCOL_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
// Sends file at |symbol_filename| using the sym-upload-v2 protocol to
|
||||
// |api_url| using key |api_key|, and using identifiers |debug_file| and
|
||||
// |debug_id|. |timeout_ms| is the number of milliseconds to wait before
|
||||
// terminating the upload attempt. |symbol_type| is the type of the symbol
|
||||
// file, which is one of:
|
||||
// "BREAKPAD"
|
||||
// "ELF"
|
||||
// "PE"
|
||||
// "MACHO"
|
||||
// "DEBUG_ONLY"
|
||||
// "DWP"
|
||||
// "DSYM"
|
||||
// "PDB"
|
||||
// "SOURCE_MAP"
|
||||
// If |force| is set then it will overwrite an existing file with the
|
||||
// same |debug_file| and |debug_id| in the store.
|
||||
bool SymUploadV2ProtocolSend(const wchar_t* api_url,
|
||||
const wchar_t* api_key,
|
||||
int* timeout_ms,
|
||||
const std::wstring& debug_file,
|
||||
const std::wstring& debug_id,
|
||||
const std::wstring& symbol_filename,
|
||||
const std::wstring& symbol_type,
|
||||
bool force);
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // COMMON_WINDOWS_SYM_UPLOAD_V2_PROTOCOL_H_
|
|
@ -12,6 +12,7 @@ namespace google_breakpad {
|
|||
bool SymbolCollectorClient::CreateUploadUrl(
|
||||
wstring& api_url,
|
||||
wstring& api_key,
|
||||
int* timeout_ms,
|
||||
UploadUrlResponse *uploadUrlResponse) {
|
||||
wstring url = api_url +
|
||||
L"/v1/uploads:create"
|
||||
|
@ -23,7 +24,7 @@ namespace google_breakpad {
|
|||
url,
|
||||
L"",
|
||||
L"",
|
||||
NULL,
|
||||
timeout_ms,
|
||||
&response,
|
||||
&response_code)) {
|
||||
wprintf(L"Failed to create upload url.\n");
|
||||
|
@ -66,17 +67,27 @@ namespace google_breakpad {
|
|||
CompleteUploadResult SymbolCollectorClient::CompleteUpload(
|
||||
wstring& api_url,
|
||||
wstring& api_key,
|
||||
int* timeout_ms,
|
||||
const wstring& upload_key,
|
||||
const wstring& debug_file,
|
||||
const wstring& debug_id) {
|
||||
const wstring& debug_id,
|
||||
const wstring& type) {
|
||||
wstring url = api_url +
|
||||
L"/v1/uploads/" + upload_key + L":complete"
|
||||
L"?key=" + api_key;
|
||||
wstring body =
|
||||
L"{ symbol_id: {"
|
||||
L"debug_file: \"" + debug_file + L"\", "
|
||||
L"debug_id: \"" + debug_id + L"\" "
|
||||
L"} }";
|
||||
L"debug_file: \"" +
|
||||
debug_file +
|
||||
L"\", "
|
||||
L"debug_id: \"" +
|
||||
debug_id +
|
||||
L"\" "
|
||||
L"}, "
|
||||
L"symbol_upload_type: \"" +
|
||||
type +
|
||||
L"\", "
|
||||
L"use_async_processing: true }";
|
||||
wstring response;
|
||||
int response_code;
|
||||
|
||||
|
@ -84,7 +95,7 @@ namespace google_breakpad {
|
|||
url,
|
||||
body,
|
||||
L"application/json",
|
||||
NULL,
|
||||
timeout_ms,
|
||||
&response,
|
||||
&response_code)) {
|
||||
wprintf(L"Failed to complete upload.\n");
|
||||
|
@ -116,6 +127,7 @@ namespace google_breakpad {
|
|||
SymbolStatus SymbolCollectorClient::CheckSymbolStatus(
|
||||
wstring& api_url,
|
||||
wstring& api_key,
|
||||
int* timeout_ms,
|
||||
const wstring& debug_file,
|
||||
const wstring& debug_id) {
|
||||
wstring response;
|
||||
|
@ -126,7 +138,7 @@ namespace google_breakpad {
|
|||
|
||||
if (!HTTPUpload::SendGetRequest(
|
||||
url,
|
||||
NULL,
|
||||
timeout_ms,
|
||||
&response,
|
||||
&response_code)) {
|
||||
wprintf(L"Failed to check symbol status.\n");
|
||||
|
|
|
@ -64,22 +64,25 @@ namespace google_breakpad {
|
|||
static bool CreateUploadUrl(
|
||||
wstring& api_url,
|
||||
wstring& api_key,
|
||||
int* timeout_ms,
|
||||
UploadUrlResponse *uploadUrlResponse);
|
||||
|
||||
// Notify the API that symbol file upload is finished and its contents
|
||||
// are ready to be read and/or used for further processing.
|
||||
static CompleteUploadResult CompleteUpload(
|
||||
wstring& api_url,
|
||||
wstring& api_key,
|
||||
const wstring& upload_key,
|
||||
const wstring& debug_file,
|
||||
const wstring& debug_id);
|
||||
static CompleteUploadResult CompleteUpload(wstring& api_url,
|
||||
wstring& api_key,
|
||||
int* timeout_ms,
|
||||
const wstring& upload_key,
|
||||
const wstring& debug_file,
|
||||
const wstring& debug_id,
|
||||
const wstring& type);
|
||||
|
||||
// Returns whether or not a symbol file corresponding to the debug_file/
|
||||
// debug_id pair is already present in symbol storage.
|
||||
static SymbolStatus CheckSymbolStatus(
|
||||
wstring& api_url,
|
||||
wstring& api_key,
|
||||
int* timeout_ms,
|
||||
const wstring& debug_file,
|
||||
const wstring& debug_id);
|
||||
};
|
||||
|
|
|
@ -239,6 +239,15 @@ typedef struct {
|
|||
MDRVA rva;
|
||||
} MDLocationDescriptor; /* MINIDUMP_LOCATION_DESCRIPTOR */
|
||||
|
||||
/* An MDRVA64 is an 64-bit offset into the minidump file. The beginning of the
|
||||
* MDRawHeader is at offset 0. */
|
||||
typedef uint64_t MDRVA64; /* RVA64 */
|
||||
|
||||
typedef struct {
|
||||
uint64_t data_size;
|
||||
MDRVA64 rva;
|
||||
} MDLocationDescriptor64; /* MINIDUMP_LOCATION_DESCRIPTOR64 */
|
||||
|
||||
|
||||
typedef struct {
|
||||
/* The base address of the memory range on the host that produced the
|
||||
|
@ -332,6 +341,7 @@ typedef enum {
|
|||
MD_JAVASCRIPT_DATA_STREAM = 20,
|
||||
MD_SYSTEM_MEMORY_INFO_STREAM = 21,
|
||||
MD_PROCESS_VM_COUNTERS_STREAM = 22,
|
||||
MD_THREAD_NAME_LIST_STREAM = 24, /* MDRawThreadNameList */
|
||||
MD_LAST_RESERVED_STREAM = 0x0000ffff,
|
||||
|
||||
/* Breakpad extension types. 0x4767 = "Gg" */
|
||||
|
@ -382,6 +392,20 @@ typedef struct {
|
|||
static const size_t MDRawThreadList_minsize = offsetof(MDRawThreadList,
|
||||
threads[0]);
|
||||
|
||||
#pragma pack(push, 4)
|
||||
typedef struct {
|
||||
uint32_t thread_id;
|
||||
MDRVA64 thread_name_rva; /* MDString */
|
||||
} MDRawThreadName; /* MINIDUMP_THREAD_NAME */
|
||||
|
||||
typedef struct {
|
||||
uint32_t number_of_thread_names;
|
||||
MDRawThreadName thread_names[1];
|
||||
} MDRawThreadNameList; /* MINIDUMP_THREAD_NAME_LIST */
|
||||
#pragma pack(pop)
|
||||
|
||||
static const size_t MDRawThreadNameList_minsize =
|
||||
offsetof(MDRawThreadNameList, thread_names[0]);
|
||||
|
||||
typedef struct {
|
||||
uint64_t base_of_image;
|
||||
|
|
|
@ -370,6 +370,86 @@ class MinidumpThreadList : public MinidumpStream {
|
|||
DISALLOW_COPY_AND_ASSIGN(MinidumpThreadList);
|
||||
};
|
||||
|
||||
// MinidumpThreadName contains the name of a thread.
|
||||
class MinidumpThreadName : public MinidumpObject {
|
||||
public:
|
||||
virtual ~MinidumpThreadName();
|
||||
|
||||
const MDRawThreadName* thread_name() const {
|
||||
return valid_ ? &thread_name_ : NULL;
|
||||
}
|
||||
|
||||
// Gets the thread ID.
|
||||
virtual bool GetThreadID(uint32_t* thread_id) const;
|
||||
|
||||
// Print a human-readable representation of the object to stdout.
|
||||
void Print();
|
||||
|
||||
// Returns the name of the thread.
|
||||
virtual std::string GetThreadName() const;
|
||||
|
||||
protected:
|
||||
explicit MinidumpThreadName(Minidump* minidump);
|
||||
|
||||
private:
|
||||
// These objects are managed by MinidumpThreadNameList.
|
||||
friend class MinidumpThreadNameList;
|
||||
|
||||
// This works like MinidumpStream::Read, but is driven by
|
||||
// MinidumpThreadNameList. No size checking is done, because
|
||||
// MinidumpThreadNameList handles that directly.
|
||||
bool Read();
|
||||
|
||||
// Reads indirectly-referenced data, including the thread name.
|
||||
bool ReadAuxiliaryData();
|
||||
|
||||
// True after a successful Read. This is different from valid_, which is not
|
||||
// set true until ReadAuxiliaryData also completes successfully.
|
||||
// thread_name_valid_ is only used by ReadAuxiliaryData and the functions it
|
||||
// calls to determine whether the object is ready for auxiliary data to be
|
||||
// read.
|
||||
bool thread_name_valid_;
|
||||
|
||||
MDRawThreadName thread_name_;
|
||||
|
||||
// Cached thread name.
|
||||
const string* name_;
|
||||
};
|
||||
|
||||
// MinidumpThreadNameList contains all of the names of the threads (as
|
||||
// MinidumpThreadNames) in a process.
|
||||
class MinidumpThreadNameList : public MinidumpStream {
|
||||
public:
|
||||
virtual ~MinidumpThreadNameList();
|
||||
|
||||
virtual unsigned int thread_name_count() const {
|
||||
return valid_ ? thread_name_count_ : 0;
|
||||
}
|
||||
|
||||
// Sequential access to thread names.
|
||||
virtual MinidumpThreadName* GetThreadNameAtIndex(unsigned int index) const;
|
||||
|
||||
// Print a human-readable representation of the object to stdout.
|
||||
void Print();
|
||||
|
||||
protected:
|
||||
explicit MinidumpThreadNameList(Minidump* aMinidump);
|
||||
|
||||
private:
|
||||
friend class Minidump;
|
||||
|
||||
typedef vector<MinidumpThreadName> MinidumpThreadNames;
|
||||
|
||||
static const uint32_t kStreamType = MD_THREAD_NAME_LIST_STREAM;
|
||||
|
||||
bool Read(uint32_t aExpectedSize) override;
|
||||
|
||||
// The list of thread names.
|
||||
MinidumpThreadNames* thread_names_;
|
||||
uint32_t thread_name_count_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(MinidumpThreadNameList);
|
||||
};
|
||||
|
||||
// MinidumpModule wraps MDRawModule, which contains information about loaded
|
||||
// code modules. Access is provided to various data referenced indirectly
|
||||
|
@ -1188,6 +1268,7 @@ class Minidump {
|
|||
// to avoid exposing an ugly API (GetStream needs to accept a garbage
|
||||
// parameter).
|
||||
virtual MinidumpThreadList* GetThreadList();
|
||||
virtual MinidumpThreadNameList* GetThreadNameList();
|
||||
virtual MinidumpModuleList* GetModuleList();
|
||||
virtual MinidumpMemoryList* GetMemoryList();
|
||||
virtual MinidumpException* GetException();
|
||||
|
|
|
@ -56,9 +56,13 @@ enum ProcessResult {
|
|||
PROCESS_ERROR_DUPLICATE_REQUESTING_THREADS, // There was more than one
|
||||
// requesting thread.
|
||||
|
||||
PROCESS_SYMBOL_SUPPLIER_INTERRUPTED // The dump processing was
|
||||
PROCESS_SYMBOL_SUPPLIER_INTERRUPTED, // The dump processing was
|
||||
// interrupted by the
|
||||
// SymbolSupplier(not fatal).
|
||||
|
||||
PROCESS_ERROR_GETTING_THREAD_NAME, // There was an error getting one
|
||||
// thread's name from the dump.
|
||||
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
|
|
@ -111,6 +111,7 @@ class ProcessState {
|
|||
const vector<MemoryRegion*>* thread_memory_regions() const {
|
||||
return &thread_memory_regions_;
|
||||
}
|
||||
const vector<string>* thread_names() const { return &thread_names_; }
|
||||
const SystemInfo* system_info() const { return &system_info_; }
|
||||
const CodeModules* modules() const { return modules_; }
|
||||
const CodeModules* unloaded_modules() const { return unloaded_modules_; }
|
||||
|
@ -176,6 +177,12 @@ class ProcessState {
|
|||
vector<CallStack*> threads_;
|
||||
vector<MemoryRegion*> thread_memory_regions_;
|
||||
|
||||
// Names of each thread at the time of the crash, one for each entry in
|
||||
// threads_. Note that a thread's name might be empty if there was no
|
||||
// corresponding ThreadNamesStream in the minidump, or if a particular thread
|
||||
// ID was not present in the THREAD_NAME_LIST.
|
||||
vector<string> thread_names_;
|
||||
|
||||
// OS and CPU information.
|
||||
SystemInfo system_info_;
|
||||
|
||||
|
|
|
@ -50,9 +50,12 @@ struct StackFrame {
|
|||
FRAME_TRUST_CFI_SCAN, // Found while scanning stack using call frame info
|
||||
FRAME_TRUST_FP, // Derived from frame pointer
|
||||
FRAME_TRUST_CFI, // Derived from call frame info
|
||||
FRAME_TRUST_PREWALKED, // Explicitly provided by some external stack walker.
|
||||
// Explicitly provided by some external stack walker.
|
||||
FRAME_TRUST_PREWALKED,
|
||||
FRAME_TRUST_CONTEXT, // Given as instruction pointer in a context
|
||||
FRAME_TRUST_INLINE // Found by inline records in symbol files.
|
||||
FRAME_TRUST_INLINE, // Found by inline records in symbol files.
|
||||
// Derived from leaf function by simulating a return.
|
||||
FRAME_TRUST_LEAF,
|
||||
};
|
||||
|
||||
StackFrame()
|
||||
|
@ -63,7 +66,8 @@ struct StackFrame {
|
|||
source_file_name(),
|
||||
source_line(0),
|
||||
source_line_base(),
|
||||
trust(FRAME_TRUST_NONE){}
|
||||
trust(FRAME_TRUST_NONE),
|
||||
is_multiple(false) {}
|
||||
virtual ~StackFrame() {}
|
||||
|
||||
// Return a string describing how this stack frame was found
|
||||
|
@ -84,7 +88,9 @@ struct StackFrame {
|
|||
return "stack scanning";
|
||||
case StackFrame::FRAME_TRUST_INLINE:
|
||||
return "inline record";
|
||||
default:
|
||||
case StackFrame::FRAME_TRUST_LEAF:
|
||||
return "simulating a return from leaf function";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
@ -140,6 +146,12 @@ struct StackFrame {
|
|||
// Amount of trust the stack walker has in the instruction pointer
|
||||
// of this frame.
|
||||
FrameTrust trust;
|
||||
|
||||
// True if the frame corresponds to multiple functions, for example as the
|
||||
// result of identical code folding by the linker. In that case the function
|
||||
// name, filename, etc. information above represents the state of an arbitrary
|
||||
// one of these functions.
|
||||
bool is_multiple;
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
|
|
@ -251,7 +251,10 @@ struct StackFrameARM : public StackFrame {
|
|||
|
||||
// Return the ContextValidity flag for register rN.
|
||||
static ContextValidity RegisterValidFlag(int n) {
|
||||
return ContextValidity(1 << n);
|
||||
if (0 <= n && n <= 15) {
|
||||
return ContextValidity(1 << n);
|
||||
}
|
||||
return CONTEXT_VALID_NONE;
|
||||
}
|
||||
|
||||
// Register state. This is only fully valid for the topmost frame in a
|
||||
|
|
|
@ -13,3 +13,4 @@
|
|||
# limitations under the License.
|
||||
|
||||
buildconfig = "//build/BUILDCONFIG.gn"
|
||||
script_executable = "python3"
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
# Copyright 2022 The Crashpad Authors. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# This is a vpython "spec" file.
|
||||
#
|
||||
# It describes patterns for python wheel dependencies of the python scripts.
|
||||
#
|
||||
# Read more about `vpython` and how to modify this file here:
|
||||
# https://chromium.googlesource.com/infra/infra/+/master/doc/users/vpython.md
|
||||
|
||||
# This is needed for snapshot/win/end_to_end_test.py.
|
||||
wheel: <
|
||||
name: "infra/python/wheels/pywin32/${vpython_platform}"
|
||||
version: "version:300"
|
||||
match_tag: <
|
||||
platform: "win32"
|
||||
>
|
||||
match_tag: <
|
||||
platform: "win_amd64"
|
||||
>
|
||||
>
|
|
@ -17,7 +17,10 @@ import("build/test.gni")
|
|||
import("util/net/tls.gni")
|
||||
|
||||
config("crashpad_config") {
|
||||
include_dirs = [ "." ]
|
||||
include_dirs = [
|
||||
".",
|
||||
root_gen_dir,
|
||||
]
|
||||
}
|
||||
|
||||
if (crashpad_is_in_chromium || crashpad_is_in_fuchsia) {
|
||||
|
|
|
@ -52,7 +52,22 @@ endfunction()
|
|||
|
||||
if(WIN32)
|
||||
enable_language(ASM_MASM)
|
||||
|
||||
if(MINGW)
|
||||
find_program(JWASM_FOUND jwasm)
|
||||
if (JWASM_FOUND)
|
||||
set(CMAKE_ASM_MASM_COMPILER ${JWASM_FOUND})
|
||||
execute_process(COMMAND ${CMAKE_C_COMPILER} --version OUTPUT_VARIABLE COMPILER_VERSION_OUTPUT)
|
||||
if (COMPILER_VERSION_OUTPUT)
|
||||
if (COMPILER_VERSION_OUTPUT MATCHES "x86_64")
|
||||
set(JWASM_FLAGS -win64)
|
||||
else()
|
||||
set(JWASM_FLAGS -coff)
|
||||
endif()
|
||||
endif()
|
||||
set(CMAKE_ASM_MASM_FLAGS ${CMAKE_ASM_MASM_FLAGS} ${JWASM_FLAGS})
|
||||
endif(JWASM_FOUND)
|
||||
|
||||
if(NOT CMAKE_ASM_MASM_COMPILER OR CMAKE_ASM_MASM_COMPILER STREQUAL "ml" OR CMAKE_ASM_MASM_COMPILER STREQUAL "ml64")
|
||||
message(WARNING "No custom ASM_MASM compiler defined via 'CMAKE_ASM_MASM_COMPILER'. Trying to use UASM...")
|
||||
set(CMAKE_ASM_MASM_COMPILER "uasm")
|
||||
|
@ -60,12 +75,12 @@ if(WIN32)
|
|||
if(NOT CMAKE_ASM_MASM_FLAGS)
|
||||
set(CMAKE_ASM_MASM_FLAGS "-win64 -10") #use default compatibility flags
|
||||
endif()
|
||||
endif()
|
||||
endif(MINGW)
|
||||
else()
|
||||
enable_language(ASM)
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
add_library(crashpad_interface INTERFACE)
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
vars = {
|
||||
'chromium_git': 'https://chromium.googlesource.com',
|
||||
'gn_version': 'git_revision:2ecd43a10266bd091c98e6dcde507c64f6a0dad3',
|
||||
'pull_linux_clang': False,
|
||||
'pull_win_toolchain': False,
|
||||
# Controls whether crashpad/build/ios/setup-ios-gn.py is run as part of
|
||||
|
@ -25,21 +26,21 @@ vars = {
|
|||
deps = {
|
||||
'buildtools':
|
||||
Var('chromium_git') + '/chromium/src/buildtools.git@' +
|
||||
'9e121212d42be62a7cce38072f925f8398d11e49',
|
||||
'8b16338d17cd71b04a6ba28da7322ab6739892c2',
|
||||
'crashpad/third_party/edo/edo': {
|
||||
'url': Var('chromium_git') + '/external/github.com/google/eDistantObject.git@' +
|
||||
'6ffbf833173f53fcd06ecf08670a95cc01c01f72',
|
||||
'727e556705278598fce683522beedbb9946bfda0',
|
||||
'condition': 'checkout_ios',
|
||||
},
|
||||
'crashpad/third_party/googletest/googletest':
|
||||
Var('chromium_git') + '/external/github.com/google/googletest@' +
|
||||
'11da093e0477185dbd78abaaa9f99db15be498d0',
|
||||
'af29db7ec28d6df1c7f0f745186884091e602e07',
|
||||
'crashpad/third_party/lss/lss':
|
||||
Var('chromium_git') + '/linux-syscall-support.git@' +
|
||||
'7bde79cc274d06451bf65ae82c012a5d3e476b5a',
|
||||
'e1e7b0ad8ee99a875b272c8e33e308472e897660',
|
||||
'crashpad/third_party/mini_chromium/mini_chromium':
|
||||
Var('chromium_git') + '/chromium/mini_chromium@' +
|
||||
'0e22eed71eec97dacbe80822a14c5cd0b580d793',
|
||||
'5654edb4225bcad13901155c819febb5748e502b',
|
||||
'crashpad/third_party/libfuzzer/src':
|
||||
Var('chromium_git') + '/chromium/llvm-project/compiler-rt/lib/fuzzer.git@' +
|
||||
'fda403cf93ecb8792cb1d061564d89a6553ca020',
|
||||
|
@ -47,7 +48,37 @@ deps = {
|
|||
Var('chromium_git') + '/chromium/src/third_party/zlib@' +
|
||||
'13dc246a58e4b72104d35f9b1809af95221ebda7',
|
||||
|
||||
# CIPD packages below.
|
||||
# CIPD packages.
|
||||
'buildtools/linux64': {
|
||||
'packages': [
|
||||
{
|
||||
'package': 'gn/gn/linux-amd64',
|
||||
'version': Var('gn_version'),
|
||||
}
|
||||
],
|
||||
'dep_type': 'cipd',
|
||||
'condition': 'host_os == "linux"',
|
||||
},
|
||||
'buildtools/mac': {
|
||||
'packages': [
|
||||
{
|
||||
'package': 'gn/gn/mac-${{arch}}',
|
||||
'version': Var('gn_version'),
|
||||
}
|
||||
],
|
||||
'dep_type': 'cipd',
|
||||
'condition': 'host_os == "mac"',
|
||||
},
|
||||
'buildtools/win': {
|
||||
'packages': [
|
||||
{
|
||||
'package': 'gn/gn/windows-amd64',
|
||||
'version': Var('gn_version'),
|
||||
}
|
||||
],
|
||||
'dep_type': 'cipd',
|
||||
'condition': 'host_os == "win"',
|
||||
},
|
||||
'crashpad/third_party/linux/clang/linux-amd64': {
|
||||
'packages': [
|
||||
{
|
||||
|
@ -106,7 +137,7 @@ deps = {
|
|||
'packages': [
|
||||
{
|
||||
'package': 'chrome_internal/third_party/sdk/windows',
|
||||
'version': 'uploaded:2018-06-13'
|
||||
'version': 'uploaded:2021-04-28'
|
||||
},
|
||||
],
|
||||
'condition': 'checkout_win and pull_win_toolchain',
|
||||
|
@ -125,7 +156,9 @@ hooks = [
|
|||
'--no_auth',
|
||||
'--bucket=chromium-clang-format',
|
||||
'--sha1_file',
|
||||
'buildtools/mac/clang-format.sha1',
|
||||
'buildtools/mac/clang-format.{host_cpu}.sha1',
|
||||
'--output',
|
||||
'buildtools/mac/clang-format',
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -169,7 +202,7 @@ hooks = [
|
|||
'pattern': '.',
|
||||
'condition': 'run_setup_ios_gn and checkout_ios',
|
||||
'action': [
|
||||
'python',
|
||||
'python3',
|
||||
'crashpad/build/ios/setup_ios_gn.py'
|
||||
],
|
||||
},
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright 2020 The Crashpad Authors. All rights reserved.
|
||||
#
|
||||
|
@ -26,14 +26,146 @@ import argparse
|
|||
import collections
|
||||
import copy
|
||||
import filecmp
|
||||
import json
|
||||
import functools
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import string
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import xml.etree.ElementTree
|
||||
|
||||
|
||||
LLDBINIT_PATH = '$(PROJECT_DIR)/.lldbinit'
|
||||
|
||||
PYTHON_RE = re.compile('[ /]python[23]?$')
|
||||
|
||||
XCTEST_PRODUCT_TYPES = frozenset((
|
||||
'com.apple.product-type.bundle.unit-test',
|
||||
'com.apple.product-type.bundle.ui-testing',
|
||||
))
|
||||
|
||||
SCHEME_PRODUCT_TYPES = frozenset((
|
||||
'com.apple.product-type.app-extension',
|
||||
'com.apple.product-type.application',
|
||||
'com.apple.product-type.framework'
|
||||
))
|
||||
|
||||
|
||||
class Template(string.Template):
|
||||
|
||||
"""A subclass of string.Template that changes delimiter."""
|
||||
|
||||
delimiter = '@'
|
||||
|
||||
|
||||
@functools.lru_cache
|
||||
def LoadSchemeTemplate(root, name):
|
||||
"""Return a string.Template object for scheme file loaded relative to root."""
|
||||
path = os.path.join(root, 'build', 'ios', name)
|
||||
with open(path) as file:
|
||||
return Template(file.read())
|
||||
|
||||
|
||||
def CreateIdentifier(str_id):
|
||||
"""Return a 24 characters string that can be used as an identifier."""
|
||||
return hashlib.sha1(str_id.encode("utf-8")).hexdigest()[:24].upper()
|
||||
|
||||
|
||||
def GenerateSchemeForTarget(root, project, old_project, name, path, tests):
|
||||
"""Generates the .xcsheme file for target named |name|.
|
||||
|
||||
The file is generated in the new project schemes directory from a template.
|
||||
If there is an existing previous project, then the old scheme file is copied
|
||||
and the lldbinit setting is set. If lldbinit setting is already correct, the
|
||||
file is not modified, just copied.
|
||||
"""
|
||||
project_name = os.path.basename(project)
|
||||
relative_path = os.path.join('xcshareddata', 'xcschemes', name + '.xcscheme')
|
||||
identifier = CreateIdentifier('%s %s' % (name, path))
|
||||
|
||||
scheme_path = os.path.join(project, relative_path)
|
||||
if not os.path.isdir(os.path.dirname(scheme_path)):
|
||||
os.makedirs(os.path.dirname(scheme_path))
|
||||
|
||||
old_scheme_path = os.path.join(old_project, relative_path)
|
||||
if os.path.exists(old_scheme_path):
|
||||
made_changes = False
|
||||
|
||||
tree = xml.etree.ElementTree.parse(old_scheme_path)
|
||||
tree_root = tree.getroot()
|
||||
|
||||
for reference in tree_root.findall('.//BuildableReference'):
|
||||
for (attr, value) in (
|
||||
('BuildableName', path),
|
||||
('BlueprintName', name),
|
||||
('BlueprintIdentifier', identifier)):
|
||||
if reference.get(attr) != value:
|
||||
reference.set(attr, value)
|
||||
made_changes = True
|
||||
|
||||
for child in tree_root:
|
||||
if child.tag not in ('TestAction', 'LaunchAction'):
|
||||
continue
|
||||
|
||||
if child.get('customLLDBInitFile') != LLDBINIT_PATH:
|
||||
child.set('customLLDBInitFile', LLDBINIT_PATH)
|
||||
made_changes = True
|
||||
|
||||
# Override the list of testables.
|
||||
if child.tag == 'TestAction':
|
||||
for subchild in child:
|
||||
if subchild.tag != 'Testables':
|
||||
continue
|
||||
|
||||
for elt in list(subchild):
|
||||
subchild.remove(elt)
|
||||
|
||||
if tests:
|
||||
template = LoadSchemeTemplate(root, 'xcodescheme-testable.template')
|
||||
for (key, test_path, test_name) in sorted(tests):
|
||||
testable = ''.join(template.substitute(
|
||||
BLUEPRINT_IDENTIFIER=key,
|
||||
BUILDABLE_NAME=test_path,
|
||||
BLUEPRINT_NAME=test_name,
|
||||
PROJECT_NAME=project_name))
|
||||
|
||||
testable_elt = xml.etree.ElementTree.fromstring(testable)
|
||||
subchild.append(testable_elt)
|
||||
|
||||
if made_changes:
|
||||
tree.write(scheme_path, xml_declaration=True, encoding='UTF-8')
|
||||
|
||||
else:
|
||||
shutil.copyfile(old_scheme_path, scheme_path)
|
||||
|
||||
else:
|
||||
|
||||
testables = ''
|
||||
if tests:
|
||||
template = LoadSchemeTemplate(root, 'xcodescheme-testable.template')
|
||||
testables = '\n' + ''.join(
|
||||
template.substitute(
|
||||
BLUEPRINT_IDENTIFIER=key,
|
||||
BUILDABLE_NAME=test_path,
|
||||
BLUEPRINT_NAME=test_name,
|
||||
PROJECT_NAME=project_name)
|
||||
for (key, test_path, test_name) in sorted(tests)).rstrip()
|
||||
|
||||
template = LoadSchemeTemplate(root, 'xcodescheme.template')
|
||||
|
||||
with open(scheme_path, 'w') as scheme_file:
|
||||
scheme_file.write(
|
||||
template.substitute(
|
||||
TESTABLES=testables,
|
||||
LLDBINIT_PATH=LLDBINIT_PATH,
|
||||
BLUEPRINT_IDENTIFIER=identifier,
|
||||
BUILDABLE_NAME=path,
|
||||
BLUEPRINT_NAME=name,
|
||||
PROJECT_NAME=project_name))
|
||||
|
||||
|
||||
class XcodeProject(object):
|
||||
|
@ -46,7 +178,7 @@ class XcodeProject(object):
|
|||
while True:
|
||||
self.counter += 1
|
||||
str_id = "%s %s %d" % (parent_name, obj['isa'], self.counter)
|
||||
new_id = hashlib.sha1(str_id.encode("utf-8")).hexdigest()[:24].upper()
|
||||
new_id = CreateIdentifier(str_id)
|
||||
|
||||
# Make sure ID is unique. It's possible there could be an id conflict
|
||||
# since this is run after GN runs.
|
||||
|
@ -54,6 +186,93 @@ class XcodeProject(object):
|
|||
self.objects[new_id] = obj
|
||||
return new_id
|
||||
|
||||
def IterObjectsByIsa(self, isa):
|
||||
"""Iterates overs objects of the |isa| type."""
|
||||
for key, obj in self.objects.items():
|
||||
if obj['isa'] == isa:
|
||||
yield (key, obj)
|
||||
|
||||
def IterNativeTargetByProductType(self, product_types):
|
||||
"""Iterates over PBXNativeTarget objects of any |product_types| types."""
|
||||
for key, obj in self.IterObjectsByIsa('PBXNativeTarget'):
|
||||
if obj['productType'] in product_types:
|
||||
yield (key, obj)
|
||||
|
||||
def UpdateBuildScripts(self):
|
||||
"""Update build scripts to respect configuration and platforms."""
|
||||
for key, obj in self.IterObjectsByIsa('PBXShellScriptBuildPhase'):
|
||||
|
||||
shell_path = obj['shellPath']
|
||||
shell_code = obj['shellScript']
|
||||
if shell_path.endswith('/sh'):
|
||||
shell_code = shell_code.replace(
|
||||
'ninja -C .',
|
||||
'ninja -C "../${CONFIGURATION}${EFFECTIVE_PLATFORM_NAME}"')
|
||||
elif PYTHON_RE.search(shell_path):
|
||||
shell_code = shell_code.replace(
|
||||
'''ninja_params = [ '-C', '.' ]''',
|
||||
'''ninja_params = [ '-C', '../' + os.environ['CONFIGURATION']'''
|
||||
''' + os.environ['EFFECTIVE_PLATFORM_NAME'] ]''')
|
||||
|
||||
# Replace the build script in the object.
|
||||
obj['shellScript'] = shell_code
|
||||
|
||||
|
||||
def UpdateBuildConfigurations(self, configurations):
|
||||
"""Add new configurations, using the first one as default."""
|
||||
|
||||
# Create a list with all the objects of interest. This is needed
|
||||
# because objects will be added to/removed from the project upon
|
||||
# iterating this list and python dictionaries cannot be mutated
|
||||
# during iteration.
|
||||
for key, obj in list(self.IterObjectsByIsa('XCConfigurationList')):
|
||||
# Use the first build configuration as template for creating all the
|
||||
# new build configurations.
|
||||
build_config_template = self.objects[obj['buildConfigurations'][0]]
|
||||
build_config_template['buildSettings']['CONFIGURATION_BUILD_DIR'] = \
|
||||
'$(PROJECT_DIR)/../$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)'
|
||||
|
||||
|
||||
# Remove the existing build configurations from the project before
|
||||
# creating the new ones.
|
||||
for build_config_id in obj['buildConfigurations']:
|
||||
del self.objects[build_config_id]
|
||||
obj['buildConfigurations'] = []
|
||||
|
||||
for configuration in configurations:
|
||||
build_config = copy.copy(build_config_template)
|
||||
build_config['name'] = configuration
|
||||
build_config_id = self.AddObject('products', build_config)
|
||||
obj['buildConfigurations'].append(build_config_id)
|
||||
|
||||
def GetHostMappingForXCTests(self):
|
||||
"""Returns a dict from targets to the list of their xctests modules."""
|
||||
mapping = collections.defaultdict(list)
|
||||
for key, obj in self.IterNativeTargetByProductType(XCTEST_PRODUCT_TYPES):
|
||||
build_config_lists_id = obj['buildConfigurationList']
|
||||
build_configs = self.objects[build_config_lists_id]['buildConfigurations']
|
||||
|
||||
# Use the first build configuration to get the name of the host target.
|
||||
# This is arbitrary, but since the build configuration are all identical
|
||||
# after UpdateBuildConfiguration, except for their 'name', it is fine.
|
||||
build_config = self.objects[build_configs[0]]
|
||||
if obj['productType'] == 'com.apple.product-type.bundle.unit-test':
|
||||
# The test_host value will look like this:
|
||||
# `$(BUILD_PRODUCTS_DIR)/host_app_name.app/host_app_name`
|
||||
#
|
||||
# Extract the `host_app_name.app` part as key for the output.
|
||||
test_host_path = build_config['buildSettings']['TEST_HOST']
|
||||
test_host_name = os.path.basename(os.path.dirname(test_host_path))
|
||||
else:
|
||||
test_host_name = build_config['buildSettings']['TEST_TARGET_NAME']
|
||||
|
||||
test_name = obj['name']
|
||||
test_path = self.objects[obj['productReference']]['path']
|
||||
|
||||
mapping[test_host_name].append((key, test_name, test_path))
|
||||
|
||||
return dict(mapping)
|
||||
|
||||
|
||||
def check_output(command):
|
||||
"""Wrapper around subprocess.check_output that decode output as utf-8."""
|
||||
|
@ -100,7 +319,7 @@ def WriteXcodeProject(output_path, json_string):
|
|||
os.path.join(output_path, 'project.pbxproj'))
|
||||
|
||||
|
||||
def UpdateXcodeProject(project_dir, configurations, root_dir):
|
||||
def UpdateXcodeProject(project_dir, old_project_dir, configurations, root_dir):
|
||||
"""Update inplace Xcode project to support multiple configurations.
|
||||
|
||||
Args:
|
||||
|
@ -113,41 +332,25 @@ def UpdateXcodeProject(project_dir, configurations, root_dir):
|
|||
json_data = json.loads(LoadXcodeProjectAsJSON(project_dir))
|
||||
project = XcodeProject(json_data['objects'])
|
||||
|
||||
objects_to_remove = []
|
||||
for value in list(project.objects.values()):
|
||||
isa = value['isa']
|
||||
project.UpdateBuildScripts()
|
||||
project.UpdateBuildConfigurations(configurations)
|
||||
|
||||
# Teach build shell script to look for the configuration and platform.
|
||||
if isa == 'PBXShellScriptBuildPhase':
|
||||
shell_path = value['shellPath']
|
||||
if shell_path.endswith('/sh'):
|
||||
value['shellScript'] = value['shellScript'].replace(
|
||||
'ninja -C .',
|
||||
'ninja -C "../${CONFIGURATION}${EFFECTIVE_PLATFORM_NAME}"')
|
||||
elif re.search('[ /]python[23]?$', shell_path):
|
||||
value['shellScript'] = value['shellScript'].replace(
|
||||
'ninja_params = [ \'-C\', \'.\' ]',
|
||||
'ninja_params = [ \'-C\', \'../\' + os.environ[\'CONFIGURATION\']'
|
||||
' + os.environ[\'EFFECTIVE_PLATFORM_NAME\'] ]')
|
||||
mapping = project.GetHostMappingForXCTests()
|
||||
|
||||
# Add new configuration, using the first one as default.
|
||||
if isa == 'XCConfigurationList':
|
||||
value['defaultConfigurationName'] = configurations[0]
|
||||
objects_to_remove.extend(value['buildConfigurations'])
|
||||
# Generate schemes for application, extensions and framework targets
|
||||
for key, obj in project.IterNativeTargetByProductType(SCHEME_PRODUCT_TYPES):
|
||||
product = project.objects[obj['productReference']]
|
||||
product_path = product['path']
|
||||
|
||||
build_config_template = project.objects[value['buildConfigurations'][0]]
|
||||
build_config_template['buildSettings']['CONFIGURATION_BUILD_DIR'] = \
|
||||
'$(PROJECT_DIR)/../$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)'
|
||||
# For XCTests, the key is the product path, while for XCUITests, the key
|
||||
# is the target name. Use a sum of both possible keys (there should not
|
||||
# be overlaps since different hosts are used for XCTests and XCUITests
|
||||
# but this make the code simpler).
|
||||
tests = mapping.get(product_path, []) + mapping.get(obj['name'], [])
|
||||
GenerateSchemeForTarget(
|
||||
root_dir, project_dir, old_project_dir,
|
||||
obj['name'], product_path, tests)
|
||||
|
||||
value['buildConfigurations'] = []
|
||||
for configuration in configurations:
|
||||
new_build_config = copy.copy(build_config_template)
|
||||
new_build_config['name'] = configuration
|
||||
value['buildConfigurations'].append(
|
||||
project.AddObject('products', new_build_config))
|
||||
|
||||
for object_id in objects_to_remove:
|
||||
del project.objects[object_id]
|
||||
|
||||
source = GetOrCreateRootGroup(project, json_data['rootObject'], 'Source')
|
||||
AddMarkdownToProject(project, root_dir, source)
|
||||
|
@ -274,7 +477,7 @@ def GetFolderForPath(project, group_object, path):
|
|||
return group_object
|
||||
|
||||
|
||||
def ConvertGnXcodeProject(root_dir, input_dir, output_dir, configurations):
|
||||
def ConvertGnXcodeProject(root_dir, proj_name, input_dir, output_dir, configs):
|
||||
'''Tweak the Xcode project generated by gn to support multiple configurations.
|
||||
|
||||
The Xcode projects generated by "gn gen --ide" only supports a single
|
||||
|
@ -284,34 +487,22 @@ def ConvertGnXcodeProject(root_dir, input_dir, output_dir, configurations):
|
|||
to select them in Xcode).
|
||||
|
||||
Args:
|
||||
root_dir: directory that is the root of the project
|
||||
proj_name: name of the Xcode project "file" (usually `all.xcodeproj`)
|
||||
input_dir: directory containing the XCode projects created by "gn gen --ide"
|
||||
output_dir: directory where the tweaked Xcode projects will be saved
|
||||
configurations: list of string corresponding to the configurations that
|
||||
need to be supported by the tweaked Xcode projects, must contains at
|
||||
least one value.
|
||||
configs: list of string corresponding to the configurations that need to be
|
||||
supported by the tweaked Xcode projects, must contains at least one
|
||||
value.
|
||||
'''
|
||||
|
||||
# Update the project (supports legacy name "products.xcodeproj" or the new
|
||||
# project name "all.xcodeproj").
|
||||
for project_name in ('all.xcodeproj', 'products.xcodeproj'):
|
||||
if os.path.exists(os.path.join(input_dir, project_name)):
|
||||
UpdateXcodeProject(
|
||||
os.path.join(input_dir, project_name),
|
||||
configurations, root_dir)
|
||||
UpdateXcodeProject(
|
||||
os.path.join(input_dir, proj_name),
|
||||
os.path.join(output_dir, proj_name),
|
||||
configs, root_dir)
|
||||
|
||||
CopyTreeIfChanged(os.path.join(input_dir, project_name),
|
||||
os.path.join(output_dir, project_name))
|
||||
|
||||
else:
|
||||
shutil.rmtree(os.path.join(output_dir, project_name), ignore_errors=True)
|
||||
|
||||
# Copy all.xcworkspace if it exists (will be removed in a future gn version).
|
||||
workspace_name = 'all.xcworkspace'
|
||||
if os.path.exists(os.path.join(input_dir, workspace_name)):
|
||||
CopyTreeIfChanged(os.path.join(input_dir, workspace_name),
|
||||
os.path.join(output_dir, workspace_name))
|
||||
else:
|
||||
shutil.rmtree(os.path.join(output_dir, workspace_name), ignore_errors=True)
|
||||
CopyTreeIfChanged(os.path.join(input_dir, proj_name),
|
||||
os.path.join(output_dir, proj_name))
|
||||
|
||||
|
||||
def Main(args):
|
||||
|
@ -329,33 +520,30 @@ def Main(args):
|
|||
parser.add_argument(
|
||||
'--root', type=os.path.abspath, required=True,
|
||||
help='root directory of the project')
|
||||
parser.add_argument(
|
||||
'--project-name', default='all.xcodeproj', dest='proj_name',
|
||||
help='name of the Xcode project (default: %(default)s)')
|
||||
args = parser.parse_args(args)
|
||||
|
||||
if not os.path.isdir(args.input):
|
||||
sys.stderr.write('Input directory does not exists.\n')
|
||||
return 1
|
||||
|
||||
# Depending on the version of "gn", there should be either one project file
|
||||
# named "all.xcodeproj" or a project file named "products.xcodeproj" and a
|
||||
# workspace named "all.xcworkspace".
|
||||
required_files_sets = [
|
||||
set(("all.xcodeproj",)),
|
||||
set(("products.xcodeproj", "all.xcworkspace")),
|
||||
]
|
||||
|
||||
for required_files in required_files_sets:
|
||||
if required_files.issubset(os.listdir(args.input)):
|
||||
break
|
||||
else:
|
||||
if args.proj_name not in os.listdir(args.input):
|
||||
sys.stderr.write(
|
||||
'Input directory does not contain all necessary Xcode projects.\n')
|
||||
'Input directory does not contain the Xcode project.\n')
|
||||
return 1
|
||||
|
||||
if not args.configurations:
|
||||
sys.stderr.write('At least one configuration required, see --add-config.\n')
|
||||
return 1
|
||||
|
||||
ConvertGnXcodeProject(args.root, args.input, args.output, args.configurations)
|
||||
ConvertGnXcodeProject(
|
||||
args.root,
|
||||
args.proj_name,
|
||||
args.input,
|
||||
args.output,
|
||||
args.configurations)
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(Main(sys.argv[1:]))
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright 2020 The Crashpad Authors. All rights reserved.
|
||||
#
|
||||
|
@ -15,334 +15,391 @@
|
|||
# limitations under the License.
|
||||
|
||||
import argparse
|
||||
import configparser
|
||||
import convert_gn_xcodeproj
|
||||
import errno
|
||||
import io
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
try:
|
||||
import configparser
|
||||
except ImportError:
|
||||
import ConfigParser as configparser
|
||||
|
||||
try:
|
||||
import StringIO as io
|
||||
except ImportError:
|
||||
import io
|
||||
SUPPORTED_TARGETS = ('iphoneos', 'iphonesimulator', 'maccatalyst')
|
||||
SUPPORTED_CONFIGS = ('Debug', 'Release', 'Profile', 'Official')
|
||||
|
||||
SUPPORTED_TARGETS = ('iphoneos', 'iphonesimulator')
|
||||
SUPPORTED_CONFIGS = ('Debug', 'Release', 'Profile', 'Official', 'Coverage')
|
||||
# Pattern matching lines from ~/.lldbinit that must not be copied to the
|
||||
# generated .lldbinit file. They match what the user were told to add to
|
||||
# their global ~/.lldbinit file before setup-gn.py was updated to generate
|
||||
# a project specific file and thus must not be copied as they would cause
|
||||
# the settings to be overwritten.
|
||||
LLDBINIT_SKIP_PATTERNS = (
|
||||
re.compile('^script sys.path\\[:0\\] = \\[\'.*/src/tools/lldb\'\\]$'),
|
||||
re.compile('^script import lldbinit$'),
|
||||
re.compile('^settings append target.source-map .* /google/src/.*$'),
|
||||
)
|
||||
|
||||
|
||||
class ConfigParserWithStringInterpolation(configparser.SafeConfigParser):
|
||||
'''A .ini file parser that supports strings and environment variables.'''
|
||||
def HostCpuArch():
|
||||
'''Returns the arch of the host cpu for GN.'''
|
||||
HOST_CPU_ARCH = {
|
||||
'arm64': '"arm64"',
|
||||
'x86_64': '"x64"',
|
||||
}
|
||||
return HOST_CPU_ARCH[platform.machine()]
|
||||
|
||||
ENV_VAR_PATTERN = re.compile(r'\$([A-Za-z0-9_]+)')
|
||||
|
||||
def values(self, section):
|
||||
return map(lambda kv: self._UnquoteString(self._ExpandEnvVar(kv[1])),
|
||||
configparser.ConfigParser.items(self, section))
|
||||
class ConfigParserWithStringInterpolation(configparser.ConfigParser):
|
||||
|
||||
def getstring(self, section, option):
|
||||
return self._UnquoteString(self._ExpandEnvVar(self.get(section,
|
||||
option)))
|
||||
'''A .ini file parser that supports strings and environment variables.'''
|
||||
|
||||
def _UnquoteString(self, string):
|
||||
if not string or string[0] != '"' or string[-1] != '"':
|
||||
return string
|
||||
return string[1:-1]
|
||||
ENV_VAR_PATTERN = re.compile(r'\$([A-Za-z0-9_]+)')
|
||||
|
||||
def _ExpandEnvVar(self, value):
|
||||
match = self.ENV_VAR_PATTERN.search(value)
|
||||
if not match:
|
||||
return value
|
||||
name, (begin, end) = match.group(1), match.span(0)
|
||||
prefix, suffix = value[:begin], self._ExpandEnvVar(value[end:])
|
||||
return prefix + os.environ.get(name, '') + suffix
|
||||
def values(self, section):
|
||||
return filter(
|
||||
lambda val: val != '',
|
||||
map(lambda kv: self._UnquoteString(self._ExpandEnvVar(kv[1])),
|
||||
configparser.ConfigParser.items(self, section)))
|
||||
|
||||
def getstring(self, section, option, fallback=''):
|
||||
try:
|
||||
raw_value = self.get(section, option)
|
||||
except configparser.NoOptionError:
|
||||
return fallback
|
||||
return self._UnquoteString(self._ExpandEnvVar(raw_value))
|
||||
|
||||
def _UnquoteString(self, string):
|
||||
if not string or string[0] != '"' or string[-1] != '"':
|
||||
return string
|
||||
return string[1:-1]
|
||||
|
||||
def _ExpandEnvVar(self, value):
|
||||
match = self.ENV_VAR_PATTERN.search(value)
|
||||
if not match:
|
||||
return value
|
||||
name, (begin, end) = match.group(1), match.span(0)
|
||||
prefix, suffix = value[:begin], self._ExpandEnvVar(value[end:])
|
||||
return prefix + os.environ.get(name, '') + suffix
|
||||
|
||||
|
||||
class GnGenerator(object):
|
||||
'''Holds configuration for a build and method to generate gn default
|
||||
files.'''
|
||||
|
||||
FAT_BUILD_DEFAULT_ARCH = '64-bit'
|
||||
'''Holds configuration for a build and method to generate gn default files.'''
|
||||
|
||||
TARGET_CPU_VALUES = {
|
||||
'iphoneos': {
|
||||
'32-bit': '"arm"',
|
||||
'64-bit': '"arm64"',
|
||||
},
|
||||
'iphonesimulator': {
|
||||
'32-bit': '"x86"',
|
||||
'64-bit': '"x64"',
|
||||
}
|
||||
}
|
||||
FAT_BUILD_DEFAULT_ARCH = '64-bit'
|
||||
|
||||
def __init__(self, settings, config, target):
|
||||
assert target in SUPPORTED_TARGETS
|
||||
assert config in SUPPORTED_CONFIGS
|
||||
self._settings = settings
|
||||
self._config = config
|
||||
self._target = target
|
||||
TARGET_CPU_VALUES = {
|
||||
'iphoneos': '"arm64"',
|
||||
'iphonesimulator': HostCpuArch(),
|
||||
'maccatalyst': HostCpuArch(),
|
||||
}
|
||||
|
||||
def _GetGnArgs(self):
|
||||
"""Build the list of arguments to pass to gn.
|
||||
TARGET_ENVIRONMENT_VALUES = {
|
||||
'iphoneos': '"device"',
|
||||
'iphonesimulator': '"simulator"',
|
||||
'maccatalyst': '"catalyst"'
|
||||
}
|
||||
|
||||
Returns:
|
||||
A list of tuple containing gn variable names and variable values (it
|
||||
is not a dictionary as the order needs to be preserved).
|
||||
"""
|
||||
args = []
|
||||
def __init__(self, settings, config, target):
|
||||
assert target in SUPPORTED_TARGETS
|
||||
assert config in SUPPORTED_CONFIGS
|
||||
self._settings = settings
|
||||
self._config = config
|
||||
self._target = target
|
||||
|
||||
args.append(('is_debug', self._config in ('Debug', 'Coverage')))
|
||||
def _GetGnArgs(self, extra_args=None):
|
||||
"""Build the list of arguments to pass to gn.
|
||||
|
||||
if os.environ.get('FORCE_MAC_TOOLCHAIN', '0') == '1':
|
||||
args.append(('use_system_xcode', False))
|
||||
Returns:
|
||||
A list of tuple containing gn variable names and variable values (it
|
||||
is not a dictionary as the order needs to be preserved).
|
||||
"""
|
||||
args = []
|
||||
|
||||
cpu_values = self.TARGET_CPU_VALUES[self._target]
|
||||
build_arch = self._settings.getstring('build', 'arch')
|
||||
if build_arch == 'fat':
|
||||
target_cpu = cpu_values[self.FAT_BUILD_DEFAULT_ARCH]
|
||||
args.append(('target_cpu', target_cpu))
|
||||
args.append(
|
||||
('additional_target_cpus',
|
||||
[cpu for cpu in cpu_values.itervalues() if cpu != target_cpu]))
|
||||
else:
|
||||
args.append(('target_cpu', cpu_values[build_arch]))
|
||||
is_debug = self._config == 'Debug'
|
||||
official = self._config == 'Official'
|
||||
is_optim = self._config in ('Profile', 'Official')
|
||||
|
||||
# Add user overrides after the other configurations so that they can
|
||||
# refer to them and override them.
|
||||
args.extend(self._settings.items('gn_args'))
|
||||
return args
|
||||
args.append(('target_os', '"ios"'))
|
||||
args.append(('is_debug', is_debug))
|
||||
|
||||
def Generate(self, gn_path, root_path, out_path):
|
||||
buf = io.StringIO()
|
||||
self.WriteArgsGn(buf)
|
||||
WriteToFileIfChanged(os.path.join(out_path, 'args.gn'),
|
||||
buf.getvalue(),
|
||||
overwrite=True)
|
||||
if os.environ.get('FORCE_MAC_TOOLCHAIN', '0') == '1':
|
||||
args.append(('use_system_xcode', False))
|
||||
|
||||
subprocess.check_call(
|
||||
self.GetGnCommand(gn_path, root_path, out_path, True))
|
||||
args.append(('target_cpu', self.TARGET_CPU_VALUES[self._target]))
|
||||
args.append((
|
||||
'target_environment',
|
||||
self.TARGET_ENVIRONMENT_VALUES[self._target]))
|
||||
|
||||
def CreateGnRules(self, gn_path, root_path, out_path):
|
||||
buf = io.StringIO()
|
||||
self.WriteArgsGn(buf)
|
||||
WriteToFileIfChanged(os.path.join(out_path, 'args.gn'),
|
||||
buf.getvalue(),
|
||||
overwrite=True)
|
||||
# If extra arguments are passed to the function, pass them before the
|
||||
# user overrides (if any).
|
||||
if extra_args is not None:
|
||||
args.extend(extra_args)
|
||||
|
||||
buf = io.StringIO()
|
||||
gn_command = self.GetGnCommand(gn_path, root_path, out_path, False)
|
||||
self.WriteBuildNinja(buf, gn_command)
|
||||
WriteToFileIfChanged(os.path.join(out_path, 'build.ninja'),
|
||||
buf.getvalue(),
|
||||
overwrite=False)
|
||||
# Add user overrides after the other configurations so that they can
|
||||
# refer to them and override them.
|
||||
args.extend(self._settings.items('gn_args'))
|
||||
return args
|
||||
|
||||
buf = io.StringIO()
|
||||
self.WriteBuildNinjaDeps(buf)
|
||||
WriteToFileIfChanged(os.path.join(out_path, 'build.ninja.d'),
|
||||
buf.getvalue(),
|
||||
overwrite=False)
|
||||
|
||||
def WriteArgsGn(self, stream):
|
||||
stream.write('# This file was generated by setup-gn.py. Do not edit\n')
|
||||
stream.write('# but instead use ~/.setup-gn or $repo/.setup-gn files\n')
|
||||
stream.write('# to configure settings.\n')
|
||||
stream.write('\n')
|
||||
def Generate(self, gn_path, proj_name, root_path, build_dir):
|
||||
self.WriteArgsGn(build_dir, xcode_project_name=proj_name)
|
||||
subprocess.check_call(self.GetGnCommand(
|
||||
gn_path, root_path, build_dir, xcode_project_name=proj_name))
|
||||
|
||||
def CreateGnRules(self, gn_path, root_path, build_dir):
|
||||
gn_command = self.GetGnCommand(gn_path, root_path, build_dir)
|
||||
self.WriteArgsGn(build_dir)
|
||||
self.WriteBuildNinja(gn_command, build_dir)
|
||||
self.WriteBuildNinjaDeps(build_dir)
|
||||
|
||||
def WriteArgsGn(self, build_dir, xcode_project_name=None):
|
||||
with open(os.path.join(build_dir, 'args.gn'), 'w') as stream:
|
||||
stream.write('# This file was generated by setup-gn.py. Do not edit\n')
|
||||
stream.write('# but instead use ~/.setup-gn or $repo/.setup-gn files\n')
|
||||
stream.write('# to configure settings.\n')
|
||||
stream.write('\n')
|
||||
|
||||
if self._target != 'maccatalyst':
|
||||
if self._settings.has_section('$imports$'):
|
||||
for import_rule in self._settings.values('$imports$'):
|
||||
stream.write('import("%s")\n' % import_rule)
|
||||
stream.write('\n')
|
||||
for import_rule in self._settings.values('$imports$'):
|
||||
stream.write('import("%s")\n' % import_rule)
|
||||
stream.write('\n')
|
||||
|
||||
gn_args = self._GetGnArgs()
|
||||
for name, value in gn_args:
|
||||
if isinstance(value, bool):
|
||||
stream.write('%s = %s\n' % (name, str(value).lower()))
|
||||
elif isinstance(value, list):
|
||||
stream.write('%s = [%s' %
|
||||
(name, '\n' if len(value) > 1 else ''))
|
||||
if len(value) == 1:
|
||||
prefix = ' '
|
||||
suffix = ' '
|
||||
else:
|
||||
prefix = ' '
|
||||
suffix = ',\n'
|
||||
for item in value:
|
||||
if isinstance(item, bool):
|
||||
stream.write('%s%s%s' %
|
||||
(prefix, str(item).lower(), suffix))
|
||||
else:
|
||||
stream.write('%s%s%s' % (prefix, item, suffix))
|
||||
stream.write(']\n')
|
||||
gn_args = self._GetGnArgs()
|
||||
|
||||
for name, value in gn_args:
|
||||
if isinstance(value, bool):
|
||||
stream.write('%s = %s\n' % (name, str(value).lower()))
|
||||
elif isinstance(value, list):
|
||||
stream.write('%s = [%s' % (name, '\n' if len(value) > 1 else ''))
|
||||
if len(value) == 1:
|
||||
prefix = ' '
|
||||
suffix = ' '
|
||||
else:
|
||||
prefix = ' '
|
||||
suffix = ',\n'
|
||||
for item in value:
|
||||
if isinstance(item, bool):
|
||||
stream.write('%s%s%s' % (prefix, str(item).lower(), suffix))
|
||||
else:
|
||||
stream.write('%s = %s\n' % (name, value))
|
||||
|
||||
def WriteBuildNinja(self, stream, gn_command):
|
||||
stream.write('rule gn\n')
|
||||
stream.write(' command = %s\n' % NinjaEscapeCommand(gn_command))
|
||||
stream.write(' description = Regenerating ninja files\n')
|
||||
stream.write('\n')
|
||||
stream.write('build build.ninja: gn\n')
|
||||
stream.write(' generator = 1\n')
|
||||
stream.write(' depfile = build.ninja.d\n')
|
||||
|
||||
def WriteBuildNinjaDeps(self, stream):
|
||||
stream.write('build.ninja: nonexistant_file.gn\n')
|
||||
|
||||
def GetGnCommand(self, gn_path, src_path, out_path, generate_xcode_project):
|
||||
gn_command = [gn_path, '--root=%s' % os.path.realpath(src_path), '-q']
|
||||
if generate_xcode_project:
|
||||
gn_command.append('--ide=xcode')
|
||||
gn_command.append('--ninja-executable=autoninja')
|
||||
if self._settings.has_section('filters'):
|
||||
target_filters = self._settings.values('filters')
|
||||
if target_filters:
|
||||
gn_command.append('--filters=%s' % ';'.join(target_filters))
|
||||
stream.write('%s%s%s' % (prefix, item, suffix))
|
||||
stream.write(']\n')
|
||||
else:
|
||||
gn_command.append('--check')
|
||||
gn_command.append('gen')
|
||||
gn_command.append('//%s' % os.path.relpath(os.path.abspath(out_path),
|
||||
os.path.abspath(src_path)))
|
||||
return gn_command
|
||||
# ConfigParser removes quote around empty string which confuse
|
||||
# `gn gen` so restore them.
|
||||
if not value:
|
||||
value = '""'
|
||||
stream.write('%s = %s\n' % (name, value))
|
||||
|
||||
def WriteBuildNinja(self, gn_command, build_dir):
|
||||
with open(os.path.join(build_dir, 'build.ninja'), 'w') as stream:
|
||||
stream.write('ninja_required_version = 1.7.2\n')
|
||||
stream.write('\n')
|
||||
stream.write('rule gn\n')
|
||||
stream.write(' command = %s\n' % NinjaEscapeCommand(gn_command))
|
||||
stream.write(' description = Regenerating ninja files\n')
|
||||
stream.write('\n')
|
||||
stream.write('build build.ninja: gn\n')
|
||||
stream.write(' generator = 1\n')
|
||||
stream.write(' depfile = build.ninja.d\n')
|
||||
|
||||
def WriteToFileIfChanged(filename, content, overwrite):
|
||||
'''Write |content| to |filename| if different. If |overwrite| is False
|
||||
and the file already exists it is left untouched.'''
|
||||
if os.path.exists(filename):
|
||||
if not overwrite:
|
||||
return
|
||||
with open(filename) as file:
|
||||
if file.read() == content:
|
||||
return
|
||||
if not os.path.isdir(os.path.dirname(filename)):
|
||||
os.makedirs(os.path.dirname(filename))
|
||||
with open(filename, 'w') as file:
|
||||
file.write(content)
|
||||
def WriteBuildNinjaDeps(self, build_dir):
|
||||
with open(os.path.join(build_dir, 'build.ninja.d'), 'w') as stream:
|
||||
stream.write('build.ninja: nonexistant_file.gn\n')
|
||||
|
||||
def GetGnCommand(self, gn_path, src_path, out_path, xcode_project_name=None):
|
||||
gn_command = [ gn_path, '--root=%s' % os.path.realpath(src_path), '-q' ]
|
||||
if xcode_project_name is not None:
|
||||
gn_command.append('--ide=xcode')
|
||||
gn_command.append('--ninja-executable=autoninja')
|
||||
gn_command.append('--xcode-build-system=new')
|
||||
gn_command.append('--xcode-project=%s' % xcode_project_name)
|
||||
if self._settings.has_section('filters'):
|
||||
target_filters = self._settings.values('filters')
|
||||
if target_filters:
|
||||
gn_command.append('--filters=%s' % ';'.join(target_filters))
|
||||
else:
|
||||
gn_command.append('--check')
|
||||
gn_command.append('gen')
|
||||
gn_command.append('//%s' %
|
||||
os.path.relpath(os.path.abspath(out_path), os.path.abspath(src_path)))
|
||||
return gn_command
|
||||
|
||||
|
||||
def NinjaNeedEscape(arg):
|
||||
'''Returns True if |arg| needs to be escaped when written to .ninja file.'''
|
||||
return ':' in arg or '*' in arg or ';' in arg
|
||||
'''Returns True if |arg| needs to be escaped when written to .ninja file.'''
|
||||
return ':' in arg or '*' in arg or ';' in arg
|
||||
|
||||
|
||||
def NinjaEscapeCommand(command):
|
||||
'''Escapes |command| in order to write it to .ninja file.'''
|
||||
result = []
|
||||
for arg in command:
|
||||
if NinjaNeedEscape(arg):
|
||||
arg = arg.replace(':', '$:')
|
||||
arg = arg.replace(';', '\\;')
|
||||
arg = arg.replace('*', '\\*')
|
||||
else:
|
||||
result.append(arg)
|
||||
return ' '.join(result)
|
||||
'''Escapes |command| in order to write it to .ninja file.'''
|
||||
result = []
|
||||
for arg in command:
|
||||
if NinjaNeedEscape(arg):
|
||||
arg = arg.replace(':', '$:')
|
||||
arg = arg.replace(';', '\\;')
|
||||
arg = arg.replace('*', '\\*')
|
||||
else:
|
||||
result.append(arg)
|
||||
return ' '.join(result)
|
||||
|
||||
|
||||
def FindGn():
|
||||
'''Returns absolute path to gn binary looking at the PATH env variable.'''
|
||||
for path in os.environ['PATH'].split(os.path.pathsep):
|
||||
gn_path = os.path.join(path, 'gn')
|
||||
if os.path.isfile(gn_path) and os.access(gn_path, os.X_OK):
|
||||
return gn_path
|
||||
return None
|
||||
'''Returns absolute path to gn binary looking at the PATH env variable.'''
|
||||
for path in os.environ['PATH'].split(os.path.pathsep):
|
||||
gn_path = os.path.join(path, 'gn')
|
||||
if os.path.isfile(gn_path) and os.access(gn_path, os.X_OK):
|
||||
return gn_path
|
||||
return None
|
||||
|
||||
|
||||
def GenerateXcodeProject(gn_path, root_dir, out_dir, settings):
|
||||
'''Convert GN generated Xcode project into multi-configuration Xcode
|
||||
project.'''
|
||||
def GenerateXcodeProject(gn_path, root_dir, proj_name, out_dir, settings):
|
||||
'''Generate Xcode project with Xcode and convert to multi-configurations.'''
|
||||
prefix = os.path.abspath(os.path.join(out_dir, '_temp'))
|
||||
temp_path = tempfile.mkdtemp(prefix=prefix)
|
||||
try:
|
||||
generator = GnGenerator(settings, 'Debug', 'iphonesimulator')
|
||||
generator.Generate(gn_path, proj_name, root_dir, temp_path)
|
||||
convert_gn_xcodeproj.ConvertGnXcodeProject(
|
||||
root_dir,
|
||||
'%s.xcodeproj' % proj_name,
|
||||
os.path.join(temp_path),
|
||||
os.path.join(out_dir, 'build'),
|
||||
SUPPORTED_CONFIGS)
|
||||
finally:
|
||||
if os.path.exists(temp_path):
|
||||
shutil.rmtree(temp_path)
|
||||
|
||||
temp_path = tempfile.mkdtemp(
|
||||
prefix=os.path.abspath(os.path.join(out_dir, '_temp')))
|
||||
try:
|
||||
generator = GnGenerator(settings, 'Debug', 'iphonesimulator')
|
||||
generator.Generate(gn_path, root_dir, temp_path)
|
||||
convert_gn_xcodeproj.ConvertGnXcodeProject(
|
||||
root_dir, os.path.join(temp_path), os.path.join(out_dir, 'build'),
|
||||
SUPPORTED_CONFIGS)
|
||||
finally:
|
||||
if os.path.exists(temp_path):
|
||||
shutil.rmtree(temp_path)
|
||||
def CreateLLDBInitFile(root_dir, out_dir, settings):
|
||||
'''
|
||||
Generate an .lldbinit file for the project that load the script that fixes
|
||||
the mapping of source files (see docs/ios/build_instructions.md#debugging).
|
||||
'''
|
||||
with open(os.path.join(out_dir, 'build', '.lldbinit'), 'w') as lldbinit:
|
||||
lldb_script_dir = os.path.join(os.path.abspath(root_dir), 'tools', 'lldb')
|
||||
lldbinit.write('script sys.path[:0] = [\'%s\']\n' % lldb_script_dir)
|
||||
lldbinit.write('script import lldbinit\n')
|
||||
|
||||
workspace_name = settings.getstring(
|
||||
'gn_args',
|
||||
'ios_internal_citc_workspace_name')
|
||||
|
||||
if workspace_name != '':
|
||||
username = os.environ['USER']
|
||||
for shortname in ('googlemac', 'third_party', 'blaze-out'):
|
||||
lldbinit.write('settings append target.source-map %s %s\n' % (
|
||||
shortname,
|
||||
'/google/src/cloud/%s/%s/google3/%s' % (
|
||||
username, workspace_name, shortname)))
|
||||
|
||||
# Append the content of //ios/build/tools/lldbinit.defaults if it exists.
|
||||
tools_dir = os.path.join(root_dir, 'ios', 'build', 'tools')
|
||||
defaults_lldbinit_path = os.path.join(tools_dir, 'lldbinit.defaults')
|
||||
if os.path.isfile(defaults_lldbinit_path):
|
||||
with open(defaults_lldbinit_path) as defaults_lldbinit:
|
||||
for line in defaults_lldbinit:
|
||||
lldbinit.write(line)
|
||||
|
||||
# Append the content of ~/.lldbinit if it exists. Line that look like they
|
||||
# are trying to configure source mapping are skipped as they probably date
|
||||
# back from when setup-gn.py was not generating an .lldbinit file.
|
||||
global_lldbinit_path = os.path.join(os.environ['HOME'], '.lldbinit')
|
||||
if os.path.isfile(global_lldbinit_path):
|
||||
with open(global_lldbinit_path) as global_lldbinit:
|
||||
for line in global_lldbinit:
|
||||
if any(pattern.match(line) for pattern in LLDBINIT_SKIP_PATTERNS):
|
||||
continue
|
||||
lldbinit.write(line)
|
||||
|
||||
|
||||
def GenerateGnBuildRules(gn_path, root_dir, out_dir, settings):
|
||||
'''Generates all template configurations for gn.'''
|
||||
for config in SUPPORTED_CONFIGS:
|
||||
for target in SUPPORTED_TARGETS:
|
||||
build_dir = os.path.join(out_dir, '%s-%s' % (config, target))
|
||||
generator = GnGenerator(settings, config, target)
|
||||
generator.CreateGnRules(gn_path, root_dir, build_dir)
|
||||
'''Generates all template configurations for gn.'''
|
||||
for config in SUPPORTED_CONFIGS:
|
||||
for target in SUPPORTED_TARGETS:
|
||||
build_dir = os.path.join(out_dir, '%s-%s' % (config, target))
|
||||
if not os.path.isdir(build_dir):
|
||||
os.makedirs(build_dir)
|
||||
|
||||
generator = GnGenerator(settings, config, target)
|
||||
generator.CreateGnRules(gn_path, root_dir, build_dir)
|
||||
|
||||
|
||||
def Main(args):
|
||||
default_root = os.path.normpath(
|
||||
os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))
|
||||
default_root = os.path.normpath(os.path.join(
|
||||
os.path.dirname(__file__), os.pardir, os.pardir))
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Generate build directories for use with gn.')
|
||||
parser.add_argument(
|
||||
'root',
|
||||
default=default_root,
|
||||
nargs='?',
|
||||
help='root directory where to generate multiple out configurations')
|
||||
parser.add_argument('--import',
|
||||
action='append',
|
||||
dest='import_rules',
|
||||
default=[],
|
||||
help='path to file defining default gn variables')
|
||||
parser.add_argument('--gn-path',
|
||||
default=None,
|
||||
help='path to gn binary (default: look up in $PATH)')
|
||||
parser.add_argument(
|
||||
'--build-dir',
|
||||
default='out',
|
||||
help='path where the build should be created (default: %(default)s)')
|
||||
args = parser.parse_args(args)
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Generate build directories for use with gn.')
|
||||
parser.add_argument(
|
||||
'root', default=default_root, nargs='?',
|
||||
help='root directory where to generate multiple out configurations')
|
||||
parser.add_argument(
|
||||
'--import', action='append', dest='import_rules', default=[],
|
||||
help='path to file defining default gn variables')
|
||||
parser.add_argument(
|
||||
'--gn-path', default=None,
|
||||
help='path to gn binary (default: look up in $PATH)')
|
||||
parser.add_argument(
|
||||
'--build-dir', default='out',
|
||||
help='path where the build should be created (default: %(default)s)')
|
||||
parser.add_argument(
|
||||
'--config-path', default=os.path.expanduser('~/.setup-gn'),
|
||||
help='path to the user config file (default: %(default)s)')
|
||||
parser.add_argument(
|
||||
'--system-config-path', default=os.path.splitext(__file__)[0] + '.config',
|
||||
help='path to the default config file (default: %(default)s)')
|
||||
parser.add_argument(
|
||||
'--project-name', default='all', dest='proj_name',
|
||||
help='name of the generated Xcode project (default: %(default)s)')
|
||||
parser.add_argument(
|
||||
'--no-xcode-project', action='store_true', default=False,
|
||||
help='do not generate the build directory with XCode project')
|
||||
args = parser.parse_args(args)
|
||||
|
||||
# Load configuration (first global and then any user overrides).
|
||||
settings = ConfigParserWithStringInterpolation()
|
||||
settings.read([
|
||||
os.path.splitext(__file__)[0] + '.config',
|
||||
os.path.expanduser('~/.setup-gn'),
|
||||
])
|
||||
# Load configuration (first global and then any user overrides).
|
||||
settings = ConfigParserWithStringInterpolation()
|
||||
settings.read([
|
||||
args.system_config_path,
|
||||
args.config_path,
|
||||
])
|
||||
|
||||
# Add private sections corresponding to --import argument.
|
||||
if args.import_rules:
|
||||
settings.add_section('$imports$')
|
||||
for i, import_rule in enumerate(args.import_rules):
|
||||
if not import_rule.startswith('//'):
|
||||
import_rule = '//%s' % os.path.relpath(
|
||||
os.path.abspath(import_rule), os.path.abspath(args.root))
|
||||
settings.set('$imports$', '$rule%d$' % i, import_rule)
|
||||
# Add private sections corresponding to --import argument.
|
||||
if args.import_rules:
|
||||
settings.add_section('$imports$')
|
||||
for i, import_rule in enumerate(args.import_rules):
|
||||
if not import_rule.startswith('//'):
|
||||
import_rule = '//%s' % os.path.relpath(
|
||||
os.path.abspath(import_rule), os.path.abspath(args.root))
|
||||
settings.set('$imports$', '$rule%d$' % i, import_rule)
|
||||
|
||||
# Validate settings.
|
||||
if settings.getstring('build', 'arch') not in ('64-bit', '32-bit', 'fat'):
|
||||
sys.stderr.write('ERROR: invalid value for build.arch: %s\n' %
|
||||
settings.getstring('build', 'arch'))
|
||||
sys.exit(1)
|
||||
# Validate settings.
|
||||
if settings.getstring('build', 'arch') not in ('64-bit', '32-bit', 'fat'):
|
||||
sys.stderr.write('ERROR: invalid value for build.arch: %s\n' %
|
||||
settings.getstring('build', 'arch'))
|
||||
sys.exit(1)
|
||||
|
||||
# Find path to gn binary either from command-line or in PATH.
|
||||
if args.gn_path:
|
||||
gn_path = args.gn_path
|
||||
else:
|
||||
gn_path = FindGn()
|
||||
if gn_path is None:
|
||||
sys.stderr.write('ERROR: cannot find gn in PATH\n')
|
||||
sys.exit(1)
|
||||
# Find path to gn binary either from command-line or in PATH.
|
||||
if args.gn_path:
|
||||
gn_path = args.gn_path
|
||||
else:
|
||||
gn_path = FindGn()
|
||||
if gn_path is None:
|
||||
sys.stderr.write('ERROR: cannot find gn in PATH\n')
|
||||
sys.exit(1)
|
||||
|
||||
out_dir = os.path.join(args.root, args.build_dir)
|
||||
if not os.path.isdir(out_dir):
|
||||
os.makedirs(out_dir)
|
||||
out_dir = os.path.join(args.root, args.build_dir)
|
||||
if not os.path.isdir(out_dir):
|
||||
os.makedirs(out_dir)
|
||||
|
||||
GenerateXcodeProject(gn_path, args.root, out_dir, settings)
|
||||
GenerateGnBuildRules(gn_path, args.root, out_dir, settings)
|
||||
if not args.no_xcode_project:
|
||||
GenerateXcodeProject(gn_path, args.root, args.proj_name, out_dir, settings)
|
||||
CreateLLDBInitFile(args.root, out_dir, settings)
|
||||
GenerateGnBuildRules(gn_path, args.root, out_dir, settings)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(Main(sys.argv[1:]))
|
||||
sys.exit(Main(sys.argv[1:]))
|
||||
|
|
10
thirdparty/sentry-native/external/crashpad/build/ios/xcodescheme-testable.template
vendored
Normal file
10
thirdparty/sentry-native/external/crashpad/build/ios/xcodescheme-testable.template
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "@{BLUEPRINT_IDENTIFIER}"
|
||||
BuildableName = "@{BUILDABLE_NAME}"
|
||||
BlueprintName = "@{BLUEPRINT_NAME}"
|
||||
ReferencedContainer = "container:@{PROJECT_NAME}">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
|
@ -0,0 +1,80 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1220"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "@{BLUEPRINT_IDENTIFIER}"
|
||||
BuildableName = "@{BUILDABLE_NAME}"
|
||||
BlueprintName = "@{BLUEPRINT_NAME}"
|
||||
ReferencedContainer = "container:@{PROJECT_NAME}">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
customLLDBInitFile = "@{LLDBINIT_PATH}"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>@{TESTABLES}
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
customLLDBInitFile = "@{LLDBINIT_PATH}"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "@{BLUEPRINT_IDENTIFIER}"
|
||||
BuildableName = "@{BUILDABLE_NAME}"
|
||||
BlueprintName = "@{BLUEPRINT_NAME}"
|
||||
ReferencedContainer = "container:@{PROJECT_NAME}">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Profile"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "@{BLUEPRINT_IDENTIFIER}"
|
||||
BuildableName = "@{BUILDABLE_NAME}"
|
||||
BlueprintName = "@{BLUEPRINT_NAME}"
|
||||
ReferencedContainer = "container:@{PROJECT_NAME}">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Official"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
|
@ -1,5 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright 2014 The Crashpad Authors. All rights reserved.
|
||||
#
|
||||
|
@ -15,8 +14,6 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import pipes
|
||||
|
@ -40,7 +37,7 @@ def _FindGNFromBinaryDir(binary_dir):
|
|||
|
||||
build_ninja = os.path.join(binary_dir, 'build.ninja')
|
||||
if os.path.isfile(build_ninja):
|
||||
with open(build_ninja, 'rb') as f:
|
||||
with open(build_ninja, 'r') as f:
|
||||
# Look for the always-generated regeneration rule of the form:
|
||||
#
|
||||
# rule gn
|
||||
|
@ -78,10 +75,11 @@ def _BinaryDirTargetOS(binary_dir):
|
|||
],
|
||||
shell=IS_WINDOWS_HOST,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=open(os.devnull))
|
||||
stderr=open(os.devnull),
|
||||
text=True)
|
||||
value = popen.communicate()[0]
|
||||
if popen.returncode == 0:
|
||||
match = re.match('target_os = "(.*)"$', value.decode('utf-8'))
|
||||
match = re.match('target_os = "(.*)"$', value)
|
||||
if match:
|
||||
return match.group(1)
|
||||
|
||||
|
@ -196,13 +194,14 @@ def _RunOnAndroidTarget(binary_dir, test, android_device, extra_command_line):
|
|||
child = subprocess.Popen(adb_command,
|
||||
shell=IS_WINDOWS_HOST,
|
||||
stdin=open(os.devnull),
|
||||
stdout=subprocess.PIPE)
|
||||
stdout=subprocess.PIPE,
|
||||
text=True)
|
||||
|
||||
FINAL_LINE_RE = re.compile('status=(\d+)$')
|
||||
final_line = None
|
||||
while True:
|
||||
# Use readline so that the test output appears “live” when running.
|
||||
data = child.stdout.readline().decode('utf-8')
|
||||
data = child.stdout.readline()
|
||||
if data == '':
|
||||
break
|
||||
if final_line is not None:
|
||||
|
@ -369,10 +368,11 @@ def _RunOnIOSTarget(binary_dir, test, is_xcuitest=False):
|
|||
|
||||
xctestrun_path = f.name
|
||||
print(xctestrun_path)
|
||||
if is_xcuitest:
|
||||
plistlib.writePlist(xcuitest(binary_dir, test), xctestrun_path)
|
||||
else:
|
||||
plistlib.writePlist(xctest(binary_dir, test), xctestrun_path)
|
||||
with open(xctestrun_path, 'wb') as fp:
|
||||
if is_xcuitest:
|
||||
plistlib.dump(xcuitest(binary_dir, test), fp)
|
||||
else:
|
||||
plistlib.dump(xctest(binary_dir, test), fp)
|
||||
|
||||
subprocess.check_call([
|
||||
'xcodebuild', 'test-without-building', '-xctestrun', xctestrun_path,
|
||||
|
@ -421,10 +421,11 @@ def main(args):
|
|||
android_device = os.environ.get('ANDROID_DEVICE')
|
||||
if not android_device:
|
||||
adb_devices = subprocess.check_output(['adb', 'devices'],
|
||||
shell=IS_WINDOWS_HOST)
|
||||
shell=IS_WINDOWS_HOST,
|
||||
text=True)
|
||||
devices = []
|
||||
for line in adb_devices.splitlines():
|
||||
line = line.decode('utf-8')
|
||||
line = line
|
||||
if (line == 'List of devices attached' or
|
||||
re.match('^\* daemon .+ \*$', line) or line == ''):
|
||||
continue
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
#include "build/build_config.h"
|
||||
|
||||
namespace crashpad {
|
||||
#if defined(OS_IOS)
|
||||
#if BUILDFLAG(IS_IOS)
|
||||
namespace internal {
|
||||
class InProcessIntermediateDumpHandler;
|
||||
} // namespace internal
|
||||
|
@ -72,7 +72,8 @@ class AnnotationList;
|
|||
class Annotation {
|
||||
public:
|
||||
//! \brief The maximum length of an annotation’s name, in bytes.
|
||||
static constexpr size_t kNameMaxLength = 64;
|
||||
//! Matches the behavior of Breakpad's SimpleStringDictionary.
|
||||
static constexpr size_t kNameMaxLength = 256;
|
||||
|
||||
//! \brief The maximum size of an annotation’s value, in bytes.
|
||||
static constexpr size_t kValueMaxSize = 5 * 4096;
|
||||
|
@ -106,7 +107,7 @@ class Annotation {
|
|||
// variables defined in a constexpr function, which is valid. Avoid them
|
||||
// and the also-problematic DCHECK until all the infrastructure is updated:
|
||||
// https://crbug.com/crashpad/201.
|
||||
#if !defined(OS_WIN) || (defined(_MSC_VER) && _MSC_VER >= 1910)
|
||||
#if !BUILDFLAG(IS_WIN) || (defined(_MSC_VER) && _MSC_VER >= 1910)
|
||||
const UnderlyingType start =
|
||||
static_cast<UnderlyingType>(Type::kUserDefinedStart);
|
||||
const UnderlyingType user_type = start + value;
|
||||
|
@ -173,7 +174,7 @@ class Annotation {
|
|||
|
||||
protected:
|
||||
friend class AnnotationList;
|
||||
#if defined(OS_IOS)
|
||||
#if BUILDFLAG(IS_IOS)
|
||||
friend class internal::InProcessIntermediateDumpHandler;
|
||||
#endif
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
#include "client/annotation.h"
|
||||
|
||||
namespace crashpad {
|
||||
#if defined(OS_IOS)
|
||||
#if BUILDFLAG(IS_IOS)
|
||||
namespace internal {
|
||||
class InProcessIntermediateDumpHandler;
|
||||
} // namespace internal
|
||||
|
@ -87,7 +87,7 @@ class AnnotationList {
|
|||
Iterator end();
|
||||
|
||||
protected:
|
||||
#if defined(OS_IOS)
|
||||
#if BUILDFLAG(IS_IOS)
|
||||
friend class internal::InProcessIntermediateDumpHandler;
|
||||
#endif
|
||||
|
||||
|
|
|
@ -28,12 +28,13 @@ namespace {
|
|||
constexpr base::FilePath::CharType kAttachmentsDirectory[] =
|
||||
FILE_PATH_LITERAL("attachments");
|
||||
|
||||
bool AttachmentNameIsOK(const std::string& name) {
|
||||
for (const char c : name) {
|
||||
if (c != '_' && c != '-' && c != '.' && !isalnum(c))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
std::string FixAttachmentName(std::string name) {
|
||||
std::replace_if(name.begin(), name.end(), [&](char c)
|
||||
{
|
||||
return c != '_' && c != '-' && c != '.' && !isalnum(c);
|
||||
}, '_');
|
||||
|
||||
return name;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -68,7 +69,7 @@ bool CrashReportDatabase::NewReport::Initialize(
|
|||
return false;
|
||||
}
|
||||
|
||||
#if defined(OS_WIN)
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
const std::wstring uuid_string = uuid_.ToWString();
|
||||
#else
|
||||
const std::string uuid_string = uuid_.ToString();
|
||||
|
@ -94,19 +95,15 @@ FileReaderInterface* CrashReportDatabase::NewReport::Reader() {
|
|||
|
||||
FileWriter* CrashReportDatabase::NewReport::AddAttachment(
|
||||
const std::string& name) {
|
||||
if (!AttachmentNameIsOK(name)) {
|
||||
LOG(ERROR) << "invalid name for attachment " << name;
|
||||
return nullptr;
|
||||
}
|
||||
base::FilePath report_attachments_dir = database_->AttachmentsPath(uuid_);
|
||||
if (!LoggingCreateDirectory(
|
||||
report_attachments_dir, FilePermissions::kOwnerOnly, true)) {
|
||||
return nullptr;
|
||||
}
|
||||
#if defined(OS_WIN)
|
||||
const std::wstring name_string = base::UTF8ToWide(name);
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
const std::wstring name_string = base::UTF8ToWide(FixAttachmentName(name));
|
||||
#else
|
||||
const std::string name_string = name;
|
||||
const std::string name_string = FixAttachmentName(name);
|
||||
#endif
|
||||
base::FilePath attachment_path = report_attachments_dir.Append(name_string);
|
||||
auto writer = std::make_unique<FileWriter>();
|
||||
|
@ -137,7 +134,7 @@ void CrashReportDatabase::UploadReport::InitializeAttachments() {
|
|||
continue;
|
||||
}
|
||||
attachment_readers_.emplace_back(std::move(file_reader));
|
||||
#if defined(OS_WIN)
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
const std::string name_string = base::WideToUTF8(filename.value());
|
||||
#else
|
||||
const std::string name_string = filename.value();
|
||||
|
@ -177,7 +174,7 @@ CrashReportDatabase::OperationStatus CrashReportDatabase::RecordUploadComplete(
|
|||
}
|
||||
|
||||
base::FilePath CrashReportDatabase::AttachmentsPath(const UUID& uuid) {
|
||||
#if defined(OS_WIN)
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
const std::wstring uuid_string = uuid.ToWString();
|
||||
#else
|
||||
const std::string uuid_string = uuid.ToString();
|
||||
|
|
|
@ -18,9 +18,9 @@
|
|||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
#include "base/ignore_result.h"
|
||||
#include "base/logging.h"
|
||||
#include "build/build_config.h"
|
||||
#include "client/settings.h"
|
||||
|
@ -257,8 +257,16 @@ class CrashReportDatabaseGeneric : public CrashReportDatabase {
|
|||
// Writes the metadata for report to the filesystem at path.
|
||||
static bool WriteMetadata(const base::FilePath& path, const Report& report);
|
||||
|
||||
Settings& SettingsInternal() {
|
||||
if (!settings_init_)
|
||||
settings_.Initialize(base_dir_.Append(kSettings));
|
||||
settings_init_ = true;
|
||||
return settings_;
|
||||
}
|
||||
|
||||
base::FilePath base_dir_;
|
||||
Settings settings_;
|
||||
bool settings_init_ = false;
|
||||
InitializationStateDcheck initialized_;
|
||||
};
|
||||
|
||||
|
@ -289,10 +297,6 @@ bool CrashReportDatabaseGeneric::Initialize(const base::FilePath& path,
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!settings_.Initialize(base_dir_.Append(kSettings))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
INITIALIZATION_STATE_SET_VALID(initialized_);
|
||||
return true;
|
||||
}
|
||||
|
@ -317,7 +321,7 @@ base::FilePath CrashReportDatabaseGeneric::DatabasePath() {
|
|||
|
||||
Settings* CrashReportDatabaseGeneric::GetSettings() {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
return &settings_;
|
||||
return &SettingsInternal();
|
||||
}
|
||||
|
||||
OperationStatus CrashReportDatabaseGeneric::PrepareNewCrashReport(
|
||||
|
@ -356,14 +360,14 @@ OperationStatus CrashReportDatabaseGeneric::FinishedWritingCrashReport(
|
|||
return kFileSystemError;
|
||||
}
|
||||
// We've moved the report to pending, so it no longer needs to be removed.
|
||||
ignore_result(report->file_remover_.release());
|
||||
std::ignore = report->file_remover_.release();
|
||||
|
||||
// Close all the attachments and disarm their removers too.
|
||||
for (auto& writer : report->attachment_writers_) {
|
||||
writer->Close();
|
||||
}
|
||||
for (auto& remover : report->attachment_removers_) {
|
||||
ignore_result(remover.release());
|
||||
std::ignore = remover.release();
|
||||
}
|
||||
|
||||
*uuid = report->ReportID();
|
||||
|
@ -588,7 +592,7 @@ OperationStatus CrashReportDatabaseGeneric::RecordUploadAttempt(
|
|||
return kDatabaseError;
|
||||
}
|
||||
|
||||
if (!settings_.SetLastUploadAttemptTime(now)) {
|
||||
if (!SettingsInternal().SetLastUploadAttemptTime(now)) {
|
||||
return kDatabaseError;
|
||||
}
|
||||
|
||||
|
@ -600,7 +604,7 @@ base::FilePath CrashReportDatabaseGeneric::ReportPath(const UUID& uuid,
|
|||
DCHECK_NE(state, kUninitialized);
|
||||
DCHECK_NE(state, kSearchable);
|
||||
|
||||
#if defined(OS_WIN)
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
const std::wstring uuid_string = uuid.ToWString();
|
||||
#else
|
||||
const std::string uuid_string = uuid.ToString();
|
||||
|
|
|
@ -14,9 +14,9 @@
|
|||
|
||||
#include "client/crash_report_database.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
|
@ -25,8 +25,10 @@
|
|||
#include <unistd.h>
|
||||
#include <uuid/uuid.h>
|
||||
|
||||
#include "base/cxx17_backports.h"
|
||||
#include "base/ignore_result.h"
|
||||
#include <array>
|
||||
#include <iterator>
|
||||
#include <tuple>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/mac/scoped_nsautorelease_pool.h"
|
||||
#include "base/posix/eintr_wrapper.h"
|
||||
|
@ -42,6 +44,10 @@
|
|||
#include "util/misc/initialization_state_dcheck.h"
|
||||
#include "util/misc/metrics.h"
|
||||
|
||||
#if BUILDFLAG(IS_IOS)
|
||||
#include "util/ios/scoped_background_task.h"
|
||||
#endif // BUILDFLAG(IS_IOS)
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
namespace {
|
||||
|
@ -52,7 +58,7 @@ constexpr char kCompletedDirectory[] = "completed";
|
|||
|
||||
constexpr char kSettings[] = "settings.dat";
|
||||
|
||||
constexpr const char* kReportDirectories[] = {
|
||||
constexpr std::array<const char*, 3> kReportDirectories = {
|
||||
kWriteDirectory,
|
||||
kUploadPendingDirectory,
|
||||
kCompletedDirectory,
|
||||
|
@ -178,6 +184,11 @@ class CrashReportDatabaseMac : public CrashReportDatabase {
|
|||
//! \brief A private extension of the Report class that maintains bookkeeping
|
||||
//! information of the database.
|
||||
struct UploadReportMac : public UploadReport {
|
||||
#if BUILDFLAG(IS_IOS)
|
||||
//! \brief Obtain a background task assertion while a flock is in use.
|
||||
//! Ensure this is defined first so it is destroyed last.
|
||||
internal::ScopedBackgroundTask ios_background_task{"UploadReportMac"};
|
||||
#endif // BUILDFLAG(IS_IOS)
|
||||
//! \brief Stores the flock of the file for the duration of
|
||||
//! GetReportForUploading() and RecordUploadAttempt().
|
||||
base::ScopedFD lock_fd;
|
||||
|
@ -251,20 +262,27 @@ class CrashReportDatabaseMac : public CrashReportDatabase {
|
|||
// Cleans any attachments that have no associated report in any state.
|
||||
void CleanOrphanedAttachments();
|
||||
|
||||
Settings& SettingsInternal() {
|
||||
if (!settings_init_)
|
||||
settings_.Initialize(base_dir_.Append(kSettings));
|
||||
settings_init_ = true;
|
||||
return settings_;
|
||||
}
|
||||
|
||||
base::FilePath base_dir_;
|
||||
Settings settings_;
|
||||
bool settings_init_;
|
||||
bool xattr_new_names_;
|
||||
InitializationStateDcheck initialized_;
|
||||
};
|
||||
|
||||
|
||||
CrashReportDatabaseMac::CrashReportDatabaseMac(const base::FilePath& path)
|
||||
: CrashReportDatabase(),
|
||||
base_dir_(path),
|
||||
settings_(),
|
||||
settings_init_(false),
|
||||
xattr_new_names_(false),
|
||||
initialized_() {
|
||||
}
|
||||
initialized_() {}
|
||||
|
||||
CrashReportDatabaseMac::~CrashReportDatabaseMac() {}
|
||||
|
||||
|
@ -281,8 +299,8 @@ bool CrashReportDatabaseMac::Initialize(bool may_create) {
|
|||
}
|
||||
|
||||
// Create the three processing directories for the database.
|
||||
for (size_t i = 0; i < base::size(kReportDirectories); ++i) {
|
||||
if (!CreateOrEnsureDirectoryExists(base_dir_.Append(kReportDirectories[i])))
|
||||
for (const auto& dir : kReportDirectories) {
|
||||
if (!CreateOrEnsureDirectoryExists(base_dir_.Append(dir)))
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -290,9 +308,6 @@ bool CrashReportDatabaseMac::Initialize(bool may_create) {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!settings_.Initialize(base_dir_.Append(kSettings)))
|
||||
return false;
|
||||
|
||||
// Do an xattr operation as the last step, to ensure the filesystem has
|
||||
// support for them. This xattr also serves as a marker for whether the
|
||||
// database uses old or new xattr names.
|
||||
|
@ -323,7 +338,7 @@ base::FilePath CrashReportDatabaseMac::DatabasePath() {
|
|||
|
||||
Settings* CrashReportDatabaseMac::GetSettings() {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
return &settings_;
|
||||
return &SettingsInternal();
|
||||
}
|
||||
|
||||
CrashReportDatabase::OperationStatus
|
||||
|
@ -385,14 +400,14 @@ CrashReportDatabaseMac::FinishedWritingCrashReport(
|
|||
PLOG(ERROR) << "rename " << path.value() << " to " << new_path.value();
|
||||
return kFileSystemError;
|
||||
}
|
||||
ignore_result(report->file_remover_.release());
|
||||
std::ignore = report->file_remover_.release();
|
||||
|
||||
// Close all the attachments and disarm their removers too.
|
||||
for (auto& writer : report->attachment_writers_) {
|
||||
writer->Close();
|
||||
}
|
||||
for (auto& remover : report->attachment_removers_) {
|
||||
ignore_result(remover.release());
|
||||
std::ignore = remover.release();
|
||||
}
|
||||
|
||||
Metrics::CrashReportPending(Metrics::PendingReportReason::kNewlyCreated);
|
||||
|
@ -516,7 +531,7 @@ CrashReportDatabaseMac::RecordUploadAttempt(UploadReport* report,
|
|||
return kDatabaseError;
|
||||
}
|
||||
|
||||
if (!settings_.SetLastUploadAttemptTime(now)) {
|
||||
if (!SettingsInternal().SetLastUploadAttemptTime(now)) {
|
||||
return kDatabaseError;
|
||||
}
|
||||
|
||||
|
|
|
@ -185,6 +185,41 @@ TEST_F(CrashReportDatabaseTest, Initialize) {
|
|||
EXPECT_FALSE(db);
|
||||
}
|
||||
|
||||
TEST_F(CrashReportDatabaseTest, Settings) {
|
||||
// Initialize three databases and ensure settings.dat isn't created yet.
|
||||
ASSERT_TRUE(db());
|
||||
|
||||
base::FilePath settings_path =
|
||||
path().Append(FILE_PATH_LITERAL("settings.dat"));
|
||||
EXPECT_FALSE(FileExists(settings_path));
|
||||
|
||||
std::unique_ptr<CrashReportDatabase> db2 =
|
||||
CrashReportDatabase::Initialize(path());
|
||||
ASSERT_TRUE(db2);
|
||||
EXPECT_FALSE(FileExists(settings_path));
|
||||
|
||||
std::unique_ptr<CrashReportDatabase> db3 =
|
||||
CrashReportDatabase::Initialize(path());
|
||||
ASSERT_TRUE(db3);
|
||||
EXPECT_FALSE(FileExists(settings_path));
|
||||
|
||||
// Ensure settings.dat exists after getter.
|
||||
Settings* settings = db3->GetSettings();
|
||||
ASSERT_TRUE(settings);
|
||||
EXPECT_TRUE(FileExists(settings_path));
|
||||
|
||||
time_t last_upload_attempt_time = 42;
|
||||
ASSERT_TRUE(settings->SetLastUploadAttemptTime(last_upload_attempt_time));
|
||||
|
||||
// Ensure the first two databases read the same value.
|
||||
ASSERT_TRUE(
|
||||
db2->GetSettings()->GetLastUploadAttemptTime(&last_upload_attempt_time));
|
||||
EXPECT_EQ(last_upload_attempt_time, 42);
|
||||
ASSERT_TRUE(
|
||||
db()->GetSettings()->GetLastUploadAttemptTime(&last_upload_attempt_time));
|
||||
EXPECT_EQ(last_upload_attempt_time, 42);
|
||||
}
|
||||
|
||||
TEST_F(CrashReportDatabaseTest, NewCrashReport) {
|
||||
std::unique_ptr<CrashReportDatabase::NewReport> new_report;
|
||||
EXPECT_EQ(db()->PrepareNewCrashReport(&new_report),
|
||||
|
@ -737,7 +772,7 @@ TEST_F(CrashReportDatabaseTest, OrphanedAttachments) {
|
|||
|
||||
ASSERT_TRUE(LoggingRemoveFile(report.file_path));
|
||||
|
||||
#if !defined(OS_APPLE) && !defined(OS_WIN)
|
||||
#if !BUILDFLAG(IS_APPLE) && !BUILDFLAG(IS_WIN)
|
||||
// CrashReportDatabaseMac stores metadata in xattrs and does not have .meta
|
||||
// files.
|
||||
// CrashReportDatabaseWin stores metadata in a global metadata file and not
|
||||
|
@ -749,7 +784,7 @@ TEST_F(CrashReportDatabaseTest, OrphanedAttachments) {
|
|||
ASSERT_EQ(db()->LookUpCrashReport(uuid, &report),
|
||||
CrashReportDatabase::kReportNotFound);
|
||||
|
||||
#if defined(OS_WIN)
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
const std::wstring uuid_string = uuid.ToWString();
|
||||
#else
|
||||
const std::string uuid_string = uuid.ToString();
|
||||
|
@ -763,7 +798,7 @@ TEST_F(CrashReportDatabaseTest, OrphanedAttachments) {
|
|||
EXPECT_TRUE(FileExists(file_path1));
|
||||
EXPECT_TRUE(FileExists(file_path1));
|
||||
|
||||
#if defined(OS_WIN)
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
// On Windows, reports removed from metadata are counted, even if the file
|
||||
// is not on the disk.
|
||||
EXPECT_EQ(db()->CleanDatabase(0), 1);
|
||||
|
@ -778,7 +813,7 @@ TEST_F(CrashReportDatabaseTest, OrphanedAttachments) {
|
|||
|
||||
// This test uses knowledge of the database format to break it, so it only
|
||||
// applies to the unfified database implementation.
|
||||
#if !defined(OS_APPLE) && !defined(OS_WIN)
|
||||
#if !BUILDFLAG(IS_APPLE) && !BUILDFLAG(IS_WIN)
|
||||
TEST_F(CrashReportDatabaseTest, CleanBrokenDatabase) {
|
||||
// Remove report files if metadata goes missing.
|
||||
CrashReportDatabase::Report report;
|
||||
|
@ -843,7 +878,7 @@ TEST_F(CrashReportDatabaseTest, CleanBrokenDatabase) {
|
|||
EXPECT_FALSE(PathExists(report.file_path));
|
||||
EXPECT_FALSE(PathExists(metadata3));
|
||||
}
|
||||
#endif // !OS_APPLE && !OS_WIN
|
||||
#endif // !BUILDFLAG(IS_APPLE) && !BUILDFLAG(IS_WIN)
|
||||
|
||||
TEST_F(CrashReportDatabaseTest, TotalSize_MainReportOnly) {
|
||||
std::unique_ptr<CrashReportDatabase::NewReport> new_report;
|
||||
|
|
|
@ -21,9 +21,9 @@
|
|||
#include <time.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
#include "base/ignore_result.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/numerics/safe_math.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
|
@ -662,13 +662,25 @@ class CrashReportDatabaseWin : public CrashReportDatabase {
|
|||
|
||||
std::unique_ptr<Metadata> AcquireMetadata();
|
||||
|
||||
Settings& SettingsInternal() {
|
||||
if (!settings_init_)
|
||||
settings_.Initialize(base_dir_.Append(kSettings));
|
||||
settings_init_ = true;
|
||||
return settings_;
|
||||
}
|
||||
|
||||
base::FilePath base_dir_;
|
||||
Settings settings_;
|
||||
bool settings_init_;
|
||||
InitializationStateDcheck initialized_;
|
||||
};
|
||||
|
||||
CrashReportDatabaseWin::CrashReportDatabaseWin(const base::FilePath& path)
|
||||
: CrashReportDatabase(), base_dir_(path), settings_(), initialized_() {}
|
||||
: CrashReportDatabase(),
|
||||
base_dir_(path),
|
||||
settings_(),
|
||||
settings_init_(false),
|
||||
initialized_() {}
|
||||
|
||||
CrashReportDatabaseWin::~CrashReportDatabaseWin() {
|
||||
}
|
||||
|
@ -691,9 +703,6 @@ bool CrashReportDatabaseWin::Initialize(bool may_create) {
|
|||
if (!CreateDirectoryIfNecessary(AttachmentsRootPath()))
|
||||
return false;
|
||||
|
||||
if (!settings_.Initialize(base_dir_.Append(kSettings)))
|
||||
return false;
|
||||
|
||||
INITIALIZATION_STATE_SET_VALID(initialized_);
|
||||
return true;
|
||||
}
|
||||
|
@ -704,7 +713,7 @@ base::FilePath CrashReportDatabaseWin::DatabasePath() {
|
|||
|
||||
Settings* CrashReportDatabaseWin::GetSettings() {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
return &settings_;
|
||||
return &SettingsInternal();
|
||||
}
|
||||
|
||||
OperationStatus CrashReportDatabaseWin::PrepareNewCrashReport(
|
||||
|
@ -735,14 +744,14 @@ OperationStatus CrashReportDatabaseWin::FinishedWritingCrashReport(
|
|||
time(nullptr),
|
||||
ReportState::kPending));
|
||||
|
||||
ignore_result(report->file_remover_.release());
|
||||
std::ignore = report->file_remover_.release();
|
||||
|
||||
// Close all the attachments and disarm their removers too.
|
||||
for (auto& writer : report->attachment_writers_) {
|
||||
writer->Close();
|
||||
}
|
||||
for (auto& remover : report->attachment_removers_) {
|
||||
ignore_result(remover.release());
|
||||
std::ignore = remover.release();
|
||||
}
|
||||
|
||||
*uuid = report->ReportID();
|
||||
|
@ -848,7 +857,7 @@ OperationStatus CrashReportDatabaseWin::RecordUploadAttempt(
|
|||
report->upload_explicitly_requested;
|
||||
}
|
||||
|
||||
if (!settings_.SetLastUploadAttemptTime(now))
|
||||
if (!SettingsInternal().SetLastUploadAttemptTime(now))
|
||||
return kDatabaseError;
|
||||
|
||||
return kNoError;
|
||||
|
|
|
@ -28,12 +28,12 @@
|
|||
#include "util/file/file_io.h"
|
||||
#include "util/misc/capture_context.h"
|
||||
|
||||
#if defined(OS_APPLE)
|
||||
#if BUILDFLAG(IS_APPLE)
|
||||
#include "base/mac/scoped_mach_port.h"
|
||||
#elif defined(OS_WIN)
|
||||
#elif BUILDFLAG(IS_WIN)
|
||||
#include <windows.h>
|
||||
#include "util/win/scoped_handle.h"
|
||||
#elif defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID)
|
||||
#elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
|
||||
#include <signal.h>
|
||||
#include <ucontext.h>
|
||||
#endif
|
||||
|
@ -125,7 +125,8 @@ class CrashpadClient {
|
|||
bool asynchronous_start,
|
||||
const std::vector<base::FilePath>& attachments = {});
|
||||
|
||||
#if defined(OS_ANDROID) || defined(OS_LINUX) || defined(OS_CHROMEOS) || DOXYGEN
|
||||
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || \
|
||||
DOXYGEN
|
||||
//! \brief Retrieve the socket and process ID for the handler.
|
||||
//!
|
||||
//! `StartHandler()` must have successfully been called before calling this
|
||||
|
@ -170,9 +171,10 @@ class CrashpadClient {
|
|||
//!
|
||||
//! \return `true` on success. Otherwise `false` with a message logged.
|
||||
static bool InitializeSignalStackForThread();
|
||||
#endif // OS_ANDROID || OS_LINUX || OS_CHROMEOS || DOXYGEN
|
||||
#endif // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX) ||
|
||||
// BUILDFLAG(IS_CHROMEOS) || DOXYGEN
|
||||
|
||||
#if defined(OS_ANDROID) || DOXYGEN
|
||||
#if BUILDFLAG(IS_ANDROID) || DOXYGEN
|
||||
//! \brief Installs a signal handler to execute `/system/bin/app_process` and
|
||||
//! load a Java class in response to a crash.
|
||||
//!
|
||||
|
@ -339,9 +341,10 @@ class CrashpadClient {
|
|||
const std::map<std::string, std::string>& annotations,
|
||||
const std::vector<std::string>& arguments,
|
||||
int socket);
|
||||
#endif // OS_ANDROID || DOXYGEN
|
||||
#endif // BUILDFLAG(IS_ANDROID) || DOXYGEN
|
||||
|
||||
#if defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_CHROMEOS) || DOXYGEN
|
||||
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_CHROMEOS) || \
|
||||
DOXYGEN
|
||||
//! \brief Installs a signal handler to launch a handler process in reponse to
|
||||
//! a crash.
|
||||
//!
|
||||
|
@ -418,7 +421,7 @@ class CrashpadClient {
|
|||
//! CaptureContext() or similar.
|
||||
static void DumpWithoutCrash(NativeCPUContext* context);
|
||||
|
||||
//! \brief Disables any installed crash handler, including any
|
||||
//! \brief Disables any installed crash handler, not including any
|
||||
//! FirstChanceHandler and crashes the current process.
|
||||
//!
|
||||
//! \param[in] message A message to be logged before crashing.
|
||||
|
@ -454,9 +457,10 @@ class CrashpadClient {
|
|||
//!
|
||||
//! \param[in] unhandled_signals The set of unhandled signals
|
||||
void SetUnhandledSignals(const std::set<int>& unhandled_signals);
|
||||
#endif // OS_LINUX || OS_ANDROID || OS_CHROMEOS || DOXYGEN
|
||||
#endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_ANDROID) ||
|
||||
// BUILDFLAG(IS_CHROMEOS) || DOXYGEN
|
||||
|
||||
#if defined(OS_IOS) || DOXYGEN
|
||||
#if BUILDFLAG(IS_IOS) || DOXYGEN
|
||||
//! \brief Configures the process to direct its crashes to the iOS in-process
|
||||
//! Crashpad handler.
|
||||
//!
|
||||
|
@ -554,9 +558,21 @@ class CrashpadClient {
|
|||
static void DumpWithoutCrashAndDeferProcessingAtPath(
|
||||
NativeCPUContext* context,
|
||||
const base::FilePath path);
|
||||
|
||||
//! \brief Unregister the Crashpad client. Intended to be used by tests so
|
||||
//! multiple Crashpad clients can be started and stopped. Not expected to
|
||||
//! be used in a shipping application.
|
||||
static void ResetForTesting();
|
||||
|
||||
//! \brief Inject a callback into Mach handling. Intended to be used by
|
||||
//! tests to trigger a reentrant exception.
|
||||
static void SetMachExceptionCallbackForTesting(void (*callback)());
|
||||
|
||||
//! \brief Returns the thread id of the Mach exception thread, used by tests.
|
||||
static uint64_t GetThreadIdForTesting();
|
||||
#endif
|
||||
|
||||
#if defined(OS_APPLE) || DOXYGEN
|
||||
#if BUILDFLAG(IS_APPLE) || DOXYGEN
|
||||
//! \brief Sets the process’ crash handler to a Mach service registered with
|
||||
//! the bootstrap server.
|
||||
//!
|
||||
|
@ -606,7 +622,7 @@ class CrashpadClient {
|
|||
base::mac::ScopedMachSendRight GetHandlerMachPort() const;
|
||||
#endif
|
||||
|
||||
#if defined(OS_WIN) || DOXYGEN
|
||||
#if BUILDFLAG(IS_WIN) || DOXYGEN
|
||||
//! \brief The type for custom handlers installed by clients.
|
||||
using FirstChanceHandlerWin = bool (*)(EXCEPTION_POINTERS*);
|
||||
|
||||
|
@ -714,20 +730,9 @@ class CrashpadClient {
|
|||
static bool DumpAndCrashTargetProcess(HANDLE process,
|
||||
HANDLE blame_thread,
|
||||
DWORD exception_code);
|
||||
|
||||
enum : uint32_t {
|
||||
//! \brief The exception code (roughly "Client called") used when
|
||||
//! DumpAndCrashTargetProcess() triggers an exception in a target
|
||||
//! process.
|
||||
//!
|
||||
//! \note This value does not have any bits of the top nibble set, to avoid
|
||||
//! confusion with real exception codes which tend to have those bits
|
||||
//! set.
|
||||
kTriggeredExceptionCode = 0xcca11ed,
|
||||
};
|
||||
#endif
|
||||
|
||||
#if defined(OS_APPLE) || DOXYGEN
|
||||
#if BUILDFLAG(IS_APPLE) || DOXYGEN
|
||||
//! \brief Configures the process to direct its crashes to the default handler
|
||||
//! for the operating system.
|
||||
//!
|
||||
|
@ -761,14 +766,14 @@ class CrashpadClient {
|
|||
#endif
|
||||
|
||||
private:
|
||||
#if defined(OS_APPLE)
|
||||
#if BUILDFLAG(IS_APPLE)
|
||||
base::mac::ScopedMachSendRight exception_port_;
|
||||
#elif defined(OS_WIN)
|
||||
#elif BUILDFLAG(IS_WIN)
|
||||
std::wstring ipc_pipe_;
|
||||
ScopedKernelHANDLE handler_start_thread_;
|
||||
#elif defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID)
|
||||
#elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
|
||||
std::set<int> unhandled_signals_;
|
||||
#endif // OS_APPLE
|
||||
#endif // BUILDFLAG(IS_APPLE)
|
||||
};
|
||||
|
||||
} // namespace crashpad
|
||||
|
|
|
@ -14,17 +14,19 @@
|
|||
|
||||
#include "client/crashpad_client.h"
|
||||
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <ios>
|
||||
#include <iterator>
|
||||
|
||||
#include "base/cxx17_backports.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/mac/mach_logging.h"
|
||||
#include "base/mac/scoped_mach_port.h"
|
||||
#include "client/ios_handler/exception_processor.h"
|
||||
#include "client/ios_handler/in_process_handler.h"
|
||||
#include "util/ios/ios_system_data_collector.h"
|
||||
#include "util/ios/raw_logging.h"
|
||||
#include "util/mach/exc_server_variants.h"
|
||||
#include "util/mach/exception_ports.h"
|
||||
#include "util/mach/mach_extensions.h"
|
||||
|
@ -40,7 +42,7 @@ bool IsBeingDebugged() {
|
|||
kinfo_proc kern_proc_info;
|
||||
int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()};
|
||||
size_t len = sizeof(kern_proc_info);
|
||||
if (sysctl(mib, base::size(mib), &kern_proc_info, &len, nullptr, 0) == 0)
|
||||
if (sysctl(mib, std::size(mib), &kern_proc_info, &len, nullptr, 0) == 0)
|
||||
return kern_proc_info.kp_proc.p_flag & P_TRACED;
|
||||
return false;
|
||||
}
|
||||
|
@ -60,21 +62,54 @@ class CrashHandler : public Thread,
|
|||
CrashHandler& operator=(const CrashHandler&) = delete;
|
||||
|
||||
static CrashHandler* Get() {
|
||||
static CrashHandler* instance = new CrashHandler();
|
||||
return instance;
|
||||
if (!instance_)
|
||||
instance_ = new CrashHandler();
|
||||
return instance_;
|
||||
}
|
||||
|
||||
static void ResetForTesting() {
|
||||
delete instance_;
|
||||
instance_ = nullptr;
|
||||
}
|
||||
|
||||
bool Initialize(const base::FilePath& database,
|
||||
const std::string& url,
|
||||
const std::map<std::string, std::string>& annotations) {
|
||||
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
|
||||
if (!in_process_handler_.Initialize(
|
||||
database, url, annotations, system_data_) ||
|
||||
if (!in_process_handler_.Initialize(database, url, annotations) ||
|
||||
!InstallMachExceptionHandler() ||
|
||||
!Signals::InstallHandler(SIGABRT, CatchSignal, 0, &old_action_)) {
|
||||
// xnu turns hardware faults into Mach exceptions, so the only signal
|
||||
// left to register is SIGABRT, which never starts off as a hardware
|
||||
// fault. Installing a handler for other signals would lead to
|
||||
// recording exceptions twice. As a consequence, Crashpad will not
|
||||
// generate intermediate dumps for anything manually calling
|
||||
// raise(SIG*). In practice, this doesn’t actually happen for crash
|
||||
// signals that originate as hardware faults.
|
||||
!Signals::InstallHandler(
|
||||
SIGABRT, CatchAndReraiseSignal, 0, &old_action_)) {
|
||||
LOG(ERROR) << "Unable to initialize Crashpad.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// For applications that haven't ignored or set a handler for SIGPIPE:
|
||||
// It’s OK for an application to set its own SIGPIPE handler (including
|
||||
// SIG_IGN) before initializing Crashpad, because Crashpad will discover the
|
||||
// existing handler and not install its own.
|
||||
// It’s OK for Crashpad to install its own SIGPIPE handler and for the
|
||||
// application to subsequently install its own (including SIG_IGN)
|
||||
// afterwards, because its handler will replace Crashpad’s.
|
||||
// This is useful to cover the default situation where nobody installs a
|
||||
// SIGPIPE handler and the disposition is at SIG_DFL, because SIGPIPE is a
|
||||
// “kill” signal (bsd/sys/signalvar.h sigprop). In that case, without
|
||||
// Crashpad, SIGPIPE results in a silent and unreported kill (and not even
|
||||
// ReportCrash will record it), but developers probably want to be alerted
|
||||
// to the conditon.
|
||||
struct sigaction sa;
|
||||
if (sigaction(SIGPIPE, nullptr, &sa) == 0 && sa.sa_handler == SIG_DFL) {
|
||||
Signals::InstallHandler(
|
||||
SIGPIPE, CatchAndReraiseSignalDefaultAction, 0, nullptr);
|
||||
}
|
||||
|
||||
InstallObjcExceptionPreprocessor(this);
|
||||
INITIALIZATION_STATE_SET_VALID(initialized_);
|
||||
return true;
|
||||
|
@ -91,37 +126,23 @@ class CrashHandler : public Thread,
|
|||
in_process_handler_.ProcessIntermediateDump(file, annotations);
|
||||
}
|
||||
|
||||
void DumpWithContext(NativeCPUContext* context) {
|
||||
const mach_exception_data_type_t code[2] = {};
|
||||
static constexpr int kSimulatedException = -1;
|
||||
HandleMachException(MACH_EXCEPTION_CODES,
|
||||
mach_thread_self(),
|
||||
kSimulatedException,
|
||||
code,
|
||||
base::size(code),
|
||||
MACHINE_THREAD_STATE,
|
||||
reinterpret_cast<ConstThreadState>(context),
|
||||
MACHINE_THREAD_STATE_COUNT);
|
||||
}
|
||||
|
||||
void DumpWithoutCrash(NativeCPUContext* context, bool process_dump) {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
internal::InProcessHandler::ScopedAlternateWriter scoper(
|
||||
&in_process_handler_);
|
||||
if (scoper.Open()) {
|
||||
DumpWithContext(context);
|
||||
if (process_dump) {
|
||||
in_process_handler_.ProcessIntermediateDump(scoper.path());
|
||||
}
|
||||
base::FilePath path;
|
||||
if (!in_process_handler_.DumpExceptionFromSimulatedMachException(
|
||||
context, kMachExceptionSimulated, &path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (process_dump) {
|
||||
in_process_handler_.ProcessIntermediateDump(path);
|
||||
}
|
||||
}
|
||||
|
||||
void DumpWithoutCrashAtPath(NativeCPUContext* context,
|
||||
const base::FilePath& path) {
|
||||
internal::InProcessHandler::ScopedAlternateWriter scoper(
|
||||
&in_process_handler_);
|
||||
if (scoper.OpenAtPath(path))
|
||||
DumpWithContext(context);
|
||||
in_process_handler_.DumpExceptionFromSimulatedMachExceptionAtPath(
|
||||
context, kMachExceptionSimulated, path);
|
||||
}
|
||||
|
||||
void StartProcessingPendingReports() {
|
||||
|
@ -129,9 +150,21 @@ class CrashHandler : public Thread,
|
|||
in_process_handler_.StartProcessingPendingReports();
|
||||
}
|
||||
|
||||
void SetMachExceptionCallbackForTesting(void (*callback)()) {
|
||||
in_process_handler_.SetMachExceptionCallbackForTesting(callback);
|
||||
}
|
||||
|
||||
uint64_t GetThreadIdForTesting() { return Thread::GetThreadIdForTesting(); }
|
||||
|
||||
private:
|
||||
CrashHandler() = default;
|
||||
|
||||
~CrashHandler() {
|
||||
UninstallObjcExceptionPreprocessor();
|
||||
Signals::InstallDefaultHandler(SIGABRT);
|
||||
UninstallMachExceptionHandler();
|
||||
}
|
||||
|
||||
bool InstallMachExceptionHandler() {
|
||||
exception_port_.reset(NewMachPort(MACH_PORT_RIGHT_RECEIVE));
|
||||
if (!exception_port_.is_valid()) {
|
||||
|
@ -164,15 +197,22 @@ class CrashHandler : public Thread,
|
|||
return false;
|
||||
}
|
||||
|
||||
mach_handler_running_ = true;
|
||||
Start();
|
||||
return true;
|
||||
}
|
||||
|
||||
void UninstallMachExceptionHandler() {
|
||||
mach_handler_running_ = false;
|
||||
exception_port_.reset();
|
||||
Join();
|
||||
}
|
||||
|
||||
// Thread:
|
||||
|
||||
void ThreadMain() override {
|
||||
UniversalMachExcServer universal_mach_exc_server(this);
|
||||
while (true) {
|
||||
while (mach_handler_running_) {
|
||||
mach_msg_return_t mr =
|
||||
MachMessageServer::Run(&universal_mach_exc_server,
|
||||
exception_port_.get(),
|
||||
|
@ -180,7 +220,20 @@ class CrashHandler : public Thread,
|
|||
MachMessageServer::kPersistent,
|
||||
MachMessageServer::kReceiveLargeIgnore,
|
||||
kMachMessageTimeoutWaitIndefinitely);
|
||||
MACH_CHECK(mr == MACH_SEND_INVALID_DEST, mr) << "MachMessageServer::Run";
|
||||
MACH_CHECK(
|
||||
mach_handler_running_
|
||||
? mr == MACH_SEND_INVALID_DEST // This shouldn't happen for
|
||||
// exception messages that come
|
||||
// from the kernel itself, but if
|
||||
// something else in-process sends
|
||||
// exception messages and breaks,
|
||||
// handle that case.
|
||||
: (mr == MACH_RCV_PORT_CHANGED || // Port was closed while the
|
||||
// thread was listening.
|
||||
mr == MACH_RCV_INVALID_NAME), // Port was closed before the
|
||||
// thread started listening.
|
||||
mr)
|
||||
<< "MachMessageServer::Run";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -209,8 +262,7 @@ class CrashHandler : public Thread,
|
|||
// inherit the task exception ports, and this process isn’t prepared to
|
||||
// handle them
|
||||
if (task != mach_task_self()) {
|
||||
LOG(WARNING) << "task 0x" << std::hex << task << " != 0x"
|
||||
<< mach_task_self();
|
||||
CRASHPAD_RAW_LOG("MachException task != mach_task_self()");
|
||||
return KERN_FAILURE;
|
||||
}
|
||||
|
||||
|
@ -224,7 +276,31 @@ class CrashHandler : public Thread,
|
|||
old_state_count);
|
||||
|
||||
// Respond with KERN_FAILURE so the system will continue to handle this
|
||||
// exception as a crash.
|
||||
// exception. xnu will turn this Mach exception into a signal and take the
|
||||
// default action to terminate the process. However, if sigprocmask is
|
||||
// called before this Mach exception returns (such as by another thread
|
||||
// calling abort, see: Libc-1506.40.4/stdlib/FreeBSD/abort.c), the Mach
|
||||
// exception will be converted into a signal but delivery will be blocked.
|
||||
// Since concurrent exceptions lead to the losing thread sleeping
|
||||
// indefinitely, if the abort thread never returns, the thread that
|
||||
// triggered this Mach exception will repeatedly trap and the process will
|
||||
// never terminate. If the abort thread didn’t have a user-space signal
|
||||
// handler that slept forever, the abort would terminate the process even if
|
||||
// all other signals had been blocked. Instead, unblock all signals
|
||||
// corresponding to all Mach exceptions Crashpad is registered for before
|
||||
// returning KERN_FAILURE. There is still racy behavior possible with this
|
||||
// call to sigprocmask, but the repeated calls to CatchMachException here
|
||||
// will eventually lead to termination.
|
||||
sigset_t unblock_set;
|
||||
sigemptyset(&unblock_set);
|
||||
sigaddset(&unblock_set, SIGILL); // EXC_BAD_INSTRUCTION
|
||||
sigaddset(&unblock_set, SIGTRAP); // EXC_BREAKPOINT
|
||||
sigaddset(&unblock_set, SIGFPE); // EXC_ARITHMETIC
|
||||
sigaddset(&unblock_set, SIGBUS); // EXC_BAD_ACCESS
|
||||
sigaddset(&unblock_set, SIGSEGV); // EXC_BAD_ACCESS
|
||||
if (sigprocmask(SIG_UNBLOCK, &unblock_set, nullptr) != 0) {
|
||||
CRASHPAD_RAW_LOG("sigprocmask");
|
||||
}
|
||||
return KERN_FAILURE;
|
||||
}
|
||||
|
||||
|
@ -236,8 +312,7 @@ class CrashHandler : public Thread,
|
|||
thread_state_flavor_t flavor,
|
||||
ConstThreadState old_state,
|
||||
mach_msg_type_number_t old_state_count) {
|
||||
in_process_handler_.DumpExceptionFromMachException(system_data_,
|
||||
behavior,
|
||||
in_process_handler_.DumpExceptionFromMachException(behavior,
|
||||
thread,
|
||||
exception,
|
||||
code,
|
||||
|
@ -249,8 +324,8 @@ class CrashHandler : public Thread,
|
|||
|
||||
void HandleUncaughtNSException(const uint64_t* frames,
|
||||
const size_t num_frames) override {
|
||||
in_process_handler_.DumpExceptionFromNSExceptionFrames(
|
||||
system_data_, frames, num_frames);
|
||||
in_process_handler_.DumpExceptionFromNSExceptionWithFrames(frames,
|
||||
num_frames);
|
||||
// After uncaught exceptions are reported, the system immediately triggers a
|
||||
// call to std::terminate()/abort(). Remove the abort handler so a second
|
||||
// dump isn't generated.
|
||||
|
@ -259,17 +334,9 @@ class CrashHandler : public Thread,
|
|||
|
||||
void HandleUncaughtNSExceptionWithContext(
|
||||
NativeCPUContext* context) override {
|
||||
const mach_exception_data_type_t code[2] = {0, 0};
|
||||
in_process_handler_.DumpExceptionFromMachException(
|
||||
system_data_,
|
||||
MACH_EXCEPTION_CODES,
|
||||
mach_thread_self(),
|
||||
kMachExceptionFromNSException,
|
||||
code,
|
||||
base::size(code),
|
||||
MACHINE_THREAD_STATE,
|
||||
reinterpret_cast<ConstThreadState>(context),
|
||||
MACHINE_THREAD_STATE_COUNT);
|
||||
base::FilePath path;
|
||||
in_process_handler_.DumpExceptionFromSimulatedMachException(
|
||||
context, kMachExceptionFromNSException, &path);
|
||||
|
||||
// After uncaught exceptions are reported, the system immediately triggers a
|
||||
// call to std::terminate()/abort(). Remove the abort handler so a second
|
||||
|
@ -277,29 +344,63 @@ class CrashHandler : public Thread,
|
|||
CHECK(Signals::InstallDefaultHandler(SIGABRT));
|
||||
}
|
||||
|
||||
void HandleUncaughtNSExceptionWithContextAtPath(
|
||||
NativeCPUContext* context,
|
||||
const base::FilePath& path) override {
|
||||
in_process_handler_.DumpExceptionFromSimulatedMachExceptionAtPath(
|
||||
context, kMachExceptionFromNSException, path);
|
||||
}
|
||||
|
||||
bool MoveIntermediateDumpAtPathToPending(
|
||||
const base::FilePath& path) override {
|
||||
if (in_process_handler_.MoveIntermediateDumpAtPathToPending(path)) {
|
||||
// After uncaught exceptions are reported, the system immediately triggers
|
||||
// a call to std::terminate()/abort(). Remove the abort handler so a
|
||||
// second dump isn't generated.
|
||||
CHECK(Signals::InstallDefaultHandler(SIGABRT));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// The signal handler installed at OS-level.
|
||||
static void CatchSignal(int signo, siginfo_t* siginfo, void* context) {
|
||||
static void CatchAndReraiseSignal(int signo,
|
||||
siginfo_t* siginfo,
|
||||
void* context) {
|
||||
Get()->HandleAndReraiseSignal(signo,
|
||||
siginfo,
|
||||
reinterpret_cast<ucontext_t*>(context),
|
||||
&(Get()->old_action_));
|
||||
}
|
||||
|
||||
static void CatchAndReraiseSignalDefaultAction(int signo,
|
||||
siginfo_t* siginfo,
|
||||
void* context) {
|
||||
Get()->HandleAndReraiseSignal(
|
||||
signo, siginfo, reinterpret_cast<ucontext_t*>(context));
|
||||
signo, siginfo, reinterpret_cast<ucontext_t*>(context), nullptr);
|
||||
}
|
||||
|
||||
void HandleAndReraiseSignal(int signo,
|
||||
siginfo_t* siginfo,
|
||||
ucontext_t* context) {
|
||||
in_process_handler_.DumpExceptionFromSignal(system_data_, siginfo, context);
|
||||
ucontext_t* context,
|
||||
struct sigaction* old_action) {
|
||||
in_process_handler_.DumpExceptionFromSignal(siginfo, context);
|
||||
|
||||
// Always call system handler.
|
||||
Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, &old_action_);
|
||||
Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, old_action);
|
||||
}
|
||||
|
||||
base::mac::ScopedMachReceiveRight exception_port_;
|
||||
ExceptionPorts::ExceptionHandlerVector original_handlers_;
|
||||
struct sigaction old_action_ = {};
|
||||
internal::InProcessHandler in_process_handler_;
|
||||
internal::IOSSystemDataCollector system_data_;
|
||||
static CrashHandler* instance_;
|
||||
std::atomic<bool> mach_handler_running_ = false;
|
||||
InitializationStateDcheck initialized_;
|
||||
};
|
||||
|
||||
CrashHandler* CrashHandler::instance_ = nullptr;
|
||||
|
||||
} // namespace
|
||||
|
||||
CrashpadClient::CrashpadClient() {}
|
||||
|
@ -364,4 +465,22 @@ void CrashpadClient::DumpWithoutCrashAndDeferProcessingAtPath(
|
|||
crash_handler->DumpWithoutCrashAtPath(context, path);
|
||||
}
|
||||
|
||||
void CrashpadClient::ResetForTesting() {
|
||||
CrashHandler* crash_handler = CrashHandler::Get();
|
||||
DCHECK(crash_handler);
|
||||
crash_handler->ResetForTesting();
|
||||
}
|
||||
|
||||
void CrashpadClient::SetMachExceptionCallbackForTesting(void (*callback)()) {
|
||||
CrashHandler* crash_handler = CrashHandler::Get();
|
||||
DCHECK(crash_handler);
|
||||
crash_handler->SetMachExceptionCallbackForTesting(callback);
|
||||
}
|
||||
|
||||
uint64_t CrashpadClient::GetThreadIdForTesting() {
|
||||
CrashHandler* crash_handler = CrashHandler::Get();
|
||||
DCHECK(crash_handler);
|
||||
return crash_handler->GetThreadIdForTesting();
|
||||
}
|
||||
|
||||
} // namespace crashpad
|
||||
|
|
|
@ -25,50 +25,67 @@
|
|||
#include "gtest/gtest.h"
|
||||
#include "test/scoped_temp_dir.h"
|
||||
#include "testing/platform_test.h"
|
||||
#include "util/thread/thread.h"
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
namespace {
|
||||
|
||||
using CrashpadIOSClient = PlatformTest;
|
||||
class CrashpadIOSClient : public PlatformTest {
|
||||
protected:
|
||||
// testing::Test:
|
||||
|
||||
void SetUp() override {
|
||||
ASSERT_TRUE(client_.StartCrashpadInProcessHandler(
|
||||
base::FilePath(database_dir.path()), "", {}));
|
||||
database_ = CrashReportDatabase::Initialize(database_dir.path());
|
||||
}
|
||||
|
||||
void TearDown() override { client_.ResetForTesting(); }
|
||||
|
||||
auto& Client() { return client_; }
|
||||
auto& Database() { return database_; }
|
||||
|
||||
private:
|
||||
std::unique_ptr<CrashReportDatabase> database_;
|
||||
CrashpadClient client_;
|
||||
ScopedTempDir database_dir;
|
||||
};
|
||||
|
||||
TEST_F(CrashpadIOSClient, DumpWithoutCrash) {
|
||||
CrashpadClient client;
|
||||
ScopedTempDir database_dir;
|
||||
ASSERT_TRUE(client.StartCrashpadInProcessHandler(
|
||||
base::FilePath(database_dir.path()), "", {}));
|
||||
std::unique_ptr<CrashReportDatabase> database =
|
||||
CrashReportDatabase::Initialize(database_dir.path());
|
||||
std::vector<CrashReportDatabase::Report> reports;
|
||||
EXPECT_EQ(database->GetPendingReports(&reports),
|
||||
EXPECT_EQ(Database()->GetPendingReports(&reports),
|
||||
CrashReportDatabase::kNoError);
|
||||
ASSERT_EQ(reports.size(), 0u);
|
||||
CRASHPAD_SIMULATE_CRASH();
|
||||
reports.clear();
|
||||
EXPECT_EQ(database->GetPendingReports(&reports),
|
||||
|
||||
EXPECT_EQ(Database()->GetPendingReports(&reports),
|
||||
CrashReportDatabase::kNoError);
|
||||
ASSERT_EQ(reports.size(), 1u);
|
||||
}
|
||||
|
||||
TEST_F(CrashpadIOSClient, DumpWithoutCrashAndDefer) {
|
||||
std::vector<CrashReportDatabase::Report> reports;
|
||||
CRASHPAD_SIMULATE_CRASH_AND_DEFER_PROCESSING();
|
||||
reports.clear();
|
||||
EXPECT_EQ(database->GetPendingReports(&reports),
|
||||
EXPECT_EQ(Database()->GetPendingReports(&reports),
|
||||
CrashReportDatabase::kNoError);
|
||||
ASSERT_EQ(reports.size(), 0u);
|
||||
Client().ProcessIntermediateDumps();
|
||||
EXPECT_EQ(Database()->GetPendingReports(&reports),
|
||||
CrashReportDatabase::kNoError);
|
||||
ASSERT_EQ(reports.size(), 1u);
|
||||
client.ProcessIntermediateDumps();
|
||||
reports.clear();
|
||||
EXPECT_EQ(database->GetPendingReports(&reports),
|
||||
CrashReportDatabase::kNoError);
|
||||
ASSERT_EQ(reports.size(), 2u);
|
||||
}
|
||||
|
||||
TEST_F(CrashpadIOSClient, DumpWithoutCrashAndDeferAtPath) {
|
||||
std::vector<CrashReportDatabase::Report> reports;
|
||||
ScopedTempDir crash_dir;
|
||||
UUID uuid;
|
||||
uuid.InitializeWithNew();
|
||||
CRASHPAD_SIMULATE_CRASH_AND_DEFER_PROCESSING_AT_PATH(
|
||||
crash_dir.path().Append(uuid.ToString()));
|
||||
reports.clear();
|
||||
EXPECT_EQ(database->GetPendingReports(&reports),
|
||||
EXPECT_EQ(Database()->GetPendingReports(&reports),
|
||||
CrashReportDatabase::kNoError);
|
||||
ASSERT_EQ(reports.size(), 2u);
|
||||
ASSERT_EQ(reports.size(), 0u);
|
||||
|
||||
NSError* error = nil;
|
||||
NSArray* paths = [[NSFileManager defaultManager]
|
||||
|
@ -76,12 +93,43 @@ TEST_F(CrashpadIOSClient, DumpWithoutCrash) {
|
|||
crash_dir.path().value())
|
||||
error:&error];
|
||||
ASSERT_EQ([paths count], 1u);
|
||||
client.ProcessIntermediateDump(
|
||||
Client().ProcessIntermediateDump(
|
||||
crash_dir.path().Append([paths[0] fileSystemRepresentation]));
|
||||
reports.clear();
|
||||
EXPECT_EQ(database->GetPendingReports(&reports),
|
||||
EXPECT_EQ(Database()->GetPendingReports(&reports),
|
||||
CrashReportDatabase::kNoError);
|
||||
ASSERT_EQ(reports.size(), 3u);
|
||||
ASSERT_EQ(reports.size(), 1u);
|
||||
}
|
||||
|
||||
class RaceThread : public Thread {
|
||||
public:
|
||||
explicit RaceThread() : Thread() {}
|
||||
|
||||
private:
|
||||
void ThreadMain() override {
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
CRASHPAD_SIMULATE_CRASH();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(CrashpadIOSClient, MultipleThreadsSimulateCrash) {
|
||||
RaceThread race_threads[2];
|
||||
for (RaceThread& race_thread : race_threads) {
|
||||
race_thread.Start();
|
||||
}
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
CRASHPAD_SIMULATE_CRASH();
|
||||
}
|
||||
for (RaceThread& race_thread : race_threads) {
|
||||
race_thread.Join();
|
||||
}
|
||||
|
||||
std::vector<CrashReportDatabase::Report> reports;
|
||||
ASSERT_EQ(Database()->GetPendingReports(&reports),
|
||||
CrashReportDatabase::kNoError);
|
||||
EXPECT_EQ(reports.size(), 30u);
|
||||
}
|
||||
|
||||
// This test is covered by a similar XCUITest, but for development purposes it's
|
||||
|
@ -89,10 +137,6 @@ TEST_F(CrashpadIOSClient, DumpWithoutCrash) {
|
|||
// to correctly run this in Google Test. Leave the test here, disabled, for use
|
||||
// during development only.
|
||||
TEST_F(CrashpadIOSClient, DISABLED_ThrowNSException) {
|
||||
CrashpadClient client;
|
||||
ScopedTempDir database_dir;
|
||||
ASSERT_TRUE(client.StartCrashpadInProcessHandler(
|
||||
base::FilePath(database_dir.path()), "", {}));
|
||||
[NSException raise:@"GoogleTestNSException" format:@"ThrowException"];
|
||||
}
|
||||
|
||||
|
@ -101,10 +145,6 @@ TEST_F(CrashpadIOSClient, DISABLED_ThrowNSException) {
|
|||
// to correctly run this in Google Test. Leave the test here, disabled, for use
|
||||
// during development only.
|
||||
TEST_F(CrashpadIOSClient, DISABLED_ThrowException) {
|
||||
CrashpadClient client;
|
||||
ScopedTempDir database_dir;
|
||||
ASSERT_TRUE(client.StartCrashpadInProcessHandler(
|
||||
base::FilePath(database_dir.path()), "", {}));
|
||||
std::vector<int> empty_vector;
|
||||
empty_vector.at(42);
|
||||
}
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <linux/futex.h>
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/mman.h>
|
||||
|
@ -26,8 +28,11 @@
|
|||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "build/build_config.h"
|
||||
#include "build/chromeos_buildflags.h"
|
||||
#include "client/client_argv_handling.h"
|
||||
#include "third_party/lss/lss.h"
|
||||
|
@ -56,7 +61,7 @@ std::string FormatArgumentAddress(const std::string& name, const void* addr) {
|
|||
return base::StringPrintf("--%s=%p", name.c_str(), addr);
|
||||
}
|
||||
|
||||
#if defined(OS_ANDROID)
|
||||
#if BUILDFLAG(IS_ANDROID)
|
||||
|
||||
std::vector<std::string> BuildAppProcessArgs(
|
||||
const std::string& class_name,
|
||||
|
@ -124,7 +129,7 @@ std::vector<std::string> BuildArgsToLaunchWithLinker(
|
|||
return argv;
|
||||
}
|
||||
|
||||
#endif // OS_ANDROID
|
||||
#endif // BUILDFLAG(IS_ANDROID)
|
||||
|
||||
// A base class for Crashpad signal handler implementations.
|
||||
class SignalHandler {
|
||||
|
@ -136,10 +141,14 @@ class SignalHandler {
|
|||
// handler has been installed.
|
||||
static SignalHandler* Get() { return handler_; }
|
||||
|
||||
// Disables any installed Crashpad signal handler for the calling thread. If a
|
||||
// crash signal is received, any previously installed (non-Crashpad) signal
|
||||
// handler will be restored and the signal reraised.
|
||||
static void DisableForThread() { disabled_for_thread_ = true; }
|
||||
// Disables any installed Crashpad signal handler. If a crash signal is
|
||||
// received, any previously installed (non-Crashpad) signal handler will be
|
||||
// restored and the signal reraised.
|
||||
static void Disable() {
|
||||
if (!handler_->disabled_.test_and_set()) {
|
||||
handler_->WakeThreads();
|
||||
}
|
||||
}
|
||||
|
||||
void SetFirstChanceHandler(CrashpadClient::FirstChanceHandlerLinux handler) {
|
||||
first_chance_handler_ = handler;
|
||||
|
@ -147,17 +156,7 @@ class SignalHandler {
|
|||
|
||||
// The base implementation for all signal handlers, suitable for calling
|
||||
// directly to simulate signal delivery.
|
||||
bool HandleCrash(int signo, siginfo_t* siginfo, void* context) {
|
||||
if (disabled_for_thread_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (first_chance_handler_ &&
|
||||
first_chance_handler_(
|
||||
signo, siginfo, static_cast<ucontext_t*>(context))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void HandleCrash(int signo, siginfo_t* siginfo, void* context) {
|
||||
exception_information_.siginfo_address =
|
||||
FromPointerCast<decltype(exception_information_.siginfo_address)>(
|
||||
siginfo);
|
||||
|
@ -168,7 +167,6 @@ class SignalHandler {
|
|||
|
||||
ScopedPrSetDumpable set_dumpable(false);
|
||||
HandleCrashImpl();
|
||||
return false;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
@ -193,27 +191,78 @@ class SignalHandler {
|
|||
virtual void HandleCrashImpl() = 0;
|
||||
|
||||
private:
|
||||
static constexpr int32_t kDumpNotDone = 0;
|
||||
static constexpr int32_t kDumpDone = 1;
|
||||
|
||||
// The signal handler installed at OS-level.
|
||||
static void HandleOrReraiseSignal(int signo,
|
||||
siginfo_t* siginfo,
|
||||
void* context) {
|
||||
if (handler_->HandleCrash(signo, siginfo, context)) {
|
||||
if (handler_->first_chance_handler_ &&
|
||||
handler_->first_chance_handler_(
|
||||
signo, siginfo, static_cast<ucontext_t*>(context))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only handle the first fatal signal observed. If another thread receives a
|
||||
// crash signal, it waits for the first dump to complete instead of
|
||||
// requesting another.
|
||||
if (!handler_->disabled_.test_and_set()) {
|
||||
handler_->HandleCrash(signo, siginfo, context);
|
||||
handler_->WakeThreads();
|
||||
} else {
|
||||
// Processes on Android normally have several chained signal handlers that
|
||||
// co-operate to report crashes. e.g. WebView will have this signal
|
||||
// handler installed, the app embedding WebView may have a signal handler
|
||||
// installed, and Bionic will have a signal handler. Each signal handler
|
||||
// runs in succession, possibly managed by libsigchain. This wait is
|
||||
// intended to avoid ill-effects from multiple signal handlers from
|
||||
// different layers (possibly all trying to use ptrace()) from running
|
||||
// simultaneously. It does not block forever so that in most conditions,
|
||||
// those signal handlers will still have a chance to run and ensures
|
||||
// process termination in case the first crashing thread crashes again in
|
||||
// its signal handler. Though less typical, this situation also occurs on
|
||||
// other Linuxes, e.g. to produce in-process stack traces for debug
|
||||
// builds.
|
||||
handler_->WaitForDumpDone();
|
||||
}
|
||||
|
||||
Signals::RestoreHandlerAndReraiseSignalOnReturn(
|
||||
siginfo, handler_->old_actions_.ActionForSignal(signo));
|
||||
}
|
||||
|
||||
void WaitForDumpDone() {
|
||||
kernel_timespec timeout;
|
||||
timeout.tv_sec = 5;
|
||||
timeout.tv_nsec = 0;
|
||||
sys_futex(&dump_done_futex_,
|
||||
FUTEX_WAIT_PRIVATE,
|
||||
kDumpNotDone,
|
||||
&timeout,
|
||||
nullptr,
|
||||
0);
|
||||
}
|
||||
|
||||
void WakeThreads() {
|
||||
dump_done_futex_ = kDumpDone;
|
||||
sys_futex(
|
||||
&dump_done_futex_, FUTEX_WAKE_PRIVATE, INT_MAX, nullptr, nullptr, 0);
|
||||
}
|
||||
|
||||
Signals::OldActions old_actions_ = {};
|
||||
ExceptionInformation exception_information_ = {};
|
||||
CrashpadClient::FirstChanceHandlerLinux first_chance_handler_ = nullptr;
|
||||
int32_t dump_done_futex_ = kDumpNotDone;
|
||||
#if !defined(__cpp_lib_atomic_value_initialization) || \
|
||||
__cpp_lib_atomic_value_initialization < 201911L
|
||||
std::atomic_flag disabled_ = ATOMIC_FLAG_INIT;
|
||||
#else
|
||||
std::atomic_flag disabled_;
|
||||
#endif
|
||||
|
||||
static SignalHandler* handler_;
|
||||
|
||||
static thread_local bool disabled_for_thread_;
|
||||
};
|
||||
SignalHandler* SignalHandler::handler_ = nullptr;
|
||||
thread_local bool SignalHandler::disabled_for_thread_ = false;
|
||||
|
||||
// Launches a single use handler to snapshot this process.
|
||||
class LaunchAtCrashHandler : public SignalHandler {
|
||||
|
@ -307,17 +356,11 @@ class RequestCrashDumpHandler : public SignalHandler {
|
|||
}
|
||||
pid = creds.pid;
|
||||
}
|
||||
if (pid > 0 && prctl(PR_SET_PTRACER, pid, 0, 0, 0) != 0) {
|
||||
PLOG(WARNING) << "prctl";
|
||||
// TODO(jperaza): If this call to set the ptracer failed, it might be
|
||||
// possible to try again just before a dump request, in case the
|
||||
// environment has changed. Revisit ExceptionHandlerClient::SetPtracer()
|
||||
// and consider saving the result of this call in ExceptionHandlerClient
|
||||
// or as a member in this signal handler. ExceptionHandlerClient hasn't
|
||||
// been responsible for maintaining state and a new ExceptionHandlerClient
|
||||
// has been constructed as a local whenever a client needs to communicate
|
||||
// with the handler. ExceptionHandlerClient lifetimes and ownership will
|
||||
// need to be reconsidered if it becomes responsible for state.
|
||||
if (pid > 0) {
|
||||
pthread_atfork(nullptr, nullptr, SetPtracerAtFork);
|
||||
if (prctl(PR_SET_PTRACER, pid, 0, 0, 0) != 0) {
|
||||
PLOG(WARNING) << "prctl";
|
||||
}
|
||||
}
|
||||
sock_to_handler_.reset(sock.release());
|
||||
handler_pid_ = pid;
|
||||
|
@ -338,6 +381,13 @@ class RequestCrashDumpHandler : public SignalHandler {
|
|||
}
|
||||
|
||||
void HandleCrashImpl() override {
|
||||
// Attempt to set the ptracer again, in case a crash occurs after a fork,
|
||||
// before SetPtracerAtFork() has been called. Ignore errors because the
|
||||
// system call may be disallowed if the sandbox is engaged.
|
||||
if (handler_pid_ > 0) {
|
||||
sys_prctl(PR_SET_PTRACER, handler_pid_, 0, 0, 0);
|
||||
}
|
||||
|
||||
ExceptionHandlerProtocol::ClientInformation info = {};
|
||||
info.exception_information_address =
|
||||
FromPointerCast<VMAddress>(&GetExceptionInfo());
|
||||
|
@ -360,6 +410,14 @@ class RequestCrashDumpHandler : public SignalHandler {
|
|||
|
||||
~RequestCrashDumpHandler() = delete;
|
||||
|
||||
static void SetPtracerAtFork() {
|
||||
auto handler = RequestCrashDumpHandler::Get();
|
||||
if (handler->handler_pid_ > 0 &&
|
||||
prctl(PR_SET_PTRACER, handler->handler_pid_, 0, 0, 0) != 0) {
|
||||
PLOG(WARNING) << "prctl";
|
||||
}
|
||||
}
|
||||
|
||||
ScopedFileHandle sock_to_handler_;
|
||||
pid_t handler_pid_ = -1;
|
||||
|
||||
|
@ -415,7 +473,7 @@ bool CrashpadClient::StartHandler(
|
|||
std::move(client_sock), handler_pid, &unhandled_signals_);
|
||||
}
|
||||
|
||||
#if defined(OS_ANDROID) || defined(OS_LINUX) || defined(OS_CHROMEOS)
|
||||
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
|
||||
// static
|
||||
bool CrashpadClient::GetHandlerSocket(int* sock, pid_t* pid) {
|
||||
auto signal_handler = RequestCrashDumpHandler::Get();
|
||||
|
@ -519,9 +577,10 @@ bool CrashpadClient::InitializeSignalStackForThread() {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
#endif // OS_ANDROID || OS_LINUX || OS_CHROMEOS
|
||||
#endif // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX) ||
|
||||
// BUILDFLAG(IS_CHROMEOS)
|
||||
|
||||
#if defined(OS_ANDROID)
|
||||
#if BUILDFLAG(IS_ANDROID)
|
||||
|
||||
bool CrashpadClient::StartJavaHandlerAtCrash(
|
||||
const std::string& class_name,
|
||||
|
@ -666,7 +725,7 @@ void CrashpadClient::DumpWithoutCrash(NativeCPUContext* context) {
|
|||
|
||||
// static
|
||||
void CrashpadClient::CrashWithoutDump(const std::string& message) {
|
||||
SignalHandler::DisableForThread();
|
||||
SignalHandler::Disable();
|
||||
LOG(FATAL) << message;
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "client/crashpad_client.h"
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <setjmp.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/syscall.h>
|
||||
|
@ -23,9 +24,11 @@
|
|||
|
||||
#include "base/check_op.h"
|
||||
#include "base/notreached.h"
|
||||
#include "build/build_config.h"
|
||||
#include "client/annotation.h"
|
||||
#include "client/annotation_list.h"
|
||||
#include "client/crash_report_database.h"
|
||||
#include "client/crashpad_info.h"
|
||||
#include "client/simulate_crash.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "snapshot/annotation_snapshot.h"
|
||||
|
@ -49,7 +52,7 @@
|
|||
#include "util/posix/signals.h"
|
||||
#include "util/thread/thread.h"
|
||||
|
||||
#if defined(OS_ANDROID)
|
||||
#if BUILDFLAG(IS_ANDROID)
|
||||
#include <android/set_abort_message.h>
|
||||
#include "dlfcn_internal.h"
|
||||
|
||||
|
@ -73,12 +76,13 @@ struct StartHandlerForSelfTestOptions {
|
|||
bool set_first_chance_handler;
|
||||
bool crash_non_main_thread;
|
||||
bool client_uses_signals;
|
||||
bool gather_indirectly_referenced_memory;
|
||||
CrashType crash_type;
|
||||
};
|
||||
|
||||
class StartHandlerForSelfTest
|
||||
: public testing::TestWithParam<
|
||||
std::tuple<bool, bool, bool, bool, CrashType>> {
|
||||
std::tuple<bool, bool, bool, bool, bool, CrashType>> {
|
||||
public:
|
||||
StartHandlerForSelfTest() = default;
|
||||
|
||||
|
@ -88,10 +92,14 @@ class StartHandlerForSelfTest
|
|||
~StartHandlerForSelfTest() = default;
|
||||
|
||||
void SetUp() override {
|
||||
// MSAN requires that padding bytes have been initialized for structs that
|
||||
// are written to files.
|
||||
memset(&options_, 0, sizeof(options_));
|
||||
std::tie(options_.start_handler_at_crash,
|
||||
options_.set_first_chance_handler,
|
||||
options_.crash_non_main_thread,
|
||||
options_.client_uses_signals,
|
||||
options_.gather_indirectly_referenced_memory,
|
||||
options_.crash_type) = GetParam();
|
||||
}
|
||||
|
||||
|
@ -101,10 +109,6 @@ class StartHandlerForSelfTest
|
|||
StartHandlerForSelfTestOptions options_;
|
||||
};
|
||||
|
||||
bool HandleCrashSuccessfully(int, siginfo_t*, ucontext_t*) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InstallHandler(CrashpadClient* client,
|
||||
bool start_at_crash,
|
||||
const base::FilePath& handler_path,
|
||||
|
@ -134,7 +138,7 @@ constexpr char kTestAnnotationValue[] = "value_of_annotation";
|
|||
constexpr char kTestAttachmentName[] = "test_attachment";
|
||||
constexpr char kTestAttachmentContent[] = "attachment_content";
|
||||
|
||||
#if defined(OS_ANDROID)
|
||||
#if BUILDFLAG(IS_ANDROID)
|
||||
constexpr char kTestAbortMessage[] = "test abort message";
|
||||
#endif
|
||||
|
||||
|
@ -147,7 +151,8 @@ void ValidateAttachment(const CrashReportDatabase::UploadReport* report) {
|
|||
0);
|
||||
}
|
||||
|
||||
void ValidateExtraMemory(const ProcessSnapshotMinidump& minidump) {
|
||||
void ValidateExtraMemory(const StartHandlerForSelfTestOptions& options,
|
||||
const ProcessSnapshotMinidump& minidump) {
|
||||
// Verify that if we have an exception, then the code around the instruction
|
||||
// pointer is included in the extra memory.
|
||||
const ExceptionSnapshot* exception = minidump.Exception();
|
||||
|
@ -164,14 +169,15 @@ void ValidateExtraMemory(const ProcessSnapshotMinidump& minidump) {
|
|||
break;
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(pc_found);
|
||||
EXPECT_EQ(pc_found, options.gather_indirectly_referenced_memory);
|
||||
}
|
||||
|
||||
void ValidateDump(const CrashReportDatabase::UploadReport* report) {
|
||||
void ValidateDump(const StartHandlerForSelfTestOptions& options,
|
||||
const CrashReportDatabase::UploadReport* report) {
|
||||
ProcessSnapshotMinidump minidump_snapshot;
|
||||
ASSERT_TRUE(minidump_snapshot.Initialize(report->Reader()));
|
||||
|
||||
#if defined(OS_ANDROID)
|
||||
#if BUILDFLAG(IS_ANDROID)
|
||||
// This part of the test requires Q. The API level on Q devices will be 28
|
||||
// until the API is finalized, so we can't check API level yet. For now, test
|
||||
// for the presence of a libc symbol which was introduced in Q.
|
||||
|
@ -184,7 +190,7 @@ void ValidateDump(const CrashReportDatabase::UploadReport* report) {
|
|||
#endif
|
||||
ValidateAttachment(report);
|
||||
|
||||
ValidateExtraMemory(minidump_snapshot);
|
||||
ValidateExtraMemory(options, minidump_snapshot);
|
||||
|
||||
for (const ModuleSnapshot* module : minidump_snapshot.Modules()) {
|
||||
for (const AnnotationSnapshot& annotation : module->AnnotationObjects()) {
|
||||
|
@ -213,13 +219,24 @@ int RecurseInfinitely(int* ptr) {
|
|||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
sigjmp_buf do_crash_sigjmp_env;
|
||||
|
||||
bool HandleCrashSuccessfully(int, siginfo_t*, ucontext_t*) {
|
||||
siglongjmp(do_crash_sigjmp_env, 1);
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wunreachable-code-return"
|
||||
return true;
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
|
||||
void DoCrash(const StartHandlerForSelfTestOptions& options,
|
||||
CrashpadClient* client) {
|
||||
if (sigsetjmp(do_crash_sigjmp_env, 1) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (options.crash_type) {
|
||||
case CrashType::kSimulated:
|
||||
if (options.set_first_chance_handler) {
|
||||
client->SetFirstChanceExceptionHandler(HandleCrashSuccessfully);
|
||||
}
|
||||
CRASHPAD_SIMULATE_CRASH();
|
||||
break;
|
||||
|
||||
|
@ -330,6 +347,11 @@ CRASHPAD_CHILD_TEST_MAIN(StartHandlerForSelfTestChild) {
|
|||
client_handler, SA_ONSTACK, &old_actions));
|
||||
}
|
||||
|
||||
if (options.gather_indirectly_referenced_memory) {
|
||||
CrashpadInfo::GetCrashpadInfo()->set_gather_indirectly_referenced_memory(
|
||||
TriState::kEnabled, 1024 * 1024 * 4);
|
||||
}
|
||||
|
||||
base::FilePath handler_path = TestPaths::Executable().DirName().Append(
|
||||
FILE_PATH_LITERAL("crashpad_handler"));
|
||||
|
||||
|
@ -350,7 +372,11 @@ CRASHPAD_CHILD_TEST_MAIN(StartHandlerForSelfTestChild) {
|
|||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
#if defined(OS_ANDROID)
|
||||
if (options.set_first_chance_handler) {
|
||||
client.SetFirstChanceExceptionHandler(HandleCrashSuccessfully);
|
||||
}
|
||||
|
||||
#if BUILDFLAG(IS_ANDROID)
|
||||
if (android_set_abort_message) {
|
||||
android_set_abort_message(kTestAbortMessage);
|
||||
}
|
||||
|
@ -372,17 +398,19 @@ class StartHandlerForSelfInChildTest : public MultiprocessExec {
|
|||
StartHandlerForSelfInChildTest(const StartHandlerForSelfTestOptions& options)
|
||||
: MultiprocessExec(), options_(options) {
|
||||
SetChildTestMainFunction("StartHandlerForSelfTestChild");
|
||||
switch (options.crash_type) {
|
||||
case CrashType::kSimulated:
|
||||
// kTerminationNormal, EXIT_SUCCESS
|
||||
break;
|
||||
case CrashType::kBuiltinTrap:
|
||||
SetExpectedChildTerminationBuiltinTrap();
|
||||
break;
|
||||
case CrashType::kInfiniteRecursion:
|
||||
SetExpectedChildTermination(TerminationReason::kTerminationSignal,
|
||||
SIGSEGV);
|
||||
break;
|
||||
if (!options.set_first_chance_handler) {
|
||||
switch (options.crash_type) {
|
||||
case CrashType::kSimulated:
|
||||
// kTerminationNormal, EXIT_SUCCESS
|
||||
break;
|
||||
case CrashType::kBuiltinTrap:
|
||||
SetExpectedChildTerminationBuiltinTrap();
|
||||
break;
|
||||
case CrashType::kInfiniteRecursion:
|
||||
SetExpectedChildTermination(TerminationReason::kTerminationSignal,
|
||||
SIGSEGV);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -433,27 +461,25 @@ class StartHandlerForSelfInChildTest : public MultiprocessExec {
|
|||
reports.clear();
|
||||
ASSERT_EQ(database->GetPendingReports(&reports),
|
||||
CrashReportDatabase::kNoError);
|
||||
ASSERT_EQ(reports.size(), options_.set_first_chance_handler ? 0u : 1u);
|
||||
|
||||
if (options_.set_first_chance_handler) {
|
||||
bool report_expected = !options_.set_first_chance_handler ||
|
||||
options_.crash_type == CrashType::kSimulated;
|
||||
ASSERT_EQ(reports.size(), report_expected ? 1u : 0u);
|
||||
|
||||
if (!report_expected) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_ptr<const CrashReportDatabase::UploadReport> report;
|
||||
ASSERT_EQ(database->GetReportForUploading(reports[0].uuid, &report),
|
||||
CrashReportDatabase::kNoError);
|
||||
ValidateDump(report.get());
|
||||
ValidateDump(options_, report.get());
|
||||
}
|
||||
|
||||
StartHandlerForSelfTestOptions options_;
|
||||
};
|
||||
|
||||
TEST_P(StartHandlerForSelfTest, StartHandlerInChild) {
|
||||
if (Options().set_first_chance_handler &&
|
||||
Options().crash_type != CrashType::kSimulated) {
|
||||
// TODO(jperaza): test first chance handlers with real crashes.
|
||||
return;
|
||||
}
|
||||
#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \
|
||||
defined(UNDEFINED_SANITIZER)
|
||||
if (Options().crash_type == CrashType::kInfiniteRecursion) {
|
||||
|
@ -471,6 +497,7 @@ INSTANTIATE_TEST_SUITE_P(
|
|||
testing::Bool(),
|
||||
testing::Bool(),
|
||||
testing::Bool(),
|
||||
testing::Bool(),
|
||||
testing::Values(CrashType::kSimulated,
|
||||
CrashType::kBuiltinTrap,
|
||||
CrashType::kInfiniteRecursion)));
|
||||
|
|
|
@ -21,9 +21,9 @@
|
|||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
#include "base/ignore_result.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/mac/mach_logging.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
|
@ -180,7 +180,7 @@ class HandlerStarter final : public NotifyServer::DefaultInterface {
|
|||
handler_restarter->StartRestartThread(
|
||||
handler, database, metrics_dir, url, annotations, arguments, attachments)) {
|
||||
// The thread owns the object now.
|
||||
ignore_result(handler_restarter.release());
|
||||
std::ignore = handler_restarter.release();
|
||||
}
|
||||
|
||||
// If StartRestartThread() failed, proceed without the ability to restart.
|
||||
|
@ -372,7 +372,7 @@ class HandlerStarter final : public NotifyServer::DefaultInterface {
|
|||
return false;
|
||||
}
|
||||
|
||||
ignore_result(receive_right.release());
|
||||
std::ignore = receive_right.release();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
#include "util/win/command_line.h"
|
||||
#include "util/win/context_wrappers.h"
|
||||
#include "util/win/critical_section_with_debug_info.h"
|
||||
#include "util/win/exception_codes.h"
|
||||
#include "util/win/get_function.h"
|
||||
#include "util/win/handle.h"
|
||||
#include "util/win/initial_client_data.h"
|
||||
|
@ -69,7 +70,18 @@ HANDLE g_non_crash_dump_done = INVALID_HANDLE_VALUE;
|
|||
CrashpadClient::FirstChanceHandlerWin first_chance_handler_ = nullptr;
|
||||
|
||||
// Guards multiple simultaneous calls to DumpWithoutCrash(). This is leaked.
|
||||
base::Lock* g_non_crash_dump_lock;
|
||||
base::Lock* g_non_crash_dump_lock = nullptr;
|
||||
|
||||
class CrashDumpAutoReleaser {
|
||||
public:
|
||||
~CrashDumpAutoReleaser() {
|
||||
if (g_non_crash_dump_lock) {
|
||||
delete g_non_crash_dump_lock;
|
||||
g_non_crash_dump_lock = nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
static CrashDumpAutoReleaser gAutoReleaser;
|
||||
|
||||
// Where we store a pointer to the context information when taking a non-crash
|
||||
// dump.
|
||||
|
@ -394,8 +406,7 @@ bool StartHandlerProcess(
|
|||
}
|
||||
for (const base::FilePath& attachment : data->attachments) {
|
||||
AppendCommandLineArgument(
|
||||
FormatArgumentString("attachment", attachment.value()),
|
||||
&command_line);
|
||||
FormatArgumentString("attachment", attachment.value()), &command_line);
|
||||
}
|
||||
|
||||
ScopedKernelHANDLE this_process(
|
||||
|
@ -932,7 +943,7 @@ bool CrashpadClient::DumpAndCrashTargetProcess(HANDLE process,
|
|||
|
||||
// ecx = kTriggeredExceptionCode for dwExceptionCode.
|
||||
data_to_write.push_back(0xb9);
|
||||
AddUint32(&data_to_write, kTriggeredExceptionCode);
|
||||
AddUint32(&data_to_write, ExceptionCodes::kTriggeredExceptionCode);
|
||||
|
||||
// jmp to RaiseException() via rax.
|
||||
data_to_write.push_back(0x48); // mov rax, imm.
|
||||
|
|
|
@ -16,10 +16,11 @@
|
|||
|
||||
#include <type_traits>
|
||||
|
||||
#include "build/build_config.h"
|
||||
#include "util/misc/address_sanitizer.h"
|
||||
#include "util/misc/from_pointer_cast.h"
|
||||
|
||||
#if defined(OS_APPLE)
|
||||
#if BUILDFLAG(IS_APPLE)
|
||||
#include <mach-o/loader.h>
|
||||
#endif
|
||||
|
||||
|
@ -52,10 +53,10 @@ static_assert(std::is_standard_layout<CrashpadInfo>::value,
|
|||
// because it’s POD, no code should need to run to initialize this under
|
||||
// release-mode optimization.
|
||||
|
||||
#if defined(OS_POSIX)
|
||||
#if BUILDFLAG(IS_POSIX)
|
||||
__attribute__((
|
||||
|
||||
#if defined(OS_APPLE)
|
||||
#if BUILDFLAG(IS_APPLE)
|
||||
// Put the structure in a well-known section name where it can be easily
|
||||
// found without having to consult the symbol table.
|
||||
section(SEG_DATA ",crashpad_info"),
|
||||
|
@ -77,16 +78,16 @@ __attribute__((
|
|||
// The “used” attribute prevents the structure from being dead-stripped.
|
||||
used))
|
||||
|
||||
#elif defined(OS_WIN)
|
||||
#elif BUILDFLAG(IS_WIN)
|
||||
|
||||
// Put the struct in a section name CPADinfo where it can be found without the
|
||||
// symbol table.
|
||||
#pragma section("CPADinfo", read, write)
|
||||
__declspec(allocate("CPADinfo"))
|
||||
|
||||
#else // !defined(OS_POSIX) && !defined(OS_WIN)
|
||||
#else // !BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_WIN)
|
||||
#error Port
|
||||
#endif // !defined(OS_POSIX) && !defined(OS_WIN)
|
||||
#endif // !BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_WIN)
|
||||
|
||||
CrashpadInfo g_crashpad_info;
|
||||
|
||||
|
@ -94,8 +95,8 @@ extern "C" int* CRASHPAD_NOTE_REFERENCE;
|
|||
|
||||
// static
|
||||
CrashpadInfo* CrashpadInfo::GetCrashpadInfo() {
|
||||
#if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID) || \
|
||||
defined(OS_FUCHSIA)
|
||||
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID) || \
|
||||
BUILDFLAG(IS_FUCHSIA)
|
||||
// This otherwise-unused reference is used so that any module that
|
||||
// references GetCrashpadInfo() will also include the note in the
|
||||
// .note.crashpad.info section. That note in turn contains the address of
|
||||
|
|
|
@ -23,15 +23,15 @@
|
|||
#include "client/simple_string_dictionary.h"
|
||||
#include "util/misc/tri_state.h"
|
||||
|
||||
#if defined(OS_WIN)
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
#include <windows.h>
|
||||
#endif // OS_WIN
|
||||
#endif // BUILDFLAG(IS_WIN)
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
namespace internal {
|
||||
|
||||
#if defined(OS_IOS)
|
||||
#if BUILDFLAG(IS_IOS)
|
||||
class InProcessIntermediateDumpHandler;
|
||||
#endif
|
||||
|
||||
|
@ -230,7 +230,7 @@ struct CrashpadInfo {
|
|||
};
|
||||
|
||||
protected:
|
||||
#if defined(OS_IOS)
|
||||
#if BUILDFLAG(IS_IOS)
|
||||
friend class internal::InProcessIntermediateDumpHandler;
|
||||
#endif
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
// that symbol to be in the dynamic symbol table.
|
||||
|
||||
#include "util/misc/elf_note_types.h"
|
||||
#include "util/misc/arm64_bti_note.S"
|
||||
#include "util/misc/arm64_pac_bti.S"
|
||||
|
||||
// namespace crashpad {
|
||||
// CrashpadInfo g_crashpad_info;
|
||||
|
|
|
@ -15,8 +15,7 @@
|
|||
#ifndef CRASHPAD_UTIL_IOS_EXCEPTION_PROCESSOR_H_
|
||||
#define CRASHPAD_UTIL_IOS_EXCEPTION_PROCESSOR_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "base/files/file_path.h"
|
||||
#include "util/misc/capture_context.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
@ -40,6 +39,28 @@ class ObjcExceptionDelegate {
|
|||
virtual void HandleUncaughtNSException(const uint64_t* frames,
|
||||
const size_t num_frames) = 0;
|
||||
|
||||
//! \brief Generate an intermediate dump from an NSException caught with its
|
||||
//! associated CPU context. Because the method for intercepting
|
||||
//! exceptions is imperfect, write the the intermediate dump to a
|
||||
//! temporary location specified by \a path. If the NSException matches
|
||||
//! the one used in the UncaughtExceptionHandler, call
|
||||
//! MoveIntermediateDumpAtPathToPending to move to the proper Crashpad
|
||||
//! database pending location.
|
||||
//!
|
||||
//! \param[in] context The cpu context of the thread throwing an exception.
|
||||
//! \param[in] path Path to write the intermediate dump.
|
||||
virtual void HandleUncaughtNSExceptionWithContextAtPath(
|
||||
NativeCPUContext* context,
|
||||
const base::FilePath& path) = 0;
|
||||
|
||||
//! \brief Moves an intermediate dump to the pending directory. This is meant
|
||||
//! to be used by the UncaughtExceptionHandler, when the NSException
|
||||
//! caught by the preprocessor matches the UncaughtExceptionHandler.
|
||||
//!
|
||||
//! \param[in] path Path to the specific intermediate dump.
|
||||
virtual bool MoveIntermediateDumpAtPathToPending(
|
||||
const base::FilePath& path) = 0;
|
||||
|
||||
protected:
|
||||
~ObjcExceptionDelegate() {}
|
||||
};
|
||||
|
@ -59,6 +80,10 @@ class ObjcExceptionDelegate {
|
|||
//! signal handler. It should only be installed once.
|
||||
void InstallObjcExceptionPreprocessor(ObjcExceptionDelegate* delegate);
|
||||
|
||||
//! \brief Uninstalls the Objective-C exception preprocessor. Expected to be
|
||||
//! used by tests only.
|
||||
void UninstallObjcExceptionPreprocessor();
|
||||
|
||||
} // namespace crashpad
|
||||
|
||||
#endif // CRASHPAD_UTIL_IOS_EXCEPTION_PROCESSOR_H_
|
||||
|
|
|
@ -53,6 +53,8 @@
|
|||
#include "client/annotation.h"
|
||||
#include "client/simulate_crash_ios.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
namespace {
|
||||
|
||||
// From 10.15.0 objc4-779.1/runtime/objc-exception.mm.
|
||||
|
@ -132,16 +134,12 @@ std::string GetTraceString() {
|
|||
return FormatStackTrace(addresses, 1024);
|
||||
}
|
||||
|
||||
crashpad::ObjcExceptionDelegate* g_exception_delegate;
|
||||
objc_exception_preprocessor g_next_preprocessor;
|
||||
NSUncaughtExceptionHandler* g_next_uncaught_exception_handler;
|
||||
|
||||
static void SetNSExceptionAnnotations(NSException* exception,
|
||||
std::string& name,
|
||||
std::string& reason) {
|
||||
@try {
|
||||
name = base::SysNSStringToUTF8(exception.name);
|
||||
static crashpad::StringAnnotation<256> nameKey("exceptionName");
|
||||
static StringAnnotation<256> nameKey("exceptionName");
|
||||
nameKey.Set(name);
|
||||
} @catch (id name_exception) {
|
||||
LOG(ERROR) << "Unable to read uncaught Objective-C exception name.";
|
||||
|
@ -149,7 +147,7 @@ static void SetNSExceptionAnnotations(NSException* exception,
|
|||
|
||||
@try {
|
||||
reason = base::SysNSStringToUTF8(exception.reason);
|
||||
static crashpad::StringAnnotation<512> reasonKey("exceptionReason");
|
||||
static StringAnnotation<1024> reasonKey("exceptionReason");
|
||||
reasonKey.Set(reason);
|
||||
} @catch (id reason_exception) {
|
||||
LOG(ERROR) << "Unable to read uncaught Objective-C exception reason.";
|
||||
|
@ -157,7 +155,7 @@ static void SetNSExceptionAnnotations(NSException* exception,
|
|||
|
||||
@try {
|
||||
if (exception.userInfo) {
|
||||
static crashpad::StringAnnotation<512> userInfoKey("exceptionUserInfo");
|
||||
static StringAnnotation<1024> userInfoKey("exceptionUserInfo");
|
||||
userInfoKey.Set(base::SysNSStringToUTF8(
|
||||
[NSString stringWithFormat:@"%@", exception.userInfo]));
|
||||
}
|
||||
|
@ -166,26 +164,125 @@ static void SetNSExceptionAnnotations(NSException* exception,
|
|||
}
|
||||
}
|
||||
|
||||
static void ObjcUncaughtExceptionHandler(NSException* exception) {
|
||||
std::string name, reason;
|
||||
SetNSExceptionAnnotations(exception, name, reason);
|
||||
NSArray<NSNumber*>* addressArray = [exception callStackReturnAddresses];
|
||||
if ([addressArray count] > 0) {
|
||||
static crashpad::StringAnnotation<256> nameKey("UncaughtNSException");
|
||||
nameKey.Set("true");
|
||||
std::vector<uint64_t> addresses;
|
||||
for (NSNumber* address in addressArray)
|
||||
addresses.push_back([address unsignedLongLongValue]);
|
||||
g_exception_delegate->HandleUncaughtNSException(&addresses[0],
|
||||
addresses.size());
|
||||
} else {
|
||||
LOG(WARNING) << "Uncaught Objective-C exception name: " << name
|
||||
<< " reason: " << reason << " with no "
|
||||
<< " -callStackReturnAddresses.";
|
||||
crashpad::NativeCPUContext cpu_context;
|
||||
crashpad::CaptureContext(&cpu_context);
|
||||
g_exception_delegate->HandleUncaughtNSExceptionWithContext(&cpu_context);
|
||||
//! \brief Helper class to own the complex types used by the Objective-C
|
||||
//! exception preprocessor.
|
||||
class ExceptionPreprocessorState {
|
||||
public:
|
||||
ExceptionPreprocessorState(const ExceptionPreprocessorState&) = delete;
|
||||
ExceptionPreprocessorState& operator=(const ExceptionPreprocessorState&) =
|
||||
delete;
|
||||
|
||||
static ExceptionPreprocessorState* Get() {
|
||||
static ExceptionPreprocessorState* instance = []() {
|
||||
return new ExceptionPreprocessorState();
|
||||
}();
|
||||
return instance;
|
||||
}
|
||||
|
||||
// Writes an intermediate dumps to a temporary location to be used by the
|
||||
// final UncaughtExceptionHandler and notifies the preprocessor chain.
|
||||
id HandleUncaughtException(NativeCPUContext* cpu_context, id exception) {
|
||||
// If this isn't the first time the preprocessor has detected an uncaught
|
||||
// NSException, note this in the second intermediate dump.
|
||||
objc_exception_preprocessor next_preprocessor = next_preprocessor_;
|
||||
static bool handled_first_exception;
|
||||
if (handled_first_exception) {
|
||||
static StringAnnotation<5> name_key("MultipleHandledUncaughtNSException");
|
||||
name_key.Set("true");
|
||||
|
||||
// Unregister so we stop getting in the way of the exception processor if
|
||||
// we aren't correctly identifying sinkholes. The final uncaught exception
|
||||
// handler is still active.
|
||||
objc_setExceptionPreprocessor(next_preprocessor_);
|
||||
next_preprocessor_ = nullptr;
|
||||
}
|
||||
handled_first_exception = true;
|
||||
|
||||
// Use tmp/ for this intermediate dump path. Normally these dumps are
|
||||
// written to the "pending-serialized-ios-dump" folder and are eligable for
|
||||
// the next pass to convert pending intermediate dumps to minidump files.
|
||||
// Since this intermediate dump isn't eligable until the uncaught handler,
|
||||
// use tmp/.
|
||||
base::FilePath path(base::SysNSStringToUTF8([NSTemporaryDirectory()
|
||||
stringByAppendingPathComponent:[[NSUUID UUID] UUIDString]]));
|
||||
exception_delegate_->HandleUncaughtNSExceptionWithContextAtPath(cpu_context,
|
||||
path);
|
||||
last_handled_intermediate_dump_ = path;
|
||||
|
||||
return next_preprocessor ? next_preprocessor(exception) : exception;
|
||||
}
|
||||
|
||||
// If the PreprocessException already captured this exception via
|
||||
// HANDLE_UNCAUGHT_NSEXCEPTION. Move last_handled_intermediate_dump_ to
|
||||
// the pending intermediate dump directory and return true. Otherwise the
|
||||
// preprocessor didn't catch anything, so pass the frames or just the context
|
||||
// to the exception_delegate.
|
||||
void FinalizeUncaughtNSException(id exception) {
|
||||
if ([last_exception_ isEqual:exception] &&
|
||||
!last_handled_intermediate_dump_.empty() &&
|
||||
exception_delegate_->MoveIntermediateDumpAtPathToPending(
|
||||
last_handled_intermediate_dump_)) {
|
||||
last_handled_intermediate_dump_ = base::FilePath();
|
||||
return;
|
||||
}
|
||||
|
||||
std::string name, reason;
|
||||
SetNSExceptionAnnotations(exception, name, reason);
|
||||
NSArray<NSNumber*>* address_array = [exception callStackReturnAddresses];
|
||||
|
||||
if ([address_array count] > 0) {
|
||||
static StringAnnotation<256> name_key("UncaughtNSException");
|
||||
name_key.Set("true");
|
||||
std::vector<uint64_t> addresses;
|
||||
for (NSNumber* address in address_array)
|
||||
addresses.push_back([address unsignedLongLongValue]);
|
||||
exception_delegate_->HandleUncaughtNSException(&addresses[0],
|
||||
addresses.size());
|
||||
} else {
|
||||
LOG(WARNING) << "Uncaught Objective-C exception name: " << name
|
||||
<< " reason: " << reason << " with no "
|
||||
<< " -callStackReturnAddresses.";
|
||||
NativeCPUContext cpu_context;
|
||||
CaptureContext(&cpu_context);
|
||||
exception_delegate_->HandleUncaughtNSExceptionWithContext(&cpu_context);
|
||||
}
|
||||
}
|
||||
|
||||
id MaybeCallNextPreprocessor(id exception) {
|
||||
return next_preprocessor_ ? next_preprocessor_(exception) : exception;
|
||||
}
|
||||
|
||||
// Register the objc_setExceptionPreprocessor and NSUncaughtExceptionHandler.
|
||||
void Install(ObjcExceptionDelegate* delegate);
|
||||
|
||||
// Restore the objc_setExceptionPreprocessor and NSUncaughtExceptionHandler.
|
||||
void Uninstall();
|
||||
|
||||
NSException* last_exception() { return last_exception_; }
|
||||
void set_last_exception(NSException* exception) {
|
||||
[last_exception_ release];
|
||||
last_exception_ = [exception retain];
|
||||
}
|
||||
|
||||
private:
|
||||
ExceptionPreprocessorState() = default;
|
||||
~ExceptionPreprocessorState() = default;
|
||||
|
||||
// Location of the intermediate dump generated after an exception triggered
|
||||
// HANDLE_UNCAUGHT_NSEXCEPTION.
|
||||
base::FilePath last_handled_intermediate_dump_;
|
||||
|
||||
// Recorded last NSException in case the exception is caught and thrown again
|
||||
// (without using objc_exception_rethrow.)
|
||||
NSException* last_exception_ = nil;
|
||||
|
||||
ObjcExceptionDelegate* exception_delegate_ = nullptr;
|
||||
objc_exception_preprocessor next_preprocessor_ = nullptr;
|
||||
NSUncaughtExceptionHandler* next_uncaught_exception_handler_ = nullptr;
|
||||
};
|
||||
|
||||
static void ObjcUncaughtExceptionHandler(NSException* exception) {
|
||||
ExceptionPreprocessorState::Get()->FinalizeUncaughtNSException(exception);
|
||||
}
|
||||
|
||||
// This function is used to make it clear to the crash processor that an
|
||||
|
@ -197,15 +294,12 @@ static __attribute__((noinline)) id HANDLE_UNCAUGHT_NSEXCEPTION(
|
|||
SetNSExceptionAnnotations(exception, name, reason);
|
||||
LOG(WARNING) << "Handling Objective-C exception name: " << name
|
||||
<< " reason: " << reason << " with sinkhole: " << sinkhole;
|
||||
crashpad::NativeCPUContext cpu_context;
|
||||
crashpad::CaptureContext(&cpu_context);
|
||||
g_exception_delegate->HandleUncaughtNSExceptionWithContext(&cpu_context);
|
||||
NativeCPUContext cpu_context{};
|
||||
CaptureContext(&cpu_context);
|
||||
|
||||
// Remove the uncaught exception handler so we don't record this twice.
|
||||
NSSetUncaughtExceptionHandler(g_next_uncaught_exception_handler);
|
||||
g_next_uncaught_exception_handler = nullptr;
|
||||
|
||||
return g_next_preprocessor ? g_next_preprocessor(exception) : exception;
|
||||
ExceptionPreprocessorState* preprocessor_state =
|
||||
ExceptionPreprocessorState::Get();
|
||||
return preprocessor_state->HandleUncaughtException(&cpu_context, exception);
|
||||
}
|
||||
|
||||
// Returns true if |path| equals |sinkhole| on device. Simulator paths prepend
|
||||
|
@ -226,13 +320,23 @@ bool ModulePathMatchesSinkhole(const char* path, const char* sinkhole) {
|
|||
}
|
||||
|
||||
id ObjcExceptionPreprocessor(id exception) {
|
||||
// Some sinkholes don't use objc_exception_rethrow when they should, which
|
||||
// would otherwise prevent the exception_preprocessor from getting called
|
||||
// again. Because of this, track the most recently seen exception and
|
||||
// ignore it.
|
||||
ExceptionPreprocessorState* preprocessor_state =
|
||||
ExceptionPreprocessorState::Get();
|
||||
if ([preprocessor_state->last_exception() isEqual:exception]) {
|
||||
return preprocessor_state->MaybeCallNextPreprocessor(exception);
|
||||
}
|
||||
preprocessor_state->set_last_exception(exception);
|
||||
|
||||
static bool seen_first_exception;
|
||||
|
||||
static crashpad::StringAnnotation<256> firstexception("firstexception");
|
||||
static crashpad::StringAnnotation<256> lastexception("lastexception");
|
||||
static crashpad::StringAnnotation<1024> firstexception_bt(
|
||||
"firstexception_bt");
|
||||
static crashpad::StringAnnotation<1024> lastexception_bt("lastexception_bt");
|
||||
static StringAnnotation<256> firstexception("firstexception");
|
||||
static StringAnnotation<256> lastexception("lastexception");
|
||||
static StringAnnotation<1024> firstexception_bt("firstexception_bt");
|
||||
static StringAnnotation<1024> lastexception_bt("lastexception_bt");
|
||||
auto* key = seen_first_exception ? &lastexception : &firstexception;
|
||||
auto* bt_key = seen_first_exception ? &lastexception_bt : &firstexception_bt;
|
||||
NSString* value = [NSString
|
||||
|
@ -470,36 +574,41 @@ id ObjcExceptionPreprocessor(id exception) {
|
|||
}
|
||||
|
||||
// Forward to the next preprocessor.
|
||||
return g_next_preprocessor ? g_next_preprocessor(exception) : exception;
|
||||
return preprocessor_state->MaybeCallNextPreprocessor(exception);
|
||||
}
|
||||
|
||||
void ExceptionPreprocessorState::Install(ObjcExceptionDelegate* delegate) {
|
||||
DCHECK(!next_preprocessor_);
|
||||
exception_delegate_ = delegate;
|
||||
|
||||
// Preprocessor.
|
||||
next_preprocessor_ =
|
||||
objc_setExceptionPreprocessor(&ObjcExceptionPreprocessor);
|
||||
|
||||
// Uncaught processor.
|
||||
next_uncaught_exception_handler_ = NSGetUncaughtExceptionHandler();
|
||||
NSSetUncaughtExceptionHandler(&ObjcUncaughtExceptionHandler);
|
||||
}
|
||||
|
||||
void ExceptionPreprocessorState::Uninstall() {
|
||||
DCHECK(next_preprocessor_);
|
||||
objc_setExceptionPreprocessor(next_preprocessor_);
|
||||
next_preprocessor_ = nullptr;
|
||||
|
||||
NSSetUncaughtExceptionHandler(next_uncaught_exception_handler_);
|
||||
next_uncaught_exception_handler_ = nullptr;
|
||||
|
||||
exception_delegate_ = nullptr;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
void InstallObjcExceptionPreprocessor(ObjcExceptionDelegate* delegate) {
|
||||
DCHECK(!g_next_preprocessor);
|
||||
|
||||
// Preprocessor.
|
||||
g_next_preprocessor =
|
||||
objc_setExceptionPreprocessor(&ObjcExceptionPreprocessor);
|
||||
|
||||
// Uncaught processor.
|
||||
g_exception_delegate = delegate;
|
||||
g_next_uncaught_exception_handler = NSGetUncaughtExceptionHandler();
|
||||
NSSetUncaughtExceptionHandler(&ObjcUncaughtExceptionHandler);
|
||||
ExceptionPreprocessorState::Get()->Install(delegate);
|
||||
}
|
||||
|
||||
void UninstallObjcExceptionPreprocessor() {
|
||||
DCHECK(g_next_preprocessor);
|
||||
|
||||
objc_setExceptionPreprocessor(g_next_preprocessor);
|
||||
g_exception_delegate = nullptr;
|
||||
|
||||
NSSetUncaughtExceptionHandler(g_next_uncaught_exception_handler);
|
||||
g_next_uncaught_exception_handler = nullptr;
|
||||
|
||||
g_next_preprocessor = nullptr;
|
||||
ExceptionPreprocessorState::Get()->Uninstall();
|
||||
}
|
||||
|
||||
} // namespace crashpad
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "base/cxx17_backports.h"
|
||||
#include "base/logging.h"
|
||||
#include "client/ios_handler/in_process_intermediate_dump_handler.h"
|
||||
#include "client/prune_crash_reports.h"
|
||||
|
@ -24,6 +27,7 @@
|
|||
#include "minidump/minidump_file_writer.h"
|
||||
#include "util/file/directory_reader.h"
|
||||
#include "util/file/filesystem.h"
|
||||
#include "util/ios/raw_logging.h"
|
||||
|
||||
namespace {
|
||||
|
||||
|
@ -46,6 +50,10 @@ constexpr char kLockedExtension[] = ".locked";
|
|||
// uuid in the intermediate dump file name.
|
||||
constexpr char kBundleSeperator[] = "@";
|
||||
|
||||
// Zero-ed codes used by kMachExceptionFromNSException and
|
||||
// kMachExceptionSimulated.
|
||||
constexpr mach_exception_data_type_t kEmulatedMachExceptionCodes[2] = {};
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace crashpad {
|
||||
|
@ -54,17 +62,16 @@ namespace internal {
|
|||
InProcessHandler::InProcessHandler() = default;
|
||||
|
||||
InProcessHandler::~InProcessHandler() {
|
||||
if (upload_thread_started_ && upload_thread_) {
|
||||
upload_thread_->Stop();
|
||||
if (cached_writer_) {
|
||||
cached_writer_->Close();
|
||||
}
|
||||
prune_thread_->Stop();
|
||||
UpdatePruneAndUploadThreads(false);
|
||||
}
|
||||
|
||||
bool InProcessHandler::Initialize(
|
||||
const base::FilePath& database,
|
||||
const std::string& url,
|
||||
const std::map<std::string, std::string>& annotations,
|
||||
const IOSSystemDataCollector& system_data) {
|
||||
const std::map<std::string, std::string>& annotations) {
|
||||
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
|
||||
annotations_ = annotations;
|
||||
database_ = CrashReportDatabase::Initialize(database);
|
||||
|
@ -72,7 +79,7 @@ bool InProcessHandler::Initialize(
|
|||
return false;
|
||||
}
|
||||
bundle_identifier_and_seperator_ =
|
||||
system_data.BundleIdentifier() + kBundleSeperator;
|
||||
system_data_.BundleIdentifier() + kBundleSeperator;
|
||||
|
||||
if (!url.empty()) {
|
||||
// TODO(scottmg): options.rate_limit should be removed when we have a
|
||||
|
@ -96,36 +103,55 @@ bool InProcessHandler::Initialize(
|
|||
if (!CreateDirectory(base_dir_))
|
||||
return false;
|
||||
|
||||
bool is_app_extension = system_data_.IsExtension();
|
||||
prune_thread_.reset(new PruneIntermediateDumpsAndCrashReportsThread(
|
||||
database_.get(),
|
||||
PruneCondition::GetDefault(),
|
||||
base_dir_,
|
||||
bundle_identifier_and_seperator_,
|
||||
system_data.IsExtension()));
|
||||
prune_thread_->Start();
|
||||
is_app_extension));
|
||||
if (is_app_extension || system_data_.IsApplicationActive())
|
||||
prune_thread_->Start();
|
||||
|
||||
if (!OpenNewFile())
|
||||
if (!is_app_extension) {
|
||||
system_data_.SetActiveApplicationCallback([this](bool active) {
|
||||
dispatch_async(
|
||||
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
UpdatePruneAndUploadThreads(active);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
base::FilePath cached_writer_path = NewLockedFilePath();
|
||||
cached_writer_ = CreateWriterWithPath(cached_writer_path);
|
||||
if (!cached_writer_.get())
|
||||
return false;
|
||||
|
||||
// Cache the locked and unlocked path here so no allocations are needed during
|
||||
// any exceptions.
|
||||
cached_writer_path_ = cached_writer_path.value();
|
||||
cached_writer_unlocked_path_ =
|
||||
cached_writer_path.RemoveFinalExtension().value();
|
||||
INITIALIZATION_STATE_SET_VALID(initialized_);
|
||||
return true;
|
||||
}
|
||||
|
||||
void InProcessHandler::DumpExceptionFromSignal(
|
||||
const IOSSystemDataCollector& system_data,
|
||||
siginfo_t* siginfo,
|
||||
ucontext_t* context) {
|
||||
void InProcessHandler::DumpExceptionFromSignal(siginfo_t* siginfo,
|
||||
ucontext_t* context) {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
{
|
||||
ScopedReport report(writer_.get(), system_data, annotations_);
|
||||
InProcessIntermediateDumpHandler::WriteExceptionFromSignal(
|
||||
writer_.get(), system_data, siginfo, context);
|
||||
ScopedLockedWriter writer(GetCachedWriter(),
|
||||
cached_writer_path_.c_str(),
|
||||
cached_writer_unlocked_path_.c_str());
|
||||
if (!writer.GetWriter()) {
|
||||
CRASHPAD_RAW_LOG("Cannot DumpExceptionFromSignal without writer");
|
||||
return;
|
||||
}
|
||||
PostReportCleanup();
|
||||
ScopedReport report(writer.GetWriter(), system_data_, annotations_);
|
||||
InProcessIntermediateDumpHandler::WriteExceptionFromSignal(
|
||||
writer.GetWriter(), system_data_, siginfo, context);
|
||||
}
|
||||
|
||||
void InProcessHandler::DumpExceptionFromMachException(
|
||||
const IOSSystemDataCollector& system_data,
|
||||
exception_behavior_t behavior,
|
||||
thread_t thread,
|
||||
exception_type_t exception,
|
||||
|
@ -135,35 +161,97 @@ void InProcessHandler::DumpExceptionFromMachException(
|
|||
ConstThreadState old_state,
|
||||
mach_msg_type_number_t old_state_count) {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
{
|
||||
ScopedReport report(writer_.get(), system_data, annotations_);
|
||||
InProcessIntermediateDumpHandler::WriteExceptionFromMachException(
|
||||
writer_.get(),
|
||||
behavior,
|
||||
thread,
|
||||
exception,
|
||||
code,
|
||||
code_count,
|
||||
flavor,
|
||||
old_state,
|
||||
old_state_count);
|
||||
ScopedLockedWriter writer(GetCachedWriter(),
|
||||
cached_writer_path_.c_str(),
|
||||
cached_writer_unlocked_path_.c_str());
|
||||
if (!writer.GetWriter()) {
|
||||
CRASHPAD_RAW_LOG("Cannot DumpExceptionFromMachException without writer");
|
||||
return;
|
||||
}
|
||||
PostReportCleanup();
|
||||
|
||||
if (mach_exception_callback_for_testing_) {
|
||||
mach_exception_callback_for_testing_();
|
||||
}
|
||||
|
||||
ScopedReport report(writer.GetWriter(), system_data_, annotations_);
|
||||
InProcessIntermediateDumpHandler::WriteExceptionFromMachException(
|
||||
writer.GetWriter(),
|
||||
behavior,
|
||||
thread,
|
||||
exception,
|
||||
code,
|
||||
code_count,
|
||||
flavor,
|
||||
old_state,
|
||||
old_state_count);
|
||||
}
|
||||
|
||||
void InProcessHandler::DumpExceptionFromNSExceptionFrames(
|
||||
const IOSSystemDataCollector& system_data,
|
||||
void InProcessHandler::DumpExceptionFromNSExceptionWithFrames(
|
||||
const uint64_t* frames,
|
||||
const size_t num_frames) {
|
||||
{
|
||||
ScopedReport report(
|
||||
writer_.get(), system_data, annotations_, frames, num_frames);
|
||||
InProcessIntermediateDumpHandler::WriteExceptionFromNSException(
|
||||
writer_.get());
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
ScopedLockedWriter writer(GetCachedWriter(),
|
||||
cached_writer_path_.c_str(),
|
||||
cached_writer_unlocked_path_.c_str());
|
||||
if (!writer.GetWriter()) {
|
||||
CRASHPAD_RAW_LOG(
|
||||
"Cannot DumpExceptionFromNSExceptionWithFrames without writer");
|
||||
return;
|
||||
}
|
||||
PostReportCleanup();
|
||||
ScopedReport report(
|
||||
writer.GetWriter(), system_data_, annotations_, frames, num_frames);
|
||||
InProcessIntermediateDumpHandler::WriteExceptionFromNSException(
|
||||
writer.GetWriter());
|
||||
}
|
||||
|
||||
bool InProcessHandler::DumpExceptionFromSimulatedMachException(
|
||||
const NativeCPUContext* context,
|
||||
exception_type_t exception,
|
||||
base::FilePath* path) {
|
||||
base::FilePath locked_path = NewLockedFilePath();
|
||||
*path = locked_path.RemoveFinalExtension();
|
||||
return DumpExceptionFromSimulatedMachExceptionAtPath(
|
||||
context, exception, locked_path);
|
||||
}
|
||||
|
||||
bool InProcessHandler::DumpExceptionFromSimulatedMachExceptionAtPath(
|
||||
const NativeCPUContext* context,
|
||||
exception_type_t exception,
|
||||
const base::FilePath& path) {
|
||||
// This does not use the cached writer. It's expected that simulated
|
||||
// exceptions can be called multiple times and there is no expectation that
|
||||
// the application is in an unsafe state, or will be terminated after this
|
||||
// call.
|
||||
std::unique_ptr<IOSIntermediateDumpWriter> unsafe_writer =
|
||||
CreateWriterWithPath(path);
|
||||
base::FilePath writer_path_unlocked = path.RemoveFinalExtension();
|
||||
ScopedLockedWriter writer(unsafe_writer.get(),
|
||||
path.value().c_str(),
|
||||
writer_path_unlocked.value().c_str());
|
||||
if (!writer.GetWriter()) {
|
||||
CRASHPAD_RAW_LOG(
|
||||
"Cannot DumpExceptionFromSimulatedMachExceptionAtPath without writer");
|
||||
return false;
|
||||
}
|
||||
ScopedReport report(writer.GetWriter(), system_data_, annotations_);
|
||||
InProcessIntermediateDumpHandler::WriteExceptionFromMachException(
|
||||
writer.GetWriter(),
|
||||
MACH_EXCEPTION_CODES,
|
||||
mach_thread_self(),
|
||||
exception,
|
||||
kEmulatedMachExceptionCodes,
|
||||
std::size(kEmulatedMachExceptionCodes),
|
||||
MACHINE_THREAD_STATE,
|
||||
reinterpret_cast<ConstThreadState>(context),
|
||||
MACHINE_THREAD_STATE_COUNT);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InProcessHandler::MoveIntermediateDumpAtPathToPending(
|
||||
const base::FilePath& path) {
|
||||
base::FilePath new_path_unlocked = NewLockedFilePath().RemoveFinalExtension();
|
||||
return MoveFileOrDirectory(path, new_path_unlocked);
|
||||
}
|
||||
void InProcessHandler::ProcessIntermediateDumps(
|
||||
const std::map<std::string, std::string>& annotations) {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
|
@ -184,9 +272,36 @@ void InProcessHandler::ProcessIntermediateDump(
|
|||
}
|
||||
|
||||
void InProcessHandler::StartProcessingPendingReports() {
|
||||
if (!upload_thread_started_ && upload_thread_) {
|
||||
upload_thread_->Start();
|
||||
upload_thread_started_ = true;
|
||||
if (!upload_thread_)
|
||||
return;
|
||||
|
||||
upload_thread_enabled_ = true;
|
||||
|
||||
// This may be a no-op if IsApplicationActive is false, as it is not safe to
|
||||
// start the upload thread when in the background (due to the potential for
|
||||
// flocked files in shared containers).
|
||||
// TODO(crbug.com/crashpad/400): Consider moving prune and upload thread to
|
||||
// BackgroundTasks and/or NSURLSession. This might allow uploads to continue
|
||||
// in the background.
|
||||
UpdatePruneAndUploadThreads(system_data_.IsApplicationActive());
|
||||
}
|
||||
|
||||
void InProcessHandler::UpdatePruneAndUploadThreads(bool active) {
|
||||
base::AutoLock lock_owner(prune_and_upload_lock_);
|
||||
// TODO(crbug.com/crashpad/400): Consider moving prune and upload thread to
|
||||
// BackgroundTasks and/or NSURLSession. This might allow uploads to continue
|
||||
// in the background.
|
||||
if (active) {
|
||||
if (!prune_thread_->is_running())
|
||||
prune_thread_->Start();
|
||||
if (upload_thread_enabled_ && !upload_thread_->is_running()) {
|
||||
upload_thread_->Start();
|
||||
}
|
||||
} else {
|
||||
if (prune_thread_->is_running())
|
||||
prune_thread_->Stop();
|
||||
if (upload_thread_enabled_ && upload_thread_->is_running())
|
||||
upload_thread_->Stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -198,6 +313,7 @@ void InProcessHandler::SaveSnapshot(
|
|||
if (database_status != CrashReportDatabase::kNoError) {
|
||||
Metrics::ExceptionCaptureResult(
|
||||
Metrics::CaptureResult::kPrepareNewCrashReportFailed);
|
||||
return;
|
||||
}
|
||||
process_snapshot.SetReportID(new_report->ReportID());
|
||||
|
||||
|
@ -212,6 +328,7 @@ void InProcessHandler::SaveSnapshot(
|
|||
if (!minidump.WriteEverything(new_report->Writer())) {
|
||||
Metrics::ExceptionCaptureResult(
|
||||
Metrics::CaptureResult::kMinidumpWriteFailed);
|
||||
return;
|
||||
}
|
||||
UUID uuid;
|
||||
database_status =
|
||||
|
@ -219,6 +336,7 @@ void InProcessHandler::SaveSnapshot(
|
|||
if (database_status != CrashReportDatabase::kNoError) {
|
||||
Metrics::ExceptionCaptureResult(
|
||||
Metrics::CaptureResult::kFinishedWritingCrashReportFailed);
|
||||
return;
|
||||
}
|
||||
|
||||
if (upload_thread_) {
|
||||
|
@ -243,6 +361,7 @@ std::vector<base::FilePath> InProcessHandler::PendingFiles() {
|
|||
// intermediate dumps into never getting processed.
|
||||
std::vector<base::FilePath> other_files;
|
||||
|
||||
base::FilePath cached_writer_path(cached_writer_path_);
|
||||
while ((result = reader.NextFile(&file)) ==
|
||||
DirectoryReader::Result::kSuccess) {
|
||||
// Don't try to process files marked as 'locked' from a different bundle id.
|
||||
|
@ -254,9 +373,9 @@ std::vector<base::FilePath> InProcessHandler::PendingFiles() {
|
|||
continue;
|
||||
}
|
||||
|
||||
// Never process the current file.
|
||||
// Never process the current cached writer path.
|
||||
file = base_dir_.Append(file);
|
||||
if (file == current_file_)
|
||||
if (file == cached_writer_path)
|
||||
continue;
|
||||
|
||||
// Otherwise, include any other unlocked, or locked files matching
|
||||
|
@ -277,35 +396,51 @@ std::vector<base::FilePath> InProcessHandler::PendingFiles() {
|
|||
return files;
|
||||
}
|
||||
|
||||
InProcessHandler::ScopedAlternateWriter::ScopedAlternateWriter(
|
||||
InProcessHandler* handler)
|
||||
: handler_(handler) {}
|
||||
IOSIntermediateDumpWriter* InProcessHandler::GetCachedWriter() {
|
||||
static_assert(
|
||||
std::atomic<uint64_t>::is_always_lock_free,
|
||||
"std::atomic_compare_exchange_strong uint64_t may not be signal-safe");
|
||||
uint64_t thread_self;
|
||||
// This is only safe when passing pthread_self(), otherwise this can lock.
|
||||
pthread_threadid_np(pthread_self(), &thread_self);
|
||||
uint64_t expected = 0;
|
||||
if (!std::atomic_compare_exchange_strong(
|
||||
&exception_thread_id_, &expected, thread_self)) {
|
||||
if (expected == thread_self) {
|
||||
// Another exception came in from this thread, which means it's likely
|
||||
// that our own handler crashed. We could open up a new intermediate dump
|
||||
// and try to save this dump, but we could end up endlessly writing dumps.
|
||||
// Instead, give up.
|
||||
} else {
|
||||
// Another thread is handling a crash. Sleep forever.
|
||||
while (1) {
|
||||
sleep(std::numeric_limits<unsigned int>::max());
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool InProcessHandler::ScopedAlternateWriter::Open() {
|
||||
return cached_writer_.get();
|
||||
}
|
||||
|
||||
std::unique_ptr<IOSIntermediateDumpWriter>
|
||||
InProcessHandler::CreateWriterWithPath(const base::FilePath& writer_path) {
|
||||
std::unique_ptr<IOSIntermediateDumpWriter> writer =
|
||||
std::make_unique<IOSIntermediateDumpWriter>();
|
||||
if (!writer->Open(writer_path)) {
|
||||
DLOG(ERROR) << "Unable to open intermediate dump file: "
|
||||
<< writer_path.value();
|
||||
return nullptr;
|
||||
}
|
||||
return writer;
|
||||
}
|
||||
|
||||
const base::FilePath InProcessHandler::NewLockedFilePath() {
|
||||
UUID uuid;
|
||||
uuid.InitializeWithNew();
|
||||
const std::string uuid_string = uuid.ToString();
|
||||
return OpenAtPath(handler_->base_dir_.Append(uuid_string));
|
||||
}
|
||||
|
||||
bool InProcessHandler::ScopedAlternateWriter::OpenAtPath(
|
||||
const base::FilePath& path) {
|
||||
path_ = path;
|
||||
handler_->SetOpenNewFileAfterReport(false);
|
||||
original_writer_ = handler_->GetWriter();
|
||||
auto writer = std::make_unique<IOSIntermediateDumpWriter>();
|
||||
if (!writer->Open(path_)) {
|
||||
DLOG(ERROR) << "Unable to open alternate intermediate dump file: "
|
||||
<< path_.value();
|
||||
return false;
|
||||
}
|
||||
handler_->SetWriter(std::move(writer));
|
||||
return true;
|
||||
}
|
||||
|
||||
InProcessHandler::ScopedAlternateWriter::~ScopedAlternateWriter() {
|
||||
handler_->SetWriter(std::move(original_writer_));
|
||||
handler_->SetOpenNewFileAfterReport(true);
|
||||
const std::string file_string =
|
||||
bundle_identifier_and_seperator_ + uuid.ToString() + kLockedExtension;
|
||||
return base_dir_.Append(file_string);
|
||||
}
|
||||
|
||||
InProcessHandler::ScopedReport::ScopedReport(
|
||||
|
@ -318,6 +453,7 @@ InProcessHandler::ScopedReport::ScopedReport(
|
|||
frames_(frames),
|
||||
num_frames_(num_frames),
|
||||
rootMap_(writer) {
|
||||
DCHECK(writer);
|
||||
InProcessIntermediateDumpHandler::WriteHeader(writer);
|
||||
InProcessIntermediateDumpHandler::WriteProcessInfo(writer, annotations);
|
||||
InProcessIntermediateDumpHandler::WriteSystemInfo(writer, system_data);
|
||||
|
@ -331,34 +467,24 @@ InProcessHandler::ScopedReport::~ScopedReport() {
|
|||
InProcessIntermediateDumpHandler::WriteModuleInfo(writer_);
|
||||
}
|
||||
|
||||
bool InProcessHandler::OpenNewFile() {
|
||||
if (!current_file_.empty()) {
|
||||
// Remove .lock extension so this dump can be processed on next run by this
|
||||
// client, or a client with a different bundle id that can access this dump.
|
||||
base::FilePath new_path = current_file_.RemoveFinalExtension();
|
||||
MoveFileOrDirectory(current_file_, new_path);
|
||||
}
|
||||
UUID uuid;
|
||||
uuid.InitializeWithNew();
|
||||
const std::string file_string =
|
||||
bundle_identifier_and_seperator_ + uuid.ToString() + kLockedExtension;
|
||||
current_file_ = base_dir_.Append(file_string);
|
||||
writer_ = std::make_unique<IOSIntermediateDumpWriter>();
|
||||
if (!writer_->Open(current_file_)) {
|
||||
DLOG(ERROR) << "Unable to open intermediate dump file: "
|
||||
<< current_file_.value();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
InProcessHandler::ScopedLockedWriter::ScopedLockedWriter(
|
||||
IOSIntermediateDumpWriter* writer,
|
||||
const char* writer_path,
|
||||
const char* writer_unlocked_path)
|
||||
: writer_path_(writer_path),
|
||||
writer_unlocked_path_(writer_unlocked_path),
|
||||
writer_(writer) {}
|
||||
|
||||
void InProcessHandler::PostReportCleanup() {
|
||||
if (writer_) {
|
||||
writer_->Close();
|
||||
writer_.reset();
|
||||
InProcessHandler::ScopedLockedWriter::~ScopedLockedWriter() {
|
||||
if (!writer_)
|
||||
return;
|
||||
|
||||
writer_->Close();
|
||||
if (rename(writer_path_, writer_unlocked_path_) != 0) {
|
||||
CRASHPAD_RAW_LOG("Could not remove locked extension.");
|
||||
CRASHPAD_RAW_LOG(writer_path_);
|
||||
CRASHPAD_RAW_LOG(writer_unlocked_path_);
|
||||
}
|
||||
if (open_new_file_after_report_)
|
||||
OpenNewFile();
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
|
|
@ -12,18 +12,22 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <mach/mach.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/synchronization/lock.h"
|
||||
#include "client/ios_handler/prune_intermediate_dumps_and_crash_reports_thread.h"
|
||||
#include "handler/crash_report_upload_thread.h"
|
||||
#include "snapshot/ios/process_snapshot_ios_intermediate_dump.h"
|
||||
#include "util/ios/ios_intermediate_dump_writer.h"
|
||||
#include "util/ios/ios_system_data_collector.h"
|
||||
#include "util/misc/capture_context.h"
|
||||
#include "util/misc/initialization_state_dcheck.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
@ -46,28 +50,28 @@ class InProcessHandler {
|
|||
//! \param[in] database The path to a Crashpad database.
|
||||
//! \param[in] url The URL of an upload server.
|
||||
//! \param[in] annotations Process annotations to set in each crash report.
|
||||
//! \param[in] system_data An object containing various system data points.
|
||||
//! \return `true` if a handler to a pending intermediate dump could be
|
||||
//! opened.
|
||||
bool Initialize(const base::FilePath& database,
|
||||
const std::string& url,
|
||||
const std::map<std::string, std::string>& annotations,
|
||||
const IOSSystemDataCollector& system_data);
|
||||
const std::map<std::string, std::string>& annotations);
|
||||
|
||||
//! \brief Generate an intermediate dump from a signal handler exception.
|
||||
//! Writes the dump with the cached writer does not allow concurrent
|
||||
//! exceptions to be written. It is expected the system will terminate
|
||||
//! the application after this call.
|
||||
//!
|
||||
//! \param[in] system_data An object containing various system data points.
|
||||
//! \param[in] siginfo A pointer to a `siginfo_t` object received by a signal
|
||||
//! handler.
|
||||
//! \param[in] context A pointer to a `ucontext_t` object received by a
|
||||
//! signal.
|
||||
void DumpExceptionFromSignal(const IOSSystemDataCollector& system_data,
|
||||
siginfo_t* siginfo,
|
||||
ucontext_t* context);
|
||||
void DumpExceptionFromSignal(siginfo_t* siginfo, ucontext_t* context);
|
||||
|
||||
//! \brief Generate an intermediate dump from a mach exception.
|
||||
//! \brief Generate an intermediate dump from a mach exception. Writes the
|
||||
//! dump with the cached writer does not allow concurrent exceptions to be
|
||||
//! written. It is expected the system will terminate the application
|
||||
//! after this call.
|
||||
//!
|
||||
//! \param[in] system_data An object containing various system data points.
|
||||
//! \param[in] behavior
|
||||
//! \param[in] thread
|
||||
//! \param[in] exception
|
||||
|
@ -76,8 +80,7 @@ class InProcessHandler {
|
|||
//! \param[in,out] flavor
|
||||
//! \param[in] old_state
|
||||
//! \param[in] old_state_count
|
||||
void DumpExceptionFromMachException(const IOSSystemDataCollector& system_data,
|
||||
exception_behavior_t behavior,
|
||||
void DumpExceptionFromMachException(exception_behavior_t behavior,
|
||||
thread_t thread,
|
||||
exception_type_t exception,
|
||||
const mach_exception_data_type_t* code,
|
||||
|
@ -91,15 +94,48 @@ class InProcessHandler {
|
|||
//! When the ObjcExceptionPreprocessor does not detect an NSException as it is
|
||||
//! thrown, the last-chance uncaught exception handler passes a list of call
|
||||
//! stack frame addresses. Record them in the intermediate dump so a minidump
|
||||
//! with a 'fake' call stack is generated.
|
||||
//! with a 'fake' call stack is generated. Writes the dump with the cached
|
||||
//! writer does not allow concurrent exceptions to be written. It is expected
|
||||
//! the system will terminate the application after this call.
|
||||
|
||||
//!
|
||||
//! \param[in] system_data An object containing various system data points.
|
||||
//! \param[in] frames An array of call stack frame addresses.
|
||||
//! \param[in] num_frames The number of frames in |frames|.
|
||||
void DumpExceptionFromNSExceptionFrames(
|
||||
const IOSSystemDataCollector& system_data,
|
||||
const uint64_t* frames,
|
||||
const size_t num_frames);
|
||||
void DumpExceptionFromNSExceptionWithFrames(const uint64_t* frames,
|
||||
const size_t num_frames);
|
||||
|
||||
//! \brief Generate a simulated intermediate dump similar to a Mach exception
|
||||
//! in the same base directory as other exceptions. Does not use the
|
||||
//! cached writer.
|
||||
//!
|
||||
//! \param[in] context A pointer to a NativeCPUContext object for this
|
||||
//! simulated exception.
|
||||
//! \param[in] exception
|
||||
//! \param[out] path The path of the intermediate dump generated.
|
||||
//! \return `true` if the pending intermediate dump could be written.
|
||||
bool DumpExceptionFromSimulatedMachException(const NativeCPUContext* context,
|
||||
exception_type_t exception,
|
||||
base::FilePath* path);
|
||||
|
||||
//! \brief Generate a simulated intermediate dump similar to a Mach exception
|
||||
//! at a specific path. Does not use the cached writer.
|
||||
//!
|
||||
//! \param[in] context A pointer to a NativeCPUContext object for this
|
||||
//! simulated exception.
|
||||
//! \param[in] exception
|
||||
//! \param[in] path Path to where the intermediate dump should be written.
|
||||
//! \return `true` if the pending intermediate dump could be written.
|
||||
bool DumpExceptionFromSimulatedMachExceptionAtPath(
|
||||
const NativeCPUContext* context,
|
||||
exception_type_t exception,
|
||||
const base::FilePath& path);
|
||||
|
||||
//! \brief Moves an intermediate dump to the pending directory. This is meant
|
||||
//! to be used by the UncaughtExceptionHandler, when NSException caught
|
||||
//! by the preprocessor matches the UncaughtExceptionHandler.
|
||||
//!
|
||||
//! \param[in] path Path to the specific intermediate dump.
|
||||
bool MoveIntermediateDumpAtPathToPending(const base::FilePath& path);
|
||||
|
||||
//! \brief Requests that the handler convert all intermediate dumps into
|
||||
//! minidumps and trigger an upload if possible.
|
||||
|
@ -121,34 +157,11 @@ class InProcessHandler {
|
|||
//! pending reports.
|
||||
void StartProcessingPendingReports();
|
||||
|
||||
//! \brief Helper that swaps out the InProcessHandler's |writer_| with an
|
||||
//! alternate writer so DumpWithContext does not interfere with the
|
||||
//! |writer_| created on startup. This is useful for -DumpWithoutCrash,
|
||||
//! which may write to an alternate location.
|
||||
class ScopedAlternateWriter {
|
||||
public:
|
||||
ScopedAlternateWriter(InProcessHandler* handler);
|
||||
~ScopedAlternateWriter();
|
||||
ScopedAlternateWriter(const ScopedAlternateWriter&) = delete;
|
||||
ScopedAlternateWriter& operator=(const ScopedAlternateWriter&) = delete;
|
||||
//! \brief Open's an alternate dump writer in the same directory as the
|
||||
//! default InProcessHandler's dump writer, so the file will be
|
||||
//! processed with -ProcessIntermediateDumps()
|
||||
bool Open();
|
||||
|
||||
//! \brief Open's an alternate dump writer in the client provided |path|.
|
||||
//! The file will only be processed by calling
|
||||
//! ProcessIntermediateDump(path)
|
||||
bool OpenAtPath(const base::FilePath& path);
|
||||
|
||||
//! \brief The path of the alternate dump writer.
|
||||
const base::FilePath& path() { return path_; }
|
||||
|
||||
private:
|
||||
InProcessHandler* handler_;
|
||||
std::unique_ptr<IOSIntermediateDumpWriter> original_writer_;
|
||||
base::FilePath path_;
|
||||
};
|
||||
//! \brief Inject a callback into Mach handling. Intended to be used by
|
||||
//! tests to trigger a reentrant exception.
|
||||
void SetMachExceptionCallbackForTesting(void (*callback)()) {
|
||||
mach_exception_callback_for_testing_ = callback;
|
||||
}
|
||||
|
||||
private:
|
||||
//! \brief Helper to start and end intermediate reports.
|
||||
|
@ -170,38 +183,74 @@ class InProcessHandler {
|
|||
IOSIntermediateDumpWriter::ScopedRootMap rootMap_;
|
||||
};
|
||||
|
||||
std::unique_ptr<IOSIntermediateDumpWriter> GetWriter() {
|
||||
return std::move(writer_);
|
||||
}
|
||||
//! \brief Helper to manage closing the intermediate dump writer and unlocking
|
||||
//! the dump file (renaming the file) after the report is written.
|
||||
class ScopedLockedWriter {
|
||||
public:
|
||||
ScopedLockedWriter(IOSIntermediateDumpWriter* writer,
|
||||
const char* writer_path,
|
||||
const char* writer_unlocked_path);
|
||||
|
||||
void SetWriter(std::unique_ptr<IOSIntermediateDumpWriter> writer) {
|
||||
writer_ = std::move(writer);
|
||||
}
|
||||
//! \brief Close the writer_ and rename to the file with path without the
|
||||
//! .locked extension.
|
||||
~ScopedLockedWriter();
|
||||
|
||||
void SetOpenNewFileAfterReport(bool open_new_file_after_report) {
|
||||
open_new_file_after_report_ = open_new_file_after_report;
|
||||
}
|
||||
ScopedLockedWriter(const ScopedLockedWriter&) = delete;
|
||||
ScopedLockedWriter& operator=(const ScopedLockedWriter&) = delete;
|
||||
|
||||
IOSIntermediateDumpWriter* GetWriter() { return writer_; }
|
||||
|
||||
private:
|
||||
const char* writer_path_;
|
||||
const char* writer_unlocked_path_;
|
||||
IOSIntermediateDumpWriter* writer_;
|
||||
};
|
||||
|
||||
//! \brief Manage the prune and upload thread when the active state changes.
|
||||
void UpdatePruneAndUploadThreads(bool active);
|
||||
|
||||
//! \brief Writes a minidump to the Crashpad database from the
|
||||
//! \a process_snapshot, and triggers the upload_thread_ if started.
|
||||
void SaveSnapshot(ProcessSnapshotIOSIntermediateDump& process_snapshot);
|
||||
|
||||
// Process a maximum of 20 pending intermediate dumps. Dumps named with our
|
||||
// bundle id get first priority to prevent spamming.
|
||||
//! \brief Process a maximum of 20 pending intermediate dumps. Dumps named
|
||||
//! with our bundle id get first priority to prevent spamming.
|
||||
std::vector<base::FilePath> PendingFiles();
|
||||
|
||||
bool OpenNewFile();
|
||||
void PostReportCleanup();
|
||||
//! \brief Lock access to the cached intermediate dump writer from
|
||||
//! concurrent signal, Mach exception and uncaught NSExceptions so that
|
||||
//! the first exception wins. If the same thread triggers another
|
||||
//! reentrant exception, ignore it. If a different thread triggers a
|
||||
//! concurrent exception, sleep indefinitely.
|
||||
IOSIntermediateDumpWriter* GetCachedWriter();
|
||||
|
||||
bool upload_thread_started_ = false;
|
||||
bool open_new_file_after_report_ = true;
|
||||
//! \brief Open a new intermediate dump writer from \a writer_path.
|
||||
std::unique_ptr<IOSIntermediateDumpWriter> CreateWriterWithPath(
|
||||
const base::FilePath& writer_path);
|
||||
|
||||
//! \brief Generates a new file path to be used by an intermediate dump
|
||||
//! writer built from base_dir_,, bundle_identifier_and_seperator_, a new
|
||||
//! UUID, with a .locked extension.
|
||||
const base::FilePath NewLockedFilePath();
|
||||
|
||||
// Intended to be used by tests triggering a reentrant exception. Called
|
||||
// in DumpExceptionFromMachException after aquiring the cached_writer_.
|
||||
void (*mach_exception_callback_for_testing_)() = nullptr;
|
||||
|
||||
// Used to synchronize access to UpdatePruneAndUploadThreads().
|
||||
base::Lock prune_and_upload_lock_;
|
||||
std::atomic_bool upload_thread_enabled_ = false;
|
||||
std::map<std::string, std::string> annotations_;
|
||||
base::FilePath base_dir_;
|
||||
base::FilePath current_file_;
|
||||
std::unique_ptr<IOSIntermediateDumpWriter> writer_;
|
||||
std::unique_ptr<IOSIntermediateDumpWriter> alternate_mach_writer_;
|
||||
std::string cached_writer_path_;
|
||||
std::string cached_writer_unlocked_path_;
|
||||
std::unique_ptr<IOSIntermediateDumpWriter> cached_writer_;
|
||||
std::atomic<uint64_t> exception_thread_id_ = 0;
|
||||
std::unique_ptr<CrashReportUploadThread> upload_thread_;
|
||||
std::unique_ptr<PruneIntermediateDumpsAndCrashReportsThread> prune_thread_;
|
||||
std::unique_ptr<CrashReportDatabase> database_;
|
||||
std::string bundle_identifier_and_seperator_;
|
||||
IOSSystemDataCollector system_data_;
|
||||
InitializationStateDcheck initialized_;
|
||||
};
|
||||
|
||||
|
|
|
@ -36,8 +36,7 @@ class InProcessHandlerTest : public testing::Test {
|
|||
// testing::Test:
|
||||
|
||||
void SetUp() override {
|
||||
ASSERT_TRUE(
|
||||
in_process_handler_.Initialize(temp_dir_.path(), "", {}, system_data_));
|
||||
ASSERT_TRUE(in_process_handler_.Initialize(temp_dir_.path(), "", {}));
|
||||
pending_dir_ = temp_dir_.path().Append("pending-serialized-ios-dump");
|
||||
bundle_identifier_and_seperator_ = system_data_.BundleIdentifier() + "@";
|
||||
}
|
||||
|
|
|
@ -21,7 +21,8 @@
|
|||
#include <sys/sysctl.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "base/cxx17_backports.h"
|
||||
#include <iterator>
|
||||
|
||||
#include "build/build_config.h"
|
||||
#include "snapshot/snapshot_constants.h"
|
||||
#include "util/ios/ios_intermediate_dump_writer.h"
|
||||
|
@ -428,7 +429,7 @@ void CaptureMemoryPointedToByThreadState(IOSIntermediateDumpWriter* writer,
|
|||
MaybeCaptureMemoryAround(writer, thread_state.__rip);
|
||||
#elif defined(ARCH_CPU_ARM_FAMILY)
|
||||
MaybeCaptureMemoryAround(writer, thread_state.__pc);
|
||||
for (size_t i = 0; i < base::size(thread_state.__x); ++i) {
|
||||
for (size_t i = 0; i < std::size(thread_state.__x); ++i) {
|
||||
MaybeCaptureMemoryAround(writer, thread_state.__x[i]);
|
||||
}
|
||||
#endif
|
||||
|
@ -598,7 +599,7 @@ void InProcessIntermediateDumpHandler::WriteProcessInfo(
|
|||
kinfo_proc kern_proc_info;
|
||||
int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()};
|
||||
size_t len = sizeof(kern_proc_info);
|
||||
if (sysctl(mib, base::size(mib), &kern_proc_info, &len, nullptr, 0) == 0) {
|
||||
if (sysctl(mib, std::size(mib), &kern_proc_info, &len, nullptr, 0) == 0) {
|
||||
WriteProperty(
|
||||
writer, IntermediateDumpKey::kPID, &kern_proc_info.kp_proc.p_pid);
|
||||
WriteProperty(writer,
|
||||
|
@ -646,7 +647,7 @@ void InProcessIntermediateDumpHandler::WriteProcessInfo(
|
|||
IntermediateDumpKey::kSystemTime,
|
||||
&task_thread_times.system_time);
|
||||
} else {
|
||||
CRASHPAD_RAW_LOG("task_info task_basic_info");
|
||||
CRASHPAD_RAW_LOG("task_info thread_times_info");
|
||||
}
|
||||
|
||||
if (!annotations.empty()) {
|
||||
|
@ -810,6 +811,22 @@ void InProcessIntermediateDumpHandler::WriteThreadInfo(
|
|||
CRASHPAD_RAW_LOG_ERROR(kr, "thread_info::THREAD_BASIC_INFO");
|
||||
}
|
||||
|
||||
thread_extended_info extended_info;
|
||||
count = THREAD_EXTENDED_INFO_COUNT;
|
||||
kr = thread_info(thread,
|
||||
THREAD_EXTENDED_INFO,
|
||||
reinterpret_cast<thread_info_t>(&extended_info),
|
||||
&count);
|
||||
if (kr == KERN_SUCCESS) {
|
||||
WritePropertyBytes(
|
||||
writer,
|
||||
IntermediateDumpKey::kThreadName,
|
||||
reinterpret_cast<const void*>(extended_info.pth_name),
|
||||
strnlen(extended_info.pth_name, sizeof(extended_info.pth_name)));
|
||||
} else {
|
||||
CRASHPAD_RAW_LOG_ERROR(kr, "thread_info::THREAD_EXTENDED_INFO");
|
||||
}
|
||||
|
||||
thread_precedence_policy precedence;
|
||||
count = THREAD_PRECEDENCE_POLICY_COUNT;
|
||||
boolean_t get_default = FALSE;
|
||||
|
@ -962,10 +979,12 @@ void InProcessIntermediateDumpHandler::WriteModuleInfo(
|
|||
return;
|
||||
}
|
||||
|
||||
WriteProperty(writer,
|
||||
IntermediateDumpKey::kName,
|
||||
image->imageFilePath,
|
||||
strlen(image->imageFilePath));
|
||||
if (image->imageFilePath) {
|
||||
WriteProperty(writer,
|
||||
IntermediateDumpKey::kName,
|
||||
image->imageFilePath,
|
||||
strlen(image->imageFilePath));
|
||||
}
|
||||
uint64_t address = FromPointerCast<uint64_t>(image->imageLoadAddress);
|
||||
WriteProperty(writer, IntermediateDumpKey::kAddress, &address);
|
||||
WriteProperty(
|
||||
|
@ -975,7 +994,12 @@ void InProcessIntermediateDumpHandler::WriteModuleInfo(
|
|||
|
||||
{
|
||||
IOSIntermediateDumpWriter::ScopedArrayMap modules(writer);
|
||||
WriteProperty(writer, IntermediateDumpKey::kName, image_infos->dyldPath);
|
||||
if (image_infos->dyldPath) {
|
||||
WriteProperty(writer,
|
||||
IntermediateDumpKey::kName,
|
||||
image_infos->dyldPath,
|
||||
strlen(image_infos->dyldPath));
|
||||
}
|
||||
uint64_t address =
|
||||
FromPointerCast<uint64_t>(image_infos->dyldImageLoadAddress);
|
||||
WriteProperty(writer, IntermediateDumpKey::kAddress, &address);
|
||||
|
|
|
@ -16,7 +16,8 @@
|
|||
|
||||
#include <sys/utsname.h>
|
||||
|
||||
#include "base/cxx17_backports.h"
|
||||
#include <iterator>
|
||||
|
||||
#include "base/files/file_path.h"
|
||||
#include "build/build_config.h"
|
||||
#include "client/annotation.h"
|
||||
|
@ -25,6 +26,7 @@
|
|||
#include "client/simple_string_dictionary.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "snapshot/ios/process_snapshot_ios_intermediate_dump.h"
|
||||
#include "test/scoped_set_thread_name.h"
|
||||
#include "test/scoped_temp_dir.h"
|
||||
#include "test/test_paths.h"
|
||||
#include "util/file/filesystem.h"
|
||||
|
@ -48,6 +50,7 @@ class InProcessIntermediateDumpHandlerTest : public testing::Test {
|
|||
}
|
||||
|
||||
void TearDown() override {
|
||||
EXPECT_TRUE(writer_->Close());
|
||||
writer_.reset();
|
||||
EXPECT_FALSE(IsRegularFile(path_));
|
||||
}
|
||||
|
@ -74,7 +77,7 @@ class InProcessIntermediateDumpHandlerTest : public testing::Test {
|
|||
mach_thread_self(),
|
||||
kSimulatedException,
|
||||
code,
|
||||
base::size(code),
|
||||
std::size(code),
|
||||
MACHINE_THREAD_STATE,
|
||||
reinterpret_cast<ConstThreadState>(&cpu_context),
|
||||
MACHINE_THREAD_STATE_COUNT);
|
||||
|
@ -103,12 +106,18 @@ TEST_F(InProcessIntermediateDumpHandlerTest, TestSystem) {
|
|||
EXPECT_STREQ(system->CPUVendor().c_str(), "GenuineIntel");
|
||||
#elif defined(ARCH_CPU_ARM64)
|
||||
EXPECT_EQ(system->GetCPUArchitecture(), kCPUArchitectureARM64);
|
||||
utsname uts;
|
||||
ASSERT_EQ(uname(&uts), 0);
|
||||
EXPECT_STREQ(system->MachineDescription().c_str(), uts.machine);
|
||||
#else
|
||||
#error Port to your CPU architecture
|
||||
#endif
|
||||
#if TARGET_OS_SIMULATOR
|
||||
EXPECT_EQ(system->MachineDescription().substr(0, 13),
|
||||
std::string("iOS Simulator"));
|
||||
#elif TARGET_OS_IPHONE
|
||||
utsname uts;
|
||||
ASSERT_EQ(uname(&uts), 0);
|
||||
EXPECT_STREQ(system->MachineDescription().c_str(), uts.machine);
|
||||
#endif
|
||||
|
||||
EXPECT_EQ(system->GetOperatingSystem(), SystemSnapshot::kOperatingSystemIOS);
|
||||
}
|
||||
|
||||
|
@ -198,6 +207,8 @@ TEST_F(InProcessIntermediateDumpHandlerTest, TestAnnotations) {
|
|||
}
|
||||
|
||||
TEST_F(InProcessIntermediateDumpHandlerTest, TestThreads) {
|
||||
const ScopedSetThreadName scoped_set_thread_name("TestThreads");
|
||||
|
||||
WriteReport();
|
||||
internal::ProcessSnapshotIOSIntermediateDump process_snapshot;
|
||||
ASSERT_TRUE(process_snapshot.InitializeWithFilePath(path(), {}));
|
||||
|
@ -213,6 +224,7 @@ TEST_F(InProcessIntermediateDumpHandlerTest, TestThreads) {
|
|||
&count),
|
||||
0);
|
||||
EXPECT_EQ(threads[0]->ThreadID(), identifier_info.thread_id);
|
||||
EXPECT_EQ(threads[0]->ThreadName(), "TestThreads");
|
||||
}
|
||||
|
||||
TEST_F(InProcessIntermediateDumpHandlerTest, TestProcess) {
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "client/prune_crash_reports.h"
|
||||
#include "util/file/directory_reader.h"
|
||||
#include "util/file/filesystem.h"
|
||||
#include "util/ios/scoped_background_task.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
|
@ -27,6 +28,9 @@ namespace {
|
|||
// The file extension used to indicate a file is locked.
|
||||
constexpr char kLockedExtension[] = ".locked";
|
||||
|
||||
// Prune onces a day.
|
||||
constexpr time_t prune_interval = 60 * 60 * 24;
|
||||
|
||||
// If the client finds a locked file matching it's own bundle id, unlock it
|
||||
// after 24 hours.
|
||||
constexpr time_t matching_bundle_locked_ttl = 60 * 60 * 24;
|
||||
|
@ -95,11 +99,12 @@ PruneIntermediateDumpsAndCrashReportsThread::
|
|||
base::FilePath pending_path,
|
||||
std::string bundle_identifier_and_seperator,
|
||||
bool is_extension)
|
||||
: thread_(60 * 60 * 24, this),
|
||||
: thread_(prune_interval, this),
|
||||
condition_(std::move(condition)),
|
||||
pending_path_(pending_path),
|
||||
bundle_identifier_and_seperator_(bundle_identifier_and_seperator),
|
||||
initial_work_delay_(is_extension ? extension_delay : app_delay),
|
||||
last_start_time_(0),
|
||||
database_(database) {}
|
||||
|
||||
PruneIntermediateDumpsAndCrashReportsThread::
|
||||
|
@ -115,8 +120,25 @@ void PruneIntermediateDumpsAndCrashReportsThread::Stop() {
|
|||
|
||||
void PruneIntermediateDumpsAndCrashReportsThread::DoWork(
|
||||
const WorkerThread* thread) {
|
||||
// This thread may be stopped and started a number of times throughout the
|
||||
// lifetime of the process to prevent 0xdead10cc kills (see
|
||||
// crbug.com/crashpad/400), but it should only run once per prune_interval
|
||||
// after initial_work_delay_.
|
||||
time_t now = time(nullptr);
|
||||
if (now - last_start_time_ < prune_interval)
|
||||
return;
|
||||
last_start_time_ = now;
|
||||
|
||||
internal::ScopedBackgroundTask scoper("PruneThread");
|
||||
database_->CleanDatabase(60 * 60 * 24 * 3);
|
||||
|
||||
// Here and below, respect Stop() being called after each task.
|
||||
if (!thread_.is_running())
|
||||
return;
|
||||
PruneCrashReportDatabase(database_, condition_.get());
|
||||
|
||||
if (!thread_.is_running())
|
||||
return;
|
||||
if (!clean_old_intermediate_dumps_) {
|
||||
clean_old_intermediate_dumps_ = true;
|
||||
UnlockOldIntermediateDumps(pending_path_, bundle_identifier_and_seperator_);
|
||||
|
|
|
@ -84,6 +84,9 @@ class PruneIntermediateDumpsAndCrashReportsThread
|
|||
//! It is expected to only be called from the same thread that called Start().
|
||||
void Stop() override;
|
||||
|
||||
//! \return `true` if the thread is running, `false` if it is not.
|
||||
bool is_running() const { return thread_.is_running(); }
|
||||
|
||||
private:
|
||||
// WorkerThread::Delegate:
|
||||
void DoWork(const WorkerThread* thread) override;
|
||||
|
@ -94,6 +97,7 @@ class PruneIntermediateDumpsAndCrashReportsThread
|
|||
std::string bundle_identifier_and_seperator_;
|
||||
bool clean_old_intermediate_dumps_;
|
||||
double initial_work_delay_;
|
||||
time_t last_start_time_;
|
||||
CrashReportDatabase* database_; // weak
|
||||
};
|
||||
|
||||
|
|
|
@ -20,12 +20,13 @@
|
|||
|
||||
#include "base/logging.h"
|
||||
#include "base/posix/eintr_wrapper.h"
|
||||
#include "build/build_config.h"
|
||||
#include "util/file/filesystem.h"
|
||||
#include "util/numeric/in_range_cast.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
#if defined(OS_FUCHSIA)
|
||||
#if BUILDFLAG(IS_FUCHSIA)
|
||||
|
||||
Settings::ScopedLockedFileHandle::ScopedLockedFileHandle()
|
||||
: handle_(kInvalidFileHandle), lockfile_path_() {
|
||||
|
@ -68,7 +69,36 @@ void Settings::ScopedLockedFileHandle::Destroy() {
|
|||
}
|
||||
}
|
||||
|
||||
#else // OS_FUCHSIA
|
||||
#else // BUILDFLAG(IS_FUCHSIA)
|
||||
|
||||
#if BUILDFLAG(IS_IOS)
|
||||
|
||||
Settings::ScopedLockedFileHandle::ScopedLockedFileHandle(
|
||||
const FileHandle& value)
|
||||
: ScopedGeneric(value) {
|
||||
ios_background_task_ = std::make_unique<internal::ScopedBackgroundTask>(
|
||||
"ScopedLockedFileHandle");
|
||||
}
|
||||
|
||||
Settings::ScopedLockedFileHandle::ScopedLockedFileHandle(
|
||||
Settings::ScopedLockedFileHandle&& rvalue) {
|
||||
ios_background_task_.reset(rvalue.ios_background_task_.release());
|
||||
reset(rvalue.release());
|
||||
}
|
||||
|
||||
Settings::ScopedLockedFileHandle& Settings::ScopedLockedFileHandle::operator=(
|
||||
Settings::ScopedLockedFileHandle&& rvalue) {
|
||||
ios_background_task_.reset(rvalue.ios_background_task_.release());
|
||||
reset(rvalue.release());
|
||||
return *this;
|
||||
}
|
||||
|
||||
Settings::ScopedLockedFileHandle::~ScopedLockedFileHandle() {
|
||||
// Call reset() to ensure the lock is released before the ios_background_task.
|
||||
reset();
|
||||
}
|
||||
|
||||
#endif // BUILDFLAG(IS_IOS)
|
||||
|
||||
namespace internal {
|
||||
|
||||
|
@ -82,7 +112,7 @@ void ScopedLockedFileHandleTraits::Free(FileHandle handle) {
|
|||
|
||||
} // namespace internal
|
||||
|
||||
#endif // OS_FUCHSIA
|
||||
#endif // BUILDFLAG(IS_FUCHSIA)
|
||||
|
||||
struct Settings::Data {
|
||||
static constexpr uint32_t kSettingsMagic = 'CPds';
|
||||
|
@ -193,7 +223,7 @@ Settings::ScopedLockedFileHandle Settings::MakeScopedLockedFileHandle(
|
|||
FileLocking locking,
|
||||
const base::FilePath& file_path) {
|
||||
ScopedFileHandle scoped(file);
|
||||
#if defined(OS_FUCHSIA)
|
||||
#if BUILDFLAG(IS_FUCHSIA)
|
||||
base::FilePath lockfile_path(file_path.value() + ".__lock__");
|
||||
if (scoped.is_valid()) {
|
||||
ScopedFileHandle lockfile_scoped(
|
||||
|
@ -206,6 +236,10 @@ Settings::ScopedLockedFileHandle Settings::MakeScopedLockedFileHandle(
|
|||
}
|
||||
return ScopedLockedFileHandle(scoped.release(), base::FilePath());
|
||||
#else
|
||||
// It's important to create the ScopedLockedFileHandle before calling
|
||||
// LoggingLockFile on iOS, so a ScopedBackgroundTask is created *before*
|
||||
// the LoggingLockFile call below.
|
||||
ScopedLockedFileHandle handle(kInvalidFileHandle);
|
||||
if (scoped.is_valid()) {
|
||||
if (LoggingLockFile(
|
||||
scoped.get(), locking, FileLockingBlocking::kBlocking) !=
|
||||
|
@ -213,7 +247,8 @@ Settings::ScopedLockedFileHandle Settings::MakeScopedLockedFileHandle(
|
|||
scoped.reset();
|
||||
}
|
||||
}
|
||||
return ScopedLockedFileHandle(scoped.release());
|
||||
handle.reset(scoped.release());
|
||||
return handle;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
@ -17,8 +17,6 @@
|
|||
|
||||
#include <time.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/scoped_generic.h"
|
||||
#include "build/build_config.h"
|
||||
|
@ -26,6 +24,10 @@
|
|||
#include "util/misc/initialization_state.h"
|
||||
#include "util/misc/uuid.h"
|
||||
|
||||
#if BUILDFLAG(IS_IOS)
|
||||
#include "util/ios/scoped_background_task.h"
|
||||
#endif // BUILDFLAG(IS_IOS)
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
namespace internal {
|
||||
|
@ -125,7 +127,7 @@ class Settings {
|
|||
// and closes the file on destruction. Note that on Fuchsia, this handle DOES
|
||||
// NOT offer correct operation, only an attempt to DCHECK if racy behavior is
|
||||
// detected.
|
||||
#if defined(OS_FUCHSIA)
|
||||
#if BUILDFLAG(IS_FUCHSIA)
|
||||
struct ScopedLockedFileHandle {
|
||||
public:
|
||||
ScopedLockedFileHandle();
|
||||
|
@ -155,10 +157,29 @@ class Settings {
|
|||
FileHandle handle_;
|
||||
base::FilePath lockfile_path_;
|
||||
};
|
||||
#else // OS_FUCHSIA
|
||||
#elif BUILDFLAG(IS_IOS)
|
||||
// iOS needs to use ScopedBackgroundTask anytime a file lock is used.
|
||||
class ScopedLockedFileHandle
|
||||
: public base::ScopedGeneric<FileHandle,
|
||||
internal::ScopedLockedFileHandleTraits> {
|
||||
public:
|
||||
using base::ScopedGeneric<
|
||||
FileHandle,
|
||||
internal::ScopedLockedFileHandleTraits>::ScopedGeneric;
|
||||
|
||||
ScopedLockedFileHandle(const FileHandle& value);
|
||||
ScopedLockedFileHandle(ScopedLockedFileHandle&& rvalue);
|
||||
ScopedLockedFileHandle& operator=(ScopedLockedFileHandle&& rvalue);
|
||||
|
||||
~ScopedLockedFileHandle();
|
||||
|
||||
private:
|
||||
std::unique_ptr<internal::ScopedBackgroundTask> ios_background_task_;
|
||||
};
|
||||
#else
|
||||
using ScopedLockedFileHandle =
|
||||
base::ScopedGeneric<FileHandle, internal::ScopedLockedFileHandleTraits>;
|
||||
#endif // OS_FUCHSIA
|
||||
#endif // BUILDFLAG(IS_FUCHSIA)
|
||||
static ScopedLockedFileHandle MakeScopedLockedFileHandle(
|
||||
FileHandle file,
|
||||
FileLocking locking,
|
||||
|
|
|
@ -154,7 +154,7 @@ TEST_F(SettingsTest, UnlinkFile) {
|
|||
EXPECT_TRUE(settings()->SetUploadsEnabled(true));
|
||||
EXPECT_TRUE(settings()->SetLastUploadAttemptTime(time(nullptr)));
|
||||
|
||||
#if defined(OS_WIN)
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
EXPECT_EQ(_wunlink(settings_path().value().c_str()), 0)
|
||||
<< ErrnoMessage("_wunlink");
|
||||
#else
|
||||
|
|
|
@ -132,8 +132,10 @@ TEST(SimpleStringDictionary, Iterator) {
|
|||
|
||||
// Set a bunch of key/value pairs like key0/value0, key1/value1, ...
|
||||
for (int i = 0; i < kPartitionIndex; ++i) {
|
||||
sprintf(key, "key%d", i);
|
||||
sprintf(value, "value%d", i);
|
||||
ASSERT_LT(snprintf(key, sizeof(key), "key%d", i),
|
||||
static_cast<int>(sizeof(key)));
|
||||
ASSERT_LT(snprintf(value, sizeof(value), "value%d", i),
|
||||
static_cast<int>(sizeof(value)));
|
||||
dict.SetKeyValue(key, value);
|
||||
}
|
||||
expected_dictionary_size = kPartitionIndex;
|
||||
|
@ -152,8 +154,10 @@ TEST(SimpleStringDictionary, Iterator) {
|
|||
|
||||
// Set some more key/value pairs like key59/value59, key60/value60, ...
|
||||
for (int i = kPartitionIndex; i < kDictionaryCapacity; ++i) {
|
||||
sprintf(key, "key%d", i);
|
||||
sprintf(value, "value%d", i);
|
||||
ASSERT_LT(snprintf(key, sizeof(key), "key%d", i),
|
||||
static_cast<int>(sizeof(key)));
|
||||
ASSERT_LT(snprintf(value, sizeof(value), "value%d", i),
|
||||
static_cast<int>(sizeof(value)));
|
||||
dict.SetKeyValue(key, value);
|
||||
}
|
||||
expected_dictionary_size += kDictionaryCapacity - kPartitionIndex;
|
||||
|
|
|
@ -17,13 +17,13 @@
|
|||
|
||||
#include "build/build_config.h"
|
||||
|
||||
#if defined(OS_MAC)
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
#include "client/simulate_crash_mac.h"
|
||||
#elif defined(OS_IOS)
|
||||
#elif BUILDFLAG(IS_IOS)
|
||||
#include "client/simulate_crash_ios.h"
|
||||
#elif defined(OS_WIN)
|
||||
#elif BUILDFLAG(IS_WIN)
|
||||
#include "client/simulate_crash_win.h"
|
||||
#elif defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID)
|
||||
#elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
|
||||
#include "client/simulate_crash_linux.h"
|
||||
#endif
|
||||
|
||||
|
|
|
@ -17,8 +17,9 @@
|
|||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <iterator>
|
||||
|
||||
#include "base/check_op.h"
|
||||
#include "base/cxx17_backports.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/mac/mach_logging.h"
|
||||
#include "base/mac/scoped_mach_port.h"
|
||||
|
@ -207,7 +208,7 @@ void SimulateCrash(const NativeCPUContext& cpu_context) {
|
|||
base::mac::ScopedMachSendRight thread(mach_thread_self());
|
||||
exception_type_t exception = kMachExceptionSimulated;
|
||||
mach_exception_data_type_t codes[] = {0, 0};
|
||||
mach_msg_type_number_t code_count = base::size(codes);
|
||||
mach_msg_type_number_t code_count = std::size(codes);
|
||||
|
||||
// Look up the handler for EXC_CRASH exceptions in the same way that the
|
||||
// kernel would: try a thread handler, then a task handler, and finally a host
|
||||
|
@ -229,7 +230,7 @@ void SimulateCrash(const NativeCPUContext& cpu_context) {
|
|||
bool success = false;
|
||||
|
||||
for (size_t target_type_index = 0;
|
||||
!success && target_type_index < base::size(kTargetTypes);
|
||||
!success && target_type_index < std::size(kTargetTypes);
|
||||
++target_type_index) {
|
||||
ExceptionPorts::ExceptionHandlerVector handlers;
|
||||
ExceptionPorts exception_ports(kTargetTypes[target_type_index],
|
||||
|
|
|
@ -18,7 +18,8 @@
|
|||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "base/cxx17_backports.h"
|
||||
#include <iterator>
|
||||
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "build/build_config.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
@ -370,13 +371,13 @@ TEST(SimulateCrash, SimulateCrash) {
|
|||
#endif
|
||||
};
|
||||
|
||||
for (size_t target_index = 0; target_index < base::size(kTargets);
|
||||
for (size_t target_index = 0; target_index < std::size(kTargets);
|
||||
++target_index) {
|
||||
TestSimulateCrashMac::ExceptionPortsTarget target = kTargets[target_index];
|
||||
SCOPED_TRACE(base::StringPrintf(
|
||||
"target_index %zu, target %d", target_index, target));
|
||||
|
||||
for (size_t behavior_index = 0; behavior_index < base::size(kBehaviors);
|
||||
for (size_t behavior_index = 0; behavior_index < std::size(kBehaviors);
|
||||
++behavior_index) {
|
||||
exception_behavior_t behavior = kBehaviors[behavior_index];
|
||||
SCOPED_TRACE(base::StringPrintf(
|
||||
|
@ -390,7 +391,7 @@ TEST(SimulateCrash, SimulateCrash) {
|
|||
target, behavior, THREAD_STATE_NONE);
|
||||
test_simulate_crash_mac.Run();
|
||||
} else {
|
||||
for (size_t flavor_index = 0; flavor_index < base::size(kFlavors);
|
||||
for (size_t flavor_index = 0; flavor_index < std::size(kFlavors);
|
||||
++flavor_index) {
|
||||
thread_state_flavor_t flavor = kFlavors[flavor_index];
|
||||
SCOPED_TRACE(base::StringPrintf(
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
// Copyright 2019 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CRASHPAD_COMPAT_NON_ELF_ELF_H_
|
||||
#define CRASHPAD_COMPAT_NON_ELF_ELF_H_
|
||||
|
||||
#include "third_party/glibc/elf/elf.h"
|
||||
|
||||
#endif // CRASHPAD_COMPAT_NON_ELF_ELF_H_
|
|
@ -42,6 +42,15 @@
|
|||
//! \sa MINIDUMP_LOCATION_DESCRIPTOR
|
||||
typedef uint32_t RVA;
|
||||
|
||||
//! \brief A 64-bit offset within a minidump file, relative to the start of its
|
||||
//! MINIDUMP_HEADER.
|
||||
//!
|
||||
//! RVA stands for “relative virtual address”. Within a minidump file, RVAs are
|
||||
//! used as pointers to link structures together.
|
||||
//!
|
||||
//! \sa MINIDUMP_LOCATION_DESCRIPTOR64
|
||||
typedef uint64_t RVA64;
|
||||
|
||||
//! \brief A pointer to a structure or union within a minidump file.
|
||||
struct __attribute__((packed, aligned(4))) MINIDUMP_LOCATION_DESCRIPTOR {
|
||||
//! \brief The size of the referenced structure or union, in bytes.
|
||||
|
@ -52,6 +61,16 @@ struct __attribute__((packed, aligned(4))) MINIDUMP_LOCATION_DESCRIPTOR {
|
|||
RVA Rva;
|
||||
};
|
||||
|
||||
//! \brief A 64-bit pointer to a structure or union within a minidump file.
|
||||
struct __attribute__((packed, aligned(4))) MINIDUMP_LOCATION_DESCRIPTOR64 {
|
||||
//! \brief The size of the referenced structure or union, in bytes.
|
||||
uint64_t DataSize;
|
||||
|
||||
//! \brief The 64-bit relative virtual address of the structure or union
|
||||
//! within the minidump file.
|
||||
RVA64 Rva;
|
||||
};
|
||||
|
||||
//! \brief A pointer to a snapshot of a region of memory contained within a
|
||||
//! minidump file.
|
||||
//!
|
||||
|
@ -176,6 +195,9 @@ enum MINIDUMP_STREAM_TYPE {
|
|||
//! \brief The stream type for MINIDUMP_MEMORY_INFO_LIST.
|
||||
MemoryInfoListStream = 16,
|
||||
|
||||
//! \brief The stream type for MINIDUMP_THREAD_NAME_LIST.
|
||||
ThreadNamesStream = 24,
|
||||
|
||||
//! \brief Values greater than this value will not be used by the system
|
||||
//! and can be used for custom user data streams.
|
||||
LastReservedStream = 0xffff,
|
||||
|
@ -1080,6 +1102,27 @@ struct __attribute__((packed, aligned(4))) MINIDUMP_MEMORY_INFO_LIST {
|
|||
uint64_t NumberOfEntries;
|
||||
};
|
||||
|
||||
//! \brief Contains the name of the thread with the given thread ID.
|
||||
struct __attribute__((packed, aligned(4))) MINIDUMP_THREAD_NAME {
|
||||
//! \brief The identifier of the thread.
|
||||
uint32_t ThreadId;
|
||||
|
||||
//! \brief RVA64 of a MINIDUMP_STRING containing the name of the thread.
|
||||
RVA64 RvaOfThreadName;
|
||||
};
|
||||
|
||||
//! \brief Variable-sized struct which contains a list of MINIDUMP_THREAD_NAME
|
||||
//! structs.
|
||||
struct __attribute__((packed, aligned(4))) MINIDUMP_THREAD_NAME_LIST {
|
||||
//! \brief The number of MINIDUMP_THREAD_NAME structs following this field.
|
||||
uint32_t NumberOfThreadNames;
|
||||
|
||||
//! \brief A variably-sized array containing zero or more
|
||||
//! MINIDUMP_THREAD_NAME structs. The length of the array is indicated by
|
||||
//! the NumberOfThreadNames field in this struct.
|
||||
struct MINIDUMP_THREAD_NAME ThreadNames[0];
|
||||
};
|
||||
|
||||
//! \brief Minidump file type values for MINIDUMP_HEADER::Flags. These bits
|
||||
//! describe the types of data carried within a minidump file.
|
||||
enum MINIDUMP_TYPE {
|
||||
|
@ -1100,6 +1143,11 @@ enum MINIDUMP_TYPE {
|
|||
//! MINIDUMP_MEMORY_DESCRIPTOR containing the 256 bytes centered around
|
||||
//! the exception address or the instruction pointer.
|
||||
MiniDumpNormal = 0x00000000,
|
||||
|
||||
//! \brief A minidump with extended contexts.
|
||||
//!
|
||||
//! Contains Normal plus a MISC_INFO_5 structure describing the contexts.
|
||||
MiniDumpWithAvxXStateContext = 0x00200000,
|
||||
};
|
||||
|
||||
#endif // CRASHPAD_COMPAT_NON_WIN_DBGHELP_H_
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue