Skip to content

Commit 14e6e39

Browse files
committed
fix: Record cached dyld images async
1 parent 79cee27 commit 14e6e39

File tree

1 file changed

+62
-127
lines changed

1 file changed

+62
-127
lines changed

Sources/SentryCrash/Recording/SentryCrashBinaryImageCache.c

Lines changed: 62 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <mach-o/dyld_images.h>
55
#include <pthread.h>
66
#include <stdio.h>
7+
#include <dispatch/dispatch.h>
78
#include <stdlib.h>
89
#include <string.h>
910
#include <unistd.h>
@@ -60,94 +61,64 @@ sentry_resetFuncForAddRemoveImage(void)
6061
# define _will_add_image()
6162
#endif // defined(SENTRY_TEST) || defined(SENTRY_TEST_CI) || defined(DEBUG)
6263

63-
typedef struct SentryCrashBinaryImageNode {
64-
SentryCrashBinaryImage image;
65-
bool available;
66-
struct SentryCrashBinaryImageNode *next;
67-
} SentryCrashBinaryImageNode;
64+
#include <stdatomic.h>
6865

69-
static SentryCrashBinaryImageNode rootNode = { 0 };
70-
static SentryCrashBinaryImageNode *tailNode = NULL;
71-
static pthread_mutex_t binaryImagesMutex = PTHREAD_MUTEX_INITIALIZER;
66+
#define MAX_DYLD_IMAGES 4096
7267

73-
static sentrycrashbic_cacheChangeCallback imageAddedCallback = NULL;
74-
static sentrycrashbic_cacheChangeCallback imageRemovedCallback = NULL;
68+
typedef struct {
69+
_Atomic(uint32_t) ready; // 0 = not published, 1 = published
70+
SentryCrashBinaryImage image;
71+
} PublishedBinaryImage;
7572

76-
static void
77-
binaryImageAdded(const struct mach_header *header, intptr_t slide)
78-
{
79-
pthread_mutex_lock(&binaryImagesMutex);
80-
if (tailNode == NULL) {
81-
pthread_mutex_unlock(&binaryImagesMutex);
73+
static PublishedBinaryImage g_images[MAX_DYLD_IMAGES];
74+
static _Atomic(uint32_t) g_next_index = 0;
75+
static _Atomic(uint32_t) g_overflowed = 0;
76+
77+
static void add_dyld_image(const struct mach_header* mh) {
78+
uint32_t idx =
79+
atomic_fetch_add_explicit(&g_next_index, 1, memory_order_relaxed);
80+
81+
if (idx >= MAX_DYLD_IMAGES) {
82+
atomic_store_explicit(&g_overflowed, 1, memory_order_relaxed);
8283
return;
8384
}
84-
pthread_mutex_unlock(&binaryImagesMutex);
85+
8586
Dl_info info;
86-
if (!dladdr(header, &info) || info.dli_fname == NULL) {
87+
if (!dladdr(mh, &info) || info.dli_fname == NULL) {
8788
return;
8889
}
8990

90-
SentryCrashBinaryImage binaryImage = { 0 };
91-
if (!sentrycrashdl_getBinaryImageForHeader(
92-
(const void *)header, info.dli_fname, &binaryImage, false)) {
93-
return;
94-
}
91+
PublishedBinaryImage* entry = &g_images[idx];
92+
sentrycrashdl_getBinaryImageForHeader(mh, info.dli_fname, &entry->image, false);
9593

96-
SentryCrashBinaryImageNode *newNode = malloc(sizeof(SentryCrashBinaryImageNode));
97-
newNode->available = true;
98-
newNode->image = binaryImage;
99-
newNode->next = NULL;
100-
_will_add_image();
101-
pthread_mutex_lock(&binaryImagesMutex);
102-
// Recheck tailNode as it could be null when
103-
// stopped from another thread.
104-
if (tailNode != NULL) {
105-
tailNode->next = newNode;
106-
tailNode = tailNode->next;
107-
} else {
108-
free(newNode);
109-
newNode = NULL;
110-
}
111-
pthread_mutex_unlock(&binaryImagesMutex);
112-
if (newNode && imageAddedCallback) {
113-
imageAddedCallback(&newNode->image);
114-
}
94+
// ---- Publish ----
95+
atomic_store_explicit(&entry->ready, 1, memory_order_release);
11596
}
11697

117-
static void
118-
binaryImageRemoved(const struct mach_header *header, intptr_t slide)
119-
{
120-
SentryCrashBinaryImageNode *nextNode = &rootNode;
121-
122-
while (nextNode != NULL) {
123-
if (nextNode->image.address == (uint64_t)header) {
124-
nextNode->available = false;
125-
if (imageRemovedCallback) {
126-
imageRemovedCallback(&nextNode->image);
127-
}
128-
break;
129-
}
130-
nextNode = nextNode->next;
131-
}
98+
static void dyld_add_image_cb(const struct mach_header* mh, intptr_t slide) {
99+
add_dyld_image(mh);
100+
}
101+
102+
void dyld_tracker_start(void) {
103+
sentry_dyld_register_func_for_add_image(dyld_add_image_cb);
132104
}
133105

134106
void
135107
sentrycrashbic_iterateOverImages(sentrycrashbic_imageIteratorCallback callback, void *context)
136108
{
137-
/**
138-
We can't use locks here because this is meant to be used during crashes,
139-
where we can't use async unsafe functions. In order to avoid potential problems,
140-
we choose an approach that doesn't remove nodes from the list.
141-
*/
142-
SentryCrashBinaryImageNode *nextNode = &rootNode;
143-
144-
// If tailNode is null it means the cache was stopped, therefore we end the iteration.
145-
// This will minimize any race condition effect without the need for locks.
146-
while (nextNode != NULL && tailNode != NULL) {
147-
if (nextNode->available) {
148-
callback(&nextNode->image, context);
109+
uint32_t count =
110+
atomic_load_explicit(&g_next_index, memory_order_acquire);
111+
112+
if (count > MAX_DYLD_IMAGES) count = MAX_DYLD_IMAGES;
113+
114+
for (uint32_t i = 0; i < count; i++) {
115+
PublishedBinaryImage* src = &g_images[i];
116+
117+
if (!atomic_load_explicit(&src->ready, memory_order_acquire)) {
118+
return; // stop at first unpublished entry
149119
}
150-
nextNode = nextNode->next;
120+
121+
callback(&src->image, context);
151122
}
152123
}
153124

@@ -170,93 +141,57 @@ sentrycrashbic_shouldAddDyld(void)
170141

171142
// Since Apple no longer includes dyld in the images listed `_dyld_image_count` and related
172143
// functions We manually include it to our cache.
173-
SentryCrashBinaryImageNode *
174-
sentrycrashbic_getDyldNode(void)
144+
void
145+
sentrycrashbic_addDyldNode(void)
175146
{
176147
const struct mach_header *header = sentryDyldHeader;
177148

178149
SentryCrashBinaryImage binaryImage = { 0 };
179150
if (!sentrycrashdl_getBinaryImageForHeader((const void *)header, "dyld", &binaryImage, false)) {
180-
return NULL;
151+
return;
181152
}
182-
183-
SentryCrashBinaryImageNode *newNode = malloc(sizeof(SentryCrashBinaryImageNode));
184-
newNode->available = true;
185-
newNode->image = binaryImage;
186-
newNode->next = NULL;
187-
188-
return newNode;
153+
add_dyld_image(header);
189154
}
190155

191156
void
192157
sentrycrashbic_startCache(void)
193158
{
194-
pthread_mutex_lock(&binaryImagesMutex);
195-
if (tailNode != NULL) {
196-
// Already initialized
197-
pthread_mutex_unlock(&binaryImagesMutex);
198-
return;
199-
}
200-
201159
if (sentrycrashbic_shouldAddDyld()) {
202160
sentrycrashdl_initialize();
203-
SentryCrashBinaryImageNode *dyldNode = sentrycrashbic_getDyldNode();
204-
tailNode = dyldNode;
205-
rootNode.next = dyldNode;
206-
} else {
207-
tailNode = &rootNode;
208-
rootNode.next = NULL;
161+
sentrycrashbic_addDyldNode();
209162
}
210-
pthread_mutex_unlock(&binaryImagesMutex);
211163

212164
// During a call to _dyld_register_func_for_add_image() the callback func is called for every
213165
// existing image
214-
sentry_dyld_register_func_for_add_image(&binaryImageAdded);
215-
sentry_dyld_register_func_for_remove_image(&binaryImageRemoved);
166+
// This must be done on a background thread to not block app launch due to the extensive use of locks
167+
// in the image added callback. The main culprit is the calls to `dladdr`.
168+
// The downside of doing this async is if there is a crash very shortly after app launch we might
169+
// not have recorded all the load addresses of images yet. We think this is an acceptible tradeoff
170+
// to not block app launch, since it's always possible to crash early in app launch before
171+
// Sentry can capture the crash.
172+
//
173+
// A future update to this code could record everything but the filename synchronously, and then
174+
// add in the image name async. However, that would still block app launch somewhat and it's not
175+
// clear that is worth the benefits.
176+
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
177+
dyld_tracker_start();
178+
});
216179
}
217180

218181
void
219182
sentrycrashbic_stopCache(void)
220183
{
221-
pthread_mutex_lock(&binaryImagesMutex);
222-
if (tailNode == NULL) {
223-
pthread_mutex_unlock(&binaryImagesMutex);
224-
return;
225-
}
226-
227-
SentryCrashBinaryImageNode *node = rootNode.next;
228-
rootNode.next = NULL;
229-
tailNode = NULL;
230-
231-
while (node != NULL) {
232-
SentryCrashBinaryImageNode *nextNode = node->next;
233-
free(node);
234-
node = nextNode;
235-
}
236-
237-
pthread_mutex_unlock(&binaryImagesMutex);
238-
}
239-
240-
static void
241-
initialReportToCallback(SentryCrashBinaryImage *image, void *context)
242-
{
243-
sentrycrashbic_cacheChangeCallback callback = (sentrycrashbic_cacheChangeCallback)context;
244-
callback(image);
184+
// TODO: Why do we need to support stopping it?
245185
}
246186

247187
void
248188
sentrycrashbic_registerAddedCallback(sentrycrashbic_cacheChangeCallback callback)
249189
{
250-
imageAddedCallback = callback;
251-
if (callback) {
252-
pthread_mutex_lock(&binaryImagesMutex);
253-
sentrycrashbic_iterateOverImages(&initialReportToCallback, callback);
254-
pthread_mutex_unlock(&binaryImagesMutex);
255-
}
190+
// TODO: I think this is only for when we used to have on-device symbolication. Should be able to remove now
256191
}
257192

258193
void
259194
sentrycrashbic_registerRemovedCallback(sentrycrashbic_cacheChangeCallback callback)
260195
{
261-
imageRemovedCallback = callback;
196+
// TODO: I think this is only for when we used to have on-device symbolication. Should be able to remove now
262197
}

0 commit comments

Comments
 (0)