https://github.com/bdwgc/bdwgc/issues/783 https://github.com/bdwgc/bdwgc/issues/802 https://github.com/bdwgc/bdwgc/commit/910b4f008f3bcb436a23a2d5f9784e5dd9ab66cd https://github.com/bdwgc/bdwgc/commit/1058994bc264dd99a7b6d3caefe0c129bfbd1fb7 https://github.com/bdwgc/bdwgc/commit/73fdfdaf45fd35993c7f9e051875147ca39438c4 From 910b4f008f3bcb436a23a2d5f9784e5dd9ab66cd Mon Sep 17 00:00:00 2001 From: Ivan Maidanski Date: Sat, 25 Oct 2025 23:19:33 +0300 Subject: [PATCH] Fix SIGSEGV in remove_all_threads_but_me if fork from unregistered thread (a cherry-pick of commit 44fbb3b9e from 'master') Issue #783 (bdwgc). Some clients call `fork()` from an unregistered thread, thus we might find no entry for the current thread in `GC_remove_all_threads_but_me`. * include/gc.h (GC_atfork_child): Add comment about `fork()` from an unregistered thread. * pthread_support.c [CAN_HANDLE_FORK] (GC_remove_all_threads_but_me): If `me` is null, then return immediately after loop (instead of `ABORT` or the assertion, regardless of `CPPCHECK` and `LINT2`). * win32_threads.c [CAN_HANDLE_FORK] (GC_remove_all_threads_but_me): Likewise. --- include/gc.h | 1 + pthread_support.c | 19 ++++++++----------- win32_threads.c | 4 +++- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/include/gc.h b/include/gc.h index 54d704a06..b929240fc 100644 --- a/include/gc.h +++ b/include/gc.h @@ -512,6 +512,7 @@ GC_API void GC_CALL GC_set_handle_fork(int); /* non-zero); GC_atfork_child is to be called immediately in the child */ /* branch (i.e., fork result is 0). Note that GC_atfork_child() call */ /* should, of course, precede GC_start_mark_threads call (if any). */ +/* Note that fork() could be called from an unregistered thread. */ GC_API void GC_CALL GC_atfork_prepare(void); GC_API void GC_CALL GC_atfork_parent(void); GC_API void GC_CALL GC_atfork_child(void); diff --git a/pthread_support.c b/pthread_support.c index dbde77f25..0efbcdd9c 100644 --- a/pthread_support.c +++ b/pthread_support.c @@ -829,11 +829,11 @@ GC_API void GC_CALL GC_register_altstack(void *stack, GC_word stack_size, GC_threads[hv] = me; } -/* Remove all entries from the GC_threads table, except the one for */ -/* the current thread. Also update thread identifiers stored in */ -/* the table for the current thread. We need to do this in the */ -/* child process after a fork(), since only the current thread */ -/* survives in the child. */ +/* Remove all entries from the GC_threads table, except the one (if */ +/* any) for the current thread. Also update thread identifiers */ +/* stored in the table for the current thread. We need to do this */ +/* in the child process after a fork(), since only the current */ +/* thread survives in the child. */ STATIC void GC_remove_all_threads_but_me(void) { int hv; @@ -870,12 +870,9 @@ STATIC void GC_remove_all_threads_but_me(void) store_to_threads_table(hv, NULL); } -# if defined(CPPCHECK) || defined(LINT2) - if (NULL == me) - ABORT("Current thread is not found after fork"); -# else - GC_ASSERT(me != NULL); -# endif + if (NULL == me) + return; /* fork() is called from an unregistered thread */ + /* Update pthread's id as it is not guaranteed to be the same */ /* between this (child) process and the parent one. */ me -> id = pthread_self(); diff --git a/win32_threads.c b/win32_threads.c index d1b7d5dbe..0335085ed 100644 --- a/win32_threads.c +++ b/win32_threads.c @@ -1185,8 +1185,10 @@ GC_API void * GC_CALL GC_get_my_stackbottom(struct GC_stack_base *sb) GC_threads[hv] = NULL; } + if (NULL == me) + return; /* fork() is called from an unregistered thread */ + /* Put "me" back to GC_threads. */ - GC_ASSERT(me != NULL); thread_id = GetCurrentThreadId(); /* differs from that in parent */ GC_threads[THREAD_TABLE_INDEX(thread_id)] = me; From 1058994bc264dd99a7b6d3caefe0c129bfbd1fb7 Mon Sep 17 00:00:00 2001 From: Ivan Maidanski Date: Thu, 30 Oct 2025 23:39:44 +0300 Subject: [PATCH] Fix code defect of LOCK/UNLOCK in separate 'if' in GC_generic_malloc_many (a cherry-pick of commit c7d342c83 from 'master') * mallocx.c [PARALLEL_MARK] (GC_generic_malloc_many): Check `GC_parallel` once around `GC_reclaim_generic()` call (so that nearby `UNLOCK()` and the following `LOCK()` to be placed inside a single `if` statement block). --- mallocx.c | 82 +++++++++++++++++++++++++++---------------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/mallocx.c b/mallocx.c index 340bd5231..927008958 100644 --- a/mallocx.c +++ b/mallocx.c @@ -374,54 +374,54 @@ GC_API void GC_CALL GC_generic_malloc_many(size_t lb, int k, void **result) ++ GC_fl_builder_count; UNLOCK(); GC_release_mark_lock(); - } -# endif - op = GC_reclaim_generic(hbp, hhdr, lb, - ok -> ok_init, 0, &my_bytes_allocd); - if (op != 0) { -# ifdef PARALLEL_MARK - if (GC_parallel) { - *result = op; - (void)AO_fetch_and_add(&GC_bytes_allocd_tmp, - (AO_t)my_bytes_allocd); + + op = GC_reclaim_generic(hbp, hhdr, lb, ok -> ok_init, NULL, + &my_bytes_allocd); + if (op != NULL) { + *result = op; + (void)AO_fetch_and_add(&GC_bytes_allocd_tmp, + (AO_t)my_bytes_allocd); + GC_acquire_mark_lock(); + -- GC_fl_builder_count; + if (GC_fl_builder_count == 0) GC_notify_all_builder(); +# ifdef THREAD_SANITIZER + GC_release_mark_lock(); + LOCK(); + GC_bytes_found += my_bytes_allocd; + UNLOCK(); +# else + GC_bytes_found += my_bytes_allocd; + /* The result may be inaccurate. */ + GC_release_mark_lock(); +# endif + (void) GC_clear_stack(0); + return; + } + GC_acquire_mark_lock(); -- GC_fl_builder_count; if (GC_fl_builder_count == 0) GC_notify_all_builder(); -# ifdef THREAD_SANITIZER - GC_release_mark_lock(); - LOCK(); - GC_bytes_found += my_bytes_allocd; - UNLOCK(); -# else - GC_bytes_found += my_bytes_allocd; - /* The result may be inaccurate. */ - GC_release_mark_lock(); -# endif - (void) GC_clear_stack(0); - return; - } -# endif - /* We also reclaimed memory, so we need to adjust */ - /* that count. */ + GC_release_mark_lock(); + LOCK(); + /* GC lock is needed for reclaim list access. We */ + /* must decrement fl_builder_count before reacquiring */ + /* the lock. Hopefully this path is rare. */ + + rlh = ok -> ok_reclaim_list; /* reload rlh after locking */ + if (NULL == rlh) break; + continue; + } +# endif + + op = GC_reclaim_generic(hbp, hhdr, lb, ok -> ok_init, NULL, + &my_bytes_allocd); + if (op != NULL) { + /* We also reclaimed memory, so we need to adjust */ + /* that count. */ GC_bytes_found += my_bytes_allocd; GC_bytes_allocd += my_bytes_allocd; goto out; } -# ifdef PARALLEL_MARK - if (GC_parallel) { - GC_acquire_mark_lock(); - -- GC_fl_builder_count; - if (GC_fl_builder_count == 0) GC_notify_all_builder(); - GC_release_mark_lock(); - LOCK(); - /* GC lock is needed for reclaim list access. We */ - /* must decrement fl_builder_count before reacquiring */ - /* the lock. Hopefully this path is rare. */ - - rlh = ok -> ok_reclaim_list; /* reload rlh after locking */ - if (NULL == rlh) break; - } -# endif } } /* Next try to use prefix of global free list if there is one. */ From 73fdfdaf45fd35993c7f9e051875147ca39438c4 Mon Sep 17 00:00:00 2001 From: Ivan Maidanski Date: Fri, 31 Oct 2025 22:44:52 +0300 Subject: [PATCH] Fix SIGSEGV if pthread_detach is called before collector initialization (a cherry-pick of commit e40697e26 from 'master') The only case this seems to be needed is when the client calls `pthread_detach(pthread_self())` before the collector initialization. * pthread_support.c [!SN_TARGET_ORBIS && !SN_TARGET_PSP2] (GC_pthread_detach): Call `GC_init()` if the collector is not initialized; add comment. * win32_threads.c [GC_PTHREADS] (GC_pthread_detach): Likewise. --- pthread_support.c | 7 +++++++ win32_threads.c | 1 + 2 files changed, 8 insertions(+) diff --git a/pthread_support.c b/pthread_support.c index 0efbcdd9c..fb09f3d17 100644 --- a/pthread_support.c +++ b/pthread_support.c @@ -2016,6 +2016,13 @@ GC_INNER_PTHRSTART void GC_thread_exit_proc(void *arg) DCL_LOCK_STATE; INIT_REAL_SYMS(); + if (!EXPECT(GC_is_initialized, TRUE)) { + /* + * The only case this seems to be needed is when the client calls + * pthread_detach(pthread_self()) before the collector initialization. + */ + GC_init(); + } LOCK(); t = (GC_thread)COVERT_DATAFLOW(GC_lookup_thread(thread)); UNLOCK(); diff --git a/win32_threads.c b/win32_threads.c index 0335085ed..0ec33cf95 100644 --- a/win32_threads.c +++ b/win32_threads.c @@ -3257,6 +3257,7 @@ GC_INNER void GC_thr_init(void) GC_thread t; DCL_LOCK_STATE; + if (!EXPECT(GC_is_initialized, TRUE)) GC_init(); GC_ASSERT(!GC_win32_dll_threads); t = GC_lookup_pthread(thread); result = pthread_detach(thread);