Skip to content

macOS: workaround a dyld/libunwind deadlock issue since 12.1 #43701

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ endif
CLANG_LDFLAGS := $(LLVM_LDFLAGS)
ifeq ($(OS), Darwin)
CLANG_LDFLAGS += -Wl,-undefined,dynamic_lookup
OSLIBS += $(SRCDIR)/mach_dyld_atfork.tbd
endif

COMMON_LIBPATHS := -L$(build_libdir) -L$(build_shlibdir)
Expand Down
25 changes: 25 additions & 0 deletions src/mach_dyld_atfork.tbd
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
--- !tapi-tbd
# copied from XCode's libSystem.tbd (current-version: 1311)
# to provide weak-linkage info for new symbols on old systems
tbd-version: 4
targets: [ x86_64-macos, x86_64-maccatalyst, arm64-macos, arm64-maccatalyst,
arm64e-macos, arm64e-maccatalyst ]
uuids:
- target: x86_64-macos
value: AFE6C76A-B47A-35F5-91D0-4E9FC439E90D
- target: x86_64-maccatalyst
value: AFE6C76A-B47A-35F5-91D0-4E9FC439E90D
- target: arm64-macos
value: 2EA09BDB-811B-33AA-BB58-4B53AA2DB522
- target: arm64-maccatalyst
value: 2EA09BDB-811B-33AA-BB58-4B53AA2DB522
- target: arm64e-macos
value: 09AB3723-C26D-3762-93BA-98E9C38B89C1
- target: arm64e-maccatalyst
value: 09AB3723-C26D-3762-93BA-98E9C38B89C1
install-name: '/usr/lib/libSystem.B.dylib'
exports:
- targets: [ arm64-macos, arm64e-macos, x86_64-macos, x86_64-maccatalyst,
arm64-maccatalyst, arm64e-maccatalyst ]
symbols: [ __dyld_atfork_parent, __dyld_atfork_prepare ]
...
42 changes: 36 additions & 6 deletions src/signals-mach.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ extern void *_keymgr_get_and_lock_processwide_ptr(unsigned int key);
extern int _keymgr_get_and_lock_processwide_ptr_2(unsigned int key, void **result);
extern int _keymgr_set_lockmode_processwide_ptr(unsigned int key, unsigned int mode);

// private dyld3/dyld4 stuff
extern void _dyld_atfork_prepare(void) __attribute__((weak_import));
extern void _dyld_atfork_parent(void) __attribute__((weak_import));
//extern void _dyld_fork_child(void) __attribute__((weak_import));

static void attach_exception_port(thread_port_t thread, int segv_only);

// low 16 bits are the thread id, the next 8 bits are the original gc_state
Expand Down Expand Up @@ -521,6 +526,31 @@ static kern_return_t profiler_segv_handler
}
#endif

// WARNING: we are unable to handle sigsegv while the dlsymlock is held
static int jl_lock_profile_mach(int dlsymlock)
{
jl_lock_profile();
// workaround for old keymgr bugs
void *unused = NULL;
int keymgr_locked = _keymgr_get_and_lock_processwide_ptr_2(KEYMGR_GCC3_DW2_OBJ_LIST, &unused) == 0;
// workaround for new dlsym4 bugs (API and bugs introduced in macOS 12.1)
if (dlsymlock && _dyld_atfork_prepare != NULL && _dyld_atfork_parent != NULL)
_dyld_atfork_prepare();
return keymgr_locked;
}

static void jl_unlock_profile_mach(int dlsymlock, int keymgr_locked)
{
if (dlsymlock && _dyld_atfork_prepare != NULL && _dyld_atfork_parent != NULL) \
_dyld_atfork_parent(); \
if (keymgr_locked)
_keymgr_unlock_processwide_ptr(KEYMGR_GCC3_DW2_OBJ_LIST);
jl_unlock_profile();
}

#define jl_lock_profile() int keymgr_locked = jl_lock_profile_mach(1)
#define jl_unlock_profile() jl_unlock_profile_mach(1, keymgr_locked)

void *mach_profile_listener(void *arg)
{
(void)arg;
Expand All @@ -537,9 +567,7 @@ void *mach_profile_listener(void *arg)
HANDLE_MACH_ERROR("mach_msg", ret);
// sample each thread, round-robin style in reverse order
// (so that thread zero gets notified last)
jl_lock_profile();
void *unused = NULL;
int keymgr_locked = _keymgr_get_and_lock_processwide_ptr_2(KEYMGR_GCC3_DW2_OBJ_LIST, &unused) == 0;
int keymgr_locked = jl_lock_profile_mach(0);
jl_shuffle_int_array_inplace(profile_round_robin_thread_order, jl_n_threads, &profile_cong_rng_seed);
for (int idx = jl_n_threads; idx-- > 0; ) {
// Stop the threads in the random round-robin order.
Expand All @@ -550,9 +578,13 @@ void *mach_profile_listener(void *arg)
break;
}

if (_dyld_atfork_prepare != NULL && _dyld_atfork_parent != NULL)
_dyld_atfork_prepare(); // briefly acquire the dlsym lock
host_thread_state_t state;
jl_thread_suspend_and_get_state2(i, &state);
unw_context_t *uc = (unw_context_t*)&state;
if (_dyld_atfork_prepare != NULL && _dyld_atfork_parent != NULL)
_dyld_atfork_parent(); // quickly release the dlsym lock

if (running) {
#ifdef LLVMLIBUNWIND
Expand Down Expand Up @@ -609,9 +641,7 @@ void *mach_profile_listener(void *arg)
// We're done! Resume the thread.
jl_thread_resume(i, 0);
}
if (keymgr_locked)
_keymgr_unlock_processwide_ptr(KEYMGR_GCC3_DW2_OBJ_LIST);
jl_unlock_profile();
jl_unlock_profile_mach(0, keymgr_locked);
if (running) {
// Reset the alarm
kern_return_t ret = clock_alarm(clk, TIME_RELATIVE, timerprof, profile_port);
Expand Down