Update sentry sdk

This commit is contained in:
Marek Roszko 2022-08-15 20:48:53 -04:00
parent 2ee6f67892
commit 96446cba1d
550 changed files with 12924 additions and 8971 deletions

View File

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

View File

@ -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()
@ -575,4 +580,4 @@ if(SENTRY_BUILD_EXAMPLES)
endif()
add_test(NAME sentry_example COMMAND sentry_example)
endif()
endif()

View File

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

View File

@ -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
@ -264,13 +267,17 @@ Legend:
- `SENTRY_FOLDER` (Default: not defined):
Sets the sentry-native projects folder name for generators which support project hierarchy (like Microsoft Visual Studio).
To use this feature you need to enable hierarchy via [`USE_FOLDERS` property](https://cmake.org/cmake/help/latest/prop_gbl/USE_FOLDERS.html)
To use this feature you need to enable hierarchy via [`USE_FOLDERS` property](https://cmake.org/cmake/help/latest/prop_gbl/USE_FOLDERS.html)
- `CRASHPAD_ENABLE_STACKTRACE` (Default: OFF):
This enables client-side stackwalking when using the crashpad backend. Stack unwinding will happen on the client's machine
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.

View File

@ -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();

View File

@ -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}>"
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -63,6 +63,8 @@
#endif
using namespace google_breakpad;
using google_breakpad::elf::FileID;
using google_breakpad::elf::kDefaultBuildIdSize;
namespace {

View File

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

View File

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

View 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

View 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_

View 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_

View File

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

View File

@ -47,6 +47,7 @@
namespace {
using namespace google_breakpad;
using namespace google_breakpad::elf::FileID;
// Argument for the writer function.
struct WriterArgument {

View File

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

View File

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

View File

@ -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);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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&);

View File

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

View 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

View 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_

View File

@ -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");

View File

@ -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);
};

View File

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

View File

@ -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();

View File

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

View File

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

View File

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

View File

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

View File

@ -13,3 +13,4 @@
# limitations under the License.
buildconfig = "//build/BUILDCONFIG.gn"
script_executable = "python3"

View File

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

View File

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

View File

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

View File

@ -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'
],
},

View File

@ -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:]))

View File

@ -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:]))

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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();

View File

@ -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();

View File

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

View File

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

View File

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

View File

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

View File

@ -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 doesnt 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:
// Its 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.
// Its 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 Crashpads.
// 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 isnt 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 didnt 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

View File

@ -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);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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_;
};

View File

@ -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() + "@";
}

View File

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

View File

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

View File

@ -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_);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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