Skip to content

Commit

Permalink
feat: Symbolize Stacktraces in-process (#261)
Browse files Browse the repository at this point in the history
  • Loading branch information
Swatinem authored May 28, 2020
1 parent 3fbfcd0 commit c6dd1f9
Show file tree
Hide file tree
Showing 11 changed files with 169 additions and 11 deletions.
9 changes: 6 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -224,10 +224,13 @@ target_include_directories(sentry
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/src>"
)

# Without this, dladdr can't find functions
# The modulefinder and symbolizer need these two settings, and they are exported
# as `PUBLIC`, so libraries that depend on sentry get these too:
# `-E`: To have all symbols in the dynamic symbol table.
# `--build-id`: To have a build-id in the ELF object.
# FIXME: cmake 3.13 introduced target_link_options
target_link_libraries(sentry INTERFACE
"$<$<OR:$<PLATFORM_ID:Linux>,$<PLATFORM_ID:Android>>:-Wl,--build-id=sha1>")
target_link_libraries(sentry PUBLIC
"$<$<OR:$<PLATFORM_ID:Linux>,$<PLATFORM_ID:Android>>:-Wl,-E,--build-id=sha1>")

#respect CMAKE_SYSTEM_VERSION
if(WIN32)
Expand Down
3 changes: 3 additions & 0 deletions examples/example.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,11 @@ int
main(int argc, char **argv)
{
sentry_options_t *options = sentry_options_new();

sentry_options_set_database_path(options, ".sentry-native");

sentry_options_set_symbolize_stacktraces(options, true);

sentry_options_set_environment(options, "development");
// sentry defaults this to the `SENTRY_RELEASE` env variable
if (!has_arg(argc, argv, "release-env")) {
Expand Down
19 changes: 18 additions & 1 deletion include/sentry.h
Original file line number Diff line number Diff line change
Expand Up @@ -718,7 +718,7 @@ SENTRY_API void sentry_options_set_debug(sentry_options_t *opts, int debug);
SENTRY_API int sentry_options_get_debug(const sentry_options_t *opts);

/**
* Enables or disabled user consent requirements for uploads.
* Enables or disables user consent requirements for uploads.
*
* This disables uploads until the user has given the consent to the SDK.
* Consent itself is given with `sentry_user_consent_give` and
Expand All @@ -733,6 +733,23 @@ SENTRY_API void sentry_options_set_require_user_consent(
SENTRY_API int sentry_options_get_require_user_consent(
const sentry_options_t *opts);

/**
* Enables or disables on-device symbolication of stack traces.
*
* This feature can have a performance impact, and is enabled by default on
* Android. It is usually only needed when it is not possible to provide debug
* information files for system libraries which are needed for serverside
* symbolication.
*/
SENTRY_API void sentry_options_set_symbolize_stacktraces(
sentry_options_t *opts, int val);

/**
* Returns true if on-device symbolication of stack traces is enabled.
*/
SENTRY_API int sentry_options_get_symbolize_stacktraces(
const sentry_options_t *opts);

/**
* Adds a new attachment to be sent along.
*
Expand Down
6 changes: 5 additions & 1 deletion src/sentry_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,11 @@ sentry_capture_event(sentry_value_t event)

SENTRY_WITH_SCOPE (scope) {
SENTRY_TRACE("merging scope into event");
sentry__scope_apply_to_event(scope, event, SENTRY_SCOPE_ALL);
sentry_scope_mode_t mode = SENTRY_SCOPE_ALL;
if (!opts->symbolize_stacktraces) {
mode &= ~SENTRY_SCOPE_STACKTRACES;
}
sentry__scope_apply_to_event(scope, event, mode);
}

if (opts->before_send_func) {
Expand Down
18 changes: 18 additions & 0 deletions src/sentry_options.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ sentry_options_new(void)
#endif
opts->user_consent = SENTRY_USER_CONSENT_UNKNOWN;
opts->system_crash_reporter_enabled = false;
opts->symbolize_stacktraces =
#ifdef SENTRY_PLATFORM_ANDROID
true;
#else
false;
#endif
opts->backend = sentry__backend_new();
opts->transport = sentry__transport_new_default();
opts->sample_rate = 1.0;
Expand Down Expand Up @@ -213,6 +219,18 @@ sentry_options_get_require_user_consent(const sentry_options_t *opts)
return opts->require_user_consent;
}

void
sentry_options_set_symbolize_stacktraces(sentry_options_t *opts, int val)
{
opts->symbolize_stacktraces = !!val;
}

int
sentry_options_get_symbolize_stacktraces(const sentry_options_t *opts)
{
return opts->symbolize_stacktraces;
}

void
sentry_options_set_system_crash_reporter_enabled(
sentry_options_t *opts, int enabled)
Expand Down
1 change: 1 addition & 0 deletions src/sentry_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ typedef struct sentry_options_s {
sentry_path_t *handler_path;
bool debug;
bool require_user_consent;
bool symbolize_stacktraces;
bool system_crash_reporter_enabled;

sentry_attachment_t *attachments;
Expand Down
107 changes: 107 additions & 0 deletions src/sentry_scope.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
#include "sentry_modulefinder.h"
#include "sentry_options.h"
#include "sentry_string.h"
#include "sentry_symbolizer.h"
#include "sentry_sync.h"
#include <stdlib.h>

static bool g_scope_initialized;
static sentry_scope_t g_scope;
Expand Down Expand Up @@ -110,6 +112,107 @@ sentry__scope_flush(const sentry_scope_t *scope)
}
}

static void
sentry__foreach_stacktrace(
sentry_value_t event, void (*func)(sentry_value_t stacktrace))
{
// We have stacktraces at the following locations:
// * `exception[.values].X.stacktrace`:
// https://develop.sentry.dev/sdk/event-payloads/exception/
// * `threads[.values].X.stacktrace`:
// https://develop.sentry.dev/sdk/event-payloads/threads/

sentry_value_t exception = sentry_value_get_by_key(event, "exception");
if (sentry_value_get_type(exception) == SENTRY_VALUE_TYPE_OBJECT) {
exception = sentry_value_get_by_key(exception, "values");
}
if (sentry_value_get_type(exception) == SENTRY_VALUE_TYPE_LIST) {
size_t len = sentry_value_get_length(exception);
for (size_t i = 0; i < len; i++) {
sentry_value_t stacktrace = sentry_value_get_by_key(
sentry_value_get_by_index(exception, i), "stacktrace");
if (!sentry_value_is_null(stacktrace)) {
func(stacktrace);
}
}
}

sentry_value_t threads = sentry_value_get_by_key(event, "threads");
if (sentry_value_get_type(threads) == SENTRY_VALUE_TYPE_OBJECT) {
threads = sentry_value_get_by_key(threads, "values");
}
if (sentry_value_get_type(threads) == SENTRY_VALUE_TYPE_LIST) {
size_t len = sentry_value_get_length(threads);
for (size_t i = 0; i < len; i++) {
sentry_value_t stacktrace = sentry_value_get_by_key(
sentry_value_get_by_index(threads, i), "stacktrace");
if (!sentry_value_is_null(stacktrace)) {
func(stacktrace);
}
}
}
}

static void
sentry__symbolize_frame(const sentry_frame_info_t *info, void *data)
{
// See https://develop.sentry.dev/sdk/event-payloads/stacktrace/
sentry_value_t frame = *(sentry_value_t *)data;

if (info->symbol
&& sentry_value_is_null(sentry_value_get_by_key(frame, "function"))) {
sentry_value_set_by_key(
frame, "function", sentry_value_new_string(info->symbol));
}

if (info->object_name
&& sentry_value_is_null(sentry_value_get_by_key(frame, "package"))) {
sentry_value_set_by_key(
frame, "package", sentry_value_new_string(info->object_name));
}

if (info->symbol_addr
&& sentry_value_is_null(
sentry_value_get_by_key(frame, "symbol_addr"))) {
sentry_value_set_by_key(frame, "symbol_addr",
sentry__value_new_addr((uint64_t)info->symbol_addr));
}

if (info->load_addr
&& sentry_value_is_null(sentry_value_get_by_key(frame, "image_addr"))) {
sentry_value_set_by_key(frame, "image_addr",
sentry__value_new_addr((uint64_t)info->load_addr));
}
}

static void
sentry__symbolize_stacktrace(sentry_value_t stacktrace)
{
sentry_value_t frames = sentry_value_get_by_key(stacktrace, "frames");
if (sentry_value_get_type(frames) != SENTRY_VALUE_TYPE_LIST) {
return;
}

size_t len = sentry_value_get_length(frames);
for (size_t i = 0; i < len; i++) {
sentry_value_t frame = sentry_value_get_by_index(frames, i);

sentry_value_t addr_value
= sentry_value_get_by_key(frame, "instruction_addr");
if (sentry_value_is_null(addr_value)) {
continue;
}

// The addr is saved as a hex-number inside the value.
uint64_t addr
= (uint64_t)strtoll(sentry_value_as_string(addr_value), NULL, 0);
if (!addr) {
continue;
}
sentry__symbolize((void *)addr, sentry__symbolize_frame, &frame);
}
}

void
sentry__scope_apply_to_event(
const sentry_scope_t *scope, sentry_value_t event, sentry_scope_mode_t mode)
Expand Down Expand Up @@ -168,6 +271,10 @@ sentry__scope_apply_to_event(
}
}

if (mode & SENTRY_SCOPE_STACKTRACES) {
sentry__foreach_stacktrace(event, sentry__symbolize_stacktrace);
}

#undef PLACE_STRING
#undef IS_NULL
#undef SET
Expand Down
6 changes: 5 additions & 1 deletion src/sentry_scope.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,13 @@ typedef struct sentry_scope_s {
*/
typedef enum {
SENTRY_SCOPE_NONE = 0x0,
// Add all the breadcrumbs from the scope to the event.
SENTRY_SCOPE_BREADCRUMBS = 0x1,
// Add the module list to the event.
SENTRY_SCOPE_MODULES = 0x2,
// TODO: SENTRY_SCOPE_STACKTRACES = 0x4,
// Symbolize all the stacktraces on-device which are found in the event.
SENTRY_SCOPE_STACKTRACES = 0x4,
// All of the above.
SENTRY_SCOPE_ALL = ~0,
} sentry_scope_mode_t;

Expand Down
2 changes: 0 additions & 2 deletions src/sentry_symbolizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ typedef struct sentry_frame_info_s {
void *symbol_addr;
void *instruction_addr;
const char *symbol;
const char *filename;
const char *object_name;
uint32_t lineno;
} sentry_frame_info_t;

/**
Expand Down
8 changes: 6 additions & 2 deletions tests/assertions.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def assert_meta(envelope, release="test-example-release"):
)


def assert_stacktrace(envelope, inside_exception=False, check_size=False):
def assert_stacktrace(envelope, inside_exception=False, check_size=True):
event = envelope.get_event()

parent = event["exception"] if inside_exception else event["threads"]
Expand All @@ -59,6 +59,10 @@ def assert_stacktrace(envelope, inside_exception=False, check_size=False):
if check_size:
assert len(frames) > 0
assert all(frame["instruction_addr"].startswith("0x") for frame in frames)
assert any(
frame.get("function") is not None and frame.get("package") is not None
for frame in frames
)


def assert_breadcrumb(envelope):
Expand Down Expand Up @@ -109,4 +113,4 @@ def assert_crash(envelope):
assert matches(event, {"level": "fatal"})
# depending on the unwinder, we currently don’t get any stack frames from
# a `ucontext`
assert_stacktrace(envelope, inside_exception=True)
assert_stacktrace(envelope, inside_exception=True, check_size=False)
1 change: 0 additions & 1 deletion tests/unit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ target_include_directories(sentry_test_unit PRIVATE
target_link_libraries(sentry_test_unit PRIVATE
${SENTRY_LINK_LIBRARIES}
${SENTRY_INTERFACE_LINK_LIBRARIES}
"$<$<OR:$<PLATFORM_ID:Linux>,$<PLATFORM_ID:Android>>:-Wl,-E>"
)

if(MINGW)
Expand Down

0 comments on commit c6dd1f9

Please sign in to comment.