Skip to content

Conversation

@V3L0C1T13S
Copy link

This PR both reworks the pthread implementation in various areas, implements pthread functions more accurately where possible, such as pthread_cond_timedwait_monotonic_np, and fixes the long standing pthread_mutex crashes.

When the app tries to use a mutex function such as pthread_mutex_lock, we run mutex_static_initializer if the mutex isn't initialized. mutex_static_initializer internally calls pthread_mutex_init, which in turn sets up the wrapped native mutex of the shim mutex struct.

A problem arises in the following scenario: thread 1 calls pthread_mutex_init; thread 2 calls pthread_mutex_lock. In this case, pthread_mutex_lock thinks the mutex isn't initialized, so it calls the static initializer which in turn calls pthread_mutex_init, setting the wrapped native mutex pointer. pthread_mutex_init runs at the same time but on a different thread, messing up the internal state.

The solution is two-pronged:

  1. use an atomic integer to determine whether a mutex is initialized;
  2. protect pthread_mutex_init exposed to the app, as well as pthread_static_initializer, with the same mutex internally, thus preventing them from racing.

When the app tries to use a mutex function such as pthread_mutex_lock,
we run mutex_static_initializer if the mutex isn't initialized.
mutex_static_initializer internally calls pthread_mutex_init, which in
turn sets up the wrapped native mutex of the shim mutex struct.

A problem arises in the following scenario: thread 1 calls
pthread_mutex_init; thread 2 calls pthread_mutex_lock. In this case,
pthread_mutex_lock thinks the mutex isn't initialized, so it calls the
static initializer which in turn calls pthread_mutex_init, setting the
wrapped native mutex pointer. pthread_mutex_init runs at the same time
but on a different thread, messing up the internal state.

The solution is two-pronged:
 1. use an atomic integer to determine whether a mutex is initialized;
 2. protect pthread_mutex_init exposed to the app, as well as
    pthread_static_initializer, with the same mutex internally, thus
    preventing them from racing.
src/pthreads.h Outdated

namespace bionic {

// EXPECTED: Size: 40 bytes, alignment 8 bytes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't we break 32bit abi? by removing the #ifdef ?

I would want to restore the 32bit abi before merging.

Copy link
Author

@V3L0C1T13S V3L0C1T13S Nov 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I've addressed this now. I do not have a 32-bit setup to test on at the moment, but it should be correct.

src/pthreads.h Outdated
std::atomic_int64_t check = 0;
int64_t priv = 0;
#else
// EXPECTED: Size: 24 bytes, alignment 4 bytes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

@V3L0C1T13S V3L0C1T13S Nov 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see now... You're definitely correct, yes. I'll need to store just an ID for the mutex like how Bionic does it, then have a mapping of ID to internal mutex info? I don't see how else I can get around the 4 byte size limitation.

Copy link
Author

@V3L0C1T13S V3L0C1T13S Nov 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Newest changes should restore 32-bit compat. The 32-bit ABI does not benefit from the race condition fix, but it should work as it did before. I was able to test this time around by using the 32-bit pthread_mutex_t structure on a 64-bit build, which surprisingly did work, MCPE doesn't seem to rely on the structure's size.

Is there a reason why we're accurate to Bionic so deeply in the first place if apps aren't actually dependent on the ABI level size?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

having a smaller mutex is fine for me, but if our layout is larger than bionic we have write after free, altering following fields errors etc.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense to me, then.

@ChristopherHX
Copy link
Member

Verification failed for me: Not sure why. Get tons of these fatal errors on macOS.

Stop reason: EXC_BAD_ACCESS (code=257, address=0x135bd765c)
bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=257, address=0x135bd765c)
  * frame #0: 0x00000001005aa660 mcpelauncher-client`void std::__1::__cxx_atomic_store[abi:ne190102]<long long>(__a=0x0000000135bd765c, __val=1, __order=memory_order_seq_cst) at cxx_atomic_impl.h:304:3
    frame #1: 0x00000001005aa5e8 mcpelauncher-client`std::__1::__atomic_base<long long, false>::store[abi:ne190102](this=0x0000000135bd765c, __d=1, __m=memory_order_seq_cst) at atomic_base.h:51:5
    frame #2: 0x00000001005a89c4 mcpelauncher-client`shim::bionic::mark_mutex_initialized(m=0x0000000135bd764c) at pthreads.h:46:31
    frame #3: 0x00000001005a7cf4 mcpelauncher-client`shim::pthread_mutex_init_internal(mutex=0x0000000135bd764c, attr=0x000000016fdf9f70) at pthreads.cpp:261:9
    frame #4: 0x00000001005a8a00 mcpelauncher-client`shim::pthread_mutex_init(mutex=0x0000000135bd764c, attr=0x000000016fdf9f70) at pthreads.cpp:268:12
    frame #5: 0x00000001306c7fc0
    frame #6: 0x00000001359d5684
    frame #7: 0x00000001359d5794
    frame #8: 0x00000001008545b4 mcpelauncher-client`call_function(function_name="function", function=0x00000001359d5788, realpath=<unavailable>) at linker_soinfo.cpp:498:3
    frame #9: 0x00000001008541ec mcpelauncher-client`void call_array<void (*)(int, char**, char**)>(array_name="DT_INIT_ARRAY", functions=0x0000000135bc6300, count=18, reverse=false, realpath="/Users/christopher/Library/Application Support/mcpelauncher/versions/1.21.111.1/lib/arm64-v8a/libmaesdk.so") at linker_soinfo.cpp:532:5
    frame #10: 0x0000000100854448 mcpelauncher-client`soinfo::call_constructors(this=0x000000010287f710) at linker_soinfo.cpp:588:3
    frame #11: 0x0000000100859f18 mcpelauncher-client`soinfo::call_constructors()::$_0::operator()(this=0x000000016fdfa19f, si=0x000000010287f710) const at linker_soinfo.cpp:574:9
    frame #12: 0x0000000100859ee8 mcpelauncher-client`void LinkedList<soinfo, SoinfoListAllocator>::for_each<soinfo::call_constructors(this=0x000000016fdfa160, si=0x000000010287f710)::$_0>(soinfo::call_constructors()::$_0) const::'lambda'(soinfo*)::operator()(soinfo*) const at linked_list.h:157:7
    frame #13: 0x0000000100859e6c mcpelauncher-client`bool LinkedList<soinfo, SoinfoListAllocator>::visit<void LinkedList<soinfo, SoinfoListAllocator>::for_each<soinfo::call_constructors()::$_0>(soinfo::call_constructors()::$_0) const::'lambda'(soinfo*)>(this=0x000000010287fab0, action=(unnamed class) @ 0x000000016fdfa160) const at linked_list.h:165:12
    frame #14: 0x00000001008544f4 mcpelauncher-client`void LinkedList<soinfo, SoinfoListAllocator>::for_each<soinfo::call_constructors()::$_0>(this=0x000000010287fab0, action=(unnamed class) @ 0x000000016fdfa19f) const at linked_list.h:156:5
    frame #15: 0x00000001008543d8 mcpelauncher-client`soinfo::call_constructors(this=0x000000010287f990) at linker_soinfo.cpp:573:18
    frame #16: 0x00000001008618b0 mcpelauncher-client`do_dlopen(name="libminecraftpe.so", flags=0, extinfo=0x000000016fdfadd8, caller_addr=0x0000000000000000) at linker.cpp:2290:9
    frame #17: 0x000000010084cbb0 mcpelauncher-client`dlopen_ext(filename="libminecraftpe.so", flags=0, extinfo=0x000000016fdfadd8, caller_addr=0x0000000000000000) at dlfcn.cpp:149:18
    frame #18: 0x000000010084cb54 mcpelauncher-client`__loader_android_dlopen_ext(filename="libminecraftpe.so", flags=0, extinfo=0x000000016fdfadd8, caller_addr=0x0000000000000000) at dlfcn.cpp:161:10
    frame #19: 0x000000010000ed20 mcpelauncher-client`linker::dlopen_ext(filename="libminecraftpe.so", flags=0, extinfo=0x000000016fdfadd8) at linker.h:42:16
    frame #20: 0x0000000100544958 mcpelauncher-client`MinecraftUtils::loadMinecraftLib(showMousePointerCallback=0x00000001003d8214, hideMousePointerCallback=0x00000001003d81e0, fullscreenCallback=0x00000001003d8258, closeCallback=0x00000001003b6ce0, hooks=size=27) at minecraft_utils.cpp:619:20
    frame #21: 0x000000010000bcb8 mcpelauncher-client`main(argc=3, argv=0x000000016fdfed40) at main.cpp:530:18
    frame #22: 0x00000001983d6b98 dyld`start + 6076

Atomics was always something that was too transparent to me, in terms what happens if you are not the one who initialize the value.

@V3L0C1T13S
Copy link
Author

I believe this is because OSX on ARM64 has different byte alignment..? I will try to force it to 8-byte alignment.

@ChristopherHX
Copy link
Member

I will try to force it to 8-byte alignment.

Does not seem to change anything, alignment requirements cannot be enforced by the libc-shim compiler. The alignment is done by the side allocating the memory, in this case the android shared library.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants