WineHQ
WineHQ

8.3. Multi-processing in Wine

Let's take a closer look at the way Wine loads and run processes in memory.

8.3.1. Starting a process from command line

When starting a Wine process from command line (we'll get later on to the differences between NE, PE and Winelib executables), there are a couple of things Wine need to do first. A first executable is run to check the threading model of the underlying OS (see Section 8.4 for the details) and will start the real Wine loader corresponding to the choosen threading model.

Then Wine graps a few elements from the Unix world: the environment, the program arguments. Then the ntdll.dll.so is loaded into memory using the standard shared library dynamic loader. When loaded, NTDLL will mainly first create a decent Windows environment:

  • create a PEB (Process Environment Block) and a TEB (Thread Environment Block).

  • set up the connection to the Wine server - and eventually launching the Wine server if none runs

  • create the process heap

Then Kernel32 is loaded (but now using the Windows dynamic loading capabilities) and a Wine specific entry point is called __wine_kernel_init. This function will actually handle all the logic of the process loading and execution, and will never return from it's call.

__wine_kernel_init will undergo the following tasks:

  • initialization of program arguments from Unix program arguments

  • lookup of executable in the file system

  • If the file is not found, then an error is printed and the Wine loader stops.

  • We'll cover the non-PE file type later on, so assume for now it's a PE file. The PE module is loaded in memory using the same mechanisms as for a native DLLs (mainly mapping the file data and code sections into memory, and handling relocation if needed). Note that the dependencies on the module are not resolved at this point.

  • A new stack is created, which size is given in the PE header, and this stack is made the one of the running thread (which is still the only one in the process). The stack used at startup will no longer be used.

  • Which this new stack, ntdll.LdrInitializeThunk is called which performs the remaining initialization parts, including resolving all the DLL imports on the PE module, and doing the init of the TLS slots.

  • Control can now be passed to the EntryPoint of the PE module, which will let the executable run.

8.3.2. Creating a child process from a running process

The steps used are closely link to what is done in the previous case.

There are however a few points to look at a bit more closely. The inner implementation creates the child process using the fork() and exec() calls. This means that we don't need to check again for the threading model, we can use what the parent (or the grand-parent process...) started from command line has found.

The Win32 process creation allows to pass a lot of information between the parent and the child. This includes object handles, windows title, console parameters, environment strings... Wine makes use of both the standard Unix inheritance mechanisms (for environment for example) and the Wine server (to pass from parent to child a chunk of data containing the relevant information).

The previously described loading mechanism will check in the Wine server if such a chunk exists, and, if so, will perform the relevant initialization.

Some further synchronization is also put in place: a parent will wait until the child has started, or has failed. The Wine server is also used to perform those tasks.

8.3.3. Starting a Winelib process

Before going into the gory details, let's first go back to what a Winelib application is. It can be either a regular Unix executable, or a more specific Wine beast. This later form in fact creates two files for a given executable (say foo.exe). The first one, named foo will be a symbolic link to the Wine loader (wine). The second one, named foo.exe.so, is the equivalent of the .dll.so files we've already described for DLLs. As in Windows, an executable is, among other things, a module with its import and export information, as any DLL, it makes sense Wine uses the same mechanisms for loading native executables and DLLs.

When starting a Winelib application from the command line (say with foo arg1 arg2), the Unix shell will execute foo as a Unix executable. Since this is in fact the Wine loader, Wine will fire up. However, it will notice that it hasn't been started as wine but as foo, and hence, will try to load (using Unix shared library mechanism) the second file foo.exe.so. Wine will recognize a 32 bit module (with its descriptor) embedded in the shared library, and once the shared library loaded, it will proceed the same path as when loading a standard native PE executable.

Wine needs to implement this second form of executable in order to maintain the order of initialization of some elements in the executable. One particular issue is when dealing with global C++ objects. In standard Unix executable, the call of the constructor to such objects is stored in the specific section of the executable (.init not to name it). All constructors in this section are called before the main() or WinMain function is called. Creating a Wine executable using the first form mentionned above will let those constructors being called before Wine gets a chance to initialize itself. So, any constructor using a Windows API will fail, because Wine infrastructure isn't in place. The use of the second form for Winelib executables ensures that we do the initialization using the following steps:

  • initialize the Wine infrastructure

  • load the executable into memory

  • handle the import sections for the executable

  • call the global object constructors (if any). They now can properly call the Windows APIs

  • call the executable entry point

The attentive reader would have noted that the resolution of imports for the executable is done, as for a DLL, when the executable/DLL descriptor is registered. However, this is done also by adding a specific constructor in the .init section. For the above describe scheme to function properly, this constructor must be the first constructor to be called, before all the other constructors, generated by the executable itself. The Wine build chain takes care of that, and also generating the executable/DLL descriptor for the Winelib executable.