一、Wine
1、 wine 实现了大多数的windows API,集成了winedbg
2、 windows API
1) kernel32.dll
允许一个W-process作为debugger 去执行另一个W-process,作为debuggee,包括设置breakpoint,单步执行等等
2) DBGHELP.DLL
让一个debbuger从任意模块查找符号和类型
3、 异常解决
怎么根据下面信息查找crash原因
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 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
Threads:
process tid prio (all id:s are in hex)
00000008 (D) C:\Perfect World Entertainment\Perfect World International\element\elementclient.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
下面信息的含义:
000d:Call advapi32.RegOpenKeyExW(00000090,7eb94da0 L"Patterns",00000000,00020019,0033f968) ret=7eb39af8
000d: 线程id
advapi32: 被调用模块
RegOpenKeyExW: 被调用函数
后面几个都是参数
ret :返回地址
4、 Useful memory address
1) 32位
linux: 0x08000000 0x00400000 0x40000000
2) 16位(增强模式): segment:offset
segment 如果最低三比特位都是1,就是一个selector,如果最低三笔特除了最低位外都是1,可能是全局内存
0x1f7 (0x40320000, 0x0000ffff, r-x) : 分别是基地址,最大偏移,访问权限 r-x 表示可读可执行
实际地址: selector基地址+offset
3) 16位(标准模式):segment:offset
segment和offset可以是0~0xffff
实际地址: segment×16+offset
5、 配置
1) 配置debuger
[MACHINE\\Software\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug] 957636538
"Auto"=dword:00000001
"Debugger"="winedbg %ld %ld"
2) 配置winedbg
[HKCU\\Software\\Wine\\WineDbg]
BreakAllThreadsStartup TRUE:所有线程停止 FALSE:第一个线程停止
BreakOnCritSectTimeOut TRUE:在临界区超时5分钟停止 FALSE:不停止
BreakOnAttach
BreakOnFirstChance
一个异常产生两个debug 事件,或者说两次chance
first chance:发生异常之后传递给debugger,debugger 要么继续执行(cont),要么交给exception handler chain(pass)
last chance:如果没有exception handler 处理异常,会再次传递给debugger,这一次不能pass
TRUE: 两次机会都会处理 FALSE :仅仅进入last chance
AlwaysShowThunk TRUE:根据名称显示所有thunks FALSE:
3) +relay 行为配置
可能输出会很多,但可以进行设置
[HKCU\\Software\\Wine\\Debug]
RelayExclude 列出不需要的输出
RelayInclude 仅输出列出的输出
怎么知道哪些输出是不需要的?
WINEDEBUG=+relay wine appname.exe &>relay.log
awk -F'(' '{print $1}' < relay.log | awk '{print $2}' | sort | uniq -c | sort
二、WineDbg
1、 WineDbg表达式
同 C 格式总体相同,有少量差异。
1) 表达式名称里可以用 !
2) 转换操作时 结构体或联合体都需要带 struct 或union 关键字
winedbg 特殊变量:$ThreadId 即 W-thread id
$ProcessId 即 W-process id
所有CPU寄存器值也是变量
2、 WineDbg 命令
NO. |
command |
Details |
1 |
abort |
abort the debbuger |
2 |
quit |
Quit the debbuger |
3 |
Attach W-process ID |
附加到另一个进程 |
4 |
Detach |
分离进程 |
5 |
help |
|
6 |
help info |
|
NO. |
command |
Details |
1 |
cont, c |
继续执行 |
2 |
pass |
传递异常给filter chain |
3 |
step,s |
单步,进入函数调用 |
4 |
stepi,si |
单个指令 |
5 |
next ,n |
单步,不进入函数 |
6 |
nexti ,ni |
单个指令,不进入调用 |
7 |
finish ,f |
执行直到当前函数退出 |
NO. |
command |
Details |
1 |
enable N |
激活break|watch point N |
2 |
disable N |
禁用 break|watch point N |
3 |
delete N |
删除 break|watch point N |
4 |
cond N |
移除任何到 break|watch point N 的条件 |
5 |
cond N expr |
按表达式设置breakpoint N触发条件 |
6 |
break * N |
增加breakpoint N(N为地址) |
7 |
break id |
增加breakpoint 符号id的地址 ?? |
8 |
break id N |
符号id 的第N行 ?? |
9 |
break N |
当前源文件的第N行 |
10 |
break |
当前$PC 地址设置breakpoint |
11 |
watch * N |
观察指令,* N 为地址 |
12 |
watch id |
符号id的地址 |
13 |
info break |
列出所有 break|watch point |
NO. |
command |
Details |
1 |
bt |
打印当前线程栈调用 |
2 |
bt N |
打印线程ID为N的线程栈调用 |
3 |
up |
往上走 1 frame ?? |
4 |
up N |
往上走 N frame |
5 |
dn |
往下走1frame |
6 |
dn N |
|
7 |
frame N |
执行直到当前函数退出 |
8 |
info local |
局部变量 |
NO. |
command |
Details |
1 |
show dir |
打印源文件查找目录 |
2 |
dir pathname |
增加路径到查找目录列表 |
3 |
dir |
删除查找目录列表 |
4 |
symbolfile pathnamme |
加载外部符号定义 |
5 |
symbolfile pathname N |
同上,但是有个偏移地址N |
6 |
list /-/N/file:N |
默认列出10行代码,-向后列出 |
7 |
list id |
列出函数id处的10行代码 |
8 |
list * N |
列出地址N处10行代码 |
9 |
list N1,N2 |
列出从N1行到N2行的源码 |
10 |
list file:N1,N2 |
列出文件file 从N1行到N2行的源码 |
NO. |
command |
Details |
1 |
info display |
?? |
2 |
display |
|
3 |
display expr |
|
4 |
display lfmt expr |
按格式输出 |
5 |
del display N,undisplay N |
删除display |
NO. |
command |
Details |
1 |
disas |
反汇编 |
2 |
disas expr |
指定地址出汇编代码 |
3 |
disas expr,expr |
两个地址之间的汇编 |
NO. |
command |
Details |
1 |
x expr/lfmt expr |
显示地址出的值 ,格式?? |
2 |
print expr/lfmt expr |
打印表达式的值 |
3 |
set lval=expr |
设置变量 |
4 |
whatis expr |
打印C 类型的表达式 |
5 |
set!symbol_picker interactive |
打印的时候由用户决定选那个符号 |
6 |
set!symbol_picker scopedb |
优先局部符号,然后才是全局的 |
v fmt 可以是 letter 或 count letter ??
s ascii string
u utf16 string
i 指令
x 32-bit 无符号16进制整数
d 32-bit 无符号10进制整数
w 16-bit无符号16进制整数
c 可打印字符,0x20~0x70 实际是可打印的
b 8-bit 无符号16进制整数
g GUID
NO. |
command |
Details |
1 |
info class |
列举所有窗口类 |
2 |
info class id |
关于窗口类id的信息 |
3 |
info share |
列举所有动态库,so或dll |
4 |
info share N |
地址N的模块信息 |
5 |
info regs |
CPU寄存器信息 |
6 |
info all-regs |
CPU 和浮点寄存器 |
7 |
info segment N |
在segment N的信息,仅i386 |
8 |
info segment |
所有segment ,仅i386 |
9 |
info stack |
栈信息 |
10 |
info map |
debbuger 的虚拟映射 |
11 |
info map N |
wpid N 的虚拟映射 |
12 |
info wnd N |
打印窗口N的信息 |
13 |
info wnd |
列举从桌面开始的所有窗口层次 |
14 |
info process |
列举wine回话中所有w-processes |
15 |
info thread |
列举所有w-threads |
16 |
info exception |
异常信息 |
NO. |
command |
Details |
1 |
set + warn |
打开warn channel |
2 |
set + channel |
打开warn/fixme/err/trace |
3 |
set - channel |
关闭上述channel |
4 |
set - fixme |
关闭 fixme |
三、其他Debuggers
1.GDB 模式
winedbg是一个远程gdb 监视器,增加 --gdb 即可激活gdb模式。
gdb和winegdb的区别
winegdb 掌控一个进程的所有线程,可以处理所有线程断点。gdb只能对单个线程进行调试。
Winegdb支持 stabs(standard Unix format) 和C,CodeView,.DBG(Microsoft). gdb 支持stabs 和 Dwarf II
2.DDD
如下命令:
winedbg --gdb --no-start *.exe optional param
然后把输出: target remote localhost:12345 粘贴到ddd即可运行
3.kdbg
其他同ddd,运行时在kdbg终端运行: kdbg -r localhost:12345 wine
四、调试技巧
1、 debuging classes
FIXME
ERR
WARN
TRACE
MESSAGE
2、 debugging channels
每个组件都会有一个channel
设置方法:
WINE_DEFAULT_DEBUG_CHANNEL(xxx);
如果要有多个channel,则增加多个名称不同即可。
使用的时候,需要使用类似FIXME_(xxx)(fmt,...); 此时FIXME(fmt,...);指向第一个声明channel
TRACE_ON WARN_ON ERR_ON FIXME_ON 用来判断是否打开了
3、 一些有用的函数
1) debugres
LPSTR debugres(const void* id);
id: 资源id指针
返回:字符串类型,格式化字符串
2) debugstr_[aw]n
处理某些NULL,控制字符或太长,或需要转换成ascii,都可以使用这些函数处理
4、 修改调试输出
第一种方法: 使用winedbg 命令
第二种方法: 通过taskmgr修改
第三种方法:创建pipe 和运行 WINEDEBUG
mknode /tmp/debug_pipe p
WINEDEBUG=+relay,+snoop wine setup.exe &>/tmp/debug_pipe
cat /tmp/debug_pipe
WINEDEBUG 格式:
WINEDEBUG=[yyy]#xxx[,[yyy1]#xxx1]*
yyy: trace,debug,warn,fixme,err等,fixme 和err 默认是激活的,trace和warn 默认是没激活的
#: + 或 -
xxx: channel,all 表示所有channel
可以用 , 隔开添加多个
如果使用MessageBox,则
WINEDEBUG=+relay wine program_name &>relmsg
5、 风格上的一些注意
输出格式: class:channel:function message
6、 一些其他技术
i386系统,栈是4字节,小端,地址向下增长,栈指针存放在esp寄存器,指向栈内存最后一次push 进去的数据的地址。可以表示为:
push操作: *(--esp) =p; pop 操作:p=*(esp++);
额外补充:
1) 调用协议常用场合
__stdcall:Windows API默认的函数调用协议。
__cdecl:C/C++默认的函数调用协议。
__fastcall:适用于对性能要求较高的场合。
2) 函数参数入栈方式
__stdcall:函数参数由右向左入栈。
__cdecl:函数参数由右向左入栈。
__fastcall:从左开始不大于4字节的参数放入CPU的ECX和EDX寄存器,其余参数从右向左入栈。
问题一:__fastcall在寄存器中放入不大于4字节的参数,故性能较高,适用于需要高性能的场合。
3) 栈内数据清除方式
__stdcall:函数调用结束后由被调用函数清除栈内数据。
__cdecl:函数调用结束后由函数调用者清除栈内数据。
__fastcall:函数调用结束后由被调用函数清除栈内数据。
问题一:不同编译器设定的栈结构不尽相同,跨开发平台时由函数调用者清除栈内数据不可行。
问题二:某些函数的参数是可变的,如printf函数,这样的函数只能由函数调用者清除栈内数据。
问题三:由调用者清除栈内数据时,每次调用都包含清除栈内数据的代码,故可执行文件较大。
4) C语言编译器函数名称修饰规则
__stdcall:编译后,函数名被修饰为“_functionname@number”。
__cdecl:编译后,函数名被修饰为“_functionname”。
__fastcall:编译后,函数名给修饰为“@functionname@nmuber”。
注:“functionname”为函数名,“number”为参数字节数。
注:函数实现和函数定义时如果使用了不同的函数调用协议,则无法实现函数调用。
5) C++语言编译器函数名称修饰规则
__stdcall:编译后,函数名被修饰为“?functionname@@YG******@Z”。
__cdecl:编译后,函数名被修饰为“?functionname@@YA******@Z”。
__fastcall:编译后,函数名被修饰为“?functionname@@YI******@Z”。
注:“******”为函数返回值类型和参数类型表。
注:函数实现和函数定义时如果使用了不同的函数调用协议,则无法实现函数调用。
C语言和C++语言间如果不进行特殊处理,也无法实现函数的互相调用。
stdcall 调用:
从右往左一次将参数push 进栈。例如: function(20,30,40,50);为
push 50
push 40
push 30
push 20
//此时栈的状况:
other variable
50
40
30
20 <--- esp 指向这里
call function
//此时
other variable <--- esp 指向这里
但是这里有个问题是调用函数如何知道被调用函数有多少个参数
有两种方法: 第一,记录栈偏移 第二,