Skip to content

Commit

Permalink
fix: guard against null thread handle for profiler sampling thread (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
armcknight authored Jun 20, 2024
1 parent 485dc2b commit 6fac4d6
Show file tree
Hide file tree
Showing 12 changed files with 76 additions and 43 deletions.
23 changes: 18 additions & 5 deletions Sources/Sentry/SentryBacktrace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,24 @@ namespace profiling {
}

void
enumerateBacktracesForAllThreads(const std::function<void(const Backtrace &)> &f,
const std::shared_ptr<ThreadMetadataCache> &cache)
enumerateBacktracesForThreads(const std::function<void(const Backtrace &)> &f,
const std::shared_ptr<ThreadMetadataCache> &cache,
const std::vector<std::unique_ptr<ThreadHandle>> &threads,
const std::unique_ptr<ThreadHandle> &currentThread)
{
const auto pair = ThreadHandle::allExcludingCurrent();
for (const auto &thread : pair.first) {
if (currentThread == nullptr) {
SENTRY_PROF_LOG_WARN("Current thread handle (profiler's sampling thread) was null, "
"will not attempt to gather other threads' backtraces.");
return;
}

for (const auto &thread : threads) {
if (thread == nullptr) {
SENTRY_PROF_LOG_WARN(
"Thread handle was null, will not attempt to gather its backtrace.");
continue;

Check warning on line 120 in Sources/Sentry/SentryBacktrace.cpp

View check run for this annotation

Codecov / codecov/patch

Sources/Sentry/SentryBacktrace.cpp#L118-L120

Added lines #L118 - L120 were not covered by tests
}

Backtrace bt;
// This one is probably safe to call while the thread is suspended, but
// being conservative here in case the platform time functions take any
Expand Down Expand Up @@ -156,7 +169,7 @@ namespace profiling {

bool reachedEndOfStack = false;
std::uintptr_t addresses[kMaxBacktraceDepth];
const auto depth = backtrace(*thread, *pair.second, addresses, stackBounds,
const auto depth = backtrace(*thread, *currentThread, addresses, stackBounds,
&reachedEndOfStack, kMaxBacktraceDepth, 0);

// Retrieving queue metadata *must* be done after suspending the thread,
Expand Down
13 changes: 12 additions & 1 deletion Sources/Sentry/SentrySamplingProfiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,18 @@ namespace profiling {
}

params->numSamples.fetch_add(1, std::memory_order_relaxed);
enumerateBacktracesForAllThreads(params->callback, params->cache);

const auto current = ThreadHandle::current();
if (current == nullptr) {
SENTRY_PROF_LOG_WARN(
"Current thread (the profiler's sampling thread) handle returned as null, "
"will not attempt to gather further backtraces.");
break;

Check warning on line 73 in Sources/Sentry/SentrySamplingProfiler.cpp

View check run for this annotation

Codecov / codecov/patch

Sources/Sentry/SentrySamplingProfiler.cpp#L70-L73

Added lines #L70 - L73 were not covered by tests
}

const auto threads = ThreadHandle::allExcludingCurrent();
enumerateBacktracesForThreads(
params->callback, params->cache, std::move(threads), std::move(current));
}
pthread_cleanup_pop(1);
pthread_cleanup_pop(1);
Expand Down
6 changes: 3 additions & 3 deletions Sources/Sentry/SentryThreadHandle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,15 @@ namespace profiling {
return threads;
}

std::pair<std::vector<std::unique_ptr<ThreadHandle>>, std::unique_ptr<ThreadHandle>>
std::vector<std::unique_ptr<ThreadHandle>>
ThreadHandle::allExcludingCurrent() noexcept
{
std::vector<std::unique_ptr<ThreadHandle>> threads;
mach_msg_type_number_t count;
thread_act_array_t list;
auto current = ThreadHandle::current();
if (current == nullptr) {
return std::make_pair(threads, nullptr);
return threads;
}

if (SENTRY_PROF_LOG_KERN_RETURN(task_threads(mach_task_self(), &list, &count))
Expand All @@ -95,7 +95,7 @@ namespace profiling {
}
SENTRY_PROF_LOG_KERN_RETURN(vm_deallocate(
mach_task_self(), reinterpret_cast<vm_address_t>(list), sizeof(*list) * count));
return std::make_pair(std::move(threads), std::move(current));
return threads;
}

thread::TIDType
Expand Down
8 changes: 6 additions & 2 deletions Sources/Sentry/include/SentryBacktrace.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,13 @@ namespace profiling {
*
* @param f The function to call for each entry.
* @param cache The cache used to look up thread metadata.
* @param threads A vector containing handles of all threads except the current thread.
* @param currentThread The handle of the current thread.
*/
void enumerateBacktracesForAllThreads(const std::function<void(const Backtrace &)> &f,
const std::shared_ptr<ThreadMetadataCache> &cache);
void enumerateBacktracesForThreads(const std::function<void(const Backtrace &)> &f,
const std::shared_ptr<ThreadMetadataCache> &cache,
const std::vector<std::unique_ptr<ThreadHandle>> &threads,
const std::unique_ptr<ThreadHandle> &currentThread);

} // namespace profiling
} // namespace sentry
Expand Down
10 changes: 4 additions & 6 deletions Sources/Sentry/include/SentryThreadHandle.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,13 @@ namespace profiling {
static std::vector<std::unique_ptr<ThreadHandle>> all() noexcept;

/**
* @return A pair, where the first element is a vector of handles for all of
* @return A vector of handles for all of
* the threads in the current process, excluding the current thread (the
* thread that this function is being called on), and the second element
* is a handle to the current thread.
* thread that this function is being called on).
*
* Returns ({}, nullptr) if the current thread could not be retrieved.
* @note Returns An empty vector if the current thread could not be retrieved.
*/
static std::pair<std::vector<std::unique_ptr<ThreadHandle>>, std::unique_ptr<ThreadHandle>>
allExcludingCurrent() noexcept;
static std::vector<std::unique_ptr<ThreadHandle>> allExcludingCurrent() noexcept;

/**
* @param handle The native handle to get the TID from.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,7 @@ static const struct {
const SentryCrashMonitorType type;
const char *const name;
} g_monitorTypes[] = {
#define MONITORTYPE(NAME) \
{ \
NAME, #NAME \
}
#define MONITORTYPE(NAME) { NAME, #NAME }
MONITORTYPE(SentryCrashMonitorTypeMachException),
MONITORTYPE(SentryCrashMonitorTypeSignal),
MONITORTYPE(SentryCrashMonitorTypeCPPException),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,7 @@ CPPExceptionTerminate(void)
strncpy(descriptionBuff, exc.what(), sizeof(descriptionBuff));
}
#define CATCH_VALUE(TYPE, PRINTFTYPE) \
catch (TYPE value) \
{ \
catch (TYPE value) { \

Check warning on line 138 in Sources/SentryCrash/Recording/Monitors/SentryCrashMonitor_CPPException.cpp

View check run for this annotation

Codecov / codecov/patch

Sources/SentryCrash/Recording/Monitors/SentryCrashMonitor_CPPException.cpp#L138

Added line #L138 was not covered by tests
snprintf(descriptionBuff, sizeof(descriptionBuff), "%" #PRINTFTYPE, value); \
}
CATCH_VALUE(char, d)
Expand Down
15 changes: 3 additions & 12 deletions Sources/SentryCrash/Recording/Tools/SentryCrashSignalInfo.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,7 @@ typedef struct {
const int numCodes;
} SentryCrashSignalInfo;

#define ENUM_NAME_MAPPING(A) \
{ \
A, #A \
}
#define ENUM_NAME_MAPPING(A) { A, #A }

static const SentryCrashSignalCodeInfo g_sigIllCodes[] = {
#ifdef ILL_NOOP
Expand Down Expand Up @@ -98,14 +95,8 @@ static const SentryCrashSignalCodeInfo g_sigSegVCodes[] = {
ENUM_NAME_MAPPING(SEGV_ACCERR),
};

#define SIGNAL_INFO(SIGNAL, CODES) \
{ \
SIGNAL, #SIGNAL, CODES, sizeof(CODES) / sizeof(*CODES) \
}
#define SIGNAL_INFO_NOCODES(SIGNAL) \
{ \
SIGNAL, #SIGNAL, 0, 0 \
}
#define SIGNAL_INFO(SIGNAL, CODES) { SIGNAL, #SIGNAL, CODES, sizeof(CODES) / sizeof(*CODES) }
#define SIGNAL_INFO_NOCODES(SIGNAL) { SIGNAL, #SIGNAL, 0, 0 }

static const SentryCrashSignalInfo g_fatalSignalData[] = {
SIGNAL_INFO_NOCODES(SIGABRT),
Expand Down
25 changes: 23 additions & 2 deletions Tests/SentryProfilerTests/SentryBacktraceTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ - (void)testCollectsMultiThreadBacktrace
bool foundThread1 = false, foundThread2 = false;
// Try up to 3 times.
for (int i = 0; i < 3; i++) {
enumerateBacktracesForAllThreads(
enumerateBacktracesForThreads(
[&](auto &backtrace) {
const auto thread = backtrace.threadMetadata.threadID;
if (thread == pthread_mach_thread_np(thread1)) {
Expand All @@ -230,7 +230,7 @@ - (void)testCollectsMultiThreadBacktrace
}
}
},
cache);
cache, ThreadHandle::allExcludingCurrent(), ThreadHandle::current());
if (foundThread1 && foundThread2) {
break;
}
Expand All @@ -247,6 +247,27 @@ - (void)testCollectsMultiThreadBacktrace
XCTAssertTrue(foundThread2);
}

- (void)testDoesNotCollectBacktracesWhenCurrentThreadHandleIsNull
{
pthread_t thread1, thread2;
XCTAssertEqual(
pthread_create(&thread1, nullptr, threadEntry, reinterpret_cast<void *>(bc_a)), 0);
XCTAssertEqual(
pthread_create(&thread2, nullptr, threadEntry, reinterpret_cast<void *>(bc_d)), 0);

const auto cache = std::make_shared<ThreadMetadataCache>();
enumerateBacktracesForThreads(
[&](__unused auto &backtrace) {
XCTFail("Should not attempt to collect backtraces if current thread handle is null");
},

Check warning on line 262 in Tests/SentryProfilerTests/SentryBacktraceTests.mm

View check run for this annotation

Codecov / codecov/patch

Tests/SentryProfilerTests/SentryBacktraceTests.mm#L261-L262

Added lines #L261 - L262 were not covered by tests
cache, ThreadHandle::allExcludingCurrent(), nullptr /* currentThread */);

XCTAssertEqual(pthread_cancel(thread1), 0);
XCTAssertEqual(pthread_join(thread1, nullptr), 0);
XCTAssertEqual(pthread_cancel(thread2), 0);
XCTAssertEqual(pthread_join(thread2, nullptr), 0);
}

@end

#endif
5 changes: 2 additions & 3 deletions Tests/SentryProfilerTests/SentryThreadHandleTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,8 @@ - (void)testAllExcludingCurrent
XCTAssertEqual(pthread_create(&thread2, nullptr, threadSpin, nullptr), 0);

bool foundThread1 = false, foundThread2 = false, foundCurrentThread = false;
const auto pair = ThreadHandle::allExcludingCurrent();
XCTAssertEqual(pair.second->nativeHandle(), currentMachThread());
for (const auto &thread : pair.first) {
const auto threads = ThreadHandle::allExcludingCurrent();
for (const auto &thread : threads) {
const auto pt = pthread_from_mach_thread_np(thread->nativeHandle());
if (pthread_equal(pt, thread1)) {
foundThread1 = true;
Expand Down
2 changes: 1 addition & 1 deletion Tests/SentryTests/SentryCrash/CrashReport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ extension XCTestCase {

func givenStoredSentryCrashReport(resource: String) throws {
let jsonData = try jsonDataOfResource(resource: resource)
jsonData.withUnsafeBytes { ( bytes: UnsafeRawBufferPointer) -> Void in
jsonData.withUnsafeBytes { ( bytes: UnsafeRawBufferPointer) in
let pointer = bytes.bindMemory(to: Int8.self)
sentrycrashcrs_addUserReport(pointer.baseAddress, Int32(jsonData.count))
}
Expand Down
4 changes: 2 additions & 2 deletions Tests/SentryTests/SentryInterfacesTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ - (void)testTransactionEvent
@1 : @"1",
@2 : @2,
@3 : @ { @"a" : @0 },
@4 : @[ @"1", @2, @{ @"a" : @0 }, @[ @"a" ], testDate, testURL ],
@4 : @[ @"1", @2, @ { @"a" : @0 }, @[ @"a" ], testDate, testURL ],
@5 : testDate,
@6 : testURL
}
Expand All @@ -191,7 +191,7 @@ - (void)testTransactionEvent
@"2" : @2,
@"3" : @ { @"a" : @0 },
@"4" : @[
@"1", @2, @{ @"a" : @0 }, @[ @"a" ], @"2020-02-27T11:35:26.000Z",
@"1", @2, @ { @"a" : @0 }, @[ @"a" ], @"2020-02-27T11:35:26.000Z",
@"https://sentry.io"
],
@"5" : @"2020-02-27T11:35:26.000Z",
Expand Down

0 comments on commit 6fac4d6

Please sign in to comment.