I tried to write down what I grasped for the current modules' evolution
since there's always questions floating around this subject, might be worthwhile to document it a bit
it's likely full of errors, shortcuts, misconceptions... so don't hesitate to comment/amend
Wine "old" module model:
-----------------------
Wine used to generate a module (DLL or EXE) as:
- an .so (ELF) module, compiled with a native compiler
- this .so module embeds also PE module information, so that
Wine's loader
exposes to Windows application the PE module only.
+--------------------------+
| FOOBAR.dll.so (ELF) |
| +----------------------+ |
| | PE information | |
| +----------------------+ |
+--------------------------+
Wine loader is also able to load "pure" PE modules from
.dll/.exe files.
A built-in module (FOOBAR.dll.so) can use standard ELF dynamic
linking to load
also ELF libraries (eg winepulse needs to loads libpulse.so).
This is needed to
implement in Wine most of the integration into the Unix world;
e.g: graphical
(X11 or image coding/decoding jpeg...), audio/video (pulse,
gstreamer), and
other utilities (xml/xslt, database connectivity...) to name a
few.
+--------------------------+ +-----------+
| FOOBAR.dll.so (ELF) | -------------> | helper.so |
| +----------------------+ | ELF dyn link +-----------+
| | PE information | |
| +----------------------+ |
+--------------------------+
But, this had some limitations: some applications (version
checking,
anti-crack...) were trying to open directly the FOOBAR.dll file
(as it exists on
Windows) to read some elements inside. Wine implemented "fake"
files to mimic
that PE file content.
+--------------------------+ +-----------+
| FOOBAR.dll.so (ELF) | -------------> | helper.so |
| +----------------------+ | ELF dyn link +-----------+
| | PE information | |
| +----------------------+ |
+--------------------------+
+--------------------------+
| FOOBAR.dll (Fake PE) |
+--------------------------+
But this doesn't take into account 32 vs 64 bit applications
(letting aside the
16bit part). So in fact, the global picture is closer to this:
+------------------------------+ +-----------+
| FOOBAR.dll.so (ELF 64bit) | -------------> | helper.so
|
| +------------------------+ | ELF dyn link | (64 bit) |
| | PE information (64bit) | | +-----------+
| +------------------------+ |
+------------------------------+
+------------------------------+
| FOOBAR.dll (Fake 64 bit PE) |
+------------------------------+
+------------------------------+ +-----------+
| FOOBAR.dll.so (ELF 32bit) | -------------> | helper.so
|
| +------------------------+ | ELF dyn link | (32 bit) |
| | PE information (32bit) | | +-----------+
| +------------------------+ |
+------------------------------+
+------------------------------+
| FOOBAR.dll (Fake 32 bit PE) |
+------------------------------+
This requires that both 32 and 64bit .so libraries to be
installed on the host.
But again, this started to show some limitations:
- some Linux distros and Apple are willing to drop the 32bit
support from their
packages, so the 32 bit helper.so libraries might not be
available in the
future
- Apple evolution to support M1 family chip also adds another
dimension to the
above picture (as a an application can either be compiled for
ARM/M1 or Intel
chip, so there's also a need to support invocation from both
processors (the
ABI is different, to start with).
Wine "new" PE model: (PE migration...)
-------------------
In this model, Wine requires a cross-compiler to generate
directly a PE file for
each module.
+-----------------+
| FOOBAR.dll (PE) |
+-----------------+
One of interesting benefit of this choice, is that the PE module
can now simply
link with Window's libc implementation (either msvcrt or ucrt).
In the "old"
model, the default was to use the unix libc libraries, yet
providing some other
potential differences.
For modules not requiring system .so files, this migration is
rather
simple. There are still some elements to look at [1]. But most
of the hard job
is done by the compiler and the Wine infrastructure.
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.
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)
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).
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);
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...
- 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)
- exception handling?
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.
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.
(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.
[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.