https://bugs.winehq.org/show_bug.cgi?id=54831
Bug ID: 54831 Summary: GStreamer gst_init_check() crashes when called from winegstreamer on recent macOS Product: Wine Version: 8.5 Hardware: x86-64 OS: Mac OS X Status: UNCONFIRMED Severity: normal Priority: P2 Component: loader Assignee: wine-bugs@winehq.org Reporter: bshanks@codeweavers.com
On macOS Ventura (and I suspect Monterey), gst_init_check() crashes (trying to jump to 0x0) when called by winegstreamer. This is caused by the macOS preloader, here's how:
* gst_init_check() calls init_static_plugins() https://gitlab.freedesktop.org/gstreamer/gstreamer/-/blob/1.22/subprojects/gstreamer/gst/gst.c#L625, which through some GModule abstractions does a dlopen(NULL) and dlsym() for a symbol that doesn't exist. dlsym() returns NULL, but GModule also calls dlerror() which returns NULL (signaling no error). * init_static_plugins() therefore thinks that the dlsym() was successful, doesn't do a NULL pointer check, and jumps to the NULL pointer.
The issue here is dlerror() returning NULL despite there being an error to report. Looking at the macOS dyld source code (https://github.com/apple-oss-distributions/dyld/blob/dyld-1042.1/dyld/DyldAPIs.cpp#L1206), dlerror() always returns NULL if libSystem was not initialized in the process. And sure enough, when running vmmap on a Wine process, the message "Process exists but has not fully started -- dyld has initialized but libSystem has not" is printed.
After more searching in dyld, it turns out that dyld initializes libSystem for binaries targeting 10.5 and newer. The Wine preloader targets 10.7 (since that's the newest target that will generate an LC_UNIXTHREAD binary, critical to how the preloader works). But dyld determines the target OS based on where the program vars (argc, argv, environ, etc) are stored, and a 10.6/10.7 binary should contain a __program_vars section for those. The preloader is missing that, and so (I believe) it falls down to being considered a 10.4 binary. A 10.4 binary should init libSystem in its own _start, but the preloader also doesn't do this, and libSystem stays uninitialized.
(See https://github.com/apple-oss-distributions/dyld/blob/dyld-1042.1/common/MachOAnalyzer.cpp#L2185 for where this is determined)
I see several possible solutions:
* The most obvious one: add a __program_vars section to the preloader, to make it a correct 10.7 binary. In fact, the code can basically be copied out of Apple's C startup code (https://github.com/apple-opensource/Csu/blob/88/crt.c#L47). This does work, and libSystem gets initialized. However, libSystem is initialized before any preloader code runs, and malloc zones are already in the middle of areas we want to reserve. That's bad; by itself this approach won't work, but it might if we also:
- add linker zerofill sections to block off the entire low 4GB. This is desirable to reserve as much address space as possible for 32-bit EXEs in wow64 mode.
- and if zerofill sections are also added to reserve the area where 64-bit EXEs generally load (0x14000000), it might even be possible to stop using the preloader altogether on 64-bit.
* Alternately, go the other way, and lean in to being a 10.4 binary. In that case, _start in the preloader needs to call back into dyld to initialize things (like this: https://github.com/apple-opensource/Csu/blob/88/crt.c#L219). We could do this after reserving our areas. I'm leery of this approach, mainly because x86_64/10.4 was not a popular target (GUI apps were not supported, they could only link against libSystem). If possible, I'd rather not target such an obscure combination. The preloader has been a source of problems with new OS versions, and I'd like to decrease the amount of black magic it's doing, not increase it. (Running 10.4 x86_64 binaries on modern macOS is still supported though, and Xcode can even still build them.)
I'd like to make the __program_vars+zerofill approach work if possible.