11#include "SentryCrashBinaryImageCache.h"
22#include "SentryCrashDynamicLinker.h"
3+ #include <dispatch/dispatch.h>
34#include <mach-o/dyld.h>
45#include <mach-o/dyld_images.h>
56#include <pthread.h>
67#include <stdio.h>
7- #include <dispatch/dispatch.h>
88#include <stdlib.h>
99#include <string.h>
1010#include <unistd.h>
@@ -66,59 +66,116 @@ sentry_resetFuncForAddRemoveImage(void)
6666#define MAX_DYLD_IMAGES 4096
6767
6868typedef struct {
69- _Atomic (uint32_t ) ready ; // 0 = not published, 1 = published
70- SentryCrashBinaryImage image ;
69+ _Atomic (uint32_t ) ready ; // 0 = not published, 1 = published
70+ SentryCrashBinaryImage image ;
7171} PublishedBinaryImage ;
7272
7373static PublishedBinaryImage g_images [MAX_DYLD_IMAGES ];
7474static _Atomic (uint32_t ) g_next_index = 0 ;
7575static _Atomic (uint32_t ) g_overflowed = 0 ;
76+ static _Atomic (uint32_t ) g_started = 0 ;
7677
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 );
78+ static _Atomic (sentrycrashbic_cacheChangeCallback ) g_addedCallback = NULL ;
79+ static _Atomic (sentrycrashbic_cacheChangeCallback ) g_removedCallback = NULL ;
8080
81- if (idx >= MAX_DYLD_IMAGES ) {
82- atomic_store_explicit (& g_overflowed , 1 , memory_order_relaxed );
81+ static void
82+ add_dyld_image (const struct mach_header * mh )
83+ {
84+ // Don't add images if the cache is not started
85+ if (!atomic_load_explicit (& g_started , memory_order_acquire )) {
8386 return ;
8487 }
85-
88+
89+ // Check dladdr first, before reserving a slot in the array.
90+ // If we increment g_next_index before this check and dladdr fails,
91+ // we'd create a "hole" with ready=0 that stops iteration.
8692 Dl_info info ;
8793 if (!dladdr (mh , & info ) || info .dli_fname == NULL ) {
8894 return ;
8995 }
9096
91- PublishedBinaryImage * entry = & g_images [idx ];
97+ // Test hook: called just before adding the image
98+ _will_add_image ();
99+
100+ uint32_t idx = atomic_fetch_add_explicit (& g_next_index , 1 , memory_order_relaxed );
101+
102+ if (idx >= MAX_DYLD_IMAGES ) {
103+ atomic_store_explicit (& g_overflowed , 1 , memory_order_relaxed );
104+ return ;
105+ }
106+
107+ PublishedBinaryImage * entry = & g_images [idx ];
92108 sentrycrashdl_getBinaryImageForHeader (mh , info .dli_fname , & entry -> image , false);
93109
110+ // Read callback BEFORE publishing to avoid race with registerAddedCallback.
111+ // If callback is NULL here, the registering thread will see ready=1 and call it.
112+ // If callback is non-NULL here, we call it and the registering thread will either
113+ // not have started iterating yet, or will skip this image since it wasn't ready
114+ // when it read g_next_index.
115+ sentrycrashbic_cacheChangeCallback callback
116+ = atomic_load_explicit (& g_addedCallback , memory_order_acquire );
117+
94118 // ---- Publish ----
95119 atomic_store_explicit (& entry -> ready , 1 , memory_order_release );
120+
121+ if (callback != NULL ) {
122+ callback (& entry -> image );
123+ }
96124}
97125
98- static void dyld_add_image_cb (const struct mach_header * mh , intptr_t slide ) {
126+ static void
127+ dyld_add_image_cb (const struct mach_header * mh , intptr_t slide )
128+ {
99129 add_dyld_image (mh );
100130}
101131
102- void dyld_tracker_start (void ) {
103- sentry_dyld_register_func_for_add_image (dyld_add_image_cb );
132+ static void
133+ dyld_remove_image_cb (const struct mach_header * mh , intptr_t slide )
134+ {
135+ sentrycrashbic_cacheChangeCallback callback
136+ = atomic_load_explicit (& g_removedCallback , memory_order_acquire );
137+
138+ // Find the image in our cache by matching the header address
139+ uint32_t count = atomic_load_explicit (& g_next_index , memory_order_acquire );
140+ if (count > MAX_DYLD_IMAGES )
141+ count = MAX_DYLD_IMAGES ;
142+
143+ for (uint32_t i = 0 ; i < count ; i ++ ) {
144+ PublishedBinaryImage * src = & g_images [i ];
145+ if (!atomic_load_explicit (& src -> ready , memory_order_acquire )) {
146+ continue ;
147+ }
148+ if (src -> image .address == (uintptr_t )mh ) {
149+ atomic_store_explicit (& src -> ready , 0 , memory_order_release );
150+ if (callback ) {
151+ callback (& src -> image );
152+ return ;
153+ }
154+ }
155+ }
156+ }
157+
158+ static void
159+ dyld_tracker_start (void )
160+ {
161+ sentry_dyld_register_func_for_add_image (dyld_add_image_cb );
162+ sentry_dyld_register_func_for_remove_image (dyld_remove_image_cb );
104163}
105164
106165void
107166sentrycrashbic_iterateOverImages (sentrycrashbic_imageIteratorCallback callback , void * context )
108167{
109- uint32_t count =
110- atomic_load_explicit (& g_next_index , memory_order_acquire );
168+ uint32_t count = atomic_load_explicit (& g_next_index , memory_order_acquire );
111169
112- if (count > MAX_DYLD_IMAGES ) count = MAX_DYLD_IMAGES ;
170+ if (count > MAX_DYLD_IMAGES )
171+ count = MAX_DYLD_IMAGES ;
113172
114173 for (uint32_t i = 0 ; i < count ; i ++ ) {
115- PublishedBinaryImage * src = & g_images [i ];
174+ PublishedBinaryImage * src = & g_images [i ];
116175
117- if (! atomic_load_explicit (& src -> ready , memory_order_acquire )) {
118- return ; // stop at first unpublished entry
176+ if (atomic_load_explicit (& src -> ready , memory_order_acquire )) {
177+ callback ( & src -> image , context );
119178 }
120-
121- callback (& src -> image , context );
122179 }
123180}
124181
@@ -130,7 +187,7 @@ sentrycrashbic_iterateOverImages(sentrycrashbic_imageIteratorCallback callback,
130187 * @return true if dyld is not found in the loaded images and should be added to the cache,
131188 * false if dyld is already present in the loaded images.
132189 */
133- bool
190+ static bool
134191sentrycrashbic_shouldAddDyld (void )
135192{
136193 // dyld is different from libdyld.dylib; the latter contains the public API
@@ -141,57 +198,104 @@ sentrycrashbic_shouldAddDyld(void)
141198
142199// Since Apple no longer includes dyld in the images listed `_dyld_image_count` and related
143200// functions We manually include it to our cache.
144- void
201+ // Note: This bypasses add_dyld_image() because dladdr() returns NULL for dyld, so we need
202+ // to use sentrycrashdl_getBinaryImageForHeader() directly with a hardcoded filename.
203+ static void
145204sentrycrashbic_addDyldNode (void )
146205{
147206 const struct mach_header * header = sentryDyldHeader ;
148207
149- SentryCrashBinaryImage binaryImage = { 0 };
150- if (!sentrycrashdl_getBinaryImageForHeader ((const void * )header , "dyld" , & binaryImage , false)) {
208+ uint32_t idx = atomic_fetch_add_explicit (& g_next_index , 1 , memory_order_relaxed );
209+ if (idx >= MAX_DYLD_IMAGES ) {
210+ atomic_store_explicit (& g_overflowed , 1 , memory_order_relaxed );
151211 return ;
152212 }
153- add_dyld_image (header );
213+
214+ PublishedBinaryImage * entry = & g_images [idx ];
215+ if (!sentrycrashdl_getBinaryImageForHeader ((const void * )header , "dyld" , & entry -> image , false)) {
216+ // Decrement because we couldn't add the image
217+ atomic_fetch_sub_explicit (& g_next_index , 1 , memory_order_relaxed );
218+ return ;
219+ }
220+
221+ sentrycrashbic_cacheChangeCallback callback
222+ = atomic_load_explicit (& g_addedCallback , memory_order_acquire );
223+ atomic_store_explicit (& entry -> ready , 1 , memory_order_release );
224+ if (callback != NULL ) {
225+ callback (& entry -> image );
226+ }
154227}
155228
156- void
157- sentrycrashbic_startCache (void )
229+ static void
230+ sentrycrashbic_startCacheImpl (void )
158231{
232+ // Check if already started
233+ uint32_t expected = 0 ;
234+ if (!atomic_compare_exchange_strong_explicit (
235+ & g_started , & expected , 1 , memory_order_acq_rel , memory_order_relaxed )) {
236+ return ;
237+ }
238+
159239 if (sentrycrashbic_shouldAddDyld ()) {
160240 sentrycrashdl_initialize ();
161241 sentrycrashbic_addDyldNode ();
162242 }
163243
244+ dyld_tracker_start ();
245+ }
246+
247+ void
248+ sentrycrashbic_startCache (void )
249+ {
164250 // During a call to _dyld_register_func_for_add_image() the callback func is called for every
165251 // existing image
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- });
252+ // This must be done on a background thread to not block app launch due to the extensive use of
253+ // locks in the image added callback. The main culprit is the calls to `dladdr`. The downside of
254+ // doing this async is if there is a crash very shortly after app launch we might not have
255+ // recorded all the load addresses of images yet. We think this is an acceptible tradeoff to not
256+ // block app launch, since it's always possible to crash early in app launch before Sentry can
257+ // capture the crash.
258+ #if defined(SENTRY_TEST ) || defined(SENTRY_TEST_CI )
259+ sentrycrashbic_startCacheImpl ();
260+ #else
261+ dispatch_async (
262+ dispatch_get_global_queue (QOS_CLASS_DEFAULT , 0 ), ^{ sentrycrashbic_startCacheImpl (); });
263+ #endif
179264}
180265
181266void
182267sentrycrashbic_stopCache (void )
183268{
184- // TODO: Why do we need to support stopping it?
269+ // The binary image cache does not allocate memory, the cache takes a fixed
270+ // size, it's not malloced. So this is kind of pointless, but there are a bunch
271+ // of tests using it. We should clean it up and make startCache use dispatchOnce
272+ atomic_store_explicit (& g_started , 0 , memory_order_release );
273+ atomic_store_explicit (& g_next_index , 0 , memory_order_release );
185274}
186275
187276void
188277sentrycrashbic_registerAddedCallback (sentrycrashbic_cacheChangeCallback callback )
189278{
190- // TODO: I think this is only for when we used to have on-device symbolication. Should be able to remove now
279+ atomic_store_explicit (& g_addedCallback , callback , memory_order_release );
280+
281+ if (callback != NULL ) {
282+ // Call for all existing images already in the cache
283+ uint32_t count = atomic_load_explicit (& g_next_index , memory_order_acquire );
284+ if (count > MAX_DYLD_IMAGES )
285+ count = MAX_DYLD_IMAGES ;
286+
287+ for (uint32_t i = 0 ; i < count ; i ++ ) {
288+ PublishedBinaryImage * src = & g_images [i ];
289+ if (!atomic_load_explicit (& src -> ready , memory_order_acquire )) {
290+ break ;
291+ }
292+ callback (& src -> image );
293+ }
294+ }
191295}
192296
193297void
194298sentrycrashbic_registerRemovedCallback (sentrycrashbic_cacheChangeCallback callback )
195299{
196- // TODO: I think this is only for when we used to have on-device symbolication. Should be able to remove now
300+ atomic_store_explicit ( & g_removedCallback , callback , memory_order_release );
197301}
0 commit comments