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:
Marek Roszko 2023-02-15 21:08:10 -05:00 committed by Mark Roszko
parent f13fda9be5
commit 89d22e5f94
138 changed files with 7622 additions and 910 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View 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

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

View 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");
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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'). */

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View 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

View File

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

View 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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -452,8 +452,9 @@ struct ALIGNAS(4) PACKED MinidumpCrashpadInfo {
report_id(),
client_id(),
simple_annotations(),
module_list() {
}
module_list(),
reserved(),
address_mask() {}
//! \brief The structures 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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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(),
&region_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

View File

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

View File

@ -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*>(&region),
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

View File

@ -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_);
&region_start_,
&region_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

View File

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

View File

@ -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*>(&region),
region_size,
VM_FLAGS_ANYWHERE),
0);
kern_return_t kr = vm_allocate(mach_task_self(),
reinterpret_cast<vm_address_t*>(&region),
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));
}

View File

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

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

View 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

View File

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