引言
本文主要以 wine 官网的这篇文章Debugging Wine来讲解。大部分内容是对该文的翻译,修正了原文的一些书写错误,删除了原文跟最新的 Wine 不适应的内容。
一、介绍
常用调试方法
Wine 为调试问题提供了多种方法。大多数 wine 开发人员更喜欢使用Wine的调试通道收集日志来解决问题。您可以在开发人员调试日志使用指南中了解如何使用调试通道来记录日志的更多内容。
本文的剩余部分详细介绍了 Wine 的内部调试器 winedbg 的使用。
在底层操作系统和 Windows 中的进程和线程
在深入讲解 Wine 的调试之前,下面是 Wine 中对进程和线程处理的小概述。必须清楚的是,我们有两种不同的模型:从 Unix 角度看到的进程/线程,从 Windows 角度看到的进程/线程。
每个 Windows 线程都用一个 Unix 线程来实现,这意味着同一个 Windows 进程的所有线程共享相同的 Unix 进程地址空间。以下中:
- W-process 表示 Windows 中的进程
- U-process 表示 Unix 中的进程
- W-thread 表示 Windows 中的线程
一个W-process 由一个或多个W-thread 组成。每个W-thread 映射到一个且只有一个U-process。同一个W-process 的所有U-process 共享相同的地址空间。
所以每个 Unix 进程都可以用两个值来标识:
- Unix 进程 ID( 简称upid)
- Windows 线程 ID(简称tID)
每个 Windows 进程还具有 Windows 进程 ID(简称wpid)。必须清楚,upid 和wpid 是不同的,不能相互替代。wpid 和tid 是 Windows 系统层面定义的,它们不能与进程或线程句柄混淆,因为任何句柄都指向系统对象(在本例中为进程或线程)。同一个进程可以对同一个内核对象有多个不同的句柄。句柄可以定义为局部(值仅在同一个进程中有效),也可以定义为系统范围的(任何W-process 都可以使用相同的句柄)。
Wine、调试和 Winedbg
在 Wine 中谈到调试时,至少需要考虑两个层次:
Windows 调试 API。
- Wine 集成调试器,被称为winedbg。
- Wine 实现了大多数 Windows 调试 API。调试 API 的第一部分在 KERNEL32.DLL 中实现,允许称为调试器的W-process 控制另一个被调试的W-process 的执行。控制意味着停止/恢复执行、启用/禁用单步、设置断点、读写内存… 等等。调试 API 的另一部分在 DBGHELP.DLL (依赖IMGHLP.DLL) 中实现,允许调试器查看任何模块中的符号和符号类型(如果模块已使用调试选项编译)。
- winedbg 就是一个使用这些 API 的 Winelib 应用程序,允许调试任何 Wine 或 Winelib 应用程序以及 Wine 本身。
调试教程
这些教程针对的是了解 C 语言编程,但刚刚开始参与开发 wine 的人。它们旨在向您演示在应用程序不工作时怎样调试问题。
- 调试 Reason 3 - 一个简单的"未处理异常"错误消息。介绍了调试跟踪、shell32.dll 和 SEH/异常跟踪。
- 调试 PE Explorer - 修复文件打开对话框中的简单挂起(另一个 shell32 错误)。介绍了 winedbg 的堆栈回溯用法和不同类型的错误码。
- 调试 Wild Metal Country - 查找游戏崩溃的原因以及如何确认错误。修复通用控件中的快捷键设置控件的一个小问题
- riched20 中跟踪速度问题
Anastasius Focht 提交的 bug 报告里面写了他如何发现问题的优秀描述;查看他的 bug 报告
二、winedbg 启动方法
启动一个进程
任何程序(原生的 Windows 程序或链接 Winelib 的程序)都可以用 winedbg 来运行,命令行选项跟wine 一样的:
- winedbg telnet.exe
- winedbg hl.exe -windowed
附加一个进程
winedbg 也可以不加任何命令行参数来启动: 此时 winedbg 以没有附加任何进程方式启动。您可以使用info proc 命令获取正在运行的W-process (及其wpid)的列表,然后使用attach 命令跟一个要调试的W-process的wpid 参数。这功能允许您调试已经启动的应用程序。
在发生异常的时候
- 当出现问题时,Windows 会将它作为异常进行跟踪。比如有分段违例、堆栈溢出、除零等异常。
- 发生异常时,Wine 会检查W-process 是否被调试。如果是,异常事件将发送到调试器,调试器负责是否传递该异常。此机制是标准 Windows 调试 API 的一部分。
- 如果W-process 没有被调试,Wine 会尝试启动调试器。此调试器(通常是 winedbg,请参阅下一节的配置以了解更多详细信息),在启动时附加到生成异常事件的W-process 。在这种情况下,您可以查看异常的原因,并修复原因(和继续执行)或深入挖掘以了解出错的原因。
- 如果 winedbg 是标准调试器,则pass 和cont 命令是让进程进一步处理异常事件的两种方法。
- 更精确地说: 当发生故障时(分段违例、堆栈溢出…),该事件首先发送到调试器(这称为第一次异常处理机会)。调试器可以给出两个答案:
- continue 调试器能够修复这个异常,并且能够让程序继续执行。
- pass 调试器在第一次异常处理机会时不能修复这个异常。Wine 将尝试遍历异常处理程序列表,查看其中一个处理程序是否可以处理该异常。如果未找到异常处理程序,则再次将这个异常发送到调试器,以指示异常处理失败。
- 注意:由于某些 Wine 代码使用异常和 try/catch 块来实现某些功能,因此在这种情况下winedbg 收到 segv 异常而停下来。例如,使用 IsBadReadPtr 函数时会发生这种情况。在这种情况下,应使用pass 命令,以便由 IsBadReadPtr 中的 catch 块处理异常。
中断
您可以在 winedbg 窗口同时按下 Ctrl+C 来停止正在运行的被调试程序,并允许您在 winedbg 里面操作被调试程序的进程上下文。
退出
Wine 支持新的 XP API,允许调试器从被调试程序上分离(见下文的detach 命令)。
三、使用 Wine 调试器
这一节介绍从何处开始调试 wine。如果您在任何时候卡住了并且需要帮助,请阅读Wine 用户指南之如何报告bug一节
崩溃
崩溃时候我们通常看到类似这样的对话框:
Unhandled exception: page fault on write access to 0x00000000 in 32-bit code (0x0043369e).
Register dump:
CS:0023 SS:002b DS:002b ES:002b FS:0063 GS:006b
EIP:0043369e ESP:0b3ee90c EBP:0b3ee938 EFLAGS:00010246( R- -- I Z- -P- )
EAX:00000072 EBX:7b8acff4 ECX:00000000 EDX:6f727265
ESI:7ba3b37c EDI:7ffa0000
Stack dump:
0x0b3ee90c: 7b82ced8 00000000 7ba3b348 7b884401
0x0b3ee91c: 7b883cdc 00000008 00000000 7bc36e7b
0x0b3ee92c: 7b8acff4 7b82ceb9 7b8acff4 0b3eea18
0x0b3ee93c: 7b82ce82 00000000 00000000 00000000
0x0b3ee94c: 00000000 0b3ee968 70d7ed7b 70c50000
0x0b3ee95c: 00000000 0b3eea40 7b87fd40 7b82d0d0
Backtrace:
=>0 0x0043369e in elementclient (+0x3369e) (0x0b3ee938)
1 0x7b82ce82 CONSOLE_SendEventThread+0xe1(pmt=0x0(nil)) [/usr/src/debug/wine-1.5.14/dlls/kernel32/console.c:1989] in kernel32 (0x0b3eea18)
2 0x7bc76320 call_thread_func_wrapper+0xb() in ntdll (0x0b3eea28)
3 0x7bc7916e call_thread_func+0x7d(entry=0x7b82cda0, arg=0x0(nil), frame=0xb3eeb18) [/usr/src/debug/wine-1.5.14/dlls/ntdll/signal_i386.c:2522] in ntdll (0x0b3eeaf8)
4 0x7bc762fe RtlRaiseException+0x21() in ntdll (0x0b3eeb18)
5 0x7bc7f3da start_thread+0xe9(info=0x7ffa0fb8) [/usr/src/debug/wine-1.5.14/dlls/ntdll/thread.c:408] in ntdll (0x0b3ef368)
6 0xf7597adf start_thread+0xce() in libpthread.so.0 (0x0b3ef468)
0x0043369e: movl %edx,0x0(%ecx)
Modules:
Module Address Debug info Name (143 modules)
PE 340000- 3af000 Deferred speedtreert
PE 3b0000- 3d6000 Deferred ftdriver
PE 3e0000- 3e6000 Deferred immwrapper
PE 400000- b87000 Export elementclient
PE b90000- e04000 Deferred elementskill
PE e10000- e42000 Deferred ifc22
PE 10000000-10016000 Deferred zlibwapi
ELF 41f75000-41f7e000 Deferred librt.so.1
ELF 41ff9000-42012000 Deferred libresolv.so.2
PE 48080000-480a8000 Deferred msls31
PE 65340000-653d2000 Deferred oleaut32
PE 70200000-70294000 Deferred wininet
PE 702b0000-70328000 Deferred urlmon
PE 70440000-704cf000 Deferred mlang
PE 70bd0000-70c34000 Deferred shlwapi
PE 70c50000-70ef3000 Deferred mshtml
PE 71930000-719b8000 Deferred shdoclc
PE 78130000-781cb000 Deferred msvcr80
ELF 79afb000-7b800000 Deferred libnvidia-glcore.so.304.51
ELF 7b800000-7ba3d000 Dwarf kernel32<elf>
\-PE 7b810000-7ba3d000 \ kernel32
ELF 7bc00000-7bcd5000 Dwarf ntdll<elf>
\-PE 7bc10000-7bcd5000 \ ntdll
ELF 7bf00000-7bf04000 Deferred <wine-loader>
ELF 7c288000-7c400000 Deferred libvorbisenc.so.2
PE 7c420000-7c4a7000 Deferred msvcp80
ELF 7c56d000-7c5b6000 Deferred dinput<elf>
\-PE 7c570000-7c5b6000 \ dinput
ELF 7c5b6000-7c600000 Deferred libdbus-1.so.3
ELF 7c70e000-7c715000 Deferred libasyncns.so.0
ELF 7c715000-7c77e000 Deferred libsndfile.so.1
ELF 7c77e000-7c7e5000 Deferred libpulsecommon-1.1.so
ELF 7c7e5000-7c890000 Deferred krnl386.exe16.so
PE 7c7f0000-7c890000 Deferred krnl386.exe16
ELF 7c890000-7c900000 Deferred ieframe<elf>
\-PE 7c8a0000-7c900000 \ ieframe
ELF 7ca00000-7ca1a000 Deferred rasapi32<elf>
\-PE 7ca10000-7ca1a000 \ rasapi32
ELF 7ca1a000-7ca21000 Deferred libnss_dns.so.2
ELF