0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Wine常用调试方法

深度操作系统 来源:深度操作系统 2025-01-16 09:52 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

本文主要以 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。这将生成相当多的日志输出,但通常原因是位于最后一个函数调用中。这些日志通常如下所示: 8661d716-d329-11ef-9310-92fbcf53809c.png

如果你已经发现了一个行为不正常的 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

    文章

    623

    浏览量

    35383
  • 函数
    +关注

    关注

    3

    文章

    4406

    浏览量

    66843
  • 进程
    +关注

    关注

    0

    文章

    208

    浏览量

    14478

原文标题:Wine 开发系列 —— 如何调试 Wine

文章出处:【微信号:linux_deepin,微信公众号:深度操作系统】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    调试嵌入式处理器常用方法有哪些?

    调试嵌入式处理器常用方法有哪些?
    发表于 12-24 06:08

    分享一些以太网常用调试方法

    对于单phy的平台以太网技术已经很成熟,这里提供一些以太网常用调试方法。帮助客户快速定位常见问题。 以太网常用的命令有哪些呢?如何对其进行调试
    发表于 12-29 07:32

    调试嵌入式处理器的几种常用方法

    调试嵌入式处理器的几种常用方法 前言 在任何产品
    发表于 03-11 12:25 1832次阅读
    <b class='flag-5'>调试</b>嵌入式处理器的几种<b class='flag-5'>常用</b><b class='flag-5'>方法</b>

    常用网络调试工具

    [4]常用网络调试工具_v1.0rc
    发表于 12-27 16:26 0次下载

    linux下wine的使用

    Wine 仍在发展阶段,仅能执行少部份的 Windows 软体,大部份的软体仍然无法正常执行。 Wine的官方站点是http://www.winehq.com/,虽然你可以在它的官方站点下载源代码
    发表于 11-07 15:37 14次下载

    Wine 5.4版本更新更多新功能

    IT之家了解到,WineWine Is Not an Emulator)是一个能够在多种 POSIX-compliant 操作系统(包括 Linux,Mac OSX 及 BSD 等)上运行 Windows 应用的兼容层。
    的头像 发表于 03-14 09:33 2743次阅读

    Wine中将提供Windows应用程序与USB更好支持

    Wine 中运行的 Windows 应用程序直接与 USB 设备交互将会有更好的支持。Wine 项目的提交记录显示,最新合并的 WineUSB 初始部分将成为 Wine USB 驱动,类似于微软的 WinUSB。
    的头像 发表于 04-19 09:52 2970次阅读
    <b class='flag-5'>Wine</b>中将提供Windows应用程序与USB更好支持

    单片机程序常用的几种调试方法

    单片机程序常用的几种调试方法,这些调试方法都需要结合电脑上位机,以下我罗列一些调试
    发表于 07-17 09:31 1.4w次阅读

     Wine更新:支持 Linux 运行 Windows 应用,PE 格式核心模块

    1月19日消息 外媒 Windows Central 报道,Wine 最近收到了更新,改善了在 Linux 系统上运行 Windows 应用的体验。该更新以 Win3 6.0 的形式出现,根据其完整
    的头像 发表于 01-19 11:49 2843次阅读

    介绍常用调试命令、利用在线汇编、各种设置断点进行程序调试方法资料下载

    电子发烧友网为你提供介绍常用调试命令、利用在线汇编、各种设置断点进行程序调试方法资料下载的电子资料下载,更有其他相关的电路图、源代码、课件教程、中文资料、英文资料、参考设计、用户指
    发表于 04-16 08:42 19次下载
    介绍<b class='flag-5'>常用</b>的<b class='flag-5'>调试</b>命令、利用在线汇编、各种设置断点进行程序<b class='flag-5'>调试</b>的<b class='flag-5'>方法</b>资料下载

    shell脚本常用调试方法介绍

    软件、配置编译环境,可以说使用起来非常的方便,但是它在调试方面常常令人头大,本文主要介绍shell脚本常用调试方法 调试
    的头像 发表于 09-01 10:43 3867次阅读

    单片机常用调试方法

    在单片机程序调试过程中,串口打印调试方法是非常重要的手段,在使用串口调试时,我们更多的是使用printf。但是下面我们不介绍printf,介绍一下另外几种我们
    的头像 发表于 04-04 14:58 6002次阅读

    常用的远程控制方法总结

    开个新系列,写一下在调试工作中,常用的远程控制方法
    的头像 发表于 09-06 15:32 3331次阅读
    <b class='flag-5'>常用</b>的远程控制<b class='flag-5'>方法</b>总结

    Wine原理介绍和开发教程

    说起 Wine,稍微资深一点的 Linux 用户应该都听过,但是真要说起 Wine 到底是怎么回事,可能大多数人不见得说得清。这篇文章会简单地介绍 Wine 的工作原理,以及如何开始 Wine
    的头像 发表于 12-31 10:06 1.2w次阅读

    Wine开发系列——如何使用Wine日志调试问题

      输出调试日志是调试程序的一种常见方法,尤其是处理那些难以捉摸的多线程错误、随机崩溃等问题时。 通过在合适的位置输出调试日志,可以帮助我们更快地定位问题所在。 对于不熟悉的代码,经常
    的头像 发表于 01-06 11:29 1819次阅读