-
-
Notifications
You must be signed in to change notification settings - Fork 340
Description
Summary
On macOS, calling gum_process_enumerate_modules() concurrently with
gum_darwin_symbolicator_new_with_task() (or gum_symbol_details_from_address())
causes a deadlock. The deadlock occurs immediately and is 100% reproducible.
Environment
- OS: macOS (tested on arm64, Darwin 25.1.0)
- Frida version: 17.5.2
- Architecture: arm64
Reproduction
Minimal C reproducer
#include <frida-gum.h>
#include <pthread.h>
#include <mach/mach.h>
static GumAddress g_addr;
static gboolean module_cb(GumModule *m, gpointer d) { return TRUE; }
static void *enum_thread(void *arg) {
while (1) gum_process_enumerate_modules(module_cb, NULL);
return NULL;
}
static void *resolve_thread(void *arg) {
while (1) {
GumDarwinSymbolicator *s =
gum_darwin_symbolicator_new_with_task(mach_task_self(), NULL);
if (s) {
GumDebugSymbolDetails details;
gum_darwin_symbolicator_details_from_address(s, g_addr, &details);
g_object_unref(s);
}
}
return NULL;
}
int main(void) {
gum_init_embedded();
g_addr = gum_module_find_global_export_by_name("malloc");
pthread_t t1, t2;
pthread_create(&t1, NULL, enum_thread, NULL);
pthread_create(&t2, NULL, resolve_thread, NULL);
pthread_join(t1, NULL); // Never returns - deadlock
pthread_join(t2, NULL);
return 0;
}Observed behavior
Both threads immediately deadlock. Neither thread completes a single iteration:
[Thread A] Starting enumerate_modules loop...
[Thread B] Starting resolve_symbol loop...
[Watchdog] 1s: enum=0 (+0), resolve=0 (+0)
[Watchdog] 2s: enum=0 (+0), resolve=0 (+0)
[Watchdog] 3s: enum=0 (+0), resolve=0 (+0)
*** DEADLOCK DETECTED ***
Expected behavior
Both operations should be able to run concurrently without deadlocking.
Analysis
This appears to be a lock ordering issue. The likely scenario:
Thread A (enumerate_modules):
1. Acquires LOCK_A
2. Needs LOCK_B → blocked
Thread B (symbolicator):
1. Acquires LOCK_B
2. Needs LOCK_A → blocked
→ Classic deadlock
Note: Even creating a fresh symbolicator via gum_darwin_symbolicator_new_with_task()
triggers the deadlock - it's not limited to the cached symbolicator used by
gum_symbol_details_from_address().
Impact
This affects any application that:
- Enumerates modules (e.g., for hooking, introspection)
- Resolves symbols (e.g., for stack traces, debugging)
- Does both operations from different threads
Workaround
Currently, the only workaround is to serialize access - never call these functions
concurrently. This is difficult in practice since module enumeration may happen during
initialization while symbol resolution happens during runtime.