diff --git a/.clang-format b/.clang-format index 56b761bb2..ba4b564d9 100644 --- a/.clang-format +++ b/.clang-format @@ -4,4 +4,4 @@ IndentPPDirectives: AfterHash ColumnLimit: 80 AlwaysBreakAfterDefinitionReturnType: All PointerAlignment: Right -ForEachMacros: ['SENTRY_WITH_SCOPE', 'SENTRY_WITH_SCOPE_MUT', 'SENTRY_WITH_SCOPE_MUT_NO_FLUSH', 'SENTRY_WITH_OPTIONS'] +ForEachMacros: ['SENTRY_WITH_SCOPE', 'SENTRY_WITH_SCOPE_MUT', 'SENTRY_WITH_SCOPE_MUT_NO_FLUSH', 'SENTRY_WITH_OPTIONS', 'SENTRY_WITH_OPTIONS_MUT'] diff --git a/.craft.yml b/.craft.yml index c9f27fa1d..6908dce36 100644 --- a/.craft.yml +++ b/.craft.yml @@ -1,29 +1,18 @@ -minVersion: "0.15.0" -github: - owner: getsentry - repo: sentry-native +minVersion: 0.23.1 changelogPolicy: auto - -statusProvider: - name: github -artifactProvider: - name: github - targets: - name: github - name: registry - type: sdk - config: - canonical: "github:getsentry/sentry-native" + sdks: + github:getsentry/sentry-native: - name: gcs bucket: sentry-sdk-assets paths: - path: /sentry-native/{{version}}/ metadata: - cacheControl: "public, max-age=2592000" + cacheControl: public, max-age=2592000 - path: /sentry-native/latest/ metadata: - cacheControl: "public, max-age=600" - + cacheControl: public, max-age=600 requireNames: - /^sentry-native.zip$/ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d9bb1abf2..8de88c126 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -120,7 +120,7 @@ jobs: if: ${{ runner.os == 'macOS' }} uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: '11.3.1' + xcode-version: '13.0.0' - name: Building (macOS) if: ${{ runner.os == 'macOS' }} diff --git a/CHANGELOG.md b/CHANGELOG.md index f0b50a7e4..eba5d6f3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,98 @@ # Changelog +## 0.4.12 + +**Features**: + +- Make the shutdown timeout configurable via `sentry_options_set_shutdown_timeout`. + +**Fixes**: + +- The crashpad backend compiles with mingw again. +- Build System improvements. + +**Thank you**: + +Features, fixes and improvements in this release have been contributed by: + +- [@irov](https://github.com/irov) +- [@past-due](https://github.com/past-due) +- [@andrei-mu](https://github.com/andrei-mu) +- [@rpadaki](https://github.com/rpadaki) + +## 0.4.11 + +**Fixes**: + +- The crashpad backend now respects the `max_breadcrumbs` setting. +- Hanging HTTP requests will now be canceled on shutdown in the WinHTTP transport. +- The Modulefinder and Android unwinder now use safer memory access. +- Possible races and deadlocks have been fixed in `init`/`close`, and in API related to sessions. + +**Thank you**: + +Features, fixes and improvements in this release have been contributed by: + +- [@smibe](https://github.com/smibe) + +## 0.4.10 + +**Fixes**: + +- Fix a potential deadlock in macOS modulefinder. +- Lower Stack usage, to lower change of stack overflows. +- Avoid a double-free when parsing an invalid DSN. +- Improvements to Unity Builds and 32-bit Builds. +- Fix infinite recursion in signal handler by correctly cleaning up on shutdown. + +**Internal**: + +- Update Crashpad and Breakpad submodules to 2021-06-14. + +**Thank you**: + +Features, fixes and improvements in this release have been contributed by: + +- [@janisozaur](https://github.com/janisozaur) +- [@bschatt](https://github.com/bschatt) +- [@GenuineAster](https://github.com/GenuineAster) + +## 0.4.9 + +**Features**: + +- Rewrote the Linux modulefinder which should now work correctly when encountering gaps in the memory mapping of loaded libraries, and supports libraries loaded from a file offset, such as libraries loaded directly from `.apk` files on Android. +- Invoke the `before_send` hook at time of a hard crash when using the Windows or Linux Crashpad backend. +- Added the following new convenience functions: + - `sentry_value_new_exception` + - `sentry_value_new_thread` + - `sentry_value_new_stacktrace` + - `sentry_event_add_exception` + - `sentry_event_add_thread` + - The `sentry_event_value_add_stacktrace` is deprecated. +- Renamed `sentry_shutdown` to `sentry_close`, though the old function is still available. +- Updated Qt integration to Qt 6. + +**Fixes**: + +- Optimized and fixed bugs in the JSON parser/serializer. +- Build fixes for PPC and universal macOS. +- Fixes to build using musl libc. +- Correctness fixes around printf and strftime usage. +- Allow building and running on older macOS versions. + +**Internal**: + +- Update Crashpad and Breakpad submodules to 2021-04-12 + +**Thank you**: + +Features, fixes and improvements in this release have been contributed by: + +- [@mastertheknife](https://github.com/mastertheknife) +- [@torarnv](https://github.com/torarnv) +- [@encounter](https://github.com/encounter) + ## 0.4.8 **Features**: diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f47133df..ac7311160 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,7 +39,7 @@ if(NOT CMAKE_C_STANDARD) endif() if(NOT CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 11) + set(CMAKE_CXX_STANDARD 14) endif() include(GNUInstallDirs) @@ -79,6 +79,7 @@ if(LINUX) if(SENTRY_BUILD_FORCE32) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m32 -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m32 -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE") + set(CMAKE_ASM_FLAGS "${CMAKE_ASM_FLAGS} -m32 -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE") set_property(GLOBAL PROPERTY FIND_LIBRARY_USE_LIB64_PATHS OFF) endif() endif() @@ -258,15 +259,17 @@ target_compile_definitions(sentry PRIVATE SIZEOF_LONG=${CMAKE_SIZEOF_LONG}) if(SENTRY_TRANSPORT_CURL) find_package(CURL REQUIRED) - target_include_directories(sentry PRIVATE ${CURL_INCLUDE_DIR}) - # The exported sentry target must not contain any path of the build machine, therefore use generator expressions - # FIXME: cmake 3.12 introduced the target CURL::libcurl - string(REPLACE ";" "$" GENEX_CURL_LIBRARIES "${CURL_LIBRARIES}") - string(REPLACE ";" "$" GENEX_CURL_COMPILE_DEFINITIONS "${CURL_COMPILE_DEFINITIONS}") - target_link_libraries(sentry PRIVATE $) - target_compile_definitions(sentry PRIVATE $) -elseif(SENTRY_TRANSPORT_WINHTTP) - target_link_libraries(sentry PRIVATE winhttp) + if(TARGET CURL::libcurl) # Only available in cmake 3.12+ + target_link_libraries(sentry PRIVATE CURL::libcurl) + else() + # Needed for cmake < 3.12 support (cmake 3.12 introduced the target CURL::libcurl) + target_include_directories(sentry PRIVATE ${CURL_INCLUDE_DIR}) + # The exported sentry target must not contain any path of the build machine, therefore use generator expressions + string(REPLACE ";" "$" GENEX_CURL_LIBRARIES "${CURL_LIBRARIES}") + string(REPLACE ";" "$" GENEX_CURL_COMPILE_DEFINITIONS "${CURL_COMPILE_DEFINITIONS}") + target_link_libraries(sentry PRIVATE $) + target_compile_definitions(sentry PRIVATE $) + endif() endif() set_property(TARGET sentry PROPERTY C_VISIBILITY_PRESET hidden) @@ -345,7 +348,6 @@ if(WIN32) target_compile_definitions(sentry PRIVATE "_WIN32_WINNT=0x0501") endif() - target_link_libraries(sentry PRIVATE shlwapi) # crashpad does not support Windows XP toolset if(MSVC AND CMAKE_GENERATOR_TOOLSET MATCHES "_xp$" AND SENTRY_BACKEND_CRASHPAD) message(FATAL_ERROR "MSVC XP toolset does not support Crashpad") @@ -358,7 +360,11 @@ if(ANDROID) elseif(LINUX) set(_SENTRY_PLATFORM_LIBS "dl" "rt") elseif(WIN32) - set(_SENTRY_PLATFORM_LIBS "dbghelp" "version") + set(_SENTRY_PLATFORM_LIBS "dbghelp" "shlwapi" "version") +endif() + +if(SENTRY_TRANSPORT_WINHTTP) + list(APPEND _SENTRY_PLATFORM_LIBS "winhttp") endif() # handle platform threads library @@ -413,7 +419,7 @@ if(SENTRY_BACKEND_CRASHPAD) endif() add_subdirectory(external/crashpad crashpad_build) - # set static runime if enabled + # set static runtime if enabled if(SENTRY_BUILD_RUNTIMESTATIC AND MSVC) set_property(TARGET crashpad_client PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") set_property(TARGET crashpad_compat PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") @@ -469,8 +475,17 @@ endif() option(SENTRY_INTEGRATION_QT "Build Qt integration") if(SENTRY_INTEGRATION_QT) - find_package(Qt5 COMPONENTS Core REQUIRED) - target_link_libraries(sentry PRIVATE Qt5::Core) + if(QT_DEFAULT_MAJOR_VERSION) + # Let user choose major version + set(Qt_VERSION_MAJOR ${QT_DEFAULT_MAJOR_VERSION}) + else() + # Find best match, prioritizing Qt 6 if available + find_package(Qt NAMES Qt6 Qt5 COMPONENTS Core REQUIRED) + endif() + find_package(Qt${Qt_VERSION_MAJOR} COMPONENTS Core REQUIRED) + message(STATUS "Found Qt: ${Qt${Qt_VERSION_MAJOR}_DIR} " + "(found version \"${Qt${Qt_VERSION_MAJOR}_VERSION}\")") + target_link_libraries(sentry PRIVATE Qt${Qt_VERSION_MAJOR}::Core) endif() include(CMakePackageConfigHelpers) diff --git a/Makefile b/Makefile index 7ca1c06a1..cf14522f9 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ all: test update-test-discovery: - @perl -ne 'print if s/SENTRY_TEST\(([^)]+)\)/XX(\1)/' tests/unit/*.c | sort | uniq > tests/unit/tests.inc + @perl -ne 'print if s/SENTRY_TEST\(([^)]+)\)/XX(\1)/' tests/unit/*.c | sort | grep -v define | uniq > tests/unit/tests.inc .PHONY: update-test-discovery build/Makefile: CMakeLists.txt @@ -55,8 +55,7 @@ setup-venv: .venv/bin/python .venv/bin/python: Makefile tests/requirements.txt @rm -rf .venv - @which virtualenv || sudo pip install virtualenv - virtualenv -p python3 .venv + python3 -m venv .venv .venv/bin/pip install --upgrade --requirement tests/requirements.txt format: setup-venv diff --git a/README.md b/README.md index 63a225369..f3329adfb 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,6 @@ The SDK supports different features on the target platform: have the `curl` library available. On other platforms, library users need to implement their own transport, based on the `function transport` API. - **Crashpad Backend** is currently only supported on Linux, Windows and macOS. -- **Breakpad Backend** is currently only supported on Linux and Windows. ## Building and Installation @@ -195,6 +194,8 @@ using `cmake -D BUILD_SHARED_LIBS=OFF ..`. `OFF` will build `sentry` as a static library instead. If sentry is used as a subdirectory of another project, the value `BUILD_SHARED_LIBS` will be inherited by default. + When using `sentry` as a static library, make sure to `#define SENTRY_BUILD_STATIC 1` before including the sentry header. + - `SENTRY_PIC` (Default: ON): By default, `sentry` is built as a position independent library. @@ -299,7 +300,7 @@ sentry_init(options); // your application code … -sentry_shutdown(); +sentry_close(); ``` Other important configuration options include: @@ -311,7 +312,7 @@ Other important configuration options include: ## Known Limitations -- The crashpad backend currently has no support for notifying the crashing +- The crashpad backend on macOS currently has no support for notifying the crashing process, and can thus not properly terminate sessions or call the registered `before_send` hook. It will also lose any events that have been queued for sending at time of crash. diff --git a/examples/example.c b/examples/example.c index 392675a58..1c1275975 100644 --- a/examples/example.c +++ b/examples/example.c @@ -9,11 +9,16 @@ #include #include #include +#ifdef NDEBUG +# undef NDEBUG +#endif +#include #ifdef SENTRY_PLATFORM_WINDOWS # include # define sleep_s(SECONDS) Sleep((SECONDS)*1000) #else +# include # include # define sleep_s(SECONDS) sleep(SECONDS) #endif @@ -149,6 +154,10 @@ main(int argc, char **argv) } } + if (has_arg(argc, argv, "reinstall")) { + sentry_reinstall_backend(); + } + if (has_arg(argc, argv, "sleep")) { sleep_s(10); } @@ -156,6 +165,20 @@ main(int argc, char **argv) if (has_arg(argc, argv, "crash")) { trigger_crash(); } + if (has_arg(argc, argv, "assert")) { + assert(0); + } + if (has_arg(argc, argv, "abort")) { + abort(); + } +#ifdef SENTRY_PLATFORM_UNIX + if (has_arg(argc, argv, "raise")) { + raise(SIGSEGV); + } + if (has_arg(argc, argv, "kill")) { + kill(getpid(), SIGSEGV); + } +#endif if (has_arg(argc, argv, "capture-event")) { sentry_value_t event = sentry_value_new_message_event( @@ -166,27 +189,21 @@ main(int argc, char **argv) sentry_capture_event(event); } if (has_arg(argc, argv, "capture-exception")) { - // TODO: Create a convenience API to create a new exception object, - // and to attach a stacktrace to the exception. - // See also https://github.com/getsentry/sentry-native/issues/235 + sentry_value_t exc = sentry_value_new_exception( + "ParseIntError", "invalid digit found in string"); + if (has_arg(argc, argv, "add-stacktrace")) { + sentry_value_t stacktrace = sentry_value_new_stacktrace(NULL, 0); + sentry_value_set_by_key(exc, "stacktrace", stacktrace); + } sentry_value_t event = sentry_value_new_event(); - sentry_value_t exception = sentry_value_new_object(); - // for example: - sentry_value_set_by_key( - exception, "type", sentry_value_new_string("ParseIntError")); - sentry_value_set_by_key(exception, "value", - sentry_value_new_string("invalid digit found in string")); - sentry_value_t exceptions = sentry_value_new_list(); - sentry_value_append(exceptions, exception); - sentry_value_t values = sentry_value_new_object(); - sentry_value_set_by_key(values, "values", exceptions); - sentry_value_set_by_key(event, "exception", values); + sentry_event_add_exception(event, exc); sentry_capture_event(event); } // make sure everything flushes - sentry_shutdown(); + sentry_close(); + if (has_arg(argc, argv, "sleep-after-shutdown")) { sleep_s(1); } diff --git a/external/breakpad b/external/breakpad index 77bf06f4d..6690e19d9 160000 --- a/external/breakpad +++ b/external/breakpad @@ -1 +1 @@ -Subproject commit 77bf06f4dee17f3b9021875a0647752c62452aaf +Subproject commit 6690e19d9d42874a782e154f2fc7fd55c7a9dc91 diff --git a/external/crashpad b/external/crashpad index d11e19648..6415106e7 160000 --- a/external/crashpad +++ b/external/crashpad @@ -1 +1 @@ -Subproject commit d11e196480ef5ab4a521e7996fe855626dee120c +Subproject commit 6415106e7997c6c1886f17d0734ac6d749fc1a99 diff --git a/external/libunwindstack-ndk b/external/libunwindstack-ndk index abcfef9e7..a4c27d48d 160000 --- a/external/libunwindstack-ndk +++ b/external/libunwindstack-ndk @@ -1 +1 @@ -Subproject commit abcfef9e77ec6c8b0fe179591e354b8509727686 +Subproject commit a4c27d48deff95fe922fe9733ef5c1339bdbf4fb diff --git a/external/third_party/lss b/external/third_party/lss index 29f7c7e01..e1e7b0ad8 160000 --- a/external/third_party/lss +++ b/external/third_party/lss @@ -1 +1 @@ -Subproject commit 29f7c7e018f4ce706a709f0b0afbf8bacf869480 +Subproject commit e1e7b0ad8ee99a875b272c8e33e308472e897660 diff --git a/include/sentry.h b/include/sentry.h index eff88e790..163bce965 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -24,7 +24,7 @@ extern "C" { /* SDK Version */ #define SENTRY_SDK_NAME "sentry.native" -#define SENTRY_SDK_VERSION "0.4.8" +#define SENTRY_SDK_VERSION "0.4.12" #define SENTRY_SDK_USER_AGENT SENTRY_SDK_NAME "/" SENTRY_SDK_VERSION /* common platform detection */ @@ -338,12 +338,18 @@ typedef enum sentry_level_e { } sentry_level_t; /** - * Creates a new empty event value. + * Creates a new empty Event value. + * + * See https://docs.sentry.io/platforms/native/enriching-events/ for how to + * further work with events, and https://develop.sentry.dev/sdk/event-payloads/ + * for a detailed overview of the possible properties of an Event. */ SENTRY_API sentry_value_t sentry_value_new_event(void); /** - * Creates a new message event value. + * Creates a new Message Event value. + * + * See https://develop.sentry.dev/sdk/event-payloads/message/ * * `logger` can be NULL to omit the logger value. */ @@ -351,13 +357,73 @@ SENTRY_API sentry_value_t sentry_value_new_message_event( sentry_level_t level, const char *logger, const char *text); /** - * Creates a new breadcrumb with a specific type and message. + * Creates a new Breadcrumb with a specific type and message. + * + * See https://develop.sentry.dev/sdk/event-payloads/breadcrumbs/ * * Either parameter can be NULL in which case no such attributes is created. */ SENTRY_API sentry_value_t sentry_value_new_breadcrumb( const char *type, const char *message); +/** + * Creates a new Exception value. + * + * This is intended for capturing language-level exception, such as from a + * try-catch block. `type` and `value` here refer to the exception class and + * a possible description. + * + * See https://develop.sentry.dev/sdk/event-payloads/exception/ + * + * The returned value needs to be attached to an event via + * `sentry_event_add_exception`. + */ +SENTRY_EXPERIMENTAL_API sentry_value_t sentry_value_new_exception( + const char *type, const char *value); + +/** + * Creates a new Thread value. + * + * See https://develop.sentry.dev/sdk/event-payloads/threads/ + * + * The returned value needs to be attached to an event via + * `sentry_event_add_thread`. + * + * `name` can be NULL. + */ +SENTRY_EXPERIMENTAL_API sentry_value_t sentry_value_new_thread( + uint64_t id, const char *name); + +/** + * Creates a new Stack Trace conforming to the Stack Trace Interface. + * + * See https://develop.sentry.dev/sdk/event-payloads/stacktrace/ + * + * The returned object needs to be attached to either an exception + * event, or a thread object. + * + * If `ips` is NULL the current stack trace is captured, otherwise `len` + * stack trace instruction pointers are attached to the event. + */ +SENTRY_EXPERIMENTAL_API sentry_value_t sentry_value_new_stacktrace( + void **ips, size_t len); + +/** + * Adds an Exception to an Event value. + * + * This takes ownership of the `exception`. + */ +SENTRY_EXPERIMENTAL_API void sentry_event_add_exception( + sentry_value_t event, sentry_value_t exception); + +/** + * Adds a Thread to an Event value. + * + * This takes ownership of the `thread`. + */ +SENTRY_EXPERIMENTAL_API void sentry_event_add_thread( + sentry_value_t event, sentry_value_t thread); + /* -- Experimental APIs -- */ /** @@ -371,10 +437,15 @@ SENTRY_EXPERIMENTAL_API char *sentry_value_to_msgpack( sentry_value_t value, size_t *size_out); /** - * Adds a stacktrace to an event. + * Adds a stack trace to an event. * - * If `ips` is NULL the current stacktrace is captured, otherwise `len` - * stacktrace instruction pointers are attached to the event. + * The stack trace is added as part of a new thread object. + * This function is **deprecated** in favor of using + * `sentry_value_new_stacktrace` in combination with `sentry_value_new_thread` + * and `sentry_event_add_thread`. + * + * If `ips` is NULL the current stack trace is captured, otherwise `len` + * stack trace instruction pointers are attached to the event. */ SENTRY_EXPERIMENTAL_API void sentry_event_value_add_stacktrace( sentry_value_t event, void **ips, size_t len); @@ -398,9 +469,12 @@ typedef struct sentry_ucontext_s { * * If the address is given in `addr` the stack is unwound form there. * Otherwise (NULL is passed) the current instruction pointer is used as - * start address. The stacktrace is written to `stacktrace_out` with upt o - * `max_len` frames being written. The actual number of unwound stackframes - * is returned. + * start address. + * Unwinding with a given `addr` is not supported on all platforms. + * + * The stack trace in the form of instruction-addresses, is written to the + * caller allocated `stacktrace_out`, with up to `max_len` frames being written. + * The actual number of unwound stackframes is returned. */ SENTRY_EXPERIMENTAL_API size_t sentry_unwind_stack( void *addr, void **stacktrace_out, size_t max_len); @@ -408,8 +482,12 @@ SENTRY_EXPERIMENTAL_API size_t sentry_unwind_stack( /** * Unwinds the stack from the given context. * - * The stacktrace is written to `stacktrace_out` with upt o `max_len` frames - * being written. The actual number of unwound stackframes is returned. + * The caller is responsible to construct an appropriate `sentry_ucontext_t`. + * Unwinding from a user context is not supported on all platforms. + * + * The stack trace in the form of instruction-addresses, is written to the + * caller allocated `stacktrace_out`, with up to `max_len` frames being written. + * The actual number of unwound stackframes is returned. */ SENTRY_EXPERIMENTAL_API size_t sentry_unwind_stack_from_ucontext( const sentry_ucontext_t *uctx, void **stacktrace_out, size_t max_len); @@ -530,7 +608,7 @@ typedef struct sentry_options_s sentry_options_t; * In case of `false`, sentry will log an error, but continue with freeing the * transport. * * `free_func`: Frees the transports `state`. This hook might be called even - * though `shudown_func` returned `false` previously. + * though `shutdown_func` returned `false` previously. * * The transport interface might be extended in the future with hooks to flush * its internal queue without shutting down, and to dump its internal queue to @@ -660,8 +738,12 @@ SENTRY_API void sentry_options_set_transport( * same event. In case the event should be discarded, the callback needs to * call `sentry_value_decref` on the provided event, and return a * `sentry_value_new_null()` instead. + * * This function may be invoked inside of a signal handler and must be safe for * that purpose, see https://man7.org/linux/man-pages/man7/signal-safety.7.html. + * On Windows, it may be called from inside of a `UnhandledExceptionFilter`, see + * the documentation on SEH (structured exception handling) for more information + * https://docs.microsoft.com/en-us/windows/win32/debug/structured-exception-handling */ typedef sentry_value_t (*sentry_event_function_t)( sentry_value_t event, void *hint, void *closure); @@ -827,7 +909,7 @@ SENTRY_API void sentry_options_set_logger( * Automatic session tracking is enabled by default and is equivalent to calling * `sentry_start_session` after startup. * There can only be one running session, and the current session will always be - * closed implicitly by `sentry_shutdown`, when starting a new session with + * closed implicitly by `sentry_close`, when starting a new session with * `sentry_start_session`, or manually by calling `sentry_end_session`. */ SENTRY_API void sentry_options_set_auto_session_tracking( @@ -962,6 +1044,19 @@ SENTRY_API void sentry_options_set_database_pathw( SENTRY_API void sentry_options_set_system_crash_reporter_enabled( sentry_options_t *opts, int enabled); +/** + * Sets the maximum time (in milliseconds) to wait for the asynchronous tasks to + * end on shutdown, before attempting a forced termination. + */ +SENTRY_API void sentry_options_set_shutdown_timeout( + sentry_options_t *opts, uint64_t shutdown_timeout); + +/** + * Gets the maximum time (in milliseconds) to wait for the asynchronous tasks to + * end on shutdown, before attempting a forced termination. + */ +SENTRY_API uint64_t sentry_options_get_shutdown_timeout(sentry_options_t *opts); + /* -- Global APIs -- */ /** @@ -980,6 +1075,15 @@ SENTRY_API int sentry_init(sentry_options_t *options); * * Returns 0 on success. */ +SENTRY_API int sentry_close(void); + +/** + * Shuts down the sentry client and forces transports to flush out. + * + * This is a **deprecated** alias for `sentry_close`. + * + * Returns 0 on success. + */ SENTRY_API int sentry_shutdown(void); /** diff --git a/src/backends/sentry_backend_crashpad.cpp b/src/backends/sentry_backend_crashpad.cpp index 6ec8b3ab6..f6f1f8418 100644 --- a/src/backends/sentry_backend_crashpad.cpp +++ b/src/backends/sentry_backend_crashpad.cpp @@ -60,6 +60,27 @@ extern "C" { #ifdef SENTRY_PLATFORM_LINUX # define SIGNAL_STACK_SIZE 65536 static stack_t g_signal_stack; + +# include "util/posix/signals.h" + +// This list was taken from crashpad's util/posix/signals.cc file +// and is used to know which signals we need to reset to default +// when shutting down the backend +constexpr int g_CrashSignals[] = { + SIGABRT, + SIGBUS, + SIGFPE, + SIGILL, + SIGQUIT, + SIGSEGV, + SIGSYS, + SIGTRAP, +# if defined(SIGEMT) + SIGEMT, +# endif // defined(SIGEMT) + SIGXCPU, + SIGXFSZ, +}; #endif typedef struct { @@ -81,7 +102,8 @@ sentry__crashpad_backend_user_consent_changed(sentry_backend_t *backend) } static void -sentry__crashpad_backend_flush_scope(sentry_backend_t *backend) +sentry__crashpad_backend_flush_scope( + sentry_backend_t *backend, const sentry_options_t *options) { const crashpad_state_t *data = (crashpad_state_t *)backend->data; if (!data->event_path) { @@ -94,7 +116,7 @@ sentry__crashpad_backend_flush_scope(sentry_backend_t *backend) sentry_value_t event = sentry_value_new_object(); SENTRY_WITH_SCOPE (scope) { // we want the scope without any modules or breadcrumbs - sentry__scope_apply_to_event(scope, event, SENTRY_SCOPE_NONE); + sentry__scope_apply_to_event(scope, options, event, SENTRY_SCOPE_NONE); } size_t mpack_size; @@ -130,6 +152,14 @@ sentry__crashpad_handler(int UNUSED(signum), siginfo_t *UNUSED(info), SENTRY_WITH_OPTIONS (options) { sentry__write_crash_marker(options); + sentry_value_t event = sentry_value_new_event(); + if (options->before_send_func) { + SENTRY_TRACE("invoking `before_send` hook"); + event = options->before_send_func( + event, NULL, options->before_send_data); + } + sentry_value_decref(event); + sentry__record_errors_on_current_session(1); sentry_session_t *session = sentry__end_current_session_with_status( SENTRY_SESSION_STATUS_CRASHED); @@ -211,8 +241,17 @@ sentry__crashpad_backend_startup( sentry_path_t *current_exe = sentry__path_current_exe(); if (current_exe && options->relaunch_argv) { annotations["__td-crashed-pid"] = std::to_string(td__getpid()); - annotations["__td-relaunch-path"] = std::string(current_exe->path); annotations["__td-relaunch-argv"] = std::string(options->relaunch_argv); +#ifdef SENTRY_PLATFORM_WINDOWS + std::wstring wstrPath(current_exe->path); + std::string strPath; + std::transform(wstrPath.begin(), wstrPath.end(), + std::back_inserter(strPath), [](wchar_t c) { return (char)c; }); + + annotations["__td-relaunch-path"] = strPath; +#else + annotations["__td-relaunch-path"] = std::string(current_exe->path); +#endif sentry__path_free(current_exe); } @@ -296,6 +335,15 @@ sentry__crashpad_backend_startup( static void sentry__crashpad_backend_shutdown(sentry_backend_t *backend) { +#ifdef SENTRY_PLATFORM_LINUX + // restore signal handlers to their default state + for (const auto signal : g_CrashSignals) { + if (crashpad::Signals::IsCrashSignal(signal)) { + crashpad::Signals::InstallDefaultHandler(signal); + } + } +#endif + crashpad_state_t *data = (crashpad_state_t *)backend->data; delete data->db; data->db = nullptr; @@ -309,16 +357,20 @@ sentry__crashpad_backend_shutdown(sentry_backend_t *backend) } static void -sentry__crashpad_backend_add_breadcrumb( - sentry_backend_t *backend, sentry_value_t breadcrumb) +sentry__crashpad_backend_add_breadcrumb(sentry_backend_t *backend, + sentry_value_t breadcrumb, const sentry_options_t *options) { crashpad_state_t *data = (crashpad_state_t *)backend->data; - bool first_breadcrumb = data->num_breadcrumbs % SENTRY_BREADCRUMBS_MAX == 0; + size_t max_breadcrumbs = options->max_breadcrumbs; + if (!max_breadcrumbs) { + return; + } + + bool first_breadcrumb = data->num_breadcrumbs % max_breadcrumbs == 0; const sentry_path_t *breadcrumb_file - = data->num_breadcrumbs % (SENTRY_BREADCRUMBS_MAX * 2) - < SENTRY_BREADCRUMBS_MAX + = data->num_breadcrumbs % (max_breadcrumbs * 2) < max_breadcrumbs ? data->breadcrumb1_path : data->breadcrumb2_path; data->num_breadcrumbs++; diff --git a/src/backends/sentry_backend_inproc.c b/src/backends/sentry_backend_inproc.c index a0b84fdd9..c15493dab 100644 --- a/src/backends/sentry_backend_inproc.c +++ b/src/backends/sentry_backend_inproc.c @@ -111,6 +111,7 @@ shutdown_inproc_backend(sentry_backend_t *UNUSED(backend)) sigaltstack(&g_signal_stack, 0); sentry_free(g_signal_stack.ss_sp); g_signal_stack.ss_sp = NULL; + reset_signal_handlers(); } #elif defined SENTRY_PLATFORM_WINDOWS @@ -177,13 +178,9 @@ make_signal_event( sentry_value_set_by_key( event, "level", sentry__value_new_level(SENTRY_LEVEL_FATAL)); - sentry_value_t exc = sentry_value_new_object(); - sentry_value_set_by_key(exc, "type", - sentry_value_new_string( - sig_slot ? sig_slot->signame : "UNKNOWN_SIGNAL")); - sentry_value_set_by_key(exc, "value", - sentry_value_new_string( - sig_slot ? sig_slot->sigdesc : "UnknownSignal")); + sentry_value_t exc = sentry_value_new_exception( + sig_slot ? sig_slot->signame : "UNKNOWN_SIGNAL", + sig_slot ? sig_slot->sigdesc : "UnknownSignal"); sentry_value_t mechanism = sentry_value_new_object(); sentry_value_set_by_key(exc, "mechanism", mechanism); @@ -217,25 +214,12 @@ make_signal_event( } SENTRY_TRACEF("captured backtrace with %lu frames", frame_count); - sentry_value_t frames = sentry__value_new_list_with_size(frame_count); - for (size_t i = 0; i < frame_count; i++) { - sentry_value_t frame = sentry_value_new_object(); - sentry_value_set_by_key(frame, "instruction_addr", - sentry__value_new_addr( - (uint64_t)(size_t)backtrace[frame_count - i - 1])); - sentry_value_append(frames, frame); - } - - sentry_value_t stacktrace = sentry_value_new_object(); - sentry_value_set_by_key(stacktrace, "frames", frames); + sentry_value_t stacktrace + = sentry_value_new_stacktrace(&backtrace[0], frame_count); sentry_value_set_by_key(exc, "stacktrace", stacktrace); - sentry_value_t exceptions = sentry_value_new_object(); - sentry_value_t values = sentry_value_new_list(); - sentry_value_set_by_key(exceptions, "values", values); - sentry_value_append(values, exc); - sentry_value_set_by_key(event, "exception", exceptions); + sentry_event_add_exception(event, exc); return event; } diff --git a/src/modulefinder/sentry_modulefinder_apple.c b/src/modulefinder/sentry_modulefinder_apple.c index 96dc1a70d..93957e0cb 100644 --- a/src/modulefinder/sentry_modulefinder_apple.c +++ b/src/modulefinder/sentry_modulefinder_apple.c @@ -43,11 +43,8 @@ add_image(const struct mach_header *mh, intptr_t UNUSED(vmaddr_slide)) return; } - sentry__mutex_lock(&g_mutex); - - sentry_value_t modules = g_modules; - sentry_value_t new_modules = sentry__value_clone(modules); sentry_value_t module = sentry_value_new_object(); + sentry_value_set_by_key(module, "type", sentry_value_new_string("macho")); sentry_value_set_by_key( module, "code_file", sentry_value_new_string(info.dli_fname)); sentry_value_set_by_key( @@ -80,7 +77,10 @@ add_image(const struct mach_header *mh, intptr_t UNUSED(vmaddr_slide)) } } - sentry_value_set_by_key(module, "type", sentry_value_new_string("macho")); + sentry__mutex_lock(&g_mutex); + + sentry_value_t modules = g_modules; + sentry_value_t new_modules = sentry__value_clone(modules); sentry_value_append(new_modules, module); sentry_value_freeze(new_modules); sentry_value_decref(g_modules); @@ -92,6 +92,12 @@ add_image(const struct mach_header *mh, intptr_t UNUSED(vmaddr_slide)) static void remove_image(const struct mach_header *mh, intptr_t UNUSED(vmaddr_slide)) { + const platform_mach_header *header = (const platform_mach_header *)(mh); + Dl_info info; + if (!dladdr(header, &info)) { + return; + } + sentry__mutex_lock(&g_mutex); if (sentry_value_is_null(g_modules) @@ -99,13 +105,7 @@ remove_image(const struct mach_header *mh, intptr_t UNUSED(vmaddr_slide)) goto done; } - const platform_mach_header *header = (const platform_mach_header *)(mh); - Dl_info info; - if (!dladdr(header, &info)) { - goto done; - } - - char ref_addr[100]; + char ref_addr[32]; snprintf(ref_addr, sizeof(ref_addr), "0x%llx", (long long)info.dli_fbase); sentry_value_t new_modules = sentry_value_new_list(); @@ -130,18 +130,28 @@ remove_image(const struct mach_header *mh, intptr_t UNUSED(vmaddr_slide)) sentry_value_t sentry_get_modules_list(void) { + // We have 2 locked blocks here (actually 3, with the one inside of the + // `add_image` callback). We do that because we have observed deadlocks when + // code concurrently `dlopen`s and thus invokes the `add_image` callback + // from a different thread. sentry__mutex_lock(&g_mutex); if (!g_initialized) { g_modules = sentry_value_new_list(); + g_initialized = true; + + sentry__mutex_unlock(&g_mutex); + // TODO: maybe use `_dyld_image_count` and `_dyld_get_image_header`? - // Those functions are documented to not be thread-safe, though using - // the `register_X` functions are also unsafe because they lack a - // corresponding `unregister` function, and will thus crash when sentry - // itself is unloaded. + // Those functions are documented to not be thread-safe, though + // using the `register_X` functions are also unsafe because they + // lack a corresponding `unregister` function, and will thus crash + // when sentry itself is unloaded. _dyld_register_func_for_add_image(add_image); _dyld_register_func_for_remove_image(remove_image); - g_initialized = true; + + sentry__mutex_lock(&g_mutex); } + sentry_value_t modules = g_modules; sentry_value_incref(modules); sentry__mutex_unlock(&g_mutex); diff --git a/src/modulefinder/sentry_modulefinder_linux.c b/src/modulefinder/sentry_modulefinder_linux.c index ffe0f170d..9629912b0 100644 --- a/src/modulefinder/sentry_modulefinder_linux.c +++ b/src/modulefinder/sentry_modulefinder_linux.c @@ -1,3 +1,6 @@ +#ifndef _GNU_SOURCE +# define _GNU_SOURCE 1 +#endif #include "sentry_modulefinder_linux.h" #include "sentry_core.h" @@ -10,37 +13,165 @@ #include #include #include -#include -#include +#include #include +#include #include -#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#if defined(__ANDROID_API__) && __ANDROID_API__ < 23 +static ssize_t +process_vm_readv(pid_t __pid, const struct iovec *__local_iov, + unsigned long __local_iov_count, const struct iovec *__remote_iov, + unsigned long __remote_iov_count, unsigned long __flags) +{ + return syscall(__NR_process_vm_readv, __pid, __local_iov, __local_iov_count, + __remote_iov, __remote_iov_count, __flags); +} +#endif + #define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define ENSURE(Ptr) \ + if (!Ptr) \ + goto fail + static bool g_initialized = false; static sentry_mutex_t g_mutex = SENTRY__MUTEX_INIT; static sentry_value_t g_modules = { 0 }; static sentry_slice_t LINUX_GATE = { "linux-gate.so", 13 }; +/** + * Checks that `start_offset` + `size` is a valid contiguous mapping in the + * mapped regions, and returns the translated pointer corresponding to + * `start_offset`. + */ +void * +sentry__module_get_addr( + const sentry_module_t *module, uint64_t start_offset, uint64_t size) +{ + for (size_t i = 0; i < module->num_mappings; i++) { + const sentry_mapped_region_t *mapping = &module->mappings[i]; + uint64_t mapping_offset = mapping->offset - module->offset_in_inode; + + // start_offset is inside this mapping + if (start_offset >= mapping_offset + && start_offset < mapping_offset + mapping->size) { + uint64_t addr = start_offset - mapping_offset + mapping->addr; + // the requested size is fully inside the mapping + if (addr + size <= mapping->addr + mapping->size) { + return (void *)(uintptr_t)(addr); + } + } + } + return NULL; +} + +/** + * Reads `size` bytes from `src` to `dst` safely without segfaulting in case + * `src` is not readable. + */ +static bool +read_safely(void *dst, void *src, size_t size) +{ + struct iovec local[1]; + struct iovec remote[1]; + + pid_t pid = getpid(); + local[0].iov_base = dst; + local[0].iov_len = size; + remote[0].iov_base = src; + remote[0].iov_len = size; + + errno = 0; + ssize_t nread = process_vm_readv(pid, local, 1, remote, 1, 0); + bool rv = nread == (ssize_t)size; + + // The syscall is only available in Linux 3.2, meaning Android 17. + // If that is the case, just fall back to an unsafe memcpy. +#if defined(__ANDROID_API__) && __ANDROID_API__ < 17 + if (!rv && errno == EINVAL) { + memcpy(dst, src, size); + rv = true; + } +#endif + return rv; +} + +/** + * Reads `size` bytes into `dst`, from the `start_offset` inside the `module`. + */ +static bool +sentry__module_read_safely(void *dst, const sentry_module_t *module, + uint64_t start_offset, uint64_t size) +{ + void *src = sentry__module_get_addr(module, start_offset, size); + if (!src) { + return false; + } + return read_safely(dst, src, (size_t)size); +} + +static void +sentry__module_mapping_push( + sentry_module_t *module, const sentry_parsed_module_t *parsed) +{ + if (module->num_mappings && module->mappings_inode != parsed->inode) { + return; + } + + size_t size = parsed->end - parsed->start; + if (module->num_mappings) { + sentry_mapped_region_t *last_mapping + = &module->mappings[module->num_mappings - 1]; + if (last_mapping->addr + last_mapping->size == parsed->start + && last_mapping->offset + last_mapping->size == parsed->offset) { + last_mapping->size += size; + return; + } + } + if (module->num_mappings < SENTRY_MAX_MAPPINGS) { + sentry_mapped_region_t *mapping + = &module->mappings[module->num_mappings]; + module->num_mappings += 1; + mapping->offset = parsed->offset; + mapping->size = size; + mapping->addr = parsed->start; + + if (module->num_mappings == 1) { + module->mappings_inode = parsed->inode; + module->offset_in_inode = parsed->offset; + } + } +} + +static bool +is_duplicated_mapping( + const sentry_module_t *module, const sentry_parsed_module_t *parsed) +{ + if (!module->num_mappings) { + return false; + } + const sentry_mapped_region_t *mapping = &module->mappings[0]; + return (mapping->offset == parsed->offset + && module->mappings_inode == parsed->inode); +} + int -sentry__procmaps_parse_module_line(const char *line, sentry_module_t *module) +sentry__procmaps_parse_module_line( + const char *line, sentry_parsed_module_t *module) { - char permissions[5] = { 0 }; - uint64_t offset; uint8_t major_device; uint8_t minor_device; - uint64_t inode; int consumed = 0; // this has been copied from the breakpad source: // https://github.com/google/breakpad/blob/13c1568702e8804bc3ebcfbb435a2786a3e335cf/src/processor/proc_maps_linux.cc#L66 if (sscanf(line, - "%" SCNxPTR "-%" SCNxPTR " %4c %" SCNx64 " %hhx:%hhx %" SCNu64 - " %n", - (uintptr_t *)&module->start, (uintptr_t *)&module->end, permissions, - &offset, &major_device, &minor_device, &inode, &consumed) + "%" SCNx64 "-%" SCNx64 " %4c %" SCNx64 " %hhx:%hhx %" SCNu64 " %n", + &module->start, &module->end, &module->permissions[0], + &module->offset, &major_device, &minor_device, &module->inode, + &consumed) < 7) { return 0; } @@ -66,53 +197,6 @@ sentry__procmaps_parse_module_line(const char *line, sentry_module_t *module) return consumed; } -bool -sentry__mmap_file(sentry_mmap_t *rv, const char *path) -{ - rv->fd = open(path, O_RDONLY); - if (rv->fd < 0) { - goto fail; - } - - struct stat sb; - if (stat(path, &sb) != 0 || !S_ISREG(sb.st_mode)) { - goto fail; - } - - rv->len = sb.st_size; - if (rv->len == 0) { - goto fail; - } - - rv->ptr = mmap(NULL, rv->len, PROT_READ, MAP_PRIVATE, rv->fd, 0); - if (rv->ptr == MAP_FAILED) { - goto fail; - } - - return true; - -fail: - if (rv->fd > 0) { - close(rv->fd); - } - rv->fd = 0; - rv->ptr = NULL; - rv->len = 0; - return false; - - return rv; -} - -void -sentry__mmap_close(sentry_mmap_t *m) -{ - munmap(m->ptr, m->len); - close(m->fd); - m->ptr = NULL; - m->len = 0; - m->fd = 0; -} - void align(size_t alignment, void **offset) { @@ -135,7 +219,7 @@ get_code_id_from_notes( const uint8_t *offset = start; while (offset < (const uint8_t *)end) { - // The note header size is independant of the architecture, so we just + // The note header size is independent of the architecture, so we just // use the `Elf64_Nhdr` variant. const Elf64_Nhdr *note = (const Elf64_Nhdr *)offset; // the headers are consecutive, and the optional `name` and `desc` are @@ -154,107 +238,123 @@ get_code_id_from_notes( return NULL; } -static bool -is_elf_module(void *addr) -{ - // we try to interpret `addr` as an ELF file, which should start with a - // magic number... - const unsigned char *e_ident = addr; - return e_ident[EI_MAG0] == ELFMAG0 && e_ident[EI_MAG1] == ELFMAG1 - && e_ident[EI_MAG2] == ELFMAG2 && e_ident[EI_MAG3] == ELFMAG3; -} - static const uint8_t * -get_code_id_from_elf(void *base, size_t *size_out) +get_code_id_from_elf(const sentry_module_t *module, size_t *size_out) { *size_out = 0; - // now this is interesting: - // `p_offset` is defined as the offset of the section relative to the file, - // and `p_vaddr` is supposed to be the memory location. - // interestingly though, when compiled with gcc 7.4, both are the same, - // because apparently it does not really patch up the `p_vaddr`. gcc 5.4 - // however does, so `p_vaddr` is an actual pointer, and not an offset to - // be added to the `base`. So we are using `p_offset` here, since it seems - // to be the correct offset relative to `base` using both compilers. - const uint8_t *addr = base; - // iterate over all the program headers, for 32/64 bit separately - const unsigned char *e_ident = addr; + unsigned char e_ident[EI_NIDENT]; + ENSURE(sentry__module_read_safely(e_ident, module, 0, EI_NIDENT)); if (e_ident[EI_CLASS] == ELFCLASS64) { - const Elf64_Ehdr *elf = base; - for (int i = 0; i < elf->e_phnum; i++) { - const Elf64_Phdr *header = (const Elf64_Phdr *)(addr + elf->e_phoff - + elf->e_phentsize * i); + Elf64_Ehdr elf; + ENSURE(sentry__module_read_safely(&elf, module, 0, sizeof(Elf64_Ehdr))); + + for (int i = 0; i < elf.e_phnum; i++) { + Elf64_Phdr header; + ENSURE(sentry__module_read_safely(&header, module, + elf.e_phoff + elf.e_phentsize * i, sizeof(Elf64_Phdr))); + // we are only interested in notes - if (header->p_type != PT_NOTE) { + if (header.p_type != PT_NOTE) { continue; } - const uint8_t *code_id = get_code_id_from_notes(header->p_align, - (void *)(addr + header->p_offset), - (void *)(addr + header->p_offset + header->p_memsz), size_out); + void *segment_addr = sentry__module_get_addr( + module, header.p_offset, header.p_filesz); + ENSURE(segment_addr); + const uint8_t *code_id = get_code_id_from_notes(header.p_align, + segment_addr, + (void *)((uintptr_t)segment_addr + header.p_filesz), size_out); if (code_id) { return code_id; } } } else { - const Elf32_Ehdr *elf = base; - for (int i = 0; i < elf->e_phnum; i++) { - const Elf32_Phdr *header = (const Elf32_Phdr *)(addr + elf->e_phoff - + elf->e_phentsize * i); + Elf32_Ehdr elf; + ENSURE(sentry__module_read_safely(&elf, module, 0, sizeof(Elf32_Ehdr))); + + for (int i = 0; i < elf.e_phnum; i++) { + Elf32_Phdr header; + ENSURE(sentry__module_read_safely(&header, module, + elf.e_phoff + elf.e_phentsize * i, sizeof(Elf32_Phdr))); + // we are only interested in notes - if (header->p_type != PT_NOTE) { + if (header.p_type != PT_NOTE) { continue; } - const uint8_t *code_id = get_code_id_from_notes(header->p_align, - (void *)(addr + header->p_offset), - (void *)(addr + header->p_offset + header->p_memsz), size_out); + void *segment_addr = sentry__module_get_addr( + module, header.p_offset, header.p_filesz); + ENSURE(segment_addr); + const uint8_t *code_id = get_code_id_from_notes(header.p_align, + segment_addr, + (void *)((uintptr_t)segment_addr + header.p_filesz), size_out); if (code_id) { return code_id; } } } +fail: return NULL; } static sentry_uuid_t -get_code_id_from_text_fallback(void *base) +get_code_id_from_text_fallback(const sentry_module_t *module) { const uint8_t *text = NULL; size_t text_size = 0; - const uint8_t *addr = base; // iterate over all the program headers, for 32/64 bit separately - const unsigned char *e_ident = addr; + unsigned char e_ident[EI_NIDENT]; + ENSURE(sentry__module_read_safely(e_ident, module, 0, EI_NIDENT)); if (e_ident[EI_CLASS] == ELFCLASS64) { - const Elf64_Ehdr *elf = base; - const Elf64_Shdr *strheader = (const Elf64_Shdr *)(addr + elf->e_shoff - + elf->e_shentsize * elf->e_shstrndx); - - const char *names = (const char *)(addr + strheader->sh_offset); - for (int i = 0; i < elf->e_shnum; i++) { - const Elf64_Shdr *header = (const Elf64_Shdr *)(addr + elf->e_shoff - + elf->e_shentsize * i); - const char *name = names + header->sh_name; - if (header->sh_type == SHT_PROGBITS && strcmp(name, ".text") == 0) { - text = addr + header->sh_offset; - text_size = header->sh_size; + Elf64_Ehdr elf; + ENSURE(sentry__module_read_safely(&elf, module, 0, sizeof(Elf64_Ehdr))); + + Elf64_Shdr strheader; + ENSURE(sentry__module_read_safely(&strheader, module, + elf.e_shoff + elf.e_shentsize * elf.e_shstrndx, + sizeof(Elf64_Shdr))); + + const char *names = sentry__module_get_addr( + module, strheader.sh_offset, strheader.sh_entsize); + ENSURE(names); + for (int i = 0; i < elf.e_shnum; i++) { + Elf64_Shdr header; + ENSURE(sentry__module_read_safely(&header, module, + elf.e_shoff + elf.e_shentsize * i, sizeof(Elf64_Shdr))); + + const char *name = names + header.sh_name; + if (header.sh_type == SHT_PROGBITS && strcmp(name, ".text") == 0) { + text = sentry__module_get_addr( + module, header.sh_offset, header.sh_size); + ENSURE(text); + text_size = header.sh_size; break; } } } else { - const Elf32_Ehdr *elf = base; - const Elf32_Shdr *strheader = (const Elf32_Shdr *)(addr + elf->e_shoff - + elf->e_shentsize * elf->e_shstrndx); - - const char *names = (const char *)(addr + strheader->sh_offset); - for (int i = 0; i < elf->e_shnum; i++) { - const Elf32_Shdr *header = (const Elf32_Shdr *)(addr + elf->e_shoff - + elf->e_shentsize * i); - const char *name = names + header->sh_name; - if (header->sh_type == SHT_PROGBITS && strcmp(name, ".text") == 0) { - text = addr + header->sh_offset; - text_size = header->sh_size; + Elf32_Ehdr elf; + ENSURE(sentry__module_read_safely(&elf, module, 0, sizeof(Elf32_Ehdr))); + + Elf32_Shdr strheader; + ENSURE(sentry__module_read_safely(&strheader, module, + elf.e_shoff + elf.e_shentsize * elf.e_shstrndx, + sizeof(Elf32_Shdr))); + + const char *names = sentry__module_get_addr( + module, strheader.sh_offset, strheader.sh_entsize); + ENSURE(names); + for (int i = 0; i < elf.e_shnum; i++) { + Elf32_Shdr header; + ENSURE(sentry__module_read_safely(&header, module, + elf.e_shoff + elf.e_shentsize * i, sizeof(Elf32_Shdr))); + + const char *name = names + header.sh_name; + if (header.sh_type == SHT_PROGBITS && strcmp(name, ".text") == 0) { + text = sentry__module_get_addr( + module, header.sh_offset, header.sh_size); + ENSURE(text); + text_size = header.sh_size; break; } } @@ -270,15 +370,18 @@ get_code_id_from_text_fallback(void *base) } return uuid; +fail: + return sentry_uuid_nil(); } bool -sentry__procmaps_read_ids_from_elf(sentry_value_t value, void *elf_ptr) +sentry__procmaps_read_ids_from_elf( + sentry_value_t value, const sentry_module_t *module) { // and try to get the debug id from the elf headers of the loaded // modules size_t code_id_size; - const uint8_t *code_id = get_code_id_from_elf(elf_ptr, &code_id_size); + const uint8_t *code_id = get_code_id_from_elf(module, &code_id_size); sentry_uuid_t uuid = sentry_uuid_nil(); if (code_id) { sentry_value_set_by_key(value, "code_id", @@ -286,13 +389,13 @@ sentry__procmaps_read_ids_from_elf(sentry_value_t value, void *elf_ptr) memcpy(uuid.bytes, code_id, MIN(code_id_size, 16)); } else { - uuid = get_code_id_from_text_fallback(elf_ptr); + uuid = get_code_id_from_text_fallback(module); } // the usage of these is described here: // https://getsentry.github.io/symbolicator/advanced/symbol-server-compatibility/#identifiers - // in particular, the debug_id is a `little-endian GUID`, so we - // have to do appropriate byte-flipping + // in particular, the debug_id is a `little-endian GUID`, so we have to do + // appropriate byte-flipping char *uuid_bytes = uuid.bytes; uint32_t *a = (uint32_t *)uuid_bytes; *a = htonl(*a); @@ -310,62 +413,30 @@ sentry__procmaps_module_to_value(const sentry_module_t *module) { sentry_value_t mod_val = sentry_value_new_object(); sentry_value_set_by_key(mod_val, "type", sentry_value_new_string("elf")); - sentry_value_set_by_key(mod_val, "image_addr", - sentry__value_new_addr((uint64_t)(size_t)module->start)); - sentry_value_set_by_key(mod_val, "image_size", - sentry_value_new_int32( - (int32_t)((size_t)module->end - (size_t)module->start))); sentry_value_set_by_key(mod_val, "code_file", sentry__value_new_string_owned(sentry__slice_to_owned(module->file))); - // At least on the android API-16, x86 simulator, the linker apparently - // does not load the complete file into memory. Or at least, the section - // headers which are located at the end of the file are not loaded, and - // we would be poking into invalid memory. To be safe, we mmap the complete - // file from disk, so we have the on-disk layout, and are independent of how - // the runtime linker would load or re-order any sections. The exception - // here is the linux-gate, which is not an actual file on disk, so we - // actually poke at its memory. - if (sentry__slice_eq(module->file, LINUX_GATE)) { - if (!is_elf_module(module->start)) { - goto fail; - } - sentry__procmaps_read_ids_from_elf(mod_val, module->start); - } else { - char *filename = sentry__slice_to_owned(module->file); - sentry_mmap_t mm; - if (!sentry__mmap_file(&mm, filename)) { - sentry_free(filename); - goto fail; - } - sentry_free(filename); - - if (!is_elf_module(mm.ptr)) { - sentry__mmap_close(&mm); - goto fail; - } - - sentry__procmaps_read_ids_from_elf(mod_val, mm.ptr); + const sentry_mapped_region_t *first_mapping = &module->mappings[0]; + const sentry_mapped_region_t *last_mapping + = &module->mappings[module->num_mappings - 1]; + sentry_value_set_by_key( + mod_val, "image_addr", sentry__value_new_addr(first_mapping->addr)); + sentry_value_set_by_key(mod_val, "image_size", + sentry_value_new_int32( + last_mapping->addr + last_mapping->size - first_mapping->addr)); - sentry__mmap_close(&mm); - } + sentry__procmaps_read_ids_from_elf(mod_val, module); return mod_val; - -fail: - sentry_value_decref(mod_val); - return sentry_value_new_null(); } static void try_append_module(sentry_value_t modules, const sentry_module_t *module) { - if (!module->file.ptr) { + if (!module->file.ptr || !module->num_mappings) { return; } - SENTRY_TRACEF( - "inspecting module \"%.*s\"", (int)module->file.len, module->file.ptr); sentry_value_t mod_val = sentry__procmaps_module_to_value(module); if (!sentry_value_is_null(mod_val)) { sentry_value_append(modules, mod_val); @@ -383,7 +454,7 @@ typedef Elf64_auxv_t elf_aux_entry; #endif // See http://man7.org/linux/man-pages/man7/vdso.7.html -static void * +static uint64_t get_linux_vdso(void) { // this is adapted from: @@ -400,11 +471,22 @@ get_linux_vdso(void) && one_aux_entry.a_type != AT_NULL) { if (one_aux_entry.a_type == AT_SYSINFO_EHDR) { close(fd); - return (void *)one_aux_entry.a_un.a_val; + return (uint64_t)one_aux_entry.a_un.a_val; } } close(fd); - return NULL; + return 0; +} + +static bool +is_valid_elf_header(void *start) +{ + unsigned char e_ident[EI_NIDENT]; + if (!read_safely(e_ident, start, EI_NIDENT)) { + return false; + } + return e_ident[EI_MAG0] == ELFMAG0 && e_ident[EI_MAG1] == ELFMAG1 + && e_ident[EI_MAG2] == ELFMAG2 && e_ident[EI_MAG3] == ELFMAG3; } static void @@ -415,71 +497,95 @@ load_modules(sentry_value_t modules) return; } - // just read the whole map at once, maybe do it line-by-line as a followup… - char buf[4096]; + // Read the whole map at once. Doing it line-by-line would be a good + // followup. sentry_stringbuilder_t sb; sentry__stringbuilder_init(&sb); while (true) { + char *buf = sentry__stringbuilder_reserve(&sb, 4096); + if (!buf) { + sentry__stringbuilder_cleanup(&sb); + close(fd); + return; + } ssize_t n = read(fd, buf, 4096); if (n < 0 && (errno == EAGAIN || errno == EINTR)) { continue; } else if (n <= 0) { break; } - if (sentry__stringbuilder_append_buf(&sb, buf, n)) { - sentry__stringbuilder_cleanup(&sb); - close(fd); - return; - } + sentry__stringbuilder_set_len(&sb, sentry__stringbuilder_len(&sb) + n); } close(fd); + // ensure the buffer is zero terminated + if (sentry__stringbuilder_append(&sb, "")) { + sentry__stringbuilder_cleanup(&sb); + return; + } char *contents = sentry__stringbuilder_into_string(&sb); if (!contents) { return; } char *current_line = contents; - void *linux_vdso = get_linux_vdso(); + uint64_t linux_vdso = get_linux_vdso(); // we have multiple memory maps per file, and we need to merge their offsets // based on the filename. Luckily, the maps are ordered by filename, so yay - sentry_module_t last_module = { (void *)-1, 0, { NULL, 0 } }; + sentry_module_t last_module; + memset(&last_module, 0, sizeof(sentry_module_t)); while (true) { - sentry_module_t module = { 0, 0, { NULL, 0 } }; + sentry_parsed_module_t module; + memset(&module, 0, sizeof(sentry_parsed_module_t)); int read = sentry__procmaps_parse_module_line(current_line, &module); current_line += read; if (!read) { break; } - // for the vdso, we use the special filename `linux-gate.so`, - // otherwise we check that we have a valid pathname (with a `/` inside), - // and skip over things that end in `)`, because entries marked as - // `(deleted)` might crash when dereferencing, trying to check if its - // a valid elf file. - char *slash; - if (module.start && module.start == linux_vdso) { - module.file = LINUX_GATE; - } else if (!module.start || !module.file.len - || module.file.ptr[module.file.len - 1] == ')' - || (slash = strchr(module.file.ptr, '/')) == NULL - || slash > module.file.ptr + module.file.len + // skip mappings that are not readable + if (!module.start || module.permissions[0] != 'r') { + continue; + } + // skip mappings in `/dev/` or mappings that have no filename + if (!module.file.len || (module.file.len >= 5 && memcmp("/dev/", module.file.ptr, 5) == 0)) { continue; } + // for the vdso, we use the special filename `linux-gate.so` + if (module.start && module.start == linux_vdso) { + module.file = LINUX_GATE; + } else if (module.file.ptr[0] != '/') { + // and skip all mappings that are not a file + continue; + } - if (last_module.file.len - && !sentry__slice_eq(last_module.file, module.file)) { - try_append_module(modules, &last_module); - last_module = module; - } else { - // otherwise merge it - last_module.start = MIN(last_module.start, module.start); - last_module.end = MAX(last_module.end, module.end); - last_module.file = module.file; + if (is_valid_elf_header((void *)(size_t)module.start)) { + // On android, we sometimes have multiple mappings for the same + // inode at the same offset, such as this, excuse the auto-format + // here: + // 737b5570d000-737b5570e000 r--p 00000000 07:70 34 + // /apex/com.android.runtime/lib64/bionic/libdl.so + // 737b5570e000-737b5570f000 r-xp 00000000 07:70 34 + // /apex/com.android.runtime/lib64/bionic/libdl.so + // 737b5570f000-737b55710000 r--p 00000000 07:70 34 + // /apex/com.android.runtime/lib64/bionic/libdl.so + + if (!is_duplicated_mapping(&last_module, &module)) { + // try to append the module based on the mappings that we have + // found so far + try_append_module(modules, &last_module); + + // start a new module based on the current mapping + memset(&last_module, 0, sizeof(sentry_module_t)); + + last_module.file = module.file; + } } + + sentry__module_mapping_push(&last_module, &module); } try_append_module(modules, &last_module); sentry_free(contents); diff --git a/src/modulefinder/sentry_modulefinder_linux.h b/src/modulefinder/sentry_modulefinder_linux.h index 8d5adaf71..5204158c0 100644 --- a/src/modulefinder/sentry_modulefinder_linux.h +++ b/src/modulefinder/sentry_modulefinder_linux.h @@ -4,26 +4,45 @@ #include "sentry_boot.h" #include "sentry_slice.h" +#define SENTRY_MAX_MAPPINGS 5 + typedef struct { - void *start; - void *end; + uint64_t start; + uint64_t end; + uint64_t offset; + char permissions[5]; + uint64_t inode; sentry_slice_t file; -} sentry_module_t; +} sentry_parsed_module_t; typedef struct { - void *ptr; - size_t len; - int fd; -} sentry_mmap_t; + uint64_t offset; // the offset in the mapped file + uint64_t size; // size of this mapping + uint64_t addr; // addr in memory of the mapping +} sentry_mapped_region_t; -#if SENTRY_UNITTEST -bool sentry__mmap_file(sentry_mmap_t *mapping, const char *path); -void sentry__mmap_close(sentry_mmap_t *mapping); +typedef struct { + sentry_slice_t file; + sentry_mapped_region_t mappings[SENTRY_MAX_MAPPINGS]; + uint64_t offset_in_inode; + uint64_t mappings_inode; + uint8_t num_mappings; +} sentry_module_t; -bool sentry__procmaps_read_ids_from_elf(sentry_value_t value, void *elf_ptr); +#ifdef SENTRY_UNITTEST +bool sentry__procmaps_read_ids_from_elf( + sentry_value_t value, const sentry_module_t *module); int sentry__procmaps_parse_module_line( - const char *line, sentry_module_t *module); + const char *line, sentry_parsed_module_t *module); + +/** + * Checks that `start_offset` + `size` is a valid contiguous mapping in the + * mapped regions, and returns the translated pointer corresponding to + * `start_offset`. + */ +void *sentry__module_get_addr( + const sentry_module_t *module, uint64_t start_offset, uint64_t size); #endif #endif diff --git a/src/sentry_backend.h b/src/sentry_backend.h index 1eb025d38..bb954272a 100644 --- a/src/sentry_backend.h +++ b/src/sentry_backend.h @@ -11,24 +11,23 @@ * can ensure that any captured crash contains the sentry scope and other * information. */ -struct sentry_backend_s; -typedef struct sentry_backend_s { - int (*startup_func)( - struct sentry_backend_s *, const sentry_options_t *options); - void (*shutdown_func)(struct sentry_backend_s *); - void (*free_func)(struct sentry_backend_s *); - void (*except_func)( - struct sentry_backend_s *, const struct sentry_ucontext_s *); - void (*flush_scope_func)(struct sentry_backend_s *); +typedef struct sentry_backend_s sentry_backend_t; +struct sentry_backend_s { + int (*startup_func)(sentry_backend_t *, const sentry_options_t *options); + void (*shutdown_func)(sentry_backend_t *); + void (*free_func)(sentry_backend_t *); + void (*except_func)(sentry_backend_t *, const struct sentry_ucontext_s *); + void (*flush_scope_func)( + sentry_backend_t *, const sentry_options_t *options); // NOTE: The breadcrumb is not moved into the hook and does not need to be // `decref`-d internally. - void (*add_breadcrumb_func)( - struct sentry_backend_s *, sentry_value_t breadcrumb); - void (*user_consent_changed_func)(struct sentry_backend_s *); - uint64_t (*get_last_crash_func)(struct sentry_backend_s *); + void (*add_breadcrumb_func)(sentry_backend_t *, sentry_value_t breadcrumb, + const sentry_options_t *options); + void (*user_consent_changed_func)(sentry_backend_t *); + uint64_t (*get_last_crash_func)(sentry_backend_t *); void *data; bool can_capture_after_shutdown; -} sentry_backend_t; +}; /** * This will free a previously allocated backend. diff --git a/src/sentry_boot.h b/src/sentry_boot.h index 3636f7806..2231366c0 100644 --- a/src/sentry_boot.h +++ b/src/sentry_boot.h @@ -33,6 +33,10 @@ # include #endif +#ifndef __has_builtin +# define __has_builtin(x) 0 +#endif + #include #include diff --git a/src/sentry_core.c b/src/sentry_core.c index 9e7aab29c..2904ce7c2 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -36,6 +36,19 @@ sentry__options_getref(void) return options; } +sentry_options_t * +sentry__options_lock(void) +{ + sentry__mutex_lock(&g_options_lock); + return g_options; +} + +void +sentry__options_unlock(void) +{ + sentry__mutex_unlock(&g_options_lock); +} + static void load_user_consent(sentry_options_t *opts) { @@ -72,7 +85,13 @@ sentry__should_skip_upload(void) int sentry_init(sentry_options_t *options) { - sentry_shutdown(); + // this function is to be called only once, so we do not allow more than one + // caller + sentry__mutex_lock(&g_options_lock); + // pre-init here, so we can consistently use bailing out to :fail + sentry_transport_t *transport = NULL; + + sentry_close(); sentry_logger_t logger = { NULL, NULL }; if (options->debug) { @@ -84,11 +103,10 @@ sentry_init(sentry_options_t *options) if (sentry__path_create_dir_all(options->database_path)) { SENTRY_WARN("failed to create database directory or there is no write " "access to this directory"); - sentry_options_free(options); - return 1; + goto fail; } - sentry_transport_t *transport = options->transport; + transport = options->transport; sentry_path_t *database_path = options->database_path; options->database_path = sentry__path_absolute(database_path); if (options->database_path) { @@ -139,9 +157,7 @@ sentry_init(sentry_options_t *options) last_crash = backend->get_last_crash_func(backend); } - sentry__mutex_lock(&g_options_lock); g_options = options; - sentry__mutex_unlock(&g_options_lock); // *after* setting the global options, trigger a scope and consent flush, // since at least crashpad needs that. @@ -167,6 +183,7 @@ sentry_init(sentry_options_t *options) sentry_start_session(); } + sentry__mutex_unlock(&g_options_lock); return 0; fail: @@ -175,30 +192,29 @@ sentry_init(sentry_options_t *options) sentry__transport_shutdown(transport, 0); } sentry_options_free(options); + sentry__mutex_unlock(&g_options_lock); return 1; } int -sentry_shutdown(void) +sentry_close(void) { - sentry_end_session(); - + // this function is to be called only once, so we do not allow more than one + // caller sentry__mutex_lock(&g_options_lock); sentry_options_t *options = g_options; - g_options = NULL; - sentry__mutex_unlock(&g_options_lock); size_t dumped_envelopes = 0; if (options) { + sentry_end_session(); if (options->backend && options->backend->shutdown_func) { SENTRY_TRACE("shutting down backend"); options->backend->shutdown_func(options->backend); } if (options->transport) { - // TODO: make this configurable if (sentry__transport_shutdown( - options->transport, SENTRY_DEFAULT_SHUTDOWN_TIMEOUT) + options->transport, options->shutdown_timeout) != 0) { SENTRY_WARN("transport did not shut down cleanly"); } @@ -210,15 +226,26 @@ sentry_shutdown(void) || !options->backend->can_capture_after_shutdown)) { sentry__run_clean(options->run); } - sentry_options_free(options); + } else { + SENTRY_DEBUG("sentry_close() called, but options was empty"); } + g_options = NULL; + sentry__mutex_unlock(&g_options_lock); + sentry__scope_cleanup(); sentry_clear_modulecache(); + return (int)dumped_envelopes; } +int +sentry_shutdown(void) +{ + return sentry_close(); +} + int sentry_reinstall_backend(void) { @@ -337,7 +364,18 @@ sentry_capture_event(sentry_value_t event) was_captured = true; envelope = sentry__prepare_event(options, event, &event_id); if (envelope) { - sentry__add_current_session_to_envelope(envelope); + if (options->session) { + SENTRY_WITH_OPTIONS_MUT (mut_options) { + sentry__envelope_add_session( + envelope, mut_options->session); + // we're assuming that if a session is added to an envelope + // it will be sent onwards. This means we now need to set + // the init flag to false because we're no longer the + // initial session update. + mut_options->session->init = false; + } + } + sentry__capture_envelope(options->transport, envelope); } } @@ -370,7 +408,7 @@ sentry__prepare_event(const sentry_options_t *options, sentry_value_t event, if (!options->symbolize_stacktraces) { mode &= ~SENTRY_SCOPE_STACKTRACES; } - sentry__scope_apply_to_event(scope, event, mode); + sentry__scope_apply_to_event(scope, options, event, mode); } if (options->before_send_func) { @@ -428,7 +466,7 @@ sentry_handle_exception(const sentry_ucontext_t *uctx) sentry_uuid_t sentry__new_event_id(void) { -#if SENTRY_UNITTEST +#ifdef SENTRY_UNITTEST return sentry_uuid_from_string("4c035723-8638-4c3a-923f-2ab9d08b4018"); #else return sentry_uuid_new_v4(); @@ -454,10 +492,18 @@ sentry__ensure_event_id(sentry_value_t event, sentry_uuid_t *uuid_out) void sentry_set_user(sentry_value_t user) { + if (!sentry_value_is_null(user)) { + SENTRY_WITH_OPTIONS_MUT (options) { + if (options->session) { + sentry__session_sync_user(options->session, user); + sentry__run_write_session(options->run, options->session); + } + } + } + SENTRY_WITH_SCOPE_MUT (scope) { sentry_value_decref(scope->user); scope->user = user; - sentry__scope_session_sync(scope); } } @@ -474,7 +520,8 @@ sentry_add_breadcrumb(sentry_value_t breadcrumb) SENTRY_WITH_OPTIONS (options) { if (options->backend && options->backend->add_breadcrumb_func) { // the hook will *not* take ownership - options->backend->add_breadcrumb_func(options->backend, breadcrumb); + options->backend->add_breadcrumb_func( + options->backend, breadcrumb, options); } max_breadcrumbs = options->max_breadcrumbs; } diff --git a/src/sentry_core.h b/src/sentry_core.h index 156801ff1..8040ef140 100644 --- a/src/sentry_core.h +++ b/src/sentry_core.h @@ -70,6 +70,11 @@ sentry_value_t sentry__ensure_event_id( */ const sentry_options_t *sentry__options_getref(void); +/** + * This will acquire a lock on the global options. + */ +sentry_options_t *sentry__options_lock(void); + /** * Release the lock on the global options. */ @@ -79,4 +84,8 @@ void sentry__options_unlock(void); for (const sentry_options_t *Options = sentry__options_getref(); Options; \ sentry_options_free((sentry_options_t *)Options), Options = NULL) +#define SENTRY_WITH_OPTIONS_MUT(Options) \ + for (sentry_options_t *Options = sentry__options_lock(); Options; \ + sentry__options_unlock(), Options = NULL) + #endif diff --git a/src/sentry_envelope.c b/src/sentry_envelope.c index 7bd621c0d..71cef6ed6 100644 --- a/src/sentry_envelope.c +++ b/src/sentry_envelope.c @@ -410,7 +410,7 @@ sentry_envelope_write_to_file( return rv; } -#if SENTRY_UNITTEST +#ifdef SENTRY_UNITTEST size_t sentry__envelope_get_item_count(const sentry_envelope_t *envelope) { diff --git a/src/sentry_envelope.h b/src/sentry_envelope.h index efd31a771..ac5c69136 100644 --- a/src/sentry_envelope.h +++ b/src/sentry_envelope.h @@ -86,7 +86,7 @@ MUST_USE int sentry_envelope_write_to_path( const sentry_envelope_t *envelope, const sentry_path_t *path); // these for now are only needed for tests -#if SENTRY_UNITTEST +#ifdef SENTRY_UNITTEST size_t sentry__envelope_get_item_count(const sentry_envelope_t *envelope); const sentry_envelope_item_t *sentry__envelope_get_item( const sentry_envelope_t *envelope, size_t idx); diff --git a/src/sentry_json.c b/src/sentry_json.c index 378725e45..316868256 100644 --- a/src/sentry_json.c +++ b/src/sentry_json.c @@ -194,7 +194,7 @@ void sentry__jsonwriter_write_double(sentry_jsonwriter_t *jw, double val) { if (can_write_item(jw)) { - char buf[50]; + char buf[24]; // The MAX_SAFE_INTEGER is 9007199254740991, which has 16 digits int written = sentry__snprintf_c(buf, sizeof(buf), "%.16g", val); // print `null` if we have printf issues or a non-finite double, which diff --git a/src/sentry_logger.h b/src/sentry_logger.h index 51fd792f4..f17efbc6b 100644 --- a/src/sentry_logger.h +++ b/src/sentry_logger.h @@ -32,4 +32,6 @@ void sentry__logger_log(sentry_level_t level, const char *message, ...); #define SENTRY_WARN(message) sentry__logger_log(SENTRY_LEVEL_WARNING, message) +#define SENTRY_ERROR(message) sentry__logger_log(SENTRY_LEVEL_ERROR, message) + #endif diff --git a/src/sentry_options.c b/src/sentry_options.c index 13f3de6f4..190c4234b 100644 --- a/src/sentry_options.c +++ b/src/sentry_options.c @@ -47,6 +47,7 @@ sentry_options_new(void) opts->transport = sentry__transport_new_default(); opts->sample_rate = 1.0; opts->refcount = 1; + opts->shutdown_timeout = SENTRY_DEFAULT_SHUTDOWN_TIMEOUT; return opts; } @@ -312,6 +313,19 @@ sentry_options_set_system_crash_reporter_enabled( opts->system_crash_reporter_enabled = !!enabled; } +void +sentry_options_set_shutdown_timeout( + sentry_options_t *opts, uint64_t shutdown_timeout) +{ + opts->shutdown_timeout = shutdown_timeout; +} + +uint64_t +sentry_options_get_shutdown_timeout(sentry_options_t *opts) +{ + return opts->shutdown_timeout; +} + static void add_attachment(sentry_options_t *opts, sentry_path_t *path) { diff --git a/src/sentry_options.h b/src/sentry_options.h index d86e55111..0bc935258 100644 --- a/src/sentry_options.h +++ b/src/sentry_options.h @@ -4,6 +4,7 @@ #include "sentry_boot.h" #include "sentry_logger.h" +#include "sentry_session.h" #include "sentry_utils.h" // Defaults to 2s as per @@ -58,9 +59,11 @@ typedef struct sentry_options_s { /* everything from here on down are options which are stored here but not exposed through the options API */ struct sentry_backend_s *backend; + sentry_session_t *session; long user_consent; long refcount; + uint64_t shutdown_timeout; } sentry_options_t; /** diff --git a/src/sentry_os.c b/src/sentry_os.c index c4479bc65..977261eba 100644 --- a/src/sentry_os.c +++ b/src/sentry_os.c @@ -34,7 +34,7 @@ sentry__get_os_context(void) } ffi->dwFileFlags &= ffi->dwFileFlagsMask; - char buf[100]; + char buf[32]; snprintf(buf, sizeof(buf), "%u.%u.%u", ffi->dwFileVersionMS >> 16, ffi->dwFileVersionMS & 0xffff, ffi->dwFileVersionLS >> 16); @@ -71,7 +71,7 @@ sentry__get_os_context(void) sentry_value_set_by_key(os, "name", sentry_value_new_string("macOS")); - char buf[100]; + char buf[32]; size_t buf_len = sizeof(buf); if (sysctlbyname("kern.osproductversion", buf, &buf_len, NULL, 0) != 0) { diff --git a/src/sentry_ratelimiter.c b/src/sentry_ratelimiter.c index 025f69707..e1dd8c855 100644 --- a/src/sentry_ratelimiter.c +++ b/src/sentry_ratelimiter.c @@ -99,7 +99,7 @@ sentry__rate_limiter_free(sentry_rate_limiter_t *rl) sentry_free(rl); } -#if SENTRY_UNITTEST +#ifdef SENTRY_UNITTEST uint64_t sentry__rate_limiter_get_disabled_until( const sentry_rate_limiter_t *rl, int category) diff --git a/src/sentry_ratelimiter.h b/src/sentry_ratelimiter.h index 0a17754d3..58a666121 100644 --- a/src/sentry_ratelimiter.h +++ b/src/sentry_ratelimiter.h @@ -41,7 +41,7 @@ bool sentry__rate_limiter_update_from_http_retry_after( bool sentry__rate_limiter_is_disabled( const sentry_rate_limiter_t *rl, int category); -#if SENTRY_UNITTEST +#ifdef SENTRY_UNITTEST /** * The rate limiters state is completely opaque. Unless in tests, where we would * want to actually peek into the specific rate limiting `category`. diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 814b6f815..5f8992e7e 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -73,7 +73,6 @@ get_scope(void) g_scope.breadcrumbs = sentry_value_new_list(); g_scope.level = SENTRY_LEVEL_ERROR; g_scope.client_sdk = get_client_sdk(); - g_scope.session = NULL; g_scope_initialized = true; @@ -112,27 +111,16 @@ sentry__scope_unlock(void) } void -sentry__scope_flush_unlock(const sentry_scope_t *scope) +sentry__scope_flush_unlock() { - bool did_unlock = false; + sentry__scope_unlock(); SENTRY_WITH_OPTIONS (options) { - if (scope->session) { - sentry__run_write_session(options->run, scope->session); - sentry__scope_unlock(); - } else { - sentry__scope_unlock(); - sentry__run_clear_session(options->run); - } - did_unlock = true; // we try to unlock the scope/session lock as soon as possible. The // backend will do its own `WITH_SCOPE` internally. if (options->backend && options->backend->flush_scope_func) { - options->backend->flush_scope_func(options->backend); + options->backend->flush_scope_func(options->backend, options); } } - if (!did_unlock) { - sentry__scope_unlock(); - } } static void @@ -237,8 +225,9 @@ sentry__symbolize_stacktrace(sentry_value_t stacktrace) } void -sentry__scope_apply_to_event( - const sentry_scope_t *scope, sentry_value_t event, sentry_scope_mode_t mode) +sentry__scope_apply_to_event(const sentry_scope_t *scope, + const sentry_options_t *options, sentry_value_t event, + sentry_scope_mode_t mode) { #define IS_NULL(Key) sentry_value_is_null(sentry_value_get_by_key(event, Key)) #define SET(Key, Value) sentry_value_set_by_key(event, Key, Value) @@ -264,11 +253,9 @@ sentry__scope_apply_to_event( PLACE_STRING("platform", "native"); - SENTRY_WITH_OPTIONS (options) { - PLACE_STRING("release", options->release); - PLACE_STRING("dist", options->dist); - PLACE_STRING("environment", options->environment); - } + PLACE_STRING("release", options->release); + PLACE_STRING("dist", options->dist); + PLACE_STRING("environment", options->environment); if (IS_NULL("level")) { SET("level", sentry__value_new_level(scope->level)); @@ -305,24 +292,3 @@ sentry__scope_apply_to_event( #undef IS_NULL #undef SET } - -void -sentry__scope_session_sync(sentry_scope_t *scope) -{ - if (!scope->session) { - return; - } - - if (!sentry_value_is_null(scope->user)) { - sentry_value_t did = sentry_value_get_by_key(scope->user, "id"); - if (sentry_value_is_null(did)) { - did = sentry_value_get_by_key(scope->user, "email"); - } - if (sentry_value_is_null(did)) { - did = sentry_value_get_by_key(scope->user, "username"); - } - sentry_value_decref(scope->session->distinct_id); - sentry_value_incref(did); - scope->session->distinct_id = did; - } -} diff --git a/src/sentry_scope.h b/src/sentry_scope.h index e22cc945d..250ff8200 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -19,7 +19,6 @@ typedef struct sentry_scope_s { sentry_value_t breadcrumbs; sentry_level_t level; sentry_value_t client_sdk; - sentry_session_t *session; } sentry_scope_t; /** @@ -54,11 +53,11 @@ void sentry__scope_unlock(void); void sentry__scope_cleanup(void); /** - * This will notify any backend of scope changes, and persist session - * information to disk. This function must be called while holding the scope - * lock, and it will be unlocked internally. + * This will notify any backend of scope changes. + * This function must be called while holding the scope lock, and it will be + * unlocked internally. */ -void sentry__scope_flush_unlock(const sentry_scope_t *scope); +void sentry__scope_flush_unlock(); /** * This will merge the requested data which is in the given `scope` to the given @@ -67,13 +66,8 @@ void sentry__scope_flush_unlock(const sentry_scope_t *scope); * attached. */ void sentry__scope_apply_to_event(const sentry_scope_t *scope, - sentry_value_t event, sentry_scope_mode_t mode); - -/** - * This will update a sessions `distinct_id`, which is generated out of other - * scope data. - */ -void sentry__scope_session_sync(sentry_scope_t *scope); + const sentry_options_t *options, sentry_value_t event, + sentry_scope_mode_t mode); /** * These are convenience macros to automatically lock/unlock a scope inside a @@ -84,7 +78,7 @@ void sentry__scope_session_sync(sentry_scope_t *scope); sentry__scope_unlock(), Scope = NULL) #define SENTRY_WITH_SCOPE_MUT(Scope) \ for (sentry_scope_t *Scope = sentry__scope_lock(); Scope; \ - sentry__scope_flush_unlock(Scope), Scope = NULL) + sentry__scope_flush_unlock(), Scope = NULL) #define SENTRY_WITH_SCOPE_MUT_NO_FLUSH(Scope) \ for (sentry_scope_t *Scope = sentry__scope_lock(); Scope; \ sentry__scope_unlock(), Scope = NULL) diff --git a/src/sentry_session.c b/src/sentry_session.c index 38288a309..f58a88e88 100644 --- a/src/sentry_session.c +++ b/src/sentry_session.c @@ -1,5 +1,6 @@ #include "sentry_session.h" #include "sentry_alloc.h" +#include "sentry_database.h" #include "sentry_envelope.h" #include "sentry_json.h" #include "sentry_options.h" @@ -185,9 +186,10 @@ sentry__session_from_json(const char *buf, size_t buflen) sentry_value_get_by_key(value, "errors")); rv->started_ms = sentry__iso8601_to_msec( sentry_value_as_string(sentry_value_get_by_key(value, "started"))); - rv->duration_ms = (uint64_t)( - sentry_value_as_double(sentry_value_get_by_key(value, "duration")) - * 1000); + + double duration + = sentry_value_as_double(sentry_value_get_by_key(value, "duration")); + rv->duration_ms = (uint64_t)(duration * 1000); sentry_value_decref(value); @@ -212,18 +214,23 @@ void sentry_start_session(void) { sentry_end_session(); - SENTRY_WITH_SCOPE_MUT (scope) { - scope->session = sentry__session_new(); - sentry__scope_session_sync(scope); + SENTRY_WITH_SCOPE (scope) { + SENTRY_WITH_OPTIONS_MUT (options) { + options->session = sentry__session_new(); + if (options->session) { + sentry__session_sync_user(options->session, scope->user); + sentry__run_write_session(options->run, options->session); + } + } } } void sentry__record_errors_on_current_session(uint32_t error_count) { - SENTRY_WITH_SCOPE_MUT (scope) { - if (scope->session) { - scope->session->errors += error_count; + SENTRY_WITH_OPTIONS_MUT (options) { + if (options->session) { + options->session->errors += error_count; } } } @@ -232,9 +239,10 @@ static sentry_session_t * sentry__end_session_internal(void) { sentry_session_t *session = NULL; - SENTRY_WITH_SCOPE_MUT (scope) { - session = scope->session; - scope->session = NULL; + SENTRY_WITH_OPTIONS_MUT (options) { + session = options->session; + options->session = NULL; + sentry__run_clear_session(options->run); } if (session && session->status == SENTRY_SESSION_STATUS_OK) { @@ -271,15 +279,17 @@ sentry_end_session(void) } void -sentry__add_current_session_to_envelope(sentry_envelope_t *envelope) +sentry__session_sync_user(sentry_session_t *session, sentry_value_t user) { - SENTRY_WITH_SCOPE_MUT (scope) { - if (scope->session) { - sentry__envelope_add_session(envelope, scope->session); - // we're assuming that if a session is added to an envelope it - // will be sent onwards. This means we now need to set the init - // flag to false because we're no longer the initial session update. - scope->session->init = false; - } + + sentry_value_t did = sentry_value_get_by_key(user, "id"); + if (sentry_value_is_null(did)) { + did = sentry_value_get_by_key(user, "email"); + } + if (sentry_value_is_null(did)) { + did = sentry_value_get_by_key(user, "username"); } + sentry_value_decref(session->distinct_id); + sentry_value_incref(did); + session->distinct_id = did; } diff --git a/src/sentry_session.h b/src/sentry_session.h index 6de5f7ec1..5ba036cbd 100644 --- a/src/sentry_session.h +++ b/src/sentry_session.h @@ -28,7 +28,7 @@ typedef struct sentry_session_s { uint64_t duration_ms; uint64_t errors; sentry_session_status_t status; - bool init; + long init; } sentry_session_t; /** @@ -71,8 +71,8 @@ sentry_session_t *sentry__end_current_session_with_status( void sentry__record_errors_on_current_session(uint32_t error_count); /** - * Add the current session an a new envelope item to `envelope`. + * This will update a sessions `distinct_id`, which is based on the user. */ -void sentry__add_current_session_to_envelope(sentry_envelope_t *envelope); +void sentry__session_sync_user(sentry_session_t *session, sentry_value_t user); #endif diff --git a/src/sentry_string.c b/src/sentry_string.c index a13eacc96..a1581c752 100644 --- a/src/sentry_string.c +++ b/src/sentry_string.c @@ -13,10 +13,10 @@ sentry__stringbuilder_init(sentry_stringbuilder_t *sb) sb->len = 0; } -static int -append(sentry_stringbuilder_t *sb, const char *s, size_t len) +char * +sentry__stringbuilder_reserve(sentry_stringbuilder_t *sb, size_t len) { - size_t needed = sb->len + len + 1; + size_t needed = sb->len + len; if (!sb->buf || needed > sb->allocated) { size_t new_alloc_size = sb->allocated; if (new_alloc_size == 0) { @@ -27,7 +27,7 @@ append(sentry_stringbuilder_t *sb, const char *s, size_t len) } char *new_buf = sentry_malloc(new_alloc_size); if (!new_buf) { - return 1; + return NULL; } if (sb->buf) { memcpy(new_buf, sb->buf, sb->allocated); @@ -36,7 +36,17 @@ append(sentry_stringbuilder_t *sb, const char *s, size_t len) sb->buf = new_buf; sb->allocated = new_alloc_size; } - memcpy(sb->buf + sb->len, s, len); + return &sb->buf[sb->len]; +} + +static int +append(sentry_stringbuilder_t *sb, const char *s, size_t len) +{ + char *buf = sentry__stringbuilder_reserve(sb, len + 1); + if (!buf) { + return 1; + } + memcpy(buf, s, len); sb->len += len; // make sure we're always zero terminated @@ -105,6 +115,12 @@ sentry__stringbuilder_len(const sentry_stringbuilder_t *sb) return sb->len; } +void +sentry__stringbuilder_set_len(sentry_stringbuilder_t *sb, size_t len) +{ + sb->len = len; +} + char * sentry__string_clone(const char *str) { diff --git a/src/sentry_string.h b/src/sentry_string.h index c537b5e3e..feeff3678 100644 --- a/src/sentry_string.h +++ b/src/sentry_string.h @@ -73,6 +73,18 @@ void sentry__stringbuilder_cleanup(sentry_stringbuilder_t *sb); */ size_t sentry__stringbuilder_len(const sentry_stringbuilder_t *sb); +/** + * Resizes the stringbuilder buffer to make sure there is at least `len` bytes + * available at the end, and returns a pointer *to the reservation*. + */ +char *sentry__stringbuilder_reserve(sentry_stringbuilder_t *sb, size_t len); + +/** + * Sets the number of used bytes in the string builder, to be used together with + * `sentry__stringbuilder_reserve` to avoid copying from an intermediate buffer. + */ +void sentry__stringbuilder_set_len(sentry_stringbuilder_t *sb, size_t len); + /** * Duplicates a zero terminated string. */ diff --git a/src/sentry_sync.c b/src/sentry_sync.c index ade4e369e..3b8746beb 100644 --- a/src/sentry_sync.c +++ b/src/sentry_sync.c @@ -207,24 +207,7 @@ sentry__bgworker_is_done(sentry_bgworker_t *bgw) return !bgw->first_task && !sentry__atomic_fetch(&bgw->running); } -#ifdef _MSC_VER -# define THREAD_FUNCTION_API __stdcall -#else -# define THREAD_FUNCTION_API -#endif - -#if defined(__MINGW32__) && !defined(__MINGW64__) -# define UNSIGNED_MINGW unsigned -#else -# define UNSIGNED_MINGW -#endif - -// pthreads use `void *` return types, whereas windows uses `DWORD` -#ifdef SENTRY_PLATFORM_WINDOWS -static UNSIGNED_MINGW DWORD THREAD_FUNCTION_API -#else -static void * -#endif +SENTRY_THREAD_FN worker_thread(void *data) { sentry_bgworker_t *bgw = data; @@ -284,11 +267,11 @@ int sentry__bgworker_start(sentry_bgworker_t *bgw) { SENTRY_TRACE("starting background worker thread"); - sentry__atomic_fetch_and_add(&bgw->running, 1); + sentry__atomic_store(&bgw->running, 1); // this incref moves the reference into the background thread sentry__bgworker_incref(bgw); if (sentry__thread_spawn(&bgw->thread_id, &worker_thread, bgw) != 0) { - sentry__atomic_fetch_and_add(&bgw->running, -1); + sentry__atomic_store(&bgw->running, 0); sentry__bgworker_decref(bgw); return 1; } @@ -299,7 +282,7 @@ static void shutdown_task(void *task_data, void *UNUSED(state)) { sentry_bgworker_t *bgw = task_data; - sentry__atomic_fetch_and_add(&bgw->running, -1); + sentry__atomic_store(&bgw->running, 0); } int @@ -325,10 +308,11 @@ sentry__bgworker_shutdown(sentry_bgworker_t *bgw, uint64_t timeout) uint64_t now = sentry__monotonic_time(); if (now > started && now - started > timeout) { + sentry__atomic_store(&bgw->running, 0); + sentry__thread_detach(bgw->thread_id); sentry__mutex_unlock(&bgw->task_lock); SENTRY_WARN( "background thread failed to shut down cleanly within timeout"); - sentry__thread_detach(bgw->thread_id); return 1; } diff --git a/src/sentry_sync.h b/src/sentry_sync.h index d7586c8dd..5c3cbe3ef 100644 --- a/src/sentry_sync.h +++ b/src/sentry_sync.h @@ -7,6 +7,25 @@ #include #include +#ifdef _MSC_VER +# define THREAD_FUNCTION_API __stdcall +#else +# define THREAD_FUNCTION_API +#endif + +#if defined(__MINGW32__) && !defined(__MINGW64__) +# define UNSIGNED_MINGW unsigned +#else +# define UNSIGNED_MINGW +#endif + +// pthreads use `void *` return types, whereas windows uses `DWORD` +#ifdef SENTRY_PLATFORM_WINDOWS +# define SENTRY_THREAD_FN static UNSIGNED_MINGW DWORD THREAD_FUNCTION_API +#else +# define SENTRY_THREAD_FN static void * +#endif + // define a recursive mutex for all platforms #ifdef SENTRY_PLATFORM_WINDOWS # if _WIN32_WINNT >= 0x0600 @@ -200,10 +219,10 @@ typedef CONDITION_VARIABLE sentry_cond_t; we're restricted in what we can do. In particular it's possible that we would end up dead locking ourselves. While we cannot fully prevent races we have a logic here that while the signal handler is active we're - disabling our mutexes so that our signal handler can access what otherwise - would be protected by the mutex but everyone else needs to wait for the - signal handler to finish. This is not without risk because another thread - might still access what the mutex protects. + disabling our mutexes so that our signal handler can access what + otherwise would be protected by the mutex but everyone else needs to wait + for the signal handler to finish. This is not without risk because + another thread might still access what the mutex protects. We are thus taking care that whatever such mutexes protect will not make us crash under concurrent modifications. The mutexes we're likely going diff --git a/src/sentry_unix_pageallocator.c b/src/sentry_unix_pageallocator.c index 09653a60d..0226c9b09 100644 --- a/src/sentry_unix_pageallocator.c +++ b/src/sentry_unix_pageallocator.c @@ -125,7 +125,7 @@ sentry__page_allocator_alloc(size_t size) return rv; } -#if SENTRY_UNITTEST +#ifdef SENTRY_UNITTEST void sentry__page_allocator_disable(void) { diff --git a/src/sentry_unix_pageallocator.h b/src/sentry_unix_pageallocator.h index 290192bc0..efc5b68b3 100644 --- a/src/sentry_unix_pageallocator.h +++ b/src/sentry_unix_pageallocator.h @@ -21,7 +21,7 @@ void sentry__page_allocator_enable(void); */ void *sentry__page_allocator_alloc(size_t size); -#if SENTRY_UNITTEST +#ifdef SENTRY_UNITTEST /** * This disables the page allocator, which invalidates every allocation that was * done through it. Therefore it is only safe to use in unit tests diff --git a/src/sentry_utils.c b/src/sentry_utils.c index c91d406e0..8e3300871 100644 --- a/src/sentry_utils.c +++ b/src/sentry_utils.c @@ -1,13 +1,13 @@ // According to http://lua-users.org/lists/lua-l/2016-04/msg00216.html we can // use `stdtod_l` on all platforms when defining `_GNU_SOURCE`. -#define _GNU_SOURCE +#include "sentry_boot.h" -#include "sentry_utils.h" #include "sentry_alloc.h" #include "sentry_core.h" #include "sentry_string.h" #include "sentry_sync.h" +#include "sentry_utils.h" #include #include #include @@ -15,7 +15,7 @@ #include #include -#ifdef SENTRY_PLATFORM_MACOS +#ifdef SENTRY_PLATFORM_DARWIN # include #elif defined(SENTRY_PLATFORM_LINUX) && !defined(SENTRY_PLATFORM_ANDROID) # include "../vendor/stb_sprintf.h" @@ -209,6 +209,7 @@ sentry__url_cleanup(sentry_url_t *url) sentry_free(url->fragment); sentry_free(url->username); sentry_free(url->password); + memset(url, 0, sizeof(sentry_url_t)); } sentry_dsn_t * @@ -357,7 +358,7 @@ sentry__dsn_get_minidump_url(const sentry_dsn_t *dsn) char * sentry__msec_time_to_iso8601(uint64_t time) { - char buf[255]; + char buf[64]; size_t buf_len = sizeof(buf); time_t secs = time / 1000; struct tm *tm; @@ -367,6 +368,12 @@ sentry__msec_time_to_iso8601(uint64_t time) struct tm tm_buf; tm = gmtime_r(&secs, &tm_buf); #endif + // It might as well be that the `time` parameter is broken in some way and + // would create a broken `tm` that then later causes formatting issues. We + // have seen super strange timestamps in some event payloads. + if (!tm || tm->tm_year > 9000) { + return NULL; + } size_t written = strftime(buf, buf_len, "%Y-%m-%dT%H:%M:%S", tm); if (written == 0) { return NULL; @@ -487,7 +494,7 @@ sentry__snprintf_c(char *buf, size_t buf_size, const char *fmt, ...) rv = _vsnprintf_l(buf, buf_size, fmt, c_locale(), args); #elif defined(SENTRY_PLATFORM_ANDROID) || defined(SENTRY_PLATFORM_IOS) rv = vsnprintf(buf, buf_size, fmt, args); -#elif defined(SENTRY_PLATFORM_MACOS) +#elif defined(SENTRY_PLATFORM_DARWIN) rv = vsnprintf_l(buf, buf_size, c_locale(), fmt, args); #else rv = stbsp_vsnprintf(buf, buf_size, fmt, args); diff --git a/src/sentry_utils.h b/src/sentry_utils.h index 27a67e1f4..cb79c08f1 100644 --- a/src/sentry_utils.h +++ b/src/sentry_utils.h @@ -3,6 +3,10 @@ #include "sentry_boot.h" +#ifdef SENTRY_PLATFORM_DARWIN +# include +# include +#endif #ifdef SENTRY_PLATFORM_WINDOWS # include #else @@ -145,6 +149,25 @@ sentry__monotonic_time(void) LARGE_INTEGER qpc_counter; QueryPerformanceCounter(&qpc_counter); return qpc_counter.QuadPart * 1000 / qpc_frequency.QuadPart; +#elif defined(SENTRY_PLATFORM_DARWIN) + +// try `clock_gettime` first if available, +// fall back to `host_get_clock_service` otherwise +# if defined(MAC_OS_X_VERSION_10_12) && __has_builtin(__builtin_available) + if (__builtin_available(macOS 10.12, *)) { + struct timespec tv; + return (clock_gettime(CLOCK_MONOTONIC, &tv) == 0) + ? (uint64_t)tv.tv_sec * 1000 + tv.tv_nsec / 1000000 + : 0; + } +# endif + + clock_serv_t cclock; + mach_timespec_t mts; + host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); + clock_get_time(cclock, &mts); + mach_port_deallocate(mach_task_self(), cclock); + return (uint64_t)mts.tv_sec * 1000 + mts.tv_nsec / 1000000; #else struct timespec tv; return (clock_gettime(CLOCK_MONOTONIC, &tv) == 0) diff --git a/src/sentry_value.c b/src/sentry_value.c index 5bed6d1bc..0fb51df69 100644 --- a/src/sentry_value.c +++ b/src/sentry_value.c @@ -544,7 +544,7 @@ sentry__value_stringify(sentry_value_t value) case SENTRY_VALUE_TYPE_STRING: return sentry__string_clone(sentry_value_as_string(value)); default: { - char buf[50]; + char buf[24]; size_t written = (size_t)sentry__snprintf_c( buf, sizeof(buf), "%g", sentry_value_as_double(value)); if (written >= sizeof(buf)) { @@ -611,13 +611,19 @@ sentry__value_append_bounded(sentry_value_t value, sentry_value_t v, size_t max) // move 99 items (len - 1) // from 20 - size_t to_move = max - 1; + size_t to_move = max >= 1 ? max - 1 : 0; size_t to_shift = l->len - to_move; for (size_t i = 0; i < to_shift; i++) { sentry_value_decref(l->items[i]); } - memmove(l->items, l->items + (to_shift), to_move * sizeof(l->items[0])); - l->items[max - 1] = v; + if (to_move) { + memmove(l->items, l->items + to_shift, to_move * sizeof(l->items[0])); + } + if (max >= 1) { + l->items[max - 1] = v; + } else { + sentry_value_decref(v); + } l->len = max; return 0; @@ -911,6 +917,9 @@ sentry_value_to_msgpack(sentry_value_t value, size_t *size_out) sentry_value_t sentry__value_new_string_owned(char *s) { + if (!s) { + return sentry_value_new_null(); + } sentry_value_t rv = new_thing_value(s, THING_TYPE_STRING | THING_TYPE_FROZEN); if (sentry_value_is_null(rv)) { @@ -931,7 +940,7 @@ sentry__value_new_string_from_wstr(const wchar_t *s) sentry_value_t sentry__value_new_addr(uint64_t addr) { - char buf[100]; + char buf[32]; size_t written = (size_t)snprintf( buf, sizeof(buf), "0x%llx", (unsigned long long)addr); if (written >= sizeof(buf)) { @@ -994,6 +1003,8 @@ sentry_value_new_event(void) sentry__value_new_string_owned( sentry__msec_time_to_iso8601(sentry__msec_time()))); + sentry_value_set_by_key(rv, "platform", sentry_value_new_string("native")); + return rv; } @@ -1033,8 +1044,38 @@ sentry_value_new_breadcrumb(const char *type, const char *message) return rv; } -void -sentry_event_value_add_stacktrace(sentry_value_t event, void **ips, size_t len) +sentry_value_t +sentry_value_new_exception(const char *type, const char *value) +{ + sentry_value_t exc = sentry_value_new_object(); + sentry_value_set_by_key(exc, "type", sentry_value_new_string(type)); + sentry_value_set_by_key(exc, "value", sentry_value_new_string(value)); + return exc; +} + +sentry_value_t +sentry_value_new_thread(uint64_t id, const char *name) +{ + sentry_value_t thread = sentry_value_new_object(); + + // NOTE: values end up as JSON, which has no support for `u64`. + char buf[20 + 1]; + size_t written + = (size_t)snprintf(buf, sizeof(buf), "%llu", (unsigned long long)id); + if (written < sizeof(buf)) { + buf[written] = '\0'; + sentry_value_set_by_key(thread, "id", sentry_value_new_string(buf)); + } + + if (name) { + sentry_value_set_by_key(thread, "name", sentry_value_new_string(name)); + } + + return thread; +} + +sentry_value_t +sentry_value_new_stacktrace(void **ips, size_t len) { void *walked_backtrace[256]; @@ -1055,14 +1096,56 @@ sentry_event_value_add_stacktrace(sentry_value_t event, void **ips, size_t len) sentry_value_t stacktrace = sentry_value_new_object(); sentry_value_set_by_key(stacktrace, "frames", frames); - sentry_value_t thread = sentry_value_new_object(); - sentry_value_set_by_key(thread, "stacktrace", stacktrace); + return stacktrace; +} + +static sentry_value_t +sentry__get_or_insert_values_list(sentry_value_t parent, const char *key) +{ + sentry_value_t obj = sentry_value_get_by_key(parent, key); + if (sentry_value_is_null(obj)) { + obj = sentry_value_new_object(); + sentry_value_set_by_key(parent, key, obj); + } + + sentry_value_type_t type = sentry_value_get_type(obj); + sentry_value_t values = sentry_value_new_null(); + if (type == SENTRY_VALUE_TYPE_OBJECT) { + values = sentry_value_get_by_key(obj, "values"); + if (sentry_value_is_null(values)) { + values = sentry_value_new_list(); + sentry_value_set_by_key(obj, "values", values); + } + } else if (type == SENTRY_VALUE_TYPE_LIST) { + values = obj; + } + + return values; +} + +void +sentry_event_add_exception(sentry_value_t event, sentry_value_t exception) +{ + sentry_value_t exceptions + = sentry__get_or_insert_values_list(event, "exception"); + sentry_value_append(exceptions, exception); +} + +void +sentry_event_add_thread(sentry_value_t event, sentry_value_t thread) +{ + sentry_value_t threads + = sentry__get_or_insert_values_list(event, "threads"); + sentry_value_append(threads, thread); +} - sentry_value_t values = sentry_value_new_list(); - sentry_value_append(values, thread); +void +sentry_event_value_add_stacktrace(sentry_value_t event, void **ips, size_t len) +{ + sentry_value_t stacktrace = sentry_value_new_stacktrace(ips, len); - sentry_value_t threads = sentry_value_new_object(); - sentry_value_set_by_key(threads, "values", values); + sentry_value_t thread = sentry_value_new_object(); + sentry_value_set_by_key(thread, "stacktrace", stacktrace); - sentry_value_set_by_key(event, "threads", threads); + sentry_event_add_thread(event, thread); } diff --git a/src/transports/sentry_disk_transport.c b/src/transports/sentry_disk_transport.c index 2ccf448d6..3f0d03329 100644 --- a/src/transports/sentry_disk_transport.c +++ b/src/transports/sentry_disk_transport.c @@ -7,7 +7,7 @@ #include "sentry_string.h" static void -send_envelope(sentry_envelope_t *envelope, void *state) +send_envelope_disk_transport(sentry_envelope_t *envelope, void *state) { const sentry_run_t *run = state; @@ -18,7 +18,8 @@ send_envelope(sentry_envelope_t *envelope, void *state) sentry_transport_t * sentry_new_disk_transport(const sentry_run_t *run) { - sentry_transport_t *transport = sentry_transport_new(send_envelope); + sentry_transport_t *transport + = sentry_transport_new(send_envelope_disk_transport); if (!transport) { return NULL; } diff --git a/src/transports/sentry_function_transport.c b/src/transports/sentry_function_transport.c index d51e29e05..57aa14db2 100644 --- a/src/transports/sentry_function_transport.c +++ b/src/transports/sentry_function_transport.c @@ -10,7 +10,7 @@ struct transport_state { }; static void -send_envelope(sentry_envelope_t *envelope, void *_state) +send_envelope_function_transport(sentry_envelope_t *envelope, void *_state) { struct transport_state *state = _state; state->func(envelope, state->data); @@ -29,7 +29,8 @@ sentry_new_function_transport( state->func = func; state->data = data; - sentry_transport_t *transport = sentry_transport_new(send_envelope); + sentry_transport_t *transport + = sentry_transport_new(send_envelope_function_transport); if (!transport) { sentry_free(state); return NULL; diff --git a/src/transports/sentry_transport_winhttp.c b/src/transports/sentry_transport_winhttp.c index 516d0ec16..afb6e7c52 100644 --- a/src/transports/sentry_transport_winhttp.c +++ b/src/transports/sentry_transport_winhttp.c @@ -20,6 +20,7 @@ typedef struct { sentry_rate_limiter_t *ratelimiter; HINTERNET session; HINTERNET connect; + HINTERNET request; bool debug; } winhttp_bgworker_state_t; @@ -110,7 +111,30 @@ static int sentry__winhttp_transport_shutdown(uint64_t timeout, void *transport_state) { sentry_bgworker_t *bgworker = (sentry_bgworker_t *)transport_state; - return sentry__bgworker_shutdown(bgworker, timeout); + winhttp_bgworker_state_t *state = sentry__bgworker_get_state(bgworker); + + int rv = sentry__bgworker_shutdown(bgworker, timeout); + if (rv != 0) { + // Seems like some requests are taking too long/hanging + // Just close them to make sure the background thread is exiting. + if (state->connect) { + WinHttpCloseHandle(state->connect); + state->connect = NULL; + } + + // NOTE: We need to close the session before closing the request. + // This will cancel all other requests which might be queued as well. + if (state->session) { + WinHttpCloseHandle(state->session); + state->session = NULL; + } + if (state->request) { + WinHttpCloseHandle(state->request); + state->request = NULL; + } + } + + return rv; } static void @@ -129,7 +153,6 @@ sentry__winhttp_send_task(void *_envelope, void *_state) wchar_t *url = sentry__string_to_wstr(req->url); wchar_t *headers = NULL; - HINTERNET request = NULL; URL_COMPONENTS url_components; wchar_t hostname[128]; @@ -152,10 +175,10 @@ sentry__winhttp_send_task(void *_envelope, void *_state) } bool is_secure = strstr(req->url, "https") == req->url; - request = WinHttpOpenRequest(state->connect, L"POST", + state->request = WinHttpOpenRequest(state->connect, L"POST", url_components.lpszUrlPath, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, is_secure ? WINHTTP_FLAG_SECURE : 0); - if (!request) { + if (!state->request) { SENTRY_WARNF( "`WinHttpOpenRequest` failed with code `%d`", GetLastError()); goto exit; @@ -178,16 +201,16 @@ sentry__winhttp_send_task(void *_envelope, void *_state) SENTRY_TRACEF( "sending request using winhttp to \"%s\":\n%S", req->url, headers); - if (WinHttpSendRequest(request, headers, (DWORD)-1, (LPVOID)req->body, - (DWORD)req->body_len, (DWORD)req->body_len, 0)) { - WinHttpReceiveResponse(request, NULL); + if (WinHttpSendRequest(state->request, headers, (DWORD)-1, + (LPVOID)req->body, (DWORD)req->body_len, (DWORD)req->body_len, 0)) { + WinHttpReceiveResponse(state->request, NULL); if (state->debug) { // this is basically the example from: // https://docs.microsoft.com/en-us/windows/win32/api/winhttp/nf-winhttp-winhttpqueryheaders#examples DWORD dwSize = 0; LPVOID lpOutBuffer = NULL; - WinHttpQueryHeaders(request, WINHTTP_QUERY_RAW_HEADERS_CRLF, + WinHttpQueryHeaders(state->request, WINHTTP_QUERY_RAW_HEADERS_CRLF, WINHTTP_HEADER_NAME_BY_INDEX, NULL, &dwSize, WINHTTP_NO_HEADER_INDEX); @@ -197,7 +220,7 @@ sentry__winhttp_send_task(void *_envelope, void *_state) // Now, use WinHttpQueryHeaders to retrieve the header. if (lpOutBuffer - && WinHttpQueryHeaders(request, + && WinHttpQueryHeaders(state->request, WINHTTP_QUERY_RAW_HEADERS_CRLF, WINHTTP_HEADER_NAME_BY_INDEX, lpOutBuffer, &dwSize, WINHTTP_NO_HEADER_INDEX)) { @@ -211,7 +234,7 @@ sentry__winhttp_send_task(void *_envelope, void *_state) // lets just assume we won’t have headers > 2k wchar_t buf[2048]; DWORD buf_size = sizeof(buf); - if (WinHttpQueryHeaders(request, WINHTTP_QUERY_CUSTOM, + if (WinHttpQueryHeaders(state->request, WINHTTP_QUERY_CUSTOM, L"x-sentry-rate-limits", buf, &buf_size, WINHTTP_NO_HEADER_INDEX)) { char *h = sentry__string_from_wstr(buf); @@ -219,7 +242,7 @@ sentry__winhttp_send_task(void *_envelope, void *_state) sentry__rate_limiter_update_from_header(state->ratelimiter, h); sentry_free(h); } - } else if (WinHttpQueryHeaders(request, WINHTTP_QUERY_CUSTOM, + } else if (WinHttpQueryHeaders(state->request, WINHTTP_QUERY_CUSTOM, L"retry-after", buf, &buf_size, WINHTTP_NO_HEADER_INDEX)) { char *h = sentry__string_from_wstr(buf); @@ -238,7 +261,9 @@ sentry__winhttp_send_task(void *_envelope, void *_state) SENTRY_TRACEF("request handled in %llums", now - started); exit: - if (request) { + if (state->request) { + HINTERNET request = state->request; + state->request = NULL; WinHttpCloseHandle(request); } sentry_free(url); diff --git a/src/unwinder/sentry_unwinder_libbacktrace.c b/src/unwinder/sentry_unwinder_libbacktrace.c index 7b6261b1c..2c5c3b20b 100644 --- a/src/unwinder/sentry_unwinder_libbacktrace.c +++ b/src/unwinder/sentry_unwinder_libbacktrace.c @@ -1,27 +1,25 @@ #include "sentry_boot.h" -#if defined(SENTRY_PLATFORM_MACOS) || defined(__GLIBC__) +#if defined(SENTRY_PLATFORM_DARWIN) || defined(__GLIBC__) # include #endif -#ifndef __has_builtin -# define __has_builtin(x) 0 -#endif - size_t sentry__unwind_stack_libbacktrace( void *addr, const sentry_ucontext_t *uctx, void **ptrs, size_t max_frames) { if (addr) { -#if defined(SENTRY_PLATFORM_MACOS) && __has_builtin(__builtin_available) - if (__builtin_available(macOS 10.14, *)) +#if defined(SENTRY_PLATFORM_MACOS) && defined(MAC_OS_X_VERSION_10_14) \ + && __has_builtin(__builtin_available) + if (__builtin_available(macOS 10.14, *)) { return (size_t)backtrace_from_fp(addr, ptrs, (int)max_frames); + } #endif return 0; } else if (uctx) { return 0; } -#if defined(SENTRY_PLATFORM_MACOS) || defined(__GLIBC__) +#if defined(SENTRY_PLATFORM_DARWIN) || defined(__GLIBC__) return (size_t)backtrace(ptrs, (int)max_frames); #else (void)ptrs; diff --git a/tests/__init__.py b/tests/__init__.py index a995441a0..02f614664 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -20,7 +20,13 @@ def make_dsn(httpserver, auth="uiaeosnrtdy", id=123456): # after a timeout of 2 seconds, falling back to the ipv4 loopback instead. host = url.netloc.replace("localhost", "127.0.0.1") return urllib.parse.urlunsplit( - (url.scheme, "{}@{}".format(auth, host), url.path, url.query, url.fragment,) + ( + url.scheme, + "{}@{}".format(auth, host), + url.path, + url.query, + url.fragment, + ) ) @@ -230,4 +236,7 @@ def deserialize( def __repr__(self): # type: (...) -> str - return "" % (self.headers, self.payload,) + return "" % ( + self.headers, + self.payload, + ) diff --git a/tests/assertions.py b/tests/assertions.py index c5b4563cc..2be06c18f 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -44,8 +44,10 @@ def assert_meta(envelope, release="test-example-release", integration=None): } expected_sdk = { "name": "sentry.native", - "version": "0.4.8", - "packages": [{"name": "github:getsentry/sentry-native", "version": "0.4.8"},], + "version": "0.4.12", + "packages": [ + {"name": "github:getsentry/sentry-native", "version": "0.4.12"}, + ], } if not is_android: if sys.platform == "win32": @@ -163,8 +165,7 @@ def assert_exception(envelope): "type": "ParseIntError", "value": "invalid digit found in string", } - expected = {"exception": {"values": [exception]}} - assert matches(event, expected) + assert matches(event["exception"]["values"][0], exception) assert_timestamp(event["timestamp"]) diff --git a/tests/cmake.py b/tests/cmake.py index f199cf2c4..c59ba6db8 100644 --- a/tests/cmake.py +++ b/tests/cmake.py @@ -76,7 +76,13 @@ def destroy(self): ] if len(coverage_dirs) > 0: subprocess.run( - ["kcov", "--clean", "--merge", coveragedir, *coverage_dirs,] + [ + "kcov", + "--clean", + "--merge", + coveragedir, + *coverage_dirs, + ] ) @@ -129,6 +135,8 @@ def cmake(cwd, targets, options=None): configcmd.append("-DSENTRY_BUILD_FORCE32=ON") if "asan" in os.environ.get("RUN_ANALYZER", ""): configcmd.append("-DWITH_ASAN_OPTION=ON") + if "tsan" in os.environ.get("RUN_ANALYZER", ""): + configcmd.append("-DWITH_TSAN_OPTION=ON") # we have to set `-Werror` for this cmake invocation only, otherwise # completely unrelated things will break @@ -145,6 +153,8 @@ def cmake(cwd, targets, options=None): flags = "-fprofile-instr-generate -fcoverage-mapping" configcmd.append("-DCMAKE_C_FLAGS='{}'".format(flags)) configcmd.append("-DCMAKE_CXX_FLAGS='{}'".format(flags)) + if "CMAKE_DEFINES" in os.environ: + configcmd.extend(os.environ.get("CMAKE_DEFINES").split()) env = dict(os.environ) env["CFLAGS"] = env["CXXFLAGS"] = " ".join(cflags) @@ -159,9 +169,10 @@ def cmake(cwd, targets, options=None): # CodeChecker invocations and options are documented here: # https://github.com/Ericsson/codechecker/blob/master/docs/analyzer/user_guide.md - buildcmd = [*cmake, "--build", ".", "--parallel"] + buildcmd = [*cmake, "--build", "."] for target in targets: buildcmd.extend(["--target", target]) + buildcmd.append("--parallel") if "code-checker" in os.environ.get("RUN_ANALYZER", ""): buildcmd = [ "CodeChecker", diff --git a/tests/requirements.txt b/tests/requirements.txt index 892a12bc4..fcbf849f6 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,3 +1,3 @@ -black==19.10b0 -pytest==5.4.1 -pytest-httpserver==0.3.4 +black==21.9b0 +pytest==6.2.5 +pytest-httpserver==1.0.1 diff --git a/tests/test_integration_crashpad.py b/tests/test_integration_crashpad.py index 8358bed7d..7eb0ca2c2 100644 --- a/tests/test_integration_crashpad.py +++ b/tests/test_integration_crashpad.py @@ -29,6 +29,23 @@ def test_crashpad_capture(cmake, httpserver): assert len(httpserver.log) == 2 +def test_crashpad_reinstall(cmake, httpserver): + tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "crashpad"}) + + env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver)) + httpserver.expect_oneshot_request("/api/123456/minidump/").respond_with_data("OK") + + with httpserver.wait(timeout=10) as waiting: + child = run(tmp_path, "sentry_example", ["log", "reinstall", "crash"], env=env) + assert child.returncode # well, its a crash after all + + assert waiting.result + + run(tmp_path, "sentry_example", ["log", "no-setup"], check=True, env=env) + + assert len(httpserver.log) == 1 + + def test_crashpad_crash(cmake, httpserver): tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "crashpad"}) @@ -67,6 +84,9 @@ def test_crashpad_crash(cmake, httpserver): assert_crashpad_upload(multipart) +@pytest.mark.skipif( + sys.platform == "linux", reason="linux clears the signal handlers on shutdown" +) def test_crashpad_crash_after_shutdown(cmake, httpserver): tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "crashpad"}) @@ -75,7 +95,10 @@ def test_crashpad_crash_after_shutdown(cmake, httpserver): with httpserver.wait(timeout=10) as waiting: child = run( - tmp_path, "sentry_example", ["log", "crash-after-shutdown"], env=env, + tmp_path, + "sentry_example", + ["log", "crash-after-shutdown"], + env=env, ) assert child.returncode # well, its a crash after all diff --git a/tests/test_integration_http.py b/tests/test_integration_http.py index c02d03312..ae640ec86 100644 --- a/tests/test_integration_http.py +++ b/tests/test_integration_http.py @@ -21,16 +21,15 @@ pytestmark = pytest.mark.skipif(not has_http, reason="tests need http") -auth_header = ( - "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.4.8" -) +auth_header = "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.4.12" def test_capture_http(cmake, httpserver): tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "none"}) httpserver.expect_oneshot_request( - "/api/123456/envelope/", headers={"x-sentry-auth": auth_header}, + "/api/123456/envelope/", + headers={"x-sentry-auth": auth_header}, ).respond_with_data("OK") env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver), SENTRY_RELEASE="🤮🚀") @@ -57,7 +56,8 @@ def test_session_http(cmake, httpserver): tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "none"}) httpserver.expect_request( - "/api/123456/envelope/", headers={"x-sentry-auth": auth_header}, + "/api/123456/envelope/", + headers={"x-sentry-auth": auth_header}, ).respond_with_data("OK") env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver)) @@ -70,7 +70,11 @@ def test_session_http(cmake, httpserver): env=env, ) run( - tmp_path, "sentry_example", ["log", "start-session"], check=True, env=env, + tmp_path, + "sentry_example", + ["log", "start-session"], + check=True, + env=env, ) assert len(httpserver.log) == 1 @@ -84,7 +88,8 @@ def test_capture_and_session_http(cmake, httpserver): tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "none"}) httpserver.expect_request( - "/api/123456/envelope/", headers={"x-sentry-auth": auth_header}, + "/api/123456/envelope/", + headers={"x-sentry-auth": auth_header}, ).respond_with_data("OK") env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver)) @@ -112,14 +117,15 @@ def test_exception_and_session_http(cmake, httpserver): tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "none"}) httpserver.expect_request( - "/api/123456/envelope/", headers={"x-sentry-auth": auth_header}, + "/api/123456/envelope/", + headers={"x-sentry-auth": auth_header}, ).respond_with_data("OK") env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver)) run( tmp_path, "sentry_example", - ["log", "start-session", "capture-exception"], + ["log", "start-session", "capture-exception", "add-stacktrace"], check=True, env=env, ) @@ -129,6 +135,7 @@ def test_exception_and_session_http(cmake, httpserver): envelope = Envelope.deserialize(output) assert_exception(envelope) + assert_stacktrace(envelope, inside_exception=True) assert_session(envelope, {"init": True, "status": "ok", "errors": 1}) output = httpserver.log[1][0].get_data() @@ -138,10 +145,14 @@ def test_exception_and_session_http(cmake, httpserver): @pytest.mark.skipif(not has_files, reason="test needs a local filesystem") def test_abnormal_session(cmake, httpserver): - tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "none"},) + tmp_path = cmake( + ["sentry_example"], + {"SENTRY_BACKEND": "none"}, + ) httpserver.expect_request( - "/api/123456/envelope/", headers={"x-sentry-auth": auth_header}, + "/api/123456/envelope/", + headers={"x-sentry-auth": auth_header}, ).respond_with_data("OK") env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver)) @@ -167,7 +178,11 @@ def test_abnormal_session(cmake, httpserver): session_file.write(session) run( - tmp_path, "sentry_example", ["log", "no-setup"], check=True, env=env, + tmp_path, + "sentry_example", + ["log", "no-setup"], + check=True, + env=env, ) assert len(httpserver.log) == 2 @@ -187,7 +202,8 @@ def test_inproc_crash_http(cmake, httpserver): tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "inproc"}) httpserver.expect_request( - "/api/123456/envelope/", headers={"x-sentry-auth": auth_header}, + "/api/123456/envelope/", + headers={"x-sentry-auth": auth_header}, ).respond_with_data("OK") env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver)) @@ -200,7 +216,11 @@ def test_inproc_crash_http(cmake, httpserver): assert child.returncode # well, its a crash after all run( - tmp_path, "sentry_example", ["log", "no-setup"], check=True, env=env, + tmp_path, + "sentry_example", + ["log", "no-setup"], + check=True, + env=env, ) assert len(httpserver.log) == 1 @@ -215,11 +235,40 @@ def test_inproc_crash_http(cmake, httpserver): assert_crash(envelope) +def test_inproc_reinstall(cmake, httpserver): + tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "inproc"}) + + env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver)) + httpserver.expect_request( + "/api/123456/envelope/", + headers={"x-sentry-auth": auth_header}, + ).respond_with_data("OK") + + child = run( + tmp_path, + "sentry_example", + ["log", "reinstall", "crash"], + env=env, + ) + assert child.returncode # well, its a crash after all + + run( + tmp_path, + "sentry_example", + ["log", "no-setup"], + check=True, + env=env, + ) + + assert len(httpserver.log) == 1 + + def test_inproc_dump_inflight(cmake, httpserver): tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "inproc"}) httpserver.expect_request( - "/api/123456/envelope/", headers={"x-sentry-auth": auth_header}, + "/api/123456/envelope/", + headers={"x-sentry-auth": auth_header}, ).respond_with_data("OK") env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver)) @@ -239,7 +288,8 @@ def test_breakpad_crash_http(cmake, httpserver): tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "breakpad"}) httpserver.expect_request( - "/api/123456/envelope/", headers={"x-sentry-auth": auth_header}, + "/api/123456/envelope/", + headers={"x-sentry-auth": auth_header}, ).respond_with_data("OK") env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver)) @@ -252,7 +302,11 @@ def test_breakpad_crash_http(cmake, httpserver): assert child.returncode # well, its a crash after all run( - tmp_path, "sentry_example", ["log", "no-setup"], check=True, env=env, + tmp_path, + "sentry_example", + ["log", "no-setup"], + check=True, + env=env, ) assert len(httpserver.log) == 1 @@ -267,12 +321,42 @@ def test_breakpad_crash_http(cmake, httpserver): assert_minidump(envelope) +@pytest.mark.skipif(not has_breakpad, reason="test needs breakpad backend") +def test_breakpad_reinstall(cmake, httpserver): + tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "breakpad"}) + + env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver)) + httpserver.expect_request( + "/api/123456/envelope/", + headers={"x-sentry-auth": auth_header}, + ).respond_with_data("OK") + + child = run( + tmp_path, + "sentry_example", + ["log", "reinstall", "crash"], + env=env, + ) + assert child.returncode # well, its a crash after all + + run( + tmp_path, + "sentry_example", + ["log", "no-setup"], + check=True, + env=env, + ) + + assert len(httpserver.log) == 1 + + @pytest.mark.skipif(not has_breakpad, reason="test needs breakpad backend") def test_breakpad_dump_inflight(cmake, httpserver): tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "breakpad"}) httpserver.expect_request( - "/api/123456/envelope/", headers={"x-sentry-auth": auth_header}, + "/api/123456/envelope/", + headers={"x-sentry-auth": auth_header}, ).respond_with_data("OK") env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver)) @@ -303,7 +387,8 @@ def delayed(req): return "{}" httpserver.expect_request( - "/api/123456/envelope/", headers={"x-sentry-auth": auth_header}, + "/api/123456/envelope/", + headers={"x-sentry-auth": auth_header}, ).respond_with_handler(delayed) env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver)) @@ -321,7 +406,8 @@ def delayed(req): httpserver.clear_log() httpserver.expect_request( - "/api/123456/envelope/", headers={"x-sentry-auth": auth_header}, + "/api/123456/envelope/", + headers={"x-sentry-auth": auth_header}, ).respond_with_data("OK") run(tmp_path, "sentry_example", ["log", "no-setup"], check=True, env=env) diff --git a/tests/test_integration_stdout.py b/tests/test_integration_stdout.py index 708e18f90..8998fefba 100644 --- a/tests/test_integration_stdout.py +++ b/tests/test_integration_stdout.py @@ -20,7 +20,11 @@ def test_capture_stdout(cmake): tmp_path = cmake( - ["sentry_example"], {"SENTRY_BACKEND": "none", "SENTRY_TRANSPORT": "none",}, + ["sentry_example"], + { + "SENTRY_BACKEND": "none", + "SENTRY_TRANSPORT": "none", + }, ) output = check_output( @@ -43,7 +47,8 @@ def test_multi_process(cmake): # NOTE: It would have been nice to do *everything* in a unicode-named # directory, but apparently cmake does not like that either. tmp_path = cmake( - ["sentry_example"], {"SENTRY_BACKEND": "none", "SENTRY_TRANSPORT": "none"}, + ["sentry_example"], + {"SENTRY_BACKEND": "none", "SENTRY_TRANSPORT": "none"}, ) cwd = tmp_path.joinpath("unicode ❤️ Юля") @@ -86,7 +91,8 @@ def test_multi_process(cmake): def test_inproc_crash_stdout(cmake): tmp_path = cmake( - ["sentry_example"], {"SENTRY_BACKEND": "inproc", "SENTRY_TRANSPORT": "none"}, + ["sentry_example"], + {"SENTRY_BACKEND": "inproc", "SENTRY_TRANSPORT": "none"}, ) child = run(tmp_path, "sentry_example", ["attachment", "crash"]) @@ -112,7 +118,8 @@ def test_inproc_crash_stdout(cmake): @pytest.mark.skipif(not has_breakpad, reason="test needs breakpad backend") def test_breakpad_crash_stdout(cmake): tmp_path = cmake( - ["sentry_example"], {"SENTRY_BACKEND": "breakpad", "SENTRY_TRANSPORT": "none"}, + ["sentry_example"], + {"SENTRY_BACKEND": "breakpad", "SENTRY_TRANSPORT": "none"}, ) child = run(tmp_path, "sentry_example", ["attachment", "crash"]) diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 1ed5ffd7b..86ffecaff 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -20,6 +20,7 @@ add_executable(sentry_test_unit test_attachments.c test_basic.c test_consent.c + test_concurrency.c test_envelopes.c test_failures.c test_fuzzfailures.c @@ -98,4 +99,13 @@ target_link_libraries(sentry_fuzz_json PRIVATE "$<$:rt>" ) +if(MSVC) + target_compile_options(sentry_fuzz_json PRIVATE $) +endif() + +# set static runtime if enabled +if (SENTRY_BUILD_RUNTIMESTATIC AND MSVC) + set_property(TARGET sentry_fuzz_json PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +endif() + add_test(NAME sentry_fuzz_json COMMAND sentry_fuzz_json) diff --git a/tests/unit/fuzz.c b/tests/unit/fuzz.c index 0ad1afd2d..1b79da7e5 100644 --- a/tests/unit/fuzz.c +++ b/tests/unit/fuzz.c @@ -18,10 +18,17 @@ afl-fuzz -i fuzzing-examples -o fuzzing-results -- fuzzing/sentry_fuzz_json @@ #undef NDEBUG +#ifdef _WIN32 +# define WIN32_LEAN_AND_MEAN +# define NOMINMAX +# define _CRT_SECURE_NO_WARNINGS +#endif + +#include "sentry.h" + #include #include -#include "sentry.h" #include "sentry_json.h" #include "sentry_path.h" #include "sentry_value.h" diff --git a/tests/unit/main.c b/tests/unit/main.c index e38f4d16f..fb351549f 100644 --- a/tests/unit/main.c +++ b/tests/unit/main.c @@ -2,7 +2,7 @@ #include "sentry_testsupport.h" -#define XX(Name) void CONCAT(test_sentry_, Name)(void); +#define XX(Name) SENTRY_TEST(Name); #include "tests.inc" #undef XX diff --git a/tests/unit/sentry_testsupport.h b/tests/unit/sentry_testsupport.h index 0a7b1f518..81927db1b 100644 --- a/tests/unit/sentry_testsupport.h +++ b/tests/unit/sentry_testsupport.h @@ -13,7 +13,7 @@ #include "../vendor/acutest.h" #define CONCAT(A, B) A##B -#define SENTRY_TEST(Name) void CONCAT(test_sentry_, Name)(void **UNUSED(state)) +#define SENTRY_TEST(Name) void CONCAT(test_sentry_, Name)(void) #define SKIP_TEST() (void)0 #define TEST_CHECK_STRING_EQUAL(Val, ReferenceVal) \ diff --git a/tests/unit/test_attachments.c b/tests/unit/test_attachments.c index 6a84413b3..4b3a6bbdd 100644 --- a/tests/unit/test_attachments.c +++ b/tests/unit/test_attachments.c @@ -10,7 +10,7 @@ typedef struct { } sentry_attachments_testdata_t; static void -send_envelope(const sentry_envelope_t *envelope, void *_data) +send_envelope_test_attachments(const sentry_envelope_t *envelope, void *_data) { sentry_attachments_testdata_t *data = _data; data->called += 1; @@ -33,8 +33,9 @@ SENTRY_TEST(lazy_attachments) sentry_options_t *options = sentry_options_new(); sentry_options_set_auto_session_tracking(options, false); sentry_options_set_dsn(options, "https://foo@sentry.invalid/42"); - sentry_options_set_transport( - options, sentry_new_function_transport(send_envelope, &testdata)); + sentry_options_set_transport(options, + sentry_new_function_transport( + send_envelope_test_attachments, &testdata)); sentry_options_set_release(options, "prod"); sentry_options_add_attachment(options, PREFIX ".existing-file-attachment"); @@ -82,7 +83,7 @@ SENTRY_TEST(lazy_attachments) != NULL); sentry_free(serialized); - sentry_shutdown(); + sentry_close(); sentry__path_remove(existing); sentry__path_remove(non_existing); diff --git a/tests/unit/test_basic.c b/tests/unit/test_basic.c index 1566f31ee..342ef0037 100644 --- a/tests/unit/test_basic.c +++ b/tests/unit/test_basic.c @@ -3,7 +3,7 @@ #include static void -send_envelope(const sentry_envelope_t *envelope, void *data) +send_envelope_test_basic(const sentry_envelope_t *envelope, void *data) { uint64_t *called = data; *called += 1; @@ -33,8 +33,8 @@ SENTRY_TEST(basic_function_transport) sentry_options_t *options = sentry_options_new(); sentry_options_set_dsn(options, "https://foo@sentry.invalid/42"); - sentry_options_set_transport( - options, sentry_new_function_transport(send_envelope, &called)); + sentry_options_set_transport(options, + sentry_new_function_transport(send_envelope_test_basic, &called)); sentry_options_set_release(options, "prod"); sentry_options_set_require_user_consent(options, true); sentry_init(options); @@ -57,7 +57,7 @@ SENTRY_TEST(basic_function_transport) sentry_capture_event(sentry_value_new_message_event(SENTRY_LEVEL_INFO, "root", "not captured either due to revoked consent")); - sentry_shutdown(); + sentry_close(); TEST_CHECK_INT_EQUAL(called, 2); } @@ -80,7 +80,8 @@ SENTRY_TEST(sampling_before_send) sentry_options_t *options = sentry_options_new(); sentry_options_set_dsn(options, "https://foo@sentry.invalid/42"); sentry_options_set_transport(options, - sentry_new_function_transport(send_envelope, &called_transport)); + sentry_new_function_transport( + send_envelope_test_basic, &called_transport)); sentry_options_set_before_send(options, before_send, &called_beforesend); sentry_options_set_sample_rate(options, 0.75); sentry_init(options); @@ -90,7 +91,7 @@ SENTRY_TEST(sampling_before_send) sentry_value_new_message_event(SENTRY_LEVEL_INFO, NULL, "foo")); } - sentry_shutdown(); + sentry_close(); TEST_CHECK_INT_EQUAL(called_transport, 0); // well, its random after all diff --git a/tests/unit/test_concurrency.c b/tests/unit/test_concurrency.c new file mode 100644 index 000000000..e2c1446bf --- /dev/null +++ b/tests/unit/test_concurrency.c @@ -0,0 +1,97 @@ +#include "sentry_core.h" +#include "sentry_testsupport.h" +#include +#include + +static void +send_envelope_test_concurrent(const sentry_envelope_t *envelope, void *data) +{ + sentry__atomic_fetch_and_add((long *)data, 1); + + sentry_value_t event = sentry_envelope_get_event(envelope); + if (!sentry_value_is_null(event)) { + const char *event_id = sentry_value_as_string( + sentry_value_get_by_key(event, "event_id")); + TEST_CHECK_STRING_EQUAL( + event_id, "4c035723-8638-4c3a-923f-2ab9d08b4018"); + } +} + +static void +init_framework(long *called) +{ + sentry_options_t *options = sentry_options_new(); + sentry_options_set_dsn(options, "https://foo@sentry.invalid/42"); + sentry_options_set_transport(options, + sentry_new_function_transport(send_envelope_test_concurrent, called)); + sentry_options_set_release(options, "prod"); + sentry_options_set_require_user_consent(options, false); + sentry_options_set_auto_session_tracking(options, true); + sentry_init(options); +} + +SENTRY_TEST(multiple_inits) +{ + long called = 0; + + init_framework(&called); + init_framework(&called); + + sentry_set_transaction("demo-trans"); + + sentry_capture_event(sentry_value_new_message_event( + SENTRY_LEVEL_INFO, "root", "Hello World!")); + + sentry_value_t obj = sentry_value_new_object(); + // something that is not a uuid, as this will be forcibly changed + sentry_value_set_by_key(obj, "event_id", sentry_value_new_int32(1234)); + sentry_capture_event(obj); + + sentry_close(); + sentry_close(); + + TEST_CHECK_INT_EQUAL(called, 4); +} + +SENTRY_THREAD_FN +thread_worker(void *called) +{ + init_framework(called); + + sentry_set_transaction("demo-trans"); + + sentry_capture_event(sentry_value_new_message_event( + SENTRY_LEVEL_INFO, "root", "Hello World!")); + + sentry_value_t obj = sentry_value_new_object(); + // something that is not a uuid, as this will be forcibly changed + sentry_value_set_by_key(obj, "event_id", sentry_value_new_int32(1234)); + sentry_capture_event(obj); + + return 0; +} + +SENTRY_TEST(concurrent_init) +{ + long called = 0; + +#define THREADS_NUM 10 + sentry_threadid_t threads[THREADS_NUM]; + + for (size_t i = 0; i < THREADS_NUM; i++) { + sentry__thread_init(&threads[i]); + sentry__thread_spawn(&threads[i], &thread_worker, &called); + } + for (size_t i = 0; i < THREADS_NUM; i++) { + sentry__thread_join(threads[i]); + sentry__thread_free(&threads[i]); + } + + sentry_close(); + + // 1 session, and up to 2 events per thread. + // might be less because `capture_event` races with close/init and might + // lose events. + TEST_CHECK(called >= THREADS_NUM * 1); + TEST_CHECK(called <= THREADS_NUM * 3); +} diff --git a/tests/unit/test_consent.c b/tests/unit/test_consent.c index 5f04779c8..2d1845fb2 100644 --- a/tests/unit/test_consent.c +++ b/tests/unit/test_consent.c @@ -25,19 +25,19 @@ SENTRY_TEST(basic_consent_tracking) init_consenting_sentry(); TEST_CHECK_INT_EQUAL( sentry_user_consent_get(), SENTRY_USER_CONSENT_UNKNOWN); - sentry_shutdown(); + sentry_close(); init_consenting_sentry(); sentry_user_consent_give(); TEST_CHECK_INT_EQUAL(sentry_user_consent_get(), SENTRY_USER_CONSENT_GIVEN); - sentry_shutdown(); + sentry_close(); init_consenting_sentry(); TEST_CHECK_INT_EQUAL(sentry_user_consent_get(), SENTRY_USER_CONSENT_GIVEN); sentry_user_consent_revoke(); TEST_CHECK_INT_EQUAL( sentry_user_consent_get(), SENTRY_USER_CONSENT_REVOKED); - sentry_shutdown(); + sentry_close(); init_consenting_sentry(); TEST_CHECK_INT_EQUAL( sentry_user_consent_get(), SENTRY_USER_CONSENT_REVOKED); @@ -45,11 +45,11 @@ SENTRY_TEST(basic_consent_tracking) sentry_user_consent_reset(); TEST_CHECK_INT_EQUAL( sentry_user_consent_get(), SENTRY_USER_CONSENT_UNKNOWN); - sentry_shutdown(); + sentry_close(); init_consenting_sentry(); TEST_CHECK_INT_EQUAL( sentry_user_consent_get(), SENTRY_USER_CONSENT_UNKNOWN); - sentry_shutdown(); + sentry_close(); sentry__path_remove_all(path); sentry__path_free(path); diff --git a/tests/unit/test_envelopes.c b/tests/unit/test_envelopes.c index 8c839b593..a579afa3e 100644 --- a/tests/unit/test_envelopes.c +++ b/tests/unit/test_envelopes.c @@ -134,5 +134,5 @@ SENTRY_TEST(serialize_envelope) sentry_envelope_free(envelope); sentry_free(str); - sentry_shutdown(); + sentry_close(); } diff --git a/tests/unit/test_logger.c b/tests/unit/test_logger.c index a4798d319..18948f2bf 100644 --- a/tests/unit/test_logger.c +++ b/tests/unit/test_logger.c @@ -39,12 +39,12 @@ SENTRY_TEST(custom_logger) SENTRY_WARNF("Oh this is %s", "bad"); data.assert_now = false; - sentry_shutdown(); + sentry_close(); TEST_CHECK_INT_EQUAL(data.called, 1); // *really* clear the logger instance options = sentry_options_new(); sentry_init(options); - sentry_shutdown(); + sentry_close(); } diff --git a/tests/unit/test_modulefinder.c b/tests/unit/test_modulefinder.c index 9ab1d1935..90fc31c06 100644 --- a/tests/unit/test_modulefinder.c +++ b/tests/unit/test_modulefinder.c @@ -37,12 +37,45 @@ SENTRY_TEST(module_finder) sentry_clear_modulecache(); } +SENTRY_TEST(module_addr) +{ +#if !defined(SENTRY_PLATFORM_LINUX) + SKIP_TEST(); +#else + sentry_module_t module = { 0 }; + module.num_mappings = 3; + // | | | | | | | | + // 00000 1111111111 + module.mappings[0].offset = 0; + module.mappings[0].size = 5; + module.mappings[0].addr = 10; + // here is a gap in the address space of size 10 + module.mappings[1].offset = 5; + module.mappings[1].size = 10; + module.mappings[1].addr = 25; + + void *ptr; + + ptr = sentry__module_get_addr(&module, 0, 5); + TEST_CHECK(ptr == (void *)10); + + ptr = sentry__module_get_addr(&module, 0, 6); + TEST_CHECK(ptr == NULL); // not contiguous + + ptr = sentry__module_get_addr(&module, 7, 8); + TEST_CHECK(ptr == (void *)27); + + ptr = sentry__module_get_addr(&module, 7, 9); + TEST_CHECK(ptr == NULL); // too big +#endif +} + SENTRY_TEST(procmaps_parser) { #if !defined(SENTRY_PLATFORM_LINUX) || __SIZEOF_POINTER__ != 8 SKIP_TEST(); #else - sentry_module_t mod; + sentry_parsed_module_t mod; char contents[] = "7fdb549ce000-7fdb54bb5000 r-xp 00000000 08:01 3803938 " " /lib/x86_64-linux-gnu/libc-2.27.so\n" @@ -57,8 +90,8 @@ SENTRY_TEST(procmaps_parser) read = sentry__procmaps_parse_module_line(lines, &mod); lines += read; TEST_CHECK(read); - TEST_CHECK(mod.start == (void *)0x7fdb549ce000); - TEST_CHECK(mod.end == (void *)0x7fdb54bb5000); + TEST_CHECK(mod.start == 0x7fdb549ce000); + TEST_CHECK(mod.end == 0x7fdb54bb5000); TEST_CHECK(strncmp(mod.file.ptr, "/lib/x86_64-linux-gnu/libc-2.27.so", mod.file.len) == 0); @@ -66,21 +99,21 @@ SENTRY_TEST(procmaps_parser) read = sentry__procmaps_parse_module_line(lines, &mod); lines += read; TEST_CHECK(read); - TEST_CHECK(mod.start == (void *)0x7f14753de000); - TEST_CHECK(mod.end == (void *)0x7f14755de000); + TEST_CHECK(mod.start == 0x7f14753de000); + TEST_CHECK(mod.end == 0x7f14755de000); read = sentry__procmaps_parse_module_line(lines, &mod); lines += read; TEST_CHECK(read); - TEST_CHECK(mod.start == (void *)0x7fe714493000); - TEST_CHECK(mod.end == (void *)0x7fe714494000); + TEST_CHECK(mod.start == 0x7fe714493000); + TEST_CHECK(mod.end == 0x7fe714494000); TEST_CHECK(mod.file.ptr == NULL); read = sentry__procmaps_parse_module_line(lines, &mod); lines += read; TEST_CHECK(read); - TEST_CHECK(mod.start == (void *)0x7fff8ca67000); - TEST_CHECK(mod.end == (void *)0x7fff8ca88000); + TEST_CHECK(mod.start == 0x7fff8ca67000); + TEST_CHECK(mod.end == 0x7fff8ca88000); TEST_CHECK(strncmp(mod.file.ptr, "[vdso]", mod.file.len) == 0); read = sentry__procmaps_parse_module_line(lines, &mod); @@ -98,16 +131,19 @@ SENTRY_TEST(buildid_fallback) sentry_path_t *dir = sentry__path_dir(path); sentry__path_free(path); + sentry_module_t module = { 0 }; + module.num_mappings = 1; + size_t *file_size = &module.mappings[0].size; + char **buf = (char **)&module.mappings[0].addr; + sentry_value_t with_id_val = sentry_value_new_object(); - sentry_mmap_t with_id_map; sentry_path_t *with_id_path = sentry__path_join_str(dir, "../fixtures/with-buildid.so"); - TEST_CHECK(sentry__mmap_file(&with_id_map, with_id_path->path)); + *buf = sentry__path_read_to_buffer(with_id_path, file_size); sentry__path_free(with_id_path); - TEST_CHECK( - sentry__procmaps_read_ids_from_elf(with_id_val, with_id_map.ptr)); - sentry__mmap_close(&with_id_map); + TEST_CHECK(sentry__procmaps_read_ids_from_elf(with_id_val, &module)); + sentry_free(*buf); TEST_CHECK_STRING_EQUAL( sentry_value_as_string(sentry_value_get_by_key(with_id_val, "code_id")), @@ -118,15 +154,13 @@ SENTRY_TEST(buildid_fallback) sentry_value_decref(with_id_val); sentry_value_t x86_exe_val = sentry_value_new_object(); - sentry_mmap_t x86_exe_map; sentry_path_t *x86_exe_path = sentry__path_join_str(dir, "../fixtures/sentry_example"); - TEST_CHECK(sentry__mmap_file(&x86_exe_map, x86_exe_path->path)); + *buf = sentry__path_read_to_buffer(x86_exe_path, file_size); sentry__path_free(x86_exe_path); - TEST_CHECK( - sentry__procmaps_read_ids_from_elf(x86_exe_val, x86_exe_map.ptr)); - sentry__mmap_close(&x86_exe_map); + TEST_CHECK(sentry__procmaps_read_ids_from_elf(x86_exe_val, &module)); + sentry_free(*buf); TEST_CHECK_STRING_EQUAL( sentry_value_as_string(sentry_value_get_by_key(x86_exe_val, "code_id")), @@ -137,15 +171,13 @@ SENTRY_TEST(buildid_fallback) sentry_value_decref(x86_exe_val); sentry_value_t without_id_val = sentry_value_new_object(); - sentry_mmap_t without_id_map; sentry_path_t *without_id_path = sentry__path_join_str(dir, "../fixtures/without-buildid.so"); - TEST_CHECK(sentry__mmap_file(&without_id_map, without_id_path->path)); + *buf = sentry__path_read_to_buffer(without_id_path, file_size); sentry__path_free(without_id_path); - TEST_CHECK( - sentry__procmaps_read_ids_from_elf(without_id_val, without_id_map.ptr)); - sentry__mmap_close(&without_id_map); + TEST_CHECK(sentry__procmaps_read_ids_from_elf(without_id_val, &module)); + sentry_free(*buf); TEST_CHECK(sentry_value_is_null( sentry_value_get_by_key(without_id_val, "code_id"))); @@ -155,15 +187,13 @@ SENTRY_TEST(buildid_fallback) sentry_value_decref(without_id_val); sentry_value_t x86_lib_val = sentry_value_new_object(); - sentry_mmap_t x86_lib_map; sentry_path_t *x86_lib_path = sentry__path_join_str(dir, "../fixtures/libstdc++.so"); - TEST_CHECK(sentry__mmap_file(&x86_lib_map, x86_lib_path->path)); + *buf = sentry__path_read_to_buffer(x86_lib_path, file_size); sentry__path_free(x86_lib_path); - TEST_CHECK( - sentry__procmaps_read_ids_from_elf(x86_lib_val, x86_lib_map.ptr)); - sentry__mmap_close(&x86_lib_map); + TEST_CHECK(sentry__procmaps_read_ids_from_elf(x86_lib_val, &module)); + sentry_free(*buf); TEST_CHECK( sentry_value_is_null(sentry_value_get_by_key(x86_lib_val, "code_id"))); diff --git a/tests/unit/test_mpack.c b/tests/unit/test_mpack.c index d805108eb..391a3d892 100644 --- a/tests/unit/test_mpack.c +++ b/tests/unit/test_mpack.c @@ -17,13 +17,15 @@ SENTRY_TEST(mpack_removed_tags) sentry_set_extra("int", sentry_value_new_int32(1234)); sentry_set_extra("double", sentry_value_new_double(12.34)); + sentry_options_t *options = sentry_options_new(); SENTRY_WITH_SCOPE (scope) { - sentry__scope_apply_to_event(scope, obj, SENTRY_SCOPE_NONE); + sentry__scope_apply_to_event(scope, options, obj, SENTRY_SCOPE_NONE); } size_t size; char *buf = sentry_value_to_msgpack(obj, &size); + sentry_options_free(options); sentry_value_decref(obj); sentry_free(buf); sentry__scope_cleanup(); diff --git a/tests/unit/test_session.c b/tests/unit/test_session.c index 9f92db368..5864526f1 100644 --- a/tests/unit/test_session.c +++ b/tests/unit/test_session.c @@ -78,7 +78,7 @@ SENTRY_TEST(session_basics) user, "username", sentry_value_new_string("swatinem")); sentry_set_user(user); - sentry_shutdown(); + sentry_close(); TEST_CHECK_INT_EQUAL(called, 2); } @@ -93,9 +93,12 @@ send_sampled_envelope(const sentry_envelope_t *envelope, void *data) { session_assertion_t *assertion = data; + SENTRY_DEBUG("send_sampled_envelope"); if (assertion->assert_session) { assertion->called += 1; + SENTRY_DEBUG("assertion + 1"); + TEST_CHECK_INT_EQUAL(sentry__envelope_get_item_count(envelope), 1); const sentry_envelope_item_t *item @@ -138,7 +141,7 @@ SENTRY_TEST(count_sampled_events) } assertion.assert_session = true; - sentry_shutdown(); + sentry_close(); TEST_CHECK_INT_EQUAL(assertion.called, 1); } diff --git a/tests/unit/test_uninit.c b/tests/unit/test_uninit.c index 1e08c5118..f9a294c2b 100644 --- a/tests/unit/test_uninit.c +++ b/tests/unit/test_uninit.c @@ -29,7 +29,7 @@ SENTRY_TEST(uninitialized) sentry_set_level(SENTRY_LEVEL_DEBUG); sentry_start_session(); sentry_end_session(); - sentry_shutdown(); + sentry_close(); } SENTRY_TEST(empty_transport) @@ -44,7 +44,7 @@ SENTRY_TEST(empty_transport) sentry_uuid_t id = sentry_capture_event(event); TEST_CHECK(!sentry_uuid_is_nil(&id)); - sentry_shutdown(); + sentry_close(); } SENTRY_TEST(invalid_dsn) @@ -59,7 +59,7 @@ SENTRY_TEST(invalid_dsn) sentry_uuid_t id = sentry_capture_event(event); TEST_CHECK(!sentry_uuid_is_nil(&id)); - sentry_shutdown(); + sentry_close(); } SENTRY_TEST(invalid_proxy) @@ -74,5 +74,5 @@ SENTRY_TEST(invalid_proxy) sentry_uuid_t id = sentry_capture_event(event); TEST_CHECK(!sentry_uuid_is_nil(&id)); - sentry_shutdown(); + sentry_close(); } diff --git a/tests/unit/test_utils.c b/tests/unit/test_utils.c index 7d9736b99..db36abe5d 100644 --- a/tests/unit/test_utils.c +++ b/tests/unit/test_utils.c @@ -93,11 +93,18 @@ SENTRY_TEST(dsn_parsing_invalid) sentry_dsn_t *dsn = sentry__dsn_new("http://username:password@example.com/foo/bar?x=y#z"); TEST_CHECK(!!dsn); - if (!dsn) { - return; + if (dsn) { + TEST_CHECK(!dsn->is_valid); + sentry__dsn_decref(dsn); + } + + dsn = sentry__dsn_new("=https://foo@bar.ingest.sentry.io/" + "1234567"); + TEST_CHECK(!!dsn); + if (dsn) { + TEST_CHECK(!dsn->is_valid); + sentry__dsn_decref(dsn); } - TEST_CHECK(!dsn->is_valid); - sentry__dsn_decref(dsn); } SENTRY_TEST(dsn_store_url_with_path) diff --git a/tests/unit/test_value.c b/tests/unit/test_value.c index 1863241fc..783567f40 100644 --- a/tests/unit/test_value.c +++ b/tests/unit/test_value.c @@ -477,6 +477,15 @@ SENTRY_TEST(value_collections_leak) TEST_CHECK_INT_EQUAL(sentry_value_refcount(obj), 3); + sentry_value_incref(obj); + sentry__value_append_bounded(list, obj, 1); + TEST_CHECK_INT_EQUAL(sentry_value_refcount(obj), 2); + + sentry_value_incref(obj); + sentry__value_append_bounded(list, obj, 0); + TEST_CHECK_INT_EQUAL(sentry_value_refcount(obj), 1); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(list), 0); + sentry_value_decref(list); TEST_CHECK_INT_EQUAL(sentry_value_refcount(obj), 1); diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index 079a3c4f3..eb1626160 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -5,6 +5,7 @@ XX(basic_http_request_preparation_for_event) XX(basic_http_request_preparation_for_event_with_attachment) XX(basic_http_request_preparation_for_minidump) XX(buildid_fallback) +XX(concurrent_init) XX(count_sampled_events) XX(custom_logger) XX(dsn_parsing_complete) @@ -18,9 +19,11 @@ XX(invalid_dsn) XX(invalid_proxy) XX(iso_time) XX(lazy_attachments) +XX(module_addr) XX(module_finder) XX(mpack_newlines) XX(mpack_removed_tags) +XX(multiple_inits) XX(os) XX(page_allocator) XX(path_basics) diff --git a/tests/valgrind.txt b/tests/valgrind.txt index e69de29bb..c3c0c6c5e 100644 --- a/tests/valgrind.txt +++ b/tests/valgrind.txt @@ -0,0 +1,29 @@ +{ + Checking the ELF Header of readable mapped memory + Memcheck:Addr1 + fun:is_valid_elf_header +} +{ + Reading Debug-Ids from readable mapped memory + Memcheck:Addr1 + ... + fun:sentry__procmaps_read_ids_from_elf +} +{ + Reading Debug-Ids from readable mapped memory + Memcheck:Addr2 + ... + fun:sentry__procmaps_read_ids_from_elf +} +{ + Reading Debug-Ids from readable mapped memory + Memcheck:Addr4 + ... + fun:sentry__procmaps_read_ids_from_elf +} +{ + Reading Debug-Ids from readable mapped memory + Memcheck:Addr8 + ... + fun:sentry__procmaps_read_ids_from_elf +}