On 4/15/22 00:26, Eric Pouech wrote:
For modules requiring linking to .so files, the scheme has to be extended by generating an additional ELF library, tied to the PE module. +-----------------+ +-----------------+ +-----------+ | FOOBAR.dll (PE) | | FOOBAR.so (ELF) | -------------> | helper.so | | | --------> | (unixlib) | ELF dyn link +-----------+ | | | | +-----------------+ +-----------------+
Addind this extra module in the middle allows to:
- do the translation from a entities defined in PE world into entities
defined in the ELF world (ABI are different, structures layout are different)
- this provides an abstraction of the library for the PE world.
- decide what to migrate from the existing code into the PE part (remember
existing library in old model lives in the ELF world)
One can see the FOOBAR.so like a Wine-"system driver" for providing the required support from the host.
But, it brings constraints too. The ELF part cannot call back into the PE part of the module, nor into other PE modules.
To clarify: the ELF part cannot call back into PE modules *without performance penalty or interference with stack unwinding*.
ELF modules calls back into user-supplied functions just fine, think e.g. window procedures. This however involves KeUserModeCallback(), accompanied by its context-switching overhead.
Perhaps we intend to say: "The ELF part cannot link directly to dlls. Also, calling back to the PE side comes with performance penalty or interferes with stack unwinding, depending on which callback approach is used."
If this ELF parts requires some Wine "kernel features", the it can (using ELF dynamic linking) use the APIs provided by ntdll.so ELF library.
+-----------------+ +-----------------+ +-----------+ | FOOBAR.dll (PE) | | FOOBAR.so (ELF) | -------------> | ntdll.so | | | --------> | (unixlib) | ELF dyn link +-----------+ | | | | -------------> | helper.so | +-----------------+ +-----------------+ +-----------+
Note: likely some other modules .so should be available too (like win32u) (to be confirmed)
I confirm that win32u.so also exists.
Here is a list of other Unixlibs:
avicap32.so, bcrypt.so, capi2032.so, crypt32.so, ctapi32.so, dnsapi.so, dwrite.so, gphoto2.so, kerberos.so, mountmgr.so, msv1_0.so, netapi32.so, nsiproxy.so, odbc32.so, opencl.so, qcap.so, sane.so, secur32.so, winealsa.so, winebus.so, winecoreaudio.so, winegstreamer.so, wineoss.so, winepulse.so, winevulkan.so, winspool.so, wldap32.so, wpcap.so, ws2_32.so
Currently, the 32 <> 64 bit is still duplicated:
+-----------------+ +------------------------+ +--------------------+ | FOOBAR.dll (PE) | | FOOBAR.so (ELF 64 bit) | -------------> | ntdll.so (64 bit) | | 64 bit | --------> | (unixlib 64 bit) | ELF dyn link +--------------------+ | | | | -------------> | helper.so (64 bit) | +-----------------+ +------------------------+ +--------------------+
+-----------------+ +------------------------+ +--------------------+ | FOOBAR.dll (PE) | | FOOBAR.so (ELF 32 bit) | -------------> | ntdll.so (32 bit) | | 32 bit | --------> | (unixlib 32 bit) | ELF dyn link +--------------------+ | | | | -------------> | helper.so (32 bit) | +-----------------+ +------------------------+ +--------------------+
When all modules are converted to the new scheme, this shall look like:
+-----------------+ +------------------------+ +--------------------+ | FOOBAR.dll (PE) | | FOOBAR.so (ELF 64 bit) | -------------> | ntdll.so (64 bit) | | 64 bit | --------> | (unixlib ) | ELF dyn link +--------------------+ | | +----> | (64 + 32 bit) | -------------> | helper.so (64 bit) | +-----------------+ | +------------------------+ +--------------------+ | +-----------------+ | | FOOBAR.dll (PE) | | | 32 bit | ---+ | | +-----------------+
This will require that FOOBAR.so correctly handles the "syscall" for both 32 bit and 64 bit unixlib calls. When this is in place, no support for 32-bit .so files will be necessary (both the packages that provide the .so files, but also for the development packages.
Unixlib in a nutshell
A unixlib interface is just a table of entry points. Both ends (calling PE side, and callee on ELF side) share:
- the semantic attached to an entry point a given index in the table
(usually defined as a value inside an enum)
- the signature of each entry point is always the same:
NTSTATUS the_func(void *args);
- args is a pointer to a structure containing input and output
parameters to this very entry point. There's a specific structure for each function (in some rare cases, several functions can use the same structure).
It's also worth noting that some DLLs (e.g. win32u.so) have not yet fully migrated to the new __wine_unix_call interface. They will eventually, though.
Invoking on PE side looks like: | struct xxx_yyy arg; | /* fill in arg */ | NTSTATUS status = __wine_unixlib_callinto(foobar.so, /* pseudo code <g> */ | 23 /* index of entry */, | &arg);
__wine_unix_call*
An on ELF side: | static NTSTATUS my_func_23(void* parg) | { | struct xxx_yyy *arg = parg; | /* do the hard work */ | } | | /* other functions */ | | entry_point_table[] = { | /* ... */ | my_func_23, /* index 23 */ | /* ... */ | }; | | /* a bit of black magic to register the entry_point_table */
Keep in mind:
- the same structure can have different sizes for fields, and different layouts
when compiled for PE or ELF, 32 or 64 bit, or different CPUs. So the ELF part must handle those discrepancies if any.
- pointers will be of different sizes...
Yes, hence __wine_unix_call_wow64_funcs.
- allocation of memory can be harder to implement on ELF side (as one must
ensure that allocated memory can be accessed from 32 bit PE part)
Ideally, all memory allocation should be done in PE side (userspace), which has access to HeapAlloc(). Failing that, we can use NtAllocateVirtualMemory.
- exception handling?
To accurately mimic stack unwinding as it happens on Windows, one should stick to the syscall interface instead of the unixlib interface. This way, unix-side stack frames will never appear in RtlVirtualUnwind() output (since they are effectively kernel-mode frames, not user-mode ones).
Actually, there will be two distinct tables:
- one used for the invocation from 64 bit PE side,
- the second one used for the invocation from 32 bit PE side.
(yes, assuming 64-bit wine build with wow64 support)
Upon invocation, their argument will be mapped to the 32 bit or the 64 bit layout of the argument structure. This allows either to adapt the implementation to any difference between the 32 and 64 bit worlds.
So the interface between the PE module and its .so counterpart has to be designed with care:
- as already stated, no callback from ELF part into PE part is available,
- some objects can be defined differently from both sides:
- integers (int) doesn't have the same size - pointers are either 32 bit or 64 bit entities... so if memory has to be returned to PE side, it must be in lower part of memory (at least for 32 bit "syscalls") - ...
So the migration of a module from the "old" model to this PE model requires to:
- define the API between the PE and the ELF parts
- ensure that code in the ELF doesn't call back into PE APIs
- call through the Unixlib mechanism between the PE and ELF part.
- the Unixlib call mechanism is available even if the module is generated in the
old model, hence allowing to put the unixlib API in place still using the old model. When all is migrated to the Unixlib interface, module can be generated using the new module.
Gradual migration is one of the explicit goals of the unixlib infrastructure, yes.
(it may be that after reading this you need either/or a) a break, b) a deep breath, c) check your aspirin stock and d) restart from the beginning)
TODO:
- debugging
- sketch the ARM/Intel on Darwin
Additional bits:
From the .so files that Wine used to link to, some of them are not tied to the physical system, and can be available directly as PE modules (eg. JPEG decoder, XML reader/writer...). The most important of those libraries are cross-compiled, and their source code inserted into Wine tree (see libs/). It's also possible to load the system-wide equivalent PE modules when installed.
It's still not clear to me how the new module wow64 will be activated.
See https://www.winehq.org/pipermail/wine-devel/2022-April/213620.html
[1]: on 64 bit, Windows chose the LLP64 integer model, while Unix chose the LP64 one (sur. The main difference is that long is 32 bit on Windows and 64 bit on Unix. In the old model, as compilation was native, the Windows types were redefined to fit the LP64 model (like DWORD was an unsigned int to get the right size). With the migration to the new model, as compilation of PE modules is done on LLP64 models, DWORD (and friends) are now defined as on Windows as an unsigned long. This is called the migration to long types (90% of modules migrated) and takes place along side the PE migration.