中小仙女 發表於 2026-5-5 09:02:28

Wine 开发系列 —— 如何调试 Wine

<p><img class="alignnone size-full wp-image-36059" src="https://www.deepin.org/wp-content/uploads/2025/01/03-zh.png" alt="" width="900" height="383" srcset="https://www.deepin.org/wp-content/uploads/2025/01/03-zh.png 900w, https://www.deepin.org/wp-content/uploads/2025/01/03-zh-300x128.png 300w, https://www.deepin.org/wp-content/uploads/2025/01/03-zh-150x64.png 150w, https://www.deepin.org/wp-content/uploads/2025/01/03-zh-768x327.png 768w, https://www.deepin.org/wp-content/uploads/2025/01/03-zh-24x10.png 24w, https://www.deepin.org/wp-content/uploads/2025/01/03-zh-36x15.png 36w, https://www.deepin.org/wp-content/uploads/2025/01/03-zh-48x20.png 48w" sizes="(max-width: 900px) 100vw, 900px" /></p>
<p>本文主要以 Wine 官网的这篇文章 《 Debugging Wine 》 来讲解。大部分内容是对该文的翻译,修正了原文的一些书写错误,删除了原文跟最新的 Wine 不适应的内容。</p>
<h1 style="text-align: center;"><strong>介绍</strong></h1>
<h2 data-tool="mdnice编辑器"><strong>常用调试方法</strong></h2>
<p data-tool="mdnice编辑器">Wine 为调试问题提供了多种方法。大多数 Wine 开发人员更喜欢使用 Wine 的调试通道收集日志来解决问题。您可以在开发人员调试日志使用指南中了解如何使用调试通道来记录日志的更多内容。(https://wiki.winehg.org/Wine Developer%27s Guide/Debug Logging)</p>
<p data-tool="mdnice编辑器">本文的剩余部分将详细介绍 Wine 的内部调试器 winedbg 的使用。</p>
<h2 data-tool="mdnice编辑器"><strong>在底层操作系统和 Windows 中的进程和线程</strong></h2>
<p data-tool="mdnice编辑器">在深入讲解 Wine 的调试之前,下面是 Wine 中对进程和线程处理的小概述。必须清楚的是,我们有两种不同的模型:从 Unix 角度看到的进程/线程,从 Windows 角度看到的进程/线程。</p>
<p data-tool="mdnice编辑器">每个 Windows 线程都用一个 Unix 线程来实现,这意味着同一个 Windows 进程的所有线程共享相同的 Unix 进程地址空间。以下:</p>
<ul class="list-paddingleft-1">
<li>
<section><code>W-process</code> 表示 Windows 中的进程</section>
</li>
<li>
<section><code>U-process</code> 表示 Unix 中的进程</section>
</li>
<li>
<section><code>W-thread</code>  表示 Windows 中的线程</section>
</li>
</ul>
<p data-tool="mdnice编辑器">一个<code>W-process</code> 由一个或多个<code>W-thread</code> 组成。每个<code>W-thread</code> 映射到一个且只有一个<code>U-process</code>。同一个<code>W-process</code> 的所有<code>U-process</code> 共享相同的地址空间。</p>
<p data-tool="mdnice编辑器">所以每个 Unix 进程都可以用两个值来标识:</p>
<ul class="list-paddingleft-1">
<li>
<section>Unix 进程 ID ( 简称<code>upid</code>)</section>
</li>
<li>
<section>Windows 线程 ID (简称<code>tid</code>)</section>
</li>
</ul>
<p data-tool="mdnice编辑器">每个 Windows 进程还具有 Windows 进程 ID (简称<code>wpid</code>)。必须清楚,<code>upid</code> 和<code>wpid</code> 是不同的,不能相互替代。<code>wpid</code> 和<code>tid</code> 是 Windows 系统层面定义的,它们不能与进程或线程句柄混淆,因为任何句柄都指向系统对象(在本例中为进程或线程)。同一个进程可以对同一个内核对象有多个不同的句柄。句柄可以定义为局部(值仅在同一个进程中有效),也可以定义为系统范围的(任何<code>W-process</code> 都可以使用相同的句柄)。</p>
<h2 data-tool="mdnice编辑器"><strong>Wine、调试和 Winedbg</strong></h2>
<p data-tool="mdnice编辑器">在 Wine 中谈到调试时,至少需要考虑两个层次:</p>
<ul class="list-paddingleft-1">
<li>
<section>Windows 调试 API。</section>
</li>
<li>
<section>Wine 集成调试器,被称为 <strong>winedbg</strong>。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">Wine 实现了大多数 Windows 调试 API。调试 API 的第一部分在 KERNEL32.DLL 中实现,允许称为调试器的<code>W-process</code> 控制另一个被调试的<code>W-process</code> 的执行。控制意味着停止/恢复执行、启用/禁用单步、设置断点、读写内存等等。调试 API 的另一部分在 DBGHELP.DLL (依赖 IMGHLP.DLL) 中实现,允许调试器查看任何模块中的符号和符号类型(如果模块已使用调试选项编译)。</p>
<p data-tool="mdnice编辑器"><strong>Winedbg</strong> 就是一个使用这些 API 的 Winelib 应用程序,允许调试任何 Wine 或 Winelib 应用程序以及 Wine 本身。</p>
<h2 data-tool="mdnice编辑器"><strong>调试教程</strong></h2>
<p data-tool="mdnice编辑器">这些教程针对的是了解 C 语言编程,但刚刚开始参与开发 Wine 的人。旨在演示当应用程序不工作时怎样调试问题。</p>
<ul class="list-paddingleft-1">
<li>
<section>调试 Reason 3 - 一个简单的"未处理异常"错误消息。介绍了调试跟踪、shell32.dll 和 SEH/异常跟踪。</section>
<section>https://wiki.winehq.org/Debugging_Reason_3</section>
</li>
<li>
<section>调试 PE Explorer - 修复文件打开对话框中的简单挂起(另一个 shell32 错误)。介绍了 winedbg 的堆栈回溯用法和不同类型的错误码。</section>
<section>https://wiki.winehq.org/Debugging_PE_Explorer</section>
</li>
<li>
<section>调试 Wild Metal Country - 查找游戏崩溃的原因以及如何确认错误。</section>
<section>https://wiki.winehq.org/Debugging_Wild_Metal_Country</section>
</li>
<li>
<section>Anastasius Focht 提交的 bug 报告里详细描述了他如何发现问题,以下网址可查看他的 bug 报告:</section>
<section>http://bugs.winehq.org/buglist.cgi?query_format=advanced&amp;amp;emailreporter1=1&amp;amp;emaillongdesc1=1&amp;amp;email1=focht&amp;amp;emailtype1=substring</section>
<section></section>
</li>
</ul>
<p>&nbsp;</p>
<h1 style="text-align: center;"><strong>Winedbg 启动方法</strong></h1>
<h2 data-tool="mdnice编辑器"><strong>启动一个进程</strong></h2>
<p data-tool="mdnice编辑器">任何程序(原生的  Windows 程序或链接 Winelib 的程序)都可以用 winedbg 来运行,命令行选项跟 Wine 一样的:</p>
<blockquote>
<p data-tool="mdnice编辑器">winedbg telnet.exe<br />
winedbg hl.exe -windowed</p>
</blockquote>
<h2 data-tool="mdnice编辑器"><strong>附加一个进程</strong></h2>
<p data-tool="mdnice编辑器">Winedbg 也可以不加任何命令行参数来启动: 此时 winedbg 以没有附加任何进程方式启动。您可以使用<code>info proc</code> 命令获取正在运行的<code>W-process</code> (及其<code>wpid</code>)的列表,然后使用<code>attach</code> 命令跟一个要调试的<code>W-process</code>的<code>wpid</code> 参数。这功能允许您调试已经启动的应用程序。</p>
<h2 data-tool="mdnice编辑器"><strong>在发生异常的时候</strong></h2>
<p data-tool="mdnice编辑器">当出现问题时,Windows 会将它作为异常进行跟踪。比如有分段违例、堆栈溢出、除零等异常。</p>
<p data-tool="mdnice编辑器">发生异常时,Wine 会检查<code>W-process</code> 是否被调试。如果是,异常事件将发送到调试器,调试器负责是否传递该异常。此机制是标准 Windows 调试 API 的一部分。</p>
<p data-tool="mdnice编辑器">如果<code>W-process</code> 没有被调试,Wine 会尝试启动调试器。此调试器(通常是 winedbg,请参阅下一节的配置以了解更多详细信息),在启动时附加到生成异常事件的<code>W-process</code> 。在这种情况下,您可以查看异常的原因,并修复原因(和继续执行)或深入挖掘以了解出错的原因。</p>
<p data-tool="mdnice编辑器">如果 winedbg 是标准调试器,则<code>pass</code> 和<code>cont</code> 命令是让进程进一步处理异常事件的两种方法。</p>
<p data-tool="mdnice编辑器">更精确地说:当发生故障时(分段违例、堆栈溢出……),该事件首先发送到调试器(这称为第一次异常处理机会)。调试器可以给出两个答案:</p>
<ul class="list-paddingleft-1">
<li>
<section>continue 调试器能够修复这个异常,并且能够让程序继续执行。</section>
</li>
<li>
<section>pass 调试器在第一次异常处理机会时不能修复这个异常。Wine 将尝试遍历异常处理程序列表,查看其中一个处理程序是否可以处理该异常。如果未找到异常处理程序,则再次将这个异常发送到调试器,以指示异常处理失败。</section>
</li>
</ul>
<blockquote>
<p data-tool="mdnice编辑器"><strong>注意:</strong>由于某些 Wine 代码使用异常和 try/catch 块来实现某些功能,因此在这种情况下winedbg 收到 segv 异常而停下来。例如,使用 IsBadReadPtr 函数时会发生这种情况。在这种情况下,应使用<code>pass</code> 命令,以便由 IsBadReadPtr 中的 catch 块处理异常。</p>
</blockquote>
<h2 data-tool="mdnice编辑器"><strong>中断</strong></h2>
<p data-tool="mdnice编辑器">您可以在winedbg 窗口同时按下 Ctrl+C 来停止正在运行的被调试程序,并允许您在 winedbg 里面操作被调试程序的进程上下文。</p>
<h2 data-tool="mdnice编辑器"><strong>退出</strong></h2>
<p data-tool="mdnice编辑器">Wine 支持新的 XP API,允许调试器从被调试程序上分离(见下文的<code>detach</code> 命令)。</p>
<p>&nbsp;</p>
<h1 style="text-align: center;"><strong>使用 Wine 调试器</strong></h1>
<p>这一节介绍从何处开始调试 Wine。如果您在任何时候卡住了并且需要帮助,请阅读 Wine 用户指南之如何报告 bug 一节。</p>
<h2 data-tool="mdnice编辑器"><strong>崩溃</strong></h2>
<p data-tool="mdnice编辑器">崩溃时我们常看到类似这样的对话框:</p>
<blockquote>
<p data-tool="mdnice编辑器">Unhandled exception: page fault on write access to 0x00000000 in 32-bit code (0x0043369e).<br />
Register dump:<br />
CS:0023 SS:002b DS:002b ES:002b FS:0063 GS:006b<br />
EIP:0043369e ESP:0b3ee90c EBP:0b3ee938 EFLAGS:00010246(  R- --  I  Z- -P- )<br />
EAX:00000072 EBX:7b8acff4 ECX:00000000 EDX:6f727265<br />
ESI:7ba3b37c EDI:7ffa0000<br />
Stack dump:<br />
0x0b3ee90c:  7b82ced8 00000000 7ba3b348 7b884401<br />
0x0b3ee91c:  7b883cdc 00000008 00000000 7bc36e7b<br />
0x0b3ee92c:  7b8acff4 7b82ceb9 7b8acff4 0b3eea18<br />
0x0b3ee93c:  7b82ce82 00000000 00000000 00000000<br />
0x0b3ee94c:  00000000 0b3ee968 70d7ed7b 70c50000<br />
0x0b3ee95c:  00000000 0b3eea40 7b87fd40 7b82d0d0<br />
Backtrace:<br />
=&gt;0 0x0043369e in elementclient (+0x3369e) (0x0b3ee938)<br />
1 0x7b82ce82 CONSOLE_SendEventThread+0xe1(pmt=0x0(nil)) in kernel32 (0x0b3eea18)<br />
2 0x7bc76320 call_thread_func_wrapper+0xb() in ntdll (0x0b3eea28)<br />
3 0x7bc7916e call_thread_func+0x7d(entry=0x7b82cda0, arg=0x0(nil), frame=0xb3eeb18) in ntdll (0x0b3eeaf8)<br />
4 0x7bc762fe RtlRaiseException+0x21() in ntdll (0x0b3eeb18)<br />
5 0x7bc7f3da start_thread+0xe9(info=0x7ffa0fb8) in ntdll (0x0b3ef368)<br />
6 0xf7597adf start_thread+0xce() in libpthread.so.0 (0x0b3ef468)<br />
0x0043369e: movl    %edx,0x0(%ecx)<br />
Modules:<br />
Module  Address         Debug info  Name (143 modules)<br />
PE    340000-  3af000   Deferred        speedtreert<br />
PE    3b0000-  3d6000   Deferred        ftdriver<br />
PE    3e0000-  3e6000   Deferred        immwrapper<br />
PE    400000-  b87000   Export          elementclient<br />
PE    b90000-  e04000   Deferred        elementskill<br />
PE    e10000-  e42000   Deferred        ifc22<br />
PE  10000000-10016000   Deferred        zlibwapi<br />
ELF 41f75000-41f7e000   Deferred        librt.so.1<br />
ELF 41ff9000-42012000   Deferred        libresolv.so.2<br />
PE  48080000-480a8000   Deferred        msls31<br />
PE  65340000-653d2000   Deferred        oleaut32<br />
PE  70200000-70294000   Deferred        wininet<br />
PE  702b0000-70328000   Deferred        urlmon<br />
PE  70440000-704cf000   Deferred        mlang<br />
PE  70bd0000-70c34000   Deferred        shlwapi<br />
PE  70c50000-70ef3000   Deferred        mshtml<br />
PE  71930000-719b8000   Deferred        shdoclc<br />
PE  78130000-781cb000   Deferred        msvcr80<br />
ELF 79afb000-7b800000   Deferred        libnvidia-glcore.so.304.51<br />
ELF 7b800000-7ba3d000   Dwarf           kernel32&lt;elf&gt;<br />
\-PE  7b810000-7ba3d000   \               kernel32<br />
ELF 7bc00000-7bcd5000   Dwarf           ntdll&lt;elf&gt;<br />
\-PE  7bc10000-7bcd5000   \               ntdll<br />
ELF 7bf00000-7bf04000   Deferred        &lt;wine-loader&gt;<br />
ELF 7c288000-7c400000   Deferred        libvorbisenc.so.2<br />
PE  7c420000-7c4a7000   Deferred        msvcp80<br />
ELF 7c56d000-7c5b6000   Deferred        dinput&lt;elf&gt;<br />
\-PE  7c570000-7c5b6000   \               dinput<br />
ELF 7c5b6000-7c600000   Deferred        libdbus-1.so.3<br />
ELF 7c70e000-7c715000   Deferred        libasyncns.so.0<br />
ELF 7c715000-7c77e000   Deferred        libsndfile.so.1<br />
ELF 7c77e000-7c7e5000   Deferred        libpulsecommon-1.1.so<br />
ELF 7c7e5000-7c890000   Deferred        krnl386.exe16.so<br />
PE  7c7f0000-7c890000   Deferred        krnl386.exe16<br />
ELF 7c890000-7c900000   Deferred        ieframe&lt;elf&gt;<br />
\-PE  7c8a0000-7c900000   \               ieframe<br />
ELF 7ca00000-7ca1a000   Deferred        rasapi32&lt;elf&gt;<br />
\-PE  7ca10000-7ca1a000   \               rasapi32<br />
ELF 7ca1a000-7ca21000   Deferred        libnss_dns.so.2<br />
ELF 7ca21000-7ca25000   Deferred        libnss_mdns4_minimal.so.2<br />
ELF 7ca25000-7ca2d000   Deferred        libogg.so.0<br />
ELF 7ca2d000-7ca5a000   Deferred        libvorbis.so.0<br />
ELF 7cd5d000-7cd9c000   Deferred        libflac.so.8<br />
ELF 7cd9c000-7cdea000   Deferred        libpulse.so.0<br />
ELF 7cdfe000-7ce23000   Deferred        iphlpapi&lt;elf&gt;<br />
\-PE  7ce00000-7ce23000   \               iphlpapi<br />
ELF 7cff1000-7cffd000   Deferred        libnss_nis.so.2<br />
ELF 7d60d000-7d629000   Deferred        wsock32&lt;elf&gt;<br />
\-PE  7d610000-7d629000   \               wsock32<br />
ELF 7d80d000-7d828000   Deferred        libnsl.so.1<br />
ELF 7d8cf000-7d8db000   Deferred        libgsm.so.1<br />
ELF 7d8db000-7d903000   Deferred        winepulse&lt;elf&gt;<br />
\-PE  7d8e0000-7d903000   \               winepulse<br />
ELF 7d95c000-7d966000   Deferred        libwrap.so.0<br />
ELF 7d966000-7d96d000   Deferred        libxtst.so.6<br />
ELF 7d96d000-7d992000   Deferred        mmdevapi&lt;elf&gt;<br />
\-PE  7d970000-7d992000   \               mmdevapi<br />
ELF 7d9b3000-7d9d0000   Deferred        msimtf&lt;elf&gt;<br />
\-PE  7d9c0000-7d9d0000   \               msimtf<br />
ELF 7d9d0000-7d9e5000   Deferred        comm.drv16.so<br />
PE  7d9e0000-7d9e5000   Deferred        comm.drv16<br />
ELF 7da83000-7db5f000   Deferred        libgl.so.1<br />
ELF 7db60000-7db63000   Deferred        libx11-xcb.so.1<br />
ELF 7db63000-7db78000   Deferred        system.drv16.so<br />
PE  7db70000-7db78000   Deferred        system.drv16<br />
ELF 7db98000-7dca1000   Deferred        opengl32&lt;elf&gt;<br />
\-PE  7dbb0000-7dca1000   \               opengl32<br />
ELF 7dca1000-7dcb6000   Deferred        vdmdbg&lt;elf&gt;<br />
\-PE  7dcb0000-7dcb6000   \               vdmdbg<br />
ELF 7dcce000-7dd04000   Deferred        uxtheme&lt;elf&gt;<br />
\-PE  7dcd0000-7dd04000   \               uxtheme<br />
ELF 7dd04000-7dd0a000   Deferred        libxfixes.so.3<br />
ELF 7dd0a000-7dd15000   Deferred        libxcursor.so.1<br />
ELF 7dd16000-7dd1f000   Deferred        libjson.so.0<br />
ELF 7dd24000-7dd38000   Deferred        psapi&lt;elf&gt;<br />
\-PE  7dd30000-7dd38000   \               psapi<br />
ELF 7dd78000-7dda1000   Deferred        libexpat.so.1<br />
ELF 7dda1000-7ddd6000   Deferred        libfontconfig.so.1<br />
ELF 7ddd6000-7dde6000   Deferred        libxi.so.6<br />
ELF 7dde6000-7ddef000   Deferred        libxrandr.so.2<br />
ELF 7ddef000-7de11000   Deferred        libxcb.so.1<br />
ELF 7de11000-7df49000   Deferred        libx11.so.6<br />
ELF 7df49000-7df5b000   Deferred        libxext.so.6<br />
ELF 7df5b000-7df75000   Deferred        libice.so.6<br />
ELF 7df75000-7e005000   Deferred        winex11&lt;elf&gt;<br />
\-PE  7df80000-7e005000   \               winex11<br />
ELF 7e005000-7e0a5000   Deferred        libfreetype.so.6<br />
ELF 7e0a5000-7e0c5000   Deferred        libtinfo.so.5<br />
ELF 7e0c5000-7e0ea000   Deferred        libncurses.so.5<br />
ELF 7e123000-7e1eb000   Deferred        crypt32&lt;elf&gt;<br />
\-PE  7e130000-7e1eb000   \               crypt32<br />
ELF 7e1eb000-7e235000   Deferred        dsound&lt;elf&gt;<br />
\-PE  7e1f0000-7e235000   \               dsound<br />
ELF 7e235000-7e2a7000   Deferred        ddraw&lt;elf&gt;<br />
\-PE  7e240000-7e2a7000   \               ddraw<br />
ELF 7e2a7000-7e3e3000   Deferred        wined3d&lt;elf&gt;<br />
\-PE  7e2b0000-7e3e3000   \               wined3d<br />
ELF 7e3e3000-7e417000   Deferred        d3d8&lt;elf&gt;<br />
\-PE  7e3f0000-7e417000   \               d3d8<br />
ELF 7e417000-7e43b000   Deferred        imm32&lt;elf&gt;<br />
\-PE  7e420000-7e43b000   \               imm32<br />
ELF 7e43b000-7e46f000   Deferred        ws2_32&lt;elf&gt;<br />
\-PE  7e440000-7e46f000   \               ws2_32<br />
ELF 7e46f000-7e49a000   Deferred        msacm32&lt;elf&gt;<br />
\-PE  7e470000-7e49a000   \               msacm32<br />
ELF 7e49a000-7e519000   Deferred        rpcrt4&lt;elf&gt;<br />
\-PE  7e4b0000-7e519000   \               rpcrt4<br />
ELF 7e519000-7e644000   Deferred        ole32&lt;elf&gt;<br />
\-PE  7e530000-7e644000   \               ole32<br />
ELF 7e644000-7e6f7000   Deferred        winmm&lt;elf&gt;<br />
\-PE  7e650000-7e6f7000   \               winmm<br />
ELF 7e6f7000-7e7fa000   Deferred        comctl32&lt;elf&gt;<br />
\-PE  7e700000-7e7fa000   \               comctl32<br />
ELF 7e7fa000-7ea23000   Deferred        shell32&lt;elf&gt;<br />
\-PE  7e810000-7ea23000   \               shell32<br />
ELF 7ea23000-7eaf9000   Deferred        gdi32&lt;elf&gt;<br />
\-PE  7ea30000-7eaf9000   \               gdi32<br />
ELF 7eafb000-7eaff000   Deferred        libnvidia-tls.so.304.51<br />
ELF 7eaff000-7eb09000   Deferred        libxrender.so.1<br />
ELF 7eb09000-7eb0f000   Deferred        libxxf86vm.so.1<br />
ELF 7eb0f000-7eb18000   Deferred        libsm.so.6<br />
ELF 7eb18000-7eb32000   Deferred        version&lt;elf&gt;<br />
\-PE  7eb20000-7eb32000   \               version<br />
ELF 7eb32000-7ec87000   Deferred        user32&lt;elf&gt;<br />
\-PE  7eb40000-7ec87000   \               user32<br />
ELF 7ec87000-7ecf1000   Deferred        advapi32&lt;elf&gt;<br />
\-PE  7ec90000-7ecf1000   \               advapi32<br />
ELF 7ecf1000-7ed8f000   Deferred        msvcrt&lt;elf&gt;<br />
\-PE  7ed00000-7ed8f000   \               msvcrt<br />
ELF 7ef8f000-7ef9c000   Deferred        libnss_files.so.2<br />
ELF 7ef9c000-7efc7000   Deferred        libm.so.6<br />
ELF 7efc8000-7efe5000   Deferred        libgcc_s.so.1<br />
ELF 7efe5000-7f000000   Deferred        crtdll&lt;elf&gt;<br />
\-PE  7eff0000-7f000000   \               crtdll<br />
ELF f73d0000-f73d4000   Deferred        libxinerama.so.1<br />
ELF f73d4000-f73d8000   Deferred        libxau.so.6<br />
ELF f73da000-f73df000   Deferred        libdl.so.2<br />
ELF f73df000-f7591000   Dwarf           libc.so.6<br />
ELF f7591000-f75ab000   Dwarf           libpthread.so.0<br />
ELF f75ab000-f76ef000   Dwarf           libwine.so.1<br />
ELF f7722000-f7728000   Deferred        libuuid.so.1<br />
ELF f7729000-f774a000   Deferred        ld-linux.so.2<br />
ELF f774a000-f774b000   Deferred        .so<br />
Threads:<br />
process  tid      prio (all id:s are in hex)<br />
00000008 (D) C:\Perfect World Entertainment\Perfect World International\element\elementclient.exe<br />
00000031    0 &lt;==<br />
00000035   15<br />
00000012    0<br />
00000021    0<br />
00000045    0<br />
00000044    0<br />
00000043    0<br />
00000038   15<br />
00000037    0<br />
00000036   15<br />
00000034    0<br />
00000033    0<br />
00000032    0<br />
00000027    0<br />
00000009    0<br />
0000000e services.exe<br />
0000000b    0<br />
00000020    0<br />
00000017    0<br />
00000010    0<br />
0000000f    0<br />
00000014 winedevice.exe<br />
0000001e    0<br />
0000001b    0<br />
00000016    0<br />
00000015    0<br />
0000001c plugplay.exe<br />
00000022    0<br />
0000001f    0<br />
0000001d    0<br />
00000023 explorer.exe<br />
00000024    0</p>
</blockquote>
<p>&nbsp;</p>
<p data-tool="mdnice编辑器">调试崩溃的步骤。您可能在任何步骤中崩溃,但请报告 bug,并在 bug 报告中提供收集到的尽可能多的信息。</p>
<ol class="list-paddingleft-1">
<li>
<section>了解崩溃的原因。通常是页面错误、调用了Wine 中未实现的函数,或类似的原因。报告崩溃时,报告整个崩溃转储,即使它对您没有意义。(在这个例子里面,在写入 0x0000000 时出现页面错误。最有可能的是 Wine 将 NULL 传递给应用程序或类似问题。)</section>
</li>
<li>
<section>确定崩溃的原因。由于通常是 Wine 实现的函数执行失败或者行为不正确导致的主要/次要反应,因此使用<code>WINEDEBUG=+relay</code> 环境变量重新运行 Wine。这将生成相当多的日志输出,但通常原因是位于最后一个函数调用中。这些日志通常如下所示:</section>
</li>
</ol>
<p><img loading="lazy" class="alignnone size-full wp-image-36060 aligncenter" src="https://www.deepin.org/wp-content/uploads/2025/01/微信图片_2025-01-16_100310_196.png" alt="" width="768" height="203" srcset="https://www.deepin.org/wp-content/uploads/2025/01/微信图片_2025-01-16_100310_196.png 768w, https://www.deepin.org/wp-content/uploads/2025/01/微信图片_2025-01-16_100310_196-300x79.png 300w, https://www.deepin.org/wp-content/uploads/2025/01/微信图片_2025-01-16_100310_196-150x40.png 150w, https://www.deepin.org/wp-content/uploads/2025/01/微信图片_2025-01-16_100310_196-24x6.png 24w, https://www.deepin.org/wp-content/uploads/2025/01/微信图片_2025-01-16_100310_196-36x10.png 36w, https://www.deepin.org/wp-content/uploads/2025/01/微信图片_2025-01-16_100310_196-48x13.png 48w" sizes="(max-width: 768px) 100vw, 768px" /></p>
<ol class="list-paddingleft-1">
<li>
<section>如果你已经发现了一个行为不正常的 Wine 函数,尝试找出它行为不正常的原因。在源代码中查找函数。试着理解传递的函数参数。通常有一个<code>WINE_DEFAULT_DEBUG_CHANNEL(channel);</code> 在源文件的开头。使用<code>WINEDEBUG=+xyz,+relay</code> 环境变量重新运行 Wine。有时,在源文件的开头以<code>WINE_DECLARE_DEBUG_CHANNEL(channel)</code>的形式定义了其他调试通道;如果是这样,有问题的函数也可能使用了这些备用通道之一。在该函数中搜索<code>TRACE_(channel)(".../n");</code> 并将找到的这些额外的通道添加到 WINEDEBUG 环境变量里面。</section>
</li>
<li>
<section>有关如何使用 winedbg 进行调试的其他信息,请参阅源码<code>programs/winedbg/README</code>。</section>
</li>
<li>
<section>如果这些信息不够清晰,或者您想知道该函数发生的更多信息,请尝试使用<code>WINEDEBUG=+all</code>重新运行 Wine ,这将转储 Wine 里面包含调试信息在内的<strong>所有</strong>日志。通常需要限制生成的调试输出。这可以通过管道把输出日志发给<code>grep</code> 过滤,或者使用注册表项来完成。有关详细信息,请参阅下文的<code>配置 +relay 行为</code> 一节。</section>
</li>
<li>
<section>如果这还不够,可以在您认为相关的函数中手动添加更多调试日志。有关详细信息,请参阅开发人员调试日志使用指南。您也可以尝试在 gdb 中运行该程序,代替使用 Wine 调试器。如果这样做,请在<code>~/.gdbinit</code> 文件里面增加这句<code>handle SIGSEGV nostop noprint</code> 来禁用 gdb 对<code>seg fault</code> 错误的处理(Win16 需要)。</section>
</li>
<li>
<section>您还可以为该函数设置断点。用 winedbg 启动调试程序而不是 Wine。一旦调试器运行起来,在命令行提示符输入命令:<code>break RegOpenKeyExW</code> (将 RegOpenKeyExW 替换成您要调试的函数,区分大小写)以设置断点。然后,使用<code>continue</code> 命令启动程序正常执行。程序运行到断点位置,程序将停止;如果程序还没有运行到该函数崩溃的那次调用,再次使用<code>continue</code> 命令继续运行程序直到达到该函数即将崩溃的那次调用。现在,您可以用单步执行命令来继续运行程序,直到达到崩溃点,然后使用其他调试器命令来查看寄存器值和相关变量值等等。</section>
</li>
</ol>
<h2 data-tool="mdnice编辑器"><strong>程序挂起,没有反应</strong></h2>
<p data-tool="mdnice编辑器">用 winedbg 启动程序而不是 Wine 。当程序没有反应的时候,切换到 winedbg 窗口,并按 Ctrl+C 。这将停止程序,并允许您调试该程序,就像崩溃时候一样。</p>
<h2 data-tool="mdnice编辑器"><strong>程序弹出错误消息框</strong></h2>
<p data-tool="mdnice编辑器">有时候程序会使用或多或少的非描述性消息框报告失败。我们可以使用与崩溃相同的方法进行调试,但有一个问题,为了设置消息框,程序会多出大量的调试日志。</p>
<p data-tool="mdnice编辑器">由于故障通常发生在设置消息框之前,您可以启动 winedbg 并在 MessageBoxA (由 Win16 和 Win32 程序调用)处设置断点,然后继续运行。程序将在设置消息框之前停止。</p>
<p data-tool="mdnice编辑器">您也可以使用这个命令来运行程序:<code>WINEDEBUG=+relay wine program.exe 2&gt;&amp;1 | less -i</code>然后在 less 里面搜索 MessageBox 。</p>
<h2 data-tool="mdnice编辑器"><strong>反汇编程序</strong></h2>
<p data-tool="mdnice编辑器">您也可以尝试反汇编有问题的程序,以检查没有公开的功能或使用它们。</p>
<p data-tool="mdnice编辑器">理解汇编代码主要是一个练习问题。Win16 函数入口通常如下所示:</p>
<blockquote>
<p data-tool="mdnice编辑器">push bp<br />
mov bp, sp<br />
... 函数代码 ..<br />
retf XXXX   &lt;--------- XXXX 是函数参数的总字节数</p>
</blockquote>
<p data-tool="mdnice编辑器">这是一个没有局部变量的 FAR 函数。参数通常从<code></code> 开始,偏移量增加。请注意,对于使用 PASCAL 调用约定导出的 Win16 函数,<code></code> 属于最右侧的参数。因此,如果我们使用带<code>a</code> 和<code>b</code> 的<code>strcmp(a,b)</code> 来说,则参数<code>b</code> 的存储位置在<code></code>,参数<code>a</code> 的存储位置在<code></code> 。</p>
<p data-tool="mdnice编辑器">大多数函数用栈存储局部变量:</p>
<blockquote>
<p data-tool="mdnice编辑器">enter 0086, 00<br />
... 函数代码 ...<br />
leave<br />
retf XXXX</p>
</blockquote>
<p data-tool="mdnice编辑器">这与上述内容基本相同,但还添加了 0x86 字节的栈存储,使用<code></code> 进行访问。在调用该函数之前,使用如下所示的代码把参数压到栈上:</p>
<blockquote><p>push word ptr   &lt;- 压到 处<br />
push di                 &lt;- 压到 处<br />
call KERNEL.LSTRLEN</p></blockquote>
<p>在这里,首先压人选择器地址,然后压入传递的字符串的偏移量。</p>
<h2 data-tool="mdnice编辑器"><strong>调试示例</strong></h2>
<p data-tool="mdnice编辑器">让我们调试臭名昭著的 WORD SHARE.EXE 消息框:</p>
<blockquote><p>|marcus@jet $ wine winword.exe<br />
|            +---------------------------------------------+<br />
|            | !  You must leave Windows and load SHARE.EXE|<br />
|            |    before starting Word.                    |<br />
|            +---------------------------------------------+</p></blockquote>
<p>&nbsp;</p>
<blockquote><p>|marcus@jet $ WINEDEBUG=+relay,-debug wine winword.exe<br />
|CallTo32(wndproc=0x40065bc0,hwnd=000001ac,msg=00000081,wp=00000000,lp=00000000)<br />
|Win16 task 'winword': Breakpoint 1 at 0x01d7:0x001a<br />
|CallTo16(func=0127:0070,ds=0927)<br />
|Call WPROCS.24: TASK_RESCHEDULE() ret=00b7:1456 ds=0927<br />
|Ret  WPROCS.24: TASK_RESCHEDULE() retval=0x8672 ret=00b7:1456 ds=0927<br />
|CallTo16(func=01d7:001a,ds=0927)<br />
|     AX=0000 BX=3cb4 CX=1f40 DX=0000 SI=0000 DI=0927 BP=0000 ES=11f7<br />
|Loading symbols: /home/marcus/wine/wine...<br />
|Stopped on breakpoint 1 at 0x01d7:0x001a<br />
|In 16 bit mode.<br />
|Wine-dbg&amp;gt;break MessageBoxA                          c                                            &amp;lt;---- Continue<br />
|Call KERNEL.91: INITTASK() ret=0157:0022 ds=08a7<br />
|     AX=0000 BX=3cb4 CX=1f40 DX=0000 SI=0000 DI=08a7 ES=11d7 EFL=00000286<br />
|CallTo16(func=090f:085c,ds=0dcf,0x0000,0x0000,0x0000,0x0000,0x0800,0x0000,0x0000,0x0dcf)<br />
|...                                                   &amp;lt;----- Much debug output<br />
|Call KERNEL.136: GETDRIVETYPE(0x0000) ret=060f:097b ds=0927<br />
^^^^^^ Drive 0 (A:)<br />
|Ret  KERNEL.136: GETDRIVETYPE() retval=0x0002 ret=060f:097b ds=0927<br />
^^^^^^  DRIVE_REMOVEABLE<br />
(It is a floppy diskdrive.)</p>
<p>|Call KERNEL.136: GETDRIVETYPE(0x0001) ret=060f:097b ds=0927<br />
^^^^^^ Drive 1 (B:)<br />
|Ret  KERNEL.136: GETDRIVETYPE() retval=0x0000 ret=060f:097b ds=0927<br />
^^^^^^  DRIVE_CANNOTDETERMINE<br />
(I don&amp;#039;t have drive B: assigned)</p>
<p>|Call KERNEL.136: GETDRIVETYPE(0x0002) ret=060f:097b ds=0927<br />
^^^^^^^ Drive 2 (C:)<br />
|Ret  KERNEL.136: GETDRIVETYPE() retval=0x0003 ret=060f:097b ds=0927<br />
^^^^^^ DRIVE_FIXED<br />
(specified as a hard disk)</p>
<p>|Call KERNEL.97: GETTEMPFILENAME(0x00c3,0x09278364&amp;quot;doc&amp;quot;,0x0000,0927:8248) ret=060f:09b1 ds=0927<br />
^^^^^^           ^^^^^        ^^^^^^^^^<br />
|                |            |buffer for fname<br />
|                |temporary name ~docXXXX.tmp<br />
|Force use of Drive C:.</p>
<p>|Warning: GetTempFileName returns &amp;#039;C:~doc9281.tmp&amp;#039;, which doesn&amp;#039;t seem to be writable.<br />
|Please check your configuration file if this generates a failure.</p></blockquote>
<p>&nbsp;</p>
<p>哎呀,日志中发现了问题 (OPENFILE 失败):</p>
<blockquote><p>|Ret  KERNEL.97: GETTEMPFILENAME() retval=0x9281 ret=060f:09b1 ds=0927<br />
^^^^^^ Temporary storage ID</p>
<p>|Call KERNEL.74: OPENFILE(0x09278248&amp;quot;C:~doc9281.tmp&amp;quot;,0927:82da,0x1012) ret=060f:09d8 ds=0927<br />
^^^^^^^^^^^^^^^^ ^^^^^^^^^ ^^^^^^^<br />
|filename        |OFSTRUCT |open mode:</p>
<p>OF_CREATE|OF_SHARE_EXCLUSIVE|OF_READWRITE</p></blockquote>
<p>这里失败的原因是我的 C 盘是只读的:</p>
<blockquote><p>|Ret  KERNEL.74: OPENFILE() retval=0xffff ret=060f:09d8 ds=0927<br />
^^^^^^ HFILE_ERROR16, yes, it failed.</p>
<p>|Call USER.1: MESSAGEBOX(0x0000,0x09278376&amp;quot;You must close Windows and load SHARE.EXE before you start Word.&amp;quot;,0x00000000,0x1030) ret=060f:084f ds=0927</p></blockquote>
<p>&nbsp;</p>
<p>并且在 MessageBoxA 的入口停下来了:</p>
<blockquote><p>|Stopped on breakpoint 2 at 0x40189100 (MessageBoxA )<br />
|190     {</p></blockquote>
<p>代码看起来要找一个可写磁盘,并试图在该磁盘创建一个文件。要解决此 Bug,可以将 C 盘定义为网络驱动器,上述代码将忽略该驱动器。</p>
<h2 data-tool="mdnice编辑器"><strong>调试技巧</strong></h2>
<p data-tool="mdnice编辑器">以下是一些其他调试技巧:</p>
<section>1. 如果您有一个程序在加载前期崩溃,以至于您无法正常使用 Wine 调试器来调试,但 Wine 已执行该程序的启动代码,则可以使用特殊的技巧。您应该执行<code>WINEDEBUG=+relay wine program</code>获取程序在启动函数中调用的所有函数清单。然后执行:<code>winedbg win</code><code>file.exe</code>这样,您就进入 winedbg。现在,您可以在 start 函数中调用的任何函数上设置断点,然后不断按 c 以跳过 Winfile 对此函数的正常调用,直到您最终到达此函数调用崩溃的位置。您就可以像平常一样继续调试该程序。</section>
<p>2. 如果尝试运行程序,程序在弹出错误消息框后就退出,则问题的原因通常可以检查在 MessageBox 之前调用的一些函数的返回值发现。您应该用下面的方式重新运行程序:<code>WINEDEBUG=+relay wine program_name &amp;&gt;relmsg</code>接着执行<code>more relmsg</code> 然后搜索最后一个出现的<code>MESSAGEBOX</code>,类似这样的:<code>Call USER.1: MESSAGEBOX(0x0000,0x01ff1246 "Runtime error 219 at 0004:1056.",0x00000000,0x1010) ret=01f7:2160 ds=01ff</code>在我的例子里面,在调用<code>MessageBox</code> 函数之前的代码类似这样:</p>
<blockquote>
<section>Call KERNEL.96: FREELIBRARY(0x0347) ret=01cf:1033 ds=01ff<br />
CallTo16(func=033f:0072,ds=01ff,0x0000)<br />
Ret  KERNEL.96: FREELIBRARY() retval=0x0001 ret=01cf:1033 ds=01ff<br />
Call KERNEL.96: FREELIBRARY(0x036f) ret=01cf:1043 ds=01ff<br />
CallTo16(func=0367:0072,ds=01ff,0x0000)<br />
Ret  KERNEL.96: FREELIBRARY() retval=0x0001 ret=01cf:1043 ds=01ff<br />
Call KERNEL.96: FREELIBRARY(0x031f) ret=01cf:105c ds=01ff<br />
CallTo16(func=0317:0072,ds=01ff,0x0000)<br />
Ret  KERNEL.96: FREELIBRARY() retval=0x0001 ret=01cf:105c ds=01ff<br />
Call USER.171: WINHELP(0x02ac,0x01ff05b4 "COMET.HLP",0x0002,0x00000000) ret=01cf:1070 ds=01ff<br />
CallTo16(func=0117:0080,ds=01ff)<br />
Call WPROCS.24: TASK_RESCHEDULE() ret=00a7:0a2d ds=002b<br />
Ret  WPROCS.24: TASK_RESCHEDULE() retval=0x0000 ret=00a7:0a2d ds=002b<br />
Ret  USER.171: WINHELP() retval=0x0001 ret=01cf:1070 ds=01ff<br />
Call KERNEL.96: FREELIBRARY(0x01be) ret=01df:3e29 ds=01ff<br />
Ret  KERNEL.96: FREELIBRARY() retval=0x0000 ret=01df:3e29 ds=01ff<br />
Call KERNEL.52: FREEPROCINSTANCE(0x02cf00ba) ret=01f7:1460 ds=01ff<br />
Ret  KERNEL.52: FREEPROCINSTANCE() retval=0x0001 ret=01f7:1460 ds=01ff<br />
Call USER.1: MESSAGEBOX(0x0000,0x01ff1246 "Runtime error 219 at 0004:1056.",0x00000000,0x1010) ret=01f7:2160 ds=01ff</section>
</blockquote>
<p>我认为本示例中对 MessageBox 的调用不是由以前调用的函数返回错误值引起的(经常发生这样的情况),而是消息框里面提到的:<code>0x0004:0x1056</code> 处出现运行时错误。由于地址的段值仅为 4,因此我认为这只是一个内部值。但偏移地址揭示了一些非常有趣的内容,偏移 0x1056 非常接近 FREELIBRARY 的返回地址:</p>
<blockquote><p>Call KERNEL.96: FREELIBRARY(0x031f) ret=01cf:105c ds=01ff<br />
^^^^</p></blockquote>
<p data-tool="mdnice编辑器">如果段 0x0004 确实是段 0x1cf,我们可以反汇编调用 FreeLibrary 的地址,分析发生运行时错误之前的某些行。</p>
<p data-tool="mdnice编辑器">3. 如果希望设置某个位置的断点,但该断点所在的模块还没有映射到内存里面,则可以将断点设置为 GetVersion16/32 函数,因为这些函数被调用很频繁,断点停下来的时候执行<code>continue</code> 命令直到您能够设置此断点而不再显示错误消息。</p>
<h2 data-tool="mdnice编辑器">调试器的基本用法</h2>
<p data-tool="mdnice编辑器">使用<code>winebg myprog.exe</code> 启动程序后,程序加载并在起点处停止,终端显示 winedbg 命令行提示符。然后,您可以这样设置断点:</p>
<blockquote><p>b RoutineName (按函数名称加断点)或<br />
b *0x812575   (按地址加断点)</p></blockquote>
<p>然后,您输入 c(continue命令简写)来运行程序。当它停在断点处后,您可以键入:</p>
<blockquote><p>step            (一次步进一行)或<br />
stepi           (一次步进一个机器指令;它有助于了解386基本指令集)<br />
info reg        (查看寄存器)<br />
info stack      (查看堆栈中的十六进制值)<br />
info local      (查看局部变量)<br />
list 行号       (列出源代码)<br />
x 变量名称      (检查变量;仅当代码关闭优化编译时候有效)<br />
x 0x4269978     (检查内存位置的内容)<br />
?               (帮助)<br />
q               (退出)</p></blockquote>
<p>直接按 Enter,您可以重复最后一个命令。</p>
<h2 data-tool="mdnice编辑器"><strong>有用的程序</strong></h2>
<p data-tool="mdnice编辑器">一些有用的程序:</p>
<ul class="list-paddingleft-1">
<li>
<section>IDA:IDA Pro 是强烈推荐的,但不是免费的。</section>
</li>
<li>
<section>pedump:http://pedump.me/,转储 PE 格式的 DLL 的导入和导出。</section>
</li>
<li>
<section>winedump:(包括在 Wine 中),转储 PE 格式的 DLL 的导入和导出。 </section>
</li>
</ul>
<p>&nbsp;</p>
<h1 style="text-align: center;"><strong>配置</strong></h1>
<h2 data-tool="mdnice编辑器"><strong>Windows 调试配置</strong></h2>
<p data-tool="mdnice编辑器">Windows 调试 API 使用这个注册表项来指明发生未处理异常时要调用哪个调试器。</p>
<blockquote>
<p data-tool="mdnice编辑器">
</blockquote>
<p data-tool="mdnice编辑器">有两个值来决定行为:</p>
<ul class="list-paddingleft-1">
<li>
<section>Debugger 指定用于启动调试器的命令行(它使用两个 printf 格式占位符 %ld 将上下文相关信息传递给调试器)。您应该在这里放置一个您的调试器的完整路径 ( winedbg 当然可以使用,但任何其他使用 Windows 调试 API 的调试器也可以 )。您选择使用的调试器的路径必须通过 Wine 容器根目录的 dosdevices 子目录里面配置的 DOS 驱动器之一进行访问。</section>
</li>
<li>
<section>Auto 如果此值为零,在发生未处理异常时将弹出对话框询问用户是否希望启动调试器。否则,调试器将自动启动。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">默认的 Wine 注册表如下所示:</p>
<blockquote>
<p data-tool="mdnice编辑器"> 957636538<br />
"Auto"=dword:00000001<br />
"Debugger"="winedbg %ld %ld"</p>
</blockquote>
<p data-tool="mdnice编辑器"><strong>注意 1:</strong> 创建这个注册表项是必需的。如果不这样做,在发生异常时不会触发调试器。</p>
<p data-tool="mdnice编辑器"><strong>注意 2:</strong> wineinstall(Wine 附带的) 创建这个注册表项。但是由于安装的注册表存在一些限制,如果存在以前的 Wine 安装,则先删除整个<code></code> ,再运行 wineinstall 以重新创建才是安全的。</p>
<h2 data-tool="mdnice编辑器"><strong>Winedbg 配置</strong></h2>
<p data-tool="mdnice编辑器">Winedbg 可通过多种选项进行配置。这些选项按用户存储在注册表中:<code></code></p>
<p data-tool="mdnice编辑器">这些选项可以在 winedbg 里面读取/写入,作为调试器表达式的一部分。要引用这些选项之一,其名称必须以<code>$</code> 符号为前缀。例如:</p>
<p data-tool="mdnice编辑器"><code>set $BreakAllThreadsStartup = 1</code></p>
<p data-tool="mdnice编辑器">将<code>BreakAllThreadsStartup</code> 选项设置为 TRUE。</p>
<p data-tool="mdnice编辑器">所有选项在 winedbg 启动时从注册表中读取(如果未找到相应的值,则使用默认值),并在 winedbg 退出时写回注册表。</p>
<p data-tool="mdnice编辑器">下面是所有选项的列表:</p>
<ul class="list-paddingleft-1">
<li>
<section>BreakAllThreadsStartup 如果为 TRUE 则在所有线程启动时调试器停止;如果为 FALSE 仅在给定进程的第一个线程启动时调试器停止。默认情况下为 FALSE。</section>
</li>
<li>
<section>BreakOnCritSectTimeOut 如果为 TRUE 则当临界区超时(5 分钟)时调试器停止;默认情况下为 TRUE。</section>
</li>
<li>
<section>BreakOnAttach 如果为 TRUE 则在未处理异常发生后 winedbg 附加到进程时,在第一个附加事件中停下来。由于附加事件在异常事件的上下文中没有意义,因此该选项最好是 FALSE。</section>
</li>
<li>
<section>BreakOnFirstChance 异常生成两个调试事件。第一个是在异常发生之后传递到调试器(称为第一次机会)。调试器可以决定恢复执行(通过 winedbg cont 命令)或将异常传递给程序中的异常处理程序链(如果存在)(winedbg 通过 pass 命令)。如果异常处理程序没有处理异常,则异常事件将再次发送到调试器(称为最后一次机会)。调试器不能传递最后一次机会的异常。如果 BreakOnFirstChance 为 TRUE ,则第一次机会异常和最后一次机会异常发生时 winedbg 都停止;如果为 FALSE,仅在最后一次机会异常是停止。</section>
</li>
</ul>
<h2 data-tool="mdnice编辑器"><strong>配置 +relay 行为</strong></h2>
<p data-tool="mdnice编辑器">将<code>WINEDEBUG</code> 设置为<code>+relay</code> 调试时,可能会得到大量输出日志。您可以通过把注册表中<code></code> 下的  <code>RelayExclude</code> 键值设置为用分号分隔的要排除的函数列表,例如:</p>
<p data-tool="mdnice编辑器"><code>"RtlEnterCriticalSection;RtlLeaveCriticalSection;kernel32.97;kernel32.98"</code></p>
<p data-tool="mdnice编辑器"><code>RelayInclude</code> 和<code>RelayExclude</code> 类似,只不过列出的函数将是输出中仅包含的函数。</p>
<p data-tool="mdnice编辑器">如果应用程序使用<code>+relay</code> 运行速度太慢无法获得有意义的输出,并且对生成的几 GB 的日志文件束手无策,不确定要排除哪些函数,下面是一个技巧。首先,运行应用程序一分钟左右,将其输出重定向到磁盘上的文件:</p>
<blockquote><p>WINEDEBUG=+relay wine appname.exe &amp;&gt;relay.log</p></blockquote>
<p>然后运行此命令以查看调用最多的函数:</p>
<blockquote><p>awk -F'(' '{print $1}' &lt; relay.log | awk '{print $2}' | sort | uniq -c | sort</p></blockquote>
<p>在确保这些函数不相关后使用<code>RelayExclude</code> 排除调用最多的函数,然后再次运行应用程序。</p>
<p>&nbsp;</p>
<h1 style="text-align: center;"><strong>Winedbg 表达式和变量</strong></h1>
<h2 data-tool="mdnice编辑器"><strong>表达式</strong></h2>
<p data-tool="mdnice编辑器">Winedbg 中的表达式大多以 C 形式编写。但是有一些差异:</p>
<ul class="list-paddingleft-1">
<li>
<section>标识符的名称中可以加一个<code>!</code>,这主要区分不同 DLL 的符号,如<code>USER32!CreateWindowExA</code> 表示 USER32.DLL 里面的 CreateWindowExA 函数。</section>
</li>
<li>
<section>在强制转换操作中,在指定结构或联合时,必须使用<code>struct</code> 或<code>union</code> 关键字(即使程序使用<code>typedef</code>)。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">当按名称指定标识符时,如果存在多个相同名称的符号,调试器将提示用户要选择哪个符号,输入你想要的那个符号前面的数字序号即可。</p>
<h2 data-tool="mdnice编辑器"><strong>变量</strong></h2>
<p data-tool="mdnice编辑器">Winedbg 定义自己的变量集。上面的配置变量是其中的一部分。其他包括:</p>
<ul class="list-paddingleft-1">
<li>
<section><code>$ThreadId</code>     当前调试的<code>W-thread</code> 的 ID</section>
</li>
<li>
<section><code>$ProcessId</code>   当前调试的<code>W-process</code> 的 ID</section>
</li>
<li>
<section><code>寄存器变量</code>   所有 CPU 寄存器用"$"前缀加寄存器名来访问。您可以使用<code>info regs</code> 来获取 CPU 寄存器的名称列表。</section>
</li>
<li>
<section><code>$ThreadId</code> 和<code>$ProcessId</code>变量可以很方便地在指定的线程或进程上设置条件断点。</section>
</li>
</ul>
<p>&nbsp;</p>
<h1 style="text-align: center;"><strong>Winedbg 命令参考</strong></h1>
<h2><strong>杂项</strong></h2>
<blockquote><p>abort           中止调试器<br />
quit            退出调试器<br />
attach N        附加到 <code>W-process</code> 进程(N 是其 ID,10进制数字或十六进制 (0xN))。<br />
ID 可以使用 <code>info process</code> 命令获取。请注意,<code>info process</code> 命令返回的是十六进制值。<br />
detach          从 <code>W-process</code> 进程分离。<br />
help            打印一些帮助<br />
help info       打印一些 info 命令的帮助</p></blockquote>
<h2><strong>流程控制</strong></h2>
<blockquote><p>cont,c          继续运行直到下一个断点或异常。<br />
pass            将异常事件传递给异常处理链。<br />
step,s          继续执行,直到下一行&amp;quot;C&amp;quot;代码(进入函数内部)<br />
next,n          继续执行,直到下一行&amp;quot;C&amp;quot;代码(不进入函数内部)<br />
stepi,si        执行下一个指令(进入函数内部)<br />
nexti,ni        执行下一个指令(不进入函数内部)<br />
finish,f        执行,直到当前函数返回</p></blockquote>
<p>cont、step、next、stepi、nexti 命令后面可以加一个数字 (N) 参数,表示命令执行 N 次。</p>
<h2><strong>断点、监视点</strong></h2>
<blockquote><p>enable N        启用编号为 N 的断点或监视点<br />
disable N       禁用编号为 N 的断点或监视点<br />
delete N        删除编号为 N 的断点或监视点<br />
cond N          删除编号为 N 的断点或监视点的条件<br />
cond N expr     设置编号为 N 的断点或监视点的条件;每次断点命中时,都会计算表达式 expr ,如果结果为零值,则不触发断点。<br />
break *N        在地址 N 处添加断点<br />
break ID        在符号 ID 的地址添加断点<br />
break N         在当前源文件的第 N 行添加断点<br />
watch *N        在地址 N 处添加写监视点<br />
watch id        在符号 ID 的地址添加写监视点<br />
info break      列出所有断点或监视点的状态</p></blockquote>
<p data-tool="mdnice编辑器">您可以使用符号 EntryPoint 代表 DLL 的入口点。</p>
<p data-tool="mdnice编辑器">在按符号名称设置断点或监视点时,如果找不到符号(例如,符号所在模块还没有加载),winedbg 将记住符号的名称,并在每次加载新模块时尝试设置该断点(直到成功)。</p>
<h2 data-tool="mdnice编辑器"><strong>栈帧操作</strong></h2>
<blockquote>
<p data-tool="mdnice编辑器">bt              打印当前线程的调用栈<br />
bt N            打印线程ID为 N 的线程的调用堆栈(注意:这不会更改当前帧的位置,因为它们由down和up命令操纵)<br />
up              当前线程栈中向上移动一帧<br />
up  N           当前线程栈中向上移动 N 帧<br />
down            当前线程栈中向下移动一帧<br />
down N          当前线程栈中向下移动 N 帧<br />
frame N         设置 N 为当前线程栈的当前帧<br />
info local      列出当前帧的局部变量信息</p>
</blockquote>
<h2 data-tool="mdnice编辑器"><strong>目录和源文件操作</strong></h2>
<blockquote><p>show dir        打印查找源文件的目录列表<br />
dir pathname    将 pathname 指定的目录添加到查找源文件的目录列表里面<br />
dir             清空查找源文件的目录列表<br />
list            列出当前位置开始的10行源码<br />
list -          列出当前位置往后的10行源码<br />
list N          列出当前文件中从 N 行开始的10行源码<br />
list path:N     列出 path 指定的文件的第N行开始的10行源码<br />
list id         列出函数 ID 的10行源码<br />
list *N         列出地址 N 开始的10行源码</p></blockquote>
<p data-tool="mdnice编辑器">您还可以使用逗号分隔来指定一段范围。例如:</p>
<blockquote>
<p data-tool="mdnice编辑器">list 123,234        列出当前文件的第 123 行到 234 行<br />
list foo.c:1,56     列出foo.c文件的第 1 行到 56 行</p>
</blockquote>
<h2 data-tool="mdnice编辑器"><strong>显示</strong></h2>
<p data-tool="mdnice编辑器">显示是在执行任何 winedbg 命令后计算并打印的表达式。</p>
<p data-tool="mdnice编辑器">Winedbg 将自动检测您输入的表达式是否包含局部变量。如果包含,则仅当上下文所在函数与设置显示表达式时所在的函数一样时,才会显示该局部变量的值。</p>
<blockquote>
<p data-tool="mdnice编辑器">info display        列出所有的活动显示<br />
display             查看所有活动显示的值(在每次调试器停止时都执行)<br />
display expr        添加表达式 expr 的显示<br />
display /fmt expr   添加给定格式打印 expr 的值的显示(有关格式的更多信息,请参阅下文的打印命令用法)<br />
undisplay N         删除显示编号为 N 的显示</p>
</blockquote>
<h2 data-tool="mdnice编辑器"><strong>反汇编</strong></h2>
<blockquote><p>disas               从当前位置反汇编<br />
disas expr          从 expr 指定的地址反汇编<br />
disas expr,expr     在两个 expr 指定的地址之间反汇编</p></blockquote>
<h2 data-tool="mdnice编辑器"><strong>内存(读取、写入、查看)</strong></h2>
<blockquote><p>x expr                          查看 expr 指定的地址处的内存<br />
x /fmt expr                     使用格式 fmt 查看 expr 指定的地址处的内存<br />
print expr                      打印 expr 的值(可能使用其类型)<br />
print /fmt expr                 使用格式 fmt 打印 expr 的值<br />
set lval=expr                   在 lval 中写入 expr 的值<br />
whatis expr                     打印表达式 expr 的 C 类型<br />
set !symbol_picker interactive  在打印值时,如果找到多个符号,询问用户要选取哪个符号(默认)<br />
set !symbol_picker scoped       在打印值时,局部符号优先于全局符号</p></blockquote>
<p data-tool="mdnice编辑器"><code>fmt</code> 是字母或个数加字母(个数和字母之间没有空格),其中字母可以是以下字符:</p>
<ul class="list-paddingleft-1">
<li>
<section>s             表示 ASCII 字符串</section>
</li>
<li>
<section>u             表示 Unicode UTF16 字符串</section>
</li>
<li>
<section>i              表示一个指令 (反汇编)</section>
</li>
<li>
<section>d             表示十进制显示 32位符号整数</section>
</li>
<li>
<section>x             表示十六进制显示 32位无符号整数</section>
</li>
<li>
<section>w            表示十六进制显示 16位无符号整数</section>
</li>
<li>
<section>b             表示十六进制显示 8位无符号整数</section>
</li>
<li>
<section>c             表示 ASCII 字符(仅打印可打印的 0x20-0x7f 之间的字符)</section>
</li>
<li>
<section>g             表示 GUID</section>
</li>
</ul>
<h2><strong>查看 Wine 内部信息</strong></h2>
<p>info class      列出在 Wine 中注册的所有 Windows 类<br />
info class id   打印 Windows 类 ID 上的信息<br />
info share      列出调试程序加载的所有模块信息(包括 .so 文件、NE 和 PE DLL)<br />
info share N    打印地址 N 对应的模块的信息<br />
info regs       打印 CPU 寄存器的值<br />
info all-regs   打印的CPU和浮点寄存器的值<br />
info stack      打印栈顶部96个字节<br />
info map        列出调试程序使用的所有虚拟映射<br />
info map N      列出 wpid 为 N 程序使用的所有虚拟映射<br />
info wnd        列出从桌面窗口开始的所有窗口层次结构<br />
info wnd N      打印句柄为 N 的窗口的信息<br />
info process    列出当前容器里面的所有 W-process 进程信息<br />
info thread     列出当前容器里面的所有 W-thread 线程信息<br />
info exception  列出异常帧(从当前栈帧开始)</p>
<h2 data-tool="mdnice编辑器"><strong>调试通道</strong></h2>
<p data-tool="mdnice编辑器">在进行调试时,可以使用 set 命令打开和关闭调试通道(仅适用于<code>WINEDEBUG</code> 环境变量中指定的调试通道)。有关调试通道的更多详细信息,请参阅 Wine 开发者指南 第 2 章。</p>
<blockquote>
<p data-tool="mdnice编辑器">set + warn channel      打开指定通道的 warn 类日志<br />
set + channel           打开指定通道的 warn/fixme/err/trace 类日志<br />
set - channel           关闭指定通道的 warn/fixme/err/trace 类日志<br />
set - fixme             关闭<code>fixme</code>类日志</p>
</blockquote>
<h2 data-tool="mdnice编辑器"><strong>bt 命令列出的调用堆栈说明</strong></h2>
<p data-tool="mdnice编辑器">一般情况下,bt 命令输出如下的信息:</p>
<blockquote>
<p data-tool="mdnice编辑器">Wine-dbg&gt;bt<br />
Backtrace:<br />
=&gt;0 0x7b83c640 UnhandledExceptionFilter(epointers=0x65f948) in kernel32 (0x0065f958)<br />
1 0x7bc7ef39 call_exception_handler+0x28() in ntdll (0x0065f988)<br />
2 0x7bc7ef0b EXC_CallHandler+0x1a() in ntdll (0x0065f9a8)<br />
3 0x7bc7f851 raise_exception+0x3a0(rec=0x65fd58, context=0x65fa8c, first_chance=&lt;is not available&gt;) in ntdll (0x0065fa18)<br />
4 0x7bc8172e NtRaiseException+0x2d(rec=&lt;couldn't compute location&gt;, context=&lt;couldn't compute location&gt;, first_chance=&lt;couldn't compute location&gt;) in ntdll (0x0065fa38)<br />
5 0x7bc81e3b raise_generic_exception+0x2a(rec=&lt;couldn't compute location&gt;, context=&lt;couldn't compute location&gt;) in ntdll (0x0065fa78)<br />
6 0xdeadbabe (0x0065fdd8)<br />
7 0x0040138b in a (+0x138a) (0x0065fe68)<br />
8 0x7b85f7ec call_process_entry+0xb() in kernel32 (0x0065fe88)<br />
9 0x7b860769 start_process+0x68(entry=&lt;couldn't compute location&gt;) in kernel32 (0x0065fec8)<br />
10 0x7bc7eebc call_thread_func_wrapper+0xb() in ntdll (0x0065fedc)<br />
11 0x7bc82069 call_thread_func+0xa8(entry=0x7b860700, arg=0x4014a0, frame=0x65ffec) in ntdll (0x0065ffcc)<br />
12 0x7bc7ee9a call_thread_entry_point+0x11() in ntdll (0x0065ffec)</p>
</blockquote>
<p data-tool="mdnice编辑器">其中</p>
<ul class="list-paddingleft-1">
<li>
<section>第1列的数字是函数调用栈的帧号,比如上面的输出有13层调用,当前栈帧号是0,也就是调用栈的最底层的函数。这个编号的用途是用来切换栈帧的,执行命令<code>frame N</code> N是要查看的栈帧编号就可以切换到指定的栈帧。</section>
</li>
<li>
<section>第2列是每层调用栈帧的上一层函数返回地址的16进制表示,,比如上面的输出里面编号是12的栈帧的第2列数字是<code>0x7bc7ee9a</code> 就是表示在函数<code>call_thread_entry_point</code>内部调用函数<code>call_thread_func</code>之后的返回地址。</section>
</li>
<li>
<section>第3列是第2列函数返回地址的符号名称,目的是增加可读性,有2种情况:</section>
<ul class="list-paddingleft-1">
<li>
<section>函数名称+偏移地址来表示,比如上面的栈帧12的<code>call_thread_entry_point+0x11</code>;</section>
</li>
<li>
<section>如果 winedbg 就找不到地址对应的函数名称,就用所在函数所在的模块名加一个偏移地址表示, 比如上面的栈帧7的<code>in a (+0x138a)</code> 表示函数调用地址是在 a 模块的首地址+0x138a字节处。</section>
</li>
</ul>
</li>
<li>
<section>第4列是函数参数,如果没有调试符号或者函数本身没有参数,就不显示;对于显示参数的,参数值可能读取不到的会以<code>&lt;couldn't compute location&gt;</code>和<code>&lt;is not available&gt;</code>来表示。</section>
</li>
<li>
<section>第5列是对应的源码,如果找不到源码,就不显示。</section>
</li>
<li>
<section><code>in</code> 后面的单词名称是所在的模块,比如栈帧12所在模块是 ntdll.dll 。</section>
</li>
<li>
<section>最后一列括号括起来的数字是当前栈帧的 ESP 寄存器值,即局部变量的起始内存区域。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">从上面这个堆栈来看,我们可以得到如下信息:</p>
<ul class="list-paddingleft-1">
<li>
<section>程序是在执行到 0xdeadbabe 触发了异常,异常信息保存在 raise_exception的 第1个参数 rec 结构体里面。</section>
</li>
<li>
<section>调用 0xdeadbabe 的函数返回地址是<code>0x0040138b</code>。该异常没有对应的处理函数,最终交由 UnhandledExceptionFilter 函数处理。</section>
</li>
</ul>
<p>&nbsp;</p>
<p>&gt; 支持 deepin(深度)社区</p>
                        </div>
頁: [1]
查看完整版本: Wine 开发系列 —— 如何调试 Wine