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

安装核心依赖

核心依赖以最少的依赖包保证Wine正常运行。
在执行config的时候会告诉你缺少一些依赖,如果想看的话就手动执行一遍,否则可以直接安装依赖:

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 

安装完整依赖

安装完整依赖。

#64位依赖
# wine6.0-V10sp1Rc1
sudo apt install gcc-multilib g++-multilib flex bison libx11-dev:i386 libfreetype6-dev:i386 libfreetype6-dev mingw-w64 autotools-dev autoconf bison bsdmainutils docbook-to-man docbook-utils docbook-xsl flex fontforge gawk gcc gettext libacl1-dev libasound2-dev libavcodec-dev libcapi20-dev libcups2-dev libdbus-1-dev libfontconfig1-dev libfreetype6-dev libgl1-mesa-dev libglu1-mesa-dev libgnutls28-dev libgphoto2-dev libgsm1-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libgtk-3-dev libice-dev libjpeg-dev libkrb5-dev liblcms2-dev libldap2-dev libldap-dev libmpg123-dev libncurses5-dev libopenal-dev libosmesa6-dev libpcap-dev libpng-dev libpulse-dev libsane-dev libsdl2-dev libssl-dev libstdc++-10-dev libtiff5-dev libudev-dev libv4l-dev libva-dev libx11-dev libx11-xcb-dev libxcomposite-dev libxcursor-dev libxext-dev libxi-dev libxinerama-dev libxml2-dev libxrandr-dev libxrender-dev libxslt1-dev libxt-dev libxxf86vm-dev linux-libc-dev patch perl prelink sharutils unixodbc-dev x11proto-xinerama-dev

#32位依赖
sudo dpkg --add-architecture i386 
sudo apt update
sudo apt-get install autotools-dev:i386 autoconf:i386 bison:i386 bsdmainutils:i386 docbook-to-man:i386 docbook-utils:i386 docbook-xsl:i386 flex:i386 fontforge:i386 gawk:i386 gcc:i386 gettext:i386 libacl1-dev:i386 libasound2-dev:i386 libavcodec-dev:i386 libcapi20-dev:i386 libcups2-dev:i386 libdbus-1-dev:i386 libfontconfig1-dev:i386 libfreetype6-dev:i386 libgl1-mesa-dev:i386 libglu1-mesa-dev:i386 libgnutls28-dev:i386 libgphoto2-dev:i386 libgsm1-dev:i386 libgstreamer1.0-dev:i386 libgstreamer-plugins-base1.0-dev:i386 libgtk-3-dev:i386 libice-dev:i386 libjpeg-dev:i386 libkrb5-dev:i386 liblcms2-dev:i386 libldap2-dev:i386 libldap-dev:i386 libmpg123-dev:i386 libncurses5-dev:i386 libopenal-dev:i386 libosmesa6-dev:i386 libpcap-dev:i386 libpng-dev:i386 libpulse-dev:i386 libsane-dev:i386 libsdl2-dev:i386 libssl-dev:i386 libstdc++-10-dev:i386 libtiff5-dev:i386 libudev-dev:i386 libv4l-dev:i386 libva-dev:i386 libx11-dev:i386 libx11-xcb-dev:i386 libxcomposite-dev:i386 libxcursor-dev:i386 libxext-dev:i386 libxi-dev:i386 libxinerama-dev:i386 libxml2-dev:i386 libxrandr-dev:i386 libxrender-dev:i386 libxslt1-dev:i386 libxt-dev:i386 libxxf86vm-dev:i386 linux-libc-dev:i386 patch:i386 perl:i386 prelink:i386 sharutils:i386 unixodbc-dev:i386 x11proto-xinerama-dev

# wine6.0原版
#sudo apt install gcc-multilib g++-multilib flex bison libx11-dev:i386 libfreetype6-dev:i386 libfreetype6-dev mingw-w64 autotools-dev, autoconf, bison, bsdmainutils, docbook-to-man, docbook-utils, docbook-xsl, flex, fontforge, gawk, gcc, gettext, libacl1-dev, libasound2-dev, libavcodec-dev, libcapi20-dev, libcups2-dev, libdbus-1-dev, libfontconfig1-dev | libfontconfig-dev, libfreetype6-dev, libgl1-mesa-dev, libglu1-mesa-dev, libgnutls30-dev | libgnutls28-dev | libgnutls-dev, libgphoto2-dev | libgphoto2-6-dev | libgphoto2-2-dev (>= 2.4.6), libgsm1-dev, libgstreamer1.0-dev, libgstreamer-plugins-base1.0-dev, libgtk-3-dev, libice-dev, libjpeg-dev, libkrb5-dev, liblcms2-dev, libldap2-dev, libldap-dev, libmpg123-dev, libncurses6-dev | libncurses5-dev | libncurses-dev, libopenal-dev (>= 1:1.12), libosmesa6-dev, libpcap-dev, libpng-dev | libpng12-dev, libpulse-dev, libsane-dev, libsdl2-dev, libssl-dev, libstdc++6-4.5-dev | libstdc++-dev, libtiff5-dev | libtiff4-dev | libtiff-dev, libudev-dev, libv4l-dev, libva-dev, libx11-dev, libx11-xcb-dev, libxcomposite-dev, libxcursor-dev, libxext-dev, libxi-dev, libxinerama-dev, libxml2-dev, libxrandr-dev, libxrender-dev, libxslt1-dev, libxt-dev, libxxf86vm-dev, linux-libc-dev, patch, perl, prelink, sharutils, unixodbc-dev, x11proto-xinerama-dev

编译只支持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 FilesFoobar 2000
还有另一个命名空间,以Explorer/Shell等级实现的(命名空间?),这就是为什么在Explorer中,系统根植于桌面。
如果你想访问特殊文件夹,如控制面板,拨号连接,回收站等等,你就需要使用 shell API。
shell中的路径不是人类可读的,而是一个指向ID列表的指针,换言之,等同于一个字符串的指针。
批注:这里所述的两种方式,一种是C:Program FilesFoobar 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:WindowsDesktop,也许我们找到了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 FilesPE Explorerpexplorer.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 FilesPE Explorerpexplorer.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 FilesPE Explorerpexplorer.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
",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
", subKeyName );
...
    if( !lpEnumCallback( &serviceProviderGUID , subKeyName,
                         majVersionNum, minVersionNum, lpContext ) )
    {
      WARN("lpEnumCallback returning FALSE
" );
      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
",
         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 = "*.**.*";//要选择的文件后缀
	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

原文地址:https://www.cnblogs.com/chendeqiang/p/14309515.html