Wine
- 編譯 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
- 運行 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
概觀
請先了解 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 之間透過鎖達到協作式多工。
dlls
: 實現諸如: NTDLL.DLL、Kernel32.DLL、GDI32.DLL 和 USER32.DLL。server
: 實現wineserver
。loader
: 啟動 Wine 之前的前置處理。include/winbase.h
: 定義 Windows API。以 ReadFile 為例。# WINAPI 表示这是个定义于 Win32 API 的函数。 WINBASEAPI BOOL WINAPI ReadFile(HANDLE,LPVOID,DWORD,LPDWORD,LPOVERLAPPED);
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)。
- iOS/ARM on Andorid/ARM
- 可以配合
winedbg
查看運行的流程。$ export DISPLAY=:1 $ winedbg notepad.exe $ b CreateFile
- 使用
gdb
監看wineserver
。注意! 如果wineserver
陷入斷點,會導致 Windows 應用程序沒有反應。# 獨立運行,不因 Windows 應用程序結束而結束。 $ wineserver -f -p $ gdb attach $WINESERVER_PID (gdb) break create_file_for_fd
- 使用
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
映像檔裝載
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 };
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 中載入映像檔分為底下兩種情況:
- 在 Linux 环境下通过键盘命令启动一个 Windows 应用程序。
- 由一个 Windows 应用程序通过
CreateProcessW
再创建一个 Windows 进程。
當下 wine notepad.exe
命令時,先由 wine
負責初始化,控制權再移交給 notepad.exe
。初始化工作包括建立與 wineserver
的連接、準備 notepad.exe
運行環境,等等。流程大致為: wine64
→ wine64-preloader
→ wine64
→ notepad.exe
4)。
- A Survey on Virtualization Technologies 6.1.5 Loading an Executable
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-preloader
或wineserver
。// 參數分別為: 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)
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 解釋器開始執行,再跳至wine64
的main
函式,跳過運行wine64-preloader
代碼,執行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 }
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 ); } }
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 ); }
LdrInitializeThunk
(dlls/ntdll/loader.c
) 近似/lib/ld-linux-x86-64.so.2
,負責用户空间的初始化和 DLL 的连接。漫谈兼容内核之十: Windows的进程创建和映像装入 和漫谈兼容内核之十一: Windows DLL的装入和连接。在进入这个函数之前,目标 EXE 映像已经被映射到当前进程的用户空间,系统 DLLntdll.dll
的映像也已经被映射,但是并没有在 EXE 映像与ntdll.dll
映像之间建立连接。LdrInitializeThunk
是ntdll.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 ); }
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(); }
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")
start_process
(dlls/ntdll/loader.c
)。注意! 這裡的kernel_start
是dlls/kernel32/process.c
裡的start_process
。static void start_process( void *kernel_start ) { call_thread_entry_point( kernel_start, NtCurrentTeb()->Peb ); }
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") );
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 */ }
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 和 Linux 上都一樣。Windows 取得所謂的句柄 (handle),Linux 取得所謂的文件號 (file descriptor)。
- 在 Linux 中,(虛擬) 設備和文件都視為文件。Linux 進程都有一份打開文件表,進程透過文件號索引其打開文件表,進而操作文件或設備。
- 在 Windows 中,系統資源皆被視為對象 (object)。注意! 相較於 Linux,對象是一個更為廣泛的抽象。Windows 進程都有一份打開對象表,進程透過句柄索引其打開對象表,進而操作各項系統資源。
- 已打开文件的遗传: 当一个进程创建一个子进程时,可以将它的一些已打开文件“遗传”给所创建的子进程。这样,子进程从一开始就取得了对这些文件的访问权和上下文。这一点上 Windows 和 Linux 都是如此。
- Windows 内核的这种遗传机制却有着与 Linux 不同的特点,可以归纳成下列要素:
- 在打开一个文件 (实际上是更为广义的“对象”,下同) 的时候,可以说明是否允许将这个已打开文件 (如果成功打开的话) 遗传给子进程。
- 在创建一个子进程的时候,可以规定是否将那些允许遗传的已打开文件遗传给所创建的子进程。
- 在 Linux 中,子进程的创建是分两步走的。第一步是通过系统调用
fork
创建一个线程 (共享父进程的用户空间和别的资源)。然后是第二步,通过系统调用execve
使新创建的线程变成进程,开始独立拥有自己的用户空间和别的资源,并开始走自己的路。在第一步中的遗传/继承是全面的,父进程所有的已打开文件都遗传给子进程 (线程)。而第二步,其实 Linux 也支持有选择的遗传和继承。Linux 内核的数据结构struct files_struct
中有个位图close_on_exec_init
,位图中的遗传控制位 (标志位) 决定着相应的已打开文件在执行execve
等系统调用时是否关闭、也即遗传与否。
于是,Windows的打开文件系统调用可以这样实现:
NtOpenFile() { ... 略 ... fd = open(); if (允许遗传) ioctl(fd, FIONCLEX);// 執行 exec 系統呼叫时不关闭 fd。 else ioctl(fd, FIOCLEX); // 執行 exec 系統呼叫时关闭 fd。 }
問題在於,Windows 允许在创建子进程时再作一次选择。如果选择不遗传,那么所有的已打开文件都不遗传。而 Linux 却不提供这一次选择,Linux 創建子進程時會將父進程所有的已打开文件都遗传给子进程。读者也许会想,如果我们在用户空间也提供一个位图、作为内核中本进程的 close_on_exec
位图在用户空间的副本,再让 NtOpenFile
在设置内核中的位图时 (我懷疑此處說的是核內補) 也设置一下用户空间的副本,最后让子进程根据这个副本位图有针对地关闭文件,这样不就可以提高效率吗?然而问题在于:一般而言,父进程用户空间的内容是不能遗传给子进程的。实际上,作为一条准则,凡是要遗传给子进程的东西,就不能只是保存在父进程的用户空间,而必须保存在别的什么地方,一般是内核中或是另一个进程(例如服务进程)中。以打開文件表為例,此表即是保存在內核資料結構。
- 文件访问的跨进程复制: Windows 不仅支持父子进程之间对于已打开文件(对象)有选择的遗传/继承,还支持跨进程的复制。这种跨进程复制可以在没有血缘关系的进程之间进行。更重要的是,Linux 进程之间管道连接的建立实际上与
fork
捆绑在一起,只能在创建子进程的过程中建立。一旦子进程业已创建,便无能为力了,因而就子进程而言这是静态的。而 Windows 的句柄复制,却可以在任何时候动态地进行。显然,要实现跨进程的句柄复制,调用NtDuplicateObject
的进程就必须能 (直接或间接地) 访问其它两个进程的打开文件表,而打开文件表在内核中。另一方面,只要 CPU 运行于用户空间,它就只能访问当前进程自己的用户空间,而不能访问别的进程的空间。不过“核外补”的办法还是有的。那就是采用文件服务器 (文件服务进程) 的方法。在这种方法里,所有的文件实际上都是由文件服务进程打开和拥有的,而“客户”进程只是名义上打开并拥有各自的那些已打开文件,“客户”进程对文件的操作都要委托服务进程代理。这实质上就是把对于已打开文件的所有权与享用权区分了开来。于是,(在 Linux 上运行的) 所有 Windows 进程的已打开文件全都属于同一个进程、即服务进程所有,“客户”进程并不直接打开任何文件。这样就使各“客户”进程本身在内核中的打开文件表失去了效用(从而无需去访问它们)。而服务进程,则在其用户空间为“客户”进程另行维持各自的幻影式的打开文件表。从效果上看,这是把内核与各进程打开文件表之间的关系和操作往上平移/提升到了用户空间,变成服务进程与各“客户”进程的“幻影”打开文件表之间的关系与操作。注意! 到這裡已經可以看出wineserver
存在的目的。
这样一来,应用软件层面的所谓跨进程句柄复制,对于 Linux 内核而言却只是服务进程 (wineserver
) 内部的句柄复制,类似于 dup
。这就实现了对于句柄复制的“核外补”。而且,这一来别的问题也可以随之而得倒类似的解决。例如有条件遗传/继承的问题,就因为不再使用“客户”进程在内核中的打开文件表,而改成使用由服务进程为各个“客户”进程维持的“幻影”打开文件表,从而变得简单了。
- 以 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 ); ... 略 ... }
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); */ };
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); }
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
中的 CreateProcessA
或 CreateProcessW
完成,不再往下調用 NtCreateProcess
。在 漫谈兼容内核之七: Wine的二进制映像装入和启动 描述了透過 Wine 運行的 Windows 應用程序呼叫 CreateProcess
建立另一個 Windows 進程的流程。
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); }
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; ... 略 ... } ... 略 ... }
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; } ... 略 ... }
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; }
- 以 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)
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; }
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; } } } } }
- 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; } } ... 略 ... }
- 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]( ¤t->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, ... 略 ... };
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上的 fwrite
。WriteFile
这个函数在动态连接库 kernel32.dll
中,执行的结果是向下调用另一个动态连接库 ntdll.dll
中的 NtWriteFile
。在 Windows 系统中,ntdll.dll
中的 NtWriteFile
直接进行系统调用,调用内核中的 NtWriteFile
,就像 Linux 的 C 库程序 fwrite
直接进行系统调用、调用内核中的 sys_write
一样。可是,如果采用的是文件服务进程的方法,NtWriteFile
就需要通过进程间通信把操作请求连同缓冲区的内容发送给文件服务进程。
为防止进程间通信过程中的缓冲区复制,Wine 利用了通过 Socket 实现文件访问授权的办法。我们知道,Unix 域的 Socket 有个特殊的功能,就是在通过 sendmsg
和 recvmsg
传递报文的同时让发送端进程将对其已打开文件的访问权授予接收端进程(读者可参阅“情景分析下册第73-113页”)5)。服务进程将对于目标文件的访问权授予客户进程之后,客户进程就可以直接进行读/写,而不必再由服务进程代劳、更不必因传递缓冲区内容而进行缓冲区复制了。
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); ... 略 ... }
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 ); ... 略 ... } ... 略 ... }
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 抽象模型,諸如: 進程、執行緒、號誌或是檔案。
process.[ch]
thread.[ch]
semaphore.c
file.[ch]
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 */ };
thread.c
。struct thread { struct object obj; /* object header */ struct list entry; /* entry in system-wide thread list */ struct list proc_entry; /* entry in per-process thread list */ struct process *process; thread_id_t id; /* thread id */ struct list mutex_list; /* list of currently owned mutexes */ struct debug_ctx *debug_ctx; /* debugger context if this thread is a debugger */ struct debug_event *debug_event; /* debug event being sent to debugger */ int debug_break; /* debug breakpoint pending? */ struct msg_queue *queue; /* message queue */ struct thread_wait *wait; /* current wait condition if sleeping */ struct list system_apc; /* queue of system async procedure calls */ struct list user_apc; /* queue of user async procedure calls */ struct inflight_fd inflight[MAX_INFLIGHT_FDS]; /* fds currently in flight */ unsigned int error; /* current error code */ union generic_request req; /* current request */ void *req_data; /* variable-size data for request */ unsigned int req_toread; /* amount of data still to read in request */ void *reply_data; /* variable-size data for reply */ unsigned int reply_size; /* size of reply data */ unsigned int reply_towrite; /* amount of data still to write in reply */ struct fd *request_fd; /* fd for receiving client requests */ struct fd *reply_fd; /* fd to send a reply to a client */ struct fd *wait_fd; /* fd to use to wake a sleeping client */ enum run_state state; /* running state */ int exit_code; /* thread exit code */ int unix_pid; /* Unix pid of client */ int unix_tid; /* Unix tid of client */ context_t *context; /* current context if in an exception handler */ context_t *suspend_context; /* current context if suspended */ client_ptr_t teb; /* TEB address (in client address space) */ affinity_t affinity; /* affinity mask */ int priority; /* priority level */ int suspend; /* suspend count */ obj_handle_t desktop; /* desktop handle */ int desktop_users; /* number of objects using the thread desktop */ timeout_t creation_time; /* Thread creation time */ timeout_t exit_time; /* Thread exit time */ struct token *token; /* security token associated with this thread */ }; // thread 的回調函式。 static const struct fd_ops thread_fd_ops = { NULL, /* get_poll_events */ thread_poll_event, /* poll_event */ NULL, /* flush */ NULL, /* get_fd_type */ NULL, /* ioctl */ NULL, /* queue_async */ NULL, /* reselect_async */ NULL /* cancel_async */ };
除錯
Wine 除錯分為兩個部分,一個是提供上層 Windows 程序 Windows debug API 以便其 debug 其它 Windows 程序; 二是 debug Wine 自身。