update sentry-native to 0.6.0
primarily interested in windows bug fix to stack unwinding
(cherry picked from commit 041b7b196d
)
This commit is contained in:
parent
f13fda9be5
commit
89d22e5f94
|
@ -1,5 +1,36 @@
|
|||
# Changelog
|
||||
|
||||
## 0.6.0
|
||||
|
||||
**Breaking changes**:
|
||||
|
||||
- When built as a shared library for Android or Linux, the Native SDK limits the export of symbols to the `sentry_`-prefix. The option `SENTRY_EXPORT_SYMBOLS` is no longer available and the linker settings are constrained to the Native SDK and no longer `PUBLIC` to parent projects. ([#363](https://github.com/getsentry/sentry-native/pull/363))
|
||||
|
||||
**Features**:
|
||||
|
||||
- A session may be ended with a different status code. ([#801](https://github.com/getsentry/sentry-native/pull/801))
|
||||
|
||||
**Fixes**:
|
||||
|
||||
- Switch Crashpad transport on Linux to use libcurl ([#803](https://github.com/getsentry/sentry-native/pull/803), [crashpad#75](https://github.com/getsentry/crashpad/pull/75), [crashpad#79](https://github.com/getsentry/crashpad/pull/79))
|
||||
- Avoid accidentally mutating CONTEXT when client-side stack walking in Crashpad ([#803](https://github.com/getsentry/sentry-native/pull/803), [crashpad#77](https://github.com/getsentry/crashpad/pull/77))
|
||||
- Fix various mingw compilation issues ([#794](https://github.com/getsentry/sentry-native/pull/794), [crashpad#78](https://github.com/getsentry/crashpad/pull/78))
|
||||
|
||||
**Internal**:
|
||||
|
||||
- Updated Crashpad backend to 2023-02-07. ([#803](https://github.com/getsentry/sentry-native/pull/803), [crashpad#80](https://github.com/getsentry/crashpad/pull/80))
|
||||
- CI: Updated GitHub Actions to test on LLVM-mingw. ([#797](https://github.com/getsentry/sentry-native/pull/797))
|
||||
- Updated Breakpad backend to 2023-02-08. ([#805](https://github.com/getsentry/sentry-native/pull/805), [breakpad#34](https://github.com/getsentry/breakpad/pull/34))
|
||||
- Updated libunwindstack to 2023-02-09. ([#807](https://github.com/getsentry/sentry-native/pull/807), [libunwindstack-ndk#7](https://github.com/getsentry/libunwindstack-ndk/pull/7))
|
||||
|
||||
**Thank you**:
|
||||
|
||||
Features, fixes and improvements in this release have been contributed by:
|
||||
|
||||
- [@BogdanLivadariu](https://github.com/BogdanLivadariu)
|
||||
- [@ShawnCZek](https://github.com/ShawnCZek)
|
||||
- [@past-due](https://github.com/past-due)
|
||||
|
||||
## 0.5.4
|
||||
|
||||
**Fixes**:
|
||||
|
|
|
@ -334,16 +334,6 @@ target_include_directories(sentry
|
|||
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/src>"
|
||||
)
|
||||
|
||||
# The modulefinder and symbolizer need these two settings, and they are exported
|
||||
# as `PUBLIC`, so libraries that depend on sentry get these too:
|
||||
# `-E`: To have all symbols in the dynamic symbol table.
|
||||
# `--build-id`: To have a build-id in the ELF object.
|
||||
# FIXME: cmake 3.13 introduced target_link_options
|
||||
option(SENTRY_EXPORT_SYMBOLS "Export symbols for modulefinder and symbolizer" ON)
|
||||
if(SENTRY_EXPORT_SYMBOLS)
|
||||
target_link_libraries(sentry PUBLIC
|
||||
"$<$<OR:$<PLATFORM_ID:Linux>,$<PLATFORM_ID:Android>>:-Wl,-E,--build-id=sha1>")
|
||||
endif()
|
||||
|
||||
#respect CMAKE_SYSTEM_VERSION
|
||||
if(WIN32)
|
||||
|
@ -601,3 +591,16 @@ if(SENTRY_BUILD_EXAMPLES)
|
|||
|
||||
add_test(NAME sentry_example COMMAND sentry_example)
|
||||
endif()
|
||||
|
||||
# Limit the exported symbols when sentry is built as a shared library to those with a "sentry_" prefix:
|
||||
# - we do this at the end of the file as to not affect subdirectories reading target_link_libraries from the parent.
|
||||
# - we do this as PRIVATE since our version script does not make sense in any other project that adds us.
|
||||
#
|
||||
# Used linker parameters:
|
||||
# `--build-id`: To have a build-id in the ELF object.
|
||||
# `--version-script`: version script either hides "foreign" symbols or defers them as unknown ("U") to system libraries.
|
||||
# FIXME: cmake 3.13 introduced target_link_options (blocked by Android)
|
||||
if(SENTRY_BUILD_SHARED_LIBS)
|
||||
target_link_libraries(sentry PRIVATE
|
||||
"$<$<OR:$<PLATFORM_ID:Linux>,$<PLATFORM_ID:Android>>:-Wl,--build-id=sha1,--version-script=${PROJECT_SOURCE_DIR}/src/exports.map>")
|
||||
endif()
|
|
@ -20,6 +20,10 @@ set(BREAKPAD_SOURCES_COMMON_LINUX
|
|||
breakpad/src/common/linux/linux_libc_support.cc
|
||||
breakpad/src/common/linux/memory_mapped_file.cc
|
||||
breakpad/src/common/linux/safe_readlink.cc
|
||||
breakpad/src/common/linux/scoped_pipe.cc
|
||||
breakpad/src/common/linux/scoped_pipe.h
|
||||
breakpad/src/common/linux/scoped_tmpfile.cc
|
||||
breakpad/src/common/linux/scoped_tmpfile.h
|
||||
)
|
||||
|
||||
set(BREAKPAD_SOURCES_COMMON_LINUX_GETCONTEXT
|
||||
|
@ -119,6 +123,8 @@ set(BREAKPAD_SOURCES_CLIENT_IOS
|
|||
)
|
||||
|
||||
add_library(breakpad_client STATIC)
|
||||
set_property(TARGET breakpad_client PROPERTY CXX_STANDARD 17)
|
||||
set_property(TARGET breakpad_client PROPERTY CXX_STANDARD_REQUIRED On)
|
||||
target_sources(breakpad_client PRIVATE ${BREAKPAD_SOURCES_COMMON})
|
||||
|
||||
if(LINUX OR ANDROID)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -151,7 +151,6 @@ CLEANFILES =
|
|||
#
|
||||
if !SYSTEM_TEST_LIBS
|
||||
check_LIBRARIES += src/testing/libtesting.a
|
||||
endif
|
||||
src_testing_libtesting_a_SOURCES = \
|
||||
src/breakpad_googletest_includes.h \
|
||||
src/testing/googletest/src/gtest-all.cc \
|
||||
|
@ -159,6 +158,7 @@ src_testing_libtesting_a_SOURCES = \
|
|||
src/testing/googlemock/src/gmock-all.cc
|
||||
src_testing_libtesting_a_CPPFLAGS = \
|
||||
$(AM_CPPFLAGS) $(TEST_CFLAGS)
|
||||
endif
|
||||
|
||||
#
|
||||
# General
|
||||
|
@ -193,7 +193,6 @@ check_PROGRAMS += \
|
|||
src/processor/basic_source_line_resolver_unittest \
|
||||
src/processor/cfi_frame_info_unittest \
|
||||
src/processor/contained_range_map_unittest \
|
||||
src/processor/disassembler_objdump_unittest \
|
||||
src/processor/disassembler_x86_unittest \
|
||||
src/processor/exploitability_unittest \
|
||||
src/processor/fast_source_line_resolver_unittest \
|
||||
|
@ -221,6 +220,12 @@ check_PROGRAMS += \
|
|||
src/processor/stackwalker_riscv64_unittest \
|
||||
src/processor/stackwalker_x86_unittest \
|
||||
src/processor/synth_minidump_unittest
|
||||
if LINUX_HOST
|
||||
check_PROGRAMS += \
|
||||
src/processor/disassembler_objdump_unittest \
|
||||
src/common/linux/scoped_pipe_unittest \
|
||||
src/common/linux/scoped_tmpfile_unittest
|
||||
endif LINUX_HOST
|
||||
if SELFTEST
|
||||
check_PROGRAMS += \
|
||||
src/processor/stackwalker_selftest
|
||||
|
@ -366,8 +371,6 @@ src_libbreakpad_a_SOURCES = \
|
|||
src/processor/contained_range_map.h \
|
||||
src/processor/convert_old_arm64_context.cc \
|
||||
src/processor/convert_old_arm64_context.h \
|
||||
src/processor/disassembler_objdump.h \
|
||||
src/processor/disassembler_objdump.cc \
|
||||
src/processor/disassembler_x86.h \
|
||||
src/processor/disassembler_x86.cc \
|
||||
src/processor/dump_context.cc \
|
||||
|
@ -449,6 +452,15 @@ src_libbreakpad_a_SOURCES = \
|
|||
src/processor/symbolic_constants_win.h \
|
||||
src/processor/tokenize.cc \
|
||||
src/processor/tokenize.h
|
||||
if LINUX_HOST
|
||||
src_libbreakpad_a_SOURCES += \
|
||||
src/common/linux/scoped_pipe.h \
|
||||
src/common/linux/scoped_pipe.cc \
|
||||
src/common/linux/scoped_tmpfile.h \
|
||||
src/common/linux/scoped_tmpfile.cc \
|
||||
src/processor/disassembler_objdump.h \
|
||||
src/processor/disassembler_objdump.cc
|
||||
endif
|
||||
|
||||
# libdisasm 3rd party library
|
||||
src_third_party_libdisasm_libdisasm_a_SOURCES = \
|
||||
|
@ -523,7 +535,6 @@ src_client_linux_libbreakpad_client_a_SOURCES += \
|
|||
endif
|
||||
|
||||
# Client tests
|
||||
|
||||
src_client_linux_linux_dumper_unittest_helper_SOURCES = \
|
||||
src/client/linux/minidump_writer/linux_dumper_unittest_helper.cc
|
||||
src_client_linux_linux_dumper_unittest_helper_LDFLAGS=$(PTHREAD_CFLAGS)
|
||||
|
@ -552,7 +563,10 @@ src_client_linux_linux_client_unittest_shlib_SOURCES = \
|
|||
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 \
|
||||
src/common/linux/tests/auto_testfile.h \
|
||||
src/common/linux/scoped_pipe.h \
|
||||
src/common/linux/scoped_pipe.cc \
|
||||
src/common/linux/scoped_tmpfile.h \
|
||||
src/common/linux/scoped_tmpfile.cc \
|
||||
src/common/linux/tests/crash_generator.cc \
|
||||
src/common/memory_allocator_unittest.cc \
|
||||
src/common/tests/auto_tempdir.h \
|
||||
|
@ -911,7 +925,6 @@ src_processor_exploitability_unittest_LDADD = \
|
|||
src/processor/convert_old_arm64_context.o \
|
||||
src/processor/minidump_processor.o \
|
||||
src/processor/process_state.o \
|
||||
src/processor/disassembler_objdump.o \
|
||||
src/processor/disassembler_x86.o \
|
||||
src/processor/exploitability.o \
|
||||
src/processor/exploitability_linux.o \
|
||||
|
@ -947,12 +960,38 @@ src_processor_exploitability_unittest_LDADD = \
|
|||
src/third_party/libdisasm/libdisasm.a \
|
||||
$(TEST_LIBS) \
|
||||
$(PTHREAD_CFLAGS) $(PTHREAD_LIBS)
|
||||
if LINUX_HOST
|
||||
src_processor_exploitability_unittest_LDADD += \
|
||||
src/common/linux/scoped_pipe.o \
|
||||
src/common/linux/scoped_tmpfile.o \
|
||||
src/processor/disassembler_objdump.o
|
||||
endif
|
||||
|
||||
src_common_linux_scoped_pipe_unittest_SOURCES = \
|
||||
src/common/linux/scoped_pipe_unittest.cc
|
||||
src_common_linux_scoped_pipe_unittest_CPPFLAGS = \
|
||||
$(AM_CPPFLAGS) $(TEST_CFLAGS)
|
||||
src_common_linux_scoped_pipe_unittest_LDADD = \
|
||||
src/common/linux/scoped_pipe.o \
|
||||
$(TEST_LIBS) \
|
||||
$(PTHREAD_CFLAGS) $(PTHREAD_LIBS)
|
||||
|
||||
src_common_linux_scoped_tmpfile_unittest_SOURCES = \
|
||||
src/common/linux/scoped_tmpfile_unittest.cc
|
||||
src_common_linux_scoped_tmpfile_unittest_CPPFLAGS = \
|
||||
$(AM_CPPFLAGS) $(TEST_CFLAGS)
|
||||
src_common_linux_scoped_tmpfile_unittest_LDADD = \
|
||||
src/common/linux/scoped_tmpfile.o \
|
||||
$(TEST_LIBS) \
|
||||
$(PTHREAD_CFLAGS) $(PTHREAD_LIBS)
|
||||
|
||||
src_processor_disassembler_objdump_unittest_SOURCES = \
|
||||
src/processor/disassembler_objdump_unittest.cc
|
||||
src_processor_disassembler_objdump_unittest_CPPFLAGS = \
|
||||
$(AM_CPPFLAGS) $(TEST_CFLAGS)
|
||||
src_processor_disassembler_objdump_unittest_LDADD = \
|
||||
src/common/linux/scoped_pipe.o \
|
||||
src/common/linux/scoped_tmpfile.o \
|
||||
src/processor/disassembler_objdump.o \
|
||||
src/processor/dump_context.o \
|
||||
src/processor/dump_object.o \
|
||||
|
@ -1033,6 +1072,12 @@ src_processor_microdump_processor_unittest_LDADD = \
|
|||
src/processor/tokenize.o \
|
||||
$(TEST_LIBS) \
|
||||
$(PTHREAD_CFLAGS) $(PTHREAD_LIBS)
|
||||
if LINUX_HOST
|
||||
src_processor_microdump_processor_unittest_LDADD += \
|
||||
src/common/linux/scoped_pipe.o \
|
||||
src/common/linux/scoped_tmpfile.o \
|
||||
src/processor/disassembler_objdump.o
|
||||
endif
|
||||
|
||||
src_processor_minidump_processor_unittest_SOURCES = \
|
||||
src/processor/minidump_processor_unittest.cc
|
||||
|
@ -1044,7 +1089,6 @@ src_processor_minidump_processor_unittest_LDADD = \
|
|||
src/processor/call_stack.o \
|
||||
src/processor/cfi_frame_info.o \
|
||||
src/processor/convert_old_arm64_context.o \
|
||||
src/processor/disassembler_objdump.o \
|
||||
src/processor/disassembler_x86.o \
|
||||
src/processor/dump_context.o \
|
||||
src/processor/dump_object.o \
|
||||
|
@ -1077,6 +1121,12 @@ src_processor_minidump_processor_unittest_LDADD = \
|
|||
src/third_party/libdisasm/libdisasm.a \
|
||||
$(TEST_LIBS) \
|
||||
$(PTHREAD_CFLAGS) $(PTHREAD_LIBS)
|
||||
if LINUX_HOST
|
||||
src_processor_minidump_processor_unittest_LDADD += \
|
||||
src/common/linux/scoped_pipe.o \
|
||||
src/common/linux/scoped_tmpfile.o \
|
||||
src/processor/disassembler_objdump.o
|
||||
endif
|
||||
|
||||
src_processor_minidump_unittest_SOURCES = \
|
||||
src/common/test_assembler.cc \
|
||||
|
@ -1194,7 +1244,6 @@ src_processor_stackwalker_selftest_LDADD = \
|
|||
src/processor/basic_code_modules.o \
|
||||
src/processor/basic_source_line_resolver.o \
|
||||
src/processor/call_stack.o \
|
||||
src/processor/disassembler_objdump.o \
|
||||
src/processor/disassembler_x86.o \
|
||||
src/processor/exploitability.o \
|
||||
src/processor/exploitability_linux.o \
|
||||
|
@ -1220,6 +1269,12 @@ src_processor_stackwalker_selftest_LDADD = \
|
|||
src/processor/stackwalker_x86.o \
|
||||
src/processor/tokenize.o \
|
||||
$(PTHREAD_CFLAGS) $(PTHREAD_LIBS)
|
||||
if LINUX_HOST
|
||||
src_processor_stackwalker_selftest_LDADD += \
|
||||
src/common/linux/scoped_pipe.o \
|
||||
src/common/linux/scoped_tmpfile.o \
|
||||
src/processor/disassembler_objdump.o
|
||||
endif
|
||||
|
||||
src_processor_stackwalker_amd64_unittest_SOURCES = \
|
||||
src/common/test_assembler.cc \
|
||||
|
@ -1406,6 +1461,12 @@ src_processor_microdump_stackwalk_LDADD = \
|
|||
src/processor/stackwalker_x86.o \
|
||||
src/processor/tokenize.o \
|
||||
src/third_party/libdisasm/libdisasm.a
|
||||
if LINUX_HOST
|
||||
src_processor_microdump_stackwalk_LDADD += \
|
||||
src/common/linux/scoped_pipe.o \
|
||||
src/common/linux/scoped_tmpfile.o \
|
||||
src/processor/disassembler_objdump.o
|
||||
endif
|
||||
|
||||
src_processor_minidump_stackwalk_SOURCES = \
|
||||
src/processor/minidump_stackwalk.cc
|
||||
|
@ -1416,7 +1477,6 @@ src_processor_minidump_stackwalk_LDADD = \
|
|||
src/processor/call_stack.o \
|
||||
src/processor/cfi_frame_info.o \
|
||||
src/processor/convert_old_arm64_context.o \
|
||||
src/processor/disassembler_objdump.o \
|
||||
src/processor/disassembler_x86.o \
|
||||
src/processor/dump_context.o \
|
||||
src/processor/dump_object.o \
|
||||
|
@ -1449,6 +1509,12 @@ src_processor_minidump_stackwalk_LDADD = \
|
|||
src/processor/symbolic_constants_win.o \
|
||||
src/processor/tokenize.o \
|
||||
src/third_party/libdisasm/libdisasm.a
|
||||
if LINUX_HOST
|
||||
src_processor_minidump_stackwalk_LDADD += \
|
||||
src/common/linux/scoped_pipe.o \
|
||||
src/common/linux/scoped_tmpfile.o \
|
||||
src/processor/disassembler_objdump.o
|
||||
endif LINUX_HOST
|
||||
|
||||
## Additional files to be included in a source distribution
|
||||
##
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -75,7 +75,7 @@ AC_CHECK_FUNCS([arc4random getcontext getrandom memfd_create])
|
|||
AM_CONDITIONAL([HAVE_GETCONTEXT], [test "x$ac_cv_func_getcontext" = xyes])
|
||||
AM_CONDITIONAL([HAVE_MEMFD_CREATE], [test "x$ac_cv_func_memfd_create" = xyes])
|
||||
|
||||
AX_CXX_COMPILE_STDCXX(17, noext, mandatory)
|
||||
AX_CXX_COMPILE_STDCXX(17, , mandatory)
|
||||
|
||||
dnl Test supported warning flags.
|
||||
WARN_CXXFLAGS=
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
|
||||
#include "breakpad_googletest_includes.h"
|
||||
#include "client/linux/minidump_writer/cpu_set.h"
|
||||
#include "common/linux/tests/auto_testfile.h"
|
||||
#include "common/linux/scoped_tmpfile.h"
|
||||
|
||||
using namespace google_breakpad;
|
||||
|
||||
|
@ -43,15 +43,6 @@ namespace {
|
|||
|
||||
typedef testing::Test CpuSetTest;
|
||||
|
||||
// Helper class to write test text file to a temporary file and return
|
||||
// its file descriptor.
|
||||
class ScopedTestFile : public AutoTestFile {
|
||||
public:
|
||||
explicit ScopedTestFile(const char* text)
|
||||
: AutoTestFile("cpu_set", text) {
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
TEST(CpuSetTest, EmptyCount) {
|
||||
|
@ -60,8 +51,8 @@ TEST(CpuSetTest, EmptyCount) {
|
|||
}
|
||||
|
||||
TEST(CpuSetTest, OneCpu) {
|
||||
ScopedTestFile file("10");
|
||||
ASSERT_TRUE(file.IsOk());
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("10"));
|
||||
|
||||
CpuSet set;
|
||||
ASSERT_TRUE(set.ParseSysFile(file.GetFd()));
|
||||
|
@ -69,8 +60,8 @@ TEST(CpuSetTest, OneCpu) {
|
|||
}
|
||||
|
||||
TEST(CpuSetTest, OneCpuTerminated) {
|
||||
ScopedTestFile file("10\n");
|
||||
ASSERT_TRUE(file.IsOk());
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("10\n"));
|
||||
|
||||
CpuSet set;
|
||||
ASSERT_TRUE(set.ParseSysFile(file.GetFd()));
|
||||
|
@ -78,8 +69,8 @@ TEST(CpuSetTest, OneCpuTerminated) {
|
|||
}
|
||||
|
||||
TEST(CpuSetTest, TwoCpusWithComma) {
|
||||
ScopedTestFile file("1,10");
|
||||
ASSERT_TRUE(file.IsOk());
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("1,10"));
|
||||
|
||||
CpuSet set;
|
||||
ASSERT_TRUE(set.ParseSysFile(file.GetFd()));
|
||||
|
@ -87,8 +78,8 @@ TEST(CpuSetTest, TwoCpusWithComma) {
|
|||
}
|
||||
|
||||
TEST(CpuSetTest, TwoCpusWithRange) {
|
||||
ScopedTestFile file("1-2");
|
||||
ASSERT_TRUE(file.IsOk());
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("1-2"));
|
||||
|
||||
CpuSet set;
|
||||
ASSERT_TRUE(set.ParseSysFile(file.GetFd()));
|
||||
|
@ -96,8 +87,8 @@ TEST(CpuSetTest, TwoCpusWithRange) {
|
|||
}
|
||||
|
||||
TEST(CpuSetTest, TenCpusWithRange) {
|
||||
ScopedTestFile file("9-18");
|
||||
ASSERT_TRUE(file.IsOk());
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("9-18"));
|
||||
|
||||
CpuSet set;
|
||||
ASSERT_TRUE(set.ParseSysFile(file.GetFd()));
|
||||
|
@ -105,8 +96,8 @@ TEST(CpuSetTest, TenCpusWithRange) {
|
|||
}
|
||||
|
||||
TEST(CpuSetTest, MultiItems) {
|
||||
ScopedTestFile file("0, 2-4, 128");
|
||||
ASSERT_TRUE(file.IsOk());
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("0, 2-4, 128"));
|
||||
|
||||
CpuSet set;
|
||||
ASSERT_TRUE(set.ParseSysFile(file.GetFd()));
|
||||
|
@ -114,14 +105,16 @@ TEST(CpuSetTest, MultiItems) {
|
|||
}
|
||||
|
||||
TEST(CpuSetTest, IntersectWith) {
|
||||
ScopedTestFile file1("9-19");
|
||||
ASSERT_TRUE(file1.IsOk());
|
||||
ScopedTmpFile file1;
|
||||
ASSERT_TRUE(file1.InitString("9-19"));
|
||||
|
||||
CpuSet set1;
|
||||
ASSERT_TRUE(set1.ParseSysFile(file1.GetFd()));
|
||||
ASSERT_EQ(11, set1.GetCount());
|
||||
|
||||
ScopedTestFile file2("16-24");
|
||||
ASSERT_TRUE(file2.IsOk());
|
||||
ScopedTmpFile file2;
|
||||
ASSERT_TRUE(file2.InitString("16-24"));
|
||||
|
||||
CpuSet set2;
|
||||
ASSERT_TRUE(set2.ParseSysFile(file2.GetFd()));
|
||||
ASSERT_EQ(9, set2.GetCount());
|
||||
|
@ -132,8 +125,9 @@ TEST(CpuSetTest, IntersectWith) {
|
|||
}
|
||||
|
||||
TEST(CpuSetTest, SelfIntersection) {
|
||||
ScopedTestFile file1("9-19");
|
||||
ASSERT_TRUE(file1.IsOk());
|
||||
ScopedTmpFile file1;
|
||||
ASSERT_TRUE(file1.InitString("9-19"));
|
||||
|
||||
CpuSet set1;
|
||||
ASSERT_TRUE(set1.ParseSysFile(file1.GetFd()));
|
||||
ASSERT_EQ(11, set1.GetCount());
|
||||
|
@ -143,14 +137,16 @@ TEST(CpuSetTest, SelfIntersection) {
|
|||
}
|
||||
|
||||
TEST(CpuSetTest, EmptyIntersection) {
|
||||
ScopedTestFile file1("0-19");
|
||||
ASSERT_TRUE(file1.IsOk());
|
||||
ScopedTmpFile file1;
|
||||
ASSERT_TRUE(file1.InitString("0-19"));
|
||||
|
||||
CpuSet set1;
|
||||
ASSERT_TRUE(set1.ParseSysFile(file1.GetFd()));
|
||||
ASSERT_EQ(20, set1.GetCount());
|
||||
|
||||
ScopedTestFile file2("20-39");
|
||||
ASSERT_TRUE(file2.IsOk());
|
||||
ScopedTmpFile file2;
|
||||
ASSERT_TRUE(file2.InitString("20-39"));
|
||||
|
||||
CpuSet set2;
|
||||
ASSERT_TRUE(set2.ParseSysFile(file2.GetFd()));
|
||||
ASSERT_EQ(20, set2.GetCount());
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
|
||||
#include "client/linux/minidump_writer/line_reader.h"
|
||||
#include "breakpad_googletest_includes.h"
|
||||
#include "common/linux/tests/auto_testfile.h"
|
||||
#include "common/linux/scoped_tmpfile.h"
|
||||
|
||||
using namespace google_breakpad;
|
||||
|
||||
|
@ -40,22 +40,11 @@ namespace {
|
|||
|
||||
typedef testing::Test LineReaderTest;
|
||||
|
||||
class ScopedTestFile : public AutoTestFile {
|
||||
public:
|
||||
explicit ScopedTestFile(const char* text)
|
||||
: AutoTestFile("line_reader", text) {
|
||||
}
|
||||
|
||||
ScopedTestFile(const char* text, size_t text_len)
|
||||
: AutoTestFile("line_reader", text, text_len) {
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
TEST(LineReaderTest, EmptyFile) {
|
||||
ScopedTestFile file("");
|
||||
ASSERT_TRUE(file.IsOk());
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString(""));
|
||||
LineReader reader(file.GetFd());
|
||||
|
||||
const char* line;
|
||||
|
@ -64,8 +53,8 @@ TEST(LineReaderTest, EmptyFile) {
|
|||
}
|
||||
|
||||
TEST(LineReaderTest, OneLineTerminated) {
|
||||
ScopedTestFile file("a\n");
|
||||
ASSERT_TRUE(file.IsOk());
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("a\n"));
|
||||
LineReader reader(file.GetFd());
|
||||
|
||||
const char* line;
|
||||
|
@ -80,8 +69,8 @@ TEST(LineReaderTest, OneLineTerminated) {
|
|||
}
|
||||
|
||||
TEST(LineReaderTest, OneLine) {
|
||||
ScopedTestFile file("a");
|
||||
ASSERT_TRUE(file.IsOk());
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("a"));
|
||||
LineReader reader(file.GetFd());
|
||||
|
||||
const char* line;
|
||||
|
@ -96,8 +85,8 @@ TEST(LineReaderTest, OneLine) {
|
|||
}
|
||||
|
||||
TEST(LineReaderTest, TwoLinesTerminated) {
|
||||
ScopedTestFile file("a\nb\n");
|
||||
ASSERT_TRUE(file.IsOk());
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("a\nb\n"));
|
||||
LineReader reader(file.GetFd());
|
||||
|
||||
const char* line;
|
||||
|
@ -118,8 +107,8 @@ TEST(LineReaderTest, TwoLinesTerminated) {
|
|||
}
|
||||
|
||||
TEST(LineReaderTest, TwoLines) {
|
||||
ScopedTestFile file("a\nb");
|
||||
ASSERT_TRUE(file.IsOk());
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("a\nb"));
|
||||
LineReader reader(file.GetFd());
|
||||
|
||||
const char* line;
|
||||
|
@ -142,8 +131,8 @@ TEST(LineReaderTest, TwoLines) {
|
|||
TEST(LineReaderTest, MaxLength) {
|
||||
char l[LineReader::kMaxLineLen-1];
|
||||
memset(l, 'a', sizeof(l));
|
||||
ScopedTestFile file(l, sizeof(l));
|
||||
ASSERT_TRUE(file.IsOk());
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitData(l, sizeof(l)));
|
||||
LineReader reader(file.GetFd());
|
||||
|
||||
const char* line;
|
||||
|
@ -158,8 +147,8 @@ TEST(LineReaderTest, TooLong) {
|
|||
// Note: this writes kMaxLineLen 'a' chars in the test file.
|
||||
char l[LineReader::kMaxLineLen];
|
||||
memset(l, 'a', sizeof(l));
|
||||
ScopedTestFile file(l, sizeof(l));
|
||||
ASSERT_TRUE(file.IsOk());
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitData(l, sizeof(l)));
|
||||
LineReader reader(file.GetFd());
|
||||
|
||||
const char* line;
|
||||
|
|
|
@ -35,33 +35,19 @@
|
|||
|
||||
#include "client/linux/minidump_writer/proc_cpuinfo_reader.h"
|
||||
#include "breakpad_googletest_includes.h"
|
||||
#include "common/linux/tests/auto_testfile.h"
|
||||
#include "common/linux/scoped_tmpfile.h"
|
||||
|
||||
using namespace google_breakpad;
|
||||
|
||||
#if !defined(__ANDROID__)
|
||||
#define TEMPDIR "/tmp"
|
||||
#else
|
||||
#define TEMPDIR "/data/local/tmp"
|
||||
#endif
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
typedef testing::Test ProcCpuInfoReaderTest;
|
||||
|
||||
class ScopedTestFile : public AutoTestFile {
|
||||
public:
|
||||
explicit ScopedTestFile(const char* text)
|
||||
: AutoTestFile("proc_cpuinfo_reader", text) {
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
TEST(ProcCpuInfoReaderTest, EmptyFile) {
|
||||
ScopedTestFile file("");
|
||||
ASSERT_TRUE(file.IsOk());
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString(""));
|
||||
ProcCpuInfoReader reader(file.GetFd());
|
||||
|
||||
const char* field;
|
||||
|
@ -69,8 +55,8 @@ TEST(ProcCpuInfoReaderTest, EmptyFile) {
|
|||
}
|
||||
|
||||
TEST(ProcCpuInfoReaderTest, OneLineTerminated) {
|
||||
ScopedTestFile file("foo : bar\n");
|
||||
ASSERT_TRUE(file.IsOk());
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("foo : bar\n"));
|
||||
ProcCpuInfoReader reader(file.GetFd());
|
||||
|
||||
const char* field;
|
||||
|
@ -82,8 +68,8 @@ TEST(ProcCpuInfoReaderTest, OneLineTerminated) {
|
|||
}
|
||||
|
||||
TEST(ProcCpuInfoReaderTest, OneLine) {
|
||||
ScopedTestFile file("foo : bar");
|
||||
ASSERT_TRUE(file.IsOk());
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("foo : bar"));
|
||||
ProcCpuInfoReader reader(file.GetFd());
|
||||
|
||||
const char* field;
|
||||
|
@ -97,8 +83,8 @@ TEST(ProcCpuInfoReaderTest, OneLine) {
|
|||
}
|
||||
|
||||
TEST(ProcCpuInfoReaderTest, TwoLinesTerminated) {
|
||||
ScopedTestFile file("foo : bar\nzoo : tut\n");
|
||||
ASSERT_TRUE(file.IsOk());
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("foo : bar\nzoo : tut\n"));
|
||||
ProcCpuInfoReader reader(file.GetFd());
|
||||
|
||||
const char* field;
|
||||
|
@ -114,8 +100,8 @@ TEST(ProcCpuInfoReaderTest, TwoLinesTerminated) {
|
|||
}
|
||||
|
||||
TEST(ProcCpuInfoReaderTest, SkipMalformedLine) {
|
||||
ScopedTestFile file("this line should have a column\nfoo : bar\n");
|
||||
ASSERT_TRUE(file.IsOk());
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("this line should have a column\nfoo : bar\n"));
|
||||
ProcCpuInfoReader reader(file.GetFd());
|
||||
|
||||
const char* field;
|
||||
|
@ -127,8 +113,8 @@ TEST(ProcCpuInfoReaderTest, SkipMalformedLine) {
|
|||
}
|
||||
|
||||
TEST(ProcCpuInfoReaderTest, SkipOneEmptyLine) {
|
||||
ScopedTestFile file("\n\nfoo : bar\n");
|
||||
ASSERT_TRUE(file.IsOk());
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("\n\nfoo : bar\n"));
|
||||
ProcCpuInfoReader reader(file.GetFd());
|
||||
|
||||
const char* field;
|
||||
|
@ -140,8 +126,8 @@ TEST(ProcCpuInfoReaderTest, SkipOneEmptyLine) {
|
|||
}
|
||||
|
||||
TEST(ProcCpuInfoReaderTest, SkipEmptyField) {
|
||||
ScopedTestFile file(" : bar\nzoo : tut\n");
|
||||
ASSERT_TRUE(file.IsOk());
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString(" : bar\nzoo : tut\n"));
|
||||
ProcCpuInfoReader reader(file.GetFd());
|
||||
|
||||
const char* field;
|
||||
|
@ -153,8 +139,8 @@ TEST(ProcCpuInfoReaderTest, SkipEmptyField) {
|
|||
}
|
||||
|
||||
TEST(ProcCpuInfoReaderTest, SkipTwoEmptyLines) {
|
||||
ScopedTestFile file("foo : bar\n\n\nfoo : bar\n");
|
||||
ASSERT_TRUE(file.IsOk());
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("foo : bar\n\n\nfoo : bar\n"));
|
||||
ProcCpuInfoReader reader(file.GetFd());
|
||||
|
||||
const char* field;
|
||||
|
@ -170,8 +156,8 @@ TEST(ProcCpuInfoReaderTest, SkipTwoEmptyLines) {
|
|||
}
|
||||
|
||||
TEST(ProcCpuInfoReaderTest, FieldWithSpaces) {
|
||||
ScopedTestFile file("foo bar : zoo\n");
|
||||
ASSERT_TRUE(file.IsOk());
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("foo bar : zoo\n"));
|
||||
ProcCpuInfoReader reader(file.GetFd());
|
||||
|
||||
const char* field;
|
||||
|
@ -183,8 +169,8 @@ TEST(ProcCpuInfoReaderTest, FieldWithSpaces) {
|
|||
}
|
||||
|
||||
TEST(ProcCpuInfoReaderTest, EmptyValue) {
|
||||
ScopedTestFile file("foo :\n");
|
||||
ASSERT_TRUE(file.IsOk());
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("foo :\n"));
|
||||
ProcCpuInfoReader reader(file.GetFd());
|
||||
|
||||
const char* field;
|
||||
|
|
|
@ -39,9 +39,9 @@
|
|||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
// TODO(saugustine): Add support for compressed debug.
|
||||
// Also need to add configure tests for zlib.
|
||||
|
@ -107,6 +107,12 @@ const int kAARCH64PLT0Size = 0x20;
|
|||
// Suffix for PLT functions when it needs to be explicitly identified as such.
|
||||
const char kPLTFunctionSuffix[] = "@plt";
|
||||
|
||||
// Replace callsites of this function to std::string_view::starts_with after
|
||||
// adopting C++20.
|
||||
bool StringViewStartsWith(std::string_view sv, std::string_view prefix) {
|
||||
return sv.compare(0, prefix.size(), prefix) == 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace google_breakpad {
|
||||
|
@ -215,7 +221,7 @@ class ElfSectionReader {
|
|||
(header_.sh_offset - offset_aligned);
|
||||
|
||||
// Check for and handle any compressed contents.
|
||||
//if (name == ".zdebug_")
|
||||
//if (StringViewStartsWith(name, ".zdebug_"))
|
||||
// DecompressZlibContents();
|
||||
// TODO(saugustine): Add support for proposed elf-section flag
|
||||
// "SHF_COMPRESS".
|
||||
|
@ -359,8 +365,8 @@ class ElfReaderImpl {
|
|||
// "opd_section_" must always be checked for NULL before use.
|
||||
opd_section_ = GetSectionInfoByName(".opd", &opd_info_);
|
||||
for (unsigned int k = 0u; k < GetNumSections(); ++k) {
|
||||
const char* name = GetSectionName(section_headers_[k].sh_name);
|
||||
if (strncmp(name, ".text", strlen(".text")) == 0) {
|
||||
std::string_view name{GetSectionName(section_headers_[k].sh_name)};
|
||||
if (StringViewStartsWith(name, ".text")) {
|
||||
base_for_text_ =
|
||||
section_headers_[k].sh_addr - section_headers_[k].sh_offset;
|
||||
break;
|
||||
|
@ -809,9 +815,11 @@ class ElfReaderImpl {
|
|||
// Debug sections are likely to be near the end, so reverse the
|
||||
// direction of iteration.
|
||||
for (int k = GetNumSections() - 1; k >= 0; --k) {
|
||||
const char* name = GetSectionName(section_headers_[k].sh_name);
|
||||
if (strncmp(name, ".debug", strlen(".debug")) == 0) return true;
|
||||
if (strncmp(name, ".zdebug", strlen(".zdebug")) == 0) return true;
|
||||
std::string_view name{GetSectionName(section_headers_[k].sh_name)};
|
||||
if (StringViewStartsWith(name, ".debug") ||
|
||||
StringViewStartsWith(name, ".zdebug")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -1213,11 +1221,15 @@ const char* ElfReader::GetSectionInfoByName(const string& section_name,
|
|||
}
|
||||
}
|
||||
|
||||
bool ElfReader::SectionNamesMatch(const string& name, const string& sh_name) {
|
||||
if ((name.find(".debug_", 0) == 0) && (sh_name.find(".zdebug_", 0) == 0)) {
|
||||
const string name_suffix(name, strlen(".debug_"));
|
||||
const string sh_name_suffix(sh_name, strlen(".zdebug_"));
|
||||
return name_suffix == sh_name_suffix;
|
||||
bool ElfReader::SectionNamesMatch(std::string_view name,
|
||||
std::string_view sh_name) {
|
||||
std::string_view debug_prefix{".debug_"};
|
||||
std::string_view zdebug_prefix{".zdebug_"};
|
||||
if (StringViewStartsWith(name, debug_prefix) &&
|
||||
StringViewStartsWith(sh_name, zdebug_prefix)) {
|
||||
name.remove_prefix(debug_prefix.length());
|
||||
sh_name.remove_prefix(zdebug_prefix.length());
|
||||
return name == sh_name;
|
||||
}
|
||||
return name == sh_name;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#define COMMON_DWARF_ELF_READER_H__
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "common/dwarf/types.h"
|
||||
|
@ -145,7 +146,8 @@ class ElfReader {
|
|||
// appears in the elf-file, adjusting for compressed debug section
|
||||
// names. For example, returns true if name == ".debug_abbrev" and
|
||||
// sh_name == ".zdebug_abbrev"
|
||||
static bool SectionNamesMatch(const string& name, const string& sh_name);
|
||||
static bool SectionNamesMatch(std::string_view name,
|
||||
std::string_view sh_name);
|
||||
|
||||
private:
|
||||
// Lazily initialize impl32_ and return it.
|
||||
|
|
|
@ -33,7 +33,9 @@
|
|||
// Implementation of google_breakpad::DwarfCFIToModule.
|
||||
// See dwarf_cfi_to_module.h for details.
|
||||
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
|
||||
#include "common/dwarf_cfi_to_module.h"
|
||||
|
||||
|
@ -151,7 +153,7 @@ bool DwarfCFIToModule::Entry(size_t offset, uint64_t address, uint64_t length,
|
|||
// need to check them here.
|
||||
|
||||
// Get ready to collect entries.
|
||||
entry_ = new Module::StackFrameEntry;
|
||||
entry_ = std::make_unique<Module::StackFrameEntry>();
|
||||
entry_->address = address;
|
||||
entry_->size = length;
|
||||
entry_offset_ = offset;
|
||||
|
@ -258,8 +260,7 @@ bool DwarfCFIToModule::ValExpressionRule(uint64_t address, int reg,
|
|||
}
|
||||
|
||||
bool DwarfCFIToModule::End() {
|
||||
module_->AddStackFrameEntry(entry_);
|
||||
entry_ = NULL;
|
||||
module_->AddStackFrameEntry(std::move(entry_));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "common/module.h"
|
||||
|
@ -131,9 +132,9 @@ class DwarfCFIToModule: public CallFrameInfo::Handler {
|
|||
DwarfCFIToModule(Module* module, const vector<string>& register_names,
|
||||
Reporter* reporter)
|
||||
: module_(module), register_names_(register_names), reporter_(reporter),
|
||||
entry_(NULL), return_address_(-1), cfa_name_(".cfa"), ra_name_(".ra") {
|
||||
return_address_(-1), cfa_name_(".cfa"), ra_name_(".ra") {
|
||||
}
|
||||
virtual ~DwarfCFIToModule() { delete entry_; }
|
||||
virtual ~DwarfCFIToModule() = default;
|
||||
|
||||
virtual bool Entry(size_t offset, uint64_t address, uint64_t length,
|
||||
uint8_t version, const string& augmentation,
|
||||
|
@ -170,7 +171,7 @@ class DwarfCFIToModule: public CallFrameInfo::Handler {
|
|||
Reporter* reporter_;
|
||||
|
||||
// The current entry we're constructing.
|
||||
Module::StackFrameEntry* entry_;
|
||||
std::unique_ptr<Module::StackFrameEntry> entry_;
|
||||
|
||||
// The section offset of the current frame description entry, for
|
||||
// use in error messages.
|
||||
|
|
|
@ -495,9 +495,42 @@ bool LoadDwarfCFI(const string& dwarf_filename,
|
|||
|
||||
google_breakpad::CallFrameInfo::Reporter dwarf_reporter(dwarf_filename,
|
||||
section_name);
|
||||
google_breakpad::CallFrameInfo parser(cfi, cfi_size,
|
||||
&byte_reader, &handler, &dwarf_reporter,
|
||||
eh_frame);
|
||||
if (!IsCompressedHeader<ElfClass>(section)) {
|
||||
google_breakpad::CallFrameInfo parser(cfi, cfi_size,
|
||||
&byte_reader, &handler,
|
||||
&dwarf_reporter, eh_frame);
|
||||
parser.Start();
|
||||
return true;
|
||||
}
|
||||
|
||||
typename ElfClass::Chdr chdr;
|
||||
uint32_t compression_header_size =
|
||||
GetCompressionHeader<ElfClass>(chdr, cfi, cfi_size);
|
||||
|
||||
if (compression_header_size == 0 || chdr.ch_size == 0) {
|
||||
fprintf(stderr, "%s: decompression failed at header\n",
|
||||
dwarf_filename.c_str());
|
||||
return false;
|
||||
}
|
||||
if (compression_header_size > cfi_size) {
|
||||
fprintf(stderr, "%s: decompression error, compression_header too large\n",
|
||||
dwarf_filename.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
cfi += compression_header_size;
|
||||
cfi_size -= compression_header_size;
|
||||
|
||||
std::pair<uint8_t *, uint64_t> uncompressed =
|
||||
UncompressSectionContents(cfi, cfi_size, chdr.ch_size);
|
||||
|
||||
if (uncompressed.first == nullptr || uncompressed.second == 0) {
|
||||
fprintf(stderr, "%s: decompression failed\n", dwarf_filename.c_str());
|
||||
return false;
|
||||
}
|
||||
google_breakpad::CallFrameInfo parser(uncompressed.first, uncompressed.second,
|
||||
&byte_reader, &handler, &dwarf_reporter,
|
||||
eh_frame);
|
||||
parser.Start();
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -36,6 +36,9 @@
|
|||
#include <elf.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "common/byte_cursor.h"
|
||||
#include "common/module.h"
|
||||
|
||||
|
@ -156,7 +159,7 @@ bool ELFSymbolsToModule(const uint8_t* symtab_section,
|
|||
while(!iterator->at_end) {
|
||||
if (ELF32_ST_TYPE(iterator->info) == STT_FUNC &&
|
||||
iterator->shndx != SHN_UNDEF) {
|
||||
Module::Extern* ext = new Module::Extern(iterator->value);
|
||||
auto ext = std::make_unique<Module::Extern>(iterator->value);
|
||||
ext->name = SymbolString(iterator->name_offset, strings);
|
||||
#if !defined(__ANDROID__) // Android NDK doesn't provide abi::__cxa_demangle.
|
||||
int status = 0;
|
||||
|
@ -168,7 +171,7 @@ bool ELFSymbolsToModule(const uint8_t* symtab_section,
|
|||
free(demangled);
|
||||
}
|
||||
#endif
|
||||
module->AddExtern(ext);
|
||||
module->AddExtern(std::move(ext));
|
||||
}
|
||||
++iterator;
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@ LibcurlWrapper::LibcurlWrapper()
|
|||
LibcurlWrapper::~LibcurlWrapper() {
|
||||
if (init_ok_) {
|
||||
(*easy_cleanup_)(curl_);
|
||||
(*global_cleanup_)();
|
||||
dlclose(curl_lib_);
|
||||
}
|
||||
}
|
||||
|
@ -262,6 +263,10 @@ bool LibcurlWrapper::SetFunctionPointers() {
|
|||
SET_AND_CHECK_FUNCTION_POINTER(formfree_,
|
||||
"curl_formfree",
|
||||
void(*)(curl_httppost*));
|
||||
|
||||
SET_AND_CHECK_FUNCTION_POINTER(global_cleanup_,
|
||||
"curl_global_cleanup",
|
||||
void(*)(void));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -39,6 +39,9 @@
|
|||
#include "third_party/curl/curl.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
// This class is only safe to be used on single-threaded code because of its
|
||||
// usage of libcurl's curl_global_cleanup().
|
||||
class LibcurlWrapper {
|
||||
public:
|
||||
LibcurlWrapper();
|
||||
|
@ -111,6 +114,7 @@ class LibcurlWrapper {
|
|||
CURLcode (*easy_getinfo_)(CURL*, CURLINFO info, ...);
|
||||
void (*easy_reset_)(CURL*);
|
||||
void (*formfree_)(struct curl_httppost*);
|
||||
void (*global_cleanup_)(void);
|
||||
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
// Copyright 2022 Google LLC
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include "common/linux/scoped_pipe.h"
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include "common/linux/eintr_wrapper.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
ScopedPipe::ScopedPipe() {
|
||||
fds_[0] = -1;
|
||||
fds_[1] = -1;
|
||||
}
|
||||
|
||||
ScopedPipe::~ScopedPipe() {
|
||||
CloseReadFd();
|
||||
CloseWriteFd();
|
||||
}
|
||||
|
||||
bool ScopedPipe::Init() {
|
||||
return pipe(fds_) == 0;
|
||||
}
|
||||
|
||||
void ScopedPipe::CloseReadFd() {
|
||||
if (fds_[0] != -1) {
|
||||
close(fds_[0]);
|
||||
fds_[0] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void ScopedPipe::CloseWriteFd() {
|
||||
if (fds_[1] != -1) {
|
||||
close(fds_[1]);
|
||||
fds_[1] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
bool ScopedPipe::ReadLine(std::string& line) {
|
||||
// Simple buffered file read. `read_buffer_` stores previously read bytes, and
|
||||
// we either return a line from this buffer, or we append blocks of read bytes
|
||||
// to the buffer until we have a complete line.
|
||||
size_t eol_index = read_buffer_.find('\n');
|
||||
|
||||
// While we don't have a full line, and read pipe is valid.
|
||||
while (eol_index == std::string::npos && GetReadFd() != -1) {
|
||||
// Read a block of 128 bytes from the read pipe.
|
||||
char read_buf[128];
|
||||
ssize_t read_len = HANDLE_EINTR(
|
||||
read(GetReadFd(), read_buf, sizeof(read_buf)));
|
||||
if (read_len <= 0) {
|
||||
// Pipe error, or pipe has been closed.
|
||||
CloseReadFd();
|
||||
break;
|
||||
}
|
||||
|
||||
// Append the block, and check if we have a full line now.
|
||||
read_buffer_.append(read_buf, read_len);
|
||||
eol_index = read_buffer_.find('\n');
|
||||
}
|
||||
|
||||
if (eol_index != std::string::npos) {
|
||||
// We have a full line to output.
|
||||
line = read_buffer_.substr(0, eol_index);
|
||||
if (eol_index < read_buffer_.size()) {
|
||||
read_buffer_ = read_buffer_.substr(eol_index + 1);
|
||||
} else {
|
||||
read_buffer_ = "";
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (read_buffer_.size()) {
|
||||
// We don't have a full line to output, but we can only reach here if the
|
||||
// pipe has closed and there are some bytes left at the end, so we should
|
||||
// return those bytes.
|
||||
line = std::move(read_buffer_);
|
||||
read_buffer_ = "";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// We don't have any buffered data left, and the pipe has closed.
|
||||
return false;
|
||||
}
|
||||
|
||||
int ScopedPipe::Dup2WriteFd(int new_fd) const {
|
||||
return dup2(fds_[1], new_fd);
|
||||
}
|
||||
|
||||
bool ScopedPipe::WriteForTesting(const void* bytes, size_t bytes_len) {
|
||||
ssize_t r = HANDLE_EINTR(write(GetWriteFd(), bytes, bytes_len));
|
||||
if (r != static_cast<ssize_t>(bytes_len)) {
|
||||
CloseWriteFd();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
|
@ -0,0 +1,115 @@
|
|||
// Copyright 2022 Google LLC
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef COMMON_LINUX_SCOPED_PIPE_H_
|
||||
#define COMMON_LINUX_SCOPED_PIPE_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
// Small RAII wrapper for a pipe pair.
|
||||
//
|
||||
// Example (both ends of pipe in same process):
|
||||
// ScopedPipe tmp;
|
||||
// std::string line;
|
||||
// if (tmp.Init() && tmp.Write(bytes, bytes_len)) {
|
||||
// tmp.CloseWriteFd();
|
||||
// while (tmp.ReadLine(&line)) {
|
||||
// std::cerr << line << std::endl;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Example (reading output from a child process):
|
||||
// ScopedPipe tmp;
|
||||
// if (fork()) {
|
||||
// // Parent process, read from the read end of the pipe.
|
||||
// std::string line;
|
||||
// while (tmp.ReadLine(line)) {
|
||||
// // Process output...
|
||||
// }
|
||||
// // Close read pipe once done processing the output that we wanted, this
|
||||
// // should ensure that the child process exits even if we didn't read all
|
||||
// // of the output.
|
||||
// tmp.CloseReadFd();
|
||||
// // Parent process should handle waiting for child to exit here...
|
||||
// } else {
|
||||
// // Child process, close the read fd and dup the write fd before exec'ing.
|
||||
// tmp.CloseReadFd();
|
||||
// tmp.Dup2WriteFd(STDOUT_FILENO);
|
||||
// tmp.Dup2WriteFd(STDERR_FILENO);
|
||||
// execl("some-command", "some-arguments");
|
||||
// }
|
||||
class ScopedPipe {
|
||||
public:
|
||||
ScopedPipe();
|
||||
~ScopedPipe();
|
||||
|
||||
// Creates the pipe pair - returns false on error.
|
||||
bool Init();
|
||||
|
||||
// Close the read pipe. This only needs to be used when the read pipe needs to
|
||||
// be closed earlier.
|
||||
void CloseReadFd();
|
||||
|
||||
// Close the write pipe. This only needs to be used when the write pipe needs
|
||||
// to be closed earlier.
|
||||
void CloseWriteFd();
|
||||
|
||||
// Reads characters until newline or end of pipe. On read failure this will
|
||||
// close the read pipe, but continue to return true and read buffered lines
|
||||
// until the internal buffering is exhausted. This will block if there is no
|
||||
// data available on the read pipe.
|
||||
bool ReadLine(std::string& line);
|
||||
|
||||
// Writes bytes to the write end of the pipe, returns false and closes write
|
||||
// pipe on failure.
|
||||
bool WriteForTesting(const void* bytes, size_t bytes_len);
|
||||
|
||||
// Calls the dup2 system call to replace any existing open file descriptor
|
||||
// with number new_fd with a copy of the current write end file descriptor
|
||||
// for the pipe.
|
||||
int Dup2WriteFd(int new_fd) const;
|
||||
|
||||
private:
|
||||
int GetReadFd() const {
|
||||
return fds_[0];
|
||||
}
|
||||
|
||||
int GetWriteFd() const {
|
||||
return fds_[1];
|
||||
}
|
||||
|
||||
int fds_[2];
|
||||
std::string read_buffer_;
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // COMMON_LINUX_SCOPED_PIPE_H_
|
71
thirdparty/sentry-native/external/breakpad/src/common/linux/scoped_pipe_unittest.cc
vendored
Normal file
71
thirdparty/sentry-native/external/breakpad/src/common/linux/scoped_pipe_unittest.cc
vendored
Normal file
|
@ -0,0 +1,71 @@
|
|||
// Copyright 2022 Google LLC
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// scoped_pipe_unittest.cc: Unit tests for google_breakpad::ScopedPipe.
|
||||
|
||||
#include "common/linux/scoped_pipe.h"
|
||||
|
||||
#include "breakpad_googletest_includes.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
TEST(ScopedPipeTest, WriteAndClose) {
|
||||
const char kTestData[] = "One\nTwo\nThree";
|
||||
ScopedPipe pipe;
|
||||
std::string line;
|
||||
|
||||
ASSERT_TRUE(pipe.Init());
|
||||
ASSERT_TRUE(pipe.WriteForTesting(kTestData, strlen(kTestData)));
|
||||
pipe.CloseWriteFd();
|
||||
|
||||
ASSERT_TRUE(pipe.ReadLine(line));
|
||||
ASSERT_EQ(line, "One");
|
||||
ASSERT_TRUE(pipe.ReadLine(line));
|
||||
ASSERT_EQ(line, "Two");
|
||||
ASSERT_TRUE(pipe.ReadLine(line));
|
||||
ASSERT_EQ(line, "Three");
|
||||
ASSERT_FALSE(pipe.ReadLine(line));
|
||||
}
|
||||
|
||||
TEST(ScopedPipeTest, MultipleWrites) {
|
||||
const char kTestDataOne[] = "One\n";
|
||||
const char kTestDataTwo[] = "Two\n";
|
||||
ScopedPipe pipe;
|
||||
std::string line;
|
||||
|
||||
ASSERT_TRUE(pipe.Init());
|
||||
ASSERT_TRUE(pipe.WriteForTesting(kTestDataOne, strlen(kTestDataOne)));
|
||||
ASSERT_TRUE(pipe.ReadLine(line));
|
||||
ASSERT_EQ(line, "One");
|
||||
|
||||
ASSERT_TRUE(pipe.WriteForTesting(kTestDataTwo, strlen(kTestDataTwo)));
|
||||
ASSERT_TRUE(pipe.ReadLine(line));
|
||||
ASSERT_EQ(line, "Two");
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
99
thirdparty/sentry-native/external/breakpad/src/common/linux/scoped_tmpfile.cc
vendored
Normal file
99
thirdparty/sentry-native/external/breakpad/src/common/linux/scoped_tmpfile.cc
vendored
Normal file
|
@ -0,0 +1,99 @@
|
|||
// Copyright 2022 Google LLC
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Utility class for creating a temporary file that is deleted in the
|
||||
// destructor.
|
||||
|
||||
#include "common/linux/scoped_tmpfile.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "common/linux/eintr_wrapper.h"
|
||||
|
||||
#if !defined(__ANDROID__)
|
||||
#define TEMPDIR "/tmp"
|
||||
#else
|
||||
#define TEMPDIR "/data/local/tmp"
|
||||
#endif
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
ScopedTmpFile::ScopedTmpFile() = default;
|
||||
|
||||
ScopedTmpFile::~ScopedTmpFile() {
|
||||
if (fd_ >= 0) {
|
||||
close(fd_);
|
||||
fd_ = -1;
|
||||
}
|
||||
}
|
||||
|
||||
bool ScopedTmpFile::InitEmpty() {
|
||||
// Prevent calling Init when fd_ is already valid, leaking the file.
|
||||
if (fd_ != -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Respect the TMPDIR environment variable.
|
||||
const char* tempdir = getenv("TMPDIR");
|
||||
if (!tempdir) {
|
||||
tempdir = TEMPDIR;
|
||||
}
|
||||
|
||||
// Create a temporary file that is not linked in to the filesystem, and that
|
||||
// is only accessible by the current user.
|
||||
fd_ = open(tempdir, O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
|
||||
|
||||
return fd_ >= 0;
|
||||
}
|
||||
|
||||
bool ScopedTmpFile::InitString(const char* text) {
|
||||
return InitData(text, strlen(text));
|
||||
}
|
||||
|
||||
bool ScopedTmpFile::InitData(const void* data, size_t data_len) {
|
||||
if (!InitEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return SetContents(data, data_len);
|
||||
}
|
||||
|
||||
bool ScopedTmpFile::SetContents(const void* data, size_t data_len) {
|
||||
ssize_t r = HANDLE_EINTR(write(fd_, data, data_len));
|
||||
if (r != static_cast<ssize_t>(data_len)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return 0 == lseek(fd_, 0, SEEK_SET);
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
85
thirdparty/sentry-native/external/breakpad/src/common/linux/scoped_tmpfile.h
vendored
Normal file
85
thirdparty/sentry-native/external/breakpad/src/common/linux/scoped_tmpfile.h
vendored
Normal file
|
@ -0,0 +1,85 @@
|
|||
// Copyright 2022 Google LLC
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Utility class for creating a temporary file for that is deleted in the
|
||||
// destructor.
|
||||
|
||||
#ifndef COMMON_LINUX_SCOPED_TMPFILE_H_
|
||||
#define COMMON_LINUX_SCOPED_TMPFILE_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
// Small RAII wrapper for temporary files.
|
||||
//
|
||||
// Example:
|
||||
// ScopedTmpFile tmp;
|
||||
// if (tmp.Init("Some file contents")) {
|
||||
// ...
|
||||
// }
|
||||
class ScopedTmpFile {
|
||||
public:
|
||||
// Initialize the ScopedTmpFile object - this does not create the temporary
|
||||
// file until Init is called.
|
||||
ScopedTmpFile();
|
||||
|
||||
// Destroy temporary file on scope exit.
|
||||
~ScopedTmpFile();
|
||||
|
||||
// Creates the empty temporary file - returns true iff the temporary file was
|
||||
// created successfully. Should always be checked before using the file.
|
||||
bool InitEmpty();
|
||||
|
||||
// Creates the temporary file with the provided C string. The terminating null
|
||||
// is not written. Returns true iff the temporary file was created
|
||||
// successfully and the contents were written successfully.
|
||||
bool InitString(const char* text);
|
||||
|
||||
// Creates the temporary file with the provided data. Returns true iff the
|
||||
// temporary file was created successfully and the contents were written
|
||||
// successfully.
|
||||
bool InitData(const void* data, size_t data_len);
|
||||
|
||||
// Returns the Posix file descriptor for the test file, or -1 if Init()
|
||||
// returned false. Note: on Windows, this always returns -1.
|
||||
int GetFd() const {
|
||||
return fd_;
|
||||
}
|
||||
|
||||
private:
|
||||
// Set the contents of the temporary file, and seek back to the start of the
|
||||
// file. On failure, returns false.
|
||||
bool SetContents(const void* data, size_t data_len);
|
||||
|
||||
int fd_ = -1;
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // COMMON_LINUX_SCOPED_TMPFILE_H_
|
46
thirdparty/sentry-native/external/breakpad/src/common/linux/scoped_tmpfile_unittest.cc
vendored
Normal file
46
thirdparty/sentry-native/external/breakpad/src/common/linux/scoped_tmpfile_unittest.cc
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
// Copyright 2022 Google LLC
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// scoped_tmpfile_unittest.cc: Unit tests for google_breakpad::ScopedTmpfile.
|
||||
|
||||
#include "common/linux/scoped_tmpfile.h"
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include "breakpad_googletest_includes.h"
|
||||
|
||||
using google_breakpad::ScopedTmpFile;
|
||||
|
||||
TEST(ScopedTmpFileTest, CheckContentsMatch) {
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("Test"));
|
||||
|
||||
char file_contents[5] = {0};
|
||||
ASSERT_EQ(4, read(file.GetFd(), file_contents, sizeof(file_contents)));
|
||||
EXPECT_STREQ(file_contents, "Test");
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
// Copyright 2013 Google LLC
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Utility class for creating a temporary file for unit tests
|
||||
// that is deleted in the destructor.
|
||||
|
||||
#ifndef GOOGLE_BREAKPAD_COMMON_LINUX_TESTS_AUTO_TESTFILE
|
||||
#define GOOGLE_BREAKPAD_COMMON_LINUX_TESTS_AUTO_TESTFILE
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "breakpad_googletest_includes.h"
|
||||
#include "common/linux/eintr_wrapper.h"
|
||||
#include "common/tests/auto_tempdir.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
class AutoTestFile {
|
||||
public:
|
||||
// Create a new empty test file.
|
||||
// test_prefix: (input) test-specific prefix, can't be NULL.
|
||||
explicit AutoTestFile(const char* test_prefix) {
|
||||
Init(test_prefix);
|
||||
}
|
||||
|
||||
// Create a new test file, and fill it with initial data from a C string.
|
||||
// The terminating zero is not written.
|
||||
// test_prefix: (input) test-specific prefix, can't be NULL.
|
||||
// text: (input) initial content.
|
||||
AutoTestFile(const char* test_prefix, const char* text) {
|
||||
Init(test_prefix);
|
||||
if (fd_ >= 0)
|
||||
WriteText(text, static_cast<size_t>(strlen(text)));
|
||||
}
|
||||
|
||||
AutoTestFile(const char* test_prefix, const char* text, size_t text_len) {
|
||||
Init(test_prefix);
|
||||
if (fd_ >= 0)
|
||||
WriteText(text, text_len);
|
||||
}
|
||||
|
||||
// Destroy test file on scope exit.
|
||||
~AutoTestFile() {
|
||||
if (fd_ >= 0) {
|
||||
close(fd_);
|
||||
fd_ = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true iff the test file could be created properly.
|
||||
// Useful in tests inside EXPECT_TRUE(file.IsOk());
|
||||
bool IsOk() {
|
||||
return fd_ >= 0;
|
||||
}
|
||||
|
||||
// Returns the Posix file descriptor for the test file, or -1
|
||||
// If IsOk() returns false. Note: on Windows, this always returns -1.
|
||||
int GetFd() {
|
||||
return fd_;
|
||||
}
|
||||
|
||||
private:
|
||||
void Init(const char* test_prefix) {
|
||||
fd_ = -1;
|
||||
char path_templ[PATH_MAX];
|
||||
int ret = snprintf(path_templ, sizeof(path_templ),
|
||||
TEMPDIR "/%s-unittest.XXXXXX",
|
||||
test_prefix);
|
||||
if (ret >= static_cast<int>(sizeof(path_templ)))
|
||||
return;
|
||||
|
||||
fd_ = mkstemp(path_templ);
|
||||
if (fd_ < 0)
|
||||
return;
|
||||
|
||||
unlink(path_templ);
|
||||
}
|
||||
|
||||
void WriteText(const char* text, size_t text_len) {
|
||||
ssize_t r = HANDLE_EINTR(write(fd_, text, text_len));
|
||||
if (r != static_cast<ssize_t>(text_len)) {
|
||||
close(fd_);
|
||||
fd_ = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
lseek(fd_, 0, SEEK_SET);
|
||||
}
|
||||
|
||||
int fd_;
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // GOOGLE_BREAKPAD_COMMON_LINUX_TESTS_AUTO_TESTFILE
|
|
@ -268,7 +268,8 @@ SuperFatArch* DumpSymbols::FindBestMatchForArchitecture(
|
|||
return &object_files_[i];
|
||||
}
|
||||
assert(best_match == NULL);
|
||||
return NULL;
|
||||
// Fall through since NXFindBestFatArch can't find arm slices on x86_64
|
||||
// macOS 13. See FB11955188.
|
||||
}
|
||||
|
||||
// Check for an exact match with cpu_type and cpu_subtype.
|
||||
|
@ -276,7 +277,8 @@ SuperFatArch* DumpSymbols::FindBestMatchForArchitecture(
|
|||
it != object_files_.end();
|
||||
++it) {
|
||||
if (static_cast<cpu_type_t>(it->cputype) == cpu_type &&
|
||||
static_cast<cpu_subtype_t>(it->cpusubtype) == cpu_subtype)
|
||||
(static_cast<cpu_subtype_t>(it->cpusubtype) & ~CPU_SUBTYPE_MASK) ==
|
||||
(cpu_subtype & ~CPU_SUBTYPE_MASK))
|
||||
return &*it;
|
||||
}
|
||||
|
||||
|
@ -285,8 +287,11 @@ SuperFatArch* DumpSymbols::FindBestMatchForArchitecture(
|
|||
// NXFindBestFatArch, located at
|
||||
// http://web.mit.edu/darwin/src/modules/cctools/libmacho/arch.c.
|
||||
fprintf(stderr, "Failed to find an exact match for an object file with cpu "
|
||||
"type: %d and cpu subtype: %d. Furthermore, at least one object file is "
|
||||
"larger than 2**32.\n", cpu_type, cpu_subtype);
|
||||
"type: %d and cpu subtype: %d.\n", cpu_type, cpu_subtype);
|
||||
if (!can_convert_to_fat_arch) {
|
||||
fprintf(stderr, "Furthermore, at least one object file is larger "
|
||||
"than 2**32.\n");
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -678,18 +683,6 @@ bool DumpSymbols::ReadSymbolData(Module** out_module) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool DumpSymbols::WriteSymbolFile(std::ostream& stream) {
|
||||
Module* module = NULL;
|
||||
|
||||
if (ReadSymbolData(&module) && module) {
|
||||
bool res = module->Write(stream, symbol_data_);
|
||||
delete module;
|
||||
return res;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read the selected object file's debugging information, and write out the
|
||||
// header only to |stream|. Return true on success; if an error occurs, report
|
||||
// it and return false.
|
||||
|
|
|
@ -117,19 +117,14 @@ class DumpSymbols {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
// Read the selected object file's debugging information, and write it out to
|
||||
// |stream|. Return true on success; if an error occurs, report it and
|
||||
// return false.
|
||||
bool WriteSymbolFile(std::ostream& stream);
|
||||
|
||||
// Read the selected object file's debugging information, and write out the
|
||||
// header only to |stream|. Return true on success; if an error occurs, report
|
||||
// it and return false.
|
||||
bool WriteSymbolFileHeader(std::ostream& stream);
|
||||
|
||||
// As above, but simply return the debugging information in module
|
||||
// instead of writing it to a stream. The caller owns the resulting
|
||||
// module object and must delete it when finished.
|
||||
// Read the selected object file's debugging information and store it in
|
||||
// `module`. The caller owns the resulting module object and must delete
|
||||
// it when finished.
|
||||
bool ReadSymbolData(Module** module);
|
||||
|
||||
// Return an identifier string for the file this DumpSymbols is dumping.
|
||||
|
|
|
@ -117,12 +117,6 @@ Module::~Module() {
|
|||
it != functions_.end(); ++it) {
|
||||
delete *it;
|
||||
}
|
||||
for (vector<StackFrameEntry*>::iterator it = stack_frame_entries_.begin();
|
||||
it != stack_frame_entries_.end(); ++it) {
|
||||
delete *it;
|
||||
}
|
||||
for (ExternSet::iterator it = externs_.begin(); it != externs_.end(); ++it)
|
||||
delete *it;
|
||||
}
|
||||
|
||||
void Module::SetLoadAddress(Address address) {
|
||||
|
@ -155,12 +149,11 @@ bool Module::AddFunction(Function* function) {
|
|||
}
|
||||
if (it_ext != externs_.end()) {
|
||||
if (enable_multiple_field_) {
|
||||
Extern* found_ext = *it_ext;
|
||||
Extern* found_ext = it_ext->get();
|
||||
// If the PUBLIC is for the same symbol as the FUNC, don't mark multiple.
|
||||
function->is_multiple |=
|
||||
found_ext->name != function->name || found_ext->is_multiple;
|
||||
}
|
||||
delete *it_ext;
|
||||
externs_.erase(it_ext);
|
||||
}
|
||||
#if _DEBUG
|
||||
|
@ -194,25 +187,22 @@ bool Module::AddFunction(Function* function) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void Module::AddStackFrameEntry(StackFrameEntry* stack_frame_entry) {
|
||||
void Module::AddStackFrameEntry(std::unique_ptr<StackFrameEntry> stack_frame_entry) {
|
||||
if (!AddressIsInModule(stack_frame_entry->address)) {
|
||||
return;
|
||||
}
|
||||
|
||||
stack_frame_entries_.push_back(stack_frame_entry);
|
||||
stack_frame_entries_.push_back(std::move(stack_frame_entry));
|
||||
}
|
||||
|
||||
void Module::AddExtern(Extern* ext) {
|
||||
void Module::AddExtern(std::unique_ptr<Extern> ext) {
|
||||
if (!AddressIsInModule(ext->address)) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::pair<ExternSet::iterator,bool> ret = externs_.insert(ext);
|
||||
std::pair<ExternSet::iterator,bool> ret = externs_.emplace(std::move(ext));
|
||||
if (!ret.second && enable_multiple_field_) {
|
||||
(*ret.first)->is_multiple = true;
|
||||
// Free the duplicate that was not inserted because this Module
|
||||
// now owns it.
|
||||
delete ext;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -223,7 +213,11 @@ void Module::GetFunctions(vector<Function*>* vec,
|
|||
|
||||
void Module::GetExterns(vector<Extern*>* vec,
|
||||
vector<Extern*>::iterator i) {
|
||||
vec->insert(i, externs_.begin(), externs_.end());
|
||||
auto pos = vec->insert(i, externs_.size(), nullptr);
|
||||
for (const std::unique_ptr<Extern>& ext : externs_) {
|
||||
*pos = ext.get();
|
||||
++pos;
|
||||
}
|
||||
}
|
||||
|
||||
Module::File* Module::FindFile(const string& name) {
|
||||
|
@ -265,7 +259,11 @@ void Module::GetFiles(vector<File*>* vec) {
|
|||
}
|
||||
|
||||
void Module::GetStackFrameEntries(vector<StackFrameEntry*>* vec) const {
|
||||
*vec = stack_frame_entries_;
|
||||
vec->clear();
|
||||
vec->reserve(stack_frame_entries_.size());
|
||||
for (const auto& ent : stack_frame_entries_) {
|
||||
vec->push_back(ent.get());
|
||||
}
|
||||
}
|
||||
|
||||
void Module::AssignSourceIds(
|
||||
|
@ -441,7 +439,7 @@ bool Module::Write(std::ostream& stream, SymbolData symbol_data) {
|
|||
// Write out 'PUBLIC' records.
|
||||
for (ExternSet::const_iterator extern_it = externs_.begin();
|
||||
extern_it != externs_.end(); ++extern_it) {
|
||||
Extern* ext = *extern_it;
|
||||
Extern* ext = extern_it->get();
|
||||
stream << "PUBLIC " << (ext->is_multiple ? "m " : "") << hex
|
||||
<< (ext->address - load_address_) << " 0 " << ext->name << dec
|
||||
<< "\n";
|
||||
|
@ -450,10 +448,9 @@ bool Module::Write(std::ostream& stream, SymbolData symbol_data) {
|
|||
|
||||
if (symbol_data & CFI) {
|
||||
// Write out 'STACK CFI INIT' and 'STACK CFI' records.
|
||||
vector<StackFrameEntry*>::const_iterator frame_it;
|
||||
for (frame_it = stack_frame_entries_.begin();
|
||||
for (auto frame_it = stack_frame_entries_.begin();
|
||||
frame_it != stack_frame_entries_.end(); ++frame_it) {
|
||||
StackFrameEntry* entry = *frame_it;
|
||||
StackFrameEntry* entry = frame_it->get();
|
||||
stream << "STACK CFI INIT " << hex
|
||||
<< (entry->address - load_address_) << " "
|
||||
<< entry->size << " " << dec;
|
||||
|
|
|
@ -292,7 +292,18 @@ class Module {
|
|||
};
|
||||
|
||||
struct ExternCompare {
|
||||
bool operator() (const Extern* lhs, const Extern* rhs) const {
|
||||
// Defining is_transparent allows
|
||||
// std::set<std::unique_ptr<Extern>, ExternCompare>::find() to be called
|
||||
// with an Extern* and have set use the overloads below.
|
||||
using is_transparent = void;
|
||||
bool operator() (const std::unique_ptr<Extern>& lhs,
|
||||
const std::unique_ptr<Extern>& rhs) const {
|
||||
return lhs->address < rhs->address;
|
||||
}
|
||||
bool operator() (const Extern* lhs, const std::unique_ptr<Extern>& rhs) const {
|
||||
return lhs->address < rhs->address;
|
||||
}
|
||||
bool operator() (const std::unique_ptr<Extern>& lhs, const Extern* rhs) const {
|
||||
return lhs->address < rhs->address;
|
||||
}
|
||||
};
|
||||
|
@ -340,12 +351,12 @@ class Module {
|
|||
// Add STACK_FRAME_ENTRY to the module.
|
||||
// This module owns all StackFrameEntry objects added with this
|
||||
// function: destroying the module destroys them as well.
|
||||
void AddStackFrameEntry(StackFrameEntry* stack_frame_entry);
|
||||
void AddStackFrameEntry(std::unique_ptr<StackFrameEntry> stack_frame_entry);
|
||||
|
||||
// Add PUBLIC to the module.
|
||||
// This module owns all Extern objects added with this function:
|
||||
// destroying the module destroys them as well.
|
||||
void AddExtern(Extern* ext);
|
||||
void AddExtern(std::unique_ptr<Extern> ext);
|
||||
|
||||
// If this module has a file named NAME, return a pointer to it. If
|
||||
// it has none, then create one and return a pointer to the new
|
||||
|
@ -465,7 +476,7 @@ class Module {
|
|||
typedef set<Function*, FunctionCompare> FunctionSet;
|
||||
|
||||
// A set containing Extern structures, sorted by address.
|
||||
typedef set<Extern*, ExternCompare> ExternSet;
|
||||
typedef set<std::unique_ptr<Extern>, ExternCompare> ExternSet;
|
||||
|
||||
// The module owns all the files and functions that have been added
|
||||
// to it; destroying the module frees the Files and Functions these
|
||||
|
@ -477,7 +488,7 @@ class Module {
|
|||
|
||||
// The module owns all the call frame info entries that have been
|
||||
// added to it.
|
||||
vector<StackFrameEntry*> stack_frame_entries_;
|
||||
vector<std::unique_ptr<StackFrameEntry>> stack_frame_entries_;
|
||||
|
||||
// The module owns all the externs that have been added to it;
|
||||
// destroying the module frees the Externs these point to.
|
||||
|
|
|
@ -36,8 +36,10 @@
|
|||
#include <string.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "breakpad_googletest_includes.h"
|
||||
#include "common/module.h"
|
||||
|
@ -137,7 +139,7 @@ TEST(Module, WriteRelativeLoadAddress) {
|
|||
m.AddFunction(function);
|
||||
|
||||
// Some stack information.
|
||||
Module::StackFrameEntry* entry = new Module::StackFrameEntry();
|
||||
auto entry = std::make_unique<Module::StackFrameEntry>();
|
||||
entry->address = 0x30f9e5c83323973dULL;
|
||||
entry->size = 0x49fc9ca7c7c13dc2ULL;
|
||||
entry->initial_rules[".cfa"] = "he was a handsome man";
|
||||
|
@ -145,7 +147,7 @@ TEST(Module, WriteRelativeLoadAddress) {
|
|||
entry->rule_changes[0x30f9e5c83323973eULL]["how"] =
|
||||
"do you like your blueeyed boy";
|
||||
entry->rule_changes[0x30f9e5c83323973eULL]["Mister"] = "Death";
|
||||
m.AddStackFrameEntry(entry);
|
||||
m.AddStackFrameEntry(std::move(entry));
|
||||
|
||||
// Set the load address. Doing this after adding all the data to
|
||||
// the module must work fine.
|
||||
|
@ -242,7 +244,7 @@ TEST(Module, WriteNoCFI) {
|
|||
m.AddFunction(function);
|
||||
|
||||
// Some stack information.
|
||||
Module::StackFrameEntry* entry = new Module::StackFrameEntry();
|
||||
auto entry = std::make_unique<Module::StackFrameEntry>();
|
||||
entry->address = 0x30f9e5c83323973dULL;
|
||||
entry->size = 0x49fc9ca7c7c13dc2ULL;
|
||||
entry->initial_rules[".cfa"] = "he was a handsome man";
|
||||
|
@ -250,7 +252,7 @@ TEST(Module, WriteNoCFI) {
|
|||
entry->rule_changes[0x30f9e5c83323973eULL]["how"] =
|
||||
"do you like your blueeyed boy";
|
||||
entry->rule_changes[0x30f9e5c83323973eULL]["Mister"] = "Death";
|
||||
m.AddStackFrameEntry(entry);
|
||||
m.AddStackFrameEntry(std::move(entry));
|
||||
|
||||
// Set the load address. Doing this after adding all the data to
|
||||
// the module must work fine.
|
||||
|
@ -321,18 +323,18 @@ TEST(Module, WriteOutOfRangeAddresses) {
|
|||
|
||||
// Add three stack frames (one lower, one in, and one higher than the allowed
|
||||
// address range). Only the middle frame should be captured.
|
||||
Module::StackFrameEntry* entry1 = new Module::StackFrameEntry();
|
||||
auto entry1 = std::make_unique<Module::StackFrameEntry>();
|
||||
entry1->address = 0x1000ULL;
|
||||
entry1->size = 0x100ULL;
|
||||
m.AddStackFrameEntry(entry1);
|
||||
Module::StackFrameEntry* entry2 = new Module::StackFrameEntry();
|
||||
m.AddStackFrameEntry(std::move(entry1));
|
||||
auto entry2 = std::make_unique<Module::StackFrameEntry>();
|
||||
entry2->address = 0x2000ULL;
|
||||
entry2->size = 0x100ULL;
|
||||
m.AddStackFrameEntry(entry2);
|
||||
Module::StackFrameEntry* entry3 = new Module::StackFrameEntry();
|
||||
m.AddStackFrameEntry(std::move(entry2));
|
||||
auto entry3 = std::make_unique<Module::StackFrameEntry>();
|
||||
entry3->address = 0x3000ULL;
|
||||
entry3->size = 0x100ULL;
|
||||
m.AddStackFrameEntry(entry3);
|
||||
m.AddStackFrameEntry(std::move(entry3));
|
||||
|
||||
// Add a function outside the allowed range.
|
||||
Module::File* file = m.FindFile("file_name.cc");
|
||||
|
@ -346,9 +348,9 @@ TEST(Module, WriteOutOfRangeAddresses) {
|
|||
m.AddFunction(function);
|
||||
|
||||
// Add an extern outside the allowed range.
|
||||
Module::Extern* extern1 = new Module::Extern(0x5000ULL);
|
||||
auto extern1 = std::make_unique<Module::Extern>(0x5000ULL);
|
||||
extern1->name = "_xyz";
|
||||
m.AddExtern(extern1);
|
||||
m.AddExtern(std::move(extern1));
|
||||
|
||||
m.Write(s, ALL_SYMBOL_DATA);
|
||||
|
||||
|
@ -357,10 +359,7 @@ TEST(Module, WriteOutOfRangeAddresses) {
|
|||
s.str().c_str());
|
||||
|
||||
// Cleanup - Prevent Memory Leak errors.
|
||||
delete (extern1);
|
||||
delete (function);
|
||||
delete (entry3);
|
||||
delete (entry1);
|
||||
}
|
||||
|
||||
TEST(Module, ConstructAddFrames) {
|
||||
|
@ -368,22 +367,22 @@ TEST(Module, ConstructAddFrames) {
|
|||
Module m(MODULE_NAME, MODULE_OS, MODULE_ARCH, MODULE_ID);
|
||||
|
||||
// First STACK CFI entry, with no initial rules or deltas.
|
||||
Module::StackFrameEntry* entry1 = new Module::StackFrameEntry();
|
||||
auto entry1 = std::make_unique<Module::StackFrameEntry>();
|
||||
entry1->address = 0xddb5f41285aa7757ULL;
|
||||
entry1->size = 0x1486493370dc5073ULL;
|
||||
m.AddStackFrameEntry(entry1);
|
||||
m.AddStackFrameEntry(std::move(entry1));
|
||||
|
||||
// Second STACK CFI entry, with initial rules but no deltas.
|
||||
Module::StackFrameEntry* entry2 = new Module::StackFrameEntry();
|
||||
auto entry2 = std::make_unique<Module::StackFrameEntry>();
|
||||
entry2->address = 0x8064f3af5e067e38ULL;
|
||||
entry2->size = 0x0de2a5ee55509407ULL;
|
||||
entry2->initial_rules[".cfa"] = "I think that I shall never see";
|
||||
entry2->initial_rules["stromboli"] = "a poem lovely as a tree";
|
||||
entry2->initial_rules["cannoli"] = "a tree whose hungry mouth is prest";
|
||||
m.AddStackFrameEntry(entry2);
|
||||
m.AddStackFrameEntry(std::move(entry2));
|
||||
|
||||
// Third STACK CFI entry, with initial rules and deltas.
|
||||
Module::StackFrameEntry* entry3 = new Module::StackFrameEntry();
|
||||
auto entry3 = std::make_unique<Module::StackFrameEntry>();
|
||||
entry3->address = 0x5e8d0db0a7075c6cULL;
|
||||
entry3->size = 0x1c7edb12a7aea229ULL;
|
||||
entry3->initial_rules[".cfa"] = "Whose woods are these";
|
||||
|
@ -395,7 +394,7 @@ TEST(Module, ConstructAddFrames) {
|
|||
"his house is in";
|
||||
entry3->rule_changes[0x36682fad3763ffffULL][".cfa"] =
|
||||
"I think I know";
|
||||
m.AddStackFrameEntry(entry3);
|
||||
m.AddStackFrameEntry(std::move(entry3));
|
||||
|
||||
// Check that Write writes STACK CFI records properly.
|
||||
m.Write(s, ALL_SYMBOL_DATA);
|
||||
|
@ -541,13 +540,13 @@ TEST(Module, ConstructExterns) {
|
|||
Module m(MODULE_NAME, MODULE_OS, MODULE_ARCH, MODULE_ID);
|
||||
|
||||
// Two externs.
|
||||
Module::Extern* extern1 = new Module::Extern(0xffff);
|
||||
auto extern1 = std::make_unique<Module::Extern>(0xffff);
|
||||
extern1->name = "_abc";
|
||||
Module::Extern* extern2 = new Module::Extern(0xaaaa);
|
||||
auto extern2 = std::make_unique<Module::Extern>(0xaaaa);
|
||||
extern2->name = "_xyz";
|
||||
|
||||
m.AddExtern(extern1);
|
||||
m.AddExtern(extern2);
|
||||
m.AddExtern(std::move(extern1));
|
||||
m.AddExtern(std::move(extern2));
|
||||
|
||||
m.Write(s, ALL_SYMBOL_DATA);
|
||||
string contents = s.str();
|
||||
|
@ -566,13 +565,13 @@ TEST(Module, ConstructDuplicateExterns) {
|
|||
Module m(MODULE_NAME, MODULE_OS, MODULE_ARCH, MODULE_ID);
|
||||
|
||||
// Two externs.
|
||||
Module::Extern* extern1 = new Module::Extern(0xffff);
|
||||
auto extern1 = std::make_unique<Module::Extern>(0xffff);
|
||||
extern1->name = "_xyz";
|
||||
Module::Extern* extern2 = new Module::Extern(0xffff);
|
||||
auto extern2 = std::make_unique<Module::Extern>(0xffff);
|
||||
extern2->name = "_abc";
|
||||
|
||||
m.AddExtern(extern1);
|
||||
m.AddExtern(extern2);
|
||||
m.AddExtern(std::move(extern1));
|
||||
m.AddExtern(std::move(extern2));
|
||||
|
||||
m.Write(s, ALL_SYMBOL_DATA);
|
||||
string contents = s.str();
|
||||
|
@ -589,13 +588,13 @@ TEST(Module, ConstructDuplicateExternsMultiple) {
|
|||
Module m(MODULE_NAME, MODULE_OS, MODULE_ARCH, MODULE_ID, "", true);
|
||||
|
||||
// Two externs.
|
||||
Module::Extern* extern1 = new Module::Extern(0xffff);
|
||||
auto extern1 = std::make_unique<Module::Extern>(0xffff);
|
||||
extern1->name = "_xyz";
|
||||
Module::Extern* extern2 = new Module::Extern(0xffff);
|
||||
auto extern2 = std::make_unique<Module::Extern>(0xffff);
|
||||
extern2->name = "_abc";
|
||||
|
||||
m.AddExtern(extern1);
|
||||
m.AddExtern(extern2);
|
||||
m.AddExtern(std::move(extern1));
|
||||
m.AddExtern(std::move(extern2));
|
||||
|
||||
m.Write(s, ALL_SYMBOL_DATA);
|
||||
string contents = s.str();
|
||||
|
@ -613,13 +612,13 @@ TEST(Module, ConstructFunctionsAndExternsWithSameAddress) {
|
|||
Module m(MODULE_NAME, MODULE_OS, MODULE_ARCH, MODULE_ID);
|
||||
|
||||
// Two externs.
|
||||
Module::Extern* extern1 = new Module::Extern(0xabc0);
|
||||
auto extern1 = std::make_unique<Module::Extern>(0xabc0);
|
||||
extern1->name = "abc";
|
||||
Module::Extern* extern2 = new Module::Extern(0xfff0);
|
||||
auto extern2 = std::make_unique<Module::Extern>(0xfff0);
|
||||
extern2->name = "xyz";
|
||||
|
||||
m.AddExtern(extern1);
|
||||
m.AddExtern(extern2);
|
||||
m.AddExtern(std::move(extern1));
|
||||
m.AddExtern(std::move(extern2));
|
||||
|
||||
Module::Function* function = new Module::Function("_xyz", 0xfff0);
|
||||
Module::Range range(0xfff0, 0x10);
|
||||
|
@ -644,13 +643,13 @@ TEST(Module, ConstructFunctionsAndExternsWithSameAddressMultiple) {
|
|||
Module m(MODULE_NAME, MODULE_OS, MODULE_ARCH, MODULE_ID, "", true);
|
||||
|
||||
// Two externs.
|
||||
Module::Extern* extern1 = new Module::Extern(0xabc0);
|
||||
auto extern1 = std::make_unique<Module::Extern>(0xabc0);
|
||||
extern1->name = "abc";
|
||||
Module::Extern* extern2 = new Module::Extern(0xfff0);
|
||||
auto extern2 = std::make_unique<Module::Extern>(0xfff0);
|
||||
extern2->name = "xyz";
|
||||
|
||||
m.AddExtern(extern1);
|
||||
m.AddExtern(extern2);
|
||||
m.AddExtern(std::move(extern1));
|
||||
m.AddExtern(std::move(extern2));
|
||||
|
||||
Module::Function* function = new Module::Function("_xyz", 0xfff0);
|
||||
Module::Range range(0xfff0, 0x10);
|
||||
|
@ -676,17 +675,17 @@ TEST(Module, ConstructFunctionsAndThumbExternsWithSameAddress) {
|
|||
Module m(MODULE_NAME, MODULE_OS, "arm", MODULE_ID);
|
||||
|
||||
// Two THUMB externs.
|
||||
Module::Extern* thumb_extern1 = new Module::Extern(0xabc1);
|
||||
auto thumb_extern1 = std::make_unique<Module::Extern>(0xabc1);
|
||||
thumb_extern1->name = "thumb_abc";
|
||||
Module::Extern* thumb_extern2 = new Module::Extern(0xfff1);
|
||||
auto thumb_extern2 = std::make_unique<Module::Extern>(0xfff1);
|
||||
thumb_extern2->name = "thumb_xyz";
|
||||
|
||||
Module::Extern* arm_extern1 = new Module::Extern(0xcc00);
|
||||
auto arm_extern1 = std::make_unique<Module::Extern>(0xcc00);
|
||||
arm_extern1->name = "arm_func";
|
||||
|
||||
m.AddExtern(thumb_extern1);
|
||||
m.AddExtern(thumb_extern2);
|
||||
m.AddExtern(arm_extern1);
|
||||
m.AddExtern(std::move(thumb_extern1));
|
||||
m.AddExtern(std::move(thumb_extern2));
|
||||
m.AddExtern(std::move(arm_extern1));
|
||||
|
||||
// The corresponding function from the DWARF debug data have the actual
|
||||
// address.
|
||||
|
|
|
@ -36,6 +36,8 @@
|
|||
#include <stdio.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "common/stabs_to_module.h"
|
||||
#include "common/using_std_string.h"
|
||||
|
@ -132,7 +134,7 @@ bool StabsToModule::Line(uint64_t address, const char *name, int number) {
|
|||
}
|
||||
|
||||
bool StabsToModule::Extern(const string& name, uint64_t address) {
|
||||
Module::Extern *ext = new Module::Extern(address);
|
||||
auto ext = std::make_unique<Module::Extern>(address);
|
||||
// Older libstdc++ demangle implementations can crash on unexpected
|
||||
// input, so be careful about what gets passed in.
|
||||
if (name.compare(0, 3, "__Z") == 0) {
|
||||
|
@ -142,7 +144,7 @@ bool StabsToModule::Extern(const string& name, uint64_t address) {
|
|||
} else {
|
||||
ext->name = name;
|
||||
}
|
||||
module_->AddExtern(ext);
|
||||
module_->AddExtern(std::move(ext));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -65,6 +65,10 @@ typedef enum {
|
|||
MD_EXCEPTION_MAC_MACH_SYSCALL = 8,
|
||||
/* EXC_MACH_SYSCALL */
|
||||
MD_EXCEPTION_MAC_RPC_ALERT = 9,
|
||||
/* EXC_RESOURCE */
|
||||
MD_EXCEPTION_MAC_RESOURCE = 11,
|
||||
/* EXC_GUARD */
|
||||
MD_EXCEPTION_MAC_GUARD = 12,
|
||||
/* EXC_RPC_ALERT */
|
||||
MD_EXCEPTION_MAC_SIMULATED = 0x43507378,
|
||||
/* Fake exception code used by Crashpad's SimulateCrash ('CPsx'). */
|
||||
|
|
|
@ -1096,10 +1096,23 @@ typedef struct {
|
|||
MDRawSimpleStringDictionaryEntry entries[0];
|
||||
} MDRawSimpleStringDictionary;
|
||||
|
||||
typedef struct {
|
||||
MDRVA name;
|
||||
uint16_t type;
|
||||
uint16_t reserved;
|
||||
MDRVA value;
|
||||
} MDRawCrashpadAnnotation;
|
||||
|
||||
typedef struct {
|
||||
uint32_t count;
|
||||
MDLocationDescriptor objects[0]; /* MDRawCrashpadAnnotation */
|
||||
} MDRawCrashpadAnnotationList;
|
||||
|
||||
typedef struct {
|
||||
uint32_t version;
|
||||
MDLocationDescriptor list_annotations;
|
||||
MDLocationDescriptor simple_annotations; /* MDRawSimpleStringDictionary */
|
||||
MDLocationDescriptor annotation_objects; /* MDRawCrashpadAnnotationList */
|
||||
} MDRawModuleCrashpadInfo;
|
||||
|
||||
typedef struct {
|
||||
|
@ -1118,6 +1131,8 @@ typedef struct {
|
|||
MDGUID client_id;
|
||||
MDLocationDescriptor simple_annotations; /* MDRawSimpleStringDictionary */
|
||||
MDLocationDescriptor module_list; /* MDRawModuleCrashpadInfoList */
|
||||
uint32_t reserved;
|
||||
uint64_t address_mask;
|
||||
} MDRawCrashpadInfo;
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
|
|
|
@ -1189,10 +1189,21 @@ class MinidumpLinuxMapsList : public MinidumpStream {
|
|||
// at the time the minidump was generated.
|
||||
class MinidumpCrashpadInfo : public MinidumpStream {
|
||||
public:
|
||||
struct AnnotationObject {
|
||||
uint16_t type;
|
||||
std::string name;
|
||||
std::vector<uint8_t> value;
|
||||
};
|
||||
|
||||
const MDRawCrashpadInfo* crashpad_info() const {
|
||||
return valid_ ? &crashpad_info_ : NULL;
|
||||
}
|
||||
|
||||
const std::vector<std::vector<AnnotationObject>>*
|
||||
GetModuleCrashpadInfoAnnotationObjects() const {
|
||||
return valid_ ? &module_crashpad_info_annotation_objects_ : NULL;
|
||||
}
|
||||
|
||||
// Print a human-readable representation of the object to stdout.
|
||||
void Print();
|
||||
|
||||
|
@ -1211,6 +1222,9 @@ class MinidumpCrashpadInfo : public MinidumpStream {
|
|||
std::vector<std::vector<std::string>> module_crashpad_info_list_annotations_;
|
||||
std::vector<std::map<std::string, std::string>>
|
||||
module_crashpad_info_simple_annotations_;
|
||||
std::vector<std::vector<AnnotationObject>>
|
||||
module_crashpad_info_annotation_objects_;
|
||||
|
||||
std::map<std::string, std::string> simple_annotations_;
|
||||
};
|
||||
|
||||
|
@ -1320,6 +1334,10 @@ class Minidump {
|
|||
off_t offset,
|
||||
std::map<std::string, std::string>* simple_string_dictionary);
|
||||
|
||||
bool ReadCrashpadAnnotationsList(
|
||||
off_t offset,
|
||||
std::vector<MinidumpCrashpadInfo::AnnotationObject>* annotations_list);
|
||||
|
||||
// SeekToStreamType positions the file at the beginning of a stream
|
||||
// identified by stream_type, and informs the caller of the stream's
|
||||
// length by setting *stream_length. Because stream_map maps each stream
|
||||
|
|
|
@ -39,6 +39,8 @@ if (crashpad_is_in_chromium || crashpad_is_in_fuchsia) {
|
|||
if (crashpad_is_in_fuchsia) {
|
||||
# TODO(fuchsia:46559): Fix the leaks and remove this.
|
||||
deps += [ "//build/config/sanitizers:suppress-lsan.DO-NOT-USE-THIS" ]
|
||||
# TODO(fxbug.dev/108368): Remove this once the underlying issue is addressed.
|
||||
exclude_toolchain_tags = [ "hwasan" ]
|
||||
}
|
||||
if (crashpad_is_android) {
|
||||
use_raw_android_executable = true
|
||||
|
|
|
@ -99,7 +99,7 @@ target_compile_definitions(crashpad_interface INTERFACE
|
|||
CRASHPAD_LSS_SOURCE_EMBEDDED
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
if(WIN32)
|
||||
target_compile_definitions(crashpad_interface INTERFACE
|
||||
NOMINMAX
|
||||
UNICODE
|
||||
|
@ -108,6 +108,8 @@ if(MSVC)
|
|||
_HAS_EXCEPTIONS=0
|
||||
_UNICODE
|
||||
)
|
||||
endif()
|
||||
if(MSVC)
|
||||
string(REGEX REPLACE "/[Ww][0123]" "" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
|
||||
string(REGEX REPLACE "/[Ww][0123]" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
|
||||
target_compile_options(crashpad_interface INTERFACE
|
||||
|
@ -135,6 +137,11 @@ elseif(MINGW)
|
|||
# redirect to wmain
|
||||
# FIXME: cmake 3.13 added target_link_options
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -municode")
|
||||
if(CRASHPAD_WER_ENABLED)
|
||||
message(STATUS "WER support enabled")
|
||||
else()
|
||||
message(STATUS "WER support disabled. Define CRASHPAD_WER_ENABLED = TRUE to enable.")
|
||||
endif()
|
||||
endif()
|
||||
add_library(crashpad::interface ALIAS crashpad_interface)
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
vars = {
|
||||
'chromium_git': 'https://chromium.googlesource.com',
|
||||
'gn_version': 'git_revision:2ecd43a10266bd091c98e6dcde507c64f6a0dad3',
|
||||
'gn_version': 'git_revision:5e19d2fb166fbd4f6f32147fbb2f497091a54ad8',
|
||||
# ninja CIPD package version.
|
||||
# https://chrome-infra-packages.appspot.com/p/infra/3pp/tools/ninja
|
||||
'ninja_version': 'version:2@1.8.2.chromium.3',
|
||||
|
|
|
@ -37,7 +37,9 @@ crashpad_is_standalone = crashpad_dependencies == "standalone"
|
|||
# This is the parent directory that contains the mini_chromium source dir.
|
||||
# This variable is not used when crashpad_is_in_chromium.
|
||||
if (crashpad_is_in_fuchsia) {
|
||||
mini_chromium_source_parent = "//third_party/crashpad/third_party/mini_chromium"
|
||||
import("//third_party/crashpad/fuchsia_buildconfig.gni")
|
||||
mini_chromium_source_parent =
|
||||
fuchsia_crashpad_root + "/third_party/mini_chromium"
|
||||
} else {
|
||||
mini_chromium_source_parent = "../third_party/mini_chromium"
|
||||
}
|
||||
|
@ -49,7 +51,7 @@ _mini_chromium_source_root = "$mini_chromium_source_parent/mini_chromium"
|
|||
if (crashpad_is_external || crashpad_is_in_dart) {
|
||||
mini_chromium_import_root = "../../../$_mini_chromium_source_root"
|
||||
} else if (crashpad_is_in_fuchsia) {
|
||||
mini_chromium_import_root = "//third_party/mini_chromium"
|
||||
mini_chromium_import_root = fuchsia_mini_chromium_root
|
||||
} else {
|
||||
mini_chromium_import_root = _mini_chromium_source_root
|
||||
}
|
||||
|
|
|
@ -312,10 +312,10 @@ def _RunOnAndroidTarget(binary_dir, test, android_device, extra_command_line):
|
|||
_adb_shell(['rm', '-rf', device_temp_dir])
|
||||
|
||||
|
||||
def _RunOnIOSTarget(binary_dir, test, is_xcuitest=False):
|
||||
def _RunOnIOSTarget(binary_dir, test, is_xcuitest=False, gtest_filter=None):
|
||||
"""Runs the given iOS |test| app on iPhone 8 with the default OS version."""
|
||||
|
||||
def xctest(binary_dir, test):
|
||||
def xctest(binary_dir, test, gtest_filter=None):
|
||||
"""Returns a dict containing the xctestrun data needed to run an
|
||||
XCTest-based test app."""
|
||||
test_path = os.path.join(CRASHPAD_DIR, binary_dir)
|
||||
|
@ -332,6 +332,8 @@ def _RunOnIOSTarget(binary_dir, test, is_xcuitest=False):
|
|||
'XCInjectBundleInto': '__TESTHOST__/' + test,
|
||||
}
|
||||
}
|
||||
if gtest_filter:
|
||||
module_data['CommandLineArguments'] = ['--gtest_filter='+gtest_filter]
|
||||
return {test: module_data}
|
||||
|
||||
def xcuitest(binary_dir, test):
|
||||
|
@ -368,16 +370,18 @@ def _RunOnIOSTarget(binary_dir, test, is_xcuitest=False):
|
|||
|
||||
xctestrun_path = f.name
|
||||
print(xctestrun_path)
|
||||
command = [
|
||||
'xcodebuild', 'test-without-building', '-xctestrun', xctestrun_path,
|
||||
'-destination', 'platform=iOS Simulator,name=iPhone 8',
|
||||
]
|
||||
with open(xctestrun_path, 'wb') as fp:
|
||||
if is_xcuitest:
|
||||
plistlib.dump(xcuitest(binary_dir, test), fp)
|
||||
if gtest_filter:
|
||||
command.append('-only-testing:' + test + '/' + gtest_filter)
|
||||
else:
|
||||
plistlib.dump(xctest(binary_dir, test), fp)
|
||||
|
||||
subprocess.check_call([
|
||||
'xcodebuild', 'test-without-building', '-xctestrun', xctestrun_path,
|
||||
'-destination', 'platform=iOS Simulator,name=iPhone 8'
|
||||
])
|
||||
plistlib.dump(xctest(binary_dir, test, gtest_filter), fp)
|
||||
subprocess.check_call(command)
|
||||
|
||||
|
||||
# This script is primarily used from the waterfall so that the list of tests
|
||||
|
@ -468,7 +472,8 @@ def main(args):
|
|||
elif is_ios:
|
||||
_RunOnIOSTarget(args.binary_dir,
|
||||
test,
|
||||
is_xcuitest=test.startswith('ios'))
|
||||
is_xcuitest=test.startswith('ios'),
|
||||
gtest_filter=args.gtest_filter)
|
||||
else:
|
||||
subprocess.check_call([os.path.join(args.binary_dir, test)] +
|
||||
extra_command_line)
|
||||
|
|
|
@ -119,6 +119,8 @@ static_library("common") {
|
|||
"crash_report_database.h",
|
||||
"crashpad_info.cc",
|
||||
"crashpad_info.h",
|
||||
"length_delimited_ring_buffer.h",
|
||||
"ring_buffer_annotation.h",
|
||||
"settings.cc",
|
||||
"settings.h",
|
||||
"simple_address_range_bag.h",
|
||||
|
@ -147,6 +149,18 @@ static_library("common") {
|
|||
configs += [ "../build:flock_always_supported_defines" ]
|
||||
}
|
||||
|
||||
crashpad_executable("ring_buffer_annotation_load_test") {
|
||||
testonly = true
|
||||
sources = [
|
||||
"ring_buffer_annotation_load_test_main.cc",
|
||||
]
|
||||
deps = [
|
||||
":client",
|
||||
"../tools:tool_support",
|
||||
"$mini_chromium_source_parent:base",
|
||||
]
|
||||
}
|
||||
|
||||
source_set("client_test") {
|
||||
testonly = true
|
||||
|
||||
|
@ -154,7 +168,9 @@ source_set("client_test") {
|
|||
"annotation_list_test.cc",
|
||||
"annotation_test.cc",
|
||||
"crash_report_database_test.cc",
|
||||
"length_delimited_ring_buffer_test.cc",
|
||||
"prune_crash_reports_test.cc",
|
||||
"ring_buffer_annotation_test.cc",
|
||||
"settings_test.cc",
|
||||
"simple_address_range_bag_test.cc",
|
||||
"simple_string_dictionary_test.cc",
|
||||
|
|
|
@ -8,6 +8,8 @@ add_library(crashpad_client STATIC
|
|||
crashpad_client.h
|
||||
crashpad_info.cc
|
||||
crashpad_info.h
|
||||
length_delimited_ring_buffer.h
|
||||
ring_buffer_annotation.h
|
||||
prune_crash_reports.cc
|
||||
prune_crash_reports.h
|
||||
settings.cc
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <optional>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
@ -26,6 +27,7 @@
|
|||
#include "base/numerics/safe_conversions.h"
|
||||
#include "base/strings/string_piece.h"
|
||||
#include "build/build_config.h"
|
||||
#include "util/synchronization/scoped_spin_guard.h"
|
||||
|
||||
namespace crashpad {
|
||||
#if BUILDFLAG(IS_IOS)
|
||||
|
@ -94,6 +96,20 @@ class Annotation {
|
|||
kUserDefinedStart = 0x8000,
|
||||
};
|
||||
|
||||
//! \brief Mode used to guard concurrent reads from writes.
|
||||
enum class ConcurrentAccessGuardMode : bool {
|
||||
//! \!brief Annotation does not guard reads from concurrent
|
||||
//! writes. Annotation values can be corrupted if the process crashes
|
||||
//! mid-write and the handler tries to read from the Annotation while
|
||||
//! being written to.
|
||||
kUnguarded = false,
|
||||
|
||||
//! \!brief Annotation guards reads from concurrent writes using
|
||||
//! ScopedSpinGuard. Clients must use TryCreateScopedSpinGuard()
|
||||
//! before reading or writing the data in this Annotation.
|
||||
kScopedSpinGuard = true,
|
||||
};
|
||||
|
||||
//! \brief Creates a user-defined Annotation::Type.
|
||||
//!
|
||||
//! This exists to remove the casting overhead of `enum class`.
|
||||
|
@ -132,12 +148,11 @@ class Annotation {
|
|||
//! \param[in] value_ptr A pointer to the value for the annotation. The
|
||||
//! pointer may not be changed once associated with an annotation, but
|
||||
//! the data may be mutated.
|
||||
constexpr Annotation(Type type, const char name[], void* const value_ptr)
|
||||
: link_node_(nullptr),
|
||||
name_(name),
|
||||
value_ptr_(value_ptr),
|
||||
size_(0),
|
||||
type_(type) {}
|
||||
constexpr Annotation(Type type, const char name[], void* value_ptr)
|
||||
: Annotation(type,
|
||||
name,
|
||||
value_ptr,
|
||||
ConcurrentAccessGuardMode::kUnguarded) {}
|
||||
|
||||
Annotation(const Annotation&) = delete;
|
||||
Annotation& operator=(const Annotation&) = delete;
|
||||
|
@ -172,7 +187,58 @@ class Annotation {
|
|||
const char* name() const { return name_; }
|
||||
const void* value() const { return value_ptr_; }
|
||||
|
||||
ConcurrentAccessGuardMode concurrent_access_guard_mode() const {
|
||||
return concurrent_access_guard_mode_;
|
||||
}
|
||||
|
||||
//! \brief If this Annotation guards concurrent access using ScopedSpinGuard,
|
||||
//! tries to obtain the spin guard and returns the result.
|
||||
//!
|
||||
//! \param[in] timeout_ns The timeout in nanoseconds after which to give up
|
||||
//! trying to obtain the spin guard.
|
||||
//! \return std::nullopt if the spin guard could not be obtained within
|
||||
//! timeout_ns, or the obtained spin guard otherwise.
|
||||
std::optional<ScopedSpinGuard> TryCreateScopedSpinGuard(uint64_t timeout_ns) {
|
||||
// This can't use DCHECK_EQ() because ostream doesn't support
|
||||
// operator<<(bool).
|
||||
DCHECK(concurrent_access_guard_mode_ ==
|
||||
ConcurrentAccessGuardMode::kScopedSpinGuard);
|
||||
if (concurrent_access_guard_mode_ ==
|
||||
ConcurrentAccessGuardMode::kUnguarded) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return ScopedSpinGuard::TryCreateScopedSpinGuard(timeout_ns,
|
||||
spin_guard_state_);
|
||||
}
|
||||
|
||||
protected:
|
||||
//! \brief Constructs a new annotation.
|
||||
//!
|
||||
//! Upon construction, the annotation will not be included in any crash
|
||||
//! reports until \sa SetSize() is called with a value greater than `0`.
|
||||
//!
|
||||
//! \param[in] type The data type of the value of the annotation.
|
||||
//! \param[in] name A `NUL`-terminated C-string name for the annotation. Names
|
||||
//! do not have to be unique, though not all crash processors may handle
|
||||
//! Annotations with the same name. Names should be constexpr data with
|
||||
//! static storage duration.
|
||||
//! \param[in] value_ptr A pointer to the value for the annotation. The
|
||||
//! pointer may not be changed once associated with an annotation, but
|
||||
//! the data may be mutated.
|
||||
//! \param[in] concurrent_access_guard_mode Mode used to guard concurrent
|
||||
//! reads from writes.
|
||||
constexpr Annotation(Type type,
|
||||
const char name[],
|
||||
void* value_ptr,
|
||||
ConcurrentAccessGuardMode concurrent_access_guard_mode)
|
||||
: link_node_(nullptr),
|
||||
name_(name),
|
||||
value_ptr_(value_ptr),
|
||||
size_(0),
|
||||
type_(type),
|
||||
concurrent_access_guard_mode_(concurrent_access_guard_mode),
|
||||
spin_guard_state_() {}
|
||||
|
||||
friend class AnnotationList;
|
||||
#if BUILDFLAG(IS_IOS)
|
||||
friend class internal::InProcessIntermediateDumpHandler;
|
||||
|
@ -192,6 +258,11 @@ class Annotation {
|
|||
void* const value_ptr_;
|
||||
ValueSizeType size_;
|
||||
const Type type_;
|
||||
|
||||
//! \brief Mode used to guard concurrent reads from writes.
|
||||
const ConcurrentAccessGuardMode concurrent_access_guard_mode_;
|
||||
|
||||
SpinGuardState spin_guard_state_;
|
||||
};
|
||||
|
||||
//! \brief An \sa Annotation that stores a `NUL`-terminated C-string value.
|
||||
|
|
|
@ -21,11 +21,58 @@
|
|||
#include "client/crashpad_info.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/gtest_death.h"
|
||||
#include "util/misc/clock.h"
|
||||
#include "util/synchronization/scoped_spin_guard.h"
|
||||
#include "util/thread/thread.h"
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
namespace {
|
||||
|
||||
class SpinGuardAnnotation final : public Annotation {
|
||||
public:
|
||||
SpinGuardAnnotation(Annotation::Type type, const char name[])
|
||||
: Annotation(type,
|
||||
name,
|
||||
&value_,
|
||||
ConcurrentAccessGuardMode::kScopedSpinGuard) {}
|
||||
|
||||
bool Set(bool value, uint64_t spin_guard_timeout_ns) {
|
||||
auto guard = TryCreateScopedSpinGuard(spin_guard_timeout_ns);
|
||||
if (!guard) {
|
||||
return false;
|
||||
}
|
||||
value_ = value;
|
||||
SetSize(sizeof(value_));
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
bool value_;
|
||||
};
|
||||
|
||||
class ScopedSpinGuardUnlockThread final : public Thread {
|
||||
public:
|
||||
ScopedSpinGuardUnlockThread(ScopedSpinGuard scoped_spin_guard,
|
||||
uint64_t sleep_time_ns)
|
||||
: scoped_spin_guard_(std::move(scoped_spin_guard)),
|
||||
sleep_time_ns_(sleep_time_ns) {}
|
||||
|
||||
private:
|
||||
void ThreadMain() override {
|
||||
SleepNanoseconds(sleep_time_ns_);
|
||||
|
||||
// Move the ScopedSpinGuard member into a local variable which is
|
||||
// destroyed when ThreadMain() returns.
|
||||
ScopedSpinGuard local_scoped_spin_guard(std::move(scoped_spin_guard_));
|
||||
|
||||
// After this point, local_scoped_spin_guard will be destroyed and unlocked.
|
||||
}
|
||||
|
||||
ScopedSpinGuard scoped_spin_guard_;
|
||||
const uint64_t sleep_time_ns_;
|
||||
};
|
||||
|
||||
class Annotation : public testing::Test {
|
||||
public:
|
||||
void SetUp() override {
|
||||
|
@ -108,6 +155,61 @@ TEST_F(Annotation, StringType) {
|
|||
EXPECT_EQ("loooo", annotation.value());
|
||||
}
|
||||
|
||||
TEST_F(Annotation, BaseAnnotationShouldNotSupportSpinGuard) {
|
||||
char buffer[1024];
|
||||
crashpad::Annotation annotation(
|
||||
crashpad::Annotation::Type::kString, "no-spin-guard", buffer);
|
||||
EXPECT_EQ(annotation.concurrent_access_guard_mode(),
|
||||
crashpad::Annotation::ConcurrentAccessGuardMode::kUnguarded);
|
||||
#ifdef NDEBUG
|
||||
// This fails a DCHECK() in debug builds, so only test it for NDEBUG builds.
|
||||
EXPECT_EQ(std::nullopt, annotation.TryCreateScopedSpinGuard(0));
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_F(Annotation, CustomAnnotationShouldSupportSpinGuardAndSet) {
|
||||
constexpr crashpad::Annotation::Type kType =
|
||||
crashpad::Annotation::UserDefinedType(1);
|
||||
SpinGuardAnnotation spin_guard_annotation(kType, "spin-guard");
|
||||
EXPECT_EQ(spin_guard_annotation.concurrent_access_guard_mode(),
|
||||
crashpad::Annotation::ConcurrentAccessGuardMode::kScopedSpinGuard);
|
||||
EXPECT_TRUE(spin_guard_annotation.Set(true, 0));
|
||||
EXPECT_EQ(1U, spin_guard_annotation.size());
|
||||
}
|
||||
|
||||
TEST_F(Annotation, CustomAnnotationSetShouldFailIfRunConcurrently) {
|
||||
constexpr crashpad::Annotation::Type kType =
|
||||
crashpad::Annotation::UserDefinedType(1);
|
||||
SpinGuardAnnotation spin_guard_annotation(kType, "spin-guard");
|
||||
auto guard = spin_guard_annotation.TryCreateScopedSpinGuard(0);
|
||||
EXPECT_NE(std::nullopt, guard);
|
||||
// This should fail, since the guard is already held and the timeout is 0.
|
||||
EXPECT_FALSE(spin_guard_annotation.Set(true, 0));
|
||||
}
|
||||
|
||||
TEST_F(Annotation,
|
||||
CustomAnnotationSetShouldSucceedIfSpinGuardUnlockedAsynchronously) {
|
||||
constexpr crashpad::Annotation::Type kType =
|
||||
crashpad::Annotation::UserDefinedType(1);
|
||||
SpinGuardAnnotation spin_guard_annotation(kType, "spin-guard");
|
||||
auto guard = spin_guard_annotation.TryCreateScopedSpinGuard(0);
|
||||
EXPECT_NE(std::nullopt, guard);
|
||||
// Pass the guard off to a background thread which unlocks it after 1 ms.
|
||||
constexpr uint64_t kSleepTimeNs = 1000000; // 1 ms
|
||||
ScopedSpinGuardUnlockThread unlock_thread(std::move(guard.value()),
|
||||
kSleepTimeNs);
|
||||
unlock_thread.Start();
|
||||
|
||||
// Try to set the annotation with a 100 ms timeout.
|
||||
constexpr uint64_t kSpinGuardTimeoutNanos = 100000000; // 100 ms
|
||||
|
||||
// This should succeed after 1 ms, since the timeout is much larger than the
|
||||
// time the background thread holds the guard.
|
||||
EXPECT_TRUE(spin_guard_annotation.Set(true, kSpinGuardTimeoutNanos));
|
||||
|
||||
unlock_thread.Join();
|
||||
}
|
||||
|
||||
TEST(StringAnnotation, ArrayOfString) {
|
||||
static crashpad::StringAnnotation<4> annotations[] = {
|
||||
{"test-1", crashpad::StringAnnotation<4>::Tag::kArray},
|
||||
|
|
|
@ -22,12 +22,15 @@
|
|||
#include <time.h>
|
||||
|
||||
#include <iterator>
|
||||
#include <optional>
|
||||
|
||||
#include "build/build_config.h"
|
||||
#include "snapshot/snapshot_constants.h"
|
||||
#include "util/ios/ios_intermediate_dump_writer.h"
|
||||
#include "util/ios/raw_logging.h"
|
||||
#include "util/ios/scoped_vm_map.h"
|
||||
#include "util/ios/scoped_vm_read.h"
|
||||
#include "util/synchronization/scoped_spin_guard.h"
|
||||
|
||||
namespace crashpad {
|
||||
namespace internal {
|
||||
|
@ -672,6 +675,8 @@ void InProcessIntermediateDumpHandler::WriteSystemInfo(
|
|||
IntermediateDumpKey::kDaylightName,
|
||||
daylight_name.c_str(),
|
||||
daylight_name.length());
|
||||
uint64_t address_mask = system_data.AddressMask();
|
||||
WriteProperty(writer, IntermediateDumpKey::kAddressMask, &address_mask);
|
||||
|
||||
vm_size_t page_size;
|
||||
host_page_size(mach_host_self(), &page_size);
|
||||
|
@ -1161,6 +1166,13 @@ void InProcessIntermediateDumpHandler::WriteCrashpadAnnotationsList(
|
|||
IOSIntermediateDumpWriter::ScopedArray annotations_array(
|
||||
writer, IntermediateDumpKey::kAnnotationObjects);
|
||||
ScopedVMRead<Annotation> current;
|
||||
|
||||
// Use vm_read() to ensure that the linked-list AnnotationList head (which is
|
||||
// a dummy node of type kInvalid) is valid and copy its memory into a
|
||||
// newly-allocated buffer.
|
||||
//
|
||||
// In the case where the pointer has been clobbered or the memory range is not
|
||||
// readable, skip reading all the Annotations.
|
||||
if (!current.Read(annotation_list->head())) {
|
||||
CRASHPAD_RAW_LOG("Unable to read annotation");
|
||||
return;
|
||||
|
@ -1171,6 +1183,12 @@ void InProcessIntermediateDumpHandler::WriteCrashpadAnnotationsList(
|
|||
index < kMaxNumberOfAnnotations;
|
||||
++index) {
|
||||
ScopedVMRead<Annotation> node;
|
||||
|
||||
// Like above, use vm_read() to ensure that the node in the linked list is
|
||||
// valid and copy its memory into a newly-allocated buffer.
|
||||
//
|
||||
// In the case where the pointer has been clobbered or the memory range is
|
||||
// not readable, skip reading this and all further Annotations.
|
||||
if (!node.Read(current->link_node())) {
|
||||
CRASHPAD_RAW_LOG("Unable to read annotation");
|
||||
return;
|
||||
|
@ -1185,6 +1203,36 @@ void InProcessIntermediateDumpHandler::WriteCrashpadAnnotationsList(
|
|||
continue;
|
||||
}
|
||||
|
||||
// For Annotations which support guarding reads from concurrent writes, map
|
||||
// their memory read-write using vm_remap(), then declare a ScopedSpinGuard
|
||||
// which lives for the duration of the read.
|
||||
ScopedVMMap<Annotation> mapped_node;
|
||||
std::optional<ScopedSpinGuard> annotation_guard;
|
||||
if (node->concurrent_access_guard_mode() ==
|
||||
Annotation::ConcurrentAccessGuardMode::kScopedSpinGuard) {
|
||||
constexpr vm_prot_t kDesiredProtection = VM_PROT_WRITE | VM_PROT_READ;
|
||||
if (!mapped_node.Map(node.get()) ||
|
||||
(mapped_node.CurrentProtection() & kDesiredProtection) !=
|
||||
kDesiredProtection) {
|
||||
CRASHPAD_RAW_LOG("Unable to map annotation");
|
||||
|
||||
// Skip this annotation rather than giving up entirely, since the linked
|
||||
// node should still be valid.
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO(https://crbug.com/crashpad/438): Pass down a `params` object into
|
||||
// this method to optionally enable a timeout here.
|
||||
constexpr uint64_t kTimeoutNanoseconds = 0;
|
||||
annotation_guard =
|
||||
mapped_node->TryCreateScopedSpinGuard(kTimeoutNanoseconds);
|
||||
if (!annotation_guard) {
|
||||
// This is expected if the process is writing to the Annotation, so
|
||||
// don't log here and skip the annotation.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
IOSIntermediateDumpWriter::ScopedArrayMap annotation_map(writer);
|
||||
WritePropertyCString(writer,
|
||||
IntermediateDumpKey::kAnnotationName,
|
||||
|
|
603
thirdparty/sentry-native/external/crashpad/client/length_delimited_ring_buffer.h
vendored
Normal file
603
thirdparty/sentry-native/external/crashpad/client/length_delimited_ring_buffer.h
vendored
Normal file
|
@ -0,0 +1,603 @@
|
|||
// Copyright 2023 The Crashpad Authors
|
||||
//
|
||||
// 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_CLIENT_LENGTH_DELIMITED_RING_BUFFER_H_
|
||||
#define CRASHPAD_CLIENT_LENGTH_DELIMITED_RING_BUFFER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "base/numerics/safe_math.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
//! \brief Capacity of a `RingBufferData`, in bytes.
|
||||
using RingBufferCapacity = uint32_t;
|
||||
|
||||
namespace internal {
|
||||
|
||||
//! \brief Default capacity of `RingBufferData`, in bytes.
|
||||
inline constexpr RingBufferCapacity kDefaultRingBufferDataCapacity = 8192;
|
||||
|
||||
//! \brief A tuple holding the current range of bytes which can be read from or
|
||||
//! have been written to.
|
||||
struct Range final {
|
||||
//! \brief The offset into a `RingBufferData` at which a `Range` begins.
|
||||
using Offset = uint32_t;
|
||||
|
||||
//! \brief The length inside a `RingBufferData` of a `Range` of data.
|
||||
using Length = uint32_t;
|
||||
|
||||
Offset offset;
|
||||
Length length;
|
||||
};
|
||||
|
||||
// This struct is persisted to disk, so its size must not change.
|
||||
static_assert(sizeof(Range) == 8,
|
||||
"struct Range is not packed on this platform");
|
||||
|
||||
//! \brief The number of bits encoded in each byte of a Base 128-encoded varint.
|
||||
inline constexpr int kBase128ByteValueBits = 7;
|
||||
|
||||
//! \!brief Calculates the length in bytes of `value` encoded using
|
||||
//! little-endian Base 128 varint encoding.
|
||||
//! \sa https://developers.google.com/protocol-buffers/docs/encoding#varints
|
||||
//!
|
||||
//! `LengthDelimitedRingBufferWriter` uses varint-encoded delimiters to enable
|
||||
//! zero-copy deserialization of the ringbuffer's contents when storing
|
||||
//! protobufs inside the ringbuffer, e.g. via
|
||||
//! `google::protobuf::util::ParseDelimitedFromZeroCopyStream()` or similar.
|
||||
//!
|
||||
//! \sa
|
||||
//! https://github.com/protocolbuffers/protobuf/blob/3202b9da88ceb75b65bbabaf4033c95e872f828d/src/google/protobuf/util/delimited_message_util.h#L85
|
||||
//! \sa
|
||||
//! https://github.com/protocolbuffers/protobuf/blob/8bd49dea5e167a389d94b71d24c981d8f9fa0c99/src/google/protobuf/io/zero_copy_stream_impl_lite.h#L68
|
||||
//! \sa
|
||||
//! https://github.com/protocolbuffers/protobuf/blob/8bd49dea5e167a389d94b71d24c981d8f9fa0c99/src/google/protobuf/io/coded_stream.h#L171
|
||||
//!
|
||||
//! \!param[in] value Value to be encoded in Base 128 varint encoding.
|
||||
//! \!return The length in bytes of `value` in Base 128 varint encoding.
|
||||
template <typename IntegerType>
|
||||
constexpr Range::Length Base128VarintEncodedLength(IntegerType value) {
|
||||
static_assert(std::is_unsigned<IntegerType>::value);
|
||||
|
||||
Range::Length size = 1;
|
||||
while (value >= 0x80) {
|
||||
value >>= kBase128ByteValueBits;
|
||||
size++;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
// Note that std::array capacity is a size_t, not a RingBufferCapacity.
|
||||
template <size_t ArrayCapacity>
|
||||
using RingBufferArray = std::array<uint8_t, ArrayCapacity>;
|
||||
|
||||
//! \return The size of the `RingBufferArray` as a `Range::Length`.
|
||||
template <size_t ArrayCapacity>
|
||||
constexpr Range::Length RingBufferArraySize(
|
||||
const RingBufferArray<ArrayCapacity>& ring_buffer_data) {
|
||||
static_assert(ArrayCapacity <= std::numeric_limits<Range::Length>::max());
|
||||
return static_cast<Range::Length>(ring_buffer_data.size());
|
||||
}
|
||||
|
||||
//! \brief Reads data from the ring buffer into a target buffer.
|
||||
//! \param[in] ring_buffer_data The ring buffer to read.
|
||||
//! \param[in,out] ring_buffer_read_range The range of the data available
|
||||
//! to read. Upon return, set to the remaining range of data available
|
||||
//! to read, if any.
|
||||
//! \param[in] target_buffer Buffer into which data will be written.
|
||||
//! \param[in] target_buffer_length Number of bytes to write into
|
||||
//! `target_buffer`.
|
||||
//!
|
||||
//! \return `true` if the read succeeded, `false` otherwise. On success, updates
|
||||
//! `ring_buffer_read_range` to reflect the bytes consumed.
|
||||
//!
|
||||
//! The bytes can wrap around the end of the ring buffer, in which case the read
|
||||
//! continues at the beginning of the ring buffer (if the ring buffer is long
|
||||
//! enough).
|
||||
template <typename RingBufferArrayType>
|
||||
bool ReadBytesFromRingBuffer(const RingBufferArrayType& ring_buffer_data,
|
||||
internal::Range& ring_buffer_read_range,
|
||||
uint8_t* target_buffer,
|
||||
Range::Length target_buffer_length) {
|
||||
if (target_buffer_length > ring_buffer_read_range.length) {
|
||||
return false;
|
||||
}
|
||||
if (target_buffer_length == 0) {
|
||||
return true;
|
||||
}
|
||||
const Range::Length initial_read_length = std::min(
|
||||
target_buffer_length,
|
||||
RingBufferArraySize(ring_buffer_data) - ring_buffer_read_range.offset);
|
||||
memcpy(target_buffer,
|
||||
&ring_buffer_data[ring_buffer_read_range.offset],
|
||||
initial_read_length);
|
||||
if (initial_read_length < target_buffer_length) {
|
||||
const Range::Length remaining_read_length =
|
||||
target_buffer_length - initial_read_length;
|
||||
memcpy(target_buffer + initial_read_length,
|
||||
&ring_buffer_data[0],
|
||||
remaining_read_length);
|
||||
}
|
||||
ring_buffer_read_range.offset =
|
||||
(ring_buffer_read_range.offset + target_buffer_length) %
|
||||
RingBufferArraySize(ring_buffer_data);
|
||||
ring_buffer_read_range.length -= target_buffer_length;
|
||||
return true;
|
||||
}
|
||||
|
||||
//! \brief Reads a single little-endian Base 128 varint-encoded integer from
|
||||
//! the ring buffer.
|
||||
//! \param[in] ring_buffer_data The ring buffer to read.
|
||||
//! \param[in,out] ring_buffer_read_range The range of the data available
|
||||
//! to read. Upon return, set to the remaining range of data available
|
||||
//! to read, if any.
|
||||
//! \param[out] result Upon success, set to the decoded value read from the
|
||||
//! buffer.
|
||||
//!
|
||||
//! \return The length in bytes of the varint if the read succeeded,
|
||||
//! `std::nullopt` otherwise. On success, updates `ring_buffer_read_range`
|
||||
//! to reflect the bytes available to read.
|
||||
//!
|
||||
//! The varint can wrap around the end of the ring buffer, in which case the
|
||||
//! read continues at the beginning of the ring buffer (if the ring buffer is
|
||||
//! long enough).
|
||||
template <typename RingBufferArrayType, typename IntegerType>
|
||||
std::optional<Range::Length> ReadBase128VarintFromRingBuffer(
|
||||
const RingBufferArrayType& ring_buffer_data,
|
||||
internal::Range& ring_buffer_read_range,
|
||||
IntegerType& result) {
|
||||
static_assert(std::is_unsigned<IntegerType>::value);
|
||||
|
||||
result = 0;
|
||||
uint8_t cur_varint_byte = 0;
|
||||
constexpr uint8_t kValueMask = 0x7f;
|
||||
constexpr uint8_t kContinuationMask = 0x80;
|
||||
Range::Length length = 0;
|
||||
do {
|
||||
if (!ReadBytesFromRingBuffer(
|
||||
ring_buffer_data, ring_buffer_read_range, &cur_varint_byte, 1)) {
|
||||
// No capacity remaining in `ring_buffer_read_range` to read the varint.
|
||||
return std::nullopt;
|
||||
}
|
||||
IntegerType cur_varint_value =
|
||||
static_cast<IntegerType>(cur_varint_byte & kValueMask);
|
||||
|
||||
// This is equivalent to:
|
||||
//
|
||||
// result |= (cur_varint_value << (length * kBase128ByteValueBits));
|
||||
//
|
||||
// but checks the result at each step for overflow, which handles two types
|
||||
// of invalid input:
|
||||
//
|
||||
// 1) Too many bytes with kContinuationMask set (e.g., trying to encode 6
|
||||
// bytes worth of data in a 32-bit value)
|
||||
// 2) Too many bits in the final byte (e.g., the 5th byte for a 32-bit value
|
||||
// has bits 33 and 34 set)
|
||||
IntegerType next_result_bits;
|
||||
if (!base::CheckLsh(cur_varint_value, length * kBase128ByteValueBits)
|
||||
.AssignIfValid(&next_result_bits)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
result |= next_result_bits;
|
||||
++length;
|
||||
} while ((cur_varint_byte & kContinuationMask) == kContinuationMask);
|
||||
return length;
|
||||
}
|
||||
|
||||
//! \brief Writes data from the source buffer into the ring buffer.
|
||||
//! \param[in] source_buffer Buffer from which data will be read.
|
||||
//! \param[in] source_buffer_length The length in bytes of `source_buffer`.
|
||||
//! \param[in] ring_buffer_data The ring buffer into which data will be read.
|
||||
//! \param[in,out] ring_buffer_write_range The range of the data available
|
||||
//! to write. Upon return, set to the remaining range of data available
|
||||
//! to write, if any.
|
||||
//!
|
||||
//! \return `true` if write read succeeded, `false` otherwise. On success,
|
||||
//! updates
|
||||
//! `ring_buffer_write_range` to reflect the bytes written.
|
||||
//!
|
||||
//! The bytes can wrap around the end of the ring buffer, in which case the
|
||||
//! write continues at the beginning of the ring buffer (if the ring buffer is
|
||||
//! long enough).
|
||||
template <typename RingBufferArrayType>
|
||||
bool WriteBytesToRingBuffer(const uint8_t* const source_buffer,
|
||||
Range::Length source_buffer_length,
|
||||
RingBufferArrayType& ring_buffer_data,
|
||||
internal::Range& ring_buffer_write_range) {
|
||||
const Range::Length ring_buffer_bytes_remaining =
|
||||
RingBufferArraySize(ring_buffer_data) - ring_buffer_write_range.length;
|
||||
if (source_buffer_length > ring_buffer_bytes_remaining) {
|
||||
return false;
|
||||
}
|
||||
const Range::Length initial_write_length = std::min(
|
||||
source_buffer_length,
|
||||
RingBufferArraySize(ring_buffer_data) - ring_buffer_write_range.offset);
|
||||
memcpy(&ring_buffer_data[ring_buffer_write_range.offset],
|
||||
source_buffer,
|
||||
initial_write_length);
|
||||
if (initial_write_length < source_buffer_length) {
|
||||
const Range::Length remaining_write_length =
|
||||
source_buffer_length - initial_write_length;
|
||||
memcpy(&ring_buffer_data[0],
|
||||
source_buffer + initial_write_length,
|
||||
remaining_write_length);
|
||||
}
|
||||
ring_buffer_write_range.offset =
|
||||
(ring_buffer_write_range.offset + source_buffer_length) %
|
||||
RingBufferArraySize(ring_buffer_data);
|
||||
ring_buffer_write_range.length -= source_buffer_length;
|
||||
return true;
|
||||
}
|
||||
|
||||
//! \brief Writes a single Base 128 varint-encoded little-endian unsigned
|
||||
//! integer into the ring buffer.
|
||||
//! \param[in] value The value to encode and write into the ring buffer.
|
||||
//! \param[in] ring_buffer_data The ring buffer into which to write.
|
||||
//! \param[in,out] ring_buffer_write_range The range of the data available
|
||||
//! to write. Upon return, set to the remaining range of data available
|
||||
//! to write, if any.
|
||||
//!
|
||||
//! \return The length in bytes of the varint if the write succeeded,
|
||||
//! `std::nullopt` otherwise. On success, updates `write_buffer_read_range`
|
||||
//! to reflect the range available to write, if any.
|
||||
//!
|
||||
//! The varint can wrap around the end of the ring buffer, in which case the
|
||||
//! write continues at the beginning of the ring buffer (if the ring buffer is
|
||||
//! long enough).
|
||||
template <typename RingBufferArrayType, typename IntegerType>
|
||||
std::optional<int> WriteBase128VarintToRingBuffer(
|
||||
IntegerType value,
|
||||
RingBufferArrayType& ring_buffer_data,
|
||||
internal::Range& ring_buffer_write_range) {
|
||||
static_assert(std::is_unsigned<IntegerType>::value);
|
||||
|
||||
uint8_t cur_varint_byte;
|
||||
constexpr uint8_t kValueMask = 0x7f;
|
||||
constexpr uint8_t kContinuationMask = 0x80;
|
||||
|
||||
// Every varint encodes to at least 1 byte of data.
|
||||
int length = 1;
|
||||
|
||||
while (value > kValueMask) {
|
||||
cur_varint_byte =
|
||||
(static_cast<uint8_t>(value) & kValueMask) | kContinuationMask;
|
||||
if (!WriteBytesToRingBuffer(
|
||||
&cur_varint_byte, 1, ring_buffer_data, ring_buffer_write_range)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
value >>= kBase128ByteValueBits;
|
||||
++length;
|
||||
}
|
||||
cur_varint_byte = static_cast<uint8_t>(value);
|
||||
if (!WriteBytesToRingBuffer(
|
||||
&cur_varint_byte, 1, ring_buffer_data, ring_buffer_write_range)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
//! \brief Storage for a ring buffer which can hold up to
|
||||
//! `RingBufferCapacity`
|
||||
//! bytes of Base 128-varint delimited variable-length items.
|
||||
//!
|
||||
//! This struct contains a header immediately followed by the ring buffer
|
||||
//! data. The current read offset and length are stored in `header.data_range`.
|
||||
//!
|
||||
//! The structure of this object is:
|
||||
//!
|
||||
//! `|magic|version|data_offset|data_length|ring_buffer_data|`
|
||||
//!
|
||||
//! To write data to this object, see `LengthDelimitedRingBufferWriter`.
|
||||
//! To read data from this object, see `LengthDelimitedRingBufferReader`.
|
||||
//!
|
||||
//! The bytes of this structure are suitable for direct serialization from
|
||||
//! memory to disk, e.g. as a crashpad::Annotation.
|
||||
template <RingBufferCapacity Capacity>
|
||||
struct RingBufferData final {
|
||||
RingBufferData() = default;
|
||||
RingBufferData(RingBufferData&) = delete;
|
||||
RingBufferData& operator=(RingBufferData&) = delete;
|
||||
|
||||
//! \brief The type of the array holding the data in this object.
|
||||
using RingBufferArrayType = internal::RingBufferArray<Capacity>;
|
||||
|
||||
//! \brief The type of the size in bytes of operations on this object.
|
||||
using SizeType = internal::Range::Length;
|
||||
|
||||
//! \brief Attempts to overwrite the contents of this object by deserializing
|
||||
//! the buffer into this object.
|
||||
//! \param[in] buffer The bytes to deserialize into this object.
|
||||
//! \param[in] length The length in bytes of `buffer`.
|
||||
//!
|
||||
//! \return `true` if the buffer was a valid RingBufferData and this object
|
||||
//! has enough capacity to store its bytes, `false` otherwise.
|
||||
bool DeserializeFromBuffer(const void* buffer, SizeType length) {
|
||||
if (length < sizeof(header) || length > sizeof(header) + sizeof(data)) {
|
||||
return false;
|
||||
}
|
||||
const Header* other_header = reinterpret_cast<const Header*>(buffer);
|
||||
if (other_header->magic != kMagic || other_header->version != kVersion) {
|
||||
return false;
|
||||
}
|
||||
header.data_range = other_header->data_range;
|
||||
const uint8_t* other_ring_buffer_bytes =
|
||||
reinterpret_cast<const uint8_t*>(buffer) + sizeof(*other_header);
|
||||
const SizeType other_ring_buffer_len = length - sizeof(*other_header);
|
||||
memcpy(&data[0], other_ring_buffer_bytes, other_ring_buffer_len);
|
||||
return true;
|
||||
}
|
||||
|
||||
//! \return The current length in bytes of the data written to the ring
|
||||
//! buffer.
|
||||
SizeType GetRingBufferLength() const {
|
||||
internal::Range data_range = header.data_range;
|
||||
return sizeof(header) + std::min(internal::RingBufferArraySize(data),
|
||||
data_range.offset + data_range.length);
|
||||
}
|
||||
|
||||
//! \brief Resets the state of the ring buffer (e.g., for testing).
|
||||
void ResetForTesting() { header.data_range = {0, 0}; }
|
||||
|
||||
//! \brief The magic signature of the ring buffer.
|
||||
static constexpr uint32_t kMagic = 0xcab00d1e;
|
||||
//! \brief The version of the ring buffer.
|
||||
static constexpr uint32_t kVersion = 1;
|
||||
|
||||
//! \brief A header containing metadata preceding the ring buffer data.
|
||||
struct Header final {
|
||||
Header() : magic(kMagic), version(kVersion), data_range({0, 0}) {}
|
||||
|
||||
//! \brief The fixed magic value identifying this as a ring buffer.
|
||||
const uint32_t magic;
|
||||
|
||||
//! \brief The version of this ring buffer data.
|
||||
const uint32_t version;
|
||||
|
||||
//! \brief The range of readable data in the ring buffer.
|
||||
internal::Range data_range;
|
||||
};
|
||||
|
||||
//! \brief The header containing ring buffer metadata.
|
||||
Header header;
|
||||
|
||||
//! \brief The bytes of the ring buffer data.
|
||||
RingBufferArrayType data;
|
||||
|
||||
// This struct is persisted to disk, so its size must not change.
|
||||
static_assert(sizeof(Header) == 16);
|
||||
static_assert(Capacity <= std::numeric_limits<uint32_t>::max());
|
||||
};
|
||||
|
||||
// Ensure the ring buffer is packed correctly at its default capacity.
|
||||
static_assert(
|
||||
sizeof(RingBufferData<internal::kDefaultRingBufferDataCapacity>) ==
|
||||
16 + internal::kDefaultRingBufferDataCapacity);
|
||||
|
||||
// Allow just `RingBufferData foo;` to be declared without template arguments
|
||||
// using C++17 class template argument deduction.
|
||||
template <
|
||||
RingBufferCapacity Capacity = internal::kDefaultRingBufferDataCapacity>
|
||||
RingBufferData() -> RingBufferData<Capacity>;
|
||||
|
||||
//! \brief Reads variable-length data buffers from a `RingBufferData`,
|
||||
//! delimited by Base128 varint-encoded length delimiters.
|
||||
//!
|
||||
//! Holds a reference to a `RingBufferData` with the capacity to hold
|
||||
//! `RingBufferDataType::size()` bytes of variable-length buffers each
|
||||
//! preceded by its length (encoded as a Base128 length varint).
|
||||
//!
|
||||
//! Provides reading capabilities via `Pop()`.
|
||||
template <typename RingBufferDataType>
|
||||
class LengthDelimitedRingBufferReader final {
|
||||
public:
|
||||
//! \brief Constructs a reader which holds a reference to `ring_buffer`.
|
||||
//! \param[in] ring_buffer The ring buffer from which data will be read.
|
||||
//! This object must outlive the lifetime of `ring_buffer`.
|
||||
constexpr explicit LengthDelimitedRingBufferReader(
|
||||
RingBufferDataType& ring_buffer)
|
||||
: ring_buffer_(ring_buffer),
|
||||
data_range_(ring_buffer_.header.data_range) {}
|
||||
|
||||
LengthDelimitedRingBufferReader(const LengthDelimitedRingBufferReader&) =
|
||||
delete;
|
||||
LengthDelimitedRingBufferReader& operator=(
|
||||
const LengthDelimitedRingBufferReader&) = delete;
|
||||
|
||||
//! \brief Pops off the next buffer from the front of the ring buffer.
|
||||
//!
|
||||
//! \param[in] target_buffer On success, the buffer into which data will
|
||||
//! be read.
|
||||
//! \return On success, returns `true` and advances `ring_buffer.data_range`
|
||||
//! past the end of the buffer read. Otherwise, returns `false`.
|
||||
bool Pop(std::vector<uint8_t>& target_buffer) {
|
||||
return PopWithRange(target_buffer, data_range_);
|
||||
}
|
||||
|
||||
//! \brief Resets the state of the reader (e.g., for testing).
|
||||
void ResetForTesting() { data_range_ = {0, 0}; }
|
||||
|
||||
private:
|
||||
//! \brief Pops off the next buffer from the front of the ring buffer.
|
||||
//! \param[in] target_buffer On success, the buffer into which data will
|
||||
//! be read.
|
||||
//! \param[in,out] data_range The range of data available to read.
|
||||
//! On success, updated to the remaining range avilable to read.
|
||||
//! \return On success, returns `true` and advances `ring_buffer.data_range`
|
||||
//! past the end of the buffer read. Otherwise, returns `false`.
|
||||
bool PopWithRange(std::vector<uint8_t>& target_buffer,
|
||||
internal::Range& data_range) {
|
||||
internal::Range::Length buffer_length;
|
||||
if (!ReadBase128VarintFromRingBuffer(
|
||||
ring_buffer_.data, data_range, buffer_length)) {
|
||||
return false;
|
||||
}
|
||||
if (buffer_length == 0) {
|
||||
// A zero-length buffer means the buffer was truncated in the middle of a
|
||||
// Push().
|
||||
return false;
|
||||
}
|
||||
const auto previous_target_buffer_size = target_buffer.size();
|
||||
target_buffer.resize(previous_target_buffer_size + buffer_length);
|
||||
if (!ReadBytesFromRingBuffer(ring_buffer_.data,
|
||||
data_range,
|
||||
&target_buffer[previous_target_buffer_size],
|
||||
buffer_length)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//! \brief Reference to the ring buffer from which data is read.
|
||||
const RingBufferDataType& ring_buffer_;
|
||||
//! \brief Range of data currently available to read.
|
||||
internal::Range data_range_;
|
||||
};
|
||||
|
||||
// Allow just `LengthDelimitedRingBufferReader reader(foo);` to be declared
|
||||
// without template arguments using C++17 class template argument deduction.
|
||||
template <typename RingBufferDataType>
|
||||
LengthDelimitedRingBufferReader(RingBufferDataType&)
|
||||
-> LengthDelimitedRingBufferReader<RingBufferDataType>;
|
||||
|
||||
//! \brief Writes variable-length data buffers to a `RingBufferData`,
|
||||
//! delimited by Base128 varint-encoded length delimiters.
|
||||
//!
|
||||
//! Holds a reference to a `RingBufferData` with the capacity to hold
|
||||
//! `RingBufferDataType::size()` bytes of variable-length buffers each
|
||||
//! preceded by its length (encoded as a Base128 length varint).
|
||||
//!
|
||||
//! Provides writing capabilities via `Push()`.
|
||||
template <typename RingBufferDataType>
|
||||
class LengthDelimitedRingBufferWriter final {
|
||||
public:
|
||||
//! \brief Constructs a writer which holds a reference to `ring_buffer`.
|
||||
//! \param[in] ring_buffer The ring buffer into which data will be written.
|
||||
//! This object must outlive the lifetime of `ring_buffer`.
|
||||
constexpr explicit LengthDelimitedRingBufferWriter(
|
||||
RingBufferDataType& ring_buffer)
|
||||
: ring_buffer_(ring_buffer), ring_buffer_write_offset_(0) {}
|
||||
|
||||
LengthDelimitedRingBufferWriter(const LengthDelimitedRingBufferWriter&) =
|
||||
delete;
|
||||
LengthDelimitedRingBufferWriter& operator=(
|
||||
const LengthDelimitedRingBufferWriter&) = delete;
|
||||
|
||||
//! \brief Writes data to the ring buffer.
|
||||
//!
|
||||
//! If there is not enough room remaining in the ring buffer to store the new
|
||||
//! data, old data will be removed from the ring buffer in FIFO order until
|
||||
//! there is room for the new data.
|
||||
//!
|
||||
//! \param[in] buffer The data to be written.
|
||||
//! \param[in] buffer_length The lengh of `buffer`, in bytes.
|
||||
//! \return On success, returns `true`, updates `ring_buffer.data_range`
|
||||
//! to reflect the remaining data available to read, and updates
|
||||
//! `ring_buffer_write_offset_` to reflec the current write positionl.
|
||||
//! Otherwise, returns `false`.
|
||||
bool Push(const void* const buffer,
|
||||
typename RingBufferDataType::SizeType buffer_length) {
|
||||
if (buffer_length == 0) {
|
||||
// Pushing a zero-length buffer is not allowed
|
||||
// (`LengthDelimitedRingBufferWriter` reserves that to represent a
|
||||
// temporarily truncated item below).
|
||||
return false;
|
||||
}
|
||||
const internal::Range::Length buffer_varint_encoded_length =
|
||||
internal::Base128VarintEncodedLength(buffer_length);
|
||||
const internal::Range::Length bytes_needed =
|
||||
buffer_varint_encoded_length + buffer_length;
|
||||
if (bytes_needed > ring_buffer_.data.size()) {
|
||||
return false;
|
||||
}
|
||||
// If needed, move the readable region forward one buffer at a time to make
|
||||
// room for `buffer_length` bytes of new data.
|
||||
auto readable_data_range = ring_buffer_.header.data_range;
|
||||
internal::Range::Length bytes_available =
|
||||
internal::RingBufferArraySize(ring_buffer_.data) -
|
||||
readable_data_range.length;
|
||||
while (bytes_available < bytes_needed) {
|
||||
internal::Range::Length bytes_to_skip;
|
||||
auto varint_length = ReadBase128VarintFromRingBuffer(
|
||||
ring_buffer_.data, readable_data_range, bytes_to_skip);
|
||||
if (!varint_length.has_value()) {
|
||||
return false;
|
||||
}
|
||||
// Skip past the next entry including its prepended varint length.
|
||||
readable_data_range.offset =
|
||||
(readable_data_range.offset + bytes_to_skip) %
|
||||
internal::RingBufferArraySize(ring_buffer_.data);
|
||||
readable_data_range.length -= bytes_to_skip;
|
||||
bytes_available += varint_length.value() + bytes_to_skip;
|
||||
}
|
||||
// Write the varint containing `buffer_length` to the current write
|
||||
// position.
|
||||
internal::Range write_range = {
|
||||
ring_buffer_write_offset_,
|
||||
bytes_needed,
|
||||
};
|
||||
|
||||
internal::WriteBase128VarintToRingBuffer(
|
||||
buffer_length, ring_buffer_.data, write_range);
|
||||
// Next, write the bytes from `buffer`.
|
||||
internal::WriteBytesToRingBuffer(
|
||||
reinterpret_cast<const uint8_t* const>(buffer),
|
||||
buffer_length,
|
||||
ring_buffer_.data,
|
||||
write_range);
|
||||
// Finally, update the write position and read data range taking into
|
||||
// account any items skipped to make room plus the new buffer's varint
|
||||
// length and the new buffer's length.
|
||||
ring_buffer_write_offset_ = write_range.offset;
|
||||
const internal::Range final_data_range = {
|
||||
readable_data_range.offset,
|
||||
readable_data_range.length + bytes_needed,
|
||||
};
|
||||
ring_buffer_.header.data_range = final_data_range;
|
||||
return true;
|
||||
}
|
||||
|
||||
//! \brief Resets the state of the ring buffer and writer (e.g., for testing).
|
||||
void ResetForTesting() {
|
||||
ring_buffer_.ResetForTesting();
|
||||
ring_buffer_write_offset_ = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
//! \brief Reference to the ring buffer from which data is written.
|
||||
RingBufferDataType& ring_buffer_;
|
||||
|
||||
// \brief Current write position next time `Push()` is invoked.
|
||||
internal::Range::Offset ring_buffer_write_offset_;
|
||||
};
|
||||
|
||||
// Allow just `LengthDelimitedRingBufferWriter writer(foo);` to be declared
|
||||
// without template arguments using C++17 class template argument deduction.
|
||||
template <typename RingBufferDataType>
|
||||
LengthDelimitedRingBufferWriter(RingBufferDataType&)
|
||||
-> LengthDelimitedRingBufferWriter<RingBufferDataType>;
|
||||
|
||||
} // namespace crashpad
|
||||
|
||||
#endif // CRASHPAD_CLIENT_LENGTH_DELIMITED_RING_BUFFER_H_
|
348
thirdparty/sentry-native/external/crashpad/client/length_delimited_ring_buffer_test.cc
vendored
Normal file
348
thirdparty/sentry-native/external/crashpad/client/length_delimited_ring_buffer_test.cc
vendored
Normal file
|
@ -0,0 +1,348 @@
|
|||
// Copyright 2023 The Crashpad Authors
|
||||
//
|
||||
// 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.
|
||||
|
||||
#include "client/length_delimited_ring_buffer.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
namespace {
|
||||
|
||||
using testing::Eq;
|
||||
using testing::IsFalse;
|
||||
using testing::IsTrue;
|
||||
|
||||
// Buffer with magic 0xcab00d1e, version 1, read_pos 0, length 3, and 3 bytes of
|
||||
// data (1 varint length, 2 bytes data)
|
||||
constexpr char kValidBufferSize3[] =
|
||||
"\x1e\x0d\xb0\xca\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x02\x42"
|
||||
"\x23";
|
||||
constexpr size_t kValidBufferSize3Len =
|
||||
sizeof(kValidBufferSize3) - 1; // Remove trailing NUL.
|
||||
|
||||
// Buffer with magic 0xcab00d1e, version 8, read_pos 0, length 3, and 3 bytes of
|
||||
// data (1 varint length, 2 bytes data).
|
||||
constexpr char kInvalidVersionBuffer[] =
|
||||
"\x1e\x0d\xb0\xca\x08\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x02\xab"
|
||||
"\xcd";
|
||||
constexpr size_t kInvalidVersionBufferLen =
|
||||
sizeof(kInvalidVersionBuffer) - 1; // Remove trailing NUL.
|
||||
|
||||
// Buffer representing process which crashed while in the middle of a Push()
|
||||
// operation, with a previously-Push()ed buffer whose length was zeroed out at
|
||||
// the start.
|
||||
constexpr char kMidCrashBuffer[] =
|
||||
"\x1e\x0d\xb0\xca\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x42"
|
||||
"\x23";
|
||||
constexpr size_t kMidCrashBufferLen =
|
||||
sizeof(kMidCrashBuffer) - 1; // Remove trailing NUL.
|
||||
|
||||
constexpr uint8_t kHello[] = {0x68, 0x65, 0x6c, 0x6c, 0x6f};
|
||||
|
||||
// Invalid buffer containing malformed varint in data payload (Base 128 varint
|
||||
// with length 6, which would represent a data length > 32 bits).
|
||||
constexpr char kInvalidBase128VarintBuffer[] =
|
||||
"\x1e\x0d\xb0\xca\x01\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x80\x80"
|
||||
"\x80\x80\x80\x01";
|
||||
constexpr size_t kInvalidBase128VarintBufferLen =
|
||||
sizeof(kInvalidBase128VarintBuffer) - 1; // Remove trailing NUL.
|
||||
|
||||
// Invalid buffer containing malformed varint in data payload (Base 128 varint
|
||||
// with length 5 but bits 33 and 34 set, which would represent a data length >
|
||||
// 32 bits).
|
||||
constexpr char kInvalidBase128VarintBits33And34SetBuffer[] =
|
||||
"\x1e\x0d\xb0\xca\x01\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x80\x80"
|
||||
"\x80\x80\x60";
|
||||
constexpr size_t kInvalidBase128VarintBits33And34SetBufferLen =
|
||||
sizeof(kInvalidBase128VarintBits33And34SetBuffer) -
|
||||
1; // Remove trailing NUL.
|
||||
|
||||
// Invalid buffer containing too-short data payload (varint length indicates
|
||||
// payload length is 4 but payload only contains 3 bytes).
|
||||
constexpr char kInvalidPayloadBufferTooShort[] =
|
||||
"\x1e\x0d\xb0\xca\x01\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x04"
|
||||
"\x42\x42\x42";
|
||||
constexpr size_t kInvalidPayloadBufferTooShortLen =
|
||||
sizeof(kInvalidPayloadBufferTooShort) - 1; // Remove trailing NUL.
|
||||
|
||||
TEST(LengthDelimitedRingBufferTest,
|
||||
RingBufferDataShouldStartWithMagicAndVersion) {
|
||||
RingBufferData ring_buffer;
|
||||
const void* ring_buffer_bytes = static_cast<const void*>(&ring_buffer);
|
||||
EXPECT_THAT(memcmp(ring_buffer_bytes, "\x1e\x0d\xb0\xca\x01\x00\x00\x00", 8),
|
||||
Eq(0));
|
||||
}
|
||||
|
||||
TEST(LengthDelimitedRingBufferTest,
|
||||
EmptyBufferSizeShouldIncludeHeaderInRingBufferLength) {
|
||||
RingBufferData ring_buffer;
|
||||
EXPECT_THAT(ring_buffer.GetRingBufferLength(),
|
||||
Eq(16U)); // 4 for uint32 magic, 4 for uint32 version, 4 for
|
||||
// uint32 read_pos, 4 for uint32 length
|
||||
}
|
||||
|
||||
TEST(LengthDelimitedRingBufferTest,
|
||||
NonEmptyBufferSizeShouldIncludeHeaderAndData) {
|
||||
RingBufferData ring_buffer;
|
||||
LengthDelimitedRingBufferWriter writer(ring_buffer);
|
||||
ASSERT_THAT(writer.Push(kHello, sizeof(kHello)), IsTrue());
|
||||
EXPECT_THAT(ring_buffer.GetRingBufferLength(),
|
||||
Eq(22U)); // 16 for header, 1 for varint length, 5 for data
|
||||
}
|
||||
|
||||
TEST(LengthDelimitedRingBufferTest, PopOnEmptyBufferShouldFail) {
|
||||
RingBufferData ring_buffer;
|
||||
LengthDelimitedRingBufferReader reader(ring_buffer);
|
||||
std::vector<uint8_t> result;
|
||||
EXPECT_THAT(reader.Pop(result), IsFalse());
|
||||
}
|
||||
|
||||
TEST(LengthDelimitedRingBufferTest, PushZeroLengthShouldFail) {
|
||||
RingBufferData ring_buffer;
|
||||
LengthDelimitedRingBufferWriter writer(ring_buffer);
|
||||
ASSERT_THAT(writer.Push(nullptr, 0), IsFalse());
|
||||
}
|
||||
|
||||
TEST(LengthDelimitedRingBufferTest, PushExactlyBufferSizeThenPopShouldSucceed) {
|
||||
RingBufferData ring_buffer;
|
||||
LengthDelimitedRingBufferWriter writer(ring_buffer);
|
||||
ASSERT_THAT(writer.Push(kHello, sizeof(kHello)), IsTrue());
|
||||
|
||||
LengthDelimitedRingBufferReader reader(ring_buffer);
|
||||
std::vector<uint8_t> result;
|
||||
EXPECT_THAT(reader.Pop(result), IsTrue());
|
||||
const std::vector<uint8_t> expected_first = {0x68, 0x65, 0x6c, 0x6c, 0x6f};
|
||||
EXPECT_THAT(result, Eq(expected_first));
|
||||
}
|
||||
|
||||
TEST(LengthDelimitedRingBufferTest, PushLargerThanBufferSizeShouldFail) {
|
||||
RingBufferData<4> ring_buffer;
|
||||
LengthDelimitedRingBufferWriter writer(ring_buffer);
|
||||
EXPECT_THAT(writer.Push(kHello, sizeof(kHello)), IsFalse());
|
||||
}
|
||||
|
||||
TEST(LengthDelimitedRingBufferTest,
|
||||
PushUntilFullThenPopUntilEmptyShouldReturnInFIFOOrder) {
|
||||
RingBufferData<4> ring_buffer;
|
||||
LengthDelimitedRingBufferWriter writer(ring_buffer);
|
||||
constexpr uint8_t a = 0x41;
|
||||
EXPECT_THAT(writer.Push(&a, sizeof(a)),
|
||||
IsTrue()); // Writes 2 bytes (1 for length)
|
||||
constexpr uint8_t b = 0x42;
|
||||
EXPECT_THAT(writer.Push(&b, sizeof(b)),
|
||||
IsTrue()); // Writes 2 bytes (1 for length)
|
||||
|
||||
LengthDelimitedRingBufferReader reader(ring_buffer);
|
||||
std::vector<uint8_t> first;
|
||||
EXPECT_THAT(reader.Pop(first), IsTrue());
|
||||
const std::vector<uint8_t> expected_first = {0x41};
|
||||
EXPECT_THAT(first, Eq(expected_first));
|
||||
|
||||
std::vector<uint8_t> second;
|
||||
EXPECT_THAT(reader.Pop(second), IsTrue());
|
||||
const std::vector<uint8_t> expected_second = {0x42};
|
||||
EXPECT_THAT(second, Eq(expected_second));
|
||||
|
||||
std::vector<uint8_t> empty;
|
||||
EXPECT_THAT(reader.Pop(empty), IsFalse());
|
||||
}
|
||||
|
||||
TEST(LengthDelimitedRingBufferTest,
|
||||
PushThenPopBuffersOfDifferingLengthsShouldReturnBuffers) {
|
||||
RingBufferData<5> ring_buffer;
|
||||
LengthDelimitedRingBufferWriter writer(ring_buffer);
|
||||
constexpr uint8_t ab[2] = {0x41, 0x42};
|
||||
EXPECT_THAT(writer.Push(ab, sizeof(ab)),
|
||||
IsTrue()); // Writes 3 bytes (1 for length)
|
||||
constexpr uint8_t c = 0x43;
|
||||
EXPECT_THAT(writer.Push(&c, sizeof(c)),
|
||||
IsTrue()); // Writes 2 bytes (1 for length)
|
||||
|
||||
LengthDelimitedRingBufferReader reader(ring_buffer);
|
||||
std::vector<uint8_t> first;
|
||||
EXPECT_THAT(reader.Pop(first), IsTrue());
|
||||
const std::vector<uint8_t> expected_first = {0x41, 0x42};
|
||||
EXPECT_THAT(first, Eq(expected_first));
|
||||
|
||||
std::vector<uint8_t> second;
|
||||
EXPECT_THAT(reader.Pop(second), IsTrue());
|
||||
const std::vector<uint8_t> expected_second = {0x43};
|
||||
EXPECT_THAT(second, Eq(expected_second));
|
||||
|
||||
std::vector<uint8_t> empty;
|
||||
EXPECT_THAT(reader.Pop(empty), IsFalse());
|
||||
}
|
||||
|
||||
TEST(LengthDelimitedRingBufferDataTest, PushOnFullBufferShouldOverwriteOldest) {
|
||||
RingBufferData<4> ring_buffer;
|
||||
LengthDelimitedRingBufferWriter writer(ring_buffer);
|
||||
constexpr uint8_t a = 0x41;
|
||||
EXPECT_THAT(writer.Push(&a, sizeof(a)),
|
||||
IsTrue()); // Writes 2 bytes (1 for length)
|
||||
constexpr uint8_t b = 0x42;
|
||||
EXPECT_THAT(writer.Push(&b, sizeof(b)),
|
||||
IsTrue()); // Writes 2 bytes (1 for length)
|
||||
constexpr uint8_t c = 0x43;
|
||||
EXPECT_THAT(writer.Push(&c, sizeof(c)), IsTrue()); // Should overwrite "A"
|
||||
|
||||
LengthDelimitedRingBufferReader reader(ring_buffer);
|
||||
std::vector<uint8_t> first;
|
||||
EXPECT_THAT(reader.Pop(first), IsTrue());
|
||||
const std::vector<uint8_t> expected_first = {uint8_t{0x42}};
|
||||
EXPECT_THAT(first, Eq(expected_first));
|
||||
|
||||
std::vector<uint8_t> second;
|
||||
EXPECT_THAT(reader.Pop(second), IsTrue());
|
||||
const std::vector<uint8_t> expected_second = {uint8_t{0x43}};
|
||||
EXPECT_THAT(second, Eq(expected_second));
|
||||
}
|
||||
|
||||
TEST(LengthDelimitedRingBufferDataTest,
|
||||
PushOnFullBufferShouldOverwriteMultipleOldest) {
|
||||
RingBufferData<4> ring_buffer;
|
||||
LengthDelimitedRingBufferWriter writer(ring_buffer);
|
||||
constexpr uint8_t a = 0x41;
|
||||
EXPECT_THAT(writer.Push(&a, sizeof(a)),
|
||||
IsTrue()); // Writes 2 bytes (1 for length)
|
||||
constexpr uint8_t b = 0x42;
|
||||
EXPECT_THAT(writer.Push(&b, sizeof(b)),
|
||||
IsTrue()); // Writes 2 bytes (1 for length)
|
||||
constexpr uint8_t cd[] = {0x43, 0x44};
|
||||
EXPECT_THAT(writer.Push(cd, sizeof(cd)),
|
||||
IsTrue()); // Needs 3 bytes; should overwrite "A" and "B"
|
||||
|
||||
LengthDelimitedRingBufferReader reader(ring_buffer);
|
||||
std::vector<uint8_t> first;
|
||||
EXPECT_THAT(reader.Pop(first), IsTrue());
|
||||
const std::vector<uint8_t> expected_first = {0x43, 0x44};
|
||||
EXPECT_THAT(first, Eq(expected_first));
|
||||
|
||||
std::vector<uint8_t> empty;
|
||||
EXPECT_THAT(reader.Pop(empty), IsFalse());
|
||||
}
|
||||
|
||||
TEST(LengthDelimitedRingBufferDataTest, PushThenPopWithLengthVarintTwoBytes) {
|
||||
RingBufferData ring_buffer;
|
||||
decltype(ring_buffer)::SizeType size = 150;
|
||||
std::string s(size, 'X');
|
||||
LengthDelimitedRingBufferWriter writer(ring_buffer);
|
||||
ASSERT_THAT(writer.Push(reinterpret_cast<const uint8_t*>(s.c_str()), size),
|
||||
IsTrue());
|
||||
|
||||
LengthDelimitedRingBufferReader reader(ring_buffer);
|
||||
std::vector<uint8_t> first;
|
||||
EXPECT_THAT(reader.Pop(first), IsTrue());
|
||||
std::string result(reinterpret_cast<const char*>(first.data()), first.size());
|
||||
EXPECT_THAT(result, Eq(s));
|
||||
}
|
||||
|
||||
TEST(LengthDelimitedRingBufferDataTest, DeserializeFromTooShortShouldFail) {
|
||||
RingBufferData<1> ring_buffer;
|
||||
EXPECT_THAT(ring_buffer.DeserializeFromBuffer(nullptr, 0), IsFalse());
|
||||
}
|
||||
|
||||
TEST(LengthDelimitedRingBufferDataTest, DeserializeFromTooLongShouldFail) {
|
||||
RingBufferData<1> ring_buffer;
|
||||
// This buffer is size 3; it won't fit in the template arg (size 1).
|
||||
EXPECT_THAT(ring_buffer.DeserializeFromBuffer(
|
||||
reinterpret_cast<const uint8_t*>(kValidBufferSize3),
|
||||
kValidBufferSize3Len),
|
||||
IsFalse());
|
||||
}
|
||||
|
||||
TEST(LengthDelimitedRingBufferDataTest,
|
||||
DeserializeFromInvalidVersionShouldFail) {
|
||||
RingBufferData<3> ring_buffer;
|
||||
EXPECT_THAT(ring_buffer.DeserializeFromBuffer(
|
||||
reinterpret_cast<const uint8_t*>(kInvalidVersionBuffer),
|
||||
kInvalidVersionBufferLen),
|
||||
IsFalse());
|
||||
}
|
||||
|
||||
TEST(LengthDelimitedRingBufferDataTest,
|
||||
DeserializeFromInvalidVarintLengthShouldSucceedButPopShouldFail) {
|
||||
RingBufferData ring_buffer;
|
||||
EXPECT_THAT(ring_buffer.DeserializeFromBuffer(
|
||||
reinterpret_cast<const uint8_t*>(kInvalidBase128VarintBuffer),
|
||||
kInvalidBase128VarintBufferLen),
|
||||
IsTrue());
|
||||
LengthDelimitedRingBufferReader reader(ring_buffer);
|
||||
std::vector<uint8_t> data;
|
||||
EXPECT_THAT(reader.Pop(data), IsFalse());
|
||||
}
|
||||
|
||||
TEST(LengthDelimitedRingBufferDataTest,
|
||||
DeserializeFromInvalidVarintBitsShouldSucceedButPopShouldFail) {
|
||||
RingBufferData ring_buffer;
|
||||
EXPECT_THAT(ring_buffer.DeserializeFromBuffer(
|
||||
reinterpret_cast<const uint8_t*>(
|
||||
kInvalidBase128VarintBits33And34SetBuffer),
|
||||
kInvalidBase128VarintBits33And34SetBufferLen),
|
||||
IsTrue());
|
||||
LengthDelimitedRingBufferReader reader(ring_buffer);
|
||||
std::vector<uint8_t> data;
|
||||
EXPECT_THAT(reader.Pop(data), IsFalse());
|
||||
}
|
||||
|
||||
TEST(LengthDelimitedRingBufferDataTest,
|
||||
DeserializeFromInvalidPayloadBufferTooShortShouldSucceedButPopShouldFail) {
|
||||
RingBufferData ring_buffer;
|
||||
EXPECT_THAT(
|
||||
ring_buffer.DeserializeFromBuffer(
|
||||
reinterpret_cast<const uint8_t*>(kInvalidPayloadBufferTooShort),
|
||||
kInvalidPayloadBufferTooShortLen),
|
||||
IsTrue());
|
||||
LengthDelimitedRingBufferReader reader(ring_buffer);
|
||||
std::vector<uint8_t> data;
|
||||
EXPECT_THAT(reader.Pop(data), IsFalse());
|
||||
}
|
||||
|
||||
TEST(LengthDelimitedRingBufferDataTest,
|
||||
DeserializeFromFullBufferShouldSucceed) {
|
||||
RingBufferData<3> ring_buffer;
|
||||
EXPECT_THAT(ring_buffer.DeserializeFromBuffer(
|
||||
reinterpret_cast<const uint8_t*>(kValidBufferSize3),
|
||||
kValidBufferSize3Len),
|
||||
IsTrue());
|
||||
LengthDelimitedRingBufferReader reader(ring_buffer);
|
||||
std::vector<uint8_t> data;
|
||||
EXPECT_THAT(reader.Pop(data), IsTrue());
|
||||
const std::vector<uint8_t> expected = {0x42, 0x23};
|
||||
EXPECT_THAT(data, Eq(expected));
|
||||
}
|
||||
|
||||
TEST(LengthDelimitedRingBufferDataTest,
|
||||
DeserializeFromMidCrashBufferShouldSucceedButSubsequentPopShouldFail) {
|
||||
RingBufferData ring_buffer;
|
||||
EXPECT_THAT(ring_buffer.DeserializeFromBuffer(
|
||||
reinterpret_cast<const uint8_t*>(kMidCrashBuffer),
|
||||
kMidCrashBufferLen),
|
||||
IsTrue());
|
||||
LengthDelimitedRingBufferReader reader(ring_buffer);
|
||||
// Pop should fail since the length was written to be 0.
|
||||
std::vector<uint8_t> data;
|
||||
EXPECT_THAT(reader.Pop(data), IsFalse());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace test
|
||||
} // namespace crashpad
|
|
@ -0,0 +1,136 @@
|
|||
// Copyright 2023 The Crashpad Authors
|
||||
//
|
||||
// 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_CLIENT_RING_BUFFER_ANNOTATION_H_
|
||||
#define CRASHPAD_CLIENT_RING_BUFFER_ANNOTATION_H_
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "client/annotation.h"
|
||||
#include "client/length_delimited_ring_buffer.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
//! \brief Capacity of `RingBufferAnnotation`, in bytes.
|
||||
using RingBufferAnnotationCapacity = RingBufferCapacity;
|
||||
|
||||
namespace internal {
|
||||
|
||||
//! \brief Default capacity of `RingBufferAnnotation`, in bytes.
|
||||
inline constexpr RingBufferAnnotationCapacity
|
||||
kDefaultRingBufferAnnotationCapacity = 8192;
|
||||
|
||||
} // namespace internal
|
||||
|
||||
//! \brief An `Annotation` which wraps a `LengthDelimitedRingBuffer`
|
||||
//! of up to `Capacity` bytes in length.
|
||||
//!
|
||||
//! Supports writing variable-length data via `Push()`. When the ring buffer is
|
||||
//! full, it will drop old data items in FIFO order until enough space is
|
||||
//! available for the write.
|
||||
//!
|
||||
//! Supports guarding concurrent reads from writes via `ScopedSpinGuard`, so
|
||||
//! writing to this object is thread-safe.
|
||||
//!
|
||||
//! Clients which read this `Annotation`'s memory can optionally invoke
|
||||
//! `TryCreateScopedSpinGuard()` on this object to ensure any pending write
|
||||
//! finishes before the memory is read.
|
||||
//!
|
||||
//! Each item in this ring buffer is delimited by its length encoded in
|
||||
//! little-endian Base 128 varint encoding.
|
||||
//!
|
||||
//! `RingBufferAnnotation` uses varint-encoded delimiters to enable
|
||||
//! zero-copy deserialization of the ringbuffer's contents when storing
|
||||
//! protobufs inside the ringbuffer, e.g. via
|
||||
//! `google::protobuf::util::ParseDelimitedFromZeroCopyStream()` or similar.
|
||||
//!
|
||||
//! \sa
|
||||
//! https://github.com/protocolbuffers/protobuf/blob/3202b9da88ceb75b65bbabaf4033c95e872f828d/src/google/protobuf/util/delimited_message_util.h#L85
|
||||
//! \sa
|
||||
//! https://github.com/protocolbuffers/protobuf/blob/8bd49dea5e167a389d94b71d24c981d8f9fa0c99/src/google/protobuf/io/zero_copy_stream_impl_lite.h#L68
|
||||
//! \sa
|
||||
//! https://github.com/protocolbuffers/protobuf/blob/8bd49dea5e167a389d94b71d24c981d8f9fa0c99/src/google/protobuf/io/coded_stream.h#L171
|
||||
//!
|
||||
//! To deserialize the items stored in this annotation, use
|
||||
//! `LengthDelimitedRingBufferReader`.
|
||||
template <RingBufferAnnotationCapacity Capacity =
|
||||
internal::kDefaultRingBufferAnnotationCapacity>
|
||||
class RingBufferAnnotation final : public Annotation {
|
||||
public:
|
||||
//! \brief Constructs a `RingBufferAnnotation`.
|
||||
//! \param[in] type A unique identifier for the type of data in the ring
|
||||
//! buffer.
|
||||
//! \param[in] name The name of the annotation.
|
||||
constexpr RingBufferAnnotation(Annotation::Type type, const char name[])
|
||||
: Annotation(type,
|
||||
name,
|
||||
reinterpret_cast<void* const>(&ring_buffer_data_),
|
||||
ConcurrentAccessGuardMode::kScopedSpinGuard),
|
||||
ring_buffer_data_(),
|
||||
ring_buffer_writer_(ring_buffer_data_) {}
|
||||
RingBufferAnnotation(const RingBufferAnnotation&) = delete;
|
||||
RingBufferAnnotation& operator=(const RingBufferAnnotation&) = delete;
|
||||
RingBufferAnnotation(RingBufferAnnotation&&) = default;
|
||||
RingBufferAnnotation& operator=(RingBufferAnnotation&&) = default;
|
||||
|
||||
//! \brief Pushes data onto this annotation's ring buffer.
|
||||
//!
|
||||
//! If the ring buffer does not have enough space to store `buffer_length`
|
||||
//! bytes of data, old data items are dropped in FIFO order until
|
||||
//! enough space is available to store the new data.
|
||||
bool Push(const void* const buffer,
|
||||
RingBufferAnnotationCapacity buffer_length) {
|
||||
// Use a zero timeout so the operation immediately fails if another thread
|
||||
// or process is currently reading this Annotation.
|
||||
constexpr uint64_t kSpinGuardTimeoutNanoseconds = 0;
|
||||
|
||||
auto spin_guard = TryCreateScopedSpinGuard(kSpinGuardTimeoutNanoseconds);
|
||||
if (!spin_guard) {
|
||||
return false;
|
||||
}
|
||||
bool success = ring_buffer_writer_.Push(buffer, buffer_length);
|
||||
if (success) {
|
||||
SetSize(ring_buffer_data_.GetRingBufferLength());
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
//! \brief Reset the annotation (e.g., for testing).
|
||||
//! This method is not thread-safe.
|
||||
void ResetForTesting() {
|
||||
ring_buffer_data_.ResetForTesting();
|
||||
ring_buffer_writer_.ResetForTesting();
|
||||
}
|
||||
|
||||
private:
|
||||
using RingBufferWriter =
|
||||
LengthDelimitedRingBufferWriter<RingBufferData<Capacity>>;
|
||||
|
||||
//! \brief The ring buffer data stored in this Anotation.
|
||||
RingBufferData<Capacity> ring_buffer_data_;
|
||||
|
||||
//! \brief The writer which wraps `ring_buffer_data_`.
|
||||
RingBufferWriter ring_buffer_writer_;
|
||||
};
|
||||
|
||||
// Allow just `RingBufferAnnotation foo;` to be declared without template
|
||||
// arguments using C++17 class template argument deduction.
|
||||
template <RingBufferAnnotationCapacity Capacity =
|
||||
internal::kDefaultRingBufferAnnotationCapacity>
|
||||
RingBufferAnnotation(Annotation::Type type, const char name[])
|
||||
-> RingBufferAnnotation<Capacity>;
|
||||
|
||||
} // namespace crashpad
|
||||
|
||||
#endif // CRASHPAD_CLIENT_RING_BUFFER_ANNOTATION_H_
|
474
thirdparty/sentry-native/external/crashpad/client/ring_buffer_annotation_load_test_main.cc
vendored
Normal file
474
thirdparty/sentry-native/external/crashpad/client/ring_buffer_annotation_load_test_main.cc
vendored
Normal file
|
@ -0,0 +1,474 @@
|
|||
// Copyright 2023 The Crashpad Authors
|
||||
//
|
||||
// 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.
|
||||
|
||||
#include <getopt.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <random>
|
||||
#include <ratio>
|
||||
#include <string>
|
||||
|
||||
#include "base/notreached.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "base/strings/string_piece.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "build/build_config.h"
|
||||
#include "client/annotation.h"
|
||||
#include "client/length_delimited_ring_buffer.h"
|
||||
#include "client/ring_buffer_annotation.h"
|
||||
#include "tools/tool_support.h"
|
||||
#include "util/stdlib/string_number_conversion.h"
|
||||
#include "util/synchronization/scoped_spin_guard.h"
|
||||
#include "util/thread/thread.h"
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <signal.h>
|
||||
#endif // BUILDFLAG(IS_WIN)
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
namespace test {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr Annotation::Type kRingBufferLoadTestType =
|
||||
Annotation::UserDefinedType(0x0042);
|
||||
std::atomic<bool> g_should_exit = false;
|
||||
|
||||
struct RingBufferAnnotationSnapshotParams final {
|
||||
enum class Mode {
|
||||
kUseScopedSpinGuard = 1,
|
||||
kDoNotUseSpinGuard = 2,
|
||||
};
|
||||
Mode mode = Mode::kUseScopedSpinGuard;
|
||||
using Duration = std::chrono::duration<uint64_t, std::nano>;
|
||||
Duration producer_thread_min_run_duration = std::chrono::milliseconds(1);
|
||||
Duration producer_thread_max_run_duration = std::chrono::milliseconds(10);
|
||||
Duration producer_thread_sleep_duration = std::chrono::nanoseconds(10);
|
||||
Duration consumer_thread_min_run_duration = std::chrono::milliseconds(5);
|
||||
Duration consumer_thread_max_run_duration = std::chrono::milliseconds(100);
|
||||
Duration quiesce_timeout = std::chrono::microseconds(500);
|
||||
uint64_t num_loops = std::numeric_limits<uint64_t>::max();
|
||||
std::optional<Duration> main_thread_run_duration = std::nullopt;
|
||||
};
|
||||
|
||||
template <uint32_t RingBufferCapacity>
|
||||
class RingBufferAnnotationSnapshot final {
|
||||
using RingBufferAnnotationType = RingBufferAnnotation<RingBufferCapacity>;
|
||||
|
||||
struct State final {
|
||||
State()
|
||||
: ring_buffer_annotation(kRingBufferLoadTestType,
|
||||
"ring-buffer-load-test"),
|
||||
ring_buffer_ready(false),
|
||||
producer_thread_running(false),
|
||||
producer_thread_finished(false),
|
||||
consumer_thread_finished(false),
|
||||
should_exit(false) {}
|
||||
|
||||
State(const State&) = delete;
|
||||
State& operator=(const State&) = delete;
|
||||
|
||||
RingBufferAnnotationType ring_buffer_annotation;
|
||||
bool ring_buffer_ready;
|
||||
bool producer_thread_running;
|
||||
bool producer_thread_finished;
|
||||
bool consumer_thread_finished;
|
||||
bool should_exit;
|
||||
};
|
||||
|
||||
class Thread final : public crashpad::Thread {
|
||||
public:
|
||||
Thread(std::function<void()> thread_main)
|
||||
: thread_main_(std::move(thread_main)) {}
|
||||
|
||||
private:
|
||||
void ThreadMain() override { thread_main_(); }
|
||||
|
||||
const std::function<void()> thread_main_;
|
||||
};
|
||||
|
||||
public:
|
||||
RingBufferAnnotationSnapshot(const RingBufferAnnotationSnapshotParams& params)
|
||||
: params_(params),
|
||||
main_loop_thread_([this]() { MainLoopThreadMain(); }),
|
||||
producer_thread_([this]() { ProducerThreadMain(); }),
|
||||
consumer_thread_([this]() { ConsumerThreadMain(); }),
|
||||
mutex_(),
|
||||
state_changed_condition_(),
|
||||
state_() {}
|
||||
|
||||
RingBufferAnnotationSnapshot(const RingBufferAnnotationSnapshot&) = delete;
|
||||
RingBufferAnnotationSnapshot& operator=(const RingBufferAnnotationSnapshot&) =
|
||||
delete;
|
||||
|
||||
void Start() {
|
||||
main_loop_thread_.Start();
|
||||
producer_thread_.Start();
|
||||
consumer_thread_.Start();
|
||||
}
|
||||
|
||||
void Stop() {
|
||||
consumer_thread_.Join();
|
||||
producer_thread_.Join();
|
||||
main_loop_thread_.Join();
|
||||
}
|
||||
|
||||
private:
|
||||
void MainLoopThreadMain() {
|
||||
std::chrono::steady_clock::time_point main_thread_end_time;
|
||||
if (params_.main_thread_run_duration) {
|
||||
main_thread_end_time =
|
||||
std::chrono::steady_clock::now() + *params_.main_thread_run_duration;
|
||||
} else {
|
||||
main_thread_end_time = std::chrono::steady_clock::time_point::max();
|
||||
}
|
||||
for (uint64_t i = 0;
|
||||
i < params_.num_loops &&
|
||||
std::chrono::steady_clock::now() < main_thread_end_time;
|
||||
i++) {
|
||||
{
|
||||
std::unique_lock<std::mutex> start_lock(mutex_);
|
||||
state_.ring_buffer_annotation.ResetForTesting();
|
||||
state_.ring_buffer_ready = true;
|
||||
state_changed_condition_.notify_all();
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
state_changed_condition_.wait(lock, [this] {
|
||||
return state_.producer_thread_finished &&
|
||||
state_.consumer_thread_finished;
|
||||
});
|
||||
state_.ring_buffer_ready = false;
|
||||
if (g_should_exit) {
|
||||
printf("Exiting on Control-C.\n");
|
||||
break;
|
||||
}
|
||||
printf(".");
|
||||
fflush(stdout);
|
||||
state_changed_condition_.notify_all();
|
||||
}
|
||||
}
|
||||
state_.should_exit = true;
|
||||
state_changed_condition_.notify_all();
|
||||
}
|
||||
|
||||
void ProducerThreadMain() {
|
||||
while (true) {
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
state_changed_condition_.wait(lock, [this] {
|
||||
return state_.should_exit || state_.ring_buffer_ready;
|
||||
});
|
||||
if (state_.should_exit) {
|
||||
return;
|
||||
}
|
||||
state_.producer_thread_running = true;
|
||||
state_.producer_thread_finished = false;
|
||||
state_changed_condition_.notify_all();
|
||||
}
|
||||
|
||||
auto min_run_duration_micros =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
params_.producer_thread_min_run_duration);
|
||||
auto max_run_duration_micros =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
params_.producer_thread_max_run_duration);
|
||||
std::uniform_int_distribution<std::chrono::microseconds::rep>
|
||||
run_duration_distribution(min_run_duration_micros.count(),
|
||||
max_run_duration_micros.count());
|
||||
static thread_local std::mt19937 random_number_generator;
|
||||
auto run_duration = std::chrono::microseconds(
|
||||
run_duration_distribution(random_number_generator));
|
||||
auto end_time = std::chrono::steady_clock::now() + run_duration;
|
||||
uint64_t next_value = 0;
|
||||
while (std::chrono::steady_clock::now() < end_time) {
|
||||
if (!Produce(next_value++)) {
|
||||
// The consumer thread interrupted this.
|
||||
break;
|
||||
}
|
||||
}
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
state_changed_condition_.wait(
|
||||
lock, [this] { return state_.consumer_thread_finished; });
|
||||
state_.producer_thread_running = false;
|
||||
state_.producer_thread_finished = true;
|
||||
state_changed_condition_.notify_all();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Produce(uint64_t value) {
|
||||
std::string hex_value = base::StringPrintf("0x%08" PRIx64, value);
|
||||
if (!state_.ring_buffer_annotation.Push(
|
||||
hex_value.data(), static_cast<uint32_t>(hex_value.size()))) {
|
||||
fprintf(stderr,
|
||||
"Ignoring failed call to Push(0x%" PRIx64
|
||||
") (ScopedSpinGuard was held by snapshot thread)\n",
|
||||
value);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConsumerThreadMain() {
|
||||
while (true) {
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
state_changed_condition_.wait(lock, [this] {
|
||||
return state_.should_exit ||
|
||||
(state_.ring_buffer_ready && state_.producer_thread_running);
|
||||
});
|
||||
if (state_.should_exit) {
|
||||
return;
|
||||
}
|
||||
state_.consumer_thread_finished = false;
|
||||
state_changed_condition_.notify_all();
|
||||
}
|
||||
auto min_run_duration_micros =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
params_.consumer_thread_min_run_duration);
|
||||
auto max_run_duration_micros =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
params_.consumer_thread_max_run_duration);
|
||||
std::uniform_int_distribution<std::chrono::microseconds::rep>
|
||||
run_duration_distribution(min_run_duration_micros.count(),
|
||||
max_run_duration_micros.count());
|
||||
static thread_local std::mt19937 random_number_generator;
|
||||
auto run_duration = std::chrono::microseconds(
|
||||
run_duration_distribution(random_number_generator));
|
||||
auto end_time = std::chrono::steady_clock::now() + run_duration;
|
||||
while (std::chrono::steady_clock::now() < end_time) {
|
||||
constexpr uint64_t kSleepTimeNs = 10000; // 10 us
|
||||
SleepNanoseconds(kSleepTimeNs);
|
||||
}
|
||||
Snapshot();
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
state_.consumer_thread_finished = true;
|
||||
state_.ring_buffer_ready = false;
|
||||
state_changed_condition_.notify_all();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Snapshot() {
|
||||
int64_t timeout_ns = static_cast<int64_t>(
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
params_.quiesce_timeout)
|
||||
.count());
|
||||
uint8_t serialized_ring_buffer[sizeof(state_.ring_buffer_annotation)];
|
||||
Annotation::ValueSizeType ring_buffer_size;
|
||||
{
|
||||
std::optional<ScopedSpinGuard> scoped_spin_guard;
|
||||
if (params_.mode ==
|
||||
RingBufferAnnotationSnapshotParams::Mode::kUseScopedSpinGuard) {
|
||||
scoped_spin_guard =
|
||||
state_.ring_buffer_annotation.TryCreateScopedSpinGuard(timeout_ns);
|
||||
}
|
||||
if (params_.mode ==
|
||||
RingBufferAnnotationSnapshotParams::Mode::kUseScopedSpinGuard &&
|
||||
!scoped_spin_guard) {
|
||||
fprintf(stderr,
|
||||
"Could not quiesce writes within %" PRIi64 " ns\n",
|
||||
timeout_ns);
|
||||
abort();
|
||||
}
|
||||
ring_buffer_size = state_.ring_buffer_annotation.size();
|
||||
memcpy(&serialized_ring_buffer[0],
|
||||
state_.ring_buffer_annotation.value(),
|
||||
ring_buffer_size);
|
||||
}
|
||||
RingBufferData ring_buffer;
|
||||
if (!ring_buffer.DeserializeFromBuffer(serialized_ring_buffer,
|
||||
ring_buffer_size)) {
|
||||
fprintf(stderr, "Could not deserialize ring buffer\n");
|
||||
abort();
|
||||
}
|
||||
LengthDelimitedRingBufferReader ring_buffer_reader(ring_buffer);
|
||||
int value = std::numeric_limits<int>::max();
|
||||
std::vector<uint8_t> bytes;
|
||||
while (ring_buffer_reader.Pop(bytes)) {
|
||||
int next_value;
|
||||
base::StringPiece str(reinterpret_cast<const char*>(&bytes[0]),
|
||||
bytes.size());
|
||||
if (!HexStringToInt(str, &next_value)) {
|
||||
fprintf(
|
||||
stderr, "Couldn't parse value: [%s]\n", str.as_string().c_str());
|
||||
abort();
|
||||
}
|
||||
if (value == std::numeric_limits<int>::max()) {
|
||||
// First value in buffer.
|
||||
} else if (value + 1 != next_value) {
|
||||
fprintf(stderr,
|
||||
"Expected value 0x%08x, got 0x%08x\n",
|
||||
value + 1,
|
||||
next_value);
|
||||
abort();
|
||||
}
|
||||
value = next_value;
|
||||
bytes.clear();
|
||||
}
|
||||
}
|
||||
|
||||
const RingBufferAnnotationSnapshotParams params_;
|
||||
Thread main_loop_thread_;
|
||||
Thread producer_thread_;
|
||||
Thread consumer_thread_;
|
||||
std::mutex mutex_;
|
||||
|
||||
// Fired whenever `state_` changes.
|
||||
std::condition_variable state_changed_condition_;
|
||||
|
||||
// Protected by `mutex_`.
|
||||
State state_;
|
||||
};
|
||||
|
||||
void Usage(const base::FilePath& me) {
|
||||
// clang-format off
|
||||
fprintf(stderr,
|
||||
"Usage: %" PRFilePath " [OPTION]...\n"
|
||||
"Runs a load test for concurrent I/O to RingBufferAnnotation.\n"
|
||||
"\n"
|
||||
"By default, enables the annotation spin guard and runs indefinitely\n"
|
||||
"until interrupted (e.g., with Control-C or SIGINT).\n"
|
||||
"\n"
|
||||
" -d,--disable-spin-guard Disables the annotation spin guard\n"
|
||||
" (the test is expected to crash in this case)\n"
|
||||
" -n,--num-loops=N Runs the test for N iterations, not indefinitely\n"
|
||||
" -s,--duration-secs=SECS Runs the test for SECS seconds, not indefinitely\n",
|
||||
me.value().c_str());
|
||||
// clang-format on
|
||||
ToolSupport::UsageTail(me);
|
||||
}
|
||||
|
||||
int TestMain(int argc, char** argv) {
|
||||
const base::FilePath argv0(
|
||||
ToolSupport::CommandLineArgumentToFilePathStringType(argv[0]));
|
||||
const base::FilePath me(argv0.BaseName());
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
auto handler_routine = [](DWORD type) -> BOOL {
|
||||
if (type == CTRL_C_EVENT) {
|
||||
g_should_exit = true;
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
};
|
||||
if (!SetConsoleCtrlHandler(handler_routine, /*Add=*/TRUE)) {
|
||||
fprintf(stderr, "Couldn't set Control-C handler\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
#else
|
||||
signal(SIGINT, [](int signal) { g_should_exit = true; });
|
||||
#endif // BUILDFLAG(IS_WIN)
|
||||
RingBufferAnnotationSnapshotParams params;
|
||||
enum OptionFlags {
|
||||
// "Short" (single-character) options.
|
||||
kOptionDisableSpinGuard = 'd',
|
||||
kOptionNumLoops = 'n',
|
||||
kOptionDurationSecs = 's',
|
||||
|
||||
// Standard options.
|
||||
kOptionHelp = -2,
|
||||
kOptionVersion = -3,
|
||||
};
|
||||
static constexpr option long_options[] = {
|
||||
{"disable-spin-guard", no_argument, nullptr, kOptionDisableSpinGuard},
|
||||
{"num-loops", required_argument, nullptr, kOptionNumLoops},
|
||||
{"duration-secs", required_argument, nullptr, kOptionDurationSecs},
|
||||
{"help", no_argument, nullptr, kOptionHelp},
|
||||
{"version", no_argument, nullptr, kOptionVersion},
|
||||
{nullptr, 0, nullptr, 0},
|
||||
};
|
||||
|
||||
int opt;
|
||||
while ((opt = getopt_long(argc, argv, "dn:s:", long_options, nullptr)) !=
|
||||
-1) {
|
||||
switch (opt) {
|
||||
case kOptionDisableSpinGuard:
|
||||
printf("Disabling spin guard logic (this test will fail!)\n");
|
||||
params.mode =
|
||||
RingBufferAnnotationSnapshotParams::Mode::kDoNotUseSpinGuard;
|
||||
break;
|
||||
case kOptionNumLoops: {
|
||||
std::string num_loops(optarg);
|
||||
uint64_t num_loops_value;
|
||||
if (!StringToNumber(num_loops, &num_loops_value)) {
|
||||
ToolSupport::UsageHint(me, "--num-loops requires integer value");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
params.num_loops = num_loops_value;
|
||||
break;
|
||||
}
|
||||
case kOptionDurationSecs: {
|
||||
std::string duration_secs(optarg);
|
||||
uint64_t duration_secs_value;
|
||||
if (!StringToNumber(duration_secs, &duration_secs_value)) {
|
||||
ToolSupport::UsageHint(me, "--duration-secs requires integer value");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
params.main_thread_run_duration =
|
||||
std::chrono::seconds(duration_secs_value);
|
||||
break;
|
||||
}
|
||||
case kOptionHelp:
|
||||
Usage(me);
|
||||
return EXIT_SUCCESS;
|
||||
case kOptionVersion:
|
||||
ToolSupport::Version(me);
|
||||
return EXIT_SUCCESS;
|
||||
default:
|
||||
ToolSupport::UsageHint(me, nullptr);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
RingBufferAnnotationSnapshot<8192> test_producer_snapshot(params);
|
||||
printf("Starting test (Control-C to exit)...\n");
|
||||
test_producer_snapshot.Start();
|
||||
test_producer_snapshot.Stop();
|
||||
printf("Test finished.\n");
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace test
|
||||
} // namespace crashpad
|
||||
|
||||
#if BUILDFLAG(IS_POSIX)
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
return crashpad::test::TestMain(argc, argv);
|
||||
}
|
||||
|
||||
#elif BUILDFLAG(IS_WIN)
|
||||
|
||||
int wmain(int argc, wchar_t* argv[]) {
|
||||
return crashpad::ToolSupport::Wmain(argc, argv, crashpad::test::TestMain);
|
||||
}
|
||||
|
||||
#endif
|
188
thirdparty/sentry-native/external/crashpad/client/ring_buffer_annotation_test.cc
vendored
Normal file
188
thirdparty/sentry-native/external/crashpad/client/ring_buffer_annotation_test.cc
vendored
Normal file
|
@ -0,0 +1,188 @@
|
|||
// Copyright 2023 The Crashpad Authors
|
||||
//
|
||||
// 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.
|
||||
|
||||
#include "client/ring_buffer_annotation.h"
|
||||
#include "client/length_delimited_ring_buffer.h"
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
#include "client/annotation_list.h"
|
||||
#include "client/crashpad_info.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/gtest_death.h"
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
namespace {
|
||||
|
||||
constexpr uint32_t kRingBufferHeaderSize = 16;
|
||||
constexpr uint32_t kLengthDelimiter1ByteSize = 1;
|
||||
|
||||
class RingBufferAnnotationTest : public testing::Test {
|
||||
public:
|
||||
void SetUp() override {
|
||||
CrashpadInfo::GetCrashpadInfo()->set_annotations_list(&annotations_);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
CrashpadInfo::GetCrashpadInfo()->set_annotations_list(nullptr);
|
||||
}
|
||||
|
||||
size_t AnnotationsCount() {
|
||||
size_t result = 0;
|
||||
for (auto* annotation : annotations_) {
|
||||
if (annotation->is_set())
|
||||
++result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected:
|
||||
AnnotationList annotations_;
|
||||
};
|
||||
|
||||
TEST_F(RingBufferAnnotationTest, Basics) {
|
||||
constexpr Annotation::Type kType = Annotation::UserDefinedType(1);
|
||||
|
||||
constexpr char kName[] = "annotation 1";
|
||||
RingBufferAnnotation annotation(kType, kName);
|
||||
|
||||
EXPECT_FALSE(annotation.is_set());
|
||||
EXPECT_EQ(0u, AnnotationsCount());
|
||||
|
||||
EXPECT_EQ(kType, annotation.type());
|
||||
EXPECT_EQ(0u, annotation.size());
|
||||
EXPECT_EQ(std::string(kName), annotation.name());
|
||||
|
||||
EXPECT_TRUE(
|
||||
annotation.Push(reinterpret_cast<const uint8_t*>("0123456789"), 10));
|
||||
|
||||
EXPECT_TRUE(annotation.is_set());
|
||||
EXPECT_EQ(1u, AnnotationsCount());
|
||||
|
||||
constexpr Annotation::ValueSizeType kExpectedSize =
|
||||
kRingBufferHeaderSize + kLengthDelimiter1ByteSize + 10u;
|
||||
EXPECT_EQ(kExpectedSize, annotation.size());
|
||||
EXPECT_EQ(&annotation, *annotations_.begin());
|
||||
|
||||
RingBufferData data;
|
||||
EXPECT_TRUE(
|
||||
data.DeserializeFromBuffer(annotation.value(), annotation.size()));
|
||||
EXPECT_EQ(kExpectedSize, data.GetRingBufferLength());
|
||||
|
||||
std::vector<uint8_t> popped_value;
|
||||
LengthDelimitedRingBufferReader reader(data);
|
||||
EXPECT_TRUE(reader.Pop(popped_value));
|
||||
|
||||
const std::vector<uint8_t> expected = {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
|
||||
EXPECT_EQ(expected, popped_value);
|
||||
|
||||
annotation.Clear();
|
||||
|
||||
EXPECT_FALSE(annotation.is_set());
|
||||
EXPECT_EQ(0u, AnnotationsCount());
|
||||
|
||||
EXPECT_EQ(0u, annotation.size());
|
||||
}
|
||||
|
||||
TEST_F(RingBufferAnnotationTest, MultiplePushesWithoutWrapping) {
|
||||
constexpr Annotation::Type kType = Annotation::UserDefinedType(1);
|
||||
|
||||
constexpr char kName[] = "annotation 1";
|
||||
RingBufferAnnotation annotation(kType, kName);
|
||||
|
||||
EXPECT_TRUE(
|
||||
annotation.Push(reinterpret_cast<const uint8_t*>("0123456789"), 10));
|
||||
EXPECT_TRUE(annotation.Push(reinterpret_cast<const uint8_t*>("ABCDEF"), 6));
|
||||
|
||||
EXPECT_TRUE(annotation.is_set());
|
||||
EXPECT_EQ(1u, AnnotationsCount());
|
||||
|
||||
constexpr Annotation::ValueSizeType kExpectedSize =
|
||||
kRingBufferHeaderSize + kLengthDelimiter1ByteSize + 10u +
|
||||
kLengthDelimiter1ByteSize + 6u;
|
||||
EXPECT_EQ(kExpectedSize, annotation.size());
|
||||
EXPECT_EQ(&annotation, *annotations_.begin());
|
||||
|
||||
RingBufferData data;
|
||||
EXPECT_TRUE(
|
||||
data.DeserializeFromBuffer(annotation.value(), annotation.size()));
|
||||
EXPECT_EQ(kExpectedSize, data.GetRingBufferLength());
|
||||
|
||||
std::vector<uint8_t> popped_value;
|
||||
LengthDelimitedRingBufferReader reader(data);
|
||||
EXPECT_TRUE(reader.Pop(popped_value));
|
||||
|
||||
const std::vector<uint8_t> expected1 = {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
|
||||
EXPECT_EQ(expected1, popped_value);
|
||||
|
||||
popped_value.clear();
|
||||
EXPECT_TRUE(reader.Pop(popped_value));
|
||||
|
||||
const std::vector<uint8_t> expected2 = {'A', 'B', 'C', 'D', 'E', 'F'};
|
||||
EXPECT_EQ(expected2, popped_value);
|
||||
}
|
||||
|
||||
TEST_F(RingBufferAnnotationTest,
|
||||
MultiplePushCallsWithWrappingShouldOverwriteInFIFOOrder) {
|
||||
constexpr Annotation::Type kType = Annotation::UserDefinedType(1);
|
||||
|
||||
constexpr char kName[] = "annotation 1";
|
||||
RingBufferAnnotation<10> annotation(kType, kName);
|
||||
|
||||
// Each Push() call will push 1 byte for the varint 128-encoded length,
|
||||
// then the number of bytes specified.
|
||||
constexpr char kFirst[] = "AAA";
|
||||
constexpr char kSecond[] = "BBB";
|
||||
constexpr char kThird[] = "CCC";
|
||||
|
||||
// This takes up bytes 0-3 of the 10-byte RingBufferAnnotation.
|
||||
ASSERT_TRUE(annotation.Push(reinterpret_cast<const uint8_t*>(kFirst), 3));
|
||||
|
||||
// This takes up bytes 4-7 of the 10-byte RingBufferAnnotation.
|
||||
ASSERT_TRUE(annotation.Push(reinterpret_cast<const uint8_t*>(kSecond), 3));
|
||||
|
||||
// This should wrap around the end of the array and overwrite kFirst since it
|
||||
// needs 4 bytes but there are only 2 left.
|
||||
ASSERT_TRUE(annotation.Push(reinterpret_cast<const uint8_t*>(kThird), 3));
|
||||
|
||||
// The size of the annotation should include the header and the full 10 bytes
|
||||
// of the ring buffer, since the third write wrapped around the end.
|
||||
ASSERT_EQ(kRingBufferHeaderSize + 10u, annotation.size());
|
||||
|
||||
// This data size needs to match the size in the RingBufferAnnotation above.
|
||||
RingBufferData<10> data;
|
||||
ASSERT_TRUE(
|
||||
data.DeserializeFromBuffer(annotation.value(), annotation.size()));
|
||||
|
||||
std::vector<uint8_t> popped_value;
|
||||
LengthDelimitedRingBufferReader reader(data);
|
||||
ASSERT_TRUE(reader.Pop(popped_value));
|
||||
|
||||
// "AAA" has been overwritten, so the first thing popped should be "BBB".
|
||||
const std::vector<uint8_t> expected_b = {'B', 'B', 'B'};
|
||||
EXPECT_EQ(expected_b, popped_value);
|
||||
|
||||
popped_value.clear();
|
||||
ASSERT_TRUE(reader.Pop(popped_value));
|
||||
const std::vector<uint8_t> expected_c = {'C', 'C', 'C'};
|
||||
EXPECT_EQ(expected_c, popped_value);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace test
|
||||
} // namespace crashpad
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wgnu-include-next"
|
||||
#pragma clang diagnostic ignored "-Wzero-length-array"
|
||||
|
||||
#include_next <dbghelp.h>
|
||||
#include <stdint.h>
|
||||
|
@ -329,6 +330,36 @@ typedef struct MINIDUMP_MISC_INFO_5 MINIDUMP_MISC_INFO_N;
|
|||
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
//! \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 of more
|
||||
//! MINIDUMP_THREAD_NAME.
|
||||
//! The length of the array is indicated by the NumberOfThreadNames field
|
||||
//! in this struct.
|
||||
struct MINIDUMP_THREAD_NAME ThreadNames[0];
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
#endif // CRASHPAD_COMPAT_MINGW_DBGHELP_H_
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright 2015 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_MINGW_WERAPI_H_
|
||||
#define CRASHPAD_COMPAT_MINGW_WERAPI_H_
|
||||
|
||||
typedef HANDLE HREPORT;
|
||||
|
||||
#ifndef WER_MAX_PREFERRED_MODULES_BUFFER
|
||||
#define WER_MAX_PREFERRED_MODULES_BUFFER 256
|
||||
#endif
|
||||
|
||||
#define PWER_SUBMIT_RESULT WER_SUBMIT_RESULT*
|
||||
|
||||
#include_next <werapi.h>
|
||||
|
||||
#endif // CRASHPAD_COMPAT_MINGW_WERAPI_H_
|
|
@ -57,4 +57,16 @@ struct PROCESSOR_POWER_INFORMATION {
|
|||
#define PROCESSOR_ARCHITECTURE_ARM32_ON_WIN64 13
|
||||
#endif
|
||||
|
||||
#include <apisetcconv.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
WINBASEAPI HRESULT WINAPI GetThreadDescription(HANDLE,PWSTR *);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // CRASHPAD_COMPAT_MINGW_WINNT_H_
|
||||
|
|
|
@ -137,9 +137,11 @@ if(CRASHPAD_WER_ENABLED)
|
|||
target_link_libraries(crashpad_wer
|
||||
PRIVATE
|
||||
$<BUILD_INTERFACE:crashpad_interface>
|
||||
$<BUILD_INTERFACE:crashpad_compat>
|
||||
)
|
||||
|
||||
set_property(TARGET crashpad_wer PROPERTY EXPORT_NAME crashpad_wer)
|
||||
set_property(TARGET crashpad_wer PROPERTY PREFIX "") # ensure MINGW doesn't prefix "lib" to dll name
|
||||
add_library(crashpad::wer ALIAS crashpad_wer)
|
||||
|
||||
install(TARGETS crashpad_wer EXPORT crashpad_export
|
||||
|
|
|
@ -102,7 +102,8 @@ bool ProcessException(const DWORD* handled_exceptions,
|
|||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
// If num_handled_exceptions == 0, all exceptions should be passed on.
|
||||
if (!found && num_handled_exceptions != 0)
|
||||
return false;
|
||||
|
||||
// Grab out the handles to the crashpad server.
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "minidump/minidump_module_crashpad_info_writer.h"
|
||||
#include "minidump/minidump_simple_string_dictionary_writer.h"
|
||||
#include "snapshot/process_snapshot.h"
|
||||
#include "snapshot/system_snapshot.h"
|
||||
#include "util/file/file_writer.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
@ -30,6 +31,7 @@ MinidumpCrashpadInfoWriter::MinidumpCrashpadInfoWriter()
|
|||
simple_annotations_(nullptr),
|
||||
module_list_(nullptr) {
|
||||
crashpad_info_.version = MinidumpCrashpadInfo::kVersion;
|
||||
crashpad_info_.reserved = 0;
|
||||
}
|
||||
|
||||
MinidumpCrashpadInfoWriter::~MinidumpCrashpadInfoWriter() {
|
||||
|
@ -56,6 +58,10 @@ void MinidumpCrashpadInfoWriter::InitializeFromSnapshot(
|
|||
SetSimpleAnnotations(std::move(simple_annotations));
|
||||
}
|
||||
|
||||
if (process_snapshot->System()) {
|
||||
SetAddressMask(process_snapshot->System()->AddressMask());
|
||||
}
|
||||
|
||||
auto modules = std::make_unique<MinidumpModuleCrashpadInfoListWriter>();
|
||||
modules->InitializeFromSnapshot(process_snapshot->Modules());
|
||||
|
||||
|
@ -90,6 +96,10 @@ void MinidumpCrashpadInfoWriter::SetModuleList(
|
|||
module_list_ = std::move(module_list);
|
||||
}
|
||||
|
||||
void MinidumpCrashpadInfoWriter::SetAddressMask(uint64_t mask) {
|
||||
crashpad_info_.address_mask = mask;
|
||||
}
|
||||
|
||||
bool MinidumpCrashpadInfoWriter::Freeze() {
|
||||
DCHECK_EQ(state(), kStateMutable);
|
||||
|
||||
|
|
|
@ -86,6 +86,9 @@ class MinidumpCrashpadInfoWriter final : public internal::MinidumpStreamWriter {
|
|||
void SetModuleList(
|
||||
std::unique_ptr<MinidumpModuleCrashpadInfoListWriter> module_list);
|
||||
|
||||
//! \brief Sets MinidumpCrashpadInfo::address_mask.
|
||||
void SetAddressMask(uint64_t mask);
|
||||
|
||||
//! \brief Determines whether the object is useful.
|
||||
//!
|
||||
//! A useful object is one that carries data that makes a meaningful
|
||||
|
|
|
@ -122,6 +122,59 @@ TEST(MinidumpCrashpadInfoWriter, ReportAndClientID) {
|
|||
EXPECT_FALSE(module_list);
|
||||
}
|
||||
|
||||
TEST(MinidumpCrashpadInfoWriter, AddressMask) {
|
||||
MinidumpFileWriter minidump_file_writer;
|
||||
auto crashpad_info_writer = std::make_unique<MinidumpCrashpadInfoWriter>();
|
||||
|
||||
constexpr uint64_t mask = 0xFFFFFF8000000000;
|
||||
crashpad_info_writer->SetAddressMask(mask);
|
||||
|
||||
ASSERT_TRUE(minidump_file_writer.AddStream(std::move(crashpad_info_writer)));
|
||||
|
||||
StringFile string_file;
|
||||
ASSERT_TRUE(minidump_file_writer.WriteEverything(&string_file));
|
||||
|
||||
const MinidumpCrashpadInfo* crashpad_info = nullptr;
|
||||
const MinidumpSimpleStringDictionary* simple_annotations = nullptr;
|
||||
const MinidumpModuleCrashpadInfoList* module_list = nullptr;
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(GetCrashpadInfoStream(
|
||||
string_file.string(), &crashpad_info, &simple_annotations, &module_list));
|
||||
|
||||
UUID empty_report_id;
|
||||
ASSERT_TRUE(empty_report_id.InitializeFromString(
|
||||
"00000000-0000-0000-0000-000000000000"));
|
||||
UUID empty_client_id;
|
||||
ASSERT_TRUE(empty_client_id.InitializeFromString(
|
||||
"00000000-0000-0000-0000-000000000000"));
|
||||
|
||||
EXPECT_EQ(crashpad_info->version, MinidumpCrashpadInfo::kVersion);
|
||||
EXPECT_EQ(crashpad_info->address_mask, mask);
|
||||
EXPECT_EQ(crashpad_info->report_id, empty_report_id);
|
||||
EXPECT_EQ(crashpad_info->client_id, empty_client_id);
|
||||
EXPECT_FALSE(simple_annotations);
|
||||
EXPECT_FALSE(module_list);
|
||||
}
|
||||
|
||||
TEST(MinidumpCrashpadInfoWriter, EmptyAddressMask) {
|
||||
MinidumpFileWriter minidump_file_writer;
|
||||
auto crashpad_info_writer = std::make_unique<MinidumpCrashpadInfoWriter>();
|
||||
|
||||
ASSERT_TRUE(minidump_file_writer.AddStream(std::move(crashpad_info_writer)));
|
||||
|
||||
StringFile string_file;
|
||||
ASSERT_TRUE(minidump_file_writer.WriteEverything(&string_file));
|
||||
|
||||
const MinidumpCrashpadInfo* crashpad_info = nullptr;
|
||||
const MinidumpSimpleStringDictionary* simple_annotations = nullptr;
|
||||
const MinidumpModuleCrashpadInfoList* module_list = nullptr;
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(GetCrashpadInfoStream(
|
||||
string_file.string(), &crashpad_info, &simple_annotations, &module_list));
|
||||
|
||||
EXPECT_EQ(crashpad_info->address_mask, 0UL);
|
||||
}
|
||||
|
||||
TEST(MinidumpCrashpadInfoWriter, SimpleAnnotations) {
|
||||
MinidumpFileWriter minidump_file_writer;
|
||||
auto crashpad_info_writer = std::make_unique<MinidumpCrashpadInfoWriter>();
|
||||
|
|
|
@ -452,8 +452,9 @@ struct ALIGNAS(4) PACKED MinidumpCrashpadInfo {
|
|||
report_id(),
|
||||
client_id(),
|
||||
simple_annotations(),
|
||||
module_list() {
|
||||
}
|
||||
module_list(),
|
||||
reserved(),
|
||||
address_mask() {}
|
||||
|
||||
//! \brief The structure’s currently-defined version number.
|
||||
//!
|
||||
|
@ -507,6 +508,28 @@ struct ALIGNAS(4) PACKED MinidumpCrashpadInfo {
|
|||
//!
|
||||
//! This field is present when #version is at least `1`.
|
||||
MINIDUMP_LOCATION_DESCRIPTOR module_list;
|
||||
|
||||
//! \brief This field is always `0`.
|
||||
uint32_t reserved;
|
||||
|
||||
//! \brief A mask indicating the range of valid addresses for a pointer.
|
||||
//!
|
||||
//! ARM64 supports MTE, TBI and PAC masking, generally in the upper bits of
|
||||
//! a pointer. This mask can be used by LLDB to mimic ptrauth_strip and strip
|
||||
//! the pointer authentication codes. To recover `pointer` in userland on
|
||||
//! Darwin, `pointer & (~mask)`. In the case of code running in high memory,
|
||||
//! where bit 55 is set (indicating that all of the high bits should be set
|
||||
//! to 1), `pointer | mask`. See ABIMacOSX_arm64::FixAddress for more details
|
||||
//! here:
|
||||
//! https://github.com/llvm/llvm-project/blob/001d18664f8bcf63af64f10688809f7681dfbf0b/lldb/source/Plugins/ABI/AArch64/ABIMacOSX_arm64.cpp#L817-L830
|
||||
//!
|
||||
//! If the platform does not support pointer authentication, or the range of
|
||||
//! valid addressees for a pointer was inaccessible, this field will be 0 and
|
||||
//! should be ignored.
|
||||
//!
|
||||
//! This field is present when #version is at least `1`, if the size of the
|
||||
//! structure is large enough to accommodate it.
|
||||
uint64_t address_mask;
|
||||
};
|
||||
|
||||
#if defined(COMPILER_MSVC)
|
||||
|
|
|
@ -505,6 +505,10 @@ crashpad_loadable_module("crashpad_snapshot_test_module") {
|
|||
"$mini_chromium_source_parent:base",
|
||||
"../client",
|
||||
]
|
||||
if (crashpad_is_in_fuchsia) {
|
||||
# TODO(fxbug.dev/108368): Remove this once the underlying issue is addressed.
|
||||
exclude_toolchain_tags = [ "hwasan" ]
|
||||
}
|
||||
}
|
||||
|
||||
crashpad_loadable_module("crashpad_snapshot_test_module_large") {
|
||||
|
@ -519,6 +523,11 @@ crashpad_loadable_module("crashpad_snapshot_test_module_large") {
|
|||
|
||||
defines = [ "CRASHPAD_INFO_SIZE_TEST_MODULE_LARGE" ]
|
||||
deps += [ "$mini_chromium_source_parent:base" ]
|
||||
|
||||
if (crashpad_is_in_fuchsia) {
|
||||
# TODO(fxbug.dev/108368): Remove this once the underlying issue is addressed.
|
||||
exclude_toolchain_tags = [ "hwasan" ]
|
||||
}
|
||||
}
|
||||
|
||||
crashpad_loadable_module("crashpad_snapshot_test_module_small") {
|
||||
|
@ -533,6 +542,11 @@ crashpad_loadable_module("crashpad_snapshot_test_module_small") {
|
|||
|
||||
defines = [ "CRASHPAD_INFO_SIZE_TEST_MODULE_SMALL" ]
|
||||
deps += [ "$mini_chromium_source_parent:base" ]
|
||||
|
||||
if (crashpad_is_in_fuchsia) {
|
||||
# TODO(fxbug.dev/108368): Remove this once the underlying issue is addressed.
|
||||
exclude_toolchain_tags = [ "hwasan" ]
|
||||
}
|
||||
}
|
||||
|
||||
if ((crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia) &&
|
||||
|
@ -543,6 +557,11 @@ if ((crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia) &&
|
|||
|
||||
# This makes `ld` emit both .hash and .gnu.hash sections.
|
||||
ldflags = [ "-Wl,--hash-style=both" ]
|
||||
|
||||
if (crashpad_is_in_fuchsia) {
|
||||
# TODO(fxbug.dev/108368): Remove this once the underlying issue is addressed.
|
||||
exclude_toolchain_tags = [ "hwasan" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -223,6 +223,17 @@ if(WIN32)
|
|||
"-Wno-unknown-attributes"
|
||||
)
|
||||
endif()
|
||||
|
||||
if (MINGW OR ("${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}" STREQUAL "") OR (CMAKE_SYSTEM_VERSION LESS 10))
|
||||
# Define NTDDI_VERSION >= NTDDI_WIN10_RS5 - so InitializeContext2 definition is always available in process_reader_win.cc
|
||||
# NTDDI_WIN10_RS5 = 0x0A000006 /* ABRACADABRA_WIN10_RS5 */
|
||||
set_property(
|
||||
SOURCE "win/process_reader_win.cc"
|
||||
APPEND
|
||||
PROPERTY COMPILE_DEFINITIONS
|
||||
WINVER=0x0A00 _WIN32_WINNT=0x0A00 NTDDI_VERSION=0x0A000006
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(APPLE AND NOT IOS AND CRASHPAD_ENABLE_STACKTRACE)
|
||||
|
|
|
@ -26,7 +26,11 @@
|
|||
#include "client/annotation_list.h"
|
||||
#include "client/simple_string_dictionary.h"
|
||||
#include "snapshot/snapshot_constants.h"
|
||||
#if BUILDFLAG(IS_FUCHSIA)
|
||||
#include "util/fuchsia/traits.h"
|
||||
#else
|
||||
#include "util/linux/traits.h"
|
||||
#endif
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
|
@ -39,6 +43,8 @@ struct Annotation {
|
|||
typename Traits::Address value;
|
||||
uint32_t size;
|
||||
uint16_t type;
|
||||
crashpad::Annotation::ConcurrentAccessGuardMode concurrent_access_guard_mode;
|
||||
bool spin_guard_state;
|
||||
};
|
||||
|
||||
template <class Traits>
|
||||
|
|
|
@ -73,6 +73,8 @@ class SystemSnapshotFuchsia final : public SystemSnapshot {
|
|||
int* daylight_offset_seconds,
|
||||
std::string* standard_name,
|
||||
std::string* daylight_name) const override;
|
||||
uint64_t AddressMask() const override { return 0; }
|
||||
|
||||
private:
|
||||
std::string os_version_full_;
|
||||
const timeval* snapshot_time_; // weak
|
||||
|
|
|
@ -57,6 +57,7 @@ SystemSnapshotIOSIntermediateDump::SystemSnapshotIOSIntermediateDump()
|
|||
daylight_offset_seconds_(0),
|
||||
standard_name_(),
|
||||
daylight_name_(),
|
||||
address_mask_(0),
|
||||
initialized_() {}
|
||||
|
||||
SystemSnapshotIOSIntermediateDump::~SystemSnapshotIOSIntermediateDump() {}
|
||||
|
@ -97,6 +98,8 @@ void SystemSnapshotIOSIntermediateDump::Initialize(
|
|||
dst_status_ = SystemSnapshot::kDoesNotObserveDaylightSavingTime;
|
||||
}
|
||||
|
||||
GetDataValueFromMap(system_data, Key::kAddressMask, &address_mask_);
|
||||
|
||||
vm_size_t page_size;
|
||||
if (GetDataValueFromMap(system_data, Key::kPageSize, &page_size)) {
|
||||
const IOSIntermediateDumpMap* vm_stat =
|
||||
|
@ -241,5 +244,10 @@ void SystemSnapshotIOSIntermediateDump::TimeZone(
|
|||
daylight_name->assign(daylight_name_);
|
||||
}
|
||||
|
||||
uint64_t SystemSnapshotIOSIntermediateDump::AddressMask() const {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
return address_mask_;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace crashpad
|
||||
|
|
|
@ -73,6 +73,7 @@ class SystemSnapshotIOSIntermediateDump final : public SystemSnapshot {
|
|||
int* daylight_offset_seconds,
|
||||
std::string* standard_name,
|
||||
std::string* daylight_name) const override;
|
||||
uint64_t AddressMask() const override;
|
||||
|
||||
private:
|
||||
std::string os_version_build_;
|
||||
|
@ -91,6 +92,7 @@ class SystemSnapshotIOSIntermediateDump final : public SystemSnapshot {
|
|||
int daylight_offset_seconds_;
|
||||
std::string standard_name_;
|
||||
std::string daylight_name_;
|
||||
uint64_t address_mask_;
|
||||
InitializationStateDcheck initialized_;
|
||||
};
|
||||
|
||||
|
|
|
@ -89,6 +89,7 @@ class SystemSnapshotLinux final : public SystemSnapshot {
|
|||
int* daylight_offset_seconds,
|
||||
std::string* standard_name,
|
||||
std::string* daylight_name) const override;
|
||||
uint64_t AddressMask() const override { return 0; }
|
||||
|
||||
private:
|
||||
void ReadKernelVersion(const std::string& version_string);
|
||||
|
|
|
@ -71,6 +71,13 @@ void MachOImageAnnotationsReader::ReadCrashReporterClientAnnotations(
|
|||
const process_types::section* crash_info_section =
|
||||
image_reader_->GetSectionByName(
|
||||
SEG_DATA, "__crash_info", &crash_info_address);
|
||||
|
||||
if (!crash_info_section) {
|
||||
// On macOS 13, under some circumstances, `__crash_info` ends up in the
|
||||
// `__DATA_DIRTY` segment. This is known to happen for `dyld`.
|
||||
crash_info_section = image_reader_->GetSectionByName(
|
||||
"__DATA_DIRTY", "__crash_info", &crash_info_address);
|
||||
}
|
||||
if (!crash_info_section) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -390,5 +390,19 @@ void SystemSnapshotMac::TimeZone(DaylightSavingTimeStatus* dst_status,
|
|||
daylight_name);
|
||||
}
|
||||
|
||||
uint64_t SystemSnapshotMac::AddressMask() const {
|
||||
uint64_t mask = 0;
|
||||
#if defined(ARCH_CPU_ARM64)
|
||||
// `machdep.virtual_address_size` is the number of addressable bits in
|
||||
// userspace virtual addresses
|
||||
uint8_t addressable_bits =
|
||||
CastIntSysctlByName<uint8_t>("machdep.virtual_address_size", 0);
|
||||
if (addressable_bits) {
|
||||
mask = ~((1UL << addressable_bits) - 1);
|
||||
}
|
||||
#endif
|
||||
return mask;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace crashpad
|
||||
|
|
|
@ -83,6 +83,7 @@ class SystemSnapshotMac final : public SystemSnapshot {
|
|||
int* daylight_offset_seconds,
|
||||
std::string* standard_name,
|
||||
std::string* daylight_name) const override;
|
||||
uint64_t AddressMask() const override;
|
||||
|
||||
private:
|
||||
std::string os_version_full_;
|
||||
|
|
|
@ -266,7 +266,10 @@ bool ProcessSnapshotMinidump::InitializeCrashpadInfo() {
|
|||
return true;
|
||||
}
|
||||
|
||||
if (stream_it->second->DataSize < sizeof(crashpad_info_)) {
|
||||
constexpr size_t crashpad_info_min_size =
|
||||
offsetof(decltype(crashpad_info_), reserved);
|
||||
size_t remaining_data_size = stream_it->second->DataSize;
|
||||
if (remaining_data_size < crashpad_info_min_size) {
|
||||
LOG(ERROR) << "crashpad_info size mismatch";
|
||||
return false;
|
||||
}
|
||||
|
@ -275,9 +278,34 @@ bool ProcessSnapshotMinidump::InitializeCrashpadInfo() {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!file_reader_->ReadExactly(&crashpad_info_, sizeof(crashpad_info_))) {
|
||||
if (!file_reader_->ReadExactly(&crashpad_info_, crashpad_info_min_size)) {
|
||||
return false;
|
||||
}
|
||||
remaining_data_size -= crashpad_info_min_size;
|
||||
|
||||
// Read `reserved` if available.
|
||||
size_t crashpad_reserved_size = sizeof(crashpad_info_.reserved);
|
||||
if (remaining_data_size >= crashpad_reserved_size) {
|
||||
if (!file_reader_->ReadExactly(&crashpad_info_.reserved,
|
||||
crashpad_reserved_size)) {
|
||||
return false;
|
||||
}
|
||||
remaining_data_size -= crashpad_reserved_size;
|
||||
} else {
|
||||
crashpad_info_.reserved = 0;
|
||||
}
|
||||
|
||||
// Read `address_mask` if available.
|
||||
size_t crashpad_address_mask_size = sizeof(crashpad_info_.address_mask);
|
||||
if (remaining_data_size >= crashpad_address_mask_size) {
|
||||
if (!file_reader_->ReadExactly(&crashpad_info_.address_mask,
|
||||
crashpad_address_mask_size)) {
|
||||
return false;
|
||||
}
|
||||
remaining_data_size -= crashpad_address_mask_size;
|
||||
} else {
|
||||
crashpad_info_.address_mask = 0;
|
||||
}
|
||||
|
||||
if (crashpad_info_.version != MinidumpCrashpadInfo::kVersion) {
|
||||
LOG(ERROR) << "crashpad_info version mismatch";
|
||||
|
|
|
@ -270,6 +270,47 @@ TEST(ProcessSnapshotMinidump, ClientID) {
|
|||
EXPECT_TRUE(process_snapshot.AnnotationsSimpleMap().empty());
|
||||
}
|
||||
|
||||
TEST(ProcessSnapshotMinidump, ReadOldCrashpadInfo) {
|
||||
StringFile string_file;
|
||||
|
||||
MINIDUMP_HEADER header = {};
|
||||
EXPECT_TRUE(string_file.Write(&header, sizeof(header)));
|
||||
|
||||
UUID client_id;
|
||||
ASSERT_TRUE(
|
||||
client_id.InitializeFromString("0001f4a9-d00d-5155-0a55-c0ffeec0ffee"));
|
||||
|
||||
MinidumpCrashpadInfo crashpad_info = {};
|
||||
crashpad_info.version = MinidumpCrashpadInfo::kVersion;
|
||||
crashpad_info.client_id = client_id;
|
||||
|
||||
MINIDUMP_DIRECTORY crashpad_info_directory = {};
|
||||
crashpad_info_directory.StreamType = kMinidumpStreamTypeCrashpadInfo;
|
||||
crashpad_info_directory.Location.Rva =
|
||||
static_cast<RVA>(string_file.SeekGet());
|
||||
EXPECT_TRUE(string_file.Write(&crashpad_info, sizeof(crashpad_info) - 8));
|
||||
crashpad_info_directory.Location.DataSize = sizeof(crashpad_info) - 8;
|
||||
|
||||
header.StreamDirectoryRva = static_cast<RVA>(string_file.SeekGet());
|
||||
EXPECT_TRUE(string_file.Write(&crashpad_info_directory,
|
||||
sizeof(crashpad_info_directory)));
|
||||
|
||||
header.Signature = MINIDUMP_SIGNATURE;
|
||||
header.Version = MINIDUMP_VERSION;
|
||||
header.NumberOfStreams = 1;
|
||||
EXPECT_TRUE(string_file.SeekSet(0));
|
||||
EXPECT_TRUE(string_file.Write(&header, sizeof(header)));
|
||||
|
||||
ProcessSnapshotMinidump process_snapshot;
|
||||
EXPECT_TRUE(process_snapshot.Initialize(&string_file));
|
||||
|
||||
UUID actual_client_id;
|
||||
process_snapshot.ClientID(&actual_client_id);
|
||||
EXPECT_EQ(actual_client_id, client_id);
|
||||
|
||||
EXPECT_TRUE(process_snapshot.AnnotationsSimpleMap().empty());
|
||||
}
|
||||
|
||||
TEST(ProcessSnapshotMinidump, AnnotationsSimpleMap) {
|
||||
StringFile string_file;
|
||||
|
||||
|
|
|
@ -195,5 +195,11 @@ void SystemSnapshotMinidump::TimeZone(DaylightSavingTimeStatus* dst_status,
|
|||
NOTREACHED(); // https://crashpad.chromium.org/bug/10
|
||||
}
|
||||
|
||||
uint64_t SystemSnapshotMinidump::AddressMask() const {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
NOTREACHED(); // https://crashpad.chromium.org/bug/10
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace crashpad
|
||||
|
|
|
@ -74,6 +74,7 @@ class SystemSnapshotMinidump : public SystemSnapshot {
|
|||
int* daylight_offset_seconds,
|
||||
std::string* standard_name,
|
||||
std::string* daylight_name) const override;
|
||||
uint64_t AddressMask() const override;
|
||||
|
||||
private:
|
||||
MINIDUMP_SYSTEM_INFO minidump_system_info_;
|
||||
|
|
|
@ -275,6 +275,20 @@ class SystemSnapshot {
|
|||
int* daylight_offset_seconds,
|
||||
std::string* standard_name,
|
||||
std::string* daylight_name) const = 0;
|
||||
|
||||
//! \brief Returns a mask indicating the range of valid addresses for a
|
||||
//! pointer.
|
||||
//!
|
||||
//! ARM64 supports storing pointer authentication codes in the upper bits of
|
||||
//! a pointer. This mask is generated based on the number of bits in a pointer
|
||||
//! reserved for the authentication codes. To recover an address from pointer
|
||||
//! with an authentication code, `AND` this mask with the pointer. If the pac
|
||||
//! sign extension bit is set, instead `~` and `OR` this mask with the
|
||||
//! pointer.
|
||||
//!
|
||||
//! If the platform does not support pointer authentication, or the range of
|
||||
//! valid addressees for a pointer was inaccessible, this field will be 0.
|
||||
virtual uint64_t AddressMask() const = 0;
|
||||
};
|
||||
|
||||
} // namespace crashpad
|
||||
|
|
|
@ -36,14 +36,14 @@ TestSystemSnapshot::TestSystemSnapshot()
|
|||
os_version_bugfix_(0),
|
||||
os_version_build_(),
|
||||
os_version_full_(),
|
||||
address_mask_(0),
|
||||
nx_enabled_(false),
|
||||
machine_description_(),
|
||||
time_zone_dst_status_(kDoesNotObserveDaylightSavingTime),
|
||||
time_zone_standard_offset_seconds_(0),
|
||||
time_zone_daylight_offset_seconds_(0),
|
||||
time_zone_standard_name_(),
|
||||
time_zone_daylight_name_() {
|
||||
}
|
||||
time_zone_daylight_name_() {}
|
||||
|
||||
TestSystemSnapshot::~TestSystemSnapshot() {
|
||||
}
|
||||
|
@ -130,5 +130,9 @@ void TestSystemSnapshot::TimeZone(DaylightSavingTimeStatus* dst_status,
|
|||
*daylight_name = time_zone_daylight_name_;
|
||||
}
|
||||
|
||||
uint64_t TestSystemSnapshot::AddressMask() const {
|
||||
return address_mask_;
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace crashpad
|
||||
|
|
|
@ -90,6 +90,8 @@ class TestSystemSnapshot final : public SystemSnapshot {
|
|||
time_zone_daylight_name_ = daylight_name;
|
||||
}
|
||||
|
||||
void SetAddressMask(uint64_t mask) { address_mask_ = mask; }
|
||||
|
||||
// SystemSnapshot:
|
||||
|
||||
CPUArchitecture GetCPUArchitecture() const override;
|
||||
|
@ -114,6 +116,7 @@ class TestSystemSnapshot final : public SystemSnapshot {
|
|||
int* daylight_offset_seconds,
|
||||
std::string* standard_name,
|
||||
std::string* daylight_name) const override;
|
||||
uint64_t AddressMask() const override;
|
||||
|
||||
private:
|
||||
CPUArchitecture cpu_architecture_;
|
||||
|
@ -134,6 +137,7 @@ class TestSystemSnapshot final : public SystemSnapshot {
|
|||
int os_version_bugfix_;
|
||||
std::string os_version_build_;
|
||||
std::string os_version_full_;
|
||||
uint64_t address_mask_;
|
||||
bool nx_enabled_;
|
||||
std::string machine_description_;
|
||||
DaylightSavingTimeStatus time_zone_dst_status_;
|
||||
|
|
|
@ -151,22 +151,20 @@ void DoStackWalk(ProcessReaderWin::Thread* thread,
|
|||
stack_frame.AddrStack.Mode = AddrModeFlat;
|
||||
|
||||
int machine_type = IMAGE_FILE_MACHINE_I386;
|
||||
LPVOID ctx = NULL;
|
||||
CONTEXT ctx;
|
||||
#if defined(ARCH_CPU_X86)
|
||||
const CONTEXT* ctx_ = thread->context.context<CONTEXT>();
|
||||
stack_frame.AddrPC.Offset = ctx_->Eip;
|
||||
stack_frame.AddrFrame.Offset = ctx_->Ebp;
|
||||
stack_frame.AddrStack.Offset = ctx_->Esp;
|
||||
ctx = (LPVOID)ctx_;
|
||||
ctx = *thread->context.context<CONTEXT>();
|
||||
stack_frame.AddrPC.Offset = ctx.Eip;
|
||||
stack_frame.AddrFrame.Offset = ctx.Ebp;
|
||||
stack_frame.AddrStack.Offset = ctx.Esp;
|
||||
#elif defined(ARCH_CPU_X86_64)
|
||||
// if (!is_64_reading_32) {
|
||||
machine_type = IMAGE_FILE_MACHINE_AMD64;
|
||||
|
||||
const CONTEXT* ctx_ = thread->context.context<CONTEXT>();
|
||||
stack_frame.AddrPC.Offset = ctx_->Rip;
|
||||
stack_frame.AddrFrame.Offset = ctx_->Rbp;
|
||||
stack_frame.AddrStack.Offset = ctx_->Rsp;
|
||||
ctx = (LPVOID)ctx_;
|
||||
ctx = *thread->context.context<CONTEXT>();
|
||||
stack_frame.AddrPC.Offset = ctx.Rip;
|
||||
stack_frame.AddrFrame.Offset = ctx.Rbp;
|
||||
stack_frame.AddrStack.Offset = ctx.Rsp;
|
||||
// } else {
|
||||
// const WOW64_CONTEXT* ctx_ = &thread->context.wow64;
|
||||
// stack_frame.AddrPC.Offset = ctx_->Eip;
|
||||
|
@ -176,7 +174,7 @@ void DoStackWalk(ProcessReaderWin::Thread* thread,
|
|||
// }
|
||||
|
||||
// TODO: we dont support this right away, maybe in the future
|
||||
//#elif defined(ARCH_CPU_ARM64)
|
||||
// #elif defined(ARCH_CPU_ARM64)
|
||||
// machine_type = IMAGE_FILE_MACHINE_ARM64;
|
||||
#else
|
||||
#error Unsupported Windows Arch
|
||||
|
@ -192,7 +190,7 @@ void DoStackWalk(ProcessReaderWin::Thread* thread,
|
|||
process,
|
||||
thread_handle,
|
||||
&stack_frame,
|
||||
ctx,
|
||||
&ctx,
|
||||
NULL,
|
||||
SymFunctionTableAccess64,
|
||||
SymGetModuleBase64,
|
||||
|
|
|
@ -79,6 +79,7 @@ class SystemSnapshotWin final : public SystemSnapshot {
|
|||
int* daylight_offset_seconds,
|
||||
std::string* standard_name,
|
||||
std::string* daylight_name) const override;
|
||||
uint64_t AddressMask() const override { return 0; }
|
||||
|
||||
private:
|
||||
std::string os_version_full_;
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
#endif // BUILDFLAG(IS_WIN)
|
||||
|
||||
#if defined(CRASHPAD_IS_IN_CHROMIUM)
|
||||
#include "base/bind.h"
|
||||
#include "base/functional/bind.h"
|
||||
#include "base/test/launcher/unit_test_launcher.h"
|
||||
#include "base/test/test_suite.h"
|
||||
#endif // CRASHPAD_IS_IN_CHROMIUM
|
||||
|
|
|
@ -70,6 +70,7 @@ source_set("xcuitests") {
|
|||
deps = [
|
||||
"../../build:ios_enable_arc",
|
||||
"../../build:ios_xctest",
|
||||
"../../client:common",
|
||||
"../../test/ios/host:app_shared_sources",
|
||||
"../../third_party/edo",
|
||||
"../../util",
|
||||
|
|
|
@ -15,8 +15,11 @@
|
|||
#import <XCTest/XCTest.h>
|
||||
#include <objc/runtime.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#import "Service/Sources/EDOClientService.h"
|
||||
#include "build/build_config.h"
|
||||
#include "client/length_delimited_ring_buffer.h"
|
||||
#import "test/ios/host/cptest_shared_object.h"
|
||||
#include "util/mach/exception_types.h"
|
||||
#include "util/mach/mach_extensions.h"
|
||||
|
@ -322,6 +325,31 @@
|
|||
isEqualToString:@"same-name 3"]);
|
||||
XCTAssertTrue([[dict[@"objects"][2] valueForKeyPath:@"#TEST# one"]
|
||||
isEqualToString:@"moocow"]);
|
||||
// Ensure `ring_buffer` is present but not `busy_ring_buffer`.
|
||||
XCTAssertEqual(1u, [dict[@"ringbuffers"] count]);
|
||||
NSData* ringBufferNSData =
|
||||
[dict[@"ringbuffers"][0] valueForKeyPath:@"#TEST# ring_buffer"];
|
||||
crashpad::RingBufferData ringBufferData;
|
||||
XCTAssertTrue(ringBufferData.DeserializeFromBuffer(ringBufferNSData.bytes,
|
||||
ringBufferNSData.length));
|
||||
crashpad::LengthDelimitedRingBufferReader reader(ringBufferData);
|
||||
|
||||
std::vector<uint8_t> ringBufferEntry;
|
||||
XCTAssertTrue(reader.Pop(ringBufferEntry));
|
||||
NSString* firstEntry = [[NSString alloc] initWithBytes:ringBufferEntry.data()
|
||||
length:ringBufferEntry.size()
|
||||
encoding:NSUTF8StringEncoding];
|
||||
XCTAssertEqualObjects(firstEntry, @"hello");
|
||||
ringBufferEntry.clear();
|
||||
|
||||
XCTAssertTrue(reader.Pop(ringBufferEntry));
|
||||
NSString* secondEntry = [[NSString alloc] initWithBytes:ringBufferEntry.data()
|
||||
length:ringBufferEntry.size()
|
||||
encoding:NSUTF8StringEncoding];
|
||||
XCTAssertEqualObjects(secondEntry, @"goodbye");
|
||||
ringBufferEntry.clear();
|
||||
|
||||
XCTAssertFalse(reader.Pop(ringBufferEntry));
|
||||
}
|
||||
|
||||
- (void)testDumpWithoutCrash {
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
#include "client/crash_report_database.h"
|
||||
#include "client/crashpad_client.h"
|
||||
#include "client/crashpad_info.h"
|
||||
#include "client/ring_buffer_annotation.h"
|
||||
#include "client/simple_string_dictionary.h"
|
||||
#include "client/simulate_crash.h"
|
||||
#include "snapshot/minidump/process_snapshot_minidump.h"
|
||||
|
@ -58,6 +59,9 @@ using Report = crashpad::CrashReportDatabase::Report;
|
|||
|
||||
namespace {
|
||||
|
||||
constexpr crashpad::Annotation::Type kRingBufferType =
|
||||
crashpad::Annotation::UserDefinedType(42);
|
||||
|
||||
base::FilePath GetDatabaseDir() {
|
||||
base::FilePath database_dir([NSFileManager.defaultManager
|
||||
URLsForDirectory:NSDocumentDirectory
|
||||
|
@ -251,7 +255,8 @@ UIWindow* GetAnyWindow() {
|
|||
NSDictionary* dict = @{
|
||||
@"simplemap" : [@{} mutableCopy],
|
||||
@"vector" : [@[] mutableCopy],
|
||||
@"objects" : [@[] mutableCopy]
|
||||
@"objects" : [@[] mutableCopy],
|
||||
@"ringbuffers" : [@[] mutableCopy],
|
||||
};
|
||||
for (const auto* module : process_snapshot->Modules()) {
|
||||
for (const auto& kv : module->AnnotationsSimpleMap()) {
|
||||
|
@ -262,14 +267,18 @@ UIWindow* GetAnyWindow() {
|
|||
[dict[@"vector"] addObject:@(annotation.c_str())];
|
||||
}
|
||||
for (const auto& annotation : module->AnnotationObjects()) {
|
||||
if (annotation.type !=
|
||||
if (annotation.type ==
|
||||
static_cast<uint16_t>(crashpad::Annotation::Type::kString)) {
|
||||
continue;
|
||||
std::string value(
|
||||
reinterpret_cast<const char*>(annotation.value.data()),
|
||||
annotation.value.size());
|
||||
[dict[@"objects"]
|
||||
addObject:@{@(annotation.name.c_str()) : @(value.c_str())}];
|
||||
} else if (annotation.type == static_cast<uint16_t>(kRingBufferType)) {
|
||||
NSData* data = [NSData dataWithBytes:annotation.value.data()
|
||||
length:annotation.value.size()];
|
||||
[dict[@"ringbuffers"] addObject:@{@(annotation.name.c_str()) : data}];
|
||||
}
|
||||
std::string value(reinterpret_cast<const char*>(annotation.value.data()),
|
||||
annotation.value.size());
|
||||
[dict[@"objects"]
|
||||
addObject:@{@(annotation.name.c_str()) : @(value.c_str())}];
|
||||
}
|
||||
}
|
||||
return [dict passByValue];
|
||||
|
@ -418,12 +427,23 @@ UIWindow* GetAnyWindow() {
|
|||
"#TEST# same-name"};
|
||||
static crashpad::StringAnnotation<32> test_annotation_four{
|
||||
"#TEST# same-name"};
|
||||
static crashpad::RingBufferAnnotation<32> test_ring_buffer_annotation(
|
||||
kRingBufferType, "#TEST# ring_buffer");
|
||||
static crashpad::RingBufferAnnotation<32> test_busy_ring_buffer_annotation(
|
||||
kRingBufferType, "#TEST# busy_ring_buffer");
|
||||
|
||||
test_annotation_one.Set("moocow");
|
||||
test_annotation_two.Set("this will be cleared");
|
||||
test_annotation_three.Set("same-name 3");
|
||||
test_annotation_four.Set("same-name 4");
|
||||
test_annotation_two.Clear();
|
||||
test_ring_buffer_annotation.Push("hello", 5);
|
||||
test_ring_buffer_annotation.Push("goodbye", 7);
|
||||
test_busy_ring_buffer_annotation.Push("busy", 4);
|
||||
// Take the scoped spin guard on `test_busy_ring_buffer_annotation` to mimic
|
||||
// an in-flight `Push()` so its contents are not included in the dump.
|
||||
auto guard = test_busy_ring_buffer_annotation.TryCreateScopedSpinGuard(
|
||||
/*timeout_nanos=*/0);
|
||||
abort();
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
#elif defined(CRASHPAD_LSS_SOURCE_EMBEDDED)
|
||||
#include "third_party/lss/lss/linux_syscall_support.h"
|
||||
#elif defined(CRASHPAD_LSS_SOURCE_FUCHSIA)
|
||||
#include "../../../../third_party/linux-syscall-support/linux_syscall_support.h"
|
||||
#include "../../../../../third_party/linux-syscall-support/src/linux_syscall_support.h"
|
||||
#else
|
||||
#error Unknown lss source
|
||||
#endif
|
||||
|
|
|
@ -20,7 +20,7 @@ group("base") {
|
|||
} else if (crashpad_is_standalone) {
|
||||
public_deps = [ "mini_chromium/base" ]
|
||||
} else if (crashpad_is_in_fuchsia) {
|
||||
public_deps = [ "//third_party/mini_chromium/base" ]
|
||||
public_deps = [ mini_chromium_import_root + "/base" ]
|
||||
} else if (crashpad_is_external) {
|
||||
public_deps = [ "../../../../mini_chromium/mini_chromium/base" ]
|
||||
} else if (crashpad_is_in_dart) {
|
||||
|
@ -42,7 +42,7 @@ group("build") {
|
|||
} else if (crashpad_is_standalone) {
|
||||
public_deps = [ "mini_chromium/build" ]
|
||||
} else if (crashpad_is_in_fuchsia) {
|
||||
public_deps = [ "//third_party/mini_chromium/build" ]
|
||||
public_deps = [ mini_chromium_import_root + "/build" ]
|
||||
} else if (crashpad_is_external) {
|
||||
public_deps = [ "../../../../mini_chromium/mini_chromium/build" ]
|
||||
} else if (crashpad_is_in_dart) {
|
||||
|
@ -56,11 +56,10 @@ group("chromeos_buildflags") {
|
|||
} else if (crashpad_is_standalone) {
|
||||
public_deps = [ "mini_chromium/build:chromeos_buildflags" ]
|
||||
} else if (crashpad_is_in_fuchsia) {
|
||||
public_deps = [ "//third_party/mini_chromium/build:chromeos_buildflags" ]
|
||||
public_deps = [ mini_chromium_import_root + "/build:chromeos_buildflags" ]
|
||||
} else if (crashpad_is_external) {
|
||||
public_deps = [ "../../../../mini_chromium/mini_chromium/build:chromeos_buildflags" ]
|
||||
} else if (crashpad_is_in_dart) {
|
||||
public_deps = [ "//third_party/mini_chromium/mini_chromium/build:chromeos_buildflags" ]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
if(CRASHPAD_ZLIB_SYSTEM)
|
||||
add_library(crashpad_zlib INTERFACE)
|
||||
string(REPLACE ";" "$<SEMICOLON>" GENEX_ZLIB_LIBRARIES "${ZLIB_LIBRARIES}")
|
||||
target_include_directories(crashpad_zlib INTERFACE
|
||||
$<BUILD_INTERFACE:${ZLIB_INCLUDE_DIRS}>
|
||||
)
|
||||
target_compile_definitions(crashpad_zlib INTERFACE
|
||||
ZLIB_CONST
|
||||
CRASHPAD_ZLIB_SOURCE_SYSTEM
|
||||
$<BUILD_INTERFACE:${ZLIB_COMPILE_DEFINITIONS}>
|
||||
)
|
||||
target_link_libraries(crashpad_zlib INTERFACE $<BUILD_INTERFACE:${GENEX_ZLIB_LIBRARIES}>)
|
||||
target_link_libraries(crashpad_zlib INTERFACE ZLIB::ZLIB)
|
||||
else()
|
||||
add_library(crashpad_zlib STATIC
|
||||
zlib/adler32.c
|
||||
|
|
|
@ -267,6 +267,7 @@ crashpad_static_library("util") {
|
|||
"stream/zlib_output_stream.h",
|
||||
"string/split_string.cc",
|
||||
"string/split_string.h",
|
||||
"synchronization/scoped_spin_guard.h",
|
||||
"synchronization/semaphore.h",
|
||||
"thread/stoppable.h",
|
||||
"thread/thread.cc",
|
||||
|
@ -402,6 +403,8 @@ crashpad_static_library("util") {
|
|||
"ios/scoped_background_task.mm",
|
||||
"ios/scoped_vm_read.cc",
|
||||
"ios/scoped_vm_read.h",
|
||||
"ios/scoped_vm_map.cc",
|
||||
"ios/scoped_vm_map.h",
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -561,7 +564,8 @@ crashpad_static_library("util") {
|
|||
|
||||
# Include generated files starting with "util".
|
||||
if (crashpad_is_in_fuchsia) {
|
||||
include_dirs = [ "$root_gen_dir/third_party/crashpad" ]
|
||||
include_dirs =
|
||||
[ "$root_gen_dir/" + rebase_path(fuchsia_crashpad_root, "//") ]
|
||||
} else {
|
||||
include_dirs = [ "$root_gen_dir/third_party/crashpad/crashpad" ]
|
||||
}
|
||||
|
@ -775,6 +779,7 @@ source_set("util_test") {
|
|||
"stream/test_output_stream.h",
|
||||
"stream/zlib_output_stream_test.cc",
|
||||
"string/split_string_test.cc",
|
||||
"synchronization/scoped_spin_guard_test.cc",
|
||||
"synchronization/semaphore_test.cc",
|
||||
"thread/thread_log_messages_test.cc",
|
||||
"thread/thread_test.cc",
|
||||
|
@ -834,6 +839,7 @@ source_set("util_test") {
|
|||
"ios/ios_intermediate_dump_reader_test.cc",
|
||||
"ios/ios_intermediate_dump_writer_test.cc",
|
||||
"ios/scoped_vm_read_test.cc",
|
||||
"ios/scoped_vm_map_test.cc",
|
||||
]
|
||||
|
||||
sources -= [
|
||||
|
|
|
@ -104,6 +104,7 @@ add_library(crashpad_util STATIC
|
|||
stream/zlib_output_stream.h
|
||||
string/split_string.cc
|
||||
string/split_string.h
|
||||
synchronization/scoped_spin_guard.h
|
||||
synchronization/semaphore.h
|
||||
thread/stoppable.h
|
||||
thread/thread.cc
|
||||
|
@ -225,6 +226,8 @@ if(APPLE)
|
|||
ios/scoped_background_task.mm
|
||||
ios/scoped_vm_read.cc
|
||||
ios/scoped_vm_read.h
|
||||
ios/scoped_vm_map.cc
|
||||
ios/scoped_vm_map.h
|
||||
)
|
||||
# This specific file requires ARC support, while other parts do not
|
||||
# build when ARC is enabled.
|
||||
|
@ -245,8 +248,30 @@ if(ANDROID)
|
|||
endif()
|
||||
|
||||
if(LINUX OR ANDROID)
|
||||
if (LINUX)
|
||||
if(NOT CURL_FOUND) # Some other lib might bring libcurl already
|
||||
find_package(CURL REQUIRED)
|
||||
endif()
|
||||
|
||||
if(TARGET CURL::libcurl) # Only available in cmake 3.12+
|
||||
target_link_libraries(crashpad_util PRIVATE CURL::libcurl)
|
||||
else()
|
||||
# Needed for cmake < 3.12 support (cmake 3.12 introduced the target CURL::libcurl)
|
||||
target_include_directories(crashpad_util PRIVATE ${CURL_INCLUDE_DIR})
|
||||
# The exported sentry target must not contain any path of the build machine, therefore use generator expressions
|
||||
string(REPLACE ";" "$<SEMICOLON>" GENEX_CURL_LIBRARIES "${CURL_LIBRARIES}")
|
||||
string(REPLACE ";" "$<SEMICOLON>" GENEX_CURL_COMPILE_DEFINITIONS "${CURL_COMPILE_DEFINITIONS}")
|
||||
target_link_libraries(crashpad_util PRIVATE $<BUILD_INTERFACE:${GENEX_CURL_LIBRARIES}>)
|
||||
target_compile_definitions(crashpad_util PRIVATE $<BUILD_INTERFACE:${GENEX_CURL_COMPILE_DEFINITIONS}>)
|
||||
endif()
|
||||
|
||||
SET(HTTP_TRANSPORT_IMPL net/http_transport_libcurl.cc)
|
||||
else()
|
||||
SET(HTTP_TRANSPORT_IMPL net/http_transport_socket.cc)
|
||||
endif()
|
||||
|
||||
target_sources(crashpad_util PRIVATE
|
||||
net/http_transport_socket.cc
|
||||
${HTTP_TRANSPORT_IMPL}
|
||||
linux/address_types.h
|
||||
linux/auxiliary_vector.cc
|
||||
linux/auxiliary_vector.h
|
||||
|
|
|
@ -85,6 +85,7 @@ namespace internal {
|
|||
TD(kFree, 5017) \
|
||||
TD(kInactive, 5018) \
|
||||
TD(kWired, 5019) \
|
||||
TD(kAddressMask, 5020) \
|
||||
TD(kThreads, 6000) \
|
||||
TD(kDebugState, 6001) \
|
||||
TD(kFloatState, 6002) \
|
||||
|
@ -103,7 +104,6 @@ namespace internal {
|
|||
TD(kMaxValue, 65535) \
|
||||
// clang-format on
|
||||
|
||||
|
||||
//! \brief They key for items in the intermediate dump file.
|
||||
//!
|
||||
//! These values are persisted to the intermediate crash dump file. Entries
|
||||
|
|
|
@ -43,6 +43,7 @@ class IOSSystemDataCollector {
|
|||
const std::string& StandardName() const { return standard_name_; }
|
||||
const std::string& DaylightName() const { return daylight_name_; }
|
||||
bool IsApplicationActive() const { return active_; }
|
||||
uint64_t AddressMask() const { return address_mask_; }
|
||||
|
||||
// Currently unused by minidump.
|
||||
int Orientation() const { return orientation_; }
|
||||
|
@ -80,6 +81,7 @@ class IOSSystemDataCollector {
|
|||
std::string standard_name_;
|
||||
std::string daylight_name_;
|
||||
ActiveApplicationCallback active_application_callback_;
|
||||
uint64_t address_mask_;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
|
|
@ -102,6 +102,16 @@ IOSSystemDataCollector::IOSSystemDataCollector()
|
|||
#if defined(ARCH_CPU_X86_64)
|
||||
cpu_vendor_ = ReadStringSysctlByName("machdep.cpu.vendor");
|
||||
#endif
|
||||
uint32_t addressable_bits = 0;
|
||||
size_t len = sizeof(uint32_t);
|
||||
// `machdep.virtual_address_size` is the number of addressable bits in
|
||||
// userspace virtual addresses
|
||||
if (sysctlbyname(
|
||||
"machdep.virtual_address_size", &addressable_bits, &len, NULL, 0) !=
|
||||
0) {
|
||||
addressable_bits = 0;
|
||||
}
|
||||
address_mask_ = ~((1UL << addressable_bits) - 1);
|
||||
|
||||
#if TARGET_OS_SIMULATOR
|
||||
// TODO(justincohen): Consider adding board and model information to
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
// Copyright 2023 The Crashpad Authors
|
||||
//
|
||||
// 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.
|
||||
|
||||
#include "util/ios/scoped_vm_map.h"
|
||||
|
||||
#include "util/ios/raw_logging.h"
|
||||
|
||||
namespace crashpad {
|
||||
namespace internal {
|
||||
|
||||
ScopedVMMapInternal::ScopedVMMapInternal()
|
||||
: data_(0),
|
||||
region_start_(0),
|
||||
region_size_(0),
|
||||
cur_protection_(VM_PROT_NONE) {}
|
||||
|
||||
ScopedVMMapInternal::~ScopedVMMapInternal() {
|
||||
Reset();
|
||||
}
|
||||
|
||||
bool ScopedVMMapInternal::Map(const void* data, const size_t data_length) {
|
||||
Reset();
|
||||
|
||||
vm_address_t data_address = reinterpret_cast<vm_address_t>(data);
|
||||
vm_address_t page_region_address = trunc_page(data_address);
|
||||
region_size_ = round_page(data_address - page_region_address + data_length);
|
||||
if (region_size_ < data_length) {
|
||||
CRASHPAD_RAW_LOG("ScopedVMMap data_length overflow");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Since region_start_ is 0, vm_remap() will choose an address to which the
|
||||
// memory will be mapped and store the mapped address in region_start_ on
|
||||
// success.
|
||||
vm_prot_t max_protection;
|
||||
kern_return_t kr = vm_remap(mach_task_self(),
|
||||
®ion_start_,
|
||||
region_size_,
|
||||
0,
|
||||
TRUE,
|
||||
mach_task_self(),
|
||||
page_region_address,
|
||||
FALSE,
|
||||
&cur_protection_,
|
||||
&max_protection,
|
||||
VM_INHERIT_DEFAULT);
|
||||
if (kr != KERN_SUCCESS) {
|
||||
// It's expected that this will sometimes fail. Don't log here.
|
||||
return false;
|
||||
}
|
||||
|
||||
data_ = region_start_ + (data_address - page_region_address);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ScopedVMMapInternal::Reset() {
|
||||
if (!region_start_) {
|
||||
return;
|
||||
}
|
||||
|
||||
kern_return_t kr =
|
||||
vm_deallocate(mach_task_self(), region_start_, region_size_);
|
||||
|
||||
if (kr != KERN_SUCCESS) {
|
||||
CRASHPAD_RAW_LOG_ERROR(kr, "vm_deallocate");
|
||||
}
|
||||
|
||||
data_ = 0;
|
||||
region_start_ = 0;
|
||||
region_size_ = 0;
|
||||
cur_protection_ = VM_PROT_NONE;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace crashpad
|
|
@ -0,0 +1,125 @@
|
|||
// Copyright 2023 The Crashpad Authors
|
||||
//
|
||||
// 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_UTIL_IOS_SCOPED_VM_MAP_H_
|
||||
#define CRASHPAD_UTIL_IOS_SCOPED_VM_MAP_H_
|
||||
|
||||
#include <mach/mach.h>
|
||||
|
||||
namespace crashpad {
|
||||
namespace internal {
|
||||
|
||||
//! \brief Non-templated internal class to be used by ScopedVMMap.
|
||||
//!
|
||||
//! Note: RUNS-DURING-CRASH.
|
||||
class ScopedVMMapInternal {
|
||||
public:
|
||||
ScopedVMMapInternal();
|
||||
|
||||
ScopedVMMapInternal(const ScopedVMMapInternal&) = delete;
|
||||
ScopedVMMapInternal& operator=(const ScopedVMMapInternal&) = delete;
|
||||
|
||||
~ScopedVMMapInternal();
|
||||
|
||||
//! \brief Releases any previously mapped data and vm_remaps \a data. Logs an
|
||||
//! error on failure.
|
||||
//!
|
||||
//! \param[in] data Memory to be mapped by vm_remap.
|
||||
//! \param[in] data_length Length of \a data.
|
||||
//!
|
||||
//! \return `true` if all the data was mapped. Logs an error and returns false
|
||||
//! on failure.
|
||||
bool Map(const void* data, size_t data_length);
|
||||
|
||||
//! \brief Returns the current protection for the memory in the region.
|
||||
vm_prot_t CurrentProtection() const { return cur_protection_; }
|
||||
|
||||
vm_address_t data() const { return data_; }
|
||||
|
||||
private:
|
||||
//! \brief Deallocates any resources allocated by this object and resets it
|
||||
//! to its original state.
|
||||
void Reset();
|
||||
|
||||
// The address within region_start_ at which the mapped data is available.
|
||||
vm_address_t data_;
|
||||
|
||||
// The region returned by vm_remap().
|
||||
vm_address_t region_start_;
|
||||
|
||||
// The size of the region returned by vm_remap().
|
||||
vm_size_t region_size_;
|
||||
|
||||
// The current protection for the memory region.
|
||||
vm_prot_t cur_protection_;
|
||||
};
|
||||
|
||||
//! \brief A scoped wrapper for calls to `vm_remap` and `vm_deallocate`. Allows
|
||||
//! in-process handler to safely read and write memory (modulo its
|
||||
//! protection level) for the intermediate dump.
|
||||
//!
|
||||
//! Note: RUNS-DURING-CRASH.
|
||||
template <typename T>
|
||||
class ScopedVMMap {
|
||||
public:
|
||||
ScopedVMMap() : internal_() {}
|
||||
|
||||
ScopedVMMap(const ScopedVMMap&) = delete;
|
||||
ScopedVMMap& operator=(const ScopedVMMap&) = delete;
|
||||
|
||||
~ScopedVMMap() {}
|
||||
|
||||
//! \brief Releases any previously mapped data and vm_remaps data.
|
||||
//!
|
||||
//! \param[in] data Memory to be mapped by vm_remap.
|
||||
//! \param[in] count Length of \a data.
|
||||
//!
|
||||
//! \return `true` if all \a data was mapped. Returns false on failure.
|
||||
bool Map(const void* data, size_t count = 1) {
|
||||
size_t data_length = count * sizeof(T);
|
||||
return internal_.Map(data, data_length);
|
||||
}
|
||||
|
||||
//! \brief Releases any previously mapped data and vm_remaps address.
|
||||
//!
|
||||
//! Before reading or writing the memory, check `CurrentProtection()` to
|
||||
//! ensure the data is readable or writable.
|
||||
//!
|
||||
//! \param[in] address Address of memory to be mapped by vm_remap.
|
||||
//! \param[in] count Length of \a data.
|
||||
//!
|
||||
//! \return `true` if all of \a address was mapped. Returns false on failure.
|
||||
bool Map(vm_address_t address, size_t count = 1) {
|
||||
return Map(reinterpret_cast<T*>(address), count);
|
||||
}
|
||||
|
||||
//! \brief Returns the pointer to memory safe to read and write (respecting
|
||||
//! the CurrentProtection() level) during the in-process crash handler.
|
||||
T* operator->() const { return get(); }
|
||||
|
||||
//! \brief Returns the pointer to memory safe to read and write (respecting
|
||||
//! the CurrentProtection() level) during the in-process crash handler.
|
||||
T* get() const { return reinterpret_cast<T*>(internal_.data()); }
|
||||
|
||||
//! \brief Returns the current protection level of the mapped memory.
|
||||
vm_prot_t CurrentProtection() const { return internal_.CurrentProtection(); }
|
||||
|
||||
private:
|
||||
ScopedVMMapInternal internal_;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace crashpad
|
||||
|
||||
#endif // CRASHPAD_UTIL_IOS_SCOPED_VM_MAP_H_
|
|
@ -0,0 +1,94 @@
|
|||
// Copyright 2023 The Crashpad Authors
|
||||
//
|
||||
// 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.
|
||||
|
||||
#include "util/ios/scoped_vm_map.h"
|
||||
|
||||
#include <sys/time.h>
|
||||
|
||||
#include "base/mac/scoped_mach_vm.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/mac/mach_errors.h"
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
namespace {
|
||||
|
||||
TEST(ScopedVMMapTest, BasicFunctionality) {
|
||||
// bad data or count.
|
||||
internal::ScopedVMMap<vm_address_t> vmmap_bad;
|
||||
EXPECT_FALSE(vmmap_bad.Map(nullptr, 100));
|
||||
EXPECT_FALSE(vmmap_bad.Map(reinterpret_cast<void*>(0x1000), 100));
|
||||
vm_address_t invalid_address = 1;
|
||||
EXPECT_FALSE(vmmap_bad.Map(&invalid_address, 1000000000));
|
||||
EXPECT_FALSE(vmmap_bad.Map(&invalid_address, -1));
|
||||
|
||||
vm_address_t valid_address = reinterpret_cast<vm_address_t>(this);
|
||||
EXPECT_FALSE(vmmap_bad.Map(&valid_address, 1000000000));
|
||||
EXPECT_FALSE(vmmap_bad.Map(&valid_address, -1));
|
||||
|
||||
// array
|
||||
static constexpr char map_me[] = "map me";
|
||||
internal::ScopedVMMap<char> vmmap_string;
|
||||
ASSERT_TRUE(vmmap_string.Map(map_me, strlen(map_me)));
|
||||
EXPECT_STREQ(vmmap_string.get(), map_me);
|
||||
EXPECT_TRUE(vmmap_string.CurrentProtection() & VM_PROT_READ);
|
||||
|
||||
// struct
|
||||
timeval time_of_day;
|
||||
EXPECT_TRUE(gettimeofday(&time_of_day, nullptr) == 0);
|
||||
internal::ScopedVMMap<timeval> vmmap_time;
|
||||
ASSERT_TRUE(vmmap_time.Map(&time_of_day));
|
||||
constexpr vm_prot_t kReadWrite = VM_PROT_READ | VM_PROT_WRITE;
|
||||
EXPECT_EQ(vmmap_time.CurrentProtection() & kReadWrite, kReadWrite);
|
||||
EXPECT_EQ(vmmap_time->tv_sec, time_of_day.tv_sec);
|
||||
EXPECT_EQ(vmmap_time->tv_usec, time_of_day.tv_usec);
|
||||
|
||||
// reset.
|
||||
timeval time_of_day2;
|
||||
EXPECT_TRUE(gettimeofday(&time_of_day2, nullptr) == 0);
|
||||
ASSERT_TRUE(vmmap_time.Map(&time_of_day2));
|
||||
EXPECT_EQ(vmmap_time.CurrentProtection() & kReadWrite, kReadWrite);
|
||||
EXPECT_EQ(vmmap_time->tv_sec, time_of_day2.tv_sec);
|
||||
EXPECT_EQ(vmmap_time->tv_usec, time_of_day2.tv_usec);
|
||||
}
|
||||
|
||||
TEST(ScopedVMMapTest, MissingMiddleVM) {
|
||||
char* region;
|
||||
vm_size_t page_size = getpagesize();
|
||||
vm_size_t region_size = page_size * 3;
|
||||
kern_return_t kr = vm_allocate(mach_task_self(),
|
||||
reinterpret_cast<vm_address_t*>(®ion),
|
||||
region_size,
|
||||
VM_FLAGS_ANYWHERE);
|
||||
ASSERT_EQ(kr, KERN_SUCCESS) << MachErrorMessage(kr, "vm_allocate");
|
||||
|
||||
base::mac::ScopedMachVM vm_owner(reinterpret_cast<vm_address_t>(region),
|
||||
region_size);
|
||||
|
||||
internal::ScopedVMMap<char> vmmap_missing_middle;
|
||||
ASSERT_TRUE(vmmap_missing_middle.Map(region, region_size));
|
||||
|
||||
// Dealloc middle page.
|
||||
kr = vm_deallocate(mach_task_self(),
|
||||
reinterpret_cast<vm_address_t>(region + page_size),
|
||||
page_size);
|
||||
ASSERT_EQ(kr, KERN_SUCCESS) << MachErrorMessage(kr, "vm_deallocate");
|
||||
|
||||
EXPECT_FALSE(vmmap_missing_middle.Map(region, region_size));
|
||||
ASSERT_TRUE(vmmap_missing_middle.Map(region, page_size));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace test
|
||||
} // namespace crashpad
|
|
@ -20,25 +20,15 @@ namespace crashpad {
|
|||
namespace internal {
|
||||
|
||||
ScopedVMReadInternal::ScopedVMReadInternal()
|
||||
: data_(0), vm_read_data_(0), vm_read_data_count_(0) {}
|
||||
: data_(0), region_start_(0), region_size_(0) {}
|
||||
|
||||
ScopedVMReadInternal::~ScopedVMReadInternal() {
|
||||
if (data_) {
|
||||
kern_return_t kr =
|
||||
vm_deallocate(mach_task_self(), vm_read_data_, vm_read_data_count_);
|
||||
if (kr != KERN_SUCCESS)
|
||||
CRASHPAD_RAW_LOG_ERROR(kr, "vm_deallocate");
|
||||
}
|
||||
Reset();
|
||||
}
|
||||
|
||||
bool ScopedVMReadInternal::Read(const void* data, const size_t data_length) {
|
||||
if (data_) {
|
||||
kern_return_t kr =
|
||||
vm_deallocate(mach_task_self(), vm_read_data_, vm_read_data_count_);
|
||||
if (kr != KERN_SUCCESS)
|
||||
CRASHPAD_RAW_LOG_ERROR(kr, "vm_deallocate");
|
||||
data_ = 0;
|
||||
}
|
||||
Reset();
|
||||
|
||||
vm_address_t data_address = reinterpret_cast<vm_address_t>(data);
|
||||
vm_address_t page_region_address = trunc_page(data_address);
|
||||
vm_size_t page_region_size =
|
||||
|
@ -50,16 +40,30 @@ bool ScopedVMReadInternal::Read(const void* data, const size_t data_length) {
|
|||
kern_return_t kr = vm_read(mach_task_self(),
|
||||
page_region_address,
|
||||
page_region_size,
|
||||
&vm_read_data_,
|
||||
&vm_read_data_count_);
|
||||
®ion_start_,
|
||||
®ion_size_);
|
||||
|
||||
if (kr == KERN_SUCCESS) {
|
||||
data_ = vm_read_data_ + (data_address - page_region_address);
|
||||
return true;
|
||||
} else {
|
||||
if (kr != KERN_SUCCESS) {
|
||||
// It's expected that this will sometimes fail. Don't log here.
|
||||
return false;
|
||||
}
|
||||
|
||||
data_ = region_start_ + (data_address - page_region_address);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ScopedVMReadInternal::Reset() {
|
||||
if (!region_start_) {
|
||||
return;
|
||||
}
|
||||
kern_return_t kr =
|
||||
vm_deallocate(mach_task_self(), region_start_, region_size_);
|
||||
if (kr != KERN_SUCCESS) {
|
||||
CRASHPAD_RAW_LOG_ERROR(kr, "vm_deallocate");
|
||||
}
|
||||
region_start_ = 0;
|
||||
region_size_ = 0;
|
||||
data_ = 0;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
|
|
@ -46,14 +46,18 @@ class ScopedVMReadInternal {
|
|||
vm_address_t data() const { return data_; }
|
||||
|
||||
private:
|
||||
// The address of the requested data.
|
||||
//! \brief Deallocates any resources allocated by this object and resets it
|
||||
//! to its original state.
|
||||
void Reset();
|
||||
|
||||
// The address within region_start_ at which the the data is available.
|
||||
vm_address_t data_;
|
||||
|
||||
// The rounded down page boundary of the requested data.
|
||||
vm_address_t vm_read_data_;
|
||||
// The region returned by vm_read().
|
||||
vm_address_t region_start_;
|
||||
|
||||
// The size of the pages that were actually read.
|
||||
mach_msg_type_number_t vm_read_data_count_;
|
||||
// The size of the region returned by vm_read().
|
||||
mach_msg_type_number_t region_size_;
|
||||
};
|
||||
|
||||
//! \brief A scoped wrapper for calls to `vm_read` and `vm_deallocate`. Allows
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
#include "base/mac/scoped_mach_vm.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/mac/mach_errors.h"
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
|
@ -26,43 +27,47 @@ namespace {
|
|||
TEST(ScopedVMReadTest, BasicFunctionality) {
|
||||
// bad data or count.
|
||||
internal::ScopedVMRead<vm_address_t> vmread_bad;
|
||||
ASSERT_FALSE(vmread_bad.Read(nullptr, 100));
|
||||
ASSERT_FALSE(vmread_bad.Read(reinterpret_cast<void*>(0x1000), 100));
|
||||
vm_address_t address = 1;
|
||||
ASSERT_FALSE(vmread_bad.Read(&address, 1000000000));
|
||||
ASSERT_FALSE(vmread_bad.Read(&address, -1));
|
||||
EXPECT_FALSE(vmread_bad.Read(nullptr, 100));
|
||||
EXPECT_FALSE(vmread_bad.Read(reinterpret_cast<void*>(0x1000), 100));
|
||||
vm_address_t invalid_address = 1;
|
||||
EXPECT_FALSE(vmread_bad.Read(&invalid_address, 1000000000));
|
||||
EXPECT_FALSE(vmread_bad.Read(&invalid_address, -1));
|
||||
|
||||
vm_address_t valid_address = reinterpret_cast<vm_address_t>(this);
|
||||
EXPECT_FALSE(vmread_bad.Read(&valid_address, 1000000000));
|
||||
EXPECT_FALSE(vmread_bad.Read(&valid_address, -1));
|
||||
// array
|
||||
constexpr char read_me[] = "read me";
|
||||
internal::ScopedVMRead<char> vmread_string;
|
||||
ASSERT_TRUE(vmread_string.Read(read_me, strlen(read_me)));
|
||||
EXPECT_STREQ(read_me, vmread_string.get());
|
||||
EXPECT_STREQ(vmread_string.get(), read_me);
|
||||
|
||||
// struct
|
||||
timeval time_of_day;
|
||||
EXPECT_TRUE(gettimeofday(&time_of_day, nullptr) == 0);
|
||||
internal::ScopedVMRead<timeval> vmread_time;
|
||||
ASSERT_TRUE(vmread_time.Read(&time_of_day));
|
||||
EXPECT_EQ(time_of_day.tv_sec, vmread_time->tv_sec);
|
||||
EXPECT_EQ(time_of_day.tv_usec, vmread_time->tv_usec);
|
||||
EXPECT_EQ(vmread_time->tv_sec, time_of_day.tv_sec);
|
||||
EXPECT_EQ(vmread_time->tv_usec, time_of_day.tv_usec);
|
||||
|
||||
// reset.
|
||||
timeval time_of_day2;
|
||||
EXPECT_TRUE(gettimeofday(&time_of_day2, nullptr) == 0);
|
||||
ASSERT_TRUE(vmread_time.Read(&time_of_day2));
|
||||
EXPECT_EQ(time_of_day2.tv_sec, vmread_time->tv_sec);
|
||||
EXPECT_EQ(time_of_day2.tv_usec, vmread_time->tv_usec);
|
||||
EXPECT_EQ(vmread_time->tv_sec, time_of_day2.tv_sec);
|
||||
EXPECT_EQ(vmread_time->tv_usec, time_of_day2.tv_usec);
|
||||
}
|
||||
|
||||
TEST(ScopedVMReadTest, MissingMiddleVM) {
|
||||
char* region;
|
||||
vm_size_t page_size = getpagesize();
|
||||
vm_size_t region_size = page_size * 3;
|
||||
ASSERT_EQ(vm_allocate(mach_task_self(),
|
||||
reinterpret_cast<vm_address_t*>(®ion),
|
||||
region_size,
|
||||
VM_FLAGS_ANYWHERE),
|
||||
0);
|
||||
kern_return_t kr = vm_allocate(mach_task_self(),
|
||||
reinterpret_cast<vm_address_t*>(®ion),
|
||||
region_size,
|
||||
VM_FLAGS_ANYWHERE);
|
||||
ASSERT_EQ(kr, KERN_SUCCESS) << MachErrorMessage(kr, "vm_allocate");
|
||||
|
||||
base::mac::ScopedMachVM vm_owner(reinterpret_cast<vm_address_t>(region),
|
||||
region_size);
|
||||
|
||||
|
@ -70,12 +75,12 @@ TEST(ScopedVMReadTest, MissingMiddleVM) {
|
|||
ASSERT_TRUE(vmread_missing_middle.Read(region, region_size));
|
||||
|
||||
// Dealloc middle page.
|
||||
ASSERT_EQ(vm_deallocate(mach_task_self(),
|
||||
reinterpret_cast<vm_address_t>(region + page_size),
|
||||
page_size),
|
||||
0);
|
||||
kr = vm_deallocate(mach_task_self(),
|
||||
reinterpret_cast<vm_address_t>(region + page_size),
|
||||
page_size);
|
||||
ASSERT_EQ(kr, KERN_SUCCESS) << MachErrorMessage(kr, "vm_deallocate");
|
||||
|
||||
ASSERT_FALSE(vmread_missing_middle.Read(region, region_size));
|
||||
EXPECT_FALSE(vmread_missing_middle.Read(region, region_size));
|
||||
ASSERT_TRUE(vmread_missing_middle.Read(region, page_size));
|
||||
}
|
||||
|
||||
|
|
|
@ -23,17 +23,6 @@
|
|||
#include "base/strings/stringprintf.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#ifdef __GLIBCXX__
|
||||
// When C++ exceptions are disabled, libstdc++ from GCC 4.2 defines |try| and
|
||||
// |catch| so as to allow exception-expecting C++ code to build properly when
|
||||
// language support for exceptions is not present. These macros interfere with
|
||||
// the use of |@try| and |@catch| in Objective-C files such as this one.
|
||||
// Undefine these macros here, after everything has been #included, since there
|
||||
// will be no C++ uses and only Objective-C uses from this point on.
|
||||
#undef try
|
||||
#undef catch
|
||||
#endif
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
namespace {
|
||||
|
|
122
thirdparty/sentry-native/external/crashpad/util/synchronization/scoped_spin_guard.h
vendored
Normal file
122
thirdparty/sentry-native/external/crashpad/util/synchronization/scoped_spin_guard.h
vendored
Normal file
|
@ -0,0 +1,122 @@
|
|||
// Copyright 2022 The Crashpad Authors
|
||||
//
|
||||
// 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_UTIL_SYNCHRONIZATION_SCOPED_SPIN_GUARD_H_
|
||||
#define CRASHPAD_UTIL_SYNCHRONIZATION_SCOPED_SPIN_GUARD_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
#include "base/check.h"
|
||||
#include "base/notreached.h"
|
||||
#include "util/misc/clock.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
//! \brief Spinlock state for `ScopedSpinGuard`.
|
||||
struct SpinGuardState final {
|
||||
//! \brief A `ScopedSpinGuard` in an unlocked state.
|
||||
constexpr SpinGuardState() : locked(false) {}
|
||||
|
||||
SpinGuardState(const SpinGuardState&) = delete;
|
||||
SpinGuardState& operator=(const SpinGuardState&) = delete;
|
||||
|
||||
//! \brief `true` if the `ScopedSpinGuard` is locked, `false` otherwise.
|
||||
std::atomic<bool> locked;
|
||||
static_assert(std::atomic<bool>::is_always_lock_free,
|
||||
"std::atomic<bool> may not be signal-safe");
|
||||
static_assert(sizeof(std::atomic<bool>) == sizeof(bool),
|
||||
"std::atomic<bool> adds size to bool");
|
||||
};
|
||||
|
||||
//! \brief A scoped mutual-exclusion guard wrapping a `SpinGuardState` with RAII
|
||||
//! semantics.
|
||||
class ScopedSpinGuard final {
|
||||
//! \brief The duration in nanoseconds between attempts to lock the spinlock.
|
||||
static constexpr uint64_t kSpinGuardSleepTimeNanos = 10;
|
||||
|
||||
public:
|
||||
ScopedSpinGuard(const ScopedSpinGuard&) = delete;
|
||||
ScopedSpinGuard& operator=(const ScopedSpinGuard&) = delete;
|
||||
ScopedSpinGuard(ScopedSpinGuard&& other) noexcept : state_(nullptr) {
|
||||
std::swap(state_, other.state_);
|
||||
}
|
||||
ScopedSpinGuard& operator=(ScopedSpinGuard&& other) {
|
||||
std::swap(state_, other.state_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
//! \brief Spins up to `timeout_nanos` nanoseconds trying to lock `state`.
|
||||
//! \param[in] timeout_nanos The timeout in nanoseconds after which this gives
|
||||
//! up trying to lock the spinlock and returns `std::nullopt`.
|
||||
//! \param[in,out] state The spinlock state to attempt to lock. This method
|
||||
//! holds a pointer to `state`, so `state` must outlive the lifetime of
|
||||
//! this object.
|
||||
//! \return The locked `ScopedSpinGuard` on success, or `std::nullopt` on
|
||||
//! timeout.
|
||||
static std::optional<ScopedSpinGuard> TryCreateScopedSpinGuard(
|
||||
uint64_t timeout_nanos,
|
||||
SpinGuardState& state) {
|
||||
const uint64_t clock_end_time_nanos =
|
||||
ClockMonotonicNanoseconds() + timeout_nanos;
|
||||
while (true) {
|
||||
bool expected_current_value = false;
|
||||
if (state.locked.compare_exchange_weak(expected_current_value,
|
||||
true,
|
||||
std::memory_order_acquire,
|
||||
std::memory_order_relaxed)) {
|
||||
return std::make_optional<ScopedSpinGuard>(state);
|
||||
}
|
||||
if (ClockMonotonicNanoseconds() >= clock_end_time_nanos) {
|
||||
return std::nullopt;
|
||||
}
|
||||
SleepNanoseconds(kSpinGuardSleepTimeNanos);
|
||||
}
|
||||
|
||||
NOTREACHED();
|
||||
}
|
||||
|
||||
~ScopedSpinGuard() {
|
||||
if (state_) {
|
||||
#ifdef NDEBUG
|
||||
state_->locked.store(false, std::memory_order_release);
|
||||
#else
|
||||
bool old = state_->locked.exchange(false, std::memory_order_release);
|
||||
DCHECK(old);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
//! \brief A `ScopedSpinGuard` wrapping a locked `SpinGuardState`.
|
||||
//! \param[in,out] locked_state A locked `SpinGuardState`. This method
|
||||
//! holds a pointer to `state`, so `state` must outlive the lifetime of
|
||||
//! this object.
|
||||
ScopedSpinGuard(SpinGuardState& locked_state) : state_(&locked_state) {
|
||||
DCHECK(locked_state.locked);
|
||||
}
|
||||
|
||||
private:
|
||||
// \brief Optional spinlock state, unlocked when this object goes out of
|
||||
// scope.
|
||||
//
|
||||
// If this is `nullptr`, then this object has been moved from, and the state
|
||||
// is no longer valid. In that case, nothing will be unlocked when this object
|
||||
// is destroyed.
|
||||
SpinGuardState* state_;
|
||||
};
|
||||
|
||||
} // namespace crashpad
|
||||
|
||||
#endif // CRASHPAD_UTIL_SYNCHRONIZATION_SCOPED_SPIN_GUARD_H_
|
109
thirdparty/sentry-native/external/crashpad/util/synchronization/scoped_spin_guard_test.cc
vendored
Normal file
109
thirdparty/sentry-native/external/crashpad/util/synchronization/scoped_spin_guard_test.cc
vendored
Normal file
|
@ -0,0 +1,109 @@
|
|||
// Copyright 2022 The Crashpad Authors
|
||||
//
|
||||
// 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.
|
||||
|
||||
#include "util/synchronization/scoped_spin_guard.h"
|
||||
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "util/misc/clock.h"
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
namespace {
|
||||
|
||||
TEST(ScopedSpinGuard, TryCreateScopedSpinGuardShouldLockStateWhileInScope) {
|
||||
SpinGuardState s;
|
||||
EXPECT_FALSE(s.locked);
|
||||
{
|
||||
std::optional<ScopedSpinGuard> guard =
|
||||
ScopedSpinGuard::TryCreateScopedSpinGuard(/*timeout_nanos=*/0, s);
|
||||
EXPECT_NE(std::nullopt, guard);
|
||||
EXPECT_TRUE(s.locked);
|
||||
}
|
||||
EXPECT_FALSE(s.locked);
|
||||
}
|
||||
|
||||
TEST(
|
||||
ScopedSpinGuard,
|
||||
TryCreateScopedSpinGuardWithZeroTimeoutShouldFailImmediatelyIfStateLocked) {
|
||||
SpinGuardState s;
|
||||
s.locked = true;
|
||||
std::optional<ScopedSpinGuard> guard =
|
||||
ScopedSpinGuard::TryCreateScopedSpinGuard(/*timeout_nanos=*/0, s);
|
||||
EXPECT_EQ(std::nullopt, guard);
|
||||
EXPECT_TRUE(s.locked);
|
||||
}
|
||||
|
||||
TEST(
|
||||
ScopedSpinGuard,
|
||||
TryCreateScopedSpinGuardWithNonZeroTimeoutShouldSucceedIfStateUnlockedDuringTimeout) {
|
||||
SpinGuardState s;
|
||||
s.locked = true;
|
||||
std::thread unlock_thread([&s] {
|
||||
constexpr uint64_t kUnlockThreadSleepTimeNanos = 10000; // 10 us
|
||||
EXPECT_TRUE(s.locked);
|
||||
SleepNanoseconds(kUnlockThreadSleepTimeNanos);
|
||||
s.locked = false;
|
||||
});
|
||||
constexpr uint64_t kLockThreadTimeoutNanos = 180000000000ULL; // 3 minutes
|
||||
std::optional<ScopedSpinGuard> guard =
|
||||
ScopedSpinGuard::TryCreateScopedSpinGuard(kLockThreadTimeoutNanos, s);
|
||||
EXPECT_NE(std::nullopt, guard);
|
||||
EXPECT_TRUE(s.locked);
|
||||
unlock_thread.join();
|
||||
}
|
||||
|
||||
TEST(ScopedSpinGuard, SwapShouldMaintainSpinLock) {
|
||||
SpinGuardState s;
|
||||
std::optional<ScopedSpinGuard> outer_guard;
|
||||
EXPECT_EQ(std::nullopt, outer_guard);
|
||||
{
|
||||
std::optional<ScopedSpinGuard> inner_guard =
|
||||
ScopedSpinGuard::TryCreateScopedSpinGuard(/*timeout_nanos=*/0, s);
|
||||
EXPECT_NE(std::nullopt, inner_guard);
|
||||
EXPECT_TRUE(s.locked);
|
||||
// If the move-assignment operator for `ScopedSpinGuard` is implemented
|
||||
// incorrectly (e.g., the `= default` implementation), `inner_guard`
|
||||
// will incorrectly think it still "owns" the spinlock after the swap,
|
||||
// and when it falls out of scope, it will release the lock prematurely
|
||||
// when it destructs.
|
||||
//
|
||||
// Confirm that the spinlock stays locked even after the swap.
|
||||
std::swap(outer_guard, inner_guard);
|
||||
EXPECT_TRUE(s.locked);
|
||||
EXPECT_EQ(std::nullopt, inner_guard);
|
||||
}
|
||||
EXPECT_NE(std::nullopt, outer_guard);
|
||||
EXPECT_TRUE(s.locked);
|
||||
}
|
||||
|
||||
TEST(ScopedSpinGuard, MoveAssignmentShouldMaintainSpinLock) {
|
||||
SpinGuardState s;
|
||||
std::optional<ScopedSpinGuard> outer_guard;
|
||||
EXPECT_EQ(std::nullopt, outer_guard);
|
||||
{
|
||||
outer_guard =
|
||||
ScopedSpinGuard::TryCreateScopedSpinGuard(/*timeout_nanos=*/0, s);
|
||||
EXPECT_NE(std::nullopt, outer_guard);
|
||||
EXPECT_TRUE(s.locked);
|
||||
}
|
||||
EXPECT_NE(std::nullopt, outer_guard);
|
||||
EXPECT_TRUE(s.locked);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace test
|
||||
} // namespace crashpad
|
|
@ -16,6 +16,12 @@
|
|||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <unwindstack/DwarfError.h>
|
||||
#include <unwindstack/DwarfLocation.h>
|
||||
#include <unwindstack/DwarfMemory.h>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue