目录

Wine

  1. 編譯 Wine。
    $ git clone git://source.winehq.org/git/wine.git
    $ mkdir build; cd build
    # 只運行 Windows 64 bit 應用程序。如果要運行 32 bit 的話,系統上要裝 multilib 版本的函式庫。
    $ ../wine/configure --prefix=$INSTALL --enable-win64
    $ make
  2. 運行 Wine。
    $ vncserver
    xauth:  file /nfs_home/chenwj/.Xauthority does not exist
     
    # 底下 i71:x 中的 x 請記下
    New 'i71:1 (chenwj)' desktop is i71:1
     
    Starting applications specified in /nfs_home/chenwj/.vnc/xstartup
    Log file is /nfs_home/chenwj/.vnc/i71:1.log
     
    # PuTTY 0.62 64-bit Builds
    # http://www.dzinovic.net/projects/windows/putty-64-bit-builds/
    $ ./wine putty.exe
    # 用 vnc client 連入伺服器。
    $ make install
    $ wine64 putty.exe
    $ ls $HOME/.wine
    dosdevices  drive_c  system.reg  user.reg  userdef.reg

概觀

以下以 wine 1.5.9 為例。

請先了解 Windows 架構,Standard Windows Architectures。Windows 內核 ,ntoskrnl.exe 1),一般稱為 NT 內核,不同 Windows 版本使用的均是 NT 內核,差別在於版本號 2)。NT 內核並不暴露給上層應用程序,用戶態中的 NTDLL.DLL 負責扮演應用程序眼中的 NT 內核。再往上,分別有 Windows、OS/2 和 POSIX 子系統,目前僅剩 Windows 子系統 (Kernel32.DLL)。再往上,分別有 GDI32.DLL 和 USER32.DLL。GDI32.DLL (Graphical Device Interface) 負責處理應用程序與螢幕或是印表機這一類週邊的互動; USER32.DLL 則是讓應用程序可以處理使用介面 3)。在 GDI32.DLL 和 USER32.DLL 之上,是各種直接與應用程序交互的 DLL。

WINE 的架構請見 Wine architecture。一個負責運行 Windows 應用程序的 Wine 進程透過 socket 將 Windows 應用程序呼叫 Windows API 的要求送至 wineserver。詳細請見 Kernel modules

每一個欲運行的 Windows 程序,在 Unix 上皆被視作一個 Unix 進程。Win16 (16 bit) 為協作式多工,因此用單一個 Unix 執行緒運行 Win16。Wine 會開啟獨立一個 Unix 進程 (WOW,Windows on Windows) 執行多個 Win16,Win16 之間透過鎖達到協作式多工。

wineserver 扮演的角色在 漫谈Wine之一: WINE的系统结构 有詳述。wineserver 基本上是在 Linux 內核之外,即用戶態,提供對 Windows 內核的補充,它負責提供 Windows 內核服務給 Windows 應用程序。漫谈Wine之二: Windows的文件操作 以 Windows 和 Linux 對文件操作上的差異為例,說明 wineserver 之所以存在的原因。

The Wine server provides the backbone for the implementation of the core DLLs. It mainly implementents inter-process synchronization and object sharing. It can be seen, from a functional point of view, as a NT kernel, although the APIs and protocols used between Wine's DLL and the Wine server are Wine specific. http://www.ecsl.cs.sunysb.edu/tr/TR179.pdf

每一個 Windows 進程由多個 Windows 執行緒組成,每一個 Windows 執行緒對應到底下一個 Unix 進程,同屬一個 Windows 進程的 Unix 進程共享同一個位址空間。每一個 Unix 進程有兩個 ID,分別為: Unix 進程 ID (upid) 和 Windows 執行緒 ID (tid)。Unix 進程 ID 不同於所屬 Windows 進程的 ID (wpid)。

  1. 可以配合 winedbg 查看運行的流程。
    $ export DISPLAY=:1
    $ winedbg notepad.exe
    $ b CreateFile
  2. 使用 gdb 監看 wineserver。注意! 如果 wineserver 陷入斷點,會導致 Windows 應用程序沒有反應。
    # 獨立運行,不因 Windows 應用程序結束而結束。
    $ wineserver -f -p
    $ gdb attach $WINESERVER_PID
    (gdb) break create_file_for_fd
  3. 使用 gdb 監看 wine64-preloader,只有這樣才能定位到 wld_start
    $ gdb --directory=/local/chenwj/temp/tmp/ wine64-preloader
    (gdb) b wld_start
    (gdb) r ~/install/bin/wine64 ~/.wine/drive_c/windows/notepad.exe

映像檔裝載

  1. dlls/kernel32/kernel_private.h 定義可以處理的二進制格式。
    /* return values for MODULE_GetBinaryType */
    enum binary_type
    {
        BINARY_UNKNOWN = 0,
        BINARY_PE,
        BINARY_WIN16,
        BINARY_OS216,
        BINARY_DOS,
        BINARY_UNIX_EXE,
        BINARY_UNIX_LIB
    };
  2. MODULE_get_binary_info (dlls/kernel32/module.c)。
    void MODULE_get_binary_info( HANDLE hfile, struct binary_info *info )
    {
        union
        {
            struct
            {
                unsigned char magic[4];
                unsigned char class;
                unsigned char data;
                unsigned char version;
                unsigned char ignored[9];
                unsigned short type;
                unsigned short machine;
            } elf; // Linux ELF
            struct
            {
                unsigned int magic;
                unsigned int cputype;
                unsigned int cpusubtype;
                unsigned int filetype;
            } macho; // Apple Mach
            IMAGE_DOS_HEADER mz; // Windows
        } header; // 映像檔頭部
     
        // 讀出頭部。
         /* Seek to the start of the file and read the header information. */
        if (SetFilePointer( hfile, 0, NULL, SEEK_SET ) == -1) return;
        if (!ReadFile( hfile, &header, sizeof(header), &len, NULL ) || len != sizeof(header)) return;
     
        // 判別是 Linux、Mach 亦或是 Windows 映像檔。
        /* Not ELF, try DOS */
        else if (header.mz.e_magic == IMAGE_DOS_SIGNATURE)
        {
            union
            {
                IMAGE_OS2_HEADER os2;
                IMAGE_NT_HEADERS32 nt;
            } ext_header;
     
            /* We do have a DOS image so we will now try to seek into
             * the file by the amount indicated by the field
             * "Offset to extended header" and read in the
             * "magic" field information at that location.
             * This will tell us if there is more header information
             * to read or not.
             */
            info->type = BINARY_DOS;
            if (SetFilePointer( hfile, header.mz.e_lfanew, NULL, SEEK_SET ) == -1) return;
            if (!ReadFile( hfile, &ext_header, sizeof(ext_header), &len, NULL ) || len < 4) return;
     
            /* Reading the magic field succeeded so
             * we will try to determine what type it is.
             */
            if (!memcmp( &ext_header.nt.Signature, "PE\0\0", 4 ))
            {
                if (len >= sizeof(ext_header.nt.FileHeader))
                {
                    info->type = BINARY_PE;
                    if (ext_header.nt.FileHeader.Characteristics & IMAGE_FILE_DLL)
                        info->flags |= BINARY_FLAG_DLL;
                    if (len < sizeof(ext_header.nt))  /* clear remaining part of header if missing */
                        memset( (char *)&ext_header.nt + len, 0, sizeof(ext_header.nt) - len );
                    switch (ext_header.nt.OptionalHeader.Magic)
                    {
                    case IMAGE_NT_OPTIONAL_HDR32_MAGIC:
                        info->res_start = (void *)(ULONG_PTR)ext_header.nt.OptionalHeader.ImageBase;
                        info->res_end = (void *)((ULONG_PTR)ext_header.nt.OptionalHeader.ImageBase +
                                                         ext_header.nt.OptionalHeader.SizeOfImage);
                        break;
                    case IMAGE_NT_OPTIONAL_HDR64_MAGIC:
                        info->flags |= BINARY_FLAG_64BIT;
                        break;
                    }
                }
            }
            else if (!memcmp( &ext_header.os2.ne_magic, "NE", 2 ))
            {
                /* This is a Windows executable (NE) header.  This can
                 * mean either a 16-bit OS/2 or a 16-bit Windows or even a
                 * DOS program (running under a DOS extender).  To decide
                 * which, we'll have to read the NE header.
                 */
                if (len >= sizeof(ext_header.os2))
                {
                    if (ext_header.os2.ne_flags & NE_FFLAGS_LIBMODULE) info->flags |= BINARY_FLAG_DLL;
                    switch ( ext_header.os2.ne_exetyp )
                    {
                    case 1:  info->type = BINARY_OS216; break; /* OS/2 */
                    case 2:  info->type = BINARY_WIN16; break; /* Windows */
                    case 3:  info->type = BINARY_DOS; break; /* European MS-DOS 4.x */
                    case 4:  info->type = BINARY_WIN16; break; /* Windows 386; FIXME: is this 32bit??? */
                    case 5:  info->type = BINARY_DOS; break; /* BOSS, Borland Operating System Services */
                    /* other types, e.g. 0 is: "unknown" */
                    default: info->type = MODULE_Decide_OS2_OldWin(hfile, &header.mz, &ext_header.os2); break;
                    }
                }
            }
        }
    }
    • include/winnt.h 定義 struct IMAGE_DOS_HEADER,此為 DOS 頭部。e_lfanew 指向 PE 頭部。
      typedef struct _IMAGE_DOS_HEADER {
          WORD  e_magic;      /* 00: MZ Header signature */
          WORD  e_cblp;       /* 02: Bytes on last page of file */
          WORD  e_cp;         /* 04: Pages in file */
          WORD  e_crlc;       /* 06: Relocations */
          WORD  e_cparhdr;    /* 08: Size of header in paragraphs */
          WORD  e_minalloc;   /* 0a: Minimum extra paragraphs needed */
          WORD  e_maxalloc;   /* 0c: Maximum extra paragraphs needed */
          WORD  e_ss;         /* 0e: Initial (relative) SS value */
          WORD  e_sp;         /* 10: Initial SP value */
          WORD  e_csum;       /* 12: Checksum */
          WORD  e_ip;         /* 14: Initial IP value */
          WORD  e_cs;         /* 16: Initial (relative) CS value */
          WORD  e_lfarlc;     /* 18: File address of relocation table */
          WORD  e_ovno;       /* 1a: Overlay number */
          WORD  e_res[4];     /* 1c: Reserved words */
          WORD  e_oemid;      /* 24: OEM identifier (for e_oeminfo) */
          WORD  e_oeminfo;    /* 26: OEM information; e_oemid specific */
          WORD  e_res2[10];   /* 28: Reserved words */
          DWORD e_lfanew;     /* 3c: Offset to extended header */
      } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
    • include/winnt.h 定義 struct IMAGE_NT_HEADERS,此為 PE 頭部。OptionalHeader 並非如字面上可有可無,其中定義執行檔入口位址,相當重要。
      typedef struct _IMAGE_NT_HEADERS {
        DWORD Signature; /* "PE"\0\0 */       /* 0x00 */
        IMAGE_FILE_HEADER FileHeader;         /* 0x04 */
        IMAGE_OPTIONAL_HEADER32 OptionalHeader;       /* 0x18 */
      } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

初始化

Wine的二进制映像装入和启动 一文中提到,Wine 載入 Windows 映像和創建其進程是一個較為複雜的過程。在 Windows 和 Linux 平台上,它們各別只負責載入 DOS/PE 或是 ELF 映像檔,並透過 CreateProcessW 或是 execve 創建進程。Wine 是在 Linux 上運行,系統中的始祖為 Linux 進程,其後代 Linux 進程必須負責創建 Windows 進程; 此外,該 Windows 進程可能還會透過 CreateProcessW 創建其它 Windows 進程。因此 Wine 中載入映像檔分為底下兩種情況:

當下 wine notepad.exe 命令時,先由 wine 負責初始化,控制權再移交給 notepad.exe。初始化工作包括建立與 wineserver 的連接、準備 notepad.exe 運行環境,等等。流程大致為: wine64wine64-preloaderwine64notepad.exe 4)

  1. wine 入口函式為 main (loader/main.c)。
    int main( int argc, char *argv[] )
    {
        // 透過設置環境變數 WINELOADERNOEXEC,從 wine64-preloader 再次載入 wine64 並運行的時候,
         // 就可以跳過底下這段運行 wine64-preloader 的代碼。
         // wine64-preloader 主要任務是確保 PE 需要的內存空間不被占用。
         if (!getenv( "WINELOADERNOEXEC" ))  /* first time around */
        {
            static char noexec[] = "WINELOADERNOEXEC=1";
     
            putenv( noexec );
            check_command_line( argc, argv );
            if (pre_exec())
            {
                wine_init_argv0_path( argv[0] );
                wine_exec_wine_binary( NULL, argv, getenv( "WINELOADER" ));
                fprintf( stderr, "wine: could not exec the wine loader\n" );
                exit(1);
            }
        }
     
        ... 略 ...
     
        // 開始模擬。
         wine_init( argc, argv, error, sizeof(error) );
        fprintf( stderr, "wine: failed to initialize: %s\n", error );
        exit(1);
    }
    • pre_exec (loader/main.c) 在 x86 上會檢查 threading model 和其它東西,x86_64 則是直接返回 1。
    • wine_exec_wine_binary (libs/wine/config.c) 運行 wineserver 或是其它 Wine 所需的執行檔,如: wine-preloaderwineserver
      // 參數分別為: NULL, "wine notepad.exe", 環境變數 WINELOADER 的值,一般為 NULL。
      /* exec a wine internal binary (either the wine loader or the wine server) */
      void wine_exec_wine_binary( const char *name, char **argv, const char *env_var )
      {   
          // argv0_name 是前述 wine_init_argv0_path 所設置,一般是 wine。 
          if (!name) name = argv0_name;  /* no name means default loader */
       
          // 若 name 以 wineserver 結尾,不使用 preloader。一般情況下使用 preloader。
          use_preloader = !strendswith( name, "wineserver" );
       
          ... 略 ...
       
          // 搜尋 wine 所在的目錄,首先搜尋安裝 wine 的目錄。
          /* first, bin directory from the current libdir or argv0 */
          if (bindir)
          {
              argv[0] = build_path( bindir, name );
              // argv = "wine notepad.exe"
              preloader_exec( argv, use_preloader );
              free( argv[0] );
          }
       
          ... 略 ...
      }
    • preloader_exec (libs/wine/config.c)。一般情況下,use_preloader 為真。
      /* exec a binary using the preloader if requested; helper for wine_exec_wine_binary */
      static void preloader_exec( char **argv, int use_preloader )
      {
          if (use_preloader)
          {
              static const char preloader[] = "wine-preloader";
              static const char preloader64[] = "wine64-preloader";
              char *p, *full_name;
              char **last_arg = argv, **new_argv;
       
              if (!(p = strrchr( argv[0], '/' ))) p = argv[0];
              else p++;
       
              full_name = xmalloc( p - argv[0] + sizeof(preloader64) );
              memcpy( full_name, argv[0], p - argv[0] );
              if (strendswith( p, "64" ))
                  memcpy( full_name + (p - argv[0]), preloader64, sizeof(preloader64) );
              else
                  memcpy( full_name + (p - argv[0]), preloader, sizeof(preloader) );
       
              /* make a copy of argv */
              while (*last_arg) last_arg++;
              new_argv = xmalloc( (last_arg - argv + 2) * sizeof(*argv) );
              memcpy( new_argv + 1, argv, (last_arg - argv + 1) * sizeof(*argv) );
              new_argv[0] = full_name;
              // argv 為: wine64, notepad.exe。
                // new_argv 為: wine64-preloader,wine64, notepad.exe。
                execv( full_name, new_argv );
              free( new_argv );
              free( full_name );
          }
          execv( argv[0], argv );
      }
    • wine64-preloader 為例。進入點為 _start。加總效果為: wine64-preloader wine64 notpad.exe。注意! 漫谈兼容内核之七: Wine的二进制映像装入和启动 中提到的 wine-kthread 已不存在。
      void _start(void);
      extern char _end[];
      __ASM_GLOBAL_FUNC(_start,
                        "movq %rsp,%rax\n\t"
                        "leaq -144(%rsp),%rsp\n\t" /* allocate some space for extra aux values */
                        "movq %rax,(%rsp)\n\t"     /* orig stack pointer */
                        "movq $thread_data,%rsi\n\t"
                        "movq $0x1002,%rdi\n\t"    /* ARCH_SET_FS */
                        "movq $158,%rax\n\t"       /* SYS_arch_prctl */
                        "syscall\n\t"
                        "movq %rsp,%rdi\n\t"       /* ptr to orig stack pointer */
                        "call wld_start\n\t"
                        "movq (%rsp),%rsp\n\t"     /* new stack pointer */
                        "pushq %rax\n\t"           /* ELF interpreter entry point */
                        "xorq %rax,%rax\n\t"
                        "xorq %rcx,%rcx\n\t"
                        "xorq %rdx,%rdx\n\t"
                        "xorq %rsi,%rsi\n\t"
                        "xorq %rdi,%rdi\n\t"
                        "xorq %r8,%r8\n\t"
                        "xorq %r9,%r9\n\t"
                        "xorq %r10,%r10\n\t"
                        "xorq %r11,%r11\n\t"
                        "ret")                     // ret 會將前面壓入的 ELF 映像解释器入口地址出棧,跳轉至該處執行。
      • Makefile.in 可知 wine64-preloader 自定義 _start 而不用 glibc_start,且不使用一般的函式庫。
        wine-preloader wine64-preloader: preloader.o Makefile.in
                $(CC) -o $@ -static -nostartfiles -nodefaultlibs -Wl,-Ttext=0x7c400000 preloader.o $(LIBPORT) $(LDFLAGS)
  2. wld_start (loader/preloader.c)。
    /*
     *  wld_start
     *
     *  Repeat the actions the kernel would do when loading a dynamically linked .so
     *  Load the binary and then its ELF interpreter.
     *  Note, we assume that the binary is a dynamically linked ELF shared object.
     */
    void* wld_start( void **stack )
    {
        ... 略 ...
     
        // argv: wine64-preloader, wine64, notepad.exe 
        argv = (char **)pargc + 1;
     
        // 跳過前述參數,定位到 argv 之後的位址。
        /* skip over the parameters */
        p = argv + *pargc + 1;
     
        // 跳過環境變數。
        /* skip over the environment */
        while (*p)
        {
            static const char res[] = "WINEPRELOADRESERVE=";
            if (!wld_strncmp( *p, res, sizeof(res)-1 )) reserve = *p + sizeof(res) - 1;
            p++;
        }
     
        // 替 PE 預留空間。
        /* reserve memory that Wine needs */
        if (reserve) preload_reserve( reserve );
        for (i = 0; preload_info[i].size; i++)
        {
            if ((char *)av >= (char *)preload_info[i].addr &&
                (char *)pargc <= (char *)preload_info[i].addr + preload_info[i].size)
            {
                remove_preload_range( i );
                i--;
            }
            else if (wld_mmap( preload_info[i].addr, preload_info[i].size, PROT_NONE,
                               MAP_FIXED | MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0 ) == (void *)-1)
            {
                /* don't warn for low 64k */
                if (preload_info[i].addr >= (void *)0x10000)
                    wld_printf( "preloader: Warning: failed to reserve range %p-%p\n",
                                preload_info[i].addr, (char *)preload_info[i].addr + preload_info[i].size );
                remove_preload_range( i );
                i--;
            }
        }
     
        // 載入 wine64。
        /* load the main binary */
        map_so_lib( argv[1], &main_binary_map );
     
        // 載入 wine64 ELF 中指定的 interpreter,如: /lib64/ld-linux-x86-64.so.2。
        // 它負責後續動態連結。
        /* load the ELF interpreter */
        interp = (char *)main_binary_map.l_addr + main_binary_map.l_interp;
        map_so_lib( interp, &ld_so_map );
     
        // argv: wine64. notepad.exe
        /* get rid of first argument */
        set_process_name( *pargc, argv );
        pargc[1] = pargc[0] - 1;
        *stack = pargc + 1;
     
        // ELF 映像解释器的入口地址。因為 x86 返回值存放在 %eax 暫存器,後續組語會從 %eax 取得此值。
        return (void *)ld_so_map.l_entry;
    }
    • preload_info (loader/preloader.c) 存放欲保留的區段其起始位址和大小。
      static struct wine_preload_info preload_info[] =
      {
      #ifdef __i386__
          { (void *)0x00000000, 0x00010000 },  /* low 64k */
          { (void *)0x00010000, 0x00100000 },  /* DOS area */
          { (void *)0x00110000, 0x67ef0000 },  /* low memory area */
          { (void *)0x7f000000, 0x03000000 },  /* top-down allocations + shared heap + virtual heap */
      #else
          { (void *)0x000000010000, 0x00100000 },  /* DOS area */
          { (void *)0x000000110000, 0x67ef0000 },  /* low memory area */
          { (void *)0x00007ff00000, 0x000f0000 },  /* shared user data */
          { (void *)0x7ffffe000000, 0x01ff0000 },  /* top-down allocations + virtual heap */
      #endif
          { 0, 0 },                            /* PE exe range set with WINEPRELOADRESERVE */
          { 0, 0 }                             /* end of list */
      };
    • wld_start 返回後,會從 ELF 解釋器開始執行,再跳至 wine64main 函式,跳過運行 wine64-preloader 代碼,執行 wine_init
  3. wine_init (libs/wine/loader.c)。漫谈兼容内核之七: Wine的二进制映像装入和启动 一文中針對 wine_init 有所描述:
    • 通过 dlopen_dll 装入由 Wine 提供的动态连接库 ntdll.dll。由于 Wine 特殊的系统结构,它不能使用微软的 ntdll.dll,而只能使用 Wine 自己的 DLL,这样的 DLL 称为“内置(built-in)”DLL。
    • 执行内置 ntdll.dll 中的函数 wine_process_init。先通过 wine_dlsym 取得这个函数的入口地址,然后通过指针 init_func 进行调用。之所以要以这样的方式调用,是因为这个函数在 ntdll.dll中,而 dlopen_dll 只是装入了这个 DLL、却并未完成与此模块的动态链接。
      void wine_init( int argc, char *argv[], char *error, int error_size )
      {
          ... 略 ...
       
          wine_init_argv0_path( argv[0] );
          // 根据环境变量 WINEDLLPATH 设置好装入各种 DLL 的目录路径。这是为装入 DLL 做好准备。
           build_dll_path();
          // 这里把调用参数 argc、argv、environ 转移到了 __wine_main_argc 等全局量中。
          __wine_main_argc = argc;
          __wine_main_argv = argv;
          __wine_main_environ = __wine_get_main_environment();
          mmap_init();
       
          // 載入 NTDLL.DLL。
          for (path = first_dll_path( "ntdll.dll", 0, &context ); path; path = next_dll_path( &context ))
          {
              if ((ntdll = wine_dlopen( path, RTLD_NOW, error, error_size )))
              {
                  /* if we didn't use the default dll dir, remove it from the search path */
                  if (default_dlldir[0] && context.index < nb_dll_paths + 2) nb_dll_paths--;
                  break;
              }
          }
       
          if (!(init_func = wine_dlsym( ntdll, "__wine_process_init", error, error_size ))) return;
          init_func(); // wine_process_init
      }
  4. wine_process_init (dlls/ntdll/loader.c) 初始化 PE 所需要的 TEB (即建立該 Windows 進程中的第一個線程),建立與 wineserver 的連線 (若 wineserver 不存在,則建立之),並在 wine_init 載入 ntdll.dll 之後,再載入其上層的 kernel32.dll,此為內置 kernel32.dll,再呼叫 wine_kernel_init
    void __wine_process_init(void)
    {
        static const WCHAR kernel32W[] = {'k','e','r','n','e','l','3','2','.','d','l','l',0};
     
        WINE_MODREF *wm;
        NTSTATUS status;
        ANSI_STRING func_name;
        void (* DECLSPEC_NORETURN CDECL init_func)(void);
     
        // 初始化 TEB。
         main_exe_file = thread_init();
     
        /* retrieve current umask */
        FILE_umask = umask(0777);
        umask( FILE_umask );
     
        load_global_options();
     
        /* setup the load callback and create ntdll modref */
        wine_dll_set_callback( load_builtin_callback );
     
        // 載入 Kernel32.DLL。
        if ((status = load_builtin_dll( NULL, kernel32W, 0, 0, &wm )) != STATUS_SUCCESS)
        {
            MESSAGE( "wine: could not load kernel32.dll, status %x\n", status );
            exit(1);
        }
        RtlInitAnsiString( &func_name, "UnhandledExceptionFilter" );
        LdrGetProcedureAddress( wm->ldr.BaseAddress, &func_name, 0, (void **)&unhandled_exception_filter );
     
        RtlInitAnsiString( &func_name, "__wine_kernel_init" );
        if ((status = LdrGetProcedureAddress( wm->ldr.BaseAddress, &func_name,
                                              0, (void **)&init_func )) != STATUS_SUCCESS)
        {
            MESSAGE( "wine: could not find __wine_kernel_init in kernel32.dll, status %x\n", status );
            exit(1);
        }
        init_func(); // wine_kernel_init
    }
    • thread_init (dlls/ntdll/thread.c) 負責建立 TEB (Thread Environment Block),這是線程在用戶態的控制塊。初始化虛擬內存空間、PEB (Process Environment Block)
      HANDLE thread_init(void)
      {
          ... 略 ...
       
          // 初始化虛擬內存空間。
          virtual_init();
       
          /* allocate and initialize the PEB */
          addr = NULL;
          size = sizeof(*peb);
          NtAllocateVirtualMemory( NtCurrentProcess(), &addr, 1, &size,
                                   MEM_COMMIT | MEM_TOP_DOWN, PAGE_READWRITE );
       
          ... 略 ...
       
          /* allocate and initialize the initial TEB */
          signal_alloc_thread( &teb );
          teb->Peb = peb;
          teb->Tib.StackBase = (void *)~0UL;
          teb->StaticUnicodeString.Buffer = teb->StaticUnicodeBuffer;
          teb->StaticUnicodeString.MaximumLength = sizeof(teb->StaticUnicodeBuffer);
       
          ... 略 ...
       
          /* setup the server connection */
          // 試圖建立與 wineserver 的 socket,若 wineserver 不存在則建立之。
           server_init_process();
          // 建立與 wineserver 的 pipe。
           info_size = server_init_thread( peb );
       
          /* create the process heap */
       
          ... 略 ...
      }
    • server_init_process (dlls/ntdll/server.c)。
      void server_init_process(void)
      {
          ... 略 ...
       
          const char *env_socket = getenv( "WINESERVERSOCKET" );
       
          server_pid = -1;
          if (env_socket)
          {
              fd_socket = atoi( env_socket );
              if (fcntl( fd_socket, F_SETFD, 1 ) == -1)
                  fatal_perror( "Bad server socket %d", fd_socket );
              unsetenv( "WINESERVERSOCKET" );
          }
          else fd_socket = server_connect();
       
          ... 略 ...
      }
      • server_connect (dlls/ntdll/server.c)。
        static int server_connect(void)
        {
            ... 略 ...
         
            /* chdir to the server directory */
            if (chdir( serverdir ) == -1)
            {
                if (errno != ENOENT) fatal_perror( "chdir to %s", serverdir );
                start_server();
                if (chdir( serverdir ) == -1) fatal_perror( "chdir to %s", serverdir );
            }
         
         
            ... 略 ...
        }
      • start_server (dlls/ntdll/server.c) 建立子進程,並呼叫 wine_exec_wine_binary 載入 wineserver
        static void start_server(void)
        {
            static int started;  /* we only try once */
            char *argv[3];
            static char wineserver[] = "server/wineserver";
            static char debug[] = "-d";
         
            if (!started)
            {
                int status;
                int pid = fork();
                if (pid == -1) fatal_perror( "fork" );
                // 子進程。
                 if (!pid)
                {
                    argv[0] = wineserver;
                    argv[1] = TRACE_ON(server) ? debug : NULL;
                    argv[2] = NULL;
                    wine_exec_wine_binary( argv[0], argv, getenv("WINESERVER") );
                    fatal_error( "could not exec wineserver\n" );
                }
                waitpid( pid, &status, 0 );
                status = WIFEXITED(status) ? WEXITSTATUS(status) : 1;
                if (status == 2) return;  /* server lock held by someone else, will retry later */
                if (status) exit(status);  /* server failed */
                started = 1;
            }
        }
    • server_init_thread (dlls/ntdll/server.c)。
      size_t server_init_thread( void *entry_point )
      {
          static const int is_win64 = (sizeof(void *) > sizeof(int));
          const char *arch = getenv( "WINEARCH" );
          int ret;
          int reply_pipe[2];
          struct sigaction sig_act;
          size_t info_size;
       
          sig_act.sa_handler = SIG_IGN;
          sig_act.sa_flags   = 0;
          sigemptyset( &sig_act.sa_mask );
       
          /* ignore SIGPIPE so that we get an EPIPE error instead  */
          sigaction( SIGPIPE, &sig_act, NULL );
       
          /* create the server->client communication pipes */
          if (server_pipe( reply_pipe ) == -1) server_protocol_perror( "pipe" );
          if (server_pipe( ntdll_get_thread_data()->wait_fd ) == -1) server_protocol_perror( "pipe" );
          wine_server_send_fd( reply_pipe[1] );
          wine_server_send_fd( ntdll_get_thread_data()->wait_fd[1] );
          ntdll_get_thread_data()->reply_fd = reply_pipe[0];
          close( reply_pipe[1] );
       
          SERVER_START_REQ( init_thread )
          {
              req->unix_pid    = getpid();
              req->unix_tid    = get_unix_tid();
              req->teb         = wine_server_client_ptr( NtCurrentTeb() );
              req->entry       = wine_server_client_ptr( entry_point );
              req->reply_fd    = reply_pipe[1];
              req->wait_fd     = ntdll_get_thread_data()->wait_fd[1];
              req->debug_level = (TRACE_ON(server) != 0);
              req->cpu         = client_cpu;
              ret = wine_server_call( req );
              NtCurrentTeb()->ClientId.UniqueProcess = ULongToHandle(reply->pid);
              NtCurrentTeb()->ClientId.UniqueThread  = ULongToHandle(reply->tid);
              info_size         = reply->info_size;
              server_start_time = reply->server_start;
              server_cpus       = reply->all_cpus;
          }
          SERVER_END_REQ;
       
          is_wow64 = !is_win64 && (server_cpus & (1 << CPU_x86_64)) != 0;
          ntdll_get_thread_data()->wow64_redir = is_wow64;
       
          switch (ret)
          {
          case STATUS_SUCCESS:
              if (arch)
              {
                  if (!strcmp( arch, "win32" ) && (is_win64 || is_wow64))
                      fatal_error( "WINEARCH set to win32 but '%s' is a 64-bit installation.\n",
                                   wine_get_config_dir() );
                  if (!strcmp( arch, "win64" ) && !is_win64 && !is_wow64)
                      fatal_error( "WINEARCH set to win64 but '%s' is a 32-bit installation.\n",
                                   wine_get_config_dir() );
              }
              return info_size;
          case STATUS_NOT_REGISTRY_FILE:
              fatal_error( "'%s' is a 32-bit installation, it cannot support 64-bit applications.\n",
                           wine_get_config_dir() );
          case STATUS_NOT_SUPPORTED:
              if (is_win64)
                  fatal_error( "wineserver is 32-bit, it cannot support 64-bit applications.\n" );
              else
                  fatal_error( "'%s' is a 64-bit installation, it cannot be used with a 32-bit wineserver.\n",
                               wine_get_config_dir() );
          default:
              server_protocol_error( "init_thread failed with status %x\n", ret );
          }
      }
  5. wine_kernel_init (dlls/kernel32/process.c) 載入 Windows 應用程序的映像檔。漫谈兼容内核之十: Windows的进程创建和映像装入
    void CDECL __wine_kernel_init(void)
    {
        static const WCHAR kernel32W[] = {'k','e','r','n','e','l','3','2',0};
        static const WCHAR dotW[] = {'.',0};
     
        WCHAR *p, main_exe_name[MAX_PATH+1];
        PEB *peb = NtCurrentTeb()->Peb;
     
        ... 略 ...
     
        // 將新載入的 wine64 改名成 notepad.exe。
         set_process_name( __wine_main_argc, __wine_main_argv );
     
        ... 略 ...
     
        if (peb->ProcessParameters->ImagePathName.Buffer)
        {
            strcpyW( main_exe_name, peb->ProcessParameters->ImagePathName.Buffer );
        }
        else
        {
            struct binary_info binary_info;
     
            if (!SearchPathW( NULL, __wine_main_wargv[0], exeW, MAX_PATH, main_exe_name, NULL ) &&
                !get_builtin_path( __wine_main_wargv[0], exeW, main_exe_name, MAX_PATH, &binary_info ))
            {
                MESSAGE( "wine: cannot find '%s'\n", __wine_main_argv[0] );
                ExitProcess( GetLastError() );
            }
            update_library_argv0( main_exe_name );
            if (!build_command_line( __wine_main_wargv )) goto error;
            start_wineboot( boot_events ); // 載入 Windows 應用程序映像檔。
        }
     
        ... 略 ...
     
        LdrInitializeThunk( start_process, 0, 0, 0 );
    }
  6. LdrInitializeThunk (dlls/ntdll/loader.c) 近似 /lib/ld-linux-x86-64.so.2,負責用户空间的初始化和 DLL 的连接。漫谈兼容内核之十: Windows的进程创建和映像装入漫谈兼容内核之十一: Windows DLL的装入和连接。在进入这个函数之前,目标 EXE 映像已经被映射到当前进程的用户空间,系统 DLL ntdll.dll 的映像也已经被映射,但是并没有在 EXE 映像与 ntdll.dll 映像之间建立连接。LdrInitializeThunkntdll.dll 中不经连接就可进入的函数,实质上就是 ntdll.dll 的入口。PEB 中的另一个字段 Ldr 是个 PEB_LDR_DATA 结构指针,所指向的数据结构用来为本进程维持三个“模块”队列、即 InLoadOrderModuleList、InMemoryOrderModuleList、和 InInitializationOrderModuleList。这里所谓“模块”就是 PE 格式的可执行映像,包括 EXE 映像和 DLL 映像。前两个队列都是模块队列,第三个是初始化队列。两个模块队列的不同之处在于排列的次序,一个是按装入的先后,一个是按装入的位置(实际上目前ReactOS的代码中并未使用这个队列)。每当为本进程装入一个模块、即 .exe 映像或 DLL 映像时,就要为其分配/创建一个 LDR_MODULE 数据结构,并将其挂入InLoadOrderModuleList。然后,完成对这个模块的动态连接以后,就把它挂入 InInitializationOrderModuleList 队列,以便依次调用它们的初始化函数。相应地,LDR_MODULE 数据结构中有三个队列头,因而可以同时挂在三个队列中。
    • Thunk
      void WINAPI LdrInitializeThunk( void *kernel_start, ULONG_PTR unknown2,
                                      ULONG_PTR unknown3, ULONG_PTR unknown4 )
      {
          // 取得當前進程控制塊,PEB。
           PEB *peb = NtCurrentTeb()->Peb;
          // 取得映像檔在內存的位址。
          IMAGE_NT_HEADERS *nt = RtlImageNtHeader( peb->ImageBaseAddress );
       
          ... 略 ...
       
          // 檢查是否為執行檔亦或是 DLL。
          LdrQueryImageFileExecutionOptions( &peb->ProcessParameters->ImagePathName, globalflagW,
                                             REG_DWORD, &peb->NtGlobalFlag, sizeof(peb->NtGlobalFlag), NULL );
       
          // 映像檔必須先載入,爾後再載入其相依的 DLL。
          /* the main exe needs to be the first in the load order list */
          RemoveEntryList( &wm->ldr.InLoadOrderModuleList );
          InsertHeadList( &peb->LdrData->InLoadOrderModuleList, &wm->ldr.InLoadOrderModuleList );
       
          ... 略 ...
       
          // start_process 是 dlls/ntdll/loader.c 裡的 start_process,
           // kernel_start 是 dlls/kernel32/process.c 裡的 start_process。
           wine_switch_to_stack( start_process, kernel_start, NtCurrentTeb()->Tib.StackBase );
       
      error:
          ERR( "Main exe initialization for %s failed, status %x\n",
               debugstr_w(peb->ProcessParameters->ImagePathName.Buffer), status );
          NtTerminateProcess( GetCurrentProcess(), status );
      }
  7. wine_switch_to_stack (libs/wine/port.c)。
    /***********************************************************************
     *           wine_switch_to_stack
     *
     * Switch to the specified stack and call the function.
     */
    // start_process, kernel_start, NtCurrentTeb()->Tib.StackBase 
    void DECLSPEC_NORETURN wine_switch_to_stack( void (*func)(void *), void *arg, void *stack )
    {
        wine_call_on_stack( (int (*)(void *))func, arg, stack );
        abort();
    }
  8. wine_call_on_stack (libs/wine/port.c)。
    #elif defined(__x86_64__) && defined(__GNUC__)
    __ASM_GLOBAL_FUNC( wine_call_on_stack,
                       "pushq %rbp\n\t"
                       __ASM_CFI(".cfi_adjust_cfa_offset 8\n\t")
                       __ASM_CFI(".cfi_rel_offset %rbp,0\n\t")
                       "movq %rsp,%rbp\n\t"
                       __ASM_CFI(".cfi_def_cfa_register %rbp\n\t")
                       "movq %rdi,%rax\n\t" /* func */
                       "movq %rsi,%rdi\n\t" /* arg */
                       "andq $~15,%rdx\n\t" /* stack */
                       "movq %rdx,%rsp\n\t"
                       "callq *%rax\n\t"    /* call func 跳轉至 start_process */
                       "movq %rbp,%rsp\n\t"
                       __ASM_CFI(".cfi_def_cfa_register %rsp\n\t")
                       "popq %rbp\n\t"
                       __ASM_CFI(".cfi_adjust_cfa_offset -8\n\t")
                       __ASM_CFI(".cfi_same_value %rbp\n\t")
                       "ret")
  9. start_process (dlls/ntdll/loader.c)。注意! 這裡的 kernel_startdlls/kernel32/process.c 裡的 start_process
    static void start_process( void *kernel_start )
    {
        call_thread_entry_point( kernel_start, NtCurrentTeb()->Peb );
    }
  10. call_thread_entry_point (dlls/ntdll/signal_x86_64.c)。
    extern void DECLSPEC_NORETURN call_thread_entry_point( LPTHREAD_START_ROUTINE entry, void *arg );
    __ASM_GLOBAL_FUNC( call_thread_entry_point,
                       "subq $56,%rsp\n\t"
                       __ASM_CFI(".cfi_adjust_cfa_offset 56\n\t")
                       "movq %rbp,48(%rsp)\n\t"
                       __ASM_CFI(".cfi_rel_offset %rbp,48\n\t")
                       "movq %rbx,40(%rsp)\n\t"
                       __ASM_CFI(".cfi_rel_offset %rbx,40\n\t")
                       "movq %r12,32(%rsp)\n\t"
                       __ASM_CFI(".cfi_rel_offset %r12,32\n\t")
                       "movq %r13,24(%rsp)\n\t"
                       __ASM_CFI(".cfi_rel_offset %r13,24\n\t")
                       "movq %r14,16(%rsp)\n\t"
                       __ASM_CFI(".cfi_rel_offset %r14,16\n\t")
                       "movq %r15,8(%rsp)\n\t"
                       __ASM_CFI(".cfi_rel_offset %r15,8\n\t")
                       "movq %rsp,%rdx\n\t"
                       "call " __ASM_NAME("call_thread_func") );
  11. call_thread_func (dlls/ntdll/signal_x86_64.c)。
    void call_thread_func( LPTHREAD_START_ROUTINE entry, void *arg, void *frame )
    {
        ntdll_get_thread_data()->exit_frame = frame;
        __TRY
        {
            RtlExitUserThread( entry( arg )); // 執行前面 LdrInitializeThunk 傳入的 start_process。
        }
        __EXCEPT(unhandled_exception_filter)
        {
            NtTerminateThread( GetCurrentThread(), GetExceptionCode() );
        }
        __ENDTRY
        abort();  /* should not be reached */
    }
  12. start_process (dlls/kernel32/process.c)。
    static DWORD WINAPI start_process( PEB *peb )
    {
        IMAGE_NT_HEADERS *nt;
        LPTHREAD_START_ROUTINE entry;
     
        nt = RtlImageNtHeader( peb->ImageBaseAddress );
        entry = (LPTHREAD_START_ROUTINE)((char *)peb->ImageBaseAddress +
                                         nt->OptionalHeader.AddressOfEntryPoint);
     
        if (!nt->OptionalHeader.AddressOfEntryPoint)
        {
            ERR( "%s doesn't have an entry point, it cannot be executed\n",
                 debugstr_w(peb->ProcessParameters->ImagePathName.Buffer) );
            ExitThread( 1 );
        }
     
        if (TRACE_ON(relay))
            DPRINTF( "%04x:Starting process %s (entryproc=%p)\n", GetCurrentThreadId(),
                     debugstr_w(peb->ProcessParameters->ImagePathName.Buffer), entry );
     
        SetLastError( 0 );  /* clear error code */
        if (peb->BeingDebugged) DbgBreakPoint();
        return call_process_entry( peb, entry );
    }
     
    static inline DWORD 
    call_process_entry( PEB *peb, LPTHREAD_START_ROUTINE entry )
    {
        return entry( peb );
    }

實現 Windows API

漫谈Wine之二: Windows的文件操作 描述 Windows 和 Linux 對於文件上的處理之差異。對於應用程序而言,文件 API (語意上的) 差異最為重要,至於底下是何種檔案格式則次之,基本上對上層應用程序而言不可見。漫谈Wine之二: Windows的文件操作 提及 Windows 和 Linux 在文件上處理的差異基本上有底下三點。注意! 基本上還要結合 Windows 和 Linux 在創建進程時的差異一起來看,才能比較清楚了解。

于是,Windows的打开文件系统调用可以这样实现:

NtOpenFile() {
 
   ... 略 ...
 
   fd = open();
 
   if (允许遗传)
      ioctl(fd, FIONCLEX)// 執行 exec 系統呼叫时不关闭 fd。
    else
      ioctl(fd, FIOCLEX)// 執行 exec 系統呼叫时关闭 fd。
}

問題在於,Windows 允许在创建子进程时再作一次选择。如果选择不遗传,那么所有的已打开文件都不遗传。而 Linux 却不提供这一次选择,Linux 創建子進程時會將父進程所有的已打开文件都遗传给子进程。读者也许会想,如果我们在用户空间也提供一个位图、作为内核中本进程的 close_on_exec 位图在用户空间的副本,再让 NtOpenFile 在设置内核中的位图时 (我懷疑此處說的是核內補) 也设置一下用户空间的副本,最后让子进程根据这个副本位图有针对地关闭文件,这样不就可以提高效率吗?然而问题在于:一般而言,父进程用户空间的内容是不能遗传给子进程的。实际上,作为一条准则,凡是要遗传给子进程的东西,就不能只是保存在父进程的用户空间,而必须保存在别的什么地方,一般是内核中或是另一个进程(例如服务进程)中。以打開文件表為例,此表即是保存在內核資料結構。

这样一来,应用软件层面的所谓跨进程句柄复制,对于 Linux 内核而言却只是服务进程 (wineserver) 内部的句柄复制,类似于 dup。这就实现了对于句柄复制的“核外补”。而且,这一来别的问题也可以随之而得倒类似的解决。例如有条件遗传/继承的问题,就因为不再使用“客户”进程在内核中的打开文件表,而改成使用由服务进程为各个“客户”进程维持的“幻影”打开文件表,从而变得简单了。

  1. OpenFile 為例,在 dlls/kernel32/file.c 有其對應的實現。
    HFILE WINAPI OpenFile( LPCSTR name, OFSTRUCT *ofs, UINT mode )
    {
        HANDLE handle;
        FILETIME filetime;
        WORD filedatetime[2];
     
        ... 略 ...
     
        if (mode & OF_CREATE)
        {
            if ((handle = create_file_OF( name, mode )) == INVALID_HANDLE_VALUE)
                goto error;
        }
     
        ... 略 ...
    }
     
    static HANDLE create_file_OF( LPCSTR path, INT mode )
    {
        ... 略 ...
     
        return CreateFileA( path, access, sharing, NULL, creation, FILE_ATTRIBUTE_NORMAL, 0 );
    }
     
    HANDLE WINAPI CreateFileA( LPCSTR filename, DWORD access, DWORD sharing,
                               LPSECURITY_ATTRIBUTES sa, DWORD creation,
                               DWORD attributes, HANDLE template)
    {
        WCHAR *nameW;
     
        if (!(nameW = FILE_name_AtoW( filename, FALSE ))) return INVALID_HANDLE_VALUE;
        return CreateFileW( nameW, access, sharing, sa, creation, attributes, template );
    }
     
    HANDLE WINAPI CreateFileW( LPCWSTR filename, DWORD access, DWORD sharing,
                                  LPSECURITY_ATTRIBUTES sa, DWORD creation,
                                  DWORD attributes, HANDLE template )
    {
        ... 略 ...
     
        status = NtCreateFile( &ret, access, &attr, &io, NULL, attributes,
                               sharing, nt_disposition[creation - CREATE_NEW],
                               options, NULL, 0 );
     
        ... 略 ...
    }
  2. CreateFile 向下呼叫到 ntdll.dll 中的 NtCreateFile (dlls/ntdll/file.c),最後呼叫到 FILE_CreateFile (dlls/ntdll/file.c)。
    NTSTATUS WINAPI NtCreateFile( PHANDLE handle, ACCESS_MASK access, POBJECT_ATTRIBUTES attr,
                                  PIO_STATUS_BLOCK io, PLARGE_INTEGER alloc_size,
                                  ULONG attributes, ULONG sharing, ULONG disposition,
                                  ULONG options, PVOID ea_buffer, ULONG ea_length )
    {
        return FILE_CreateFile( handle, access, attr, io, alloc_size, attributes,
                                sharing, disposition, options, ea_buffer, ea_length );
    }
     
    static NTSTATUS FILE_CreateFile( PHANDLE handle, ACCESS_MASK access, POBJECT_ATTRIBUTES attr,
                                     PIO_STATUS_BLOCK io, PLARGE_INTEGER alloc_size,
                                     ULONG attributes, ULONG sharing, ULONG disposition,
                                     ULONG options, PVOID ea_buffer, ULONG ea_length )
    {
        ... 略 ...
     
        // 送出命令給 Wine server。
        if (io->u.Status == STATUS_BAD_DEVICE_TYPE)
        {
            SERVER_START_REQ( open_file_object )
            {
                req->access     = access;
                req->attributes = attr->Attributes;
                req->rootdir    = wine_server_obj_handle( attr->RootDirectory );
                req->sharing    = sharing;
                req->options    = options;
                wine_server_add_data( req, attr->ObjectName->Buffer, attr->ObjectName->Length );
                io->u.Status = wine_server_call( req );
                *handle = wine_server_ptr_handle( reply->handle );
            }
            SERVER_END_REQ;
            if (io->u.Status == STATUS_SUCCESS) io->Information = FILE_OPENED;
            return io->u.Status;
        }
     
        ... 略 ...
    }
    • include/wine/server.h 定義前述巨集。漫谈Wine之一: WINE的系统结构 解釋此巨集的內容。
      struct __server_request_info
      {
          union
          {
              union generic_request req;    /* request structure */
              union generic_reply   reply;  /* reply structure */
          } u;
          unsigned int          data_count; /* count of request data pointers */
          void                 *reply_data; /* reply data pointer */
          struct __server_iovec data[__SERVER_MAX_DATA];  /* request variable size data */
      };
       
      #define SERVER_START_REQ(type) \
          do { \
              struct __server_request_info __req; \
              struct type##_request * const req = &__req.u.req.type##_request; \
              const struct type##_reply * const reply = &__req.u.reply.type##_reply; \
              memset( &__req.u.req, 0, sizeof(__req.u.req) ); \
              __req.u.req.request_header.req = REQ_##type; \
              __req.data_count = 0; \
              (void)reply; \
              do
       
      #define SERVER_END_REQ \
              while(0); \
          } while(0)
      • 把发送给服务进程的数据结构视为 struct open_file_object_request,而把服务进程的答复则视为 struct open_file_object_reply,調用號為 REQ_open_file_object
      • 展開後,為底下這樣。
            do { \
                struct __server_request_info __req; \
                struct open_file_object_request * const req = &__req.u.req.open_file_object_request; \
                const struct open_file_object_reply * const reply = &__req.u.reply.open_file_object_reply; \
                memset( &__req.u.req, 0, sizeof(__req.u.req) ); \
                __req.u.req.request_header.req = REQ_open_file_object; \
                __req.data_count = 0; \
                (void)reply; \
                do
                {
                    req->access     = access;
                    req->attributes = attr->Attributes;
                    req->rootdir    = wine_server_obj_handle( attr->RootDirectory );
                    req->sharing    = sharing;
                    req->options    = options;
                    wine_server_add_data( req, attr->ObjectName->Buffer, attr->ObjectName->Length );
                    io->u.Status = wine_server_call( req );
                    *handle = wine_server_ptr_handle( reply->handle );
                }
                while(0); \
          } while(0)
      • struct open_file_object_request (include/wine/server_protocol.h)。
        struct open_file_object_request
        {
            struct request_header __header;
            unsigned int access;
            unsigned int attributes;
            obj_handle_t rootdir;
            unsigned int sharing;
            unsigned int options;
            /* VARARG(filename,unicode_str); */
        };
  3. wine_server_call (dlls/ntdll/server.c) 向 wineserver 發送請求,並等待其回應。
    unsigned int wine_server_call( void *req_ptr )
    {
        struct __server_request_info * const req = req_ptr;
        sigset_t old_set;
        unsigned int ret;
     
        pthread_sigmask( SIG_BLOCK, &server_block_set, &old_set );
        ret = send_request( req );
        if (!ret) ret = wait_reply( req );
        pthread_sigmask( SIG_SETMASK, &old_set, NULL );
        return ret;
    }
    • send_request (dlls/ntdll/server.c) 将请求写入通向服务进程的管道。
      static unsigned int send_request( const struct __server_request_info *req )
      {
          unsigned int i;
          int ret;
       
          if (!req->u.req.request_header.request_size)
          {
              if ((ret = write( ntdll_get_thread_data()->request_fd, &req->u.req,
                                sizeof(req->u.req) )) == sizeof(req->u.req)) return STATUS_SUCCESS;
       
          }
          else
          {
              ... 略 ...
          }
      }
    • wait_reply (dlls/ntdll/server.c)。
      static inline unsigned int wait_reply( struct __server_request_info *req )
      {
          read_reply_data( &req->u.reply, sizeof(req->u.reply) );
          if (req->u.reply.reply_header.reply_size)
              read_reply_data( req->reply_data, req->u.reply.reply_header.reply_size );
          return req->u.reply.reply_header.error;
      }
       
      static void read_reply_data( void *buffer, size_t size )
      {
          int ret;
       
          for (;;)
          {
              if ((ret = read( ntdll_get_thread_data()->reply_fd, buffer, size )) > 0)
              {
                  if (!(size -= ret)) return;
                  buffer = (char *)buffer + ret;
                  continue;
              }
              if (!ret) break;
              if (errno == EINTR) continue;
              if (errno == EPIPE) break;
              server_protocol_perror("read");
          }
          /* the server closed the connection; time to die... */
          abort_thread(0);
      }
  4. wineserver 相對應的 handler 為 open_file_object (server/fd.c)。
    /* open a file object */
    DECL_HANDLER(open_file_object)
    {
        struct unicode_str name;
        struct directory *root = NULL;
        struct object *obj, *result;
     
        get_req_unicode_str( &name );
        if (req->rootdir && !(root = get_directory_obj( current->process, req->rootdir, 0 )))
            return;
     
        if ((obj = open_object_dir( root, &name, req->attributes, NULL )))
        {
            if ((result = obj->ops->open_file( obj, req->access, req->sharing, req->options )))
            {
                reply->handle = alloc_handle( current->process, result, req->access, req->attributes );
                release_object( result );
            }
            release_object( obj );
        }
     
        if (root) release_object( root );
    }

NtCreateProcess

位在 ntdll.dll 中的 NtCreateProcess 並未實現,創建進程在 kernel32.dll 中的 CreateProcessACreateProcessW 完成,不再往下調用 NtCreateProcess。在 漫谈兼容内核之七: Wine的二进制映像装入和启动 描述了透過 Wine 運行的 Windows 應用程序呼叫 CreateProcess 建立另一個 Windows 進程的流程。

  1. CreateProcessW (dlls/kernel32/process.c)。
    BOOL WINAPI DECLSPEC_HOTPATCH CreateProcessW( LPCWSTR app_name, LPWSTR cmd_line, LPSECURITY_ATTRIBUTES process_attr,
                                                  LPSECURITY_ATTRIBUTES thread_attr, BOOL inherit, DWORD flags,
                                                  LPVOID env, LPCWSTR cur_dir, LPSTARTUPINFOW startup_info,
                                                  LPPROCESS_INFORMATION info )
    {
        return create_process_impl( app_name, cmd_line, process_attr, thread_attr,
                                    inherit, flags, env, cur_dir, startup_info, info);
    }
  2. create_process_impl (dlls/kernel32/process.c)。
    static BOOL create_process_impl( LPCWSTR app_name, LPWSTR cmd_line, LPSECURITY_ATTRIBUTES process_attr,
                                     LPSECURITY_ATTRIBUTES thread_attr, BOOL inherit, DWORD flags,
                                     LPVOID env, LPCWSTR cur_dir, LPSTARTUPINFOW startup_info,
                                     LPPROCESS_INFORMATION info )
    {
        ... 略 ...
     
        if (binary_info.flags & BINARY_FLAG_DLL)
        {
            TRACE( "not starting %s since it is a dll\n", debugstr_w(name) );
            SetLastError( ERROR_BAD_EXE_FORMAT );
        }
        else switch (binary_info.type)
        {
        case BINARY_PE:
            TRACE( "starting %s as Win%d binary (%p-%p)\n",
                   debugstr_w(name), (binary_info.flags & BINARY_FLAG_64BIT) ? 64 : 32,
                   binary_info.res_start, binary_info.res_end );
            retv = create_process( hFile, name, tidy_cmdline, envW, cur_dir, process_attr, thread_attr,
                                   inherit, flags, startup_info, info, unixdir, &binary_info, FALSE );
            break;
     
        ... 略 ...
     
        }
     
        ... 略 ...
    }
  3. create_process (dlls/kernel32/process.c)。
    static BOOL create_process( HANDLE hFile, LPCWSTR filename, LPWSTR cmd_line, LPWSTR env,
                                LPCWSTR cur_dir, LPSECURITY_ATTRIBUTES psa, LPSECURITY_ATTRIBUTES tsa,
                                BOOL inherit, DWORD flags, LPSTARTUPINFOW startup,
                                LPPROCESS_INFORMATION info, LPCSTR unixdir,
                                const struct binary_info *binary_info, int exec_only )
    {
        ... 略 ...
     
        if (exec_only)  /* things are much simpler in this case */
        {
            wine_server_send_fd( socketfd[1] );
            close( socketfd[1] );
            SERVER_START_REQ( new_process )
            {
                req->create_flags   = flags;
                req->socket_fd      = socketfd[1];
                req->exe_file       = wine_server_obj_handle( hFile );
                ret = !wine_server_call_err( req );
            }
            SERVER_END_REQ;
     
            if (ret) exec_loader( cmd_line, flags, socketfd[0], stdin_fd, stdout_fd, unixdir,
                                  winedebug, binary_info, TRUE );
     
            close( socketfd[0] );
            return FALSE;
        }
     
        ... 略 ...
    }
  4. exec_loader (dlls/kernel32/process.c)。
    static pid_t exec_loader( LPCWSTR cmd_line, unsigned int flags, int socketfd,
                              int stdin_fd, int stdout_fd, const char *unixdir, char *winedebug,
                              const struct binary_info *binary_info, int exec_only )
    {
        pid_t pid;
        char *wineloader = NULL;
        const char *loader = NULL;
        char **argv;
     
        argv = build_argv( cmd_line, 1 );
     
        if (!is_win64 ^ !(binary_info->flags & BINARY_FLAG_64BIT))
            loader = get_alternate_loader( &wineloader );
     
        if (exec_only || !(pid = fork()))  /* child */
        {
            if (exec_only || !(pid = fork()))  /* grandchild */
            {
                char preloader_reserve[64], socket_env[64];
     
                if (flags & (CREATE_NEW_PROCESS_GROUP | CREATE_NEW_CONSOLE | DETACHED_PROCESS))
                {
                    int fd = open( "/dev/null", O_RDWR );
                    setsid();
                    /* close stdin and stdout */
                    if (fd != -1)
                    {
                        dup2( fd, 0 );
                        dup2( fd, 1 );
                        close( fd );
                    }
                }
                else
                {
                    if (stdin_fd != -1) dup2( stdin_fd, 0 );
                    if (stdout_fd != -1) dup2( stdout_fd, 1 );
                }
     
                if (stdin_fd != -1) close( stdin_fd );
                if (stdout_fd != -1) close( stdout_fd );
     
                /* Reset signals that we previously set to SIG_IGN */
                signal( SIGPIPE, SIG_DFL );
     
                sprintf( socket_env, "WINESERVERSOCKET=%u", socketfd );
                sprintf( preloader_reserve, "WINEPRELOADRESERVE=%lx-%lx",
                         (unsigned long)binary_info->res_start, (unsigned long)binary_info->res_end );
     
                putenv( preloader_reserve );
                putenv( socket_env );
                if (winedebug) putenv( winedebug );
                if (wineloader) putenv( wineloader );
                if (unixdir) chdir(unixdir);
     
                if (argv)
                {
                    do
                    {
                        wine_exec_wine_binary( loader, argv, getenv("WINELOADER") );
                    }
                    while (0);
                }
                _exit(1);
            }
     
            _exit(pid == -1);
        }
     
        if (pid != -1)
        {
            /* reap child */
            pid_t wret;
            do {
                wret = waitpid(pid, NULL, 0);
            } while (wret < 0 && errno == EINTR);
        }
     
        HeapFree( GetProcessHeap(), 0, wineloader );
        HeapFree( GetProcessHeap(), 0, argv );
        return pid;
    }
  1. CreateProcess 為例,wineserver 的處理流程。
      main (server/main.c) -> main_loop (server/fd.c) -> main_loop_epoll (server/fd.c) -> fd_poll_event (server/fd.c)
        -> thread_poll_event (server/thread.c) -> read_request (server/request.c)
        -> call_req_handler (server/request.c) -> req_new_process (server/process.c)
        -> create_process (server/process.c)
  2. main (server/main.c)。
    int main( int argc, char *argv[] )
    {
        setvbuf( stderr, NULL, _IOLBF, 0 );
        parse_args( argc, argv );
     
        /* setup temporary handlers before the real signal initialization is done */
        signal( SIGPIPE, SIG_IGN );
        signal( SIGHUP, sigterm_handler );
        signal( SIGINT, sigterm_handler );
        signal( SIGQUIT, sigterm_handler );
        signal( SIGTERM, sigterm_handler );
        signal( SIGABRT, sigterm_handler );
     
        sock_init();
        open_master_socket();
     
        if (debug_level) fprintf( stderr, "wineserver: starting (pid=%ld)\n", (long) getpid() );
        init_signals();
        init_directories();
        init_registry();
        main_loop();
        return 0;
    }
  3. main_loop (server/fd.c)。
    void main_loop(void)
    {
        int i, ret, timeout;
     
        set_current_time();
        server_start_time = current_time;
     
        main_loop_epoll();
        /* fall through to normal poll loop */
     
        while (active_users)
        {
            timeout = get_next_timeout();
     
            if (!active_users) break;  /* last user removed by a timeout */
     
            ret = poll( pollfd, nb_users, timeout );
            set_current_time();
     
            if (ret > 0)
            {
                for (i = 0; i < nb_users; i++)
                {
                    if (pollfd[i].revents)
                    {
                        fd_poll_event( poll_users[i], pollfd[i].revents );
                        if (!--ret) break;
                    }
                }
            }
        }
    }
  4. read_request (server/request.c)。
    /* read a request from a thread */
    void read_request( struct thread *thread )
    {
        int ret;
     
        if (!thread->req_toread)  /* no pending request */
        {
            if ((ret = read( get_unix_fd( thread->request_fd ), &thread->req,
                             sizeof(thread->req) )) != sizeof(thread->req)) goto error;
            if (!(thread->req_toread = thread->req.request_header.request_size))
            {
                /* no data, handle request at once */
                call_req_handler( thread );
                return;
            }
            if (!(thread->req_data = malloc( thread->req_toread )))
            {
                fatal_protocol_error( thread, "no memory for %u bytes request %d\n",
                                      thread->req_toread, thread->req.request_header.req );
                return;
            }
        }
     
        ... 略 ...
    }
  5. call_req_handler (server/request.c)
    /* call a request handler */
    static void call_req_handler( struct thread *thread )
    {
        ... 略 ...
     
        if (req < REQ_NB_REQUESTS)
            req_handlers[req]( &current->req, &reply );
        else
            set_error( STATUS_NOT_IMPLEMENTED );
     
        ... 略 ...
     
    }
    • req_handlers (request.h) 定義 wineserver 提供給 wine 有哪些服務。
      typedef void (*req_handler)( const void *req, void *reply );
      static const req_handler req_handlers[REQ_NB_REQUESTS] =
      {
          (req_handler)req_new_process,
          (req_handler)req_get_new_process_info,
          (req_handler)req_new_thread,
          (req_handler)req_get_startup_info,
       
          ... 略 ...
       
      };
  6. wineserver 相對應的 handler 為 new_process (server/process.c)。
    DECL_HANDLER(new_process)
    {
        struct startup_info *info;
        struct thread *thread;
        struct process *process;
        struct process *parent = current->process;
        int socket_fd = thread_get_inflight_fd( current, req->socket_fd );
     
        if (socket_fd == -1)
        {
            set_error( STATUS_INVALID_PARAMETER );
            return;
        }
        if (fcntl( socket_fd, F_SETFL, O_NONBLOCK ) == -1)
        {
            set_error( STATUS_INVALID_HANDLE );
            close( socket_fd );
            return;
        }
        if (shutdown_stage)
        {
            set_error( STATUS_SHUTDOWN_IN_PROGRESS );
            close( socket_fd );
            return;
        }
     
        if (!req->info_size)  /* create an orphaned process */
        {
            create_process( socket_fd, NULL, 0 );
            return;
        }
     
        /* build the startup info for a new process */
        if (!(info = alloc_object( &startup_info_ops ))) return;
        info->exe_file = NULL;
        info->process  = NULL;
        info->data     = NULL;
     
        if (req->exe_file &&
            !(info->exe_file = get_file_obj( current->process, req->exe_file, FILE_READ_DATA )))
            goto done;
     
        info->data_size = get_req_data_size();
        info->info_size = min( req->info_size, info->data_size );
     
        if (req->info_size < sizeof(*info->data))
        {
            /* make sure we have a full startup_info_t structure */
            data_size_t env_size = info->data_size - info->info_size;
            data_size_t info_size = min( req->info_size, FIELD_OFFSET( startup_info_t, curdir_len ));
     
            if (!(info->data = mem_alloc( sizeof(*info->data) + env_size ))) goto done;
            memcpy( info->data, get_req_data(), info_size );
            memset( (char *)info->data + info_size, 0, sizeof(*info->data) - info_size );
            memcpy( info->data + 1, (const char *)get_req_data() + req->info_size, env_size );
            info->info_size = sizeof(startup_info_t);
            info->data_size = info->info_size + env_size;
        }
        else
        {
            data_size_t pos = sizeof(*info->data);
     
            if (!(info->data = memdup( get_req_data(), info->data_size ))) goto done;
    #define FIXUP_LEN(len) do { (len) = min( (len), info->info_size - pos ); pos += (len); } while(0)
            FIXUP_LEN( info->data->curdir_len );
            FIXUP_LEN( info->data->dllpath_len );
            FIXUP_LEN( info->data->imagepath_len );
            FIXUP_LEN( info->data->cmdline_len );
            FIXUP_LEN( info->data->title_len );
            FIXUP_LEN( info->data->desktop_len );
            FIXUP_LEN( info->data->shellinfo_len );
            FIXUP_LEN( info->data->runtime_len );
    #undef FIXUP_LEN
        }
     
        if (!(thread = create_process( socket_fd, current, req->inherit_all ))) goto done;
        process = thread->process;
        process->debug_children = !(req->create_flags & DEBUG_ONLY_THIS_PROCESS);
        process->startup_info = (struct startup_info *)grab_object( info );
        /* connect to the window station */
        connect_process_winstation( process, current );
     
        /* thread will be actually suspended in init_done */
        if (req->create_flags & CREATE_SUSPENDED) thread->suspend++;
     
        /* set the process console */
        if (!(req->create_flags & (DETACHED_PROCESS | CREATE_NEW_CONSOLE)))
        {
            /* FIXME: some better error checking should be done...
             * like if hConOut and hConIn are console handles, then they should be on the same
             * physical console
             */
            inherit_console( current, process, req->inherit_all ? info->data->hstdin : 0 );
        }
     
        if (!req->inherit_all && !(req->create_flags & CREATE_NEW_CONSOLE))
        {
            info->data->hstdin  = duplicate_handle( parent, info->data->hstdin, process,
                                                    0, OBJ_INHERIT, DUPLICATE_SAME_ACCESS );
            info->data->hstdout = duplicate_handle( parent, info->data->hstdout, process,
                                                    0, OBJ_INHERIT, DUPLICATE_SAME_ACCESS );
            info->data->hstderr = duplicate_handle( parent, info->data->hstderr, process,
                                                    0, OBJ_INHERIT, DUPLICATE_SAME_ACCESS );
            /* some handles above may have been invalid; this is not an error */
            if (get_error() == STATUS_INVALID_HANDLE ||
                get_error() == STATUS_OBJECT_TYPE_MISMATCH) clear_error();
        }
     
        /* attach to the debugger if requested */
        if (req->create_flags & (DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS))
            set_process_debugger( process, current );
        else if (parent->debugger && parent->debug_children)
            set_process_debugger( process, parent->debugger );
     
        if (!(req->create_flags & CREATE_NEW_PROCESS_GROUP))
            process->group_id = parent->group_id;
     
        info->process = (struct process *)grab_object( process );
        reply->info = alloc_handle( current->process, info, SYNCHRONIZE, 0 );
        reply->pid = get_process_id( process );
        reply->tid = get_thread_id( thread );
        reply->phandle = alloc_handle( parent, process, req->process_access, req->process_attr );
        reply->thandle = alloc_handle( parent, thread, req->thread_access, req->thread_attr );
     
     done:
        release_object( info );
    }

NtWriteFile

漫谈Wine之三: Wine的文件读写 描述 Wine 如何處有效率的處理 Windows 應用程序呼叫 WriteFile。以写文件操作为例,客户进程先准备好一个缓冲区,然后调用 API 上的相应函数,就 Wine 而言是 WriteFile。这是由Windows 的 Win32 API 规定的,类似于 Linux上的 fwriteWriteFile 这个函数在动态连接库 kernel32.dll 中,执行的结果是向下调用另一个动态连接库 ntdll.dll 中的 NtWriteFile。在 Windows 系统中,ntdll.dll 中的 NtWriteFile 直接进行系统调用,调用内核中的 NtWriteFile,就像 Linux 的 C 库程序 fwrite 直接进行系统调用、调用内核中的 sys_write 一样。可是,如果采用的是文件服务进程的方法,NtWriteFile 就需要通过进程间通信把操作请求连同缓冲区的内容发送给文件服务进程。

为防止进程间通信过程中的缓冲区复制,Wine 利用了通过 Socket 实现文件访问授权的办法。我们知道,Unix 域的 Socket 有个特殊的功能,就是在通过 sendmsgrecvmsg 传递报文的同时让发送端进程将对其已打开文件的访问权授予接收端进程(读者可参阅“情景分析下册第73-113页”)5)服务进程将对于目标文件的访问权授予客户进程之后,客户进程就可以直接进行读/写,而不必再由服务进程代劳、更不必因传递缓冲区内容而进行缓冲区复制了。

  1. WriteFile (dlls/kernel32/file.c)。WriteFile
    BOOL WINAPI WriteFile( HANDLE hFile, LPCVOID buffer, DWORD bytesToWrite,
                           LPDWORD bytesWritten, LPOVERLAPPED overlapped )
    {
        ... 略 ...
     
        status = NtWriteFile(hFile, hEvent, NULL, cvalue, piosb,
                             buffer, bytesToWrite, poffset, NULL);
     
        ... 略 ...
    }
  2. NtWriteFile (dlls/ntdll/file.c)。NtCreateFile
    NTSTATUS WINAPI NtWriteFile(HANDLE hFile, HANDLE hEvent,
                                PIO_APC_ROUTINE apc, void* apc_user,
                                PIO_STATUS_BLOCK io_status,
                                const void* buffer, ULONG length,
                                PLARGE_INTEGER offset, PULONG key)
    {
        ... 略 ...
     
        // Windows 應用程序送出 handle 給 wineserver,wineserver 返回其對應的 UNIX fd。
        status = server_get_unix_fd( hFile, FILE_WRITE_DATA, &unix_handle,
                                     &needs_close, &type, &options );
     
        for (;;)
        {
            /* zero-length writes on sockets may not work with plain write(2) */
            if (!length && (type == FD_TYPE_MAILSLOT || type == FD_TYPE_PIPE || type == FD_TYPE_SOCKET))
                result = send( unix_handle, buffer, 0, 0 );
            else
                result = write( unix_handle, (const char *)buffer + total, length - total );
     
            ... 略 ...
        }
     
        ... 略 ...
     
    }
  3. server_get_unix_fd (dlls/ntdll/server.c)。
    int server_get_unix_fd( HANDLE handle, unsigned int wanted_access, int *unix_fd,
                            int *needs_close, enum server_fd_type *type, unsigned int *options )
    {
        ... 略 ...
     
        SERVER_START_REQ( get_handle_fd )
        {
            req->handle = wine_server_obj_handle( handle );
            if (!(ret = wine_server_call( req )))
            {
                if (type) *type = reply->type;
                if (options) *options = reply->options;
                access = reply->access;
                if ((fd = receive_fd( &fd_handle )) != -1)
                {
                    assert( wine_server_ptr_handle(fd_handle) == handle );
                    *needs_close = (!reply->cacheable ||
                                    !add_fd_to_cache( handle, fd, reply->type,
                                                      reply->access, reply->options ));
                }
                else ret = STATUS_TOO_MANY_OPENED_FILES;
            }
        }
        SERVER_END_REQ;
     
        ... 略 ...
     
        if (!ret) *unix_fd = fd; // 返回 handle 對應的 unix fd。
         return ret;
    }
    • wineserver 呼叫 req_get_handle_fd (server/fd.c) 處理上述請求。
      /* get a Unix fd to access a file */
      DECL_HANDLER(get_handle_fd)
      {
          struct fd *fd; // 定義在 server/fd.c 的資料結構,不要跟一般 unix fd 混淆。
       
           // 根据具体的客户进程,以及这个客户进程所看到的 Windows 打开文件号,即 handle,查看一个对照表,並檢查訪問權限。
           // 如果这个进程的这个 Handle 是有效的,那么服务进程这一头已经建立起从 <进程,Handle> 到目标文件在服务进程中的
           // Unix 打开文件号的映射,所以返回指向表现着这个映射关系的 struct fd 数据结构的指针 fd。
           if ((fd = get_handle_fd_obj( current->process, req->handle, 0 )))
          {
              // 然后进一步通过 get_unix_fd 取得从 <进程,Handle> 到目标文件在客户进程中的 Unix 已打开文件号的映射,
               // 并取得该文件在客户进程中的打开文件号 unix_fd。
               int unix_fd = get_unix_fd( fd );
              if (unix_fd != -1)
              {
                  reply->type = fd->fd_ops->get_fd_type( fd );
                  reply->cacheable = fd->cacheable;
                  reply->options = fd->options;
                  reply->access = get_handle_access( current->process, req->handle );
                  // 將 wineserver 這一邊的 unix fd 通知 client,client 再透過 socket 讀寫該 unix fd。
                    send_client_fd( current->process, unix_fd, req->handle );
              }
              release_object( fd );
          }
      }
    • send_client_fd (server/request.c)。
      int send_client_fd( struct process *process, int fd, obj_handle_t handle )
      {
          ... 略 ...
       
          ret = sendmsg( get_unix_fd( process->msg_fd ), &msghdr, 0 );
       
          ... 略 ...
      }

資料結構

server 目錄底下實現 Windows 抽象模型,諸如: 進程、執行緒、號誌或是檔案。

  1. struct fd (server/fd.c)。
    struct fd
    {
        struct object        obj;         /* object header */
        const struct fd_ops *fd_ops;      /* file descriptor operations */
        struct inode        *inode;       /* inode that this fd belongs to */
        struct list          inode_entry; /* entry in inode fd list */
        struct closed_fd    *closed;      /* structure to store the unix fd at destroy time */
        struct object       *user;        /* object using this file descriptor */
        struct list          locks;       /* list of locks on this fd */
        unsigned int         access;      /* file access (FILE_READ_DATA etc.) */
        unsigned int         options;     /* file options (FILE_DELETE_ON_CLOSE, FILE_SYNCHRONOUS...) */
        unsigned int         sharing;     /* file sharing mode */
        char                *unix_name;   /* unix file name */
        int                  unix_fd;     /* unix file descriptor */
        unsigned int         no_fd_status;/* status to return when unix_fd is -1 */
        unsigned int         cacheable :1;/* can the fd be cached on the client side? */
        unsigned int         signaled :1; /* is the fd signaled? */
        unsigned int         fs_locks :1; /* can we use filesystem locks for this fd? */
        int                  poll_index;  /* index of fd in poll array */
        struct async_queue  *read_q;      /* async readers of this fd */
        struct async_queue  *write_q;     /* async writers of this fd */
        struct async_queue  *wait_q;      /* other async waiters of this fd */
        struct completion   *completion;  /* completion object attached to this fd */
        apc_param_t          comp_key;    /* completion key to set in completion events */
    };

除錯

Wine 除錯分為兩個部分,一個是提供上層 Windows 程序 Windows debug API 以便其 debug 其它 Windows 程序; 二是 debug Wine 自身。

其它

外部連結