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
134106void
135107sentrycrashbic_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
191156void
192157sentrycrashbic_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
218181void
219182sentrycrashbic_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
247187void
248188sentrycrashbic_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
258193void
259194sentrycrashbic_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