Wine筆記


Wine簡介

虛擬機是模擬CPU;
Wine是API轉化,將Winsows API轉化為Linux API。例如,Win下打開文件的API是CreateFile( ),Linux下打開文件的API是open( )

Wine采用了WindowsNT架構。首先實現了NTDLL,然后在NTDLL上又實現了了GDI,USER,KERNEL三大件,而其他Windows DLL都是在這三個DLL上開發的。具體架構請查看架構概覽

包管理器安裝Wine

Ubuntu

sudo dpkg --add-architecture i386 
sudo apt update
wget -nc https://dl.winehq.org/wine-builds/winehq.key
sudo apt-key add winehq.key
sudo add-apt-repository 'deb https://dl.winehq.org/wine-builds/ubuntu/ groovy main' 
sudo apt install --install-recommends winehq-stable

詳細說明見官網:
https://wiki.winehq.org/Ubuntu

源碼安裝Wine

環境:Ubuntu 20.04

下載Wine源碼

git clone git://source.winehq.org/git/wine.git ~/wine-dirs/wine-source

安裝依賴

wine6.16。

sudo dpkg --add-architecture i386 
sudo apt update
sudo apt-get install gcc-multilib g++-multilib flex bison libx11-dev:i386 libfreetype6-dev:i386 libfreetype6-dev mingw-w64  libxcursor-dev:i386 libxi-dev:i386 libxshmfence-dev:i386 libxxf86vm-dev:i386 libxrandr-dev:i386 libxfixes-dev:i386 libxinerama-dev:i386 libxcomposite-dev:i386 libosmesa6-dev:i386 libopengl-dev:i386 libpcap-dev:i386 libdbus-1-dev:i386 libsane-dev:i386 libusb-1.0-0-dev:i386 libgphoto2-dev:i386 liblcms2-dev:i386 libpulse-dev:i386 libgstreamer1.0-dev:i386 libudev-dev:i386 libsdl2-dev:i386 libfaudio-dev:i386 libcapi20-dev:i386 libcups2-dev libjxr-dev libvkd3d-dev:i386 libgettextpo0 libopengl0 libfontconfig1-dev:i386 libgsm1-dev:i386 libkrb5-dev:i386 libjxr0 libtiff-dev:i386 libmpg123-dev:i386 libopenal-dev:i386 libvulkan-dev:i386 libvkd3d-dev:i386 libldap2-dev:i386 libgettextpo-dev:i386 libxrender-dev:i386 libopengl-dev:i386 libxml2-dev:i386 libxslt1-dev:i386 libgnutls28-dev:i386 libjpeg9-dev:i386 clinfo libv4l-dev:i386 libgstreamer-plugins-base1.0-dev:i386
sudo add-apt-repository ppa:cybermax-dexter/vkd3d
sudo apt-get update
sudo apt install libvkd3d1 libvkd3d1:i386
sudo apt install -f

編譯只支持Win32版本Wine

如果只想構建Win32版本的Wine,那么,
默認情況安裝就是只支持32位win應用的wine,不支持64位應用,安裝過程如下:

cd ~/wine-dirs/wine64-build/
../wine-source/configure
#../wine-source/configure --prefix=/usr/cdq/
make -j4
make install

編譯兼容Win32_64的WoW64版本

沒有人會構建只支持64位應用的版本吧?所以這里構建32_64位兼容的版本。
1.先構建wine64版本

cd ~/wine-dirs/wine64-build/
../wine-source/configure --enable-win64
make -j4

2.再構建32位版本

cd ~/wine-dirs/wine32-build/
PKG_CONFIG_PATH=/usr/lib/pkgconfig ../wine-source/configure --with-wine64=../wine64-build
make -j4

3.先安裝32位版本

cd ~/wine-dirs/wine32-build/
sudo make install

4.再安裝64位版本

cd ~/wine-dirs/wine64-build/
sudo make install

5.安裝過程中的bug解析
1.如果報錯:

0024:err:module:process_init L"Z:\\media\\psf\\Home\\project\\exe\\notepad.exe" 64-bit application not supported in 32-bit prefix

那么代表當前的wine是32位版本,而要執行的程序是64位版本。

2.如果報錯:

0024:err:process:exec_process L"Z:\\media\\psf\\Home\\project\\exe\\weChatSetup.exe" not supported on this system

那么多半是未make install或者make install的順序不對,應該先安裝32位再安裝64位,缺一不可,反向不可。

ARM上編譯wine

sudo dpkg --add-architecture armhf
sudo apt update
sudo dpkg --add-architecture i386
sudo apt update

sudo apt-get install libx11-6:armhf libx11-dev:armhf libfreetype6:armhf  linux-libc-dev libxcursor-dev:armhf libxi-dev:armhf libxrandr-dev:armhf  libxinerama-dev:armhf libxcomposite-dev:armhf libglu1-mesa-dev:armhf libosmesa6-dev:armhf ocl-icd-opencl-dev:armhf libdbus-1-dev:armhf  libv4l-dev:armhf  liblcms2-dev:armhf libudev-dev:armhf libcapi20-dev:armhf  libvulkan-dev:armhf libopenal-dev:armhf libmpg123-dev:armhf libgsm1-dev:armhf  libhbalinux-dev:armhf nettle-dev:armhf libidn11-dev:armhf libegl1-mesa-dev:armhf libgles2-mesa-dev:armhf libxkbcommon-dev:armhf  libglib2.0-0:armhf libgphoto2-6:armhf  libpulse0:armhf libxml2:armhf libasound2-plugins:armhf libexpat1:armhf libx11-6:armhf libx11-dev:armhf


sudo apt-get install libx11-6:i386 libx11-dev:i386 libfreetype6:i386  linux-libc-dev libxcursor-dev:i386 libxi-dev:i386 libxrandr-dev:i386  libxinerama-dev:i386 libxcomposite-dev:i386 libglu1-mesa-dev:i386 libosmesa6-dev:i386 ocl-icd-opencl-dev:i386 libdbus-1-dev:i386  libv4l-dev:i386  liblcms2-dev:i386 libudev-dev:i386 libcapi20-dev:i386  libvulkan-dev:i386 libopenal-dev:i386 libmpg123-dev:i386 libgsm1-dev:i386  libhbalinux-dev:i386 nettle-dev:i386 libidn11-dev:i386 libegl1-mesa-dev:i386 libgles2-mesa-dev:i386 libxkbcommon-dev:i386  libglib2.0-0:i386 libgphoto2-6:i386  libpulse0:i386 libxml2:i386 libasound2-plugins:i386 libexpat1:i386 libx11-6:i386 libx11-dev:i386

sudo apt-get install clang flex bison libfreetype6-dev libxcursor-dev libxi-dev libxrandr-dev libxinerama-dev libxcomposite-dev libosmesa6-dev libpcap-dev libasound2-dev libpulse-dev libdbus-1-dev libfontconfig1-dev libfreetype6-dev libgnutls28-dev libinotify-ocaml-dev libjpeg-dev libpng-dev libtiff5-dev libunwind-dev libx11-dev libxml2-dev libxslt1-dev libncurses5-dev libgstreamer-plugins-base1.0-dev libmpg123-dev libosmesa6-dev libsdl2-dev libudev-dev libvulkan-dev libcapi20-dev liblcms2-dev libcups2-dev libgphoto2-dev libsane-dev libglu1-mesa-dev libgsm1-dev libgssapi-krb5-2 libkrb5-dev libldap2-dev libnet1-dev ocl-icd-opencl-dev libpcap-dev libusb-1.0-0-dev libv4l-dev libopenal-dev libxcomposite-dev mingw-w64 

cd win.64
CC=/usr/bin/clang CXX=/usr/bin/clang++ ../wine-source/configure --enable-win64 

使用Wine

中文亂碼解決

復制windows中的C:/windows/Fonts/simsun.ttc復制到~/.wine/drive_c/windows/Fonts中 ;

環境變量

在使用時有兩個環境變量比較重要,WINEARCHWINEPRFIX
默認情況下,這兩個環境變量是沒有的。
默認情況下,arch是32位,prefix是 ~/.wine
如果要使用64位應用,且默認情況下wine打不開它,則需要設置這兩個環境變量:

#設置啟用64位架構
WINEARCH=win64
#備份配置目錄
mv ~/.wine ~/.wine.bak
#設置prefix
WINEPREFIX=~/.wine
wine xxx.exe

Debug Wine

Debug Wine有兩種方式,一種是輸出log日志文件,另一種是斷點查看。

崩潰式樣

通常,程序崩潰時的報告如下:

|marcus@jet $ WINEDEBUG=+relay,-debug wine winword.exe
|CallTo32(wndproc=0x40065bc0,hwnd=000001ac,msg=00000081,wp=00000000,lp=00000000)
|Win16 task 'winword': Breakpoint 1 at 0x01d7:0x001a
|CallTo16(func=0127:0070,ds=0927)
|Call WPROCS.24: TASK_RESCHEDULE() ret=00b7:1456 ds=0927
|Ret  WPROCS.24: TASK_RESCHEDULE() retval=0x8672 ret=00b7:1456 ds=0927
|CallTo16(func=01d7:001a,ds=0927)
|     AX=0000 BX=3cb4 CX=1f40 DX=0000 SI=0000 DI=0927 BP=0000 ES=11f7
|Loading symbols: /home/marcus/wine/wine...
|Stopped on breakpoint 1 at 0x01d7:0x001a
|In 16 bit mode.
|Wine-dbg>break MessageBoxA                          <---- Set Breakpoint
|Breakpoint 2 at 0x40189100 (MessageBoxA [msgbox.c:190])
|Wine-dbg>c                                            <---- Continue
|Call KERNEL.91: INITTASK() ret=0157:0022 ds=08a7
|     AX=0000 BX=3cb4 CX=1f40 DX=0000 SI=0000 DI=08a7 ES=11d7 EFL=00000286
|CallTo16(func=090f:085c,ds=0dcf,0x0000,0x0000,0x0000,0x0000,0x0800,0x0000,0x0000,0x0dcf)
|...                                                   <----- Much debug output
|Call KERNEL.136: GETDRIVETYPE(0x0000) ret=060f:097b ds=0927
                               ^^^^# Debug Wine
Debug Wine有兩種方式,一種是輸出log日志文件,另一種是斷點查看。

通常,程序崩潰時的報告如下:
```crash
|marcus@jet $ WINEDEBUG=+relay,-debug wine winword.exe
|CallTo32(wndproc=0x40065bc0,hwnd=000001ac,msg=00000081,wp=00000000,lp=00000000)
|Win16 task 'winword': Breakpoint 1 at 0x01d7:0x001a
|CallTo16(func=0127:0070,ds=0927)
|Call WPROCS.24: TASK_RESCHEDULE() ret=00b7:1456 ds=0927
|Ret  WPROCS.24: TASK_RESCHEDULE() retval=0x8672 ret=00b7:1456 ds=0927
|CallTo16(func=01d7:001a,ds=0927)
|     AX=0000 BX=3cb4 CX=1f40 DX=0000 SI=0000 DI=0927 BP=0000 ES=11f7
|Loading symbols: /home/marcus/wine/wine...
|Stopped on breakpoint 1 at 0x01d7:0x001a
|In 16 bit mode.
|Wine-dbg>break MessageBoxA                          <---- Set Breakpoint
|Breakpoint 2 at 0x40189100 (MessageBoxA [msgbox.c:190])
|Wine-dbg>c                                            <---- Continue
|Call KERNEL.91: INITTASK() ret=0157:0022 ds=08a7
|     AX=0000 BX=3cb4 CX=1f40 DX=0000 SI=0000 DI=08a7 ES=11d7 EFL=00000286
|CallTo16(func=090f:085c,ds=0dcf,0x0000,0x0000,0x0000,0x0000,0x0800,0x0000,0x0000,0x0dcf)
|...                                                   <----- Much debug output
|Call KERNEL.136: GETDRIVETYPE(0x0000) ret=060f:097b ds=0927
                               ^^^^^^ Drive 0 (A:)
|Ret  KERNE## bug樣例L.136: GETDRIVETYPE() retval=0x0002 ret=060f:097b ds=0927
                                        ^^^^^^  DRIVE_REMOVEABLE
                        (It is a floppy diskdrive.)

|Call KERNEL.136: GETDRIVETYPE(0x0001) ret=060f:097b ds=0927
                               ^^^^^^ Drive 1 (B:)
|Ret  KERNEL.136: GETDRIVETYPE() retval=0x0000 ret=060f:097b ds=0927
                                        ^^^^^^  DRIVE_CANNOTDETERMINE
                        (I don't have drive B: assigned)

|Call KERNEL.136: GETDRIVETYPE(0x0002) ret=060f:097b ds=0927
                               ^^^^^^^ Drive 2 (C:)
|Ret  KERNEL.136: GETDRIVETYPE() retval=0x0003 ret=060f:097b ds=0927
                                        ^^^^^^ DRIVE_FIXED
                                               (specified as a hard disk)

|Call KERNEL.97: GETTEMPFILENAME(0x00c3,0x09278364"doc",0x0000,0927:8248) ret=060f:09b1 ds=0927
                                 ^^^^^^           ^^^^^        ^^^^^^^^^
                                 |                |            |buffer for fname
                                 |                |temporary name ~docXXXX.tmp
                                 |Force use of Drive C:.

|Warning: GetTempFileName returns 'C:~doc9281.tmp', which doesn't seem to be writable.
|Please check your configuration file if this generates a failure.

log日志查看

log日志格式

為了更好的管理wine的debug輸出信息,我們將信息以通道(channel,即模塊)和等級(class)來划分。例如:

trace:shell:SHAlloc 20 bytes at 0x77c5a568

其中trace是debug等級,shell是debug模塊,SHAlloc是模塊內的函數。

000d:Call advapi32.RegOpenKeyExW(00000090,7eb94da0 L"Patterns",00000000,00020019,0033f968) ret=7eb39af8
^^^^      ^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^
|         |        |             |                 |           |                           |Return address.
|         |        |             |                 |           |More arguments.
|         |        |             |                 |Textual parameter.
|         |        |             |Arguments.
|         |        |Function called.
|         |The module of the called function.
|The thread in which the call was made.

000d:Ret  advapi32.RegOpenKeyExW() retval=00000000 ret=7eb39af8
                                   ^^^^^^^^^^^^^^^
                                   |Return value is 32-bit and has the value 0.

000d:線程id
Call:調用/跳轉
advapi32:模塊,如.c文件
RegOpenKeyExW:函數
(00000090,7eb94da0 L"Patterns",00000000,00020019,0033f968) :參數,其中L指代文字參數
ret=7eb39af8:返回地址

Bug定位思路

找到異常的log位置,去源碼搜索該行log的Channel,一般在模塊的文件頭會有該Channel的聲明,如WINE_DEFAULT_DEBUG_CHANNEL(abc);,
然后使用VSCode搜索所有abc所在模塊的文件,找到問題log所在的源碼,解析問題。

Debugging Classes

  • FIXME
    表明有一些未實現的函數等。
  • ERR
    表明在wine中有一些錯誤。
  • WARN
    一些不影響基本功能的問題。
  • TRACE
    TRACE用於追蹤某個模塊的信息。
  • Message
    這些信息不屬於任何Channel,和WARN一樣,很少需要輸出。

默認情況下只輸出 FIXMEERR兩項。

Debugging Channels

一般情況下,一個模塊只需要一個Channel,因此使用默認通道宏定義即可。

#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(xxx);
...

    FIXME("some unimplemented feature", ...);
...
    if (zero != 0)
        ERR("This should never be non-null: %d", zero);
...

但是有些情況下需要輸出多個Channel,則可以:

#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(xxx);//聲明默認通道為xxx
WINE_DECLARE_DEBUG_CHANNEL(yyy);//聲明yyy通道
WINE_DECLARE_DEBUG_CHANNEL(zzz);//聲明zzz通道
...

    FIXME("this one goes to xxx channel");//使用默認通道xxx
...
    FIXME(yyy)("Some other msg for the yyy channel");//使用通道yyy
...
    WARN(zzz)("And yet another msg on another channel!");//使用默認通道zzz
...

常用的Debug Channel:
+all:輸出所有通道。
+heap:追蹤棧信息。
+loaddll:輸出每個被加載的dll信息。
+message:
+msgbox:
+pid:進程
+relay:輸出build-in DLL間的跨dll函數調用。注意與dll內的單個模塊內的Channel區別開。
+seh:輸出Windows結構化異常處理信息。
+server:
+snoop:輸出native DLL間的跨dll函數調用。
+synchronous:
+timestamp:
+fps:

控制debug輸出

#重定位輸出信息到 /tmp/log_setup 中,以保存信息。
WINEDEBUG=+relay,+snoop wine setup.exe &>/tmp/log_setup
#重開一個終端,查看實時信息。
tail -f /tmp/debug_pipe

Debug 案例

案例分為以下幾個部分:
Debugging Reason 3:調試結構化異常seh
Debugging PE Explorer:調試多線程
Debugging Wild Metal Country:異常棧溢出,DirectPlay

Debugging Reason 3

問題描述:打開軟件的時候,程序顯示“Unknown exception”錯誤對話框。

現在我們使用 +relay,+seh,+tid去輸出log信息,在log信息中去找第一個“trace:seh”,因為他是異常的根源:

000b:Call shell32.SHGetMalloc(778fe0bc) ret=7721a8f8
000b:Ret  shell32.SHGetMalloc() retval=00000000 ret=7721a8f8
000b:Call shell32.SHGetSpecialFolderLocation(00000000,00000000,778fe0b8) ret=7721a94b
000b:Ret  shell32.SHGetSpecialFolderLocation() retval=00000000 ret=7721a94b
000b:Call shell32.SHGetPathFromIDListW(77c6b4b8,778fe108) ret=7721a963
000b:Ret  shell32.SHGetPathFromIDListW() retval=00000001 ret=7721a963
trace:seh:EXC_RtlRaiseException code=e06d7363 flags=1 addr=0x77b6b9c0
  ...

e06d7363是一個MSVC++異常,此時程序還沒有崩潰,而是處於崩潰之前。
函數返回值通常是我們要查看的信息,因為它的值表明了函數最終的執行結果,如果不正確,可以根據錯誤碼來識別是什么錯誤。
但是這里的三個函數(SHGetMalloc,SHGetSpecialFolderLocation,SHGetPathFromIDListW)從返回值來看,都正確執行了,返回值可以通過查閱微軟官方文檔查看。
那么接下來怎么進行呢?這些調用都是針對shell和Explorer的,根據經驗,似乎實在獲取一個特殊目錄,特殊目錄一般指桌面,回收站,網絡鄰居等。

為了獲取更多信息,現在我們使用+shell,+pidl,+seh來輸出log,這幾個通道是根據SHGetPathFromIDListW所在文件的頂部鎖定義的通道來選取的。
如果你不知道該函數在哪些文件被調用,這里建議使用VSCode來查詢。
下邊是輸出信息:

trace:shell:SHAlloc 20 bytes at 0x77c5a568
trace:shell:SIC_IconAppend L"c:\\windows\\system\\shell32.dll" 38 0x112e 0x1136
trace:shell:SHAlloc 20 bytes at 0x77c5ad58
trace:shell:SIC_Initialize hIconSmall=0x77c59de0 hIconBig=0x77c5a6c0
trace:shell:SHGetFolderPathW (nil),0x778fda9c,nFolder=0x0025
trace:shell:PathFileExistsW (L"c:\\windows\\system")
trace:shell:SHGetFolderPathW returning 0x00000000 (final path is L"c:\\windows\\system")
fixme:win:WIN_CreateWindowEx Parent is HWND_MESSAGE
fixme:win:WIN_CreateWindowEx Parent is HWND_MESSAGE
trace:shell:SHGetMalloc (0x778fe0bc)
trace:shell:SHGetSpecialFolderLocation ((nil),0x0,0x778fe0b8)
trace:shell:SHGetFolderLocation (nil) 0x00000000 (nil) 0x00000000 0x778fe0b8
trace:pidl:_ILCreateDesktop ()
trace:shell:SHAlloc 2 bytes at 0x77c6b208
trace:shell:SHGetFolderLocation -- (new pidl 0x77c6b208)
trace:shell:SHGetPathFromIDListW (pidl=0x77c6b208,0x77efedea)
-------- pidl=0x77c6b208
empty pidl (Desktop)
trace:pidl:_ILIsValue (0x77c6b208)
trace:pidl:_ILIsFolder (0x77c6b208)
trace:pidl:_ILGetGUIDPointer 0x77c6b208
trace:pidl:_ILIsMyComputer (0x77c6b208)
trace:shell:SHELL_GetPathFromIDListW -- L"", 0x00000000
trace:shell:SHGetPathFromIDListW -- L"", 0x00000000
trace:seh:EXC_RtlRaiseException code=e06d7363 flags=1 addr=0x77b6b9c0

這里我們看到調用了 SHGetSpecialFolderLocation 來獲取桌面的PIDL,一個短字節的PIDL。
Windows有幾種文件的命名空間,最通用的就是我們熟悉的格式:C:\Program Files\Foobar 2000
還有另一個命名空間,以Explorer/Shell等級實現的(命名空間?),這就是為什么在Explorer中,系統根植於桌面。
如果你想訪問特殊文件夾,如控制面板,撥號連接,回收站等等,你就需要使用 shell API。
shell中的路徑不是人類可讀的,而是一個指向ID列表的指針,換言之,等同於一個字符串的指針。
批注:這里所述的兩種方式,一種是C:\Program Files\Foobar 2000方式,另一種是表方式,即有一張表來存儲位置,鍵是PIDL,值是路徑

Once it's retrieved the desktop PIDL (which is in fact special: it's empty) Reason calls SHGetPathFromIDListW using it.

從MSDN我們可以知道這個函數用於將Explorer/shell路徑轉化為一個真實的Windows文件路徑。
在最后的SEH行的上邊表明我們收到了一個空字符串,這是錯的,桌面通常被存儲在c:\Windows\Desktop,也許我們找到了bug。

查看SHGetPathFromIDListW的源文件,它在到達SHELL_GetPathFromIDListW之前調用了幾個其他的函數,包含以下部分:

/* One case is a PIDL rooted at desktop level */
if (_ILIsValue(pidl) || _ILIsFolder(pidl))
{
	hr = SHGetSpecialFolderPathW(0, pszPath, CSIDL_DESKTOP, FALSE);

正如我們看到的,他是一個獲取桌面PIDL的特殊case,由於一些原因,這個分支沒有被走到,查看_ILIs前綴的函數,表明他們是Wine內部的,並且有一個_IL!IsDesktop函數,更改if語句以包含 _IL!IsDesktop這個分支就可以修復bug!
我們找到了這個bug,改變它

Debugging PE Explorer

現在有一個問題,在使用PE Explorer from Heaventools時,點擊File->Open File對話框后,選擇一個文件,
如果雙擊打開一個Dll文件,它會掛掉;如果單擊這個Dll,再點擊open,它會正常打開;如果雙擊打開一個EXE文件,也會也會正常打開;只有雙擊DLL文件時會掛掉。

這個問題看上去是個線程互所鎖的問題,兩個線程在互相等待資源,所以可以使用 bt 命令查看堆棧信息。
先在一個終端打開PE Explorer:

wine64 pexplorer.exe

然后再打開一個終端使用winedbg查看線程:

winedbg
Wine-dbg>bt all

輸出信息摘選如下:

In 32 bit mode.
0xb7fe87a2: ret
Backtracing for thread 0xb in process 0x8 (C:\Program Files\PE Explorer\pexplorer.exe):
Backtrace:
=>1 0xb7fe87a2 (0x7e6cebd0)
  2 0x77ee0cd7 NTDLL_wait_for_multiple_objects(count=0x0, handles=0x0, flags=0x8, timeout=0x7e6ced10, signal_object=0x0)
  3 0x77edf2a6 usr1_handler+0x3a(__signal=0xa, __context=0x33)
  5 0x77ee0cd7 NTDLL_wait_for_multiple_objects+0x207(count=0x1, handles=0x7e6cb9c4, flags=0xc, timeout=0x7e6cb9bc, signal_object=0x0) 
  6 0x77ee0d9d NtWaitForMultipleObjects+0x4d(count=0x1, handles=0x7e6cb9c4, wait_all=0x0, alertable=0x0, timeout=0x7e6cb9bc)
  7 0x77baeaff WaitForMultipleObjectsEx+0xa3(count=0x1, handles=0x7e6cbaf8, wait_all=0x0, timeout=0x23, alertable=0x0)
  8 0x77baebee WaitForSingleObject+0x22(handle=0xc, timeout=0x23)
  9 0x77218d8c TIME_MMSysTimeThread(arg=0x7725ed40)
  10 0x77bb4bda THREAD_Start(ptr=0x77ccba40)
  11 0x77ee2437 start_thread+0x14b(info=0x77cb1e18)
  12 0xb7fae341 (0x7e6cc4c8)
  13 0xb7f44fee (0x00000000)

In 32 bit mode.
0xb7fe87a2: ret
Backtracing for thread 0xa in process 0x8 (C:\Program Files\PE Explorer\pexplorer.exe):
Backtrace:
  1 0xb7fe87a2 (0x7e7dfbd0)
  2 0x77ee0cd7 NTDLL_wait_for_multiple_objects(count=0x0, handles=0x0, flags=0x8, timeout=0x7e7dfd10, signal_object=0x0)
  3 0x77edf2a6 usr1_handler+0x3a(__signal=0xa, __context=0x33)
  5 0x77ee0cd7 NTDLL_wait_for_multiple_objects+0x207(count=0x1, handles=0x7e7ce728, flags=0x4, timeout=0x0, signal_object=0x0)
  6 0x77ee0d9d NtWaitForMultipleObjects+0x4d(count=0x1, handles=0x7e7ce728, wait_all=0x0, alertable=0x0, timeout=0x0)
  7 0x77baeaff WaitForMultipleObjectsEx+0xa3(count=0x1, handles=0x7e7ce9bc, wait_all=0x0, timeout=0xffffffff, alertable=0x0)
  8 0x7fedc9a3 X11DRV_MsgWaitForMultipleObjectsEx+0x4b(count=0x1, handles=0x7e7ce9bc, timeout=0xffffffff, mask=0xff, flags=0x0)
  9 0x77780958 send_inter_thread_message(res_ptr=0x7e7cea78)
  10 0x77781405 SendMessageTimeoutA+0x115(hwnd=0x100d4, msg=0x8fff, wparam=0x0, lparam=0x7f19aa10, flags=0x0, timeout=0xffffffff, res_ptr=0x7e7ceae0) 
  11 0x777814e1 SendMessageA+0x31(hwnd=0x100d4, msg=0x8fff, wparam=0x0, lparam=0x7f19aa10)
  12 0x00415b2c in pexplorer (+0x15b2c) (0x7e7ceb04)
  13 0x004a22b3 in pexplorer (+0xa22b3) (0x7e7ceb18)
  14 0x00415961 in pexplorer (+0x15961) (0x7e7ceb2c)
  15 0x004039b6 in pexplorer (+0x39b6) (0x7e7ceb40)
  16 0x77bb4bda THREAD_Start(ptr=0x77ca95b8)
  17 0x77ee2437 start_thread+0x14b(info=0x77ca98d0)
  18 0xb7fae341 (0x7e7cf4c8)
  19 0xb7f44fee (0x00000000)

In 32 bit mode.
0xb7fe87a2: ret
Backtracing for thread 0x9 in process 0x8 (C:\Program Files\PE Explorer\pexplorer.exe):
Backtrace:
  1 0xb7fe87a2 (0x77d9abd0)
  2 0x77ee0cd7 NTDLL_wait_for_multiple_objects(count=0x0, handles=0x0, flags=0x8, timeout=0x77d9ad10, signal_object=0x0)
  3 0x77edf2a6 usr1_handler+0x3a(__signal=0xa, __context=0x33)
  4 0xb7fb47c8 (0x7790b45c)
  5 0x77ee0cd7 NTDLL_wait_for_multiple_objects+0x207(count=0x1, handles=0x7790b5d0, flags=0x4, timeout=0x0, signal_object=0x0)
  6 0x77ee0d9d NtWaitForMultipleObjects+0x4d(count=0x1, handles=0x7790b5d0, wait_all=0x0, alertable=0x0, timeout=0x0)
  7 0x77baeaff WaitForMultipleObjectsEx+0xa3(count=0x1, handles=0x7790b704, wait_all=0x0, timeout=0xffffffff, alertable=0x0)
  8 0x77baebee WaitForSingleObject+0x22(handle=0x10, timeout=0xffffffff)
  9 0x77b9fa37 create_process(cmd_line=0x7790e3d4, env=0x0, cur_dir=0x0, psa=0x0, tsa=0x0, inherit=0x0, flags=0x400, startup=0x7790c654, info=0x7790c644, unixdir=0x77cd6808, res_start=0x0, res_end=0x0)
  10 0x77b9ffac CreateProcessW(app_name=0x0, cmd_line=0x7790e3d4, process_attr=0x0, thread_attr=0x0, inherit=0x0, flags=0x400, env=0x0, cur_dir=0x0, startup_info=0x7790c654, info=0x7790c644)
  11 0x77306642 SHELL_ExecuteW+0x96(lpCmd=0x7790e3d4, env=0x0, shWait=0x0, psei=0x7790d984, psei_out=0x7790e618)
  12 0x77308739 ShellExecuteExW32+0x949(sei=0x7790e618, execfunc=0x773065ac)
  13 0x77309374 ShellExecuteExA+0xbc(sei=0x7790e6a8)
  14 0x7730f575 ShellView_DoContextMenu(y=0x0, bDefault=0x1)
  15 0x773107ff ShellView_WndProc(hWnd=0x100f6, uMessage=0x4e, wParam=0x7d0, lParam=0x7790f1d4)
  16 0x777a36b3 WINPROC_wrapper+0x17
  17 0x777a3a64 WINPROC_CallWndProc+0x60(msg=0x4e, wParam=0x7d0, lParam=0x7790f1d4)
  18 0x777a87b5 CallWindowProcW+0x169(func=0x77800316, hwnd=0x100f6, msg=0x4e, wParam=0x7d0, lParam=0x7790f1d4)
  19 0x7777d2fe call_window_proc+0xb2(wparam=0x7d0, lparam=0x7790f1d4, unicode=0x1, same_thread=0x1)
  20 0x7777d6a2 SendMessageTimeoutW+0x136(hwnd=0x100f6, msg=0x4e, wparam=0x7d0, lparam=0x7790f1d4, flags=0x0, timeout=0xffffffff, res_ptr=0x7790f154) 
  21 0x7777d6e1 SendMessageW(hwnd=0x100f6, msg=0x4e, wparam=0x7d0, lparam=0x7790f1d4)
  22 0x77445eed notify_hdr(pnmh=0x7790f1d4)
  23 0x774528fa LISTVIEW_LButtonDblClk+0x11a(x=0x125, y=0x49)
  24 0x7745596e LISTVIEW_WindowProc+0xafe(hwnd=0x100f8, uMsg=0x203, wParam=0x1, lParam=0x490125)
  25 0x777a36b3 WINPROC_wrapper+0x17 in user32 (0x7790f35c)
  26 0x777a3a64 WINPROC_CallWndProc+0x60(msg=0x203, wParam=0x1, lParam=0x490125)
  27 0x777a86cf CallWindowProcW+0x83(func=0x777ff96c, hwnd=0x100f8, msg=0x203, wParam=0x1, lParam=0x490125)
  28 0x777821a2 DispatchMessageW+0xe2(msg=0x7790fa1c)
  29 0x7775a1d4 IsDialogMessageW+0xe0(hwndDlg=0x20028, msg=0x7790fa1c)
  30 0x7775a7c8 DIALOG_DoDialogBox(hwnd=0x20028, owner=0x10022)
  31 0x7775bc15 DialogBoxIndirectParamAorW+0x45(hInstance=0x77380000, template=0x773d4340, owner=0x10022, dlgProc=0x7739101c, param=0x7790faec, flags=0x2)
  32 0x7775bc7a DialogBoxIndirectParamA+0x26(hInstance=0x77380000, template=0x773d4340, owner=0x10022, dlgProc=0x7739101c, param=0x7790faec)
  33 0x7738c9b7 GetFileName95(fodInfos=0x7790faec)
  34 0x7738f0d0 GetFileDialog95A(ofn=0x7790fbdc, iDlgType=0x2)
  35 0x77392931 GetOpenFileNameA+0x39(ofn=0x7790fbdc)
  36 0x00497e0d in pexplorer (+0x97e0d) (0x7790fbc0)
  37 0x0049862c in pexplorer (+0x9862c) (0x7790fc50)
  38 0x00498a62 in pexplorer (+0x98a62) (0x7790fc60)
  39 0x00591461 in pexplorer (+0x191461) (0x7790fca8)
  40 0x0042444b in pexplorer (+0x2444b) (0x7790fccc)
  41 0x00424781 in pexplorer (+0x24781) (0x7790fd3c)
  42 0x00425ba7 in pexplorer (+0x25ba7) (0x7790fd9c)
  43 0x00425a1b in pexplorer (+0x25a1b) (0x7790fdcc)
  44 0x0042db32 in pexplorer (+0x2db32) (0x7790fde4)
  45 0x777a36b3 WINPROC_wrapper in user32 (0x7790fe08)
  46 0x777a3a64 WINPROC_CallWndProc(msg=0x202, wParam=0x0, lParam=0xa000a)
  47 0x777a8548 CallWindowProcA+0xe4(func=0x77800268, hwnd=0x100d2, msg=0x202, wParam=0x0, lParam=0xa000a)
  48 0x7778206e DispatchMessageA(msg=0x7790fec0)
  49 0x00433742 in pexplorer (+0x33742) (0x7790ff00)
  50 0x005a8b11 EntryPoint+0x13d in pexplorer (0x7790ff2c)
  51 0x77b9eaaf start_process+0xc3(arg=0x0)
  52 0xb7fd46c9 wine_switch_to_stack+0x11 in libwine.so.1 (0x00000000)

以上看到的是棧回溯信息,其輸出結果是反向的。
這里有兩個特別的地方,
一個特別的地方是主線程的開始標志:wine_switch_to_stackstart_process,可以根據這兩個幀(frame)來確定主線程的位置。批注:一行為一幀。
如你所見,有一些幀沒有函數信息(in pexplorer),因為他們是pexplorer內部的,這些幀不會告訴我們任何信息,我們可以忽略。
另一個特別的地方是回溯的結尾處:

  1 0xb7fe87a2 (0x77d9abd0)
  2 0x77ee0cd7 NTDLL_wait_for_multiple_objects(count=0x0, handles=0x0, flags=0x8, timeout=0x77d9ad10, signal_object=0x0)
  3 0x77edf2a6 usr1_handler+0x3a(__signal=0xa, __context=0x33)

這三幀是wine debug的結果,他們不是app在做的事,而是wine debug這個活動本身。

現在解碼程序:

Starts up
開始
Enters a standard Win32 message loop
進入主循環
We selected the File -> Open menu item, so it does some stuff, then calls GetFileDialog95A which pops up the file open dialog
選擇 打開 菜單
The file dialog enters a message loop, which in turn then dispatches the double click
對話框進入消息循環狀態,然后調度雙擊
The double click is handled by the list view control (in comctl32), which in turn sends another message (a notification).
雙擊被處理,然后轉到另一個信息
This is received by the ShellView object in its window procedure (wndproc).
這是由ShellView對象接收處理
For some reason control then passes to the context menu routine. This is strange, we didn't invoke a context menu, we double left clicked!
從這些信息看到,訪問到了右鍵菜單,這很奇怪,因為我們明明是左鍵雙擊。
Even stranger, the context menu function then tries to execute something. It's a fair bet that it's trying to execute the DLL we double clicked which clearly isn't going to work
更奇怪的是,右鍵菜單在執行我們雙擊的那個dll文件
And indeed the thread then hangs whilst trying to execute the DLL (we can prove this by doing an +exec trace)
可以確認,線程掛掉,同時試圖執行了dll

以上信息表明,我們雙擊一個dll時,似乎執行的不是打開命令,而是運行命令。
然后我們看到這段log:

 14 0x7730f575 ShellView_DoContextMenu(y=0x0, bDefault=0x1) [/wine/dlls/shell32/shlview.c:892] in shell32 (0x7790e754)
 15 0x773107ff ShellView_WndProc(hWnd=0x100f6, uMessage=0x4e, wParam=0x7d0, lParam=0x7790f1d4) [/wine/dlls/shell32/shlview.c:1284] in shell32 (0x7790eb30)

我們看到1248行不是ShellView_WndProc,它實際上在ShellView_OnNotify里,是個靜態函數。在這種情況下,GCC檢測到此函數只被用到一次,所以內聯了它。如果遇到這種情況,可以使用-O0禁能優化重建這個dll,然后我們來看其中的一部分代碼:

case LVN_ITEMACTIVATE:

	TRACE("-- LVN_ITEMACTIVATE %p\n",This);

	OnStateChange(This, CDBOSC_SELCHANGE);  /* the browser will get the IDataObject now */

	ShellView_DoContextMenu(This, 0, 0, TRUE);

	break;

這里信息不夠多,讓我們看看 ShellView_DoContextMenu 內部,他有很多分支點,幸運的是,他也有很多追蹤信息,我們使用+shell來看看(因為從源文件頂部信息看它屬於shell模塊):

trace:shell:ShellView_DoContextMenu -- get menu default command
trace:shell:ShellView_DoContextMenu -- uCommand=28930
trace:shell:ShellView_DoContextMenu -- dlg: OnDefaultCommand
trace:shell:OnDefaultCommand ICommDlgBrowser::OnDefaultCommand

啊哈,他調用了ICommDlgBrowser對象的OnDefaultCommand方法,這是一個COM對象,我們不能立刻知道他在哪。通過源文件搜索OnDefaultCommand,可以關聯到它隱藏在dlls/commdlg/filedlgbrowser.c中。
可以看到這條注釋的建議:

*  IShellBrowserImpl_ICommDlgBrowser_OnDefaultCommand
*
*   Called when a user double-clicks in the view or presses the ENTER key

我們似乎找對了位置,這個函數很簡單,並告訴了我們該怎樣做“如果被選對象不是一個文件夾,發送一個IDOK命令給父窗口”。看起來這條是我們想要的分支:

else

{

  /* Tell the dialog that the user selected a file */

  hRes = PostMessageA(This->hwndOwner, WM_COMMAND, IDOK, 0L);

}


/* Free memory used by pidl */

COMDLG32_SHFree((LPVOID)pidl);


return hRes

我們立刻就明白了錯誤在哪,他把PostMessage的返回值付給了HRESULT。
從已有的知識我們知道,HRESULT只能用於系統的COM部分。
而PostMessage誕生於COM之前,換句話說,它返回0代表失敗,非0代表成功。但是HRESULT的返回結果相反,0代表成功,非0代表失敗。
所以我們找到了BUG所在!
接下來把代碼修改一下就ok了:

        else

	{

          /* Tell the dialog that the user selected a file */

	  PostMessageA(This->hwndOwner, WM_COMMAND, IDOK, 0L);

          hRes = S_OK;

	}


        /* Free memory used by pidl */

        COMDLG32_SHFree((LPVOID)pidl);


        return hRes;

Debugging Wild Metal Country

問題描述:這是一款游戲,初始化時正常,在提示輸入“你的名字”時崩潰了,並顯示以下信息:

err:seh:setup_exception stack overflow 252 bytes in thread 0009 eip 00500143 esp 405a0f04 stack 0x405a0000-0x406a0000

這是一個典型問題,游戲進入了異常循環狀態(一個異常里包含另一個異常,無限循環),最終導致異常棧耗盡。
我們感興趣的是第一個異常,使用以下命令查看:

WINEDEBUG=+seh,+relay,+tid,+ddraw,+dinput,+dsound wine WinEnv

批注:我很疑惑WinEnv是游戲的exe嗎,這名字很奇怪。
從錯誤信息來看,這是一個DirectX游戲,因此簡單的使用+relay進行追蹤是不夠的,因為它無法顯示COM組件函數的調用。
所以我們需要添加一些來自DirectX組件最通用的通道(relay,seh,tid)來調試。
在查看一些seh相關的log后,我們看到了這個:

0009:Ret  ntdll.RtlNtStatusToDosError() retval=00000103 ret=40934283
0009:Ret  advapi32.RegEnumKeyExA() retval=00000103 ret=40a69f1e
0009:Ret  dplayx.DirectPlayEnumerateA() retval=00000000 ret=00465f13
0009:trace:seh:EXC_RtlRaiseException code=c0000005 flags=0 addr=0x466003
0009:trace:seh:EXC_RtlRaiseException  info[0]=00000000

嗯。。。它似乎不喜歡DirectPlayEnumerateA的調用,那么我們精簡輸出:

WINEDEBUG=+dplay,+dplayx wine WinEnv.exe

他給出了這些信息:

trace:dplay:DirectPlayEnumerateA : lpEnumCallback=0x465d60 lpContext=0x5adad2
err:seh:setup_exception stack overflow 252 bytes in thread 0009 eip 00500143 esp 405a0f04 stack 0x405a0000-0x406a0000

精簡了輸出看起來很舒服了,查看一下DirectPlayEnumerateA函數的源碼,在dlls/dplayx中,我們會看到這些代碼:

  for( dwIndex=0;
       RegEnumKeyExA( hkResult, dwIndex, subKeyName, &sizeOfSubKeyName,
                      NULL, NULL, NULL, &filetime ) != ERROR_NO_MORE_ITEMS;
       ++dwIndex, sizeOfSubKeyName=50 )
  {
...
    TRACE(" this time through: %s\n", subKeyName );
...
    if( !lpEnumCallback( &serviceProviderGUID , subKeyName,
                         majVersionNum, minVersionNum, lpContext ) )
    {
      WARN("lpEnumCallback returning FALSE\n" );
      break;
    }

批注:

這是一個for循環
迭代值是dwIndex,退出條件是RegEnumKeyExA的返回值為沒有項目時,迭代值變更方式是 ++dwIndex和sizeOfSubKeyName=50
循環內容是大括號里的內容,
其中callback是回調函數,用於枚舉輸出所有可用網絡服務,后續再解釋。

那么我們看到每個枚舉給游戲的網絡服務提供商(批注:此處的service provider是我根據某游戲貼圖理解的)都會有一個TRACE輸出log,因為我們什么都沒看到,所以結論就是我們並沒調用游戲所提供的枚舉函數。
批注:此處需要注意的是,我們假設app是好用的,只要它在windows上表現良好。那么出問題的就是wine,所以我們修改的一定是wine代碼。說這個是因為我在這里思維陷入了誤區,一度在想這段代碼是游戲的還是wine的,出現這個誤區是因為在翻譯上述game-provided時不知道該翻譯為“游戲提供的”還是“提供給游戲的”。所以這段代碼是在描述對游戲提供的可用的服務商,wine沒有調用,而游戲對這種異常不會去處理,因此導致了異常循環。

經過對后面代碼的解讀,證明我上邊的分析是錯的。那么重新描述這段:
那么從代碼可以看出,對於每個提供給游戲的網絡服務都會有一條TRACE log記錄,又因為我們什么也沒看到,所以結論就是,我們並沒有調用我們事實上並沒有調用提供給游戲的枚舉函數。

眾所周知,應用程序對異常的處理是不給力的,所以當前崩潰現象的合理解釋就是游戲需要至少一個網絡服務。

為了驗證這一點,我們在Windows上寫一個測試程序:

#include <dplay.h>
#include <dplay8.h>

BOOL FAR PASCAL EnumDPCallback(LPGUID lpguidSP, LPSTR lpSPName, DWORD dwMajorVersion, DWORD dwMinorVersion, LPVOID lpContext)
{
  printf("{%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x} %s %d %d\n",
         lpguidSP->Data1, lpguidSP->Data2, lpguidSP->Data3,
         lpguidSP->Data4[0], lpguidSP->Data4[1],
         lpguidSP->Data4[2], lpguidSP->Data4[3],
         lpguidSP->Data4[4], lpguidSP->Data4[5],
         lpguidSP->Data4[6], lpguidSP->Data4[7],
         lpSPName,
         dwMajorVersion,
         dwMinorVersion);
  return TRUE;
}

int main(int argc, char *argv[])
{
  DirectPlayEnumerate(&EnumDPCallback, NULL); //該函數用於枚舉輸出所有DirectPlay可用的網絡服務,第一個參數是回調函數,其作用是對每個枚舉項執行該函數,在這里的回調函數的作用是格式化輸出每個枚舉項的內容;第二個參數沒查。
}

批注:注釋是我后加的。
可以看到以下信息:

{0f1d6860-88d9-11cf-9c4e-00a0c905425e} Serial Connection For DirectPlay 6 0
{44eaa760-cb68-11cf-9c4e-00a0c905425e} Modem Connection For DirectPlay 6 0
{685bc400-9d2c-11cf-a9cd-00aa006886e3} IPX Connection For DirectPlay 6 0
{36e95ee0-8577-11cf-960c-0080c7534e82} Internet TCP/IP Connection For DirectPlay 6 0

批注:可以看到是一些網絡服務,翻譯后和下圖對比就更明確了。

所以我們只需要向Wine的枚舉函數中添加一段代碼就可以:

GUID hack_guid = { 0x36E95EE0, 0x8577, 0x11CF, { 0x96, 0x0c, 0x00, 0x80, 0xC7, 0x53, 0x4E, 0x82 } } ;

HRESULT WINAPI DirectPlayEnumerateA( LPDPENUMDPCALLBACKA lpEnumCallback,
                                     LPVOID lpContext )
{
...
  lpEnumCallback( &hack_guid, "Internet TCP/IP Connection For DirectPlay", 6, 0, lpContext );
...
}

批注:這里將形參的回調函數的方式直接寫死為TCP/IP方式了,正常情況下應該傳遞的參數是上述if條件中的( &serviceProviderGUID , subKeyName,majVersionNum, minVersionNum, lpContext ),但顯這些參數有問題,什么問題?繼續調查才能知道。因此這里寫死了也就意味着只能用TCP/IP這一種聯網方式,是一種臨時的解決方案。

重構dplayx這個dll。
重新運行游戲,surprise,目測好用了。

So here we did the first step of all debugging: we found the reason of the crash and confirmed the way to fix it with a small hack (it's often useful to proceed this way to prevent coding thousands of lines of code to finally find out that they do not fix the problem at all - i.e. start small with a hack and then code the proper way being sure of the problem).

Note that I just sent a possible 'real' fix for this issue: basically, at Wine install / upgrade time, the set of keys needed to enumerate all service providers will be installed via the 'wine.inf' file.

Wine開發經驗總結

win32和MFC

win32 API是最核心的c代碼,MFC是在win32上包裝成的C++接口。

c語言中的接口與封裝

c語言中沒有C++中的面向對象概念,因此c語言中實現接口與封裝的方法是:
在.h文件中的內容為接口,可以被外部調用,在.c中的內容通過結構體包含接口的結構體來實現繼承,例如:
在.h文件中的接口:

struct IAnimal{
char *name;
int age;
};

在.c中:

struct IAnimalImp{
struct IAnimal animal;
char *color;
};

winegcc混合編程

使用winegcc進行windows和linux混合編程。
例程,打開文件對話框:

#include <windows.h>
#include <stdio.h>
#include <gtk/gtk.h>
#include<commdlg.h>


int on_open_file(GtkWidget *Widget,int data);

 
int main(int argc, char *argv[])
{
	gtk_init(NULL,NULL);
	
	GtkWindow* window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
	
	g_signal_connect(window,"destroy",gtk_main_quit,NULL);
	gtk_widget_show(window);
	GtkButton *button = gtk_button_new_with_label("打開對話框");
	g_signal_connect(button,"clicked",on_open_file,NULL);
	gtk_widget_show(button);
	gtk_container_add(window,button);
	gtk_main();
	
	return 0;
	
}


int on_open_file(GtkWidget *Widget,int data)
{
	//執行打開對話框選擇文件主要代碼
	char szBuffer[1024] = { 0 };
	OPENFILENAME ofn = { 0 };
	ofn.lStructSize = sizeof(ofn);
	ofn.lpstrFilter = "*.*\0*.*\0\0";//要選擇的文件后綴
	ofn.lpstrInitialDir = "D:\\";//默認的文件路徑
	ofn.lpstrFile = szBuffer;//存放文件的緩沖區
	ofn.nMaxFile = sizeof(szBuffer) / sizeof(*szBuffer);
	ofn.nFilterIndex = 0;
	ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_EXPLORER;//標志如果是多選要加上OFN_ALLOWMULTISELECT
	BOOL bSel = GetOpenFileName(&ofn);
	MessageBoxA(NULL,szBuffer,"!",MB_OK);//在消息框顯示所選文件路徑
}

編譯:

winegcc -o x11demo  x11demo.c `pkg-config --libs --cflags gtk+-2.0`  -static-libgcc -lgdi32 -lcomdlg32

運行:

wine x11demo.exe.so

使用CrossOver搭建WINEPREFIX

crossover的容器里內置了html引擎,能顯示html內容,而原生的wine里不能顯示html

1.安裝crossover
2.新建crossover容器"wine"
3.拷貝crossover容器為WINEPREFIX

rm -rf ~/.wine/
cp -r ~/.cxoffice/wine/ ~/.wine

Crossover安裝字體

安裝simsun.ttc字體到/opt/cxoffice/share/wine/fonts/目錄下即可

使用QTCreator編譯/調試工程

准備條件:QtCreator,用命令行編譯好的wine工程
假設編譯好的目錄為:/wine-source,/win.32,~/win.64
使用root賬戶打開qtcreator,因為make install時候需要權限
導入工程:文件-新建文件或項目-Import Project-導入現有項目-Choose-項目名稱(wine)-位置(~/wine-source)-選擇-下一步
現在工程打開了。
配置編譯和運行條件:
項目-Build-構建目錄(~/win.32)-構建步驟(make -j4 ,make install)
項目-Run-Executable(/usr/local/bin/wine)-Command linearguments("/home/cdq/FoxmailSetup_7.2.19.158.exe")-Working directory(%{buildDir})-Run Environment(WINEDEBUG +richedit,WINEPREFIX /root/.wine)

注意1:因為賬戶使用的是root,因此wine的WINEPREFIX默認安裝到了/root下,因此如果要添加字體需要到/root/.wine/下添加。
注意2:盡管使用的是root賬戶,但是應用安裝還是會默認安裝在當前home目錄下,因此/root下的.wine是配置相關,/home下的是應用相關。

使用技巧

全局搜索:ctrl+shift+f,Scope選Project
書簽:ctrl+m添加書簽,ctrl+.跳轉到下一個書簽
代碼修改:提交正式版本之前,不要輕易刪除源代碼,最好使用#if 0屏蔽源代碼,這樣方便在源代碼和測試代碼之間切換。

使用gdb調試Wine

winedbg沒會用。
gdb用法如下。
在wine源碼目錄:

sudo gdb wine

新開一個終端,輸入ps ax | grep wine,找到所有wine進程,在gdb中,重復操作所有Wine進程找到需要的進程:

(gdb)attach wpid

注意wpid是wine概念中的windows進程id。

使用(gdb)bt可以獲得當前wine進程的回溯,例如,函數調用的所有歷史,這樣你就能找出當前進程正在做什么,然后可以花一些時間

(gdb)n

或者在函數設置斷點:

(gdb)b SomeFunction

設置斷點后可以使用

(gdb)c

來繼續運行。
最后可以使用

(gdb)detach

來結束進程。

原生DLL和內置DLL

原生DLL(native),即~/.wine/drive_c/windows/system32/下的DLL
內置DLL(build-in),即/usr/local/lib/wine下的DLL

對於Crossover,
原生DLL位置:~/.cxoffice/容器/drive_c/windows/system32/
內置DLL位置:/opt/cxoffice/lib/wine/

Before answering your questions, let me get some definitions for you:

  • Builtin dll is a Wine's .dll.so file which is a standard ELF shared
    library that can be loaded by libc. It contains code as well as some extra
    Wine specific information like resources
  • Native dll is a Windows PE library that contains code and resources. It
    can not be loaded by libc directly only by Wine itself.
  • Wine's "fake dll" is a Windows PE library that contains no code but only
    resources. It can not by loaded by libc directly
  • Winelib dll - same as builtin

https://www.winehq.org/pipermail/wine-devel/2010-March/082266.html
https://forum.winehq.org/viewtopic.php?f=2&t=8204

Wine加載PE過程

Wine中PE格式文件的加載(一):Wine初始化過程
https://blog.csdn.net/chrisnotfound/article/details/79957441

Wine生成Windows DLL方法

wine-source/tools/winebuild/spec32.c

#if 1
static const char builtin_signature[32] = " ";
#else
static const char builtin_signature[32] = "Wine builtin DLL";
#endif

此時在生成目錄會有生成的.dll

然后可以strip減小dll

//構建目錄
strip riched20.dll

此時riched20.dll就由2M變為了490K。

使用C89編譯代碼

Wine規定使用C89編譯代碼,以確保在任何平台都能編譯成功。

  • 使用 /* */, 不使用 //
  • 沒有參數時要寫void
int foo() { }                   /* Incorrect */
int foo(void) { }               /* Much better */
  • 變量聲明要前置
int bar1(void)
{
    do_something();
    int number1 = 5;            /* Not C89 compliant */
}

int bar2(void)
{
    int number2 = 17;           /* Much better */
    int number3 = 550;          /* Ditto */
    do_something();
}
  • 使用標准的變量類型
long wrong;                     /* long is different in Win64 and Unix64 */
LONG right;                     /* LONG is the same on both platforms */
  • 在非PE模塊中使用WCHAR和字符數組
const WCHAR str1[] = L"Hello";  /* Preferred on PE modules. But it won't compile on non-PE modules.*/
const WCHAR str2[] = {          /* Tedious, but correct */
    'H','e','l','l','o',0
};

winetricks

使用winetricks可以方便的裝一些windows應用。

經驗總結

重點在於復現和定位。
1.通過替換dll找出出問題的模塊;
2.通過在程序中設置log追蹤流向;
3.通過查閱資料熟悉模塊相關的概念;
4.模仿代碼中已實現的案例。

參考鏈接:
https://wiki.winehq.org/Building_Wine#Shared_WoW64
https://www.cnblogs.com/bobo1223/p/7287511.html
https://www.cnblogs.com/garyw/p/13468491.html
https://wiki.ubuntu.org.cn/Wine簡明教程
https://blog.csdn.net/wwyyxx26/article/details/9853089
https://www.cnblogs.com/chendeqiang/p/14515577.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM