本文主要以 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/異常跟蹤。 https://wiki.winehq.org/Debugging_Reason_3
調試 PE Explorer - 修復文件打開對話框中的簡單掛起(另一個shell32 錯誤)。介紹了 winedbg 的堆棧回溯用法和不同類型的錯誤碼。 https://wiki.winehq.org/Debugging_PE_Explorer
調試 Wild Metal Country - 查找游戲崩潰的原因以及如何確認錯誤。 https://wiki.winehq.org/Debugging_Wild_Metal_Country
Anastasius Focht 提交的 bug 報告里詳細描述了他如何發現問題,以下網址可查看他的 bug 報告: http://bugs.winehq.org/buglist.cgi?query_format=advanced&emailreporter1=1&emaillongdesc1=1&email1=focht&emailtype1=substring
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-PE 7b810000-7ba3d000 kernel32 ELF 7bc00000-7bcd5000 Dwarf ntdll -PE 7bc10000-7bcd5000 ntdll ELF 7bf00000-7bf04000 Deferred ELF 7c288000-7c400000 Deferred libvorbisenc.so.2 PE 7c420000-7c4a7000 Deferred msvcp80 ELF 7c56d000-7c5b6000 Deferred dinput -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 -PE 7c8a0000-7c900000 ieframe ELF 7ca00000-7ca1a000 Deferred rasapi32 -PE 7ca10000-7ca1a000 rasapi32 ELF 7ca1a000-7ca21000 Deferred libnss_dns.so.2 ELF 7ca21000-7ca25000 Deferred libnss_mdns4_minimal.so.2 ELF 7ca25000-7ca2d000 Deferred libogg.so.0 ELF 7ca2d000-7ca5a000 Deferred libvorbis.so.0 ELF 7cd5d000-7cd9c000 Deferred libflac.so.8 ELF 7cd9c000-7cdea000 Deferred libpulse.so.0 ELF 7cdfe000-7ce23000 Deferred iphlpapi -PE 7ce00000-7ce23000 iphlpapi ELF 7cff1000-7cffd000 Deferred libnss_nis.so.2 ELF 7d60d000-7d629000 Deferred wsock32 -PE 7d610000-7d629000 wsock32 ELF 7d80d000-7d828000 Deferred libnsl.so.1 ELF 7d8cf000-7d8db000 Deferred libgsm.so.1 ELF 7d8db000-7d903000 Deferred winepulse -PE 7d8e0000-7d903000 winepulse ELF 7d95c000-7d966000 Deferred libwrap.so.0 ELF 7d966000-7d96d000 Deferred libxtst.so.6 ELF 7d96d000-7d992000 Deferred mmdevapi -PE 7d970000-7d992000 mmdevapi ELF 7d9b3000-7d9d0000 Deferred msimtf -PE 7d9c0000-7d9d0000 msimtf ELF 7d9d0000-7d9e5000 Deferred comm.drv16.so PE 7d9e0000-7d9e5000 Deferred comm.drv16 ELF 7da83000-7db5f000 Deferred libgl.so.1 ELF 7db60000-7db63000 Deferred libx11-xcb.so.1 ELF 7db63000-7db78000 Deferred system.drv16.so PE 7db70000-7db78000 Deferred system.drv16 ELF 7db98000-7dca1000 Deferred opengl32 -PE 7dbb0000-7dca1000 opengl32 ELF 7dca1000-7dcb6000 Deferred vdmdbg -PE 7dcb0000-7dcb6000 vdmdbg ELF 7dcce000-7dd04000 Deferred uxtheme -PE 7dcd0000-7dd04000 uxtheme ELF 7dd04000-7dd0a000 Deferred libxfixes.so.3 ELF 7dd0a000-7dd15000 Deferred libxcursor.so.1 ELF 7dd16000-7dd1f000 Deferred libjson.so.0 ELF 7dd24000-7dd38000 Deferred psapi -PE 7dd30000-7dd38000 psapi ELF 7dd78000-7dda1000 Deferred libexpat.so.1 ELF 7dda1000-7ddd6000 Deferred libfontconfig.so.1 ELF 7ddd6000-7dde6000 Deferred libxi.so.6 ELF 7dde6000-7ddef000 Deferred libxrandr.so.2 ELF 7ddef000-7de11000 Deferred libxcb.so.1 ELF 7de11000-7df49000 Deferred libx11.so.6 ELF 7df49000-7df5b000 Deferred libxext.so.6 ELF 7df5b000-7df75000 Deferred libice.so.6 ELF 7df75000-7e005000 Deferred winex11 -PE 7df80000-7e005000 winex11 ELF 7e005000-7e0a5000 Deferred libfreetype.so.6 ELF 7e0a5000-7e0c5000 Deferred libtinfo.so.5 ELF 7e0c5000-7e0ea000 Deferred libncurses.so.5 ELF 7e123000-7e1eb000 Deferred crypt32 -PE 7e130000-7e1eb000 crypt32 ELF 7e1eb000-7e235000 Deferred dsound -PE 7e1f0000-7e235000 dsound ELF 7e235000-7e2a7000 Deferred ddraw -PE 7e240000-7e2a7000 ddraw ELF 7e2a7000-7e3e3000 Deferred wined3d -PE 7e2b0000-7e3e3000 wined3d ELF 7e3e3000-7e417000 Deferred d3d8 -PE 7e3f0000-7e417000 d3d8 ELF 7e417000-7e43b000 Deferred imm32 -PE 7e420000-7e43b000 imm32 ELF 7e43b000-7e46f000 Deferred ws2_32 -PE 7e440000-7e46f000 ws2_32 ELF 7e46f000-7e49a000 Deferred msacm32 -PE 7e470000-7e49a000 msacm32 ELF 7e49a000-7e519000 Deferred rpcrt4 -PE 7e4b0000-7e519000 rpcrt4 ELF 7e519000-7e644000 Deferred ole32 -PE 7e530000-7e644000 ole32 ELF 7e644000-7e6f7000 Deferred winmm -PE 7e650000-7e6f7000 winmm ELF 7e6f7000-7e7fa000 Deferred comctl32 -PE 7e700000-7e7fa000 comctl32 ELF 7e7fa000-7ea23000 Deferred shell32 -PE 7e810000-7ea23000 shell32 ELF 7ea23000-7eaf9000 Deferred gdi32 -PE 7ea30000-7eaf9000 gdi32 ELF 7eafb000-7eaff000 Deferred libnvidia-tls.so.304.51 ELF 7eaff000-7eb09000 Deferred libxrender.so.1 ELF 7eb09000-7eb0f000 Deferred libxxf86vm.so.1 ELF 7eb0f000-7eb18000 Deferred libsm.so.6 ELF 7eb18000-7eb32000 Deferred version -PE 7eb20000-7eb32000 version ELF 7eb32000-7ec87000 Deferred user32 -PE 7eb40000-7ec87000 user32 ELF 7ec87000-7ecf1000 Deferred advapi32 -PE 7ec90000-7ecf1000 advapi32 ELF 7ecf1000-7ed8f000 Deferred msvcrt -PE 7ed00000-7ed8f000 msvcrt ELF 7ef8f000-7ef9c000 Deferred libnss_files.so.2 ELF 7ef9c000-7efc7000 Deferred libm.so.6 ELF 7efc8000-7efe5000 Deferred libgcc_s.so.1 ELF 7efe5000-7f000000 Deferred crtdll -PE 7eff0000-7f000000 crtdll ELF f73d0000-f73d4000 Deferred libxinerama.so.1 ELF f73d4000-f73d8000 Deferred libxau.so.6 ELF f73da000-f73df000 Deferred libdl.so.2 ELF f73df000-f7591000 Dwarf libc.so.6 ELF f7591000-f75ab000 Dwarf libpthread.so.0 ELF f75ab000-f76ef000 Dwarf libwine.so.1 ELF f7722000-f7728000 Deferred libuuid.so.1 ELF f7729000-f774a000 Deferred ld-linux.so.2 ELF f774a000-f774b000 Deferred [vdso].so Threads: process tid prio (all id:s are in hex) 00000008 (D) C:Perfect World EntertainmentPerfect World Internationalelementelementclient.exe 00000031 0 <== ? ?00000035 ? 15 ? ?00000012 ? ?0 ? ?00000021 ? ?0 ? ?00000045 ? ?0 ? ?00000044 ? ?0 ? ?00000043 ? ?0 ? ?00000038 ? 15 ? ?00000037 ? ?0 ? ?00000036 ? 15 ? ?00000034 ? ?0 ? ?00000033 ? ?0 ? ?00000032 ? ?0 ? ?00000027 ? ?0 ? ?00000009 ? ?0 0000000e services.exe ? ?0000000b ? ?0 ? ?00000020 ? ?0 ? ?00000017 ? ?0 ? ?00000010 ? ?0 ? ?0000000f ? ?0 00000014 winedevice.exe ? ?0000001e ? ?0 ? ?0000001b ? ?0 ? ?00000016 ? ?0 ? ?00000015 ? ?0 0000001c plugplay.exe ? ?00000022 ? ?0 ? ?0000001f ? ?0 ? ?0000001d ? ?0 00000023 explorer.exe ? ?00000024 ? ?0
調試崩潰的步驟。您可能在任何步驟中崩潰,但請報告 bug,并在 bug 報告中提供收集到的盡可能多的信息。
了解崩潰的原因。通常是頁面錯誤、調用了Wine 中未實現的函數,或類似的原因。報告崩潰時,報告整個崩潰轉儲,即使它對您沒有意義。(在這個例子里面,在寫入 0x0000000 時出現頁面錯誤。最有可能的是 Wine 將 NULL 傳遞給應用程序或類似問題。)
確定崩潰的原因。由于通常是 Wine 實現的函數執行失敗或者行為不正確導致的主要/次要反應,因此使用WINEDEBUG=+relay 環境變量重新運行 Wine。這將生成相當多的日志輸出,但通常原因是位于最后一個函數調用中。這些日志通常如下所示:
如果你已經發現了一個行為不正常的 Wine 函數,嘗試找出它行為不正常的原因。在源代碼中查找函數。試著理解傳遞的函數參數。通常有一個WINE_DEFAULT_DEBUG_CHANNEL(channel); 在源文件的開頭。使用WINEDEBUG=+xyz,+relay 環境變量重新運行 Wine。有時,在源文件的開頭以WINE_DECLARE_DEBUG_CHANNEL(channel)的形式定義了其他調試通道;如果是這樣,有問題的函數也可能使用了這些備用通道之一。在該函數中搜索TRACE_(channel)(".../n"); 并將找到的這些額外的通道添加到 WINEDEBUG 環境變量里面。
有關如何使用 winedbg 進行調試的其他信息,請參閱源碼programs/winedbg/README。
如果這些信息不夠清晰,或者您想知道該函數發生的更多信息,請嘗試使用WINEDEBUG=+all重新運行 Wine ,這將轉儲 Wine 里面包含調試信息在內的所有日志。通常需要限制生成的調試輸出。這可以通過管道把輸出日志發給grep 過濾,或者使用注冊表項來完成。有關詳細信息,請參閱下文的配置 +relay 行為 一節。
如果這還不夠,可以在您認為相關的函數中手動添加更多調試日志。有關詳細信息,請參閱開發人員調試日志使用指南。您也可以嘗試在 gdb 中運行該程序,代替使用 Wine 調試器。如果這樣做,請在~/.gdbinit 文件里面增加這句handle SIGSEGV nostop noprint 來禁用 gdb 對seg fault 錯誤的處理(Win16 需要)。
您還可以為該函數設置斷點。用 winedbg 啟動調試程序而不是 Wine。一旦調試器運行起來,在命令行提示符輸入命令:break RegOpenKeyExW (將 RegOpenKeyExW 替換成您要調試的函數,區分大小寫)以設置斷點。然后,使用continue 命令啟動程序正常執行。程序運行到斷點位置,程序將停止;如果程序還沒有運行到該函數崩潰的那次調用,再次使用continue 命令繼續運行程序直到達到該函數即將崩潰的那次調用。現在,您可以用單步執行命令來繼續運行程序,直到達到崩潰點,然后使用其他調試器命令來查看寄存器值和相關變量值等等。
程序掛起,沒有反應
用 winedbg 啟動程序而不是 Wine 。當程序沒有反應的時候,切換到 winedbg 窗口,并按 Ctrl+C 。這將停止程序,并允許您調試該程序,就像崩潰時候一樣。
程序彈出錯誤消息框
有時候程序會使用或多或少的非描述性消息框報告失敗。我們可以使用與崩潰相同的方法進行調試,但有一個問題,為了設置消息框,程序會多出大量的調試日志。
由于故障通常發生在設置消息框之前,您可以啟動 winedbg 并在 MessageBoxA (由 Win16 和 Win32 程序調用)處設置斷點,然后繼續運行。程序將在設置消息框之前停止。
您也可以使用這個命令來運行程序:WINEDEBUG=+relay wine program.exe 2>&1 | less -i然后在 less 里面搜索 MessageBox 。
反匯編程序
您也可以嘗試反匯編有問題的程序,以檢查沒有公開的功能或使用它們。
理解匯編代碼主要是一個練習問題。Win16 函數入口通常如下所示:
push bp mov bp, sp ... 函數代碼 .. retf XXXX <--------- XXXX 是函數參數的總字節數
這是一個沒有局部變量的 FAR 函數。參數通常從[bp+6] 開始,偏移量增加。請注意,對于使用 PASCAL 調用約定導出的 Win16 函數,[bp+6] 屬于最右側的參數。因此,如果我們使用帶a 和b 的strcmp(a,b) 來說,則參數b 的存儲位置在[bp+6],參數a 的存儲位置在[bp+10] 。
大多數函數用棧存儲局部變量:
enter 0086, 00 ... 函數代碼 ... leave retf XXXX
這與上述內容基本相同,但還添加了 0x86 字節的棧存儲,使用[bp-xx] 進行訪問。在調用該函數之前,使用如下所示的代碼把參數壓到棧上:
push word ptr [bp-02] <- 壓到 [bp+8] 處 push di ? ? ? ? ? ? ? ? <- 壓到 [bp+6] 處 call KERNEL.LSTRLEN
在這里,首先壓人選擇器地址,然后壓入傳遞的字符串的偏移量。
調試示例
讓我們調試臭名昭著的 WORD SHARE.EXE 消息框:
|marcus@jet $ wine winword.exe | +---------------------------------------------+ | | ! You must leave Windows and load SHARE.EXE| | | before starting Word. | | +---------------------------------------------+
|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 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 ?KERNEL.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.
哎呀,日志中發現了問題 (OPENFILE 失敗):
|Ret KERNEL.97: GETTEMPFILENAME() retval=0x9281 ret=060f:09b1 ds=0927 ^^^^^^ Temporary storage ID |Call KERNEL.74: OPENFILE(0x09278248"C:~doc9281.tmp",0927:82da,0x1012) ret=060f:09d8 ds=0927 ^^^^^^^^^^^^^^^^ ^^^^^^^^^ ^^^^^^^ |filename |OFSTRUCT |open mode: OF_CREATE|OF_SHARE_EXCLUSIVE|OF_READWRITE
這里失敗的原因是我的 C 盤是只讀的:
|Ret KERNEL.74: OPENFILE() retval=0xffff ret=060f:09d8 ds=0927 ^^^^^^ HFILE_ERROR16, yes, it failed. |Call USER.1: MESSAGEBOX(0x0000,0x09278376"You must close Windows and load SHARE.EXE before you start Word.",0x00000000,0x1030) ret=060f:084f ds=0927
并且在 MessageBoxA 的入口停下來了:
|Stopped on breakpoint 2 at 0x40189100 (MessageBoxA [msgbox.c:190]) |190 {
代碼看起來要找一個可寫磁盤,并試圖在該磁盤創建一個文件。要解決此 Bug,可以將 C 盤定義為網絡驅動器,上述代碼將忽略該驅動器。
調試技巧
以下是一些其他調試技巧:
1. 如果您有一個程序在加載前期崩潰,以至于您無法正常使用 Wine 調試器來調試,但 Wine 已執行該程序的啟動代碼,則可以使用特殊的技巧。您應該執行WINEDEBUG=+relay wine program獲取程序在啟動函數中調用的所有函數清單。然后執行:winedbg winfile.exe這樣,您就進入 winedbg。現在,您可以在 start 函數中調用的任何函數上設置斷點,然后不斷按 c 以跳過 Winfile 對此函數的正常調用,直到您最終到達此函數調用崩潰的位置。您就可以像平常一樣繼續調試該程序。 2. 如果嘗試運行程序,程序在彈出錯誤消息框后就退出,則問題的原因通常可以檢查在 MessageBox 之前調用的一些函數的返回值發現。您應該用下面的方式重新運行程序:WINEDEBUG=+relay wine program_name &>relmsg接著執行more relmsg 然后搜索最后一個出現的MESSAGEBOX,類似這樣的:Call USER.1: MESSAGEBOX(0x0000,0x01ff1246 "Runtime error 219 at 0004:1056.",0x00000000,0x1010) ret=01f7:2160 ds=01ff在我的例子里面,在調用MessageBox 函數之前的代碼類似這樣:
Call KERNEL.96: FREELIBRARY(0x0347) ret=01cf:1033 ds=01ff CallTo16(func=033f:0072,ds=01ff,0x0000) Ret KERNEL.96: FREELIBRARY() retval=0x0001 ret=01cf:1033 ds=01ff Call KERNEL.96: FREELIBRARY(0x036f) ret=01cf:1043 ds=01ff CallTo16(func=0367:0072,ds=01ff,0x0000) Ret KERNEL.96: FREELIBRARY() retval=0x0001 ret=01cf:1043 ds=01ff Call KERNEL.96: FREELIBRARY(0x031f) ret=01cf:105c ds=01ff CallTo16(func=0317:0072,ds=01ff,0x0000) Ret KERNEL.96: FREELIBRARY() retval=0x0001 ret=01cf:105c ds=01ff Call USER.171: WINHELP(0x02ac,0x01ff05b4 "COMET.HLP",0x0002,0x00000000) ret=01cf:1070 ds=01ff CallTo16(func=0117:0080,ds=01ff) Call WPROCS.24: TASK_RESCHEDULE() ret=00a7:0a2d ds=002b Ret WPROCS.24: TASK_RESCHEDULE() retval=0x0000 ret=00a7:0a2d ds=002b Ret USER.171: WINHELP() retval=0x0001 ret=01cf:1070 ds=01ff Call KERNEL.96: FREELIBRARY(0x01be) ret=01df:3e29 ds=01ff Ret KERNEL.96: FREELIBRARY() retval=0x0000 ret=01df:3e29 ds=01ff Call KERNEL.52: FREEPROCINSTANCE(0x02cf00ba) ret=01f7:1460 ds=01ff Ret KERNEL.52: FREEPROCINSTANCE() retval=0x0001 ret=01f7:1460 ds=01ff Call USER.1: MESSAGEBOX(0x0000,0x01ff1246 "Runtime error 219 at 0004:1056.",0x00000000,0x1010) ret=01f7:2160 ds=01ff
我認為本示例中對 MessageBox 的調用不是由以前調用的函數返回錯誤值引起的(經常發生這樣的情況),而是消息框里面提到的:0x0004:0x1056 處出現運行時錯誤。由于地址的段值僅為 4,因此我認為這只是一個內部值。但偏移地址揭示了一些非常有趣的內容,偏移 0x1056 非常接近 FREELIBRARY 的返回地址:
Call KERNEL.96: FREELIBRARY(0x031f) ret=01cf:105c ds=01ff ^^^^
如果段 0x0004 確實是段 0x1cf,我們可以反匯編調用 FreeLibrary 的地址,分析發生運行時錯誤之前的某些行。
3. 如果希望設置某個位置的斷點,但該斷點所在的模塊還沒有映射到內存里面,則可以將斷點設置為 GetVersion16/32 函數,因為這些函數被調用很頻繁,斷點停下來的時候執行continue 命令直到您能夠設置此斷點而不再顯示錯誤消息。
調試器的基本用法
使用winebg myprog.exe 啟動程序后,程序加載并在起點處停止,終端顯示 winedbg 命令行提示符。然后,您可以這樣設置斷點:
b RoutineName (按函數名稱加斷點)或 b *0x812575 (按地址加斷點)
然后,您輸入 c(continue命令簡寫)來運行程序。當它停在斷點處后,您可以鍵入:
step (一次步進一行)或 stepi (一次步進一個機器指令;它有助于了解386基本指令集) info reg (查看寄存器) info stack (查看堆棧中的十六進制值) info local (查看局部變量) list 行號 (列出源代碼) x 變量名稱 (檢查變量;僅當代碼關閉優化編譯時候有效) x 0x4269978 (檢查內存位置的內容) ? (幫助) q (退出)
直接按 Enter,您可以重復最后一個命令。
有用的程序
一些有用的程序:
IDA:IDA Pro 是強烈推薦的,但不是免費的。
pedump:http://pedump.me/,轉儲 PE 格式的 DLL 的導入和導出。
winedump:(包括在 Wine 中),轉儲 PE 格式的 DLL 的導入和導出。
配置
Windows 調試配置
Windows 調試 API 使用這個注冊表項來指明發生未處理異常時要調用哪個調試器。
[MACHINE\Software\Microsoft\Windows NT\CurrentVersion\AeDebug]
有兩個值來決定行為:
Debugger 指定用于啟動調試器的命令行(它使用兩個 printf 格式占位符 %ld將上下文相關信息傳遞給調試器)。您應該在這里放置一個您的調試器的完整路徑 ( winedbg 當然可以使用,但任何其他使用 Windows 調試 API 的調試器也可以 )。您選擇使用的調試器的路徑必須通過 Wine 容器根目錄的 dosdevices 子目錄里面配置的 DOS 驅動器之一進行訪問。
Auto 如果此值為零,在發生未處理異常時將彈出對話框詢問用戶是否希望啟動調試器。否則,調試器將自動啟動。
默認的 Wine 注冊表如下所示:
[MACHINE\Software\Microsoft\Windows NT\CurrentVersion\AeDebug] 957636538 "Auto"=dword:00000001 "Debugger"="winedbg %ld %ld"
注意 1: 創建這個注冊表項是必需的。如果不這樣做,在發生異常時不會觸發調試器。
注意 2: wineinstall(Wine 附帶的) 創建這個注冊表項。但是由于安裝的注冊表存在一些限制,如果存在以前的 Wine 安裝,則先刪除整個[MACHINE\Software\Microsoft\Windows NT\CurrentVersion\AeDebug] ,再運行 wineinstall 以重新創建才是安全的。
Winedbg 配置
Winedbg 可通過多種選項進行配置。這些選項按用戶存儲在注冊表中:[HKCU\Software\Wine\WineDbg]
這些選項可以在 winedbg 里面讀取/寫入,作為調試器表達式的一部分。要引用這些選項之一,其名稱必須以$ 符號為前綴。例如:
set $BreakAllThreadsStartup = 1
將BreakAllThreadsStartup 選項設置為 TRUE。
所有選項在 winedbg 啟動時從注冊表中讀取(如果未找到相應的值,則使用默認值),并在 winedbg 退出時寫回注冊表。
下面是所有選項的列表:
BreakAllThreadsStartup 如果為 TRUE 則在所有線程啟動時調試器停止;如果為 FALSE 僅在給定進程的第一個線程啟動時調試器停止。默認情況下為 FALSE。
BreakOnCritSectTimeOut 如果為 TRUE 則當臨界區超時(5 分鐘)時調試器停止;默認情況下為 TRUE。
BreakOnAttach 如果為 TRUE 則在未處理異常發生后 winedbg 附加到進程時,在第一個附加事件中停下來。由于附加事件在異常事件的上下文中沒有意義,因此該選項最好是 FALSE。
BreakOnFirstChance 異常生成兩個調試事件。第一個是在異常發生之后傳遞到調試器(稱為第一次機會)。調試器可以決定恢復執行(通過 winedbg cont 命令)或將異常傳遞給程序中的異常處理程序鏈(如果存在)(winedbg 通過 pass 命令)。如果異常處理程序沒有處理異常,則異常事件將再次發送到調試器(稱為最后一次機會)。調試器不能傳遞最后一次機會的異常。如果 BreakOnFirstChance 為 TRUE ,則第一次機會異常和最后一次機會異常發生時 winedbg 都停止;如果為 FALSE,僅在最后一次機會異常是停止。
配置 +relay 行為
將WINEDEBUG 設置為+relay 調試時,可能會得到大量輸出日志。您可以通過把注冊表中[HKCU\Software\Wine\Debug] 下的 RelayExclude 鍵值設置為用分號分隔的要排除的函數列表,例如:
"RtlEnterCriticalSection;RtlLeaveCriticalSection;kernel32.97;kernel32.98"
RelayInclude 和RelayExclude 類似,只不過列出的函數將是輸出中僅包含的函數。
如果應用程序使用+relay 運行速度太慢無法獲得有意義的輸出,并且對生成的幾 GB 的日志文件束手無策,不確定要排除哪些函數,下面是一個技巧。首先,運行應用程序一分鐘左右,將其輸出重定向到磁盤上的文件:
WINEDEBUG=+relay wine appname.exe &>relay.log
然后運行此命令以查看調用最多的函數:
awk -F'(' '{print $1}' < relay.log | awk '{print $2}' | sort | uniq -c | sort
在確保這些函數不相關后使用RelayExclude 排除調用最多的函數,然后再次運行應用程序。
Winedbg 表達式和變量
表達式
Winedbg 中的表達式大多以 C 形式編寫。但是有一些差異:
標識符的名稱中可以加一個!,這主要區分不同 DLL 的符號,如USER32!CreateWindowExA 表示 USER32.DLL 里面的 CreateWindowExA 函數。
在強制轉換操作中,在指定結構或聯合時,必須使用struct 或union 關鍵字(即使程序使用typedef)。
當按名稱指定標識符時,如果存在多個相同名稱的符號,調試器將提示用戶要選擇哪個符號,輸入你想要的那個符號前面的數字序號即可。
變量
Winedbg 定義自己的變量集。上面的配置變量是其中的一部分。其他包括:
$ThreadId 當前調試的W-thread 的 ID
$ProcessId 當前調試的W-process 的 ID
寄存器變量 所有 CPU 寄存器用"$"前綴加寄存器名來訪問。您可以使用info regs 來獲取 CPU 寄存器的名稱列表。
$ThreadId 和$ProcessId變量可以很方便地在指定的線程或進程上設置條件斷點。
Winedbg 命令參考
雜項
abort 中止調試器 quit 退出調試器 attach N 附加到 `W-process` 進程(N 是其 ID,10進制數字或十六進制 (0xN))。 ID 可以使用 `info process` 命令獲取。請注意,`info process` 命令返回的是十六進制值。 detach 從 `W-process` 進程分離。 help 打印一些幫助 help info 打印一些 info 命令的幫助
流程控制
cont,c 繼續運行直到下一個斷點或異常。 pass 將異常事件傳遞給異常處理鏈。 step,s 繼續執行,直到下一行"C"代碼(進入函數內部) next,n 繼續執行,直到下一行"C"代碼(不進入函數內部) stepi,si 執行下一個指令(進入函數內部) nexti,ni 執行下一個指令(不進入函數內部) finish,f 執行,直到當前函數返回
cont、step、next、stepi、nexti 命令后面可以加一個數字 (N) 參數,表示命令執行 N 次。
斷點、監視點
enable N 啟用編號為 N 的斷點或監視點 disable N 禁用編號為 N 的斷點或監視點 delete N 刪除編號為 N 的斷點或監視點 cond N 刪除編號為 N 的斷點或監視點的條件 cond N expr 設置編號為 N 的斷點或監視點的條件;每次斷點命中時,都會計算表達式 expr ,如果結果為零值,則不觸發斷點。 break *N 在地址 N 處添加斷點 break ID 在符號 ID 的地址添加斷點 break N 在當前源文件的第 N 行添加斷點 watch *N 在地址 N 處添加寫監視點 watch id 在符號 ID 的地址添加寫監視點 info break 列出所有斷點或監視點的狀態
您可以使用符號 EntryPoint 代表 DLL 的入口點。
在按符號名稱設置斷點或監視點時,如果找不到符號(例如,符號所在模塊還沒有加載),winedbg 將記住符號的名稱,并在每次加載新模塊時嘗試設置該斷點(直到成功)。
棧幀操作
bt 打印當前線程的調用棧 bt N 打印線程ID為 N 的線程的調用堆棧(注意:這不會更改當前幀的位置,因為它們由down和up命令操縱) up 當前線程棧中向上移動一幀 up N 當前線程棧中向上移動 N 幀 down 當前線程棧中向下移動一幀 down N 當前線程棧中向下移動 N 幀 frame N 設置 N 為當前線程棧的當前幀 info local 列出當前幀的局部變量信息
目錄和源文件操作
show dir 打印查找源文件的目錄列表 dir pathname 將 pathname 指定的目錄添加到查找源文件的目錄列表里面 dir 清空查找源文件的目錄列表 list 列出當前位置開始的10行源碼 list - 列出當前位置往后的10行源碼 list N 列出當前文件中從 N 行開始的10行源碼 list path:N 列出 path 指定的文件的第N行開始的10行源碼 list id 列出函數 ID 的10行源碼 list *N 列出地址 N 開始的10行源碼
您還可以使用逗號分隔來指定一段范圍。例如:
list 123,234 列出當前文件的第 123 行到 234 行 list foo.c:1,56 列出foo.c文件的第 1 行到 56 行
顯示
顯示是在執行任何 winedbg 命令后計算并打印的表達式。
Winedbg 將自動檢測您輸入的表達式是否包含局部變量。如果包含,則僅當上下文所在函數與設置顯示表達式時所在的函數一樣時,才會顯示該局部變量的值。
info display 列出所有的活動顯示 display 查看所有活動顯示的值(在每次調試器停止時都執行) display expr 添加表達式 expr 的顯示 display /fmt expr 添加給定格式打印 expr 的值的顯示(有關格式的更多信息,請參閱下文的打印命令用法) undisplay N 刪除顯示編號為 N 的顯示
反匯編
disas 從當前位置反匯編 disas expr 從 expr 指定的地址反匯編 disas expr,expr 在兩個 expr 指定的地址之間反匯編
內存(讀取、寫入、查看)
x expr 查看 expr 指定的地址處的內存 x /fmt expr 使用格式 fmt 查看 expr 指定的地址處的內存 print expr 打印 expr 的值(可能使用其類型) print /fmt expr 使用格式 fmt 打印 expr 的值 set lval=expr 在 lval 中寫入 expr 的值 whatis expr 打印表達式 expr 的 C 類型 set !symbol_picker interactive 在打印值時,如果找到多個符號,詢問用戶要選取哪個符號(默認) set !symbol_picker scoped 在打印值時,局部符號優先于全局符號
fmt 是字母或個數加字母(個數和字母之間沒有空格),其中字母可以是以下字符:
s 表示 ASCII 字符串
u 表示 Unicode UTF16 字符串
i 表示一個指令 (反匯編)
d 表示十進制顯示 32位符號整數
x 表示十六進制顯示 32位無符號整數
w 表示十六進制顯示 16位無符號整數
b 表示十六進制顯示 8位無符號整數
c 表示 ASCII 字符(僅打印可打印的 0x20-0x7f 之間的字符)
g 表示 GUID
查看 Wine 內部信息
info class 列出在 Wine 中注冊的所有 Windows 類 info class id 打印 Windows 類 ID 上的信息 info share 列出調試程序加載的所有模塊信息(包括 .so 文件、NE 和 PE DLL) info share N 打印地址 N 對應的模塊的信息 info regs 打印 CPU 寄存器的值 info all-regs 打印的CPU和浮點寄存器的值 info stack 打印棧頂部96個字節 info map 列出調試程序使用的所有虛擬映射 info map N 列出 wpid 為 N 程序使用的所有虛擬映射 info wnd 列出從桌面窗口開始的所有窗口層次結構 info wnd N 打印句柄為 N 的窗口的信息 info process 列出當前容器里面的所有 W-process 進程信息 info thread 列出當前容器里面的所有 W-thread 線程信息 info exception 列出異常幀(從當前棧幀開始)
調試通道
在進行調試時,可以使用 set 命令打開和關閉調試通道(僅適用于WINEDEBUG環境變量中指定的調試通道)。有關調試通道的更多詳細信息,請參閱 Wine 開發者指南 第 2 章。
set + warn channel 打開指定通道的 warn 類日志 set + channel 打開指定通道的 warn/fixme/err/trace 類日志 set - channel 關閉指定通道的 warn/fixme/err/trace 類日志 set - fixme 關閉`fixme`類日志
bt 命令列出的調用堆棧說明
一般情況下,bt 命令輸出如下的信息:
Wine-dbg>bt Backtrace: =>0 0x7b83c640 UnhandledExceptionFilter(epointers=0x65f948) [/home/deepin/deepin-wine/dlls/kernel32/except.c:426] in kernel32 (0x0065f958) 1 0x7bc7ef39 call_exception_handler+0x28() in ntdll (0x0065f988) 2 0x7bc7ef0b EXC_CallHandler+0x1a() in ntdll (0x0065f9a8) 3 0x7bc7f851 raise_exception+0x3a0(rec=0x65fd58, context=0x65fa8c, first_chance=) [/home/deepin/deepin-wine/dlls/ntdll/signal_i386.c:698] in ntdll (0x0065fa18) 4 0x7bc8172e NtRaiseException+0x2d(rec= , context= , first_chance= ) [/home/deepin/deepin-wine/dlls/ntdll/signal_i386.c:2840] in ntdll (0x0065fa38) 5 0x7bc81e3b raise_generic_exception+0x2a(rec= , context= ) [/home/deepin/deepin-wine/dlls/ntdll/signal_i386.c:2167] in ntdll (0x0065fa78) 6 0xdeadbabe (0x0065fdd8) 7 0x0040138b in a (+0x138a) (0x0065fe68) 8 0x7b85f7ec call_process_entry+0xb() in kernel32 (0x0065fe88) 9 0x7b860769 start_process+0x68(entry= ) [/home/deepin/deepin-wine/dlls/kernel32/process.c:1124] in kernel32 (0x0065fec8) 10 0x7bc7eebc call_thread_func_wrapper+0xb() in ntdll (0x0065fedc) 11 0x7bc82069 call_thread_func+0xa8(entry=0x7b860700, arg=0x4014a0, frame=0x65ffec) [/home/deepin/deepin-wine/dlls/ntdll/signal_i386.c:2962] in ntdll (0x0065ffcc) 12 0x7bc7ee9a call_thread_entry_point+0x11() in ntdll (0x0065ffec)
其中
第1列的數字是函數調用棧的幀號,比如上面的輸出有13層調用,當前棧幀號是0,也就是調用棧的最底層的函數。這個編號的用途是用來切換棧幀的,執行命令frame N N是要查看的棧幀編號就可以切換到指定的棧幀。
第2列是每層調用棧幀的上一層函數返回地址的16進制表示,,比如上面的輸出里面編號是12的棧幀的第2列數字是0x7bc7ee9a 就是表示在函數call_thread_entry_point內部調用函數call_thread_func之后的返回地址。
第3列是第2列函數返回地址的符號名稱,目的是增加可讀性,有2種情況:
函數名稱+偏移地址來表示,比如上面的棧幀12的call_thread_entry_point+0x11;
如果 winedbg 就找不到地址對應的函數名稱,就用所在函數所在的模塊名加一個偏移地址表示, 比如上面的棧幀7的in a (+0x138a) 表示函數調用地址是在 a 模塊的首地址+0x138a字節處。
第4列是函數參數,如果沒有調試符號或者函數本身沒有參數,就不顯示;對于顯示參數的,參數值可能讀取不到的會以
第5列是對應的源碼,如果找不到源碼,就不顯示。
in 后面的單詞名稱是所在的模塊,比如棧幀12所在模塊是 ntdll.dll 。
最后一列括號括起來的數字是當前棧幀的 ESP 寄存器值,即局部變量的起始內存區域。
從上面這個堆棧來看,我們可以得到如下信息:
程序是在執行到 0xdeadbabe 觸發了異常,異常信息保存在 raise_exception的 第1個參數 rec 結構體里面。
調用 0xdeadbabe 的函數返回地址是0x0040138b。該異常沒有對應的處理函數,最終交由 UnhandledExceptionFilter 函數處理。
-
調試
+關注
關注
7文章
586瀏覽量
34001 -
函數
+關注
關注
3文章
4344瀏覽量
62809 -
進程
+關注
關注
0文章
204瀏覽量
13971
原文標題:Wine 開發系列 —— 如何調試 Wine
文章出處:【微信號:linux_deepin,微信公眾號:深度操作系統】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論