如何使用 Wine 日志调试问题
<p><img class="alignnone size-full wp-image-35941" src="https://www.deepin.org/wp-content/uploads/2025/01/02-zh.png" alt="" width="900" height="383" srcset="https://www.deepin.org/wp-content/uploads/2025/01/02-zh.png 900w, https://www.deepin.org/wp-content/uploads/2025/01/02-zh-300x128.png 300w, https://www.deepin.org/wp-content/uploads/2025/01/02-zh-150x64.png 150w, https://www.deepin.org/wp-content/uploads/2025/01/02-zh-768x327.png 768w, https://www.deepin.org/wp-content/uploads/2025/01/02-zh-24x10.png 24w, https://www.deepin.org/wp-content/uploads/2025/01/02-zh-36x15.png 36w, https://www.deepin.org/wp-content/uploads/2025/01/02-zh-48x20.png 48w" sizes="(max-width: 900px) 100vw, 900px" /></p><p>输出调试日志是调试程序的一种常见方法,尤其是处理那些难以捉摸的多线程错误、随机崩溃等问题时 。通过在合适的位置输出调试日志,可以帮助我们更快地定位问题所在。</p>
<p>对于不熟悉的代码,经常打日志也有助于快速理解代码的执行流程和功能。在大型项目中,通常会先实现一套自己的调试日志框架,主要有两个目的:</p>
<ul class="list-paddingleft-1" data-tool="mdnice编辑器">
<li>统一日志风格和存储:确保日志格式一致,并且有统一的存储方式,这有助于用户更容易地报告问题。</li>
<li>方便开发人员:开发人员能够轻松地记录日志,从而快速调试问题。</li>
</ul>
<p>Wine 项目也不例外,它也实现了一套自己的日志系统。这套系统非常简洁,下面我们就来详细介绍。</p>
<p> </p>
<h1 style="text-align: center;"><strong>Wine 的调试日志实现</strong></h1>
<h2><strong>调试通道(debug channel)</strong></h2>
<p>Wine 定义了调试通道的概念来分类日志,将日志的记录和实际的输出分离,无需重新编译 Wine,就能动态地灵活控制 Wine 运行时的日志输出。</p>
<ul class="list-paddingleft-1" data-tool="mdnice编辑器">
<li>每个调试通道有一个唯一的名字, 长度不超过14个可见的 ASCII 字符, 一般一个模块至少定义了一个调试通道,比如 gdi32.dll 模块,有一个名称叫 gdi 的调试通道。</li>
<li>复杂的模块,为了细分日志定义了多个调试通道,比如 gdi32.dll 模块,还定义了clipping、region、font、bitmap、print、bitblt 等调试通道。</li>
<li>调试通道在代码里面来看实际是一个<code>__wine_debug_channel </code> 的结构体,刚好16个字节,非常符合 UNIX 简单原则的哲学理念:</li>
</ul>
<blockquote><p>struct __wine_debug_channel<br />
{<br />
unsigned char flags;<br />
char name;<br />
};</p></blockquote>
<ul>
<li>
<section>日志一次只发送给一个调试通道。</section>
</li>
<li>
<section>代码里增加一个新的调试通道,非常简单:</section>
<p> </li>
</ul>
<section>1. 包含 <code>include/wine/debug.h </code>;</section>
<p>2. 然后用<code>WINE_DEFAULT_DEBUG_CHANNEL</code>或<code>WINE_DEFAULT_DEBUG_CHANNEL</code>宏来声明。</p>
<ul>
<li>要知道一个模块定义了哪些调试通道,只需这样搜索该模块的所有源码:<code>git grep _DEBUG_CHANNEL </code>。</li>
</ul>
<p> </p>
<h2><strong>向调试通道发送日志</strong></h2>
<p>Wine 把日志分成了4个级别,从高到低依次是:fixme/ err(or)/ warn/ trace,对应的提供了4个宏来输出不同级别的日志到调试通道:FIXME/ ERR/ WARN/ TRACE,非常简单、清晰。</p>
<blockquote><p>#define __WINE_DPRINTF(dbcl,dbch) \<br />
do { if(__WINE_GET_DEBUGGING(dbcl,(dbch))) { \<br />
struct __wine_debug_channel * const __dbch = (dbch); \<br />
const enum __wine_debug_class __dbcl = __WINE_DBCL##dbcl; \<br />
__WINE_DBG_LOG</p>
<p>#define __WINE_DBG_LOG(args...) \<br />
wine_dbg_log( __dbcl, __dbch, __FUNCTION__, args); } } while(0)</p>
<p>#define TRACE __WINE_DPRINTF(_TRACE,__wine_dbch___default)<br />
#define TRACE_(ch) __WINE_DPRINTF(_TRACE,&__wine_dbch_##ch)<br />
#define TRACE_ON(ch) __WINE_IS_DEBUG_ON(_TRACE,&__wine_dbch_##ch)</p>
<p>#define WARN __WINE_DPRINTF(_WARN,__wine_dbch___default)<br />
#define WARN_(ch) __WINE_DPRINTF(_WARN,&__wine_dbch_##ch)<br />
#define WARN_ON(ch) __WINE_IS_DEBUG_ON(_WARN,&__wine_dbch_##ch)</p>
<p>#define FIXME __WINE_DPRINTF(_FIXME,__wine_dbch___default)<br />
#define FIXME_(ch) __WINE_DPRINTF(_FIXME,&__wine_dbch_##ch)<br />
#define FIXME_ON(ch) __WINE_IS_DEBUG_ON(_FIXME,&__wine_dbch_##ch)</p>
<p>#define ERR __WINE_DPRINTF(_ERR,__wine_dbch___default)<br />
#define ERR_(ch) __WINE_DPRINTF(_ERR,&__wine_dbch_##ch)<br />
#define ERR_ON(ch) __WINE_IS_DEBUG_ON(_ERR,&__wine_dbch_##ch)</p></blockquote>
<p>最终都是调用函数 wine_dbg_log 来打日志:</p>
<blockquote><p>int wine_dbg_log( enum __wine_debug_class cls, struct __wine_debug_channel *channel,<br />
const char *func, const char *format, ... )<br />
{<br />
int ret;<br />
va_list valist;</p>
<p>if (!(__wine_dbg_get_channel_flags( channel ) & (1 << cls))) return -1;</p>
<p>va_start(valist, format);<br />
ret = funcs.dbg_vlog( cls, channel, func, format, valist );<br />
va_end(valist);<br />
return ret;<br />
}</p></blockquote>
<p>其中的 funcs.dbg_vlog 初始化时会指向 default_dbg_vlog:</p>
<blockquote><p>static int default_dbg_vlog( enum __wine_debug_class cls, struct __wine_debug_channel *channel,<br />
const char *func, const char *format, va_list args )</p></blockquote>
<p>在Wine线程创建成功后 funcs.dbg_vlog 会指向 ntdll/debugtools.c 的</p>
<blockquote><p> static int NTDLL_dbg_vlog( enum __wine_debug_class cls, struct __wine_debug_channel *channel,<br />
const char *function, const char *format, va_list args )</p></blockquote>
<p> </p>
<h2><strong>程序运行前开启调试通道</strong></h2>
<p>用这样的格式定义环境变量:<code> WINEDEBUG=[+/-]channel[,[+/-]channel2]</code></p>
<p>其中:</p>
<ul class="list-paddingleft-1">
<li><span class="wx_text_underline">class:是代表 </span><span class="wx_text_underline">fixme/ err/ warn/ trace</span><span class="wx_text_underline"> 这4个日志级别的一个单词,如果没有指定就开关所有的日志级别。</span></li>
<li><span class="wx_text_underline">channel:就是要开关的调试通道的名称,all 代表所有的通道。</span></li>
<li><span class="wx_text_underline">+:就是开启指定调试通道的对应的日志级别。</span></li>
<li><span class="wx_text_underline">-:就是关闭指定调试通道的对应的日志级别。</span></li>
</ul>
<p><span class="wx_text_underline">例:</span></p>
<blockquote><p>WINEDEBUG=warn+all<br />
WINEDEBUG=warn+dll,+heap<br />
WINEDEBUG=fixme-all,warn+cursor,+relay</p></blockquote>
<p>如果没有定义<code>WINEDEBUG</code>环境变量,发给每个调试通道的 fixme 和 err 级别的日志都会输出;Wine 默认同时开启运行的调试通道是 256个,由这个宏 <code>MAX_DEBUG_OPTIONS</code> 决定。</p>
<p>关键代码如下:</p>
<blockquote><p>enum __wine_debug_class<br />
{<br />
__WINE_DBCL_FIXME,<br />
__WINE_DBCL_ERR,<br />
__WINE_DBCL_WARN,<br />
__WINE_DBCL_TRACE,</p>
<p>__WINE_DBCL_INIT = 7 /* lazy init flag, bit7 */<br />
};</p>
<p>static unsigned char default_flags = (1 << __WINE_DBCL_ERR) | (1 <name, debug_options, nb_debug_options,<br />
sizeof(debug_options), cmp_name );<br />
if (opt) return opt->flags;<br />
}<br />
/* no option for this channel */<br />
if (channel->flags & (1 <flags = default_flags;<br />
return default_flags;<br />
}</p></blockquote>
<p> </p>
<h2><strong>仅标记作用的调试通道</strong></h2>
<ul class="list-paddingleft-1" data-tool="mdnice编辑器">
<li>
<section>pid:在每个日志的前面插入当前进程的 ID号,格式:%04x。</section>
</li>
<li>
<section>tid:在每个日志的前面插入当前线程的 ID号,格式:%04x。</section>
</li>
<li>
<section>timestamp:在每个日志的前面插入时间戳,相对系统启动的时间、单位秒、保留3位小数。</section>
</li>
</ul>
<p> </p>
<h2><strong>比较特殊的高级调试通道</strong></h2>
<ul>
<li>seh:记录所有的异常情况,快速定位程序崩溃地址。</li>
</ul>
<blockquote><p>0009:trace:seh:raise_exception code=c00002b5 flags=0 addr=0xc4194be ip=0c4194be tid=0009<br />
0009:trace:seh:raise_exception info=00000000<br />
0009:trace:seh:raise_exception eax=00000006 ebx=0b6f4f58 ecx=08b44020 edx=0033d15c esi=0dfde520 edi=0df80020<br />
0009:trace:seh:raise_exception ebp=0033d0d0 esp=0033d0c0 cs=0023 ds=002b es=002b fs=0063 gs=006b flags=00210206</p></blockquote>
<ul>
<li>relay:无需修改代码,记录程序调用 Wine 实现的所有 API 的详细参数和返回值。</li>
</ul>
<blockquote><p>...<br />
0017:Call KERNEL32.CreateFileA(7ea8e936 "CONIN$",c0000000,00000003,00000000,00000003,00000000,00000000) ret=7ea323fd<br />
0017:Ret KERNEL32.CreateFileA() retval=00000023 ret=7ea323fd<br />
...</p></blockquote>
<ul>
<li>snoop:无需修改代码,记录程序对第三方 native 模块的所有导出函数的调用参数和返回值。</li>
</ul>
<p>Snoop 是自己检查 stack 数据和反汇编来探测函数调用约定、参数和返回地址的, 如果探测错了会影响程序的稳定,甚至导致程序崩溃,建议仅在非常规情况下使用。</p>
<blockquote><p>...<br />
trace:snoop:SNOOP_SetupDLL hmod=0x4ae90000, name=gdiplus.dll<br />
0043:CALL MSVCR100_CLR0400.wcscpy_s(04b7c808,0000000c,0033d188 L"gdiplus.dll") ret=79203d6b<br />
0043:RET MSVCR100_CLR0400.wcscpy_s() retval=00000000 ret=79203d6b<br />
0043:CALL MSVCR100_CLR0400.memset(0033dc9c,00000000,00000010) ret=792bd727<br />
0043:RET MSVCR100_CLR0400.memset() retval=0033dc9c ret=792bd727<br />
0043:CALL gdiplus.GdiplusStartup() ret=04b8e775<br />
0043:RET gdiplus.GdiplusStartup(03c00ae0,0033dc9c,0033dcec) retval=00000000 ret=04b8e775<br />
...<br />
0043:CALL gdiplus.GdipCreateFromHWND() ret=04b8e8b3<br />
0043:RET gdiplus.GdipCreateFromHWND(0004003a,0033e988) retval=00000000 ret=04b8e8b3<br />
...</p></blockquote>
<p>Relay 和 snoop 的缺点是记录的日志巨大导致程序反应非常慢,只建议在没有任何思路、一筹莫展时使用。</p>
<p> </p>
<h2><strong>程序运行中动态开关调试通道</strong></h2>
<ul>
<li>
<section>方法1:运行任务管理器(wine taskmgr),打开“进程”标签页,右键选中进程,在右键菜单里面选中“编辑调试频道”。这个方法只能开关事先在<code>WINEDEBUG</code>环境变量里面列出的调试通道。</section>
</li>
<li>
<section>方法2:在 Winedbg 里 attach 指定的 Wine 进程,然后用 set 命令:</section>
<p> </li>
</ul>
<p>1. set + channel:开启指定通道的所有 fixme/ err/ warn/ trace 日志。</p>
<p>2. set - channel:关闭指定通道的所有 fixme/ err/ warn/ trace 日志。</p>
<p>3. set class + channel:开启指定通道的 fixme/ err/ warn/ trace 日志中的某一类。class 替换为 fixme/ err/ warn/ trace 这4个单词中的任意一个。</p>
<p>4. set class - channel:关闭指定通道的 fixme/ err/ warn/ trace 日志中的某一类。</p>
<p>Winedbg 的 set 命令也只能设置在<code>WINEDEBUG</code>已经开启了的调试通道。如果没有在<code>WINEDEBUG</code>里面定义的,就会提示: <code>Unable to find debug channel xxx</code> 。</p>
<ul>
<li>
<section>方法3:在 Winedbg 里 attach 指定的 Wine 进程,手动修改<code>debug_options</code>和<code>nb_debug_options</code>的数据。因为<code>debug_options</code>是按照调试通道名称字符串比较排序的,所以开启多个通道需要手动排序。这个方法适合运行程序时忘记设置<code>WINEDEBUG</code>,但是想查看某个调试通道日志时又不想重新运行程序的时候使用。</section>
<p> </li>
</ul>
<blockquote><p>Wine-dbg>set + win<br />
Unable to find debug channel win<br />
Wine-dbg>p debug_options
{flags=0, name=""}<br />
Wine-dbg);set debug_options.flags=0xf<br />
Wine-dbg>p debug_options
{flags=f, name=""}<br />
Wine-dbg>set debug_options.name='w'<br />
Wine-dbg>set debug_options.name='i'<br />
Wine-dbg>set debug_options.name='n'<br />
Wine-dbg>set debug_options.name= 0<br />
Wine-dbg>p debug_options
{flags=f, name="楷n"}<br />
Wine-dbg>p &debug_options
0xf77092d0<br />
Wine-dbg>x /s 0xf77092d1<br />
win<br />
Wine-dbg>p nb_debug_options<br />
0<br />
Wine-dbg>set nb_debug_options=1</p></blockquote>
<ul class="list-paddingleft-1" data-tool="mdnice编辑器">
<li>
<section>方法4:一般正式发布的 libwine.so 没有调试符号,就只能反汇编定位<code>debug_options</code>和<code>nb_debug_options</code>的地址。</section>
</li>
</ul>
<p>1. 先查询 __wine_dbg_get_channel_flags 的偏移,readelf -s libwine.so.1.0 | grep __wine_dbg :</p>
<blockquote><p>109: 00005110 206 FUNC GLOBAL DEFAULT 12 __wine_dbg_set_channel_fl@@WINE_1.0<br />
166: 00005030 223 FUNC GLOBAL DEFAULT 12 __wine_dbg_get_channel_fl@@WINE_1.0<br />
172: 00005390 253 FUNC GLOBAL DEFAULT 12 __wine_dbg_set_functions@@WINE_1.0</p></blockquote>
<p>2. 再查询 libwine.so 的基地址得到 __wine_dbg_get_channel_flags 的地址:</p>
<blockquote><p>Wine-dbg>info share<br />
Module Address Debug info Name (23 modules)<br />
...<br />
ELF f75d5000-f778c000 Dwarf libwine.so.1 @,/opt/cxoffice/bin/../lib/libwine.so.1<br />
...</p></blockquote>
<p>3. 接着看 __wine_dbg_get_channel_flags 反汇编:</p>
<blockquote><p>Wine-dbg>disass 0xf75d5000+0x5030<br />
0xf75da030 __wine_dbg_get_channel_flags in libwine.so.1: push ebp<br />
0xf75da031 __wine_dbg_get_channel_flags+0x1 in libwine.so.1: mov ebp, esp<br />
0xf75da033 __wine_dbg_get_channel_flags+0x3 in libwine.so.1: push edi<br />
0xf75da034 __wine_dbg_get_channel_flags+0x4 in libwine.so.1: push esi<br />
0xf75da035 __wine_dbg_get_channel_flags+0x5 in libwine.so.1: push ebx<br />
0xf75da036 __wine_dbg_get_channel_flags+0x6 in libwine.so.1: call 0xf75d81a0<br />
0xf75da03b __wine_dbg_get_channel_flags+0xb in libwine.so.1: add ebx, 0x19dfc5<br />
0xf75da041 __wine_dbg_get_channel_flags+0x11 in libwine.so.1: sub esp, 0x1c<br />
0xf75da044 __wine_dbg_get_channel_flags+0x14 in libwine.so.1: mov ecx, --> nb_debug_options<br />
0xf75da04a __wine_dbg_get_channel_flags+0x1a in libwine.so.1: cmp ecx, 0xffffffff<br />
0xf75da04d __wine_dbg_get_channel_flags+0x1d in libwine.so.1: jz 0xf75da0f0<br />
0xf75da053 __wine_dbg_get_channel_flags+0x23 in libwine.so.1: test ecx, ecx<br />
0xf75da055 __wine_dbg_get_channel_flags+0x25 in libwine.so.1: jz 0xf75da0c0<br />
0xf75da057 __wine_dbg_get_channel_flags+0x27 in libwine.so.1: mov eax,
0xf75da05a __wine_dbg_get_channel_flags+0x2a in libwine.so.1: mov edi, ecx<br />
0xf75da05c __wine_dbg_get_channel_flags+0x2c in libwine.so.1: mov dword , 0x0<br />
0xf75da063 __wine_dbg_get_channel_flags+0x33 in libwine.so.1: add eax, 0x1<br />
0xf75da066 __wine_dbg_get_channel_flags+0x36 in libwine.so.1: mov , eax<br />
0xf75da069 __wine_dbg_get_channel_flags+0x39 in libwine.so.1: lea eax, --> debug_options<br />
0xf75da06f __wine_dbg_get_channel_flags+0x3f in libwine.so.1: mov , eax<br />
0xf75da072 __wine_dbg_get_channel_flags+0x42 in libwine.so.1: jmp 0xf75da07f __wine_dbg_get_channel_flags+0x4f in libwine.so.1<br />
0xf75d3074 __wine_dbg_get_channel_flags+0x44 in libwine.so.1: lea esi,
...<br />
0xf75d81a0: mov ebx,
0xf75d81a3: ret</p></blockquote>
<p>GCC 习惯通过 ebx 寄存器来引用全局变量,所以<code>nb_debug_options</code>的地址是:<code>0xf75d303b+0x19dfc5+0x134</code>;<code>debug_options</code>的地址是:<code>0xf75d303b+0x19dfc5+0x260</code>;然后参考方法 2 的 set 命令修改内存即可。</p>
<p> </p>
<h1 style="text-align: center;"><strong>Relay 调试通道实现原理</strong></h1>
<p>在 LoadLibrary 内部,如果检查到已经开启了 relay 通道,并且已加载 Wine 内建的 DLL 文件,那么就调用 RELAY_SetupDLL 来解析 DLL 的导出函数表(IMAGE_DIRECTORY_ENTRY_EXPORT)。对于导出表中的 AddressOfFunctions 数组中的每个条目,先备份原始值,然后将每个条目值修改为可跳转 relay_call 函数的 hack 函数地址。hack 函数在 faked PE 模块中,固定为 24 字节大小,形式如下:</p>
<blockquote><p>0x7e7a4210: push esp<br />
0x7e7a4211: push 0x50000<br />
0x7e7a4216: call 0x7e7a6a40 __wine_spec_get_pc_thunk_eax in gdi32<br />
0x7e7a421b: lea eax,
0x7e7a4221: push eax<br />
0x7e7a4222: call dword --> relay_call<br />
0x7e7a4225: ret 0x14</p></blockquote>
<p>不同的 API 变化的只是里面的数字常量。在 GetProcAddress 内部检查是否开启了 relay 通道,如已开启就调用 RELAY_GetProcAddress 返回 hack 的函数地址。以 user32 模块的GetMenu 举例,返回的 hack 的 GetMenu 函数如下:</p>
<blockquote><p>FARPROC RELAY_GetProcAddress( HMODULE module, const IMAGE_EXPORT_DIRECTORY *exports,<br />
DWORD exp_size, FARPROC proc, DWORD ordinal, const WCHAR *user )<br />
{<br />
struct relay_private_data *data;<br />
const struct relay_descr *descr = (const struct relay_descr *)((const char *)exports + exp_size);</p>
<p>if (descr->magic != RELAY_DESCR_MAGIC || !(data = descr->private)) return proc; /* no relay data */<br />
if (!data->entry_points.orig_func) return proc; /* not a relayed function */<br />
if (check_from_module( debug_from_relay_includelist, debug_from_relay_excludelist, user ))<br />
return proc; /* we want to relay it */<br />
return data->entry_points.orig_func;<br />
}</p>
<p>Wine-dbg>disass proc<br />
0x7e8d7cf0: push esp<br />
0x7e8d7cf1: push 0x10130<br />
0x7e8d7cf6: call 0x7e8dae44 __wine_spec_get_pc_thunk_eax in user32<br />
0x7e8d7cfb: lea eax,
0x7e8d7d01: push eax<br />
0x7e8d7d02: call dword
0x7e8d7d05: ret 0x4<br />
Wine-dbg>p 0x7e8d7cfb+0xd9fcd+0x4<br />
0x7e9b1ccc<br />
Wine-dbg>x /4x 0x7e9b1ccc<br />
0x7e9b1ccc: 7bc76c2c 7bc77270 00124640 7e8d615d<br />
Wine-dbg>disass 0x7bc76c2c<br />
0x7bc76c2c relay_call in ntdll: push ebp<br />
0x7bc76c2d relay_call+0x1 in ntdll: mov ebp, esp<br />
0x7bc76c2f relay_call+0x3 in ntdll: push esi<br />
0x7bc76c30 relay_call+0x4 in ntdll: push edi<br />
0x7bc76c31 relay_call+0x5 in ntdll: push ecx<br />
0x7bc76c32 relay_call+0x6 in ntdll: push dword
0x7bc76c35 relay_call+0x9 in ntdll: push dword
0x7bc76c38 relay_call+0xc in ntdll: push dword
0x7bc76c3b relay_call+0xf in ntdll: call 0x7bc76835 relay_trace_entry [../wine_git/dlls/ntdll/relay.c:334] in ntdll<br />
0x7bc76c40 relay_call+0x14 in ntdll: movzx ecx, byte
...</p></blockquote>
<p>原始的 GetMenu 地址:</p>
<blockquote><p>Wine-dbg>info local<br />
0x7bc772f4 RELAY_GetProcAddress+0x75: (0033eac8)<br />
HMODULE module=0x7e8d0000 (parameter )<br />
IMAGE_EXPORT_DIRECTORY* exports=0x7e9ad19c (parameter )<br />
DWORD exp_size=0x4b2c (parameter )<br />
FARPROC proc=0x7e8d7cf0 (parameter )<br />
DWORD ordinal=0x130 (parameter )<br />
WCHAR* user=0x0(nil) (parameter )<br />
struct relay_private_data* data=0x124640 (local )<br />
struct relay_descr* descr=0x7e9b1cc8 (local )<br />
Wine-dbg>p *descr<br />
{magic=0xdeb90001, relay_call=0x7bc76c2c, relay_call_regs=0x7bc77270,<br />
private=0x124640, entry_point_base="恌怲h", entry_point_offsets=0x7e9744f8, arg_types=0x7e975048}<br />
Wine-dbg>x /14x 0x124640<br />
0x00124640: 7e8d0000 00000001 72657375 00003233<br />
0x00124650: 00000000 00000000 00000000 00000000<br />
0x00124660: 00000000 00000000 00000000 00000000<br />
0x00124670: 7e913010 7e9aee17<br />
Wine-dbg>x /10x 0x00124670+0x130*8<br />
0x00124ff0: 7e928890 7e9b018c 7e92c6d0 7e9b0194<br />
0x00125000: 7e927fc0 7e9b01a3 7e92cc30 7e9b01be<br />
0x00125010: 7e92b680 7e9b01d3<br />
Wine-dbg>x /10c 0x7e9b018c<br />
0x7e9b018c: G e t M e n u G e<br />
Wine-dbg>disass 0x7e928890<br />
0x7e928890 GetMenu [../wine_git/dlls/user32/menu.c:4208] in user32: lea ecx,
0x7e928894 GetMenu+0x4 [../wine_git/dlls/user32/menu.c:4208] in user32: and esp, 0xfffffff0<br />
0x7e928897 GetMenu+0x7 [../wine_git/dlls/user32/menu.c:4208] in user32: push dword
0x7e92889a GetMenu+0xa [../wine_git/dlls/user32/menu.c:4208] in user32: push ebp<br />
0x7e92889b GetMenu+0xb [../wine_git/dlls/user32/menu.c:4208] in user32: mov ebp, esp<br />
0x7e92889d GetMenu+0xd [../wine_git/dlls/user32/menu.c:4208] in user32: push edi<br />
0x7e92889e GetMenu+0xe [../wine_git/dlls/user32/menu.c:4208] in user32: push esi<br />
0x7e92889f GetMenu+0xf [../wine_git/dlls/user32/menu.c:4208] in user32: push ebx<br />
0x7e9288a0 GetMenu+0x10 [../wine_git/dlls/user32/menu.c:4208] in user32: push ecx<br />
0x7e9288a1 GetMenu+0x11 [../wine_git/dlls/user32/menu.c:4208] in user32: call 0x7e8d5b60 __x86.get_pc_thunk.bx in user32<br />
...</p></blockquote>
<p>Relay_call 里面调用 relay_trace_entry/relay_trace_exit 来记录函数的进和出,以及调用真实的API。</p>
<p> </p>
<h1 style="text-align: center;"><strong>Snoop 调试通道的实现原理</strong></h1>
<p>在 LoadLibrary 内部检查到已开启 snoop 通道并且加载了 native 的 DLL,就调用 SNOOP_SetupDLL 解析 DLL 的导出函数,对每个导出函数动态分配一块可读写和执行的 hack 内存。</p>
<p>在 GetProcAddress 内部检查到已开启 snoop 通道,就调用 SNOOP_GetProcAddress,对 hack 内存填充一个可以跳到 SNOOP_Entry 函数的 jmp 指令,然后返回这个 hack 内存块的首地址。</p>
<p>SNOOP_Entry 探测函数的返回地址、调用约定、调用参数,打印出来,然后把当前 EIP 设置成真实的导出函数,把返回地址设置为 SNOOP_Return。</p>
<p> </p>
<p>总之,在解决实际问题的时候, 我们先收集日志,然后重点看 err:、fixme:、seh: 的日志,一般能从中找到问题的相关线索。</p>
<p> </p>
<h1><strong>相关阅读:</strong></h1>
<p>(1)支持 deepin(深度)社区</p>
<p>(2)Wine 开发系列 -01</p>
<p>(3)deepin 技术分享合集</p>
<p> </p>
<p style="text-align: right;">内容来源:deepin(深度)社区</p>
<p style="text-align: right;">转载请注明出处</p>
</div>
頁:
[1]