Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

**Features**:

- Add an opt-in memory reserve that is released when a crash is handled, so crashes are still captured after running out of memory. ([#2274](https://github.com/getsentry/sentry-native/pull/2274))

**Fixes**:

- Events captured while a crash is being handled, such as from the `on_crash` hook, are no longer dropped by `sample_rate`, matching the crash itself. ([#2269](https://github.com/getsentry/sentry-native/pull/2269))
Expand Down
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,10 @@ endif()

set(SENTRY_SDK_NAME "" CACHE STRING "The SDK name to report when sending events.")
set(SENTRY_HANDLER_STACK_SIZE 64 CACHE STRING "The stack size (in KiB) that should be reserved for the crash handler.")
set(SENTRY_CRASH_MEMORY_RESERVE 0 CACHE STRING "The memory (in KiB) that is reserved on init and released when a crash is handled. 0 disables it.")
if(NOT SENTRY_CRASH_MEMORY_RESERVE MATCHES "^[0-9]+$")
message(FATAL_ERROR "SENTRY_CRASH_MEMORY_RESERVE must be a non-negative integer")
endif()
set(SENTRY_BATCHER_BUFFER_COUNT 3 CACHE STRING "Number of 100-item telemetry buffers (2-10).")
if(NOT SENTRY_BATCHER_BUFFER_COUNT MATCHES "^[0-9]+$"
OR SENTRY_BATCHER_BUFFER_COUNT LESS 2
Expand All @@ -307,6 +311,7 @@ message(STATUS "SENTRY_LIBRARY_TYPE=${SENTRY_LIBRARY_TYPE}")
message(STATUS "SENTRY_SDK_NAME=${SENTRY_SDK_NAME}")
message(STATUS "SENTRY_INTEGRATION_PLATFORM=${SENTRY_INTEGRATION_PLATFORM}")
message(STATUS "SENTRY_HANDLER_STACK_SIZE=${SENTRY_HANDLER_STACK_SIZE}")
message(STATUS "SENTRY_CRASH_MEMORY_RESERVE=${SENTRY_CRASH_MEMORY_RESERVE}")
message(STATUS "SENTRY_BATCHER_BUFFER_COUNT=${SENTRY_BATCHER_BUFFER_COUNT}")
if (WIN32)
message(STATUS "SENTRY_THREAD_STACK_GUARANTEE_FACTOR=${SENTRY_THREAD_STACK_GUARANTEE_FACTOR}")
Expand Down Expand Up @@ -432,6 +437,7 @@ if(SENTRY_INTEGRATION_PLATFORM)
endif()

target_compile_definitions(sentry PRIVATE SENTRY_HANDLER_STACK_SIZE=${SENTRY_HANDLER_STACK_SIZE})
target_compile_definitions(sentry PRIVATE SENTRY_CRASH_MEMORY_RESERVE=${SENTRY_CRASH_MEMORY_RESERVE})
target_compile_definitions(sentry PRIVATE SENTRY_BATCHER_BUFFER_COUNT=${SENTRY_BATCHER_BUFFER_COUNT})
if(WIN32)
target_compile_definitions(sentry PRIVATE SENTRY_THREAD_STACK_GUARANTEE_FACTOR=${SENTRY_THREAD_STACK_GUARANTEE_FACTOR})
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,10 @@ using `cmake -D BUILD_SHARED_LIBS=OFF ..`.
this value can be as small as 16KiB with `breakpad` and 24KiB with `crashpad` and `native`, but we recommend 32KiB
as the lower bound on 64-bit systems. **The value should be a multiple of the page size**.

- `SENTRY_CRASH_MEMORY_RESERVE` (Default: `0`):
The size in KiB of memory reserved on `sentry_init()` and released when a crash is handled, so the crash handler
can still allocate after the application has run out of memory. `0` disables it.

- `SENTRY_THREAD_STACK_GUARANTEE_FACTOR` (Default: `10`, only for Windows):
Defines the factor by which the thread's stack reserve must exceed the specified guarantee for the handler.
_Example_: if the `SENTRY_HANDLER_STACK_SIZE` is defined as 64KiB, then the thread's stack reserve must at least have
Expand Down
11 changes: 11 additions & 0 deletions include/sentry.h
Original file line number Diff line number Diff line change
Expand Up @@ -2458,6 +2458,17 @@ SENTRY_API void sentry_options_set_crashpad_limit_stack_capture_to_sp(
SENTRY_API void sentry_options_set_max_stack_capture_size(
sentry_options_t *opts, uint32_t max_stack_capture_size);

/**
* Sets the size (in bytes) of memory that is reserved on `sentry_init` and
* released when a crash is handled, so the crash handler can still allocate
* after the application has run out of memory. `0` disables it.
*
* Defaults to the `SENTRY_CRASH_MEMORY_RESERVE` build setting (in KiB), which
* is `0`. Custom backends must release the reserve from their crash handler.
*/
SENTRY_API void sentry_options_set_crash_memory_reserve(
sentry_options_t *opts, size_t bytes);

/**
* Sets the maximum time (in milliseconds) to wait for the asynchronous
* tasks to end on shutdown before attempting a forced termination.
Expand Down
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ sentry_target_sources_cwd(sentry
sentry_client_report.h
sentry_core.c
sentry_core.h
sentry_crash_reserve.c
sentry_crash_reserve.h
sentry_cpu_relax.h
sentry_database.c
sentry_database.h
Expand Down
6 changes: 6 additions & 0 deletions src/backends/native/sentry_crash_handler.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include "sentry_alloc.h"
#include "sentry_core.h"
#include "sentry_crash_reserve.h"
#include "sentry_logger.h"
#include "sentry_os.h"
#include "sentry_sync.h"
Expand Down Expand Up @@ -336,6 +337,8 @@ crash_signal_handler(int signum, siginfo_t *info, void *context)
_exit(1);
}

sentry__crash_reserve_release();

sentry_crash_context_t *ctx = ipc->shmem;

// Fill crash context
Expand Down Expand Up @@ -1037,6 +1040,9 @@ crash_exception_filter(EXCEPTION_POINTERS *exception_info)
return EXCEPTION_CONTINUE_SEARCH;
}

// the daemon may share the application's memory budget
sentry__crash_reserve_release();

sentry_crash_context_t *ctx = ipc->shmem;

// Fill crash context
Expand Down
39 changes: 28 additions & 11 deletions src/backends/sentry_backend_breakpad.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ extern "C" {
#include "sentry_backend.h"
#include "sentry_client_report.h"
#include "sentry_core.h"
#include "sentry_crash_reserve.h"
#include "sentry_database.h"
#include "sentry_envelope.h"
#include "sentry_hint.h"
Expand Down Expand Up @@ -344,6 +345,20 @@ IsDebuggerActive()
}
#endif

// called before the minidump is written
#ifdef SENTRY_PLATFORM_WINDOWS
static bool
breakpad_backend_filter(void *UNUSED(context),
EXCEPTION_POINTERS *UNUSED(exinfo), MDRawAssertionInfo *UNUSED(assertion))
#else
static bool
breakpad_backend_filter(void *UNUSED(context))
#endif
{
sentry__crash_reserve_release();
return true;
}

static int
breakpad_backend_startup(
sentry_backend_t *backend, const sentry_options_t *options)
Expand All @@ -355,26 +370,28 @@ breakpad_backend_startup(
&& defined(SENTRY_THREAD_STACK_GUARANTEE_AUTO_INIT)
sentry__set_default_thread_stack_guarantee();
# endif
backend->data = new (std::nothrow) google_breakpad::ExceptionHandler(
current_run_folder->path_w, nullptr, breakpad_backend_callback, nullptr,
google_breakpad::ExceptionHandler::HANDLER_EXCEPTION);
backend->data = new (std::nothrow)
google_breakpad::ExceptionHandler(current_run_folder->path_w,
breakpad_backend_filter, breakpad_backend_callback, nullptr,
google_breakpad::ExceptionHandler::HANDLER_EXCEPTION);
if (backend->data) {
sentry__win32_install_sigabrt_handler(handle_sigabrt);
}
#elif defined(SENTRY_PLATFORM_MACOS)
// If process is being debugged and there are breakpoints set it will cause
// task_set_exception_ports to crash the whole process and debugger
backend->data = new (std::nothrow)
google_breakpad::ExceptionHandler(current_run_folder->path, nullptr,
breakpad_backend_callback, nullptr, !IsDebuggerActive(), nullptr);
backend->data = new (std::nothrow) google_breakpad::ExceptionHandler(
current_run_folder->path, breakpad_backend_filter,
breakpad_backend_callback, nullptr, !IsDebuggerActive(), nullptr);
#elif defined(SENTRY_PLATFORM_IOS)
backend->data = new (std::nothrow)
google_breakpad::ExceptionHandler(current_run_folder->path, nullptr,
breakpad_backend_callback, nullptr, true, nullptr);
backend->data = new (std::nothrow) google_breakpad::ExceptionHandler(
current_run_folder->path, breakpad_backend_filter,
breakpad_backend_callback, nullptr, true, nullptr);
#else
google_breakpad::MinidumpDescriptor descriptor(current_run_folder->path);
backend->data = new (std::nothrow) google_breakpad::ExceptionHandler(
descriptor, nullptr, breakpad_backend_callback, nullptr, true, -1);
backend->data = new (std::nothrow)
google_breakpad::ExceptionHandler(descriptor, breakpad_backend_filter,
breakpad_backend_callback, nullptr, true, -1);
#endif
return backend->data == nullptr;
}
Expand Down
2 changes: 2 additions & 0 deletions src/backends/sentry_backend_crashpad.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ extern "C" {
#include "sentry_backend.h"
#include "sentry_core.h"
#include "sentry_cpu_relax.h"
#include "sentry_crash_reserve.h"
#include "sentry_database.h"
#include "sentry_envelope.h"
#include "sentry_hint.h"
Expand Down Expand Up @@ -557,6 +558,7 @@ crashpad_handler(int signum, siginfo_t *info, ucontext_t *user_context)
(void)!sentry__enter_signal_handler();
# endif
sentry__enter_crash_handler();
sentry__crash_reserve_release();
// Disable logging during crash handling if the option is set
SENTRY_WITH_OPTIONS (options) {
if (!options->enable_logging_when_crashed) {
Expand Down
3 changes: 3 additions & 0 deletions src/backends/sentry_backend_inproc.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "sentry_backend.h"
#include "sentry_core.h"
#include "sentry_cpu_relax.h"
#include "sentry_crash_reserve.h"
#include "sentry_database.h"
#include "sentry_envelope.h"
#include "sentry_hint.h"
Expand Down Expand Up @@ -1358,6 +1359,8 @@ process_ucontext(const sentry_ucontext_t *uctx)
}
#endif

sentry__crash_reserve_release();

if (!g_backend_config.enable_logging_when_crashed) {
sentry__logger_disable();
}
Expand Down
5 changes: 5 additions & 0 deletions src/sentry_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "sentry_backend.h"
#include "sentry_client_report.h"
#include "sentry_core.h"
#include "sentry_crash_reserve.h"
#include "sentry_database.h"
#include "sentry_envelope.h"
#include "sentry_hint.h"
Expand Down Expand Up @@ -253,6 +254,8 @@ sentry_init(sentry_options_t *options)
}
initial_scope_prepared = true;

sentry__crash_reserve_alloc(options->crash_memory_reserve);

// and then we will start the backend, since it requires a valid run
sentry_backend_t *backend = options->backend;
if (backend && backend->startup_func) {
Expand Down Expand Up @@ -313,6 +316,7 @@ sentry_init(sentry_options_t *options)

fail:
SENTRY_WARN("`sentry_init` failed");
sentry__crash_reserve_release();
if (transport) {
sentry__transport_shutdown(transport, 0);
}
Expand Down Expand Up @@ -384,6 +388,7 @@ sentry_close(void)
SENTRY_DEBUG("shutting down backend");
options->backend->shutdown_func(options->backend);
}
sentry__crash_reserve_release();

if (options->transport) {
if (sentry__transport_shutdown(
Expand Down
96 changes: 96 additions & 0 deletions src/sentry_crash_reserve.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#include "sentry_crash_reserve.h"
#include "sentry_logger.h"

#include <string.h>

#if defined(SENTRY_PLATFORM_WINDOWS)
# include <windows.h>
# define SENTRY_CRASH_RESERVE_SUPPORTED
#elif defined(SENTRY_PLATFORM_UNIX) && !defined(SENTRY_PLATFORM_PS)
# include <sys/mman.h>
# if !defined(MAP_ANONYMOUS) && defined(SENTRY_PLATFORM_DARWIN)
# define MAP_ANONYMOUS MAP_ANON
# endif
# define SENTRY_CRASH_RESERVE_SUPPORTED
#endif

static void *volatile g_reserve = NULL;

#ifdef SENTRY_CRASH_RESERVE_SUPPORTED
static void *
swap_reserve(void *ptr)
{
# ifdef SENTRY_PLATFORM_WINDOWS
return InterlockedExchangePointer((void *volatile *)&g_reserve, ptr);
# else
return __atomic_exchange_n(&g_reserve, ptr, __ATOMIC_SEQ_CST);
# endif
}

static void *
map_memory(size_t size)
{
# ifdef SENTRY_PLATFORM_WINDOWS
return VirtualAlloc(NULL, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
# else
void *ptr = mmap(
NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
return ptr == MAP_FAILED ? NULL : ptr;
# endif
}
#endif

void
sentry__crash_reserve_alloc(size_t size)
{
sentry__crash_reserve_release();
if (size == 0) {
return;
}

#ifdef SENTRY_CRASH_RESERVE_SUPPORTED
void *ptr = map_memory(size);
if (!ptr) {
SENTRY_WARNF(
"failed to reserve %zu bytes of crash handler memory", size);
return;
}
// touch the pages so they are backed by memory
memset(ptr, 1, size);
// the size travels with the mapping
size_t *hdr = ptr;
hdr[0] = size;
hdr[1] = ~size;
swap_reserve(ptr);
SENTRY_DEBUGF("reserved %zu bytes of crash handler memory", size);
#else
SENTRY_WARN("the crash memory reserve is not supported on this platform");
#endif
}

void
sentry__crash_reserve_release(void)
{
#ifdef SENTRY_CRASH_RESERVE_SUPPORTED
void *ptr = swap_reserve(NULL);
if (!ptr) {
return;
}
# ifdef SENTRY_PLATFORM_WINDOWS
VirtualFree(ptr, 0, MEM_RELEASE);
# else
size_t *hdr = ptr;
// don't let a corrupted size unmap unrelated memory
if (hdr[1] == ~hdr[0]) {
munmap(ptr, hdr[0]);
}
# endif
#endif
}

size_t
sentry__crash_reserve_size(void)
{
void *ptr = g_reserve;
return ptr ? *(size_t *)ptr : 0;
}
22 changes: 22 additions & 0 deletions src/sentry_crash_reserve.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#ifndef SENTRY_CRASH_RESERVE_H_INCLUDED
#define SENTRY_CRASH_RESERVE_H_INCLUDED

#include "sentry_boot.h"

/**
* Reserves `size` bytes of committed memory for the crash handler, replacing
* any previous reserve.
*/
void sentry__crash_reserve_alloc(size_t size);

/**
* Releases the reserve. Safe to call from a crash handler and more than once.
*/
void sentry__crash_reserve_release(void);

/**
* Returns the size of the memory that is currently reserved, or `0`.
*/
size_t sentry__crash_reserve_size(void);

#endif
7 changes: 7 additions & 0 deletions src/sentry_options.c
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ sentry_options_new(void)
opts->strict_trace_continuation = false;
opts->crashpad_limit_stack_capture_to_sp = false;
opts->max_stack_capture_size = 0;
opts->crash_memory_reserve = (size_t)SENTRY_CRASH_MEMORY_RESERVE * 1024;
opts->cache_keep = SENTRY_CACHE_KEEP_NONE;
opts->cache_max_age = 0;
opts->cache_max_size = 0;
Expand Down Expand Up @@ -767,6 +768,12 @@ sentry_options_set_max_stack_capture_size(
opts->max_stack_capture_size = max_stack_capture_size;
}

void
sentry_options_set_crash_memory_reserve(sentry_options_t *opts, size_t bytes)
{
opts->crash_memory_reserve = bytes;
}

void
sentry_options_set_shutdown_timeout(
sentry_options_t *opts, uint64_t shutdown_timeout)
Expand Down
1 change: 1 addition & 0 deletions src/sentry_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ struct sentry_options_s {
bool strict_trace_continuation;
bool crashpad_limit_stack_capture_to_sp;
uint32_t max_stack_capture_size;
size_t crash_memory_reserve;
sentry_cache_keep_t cache_keep;

time_t cache_max_age;
Expand Down
Loading
Loading