一、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<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>
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 命令
Misc commands
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 |
|
Flow control commands
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 |
執行直到當前函數退出 |
Breakpoints, watch points
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 |
Stack manipulation
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 |
局部變量 |
Directory & source file manipulation
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行的源碼 |
Displaying
NO. |
command |
Details |
1 |
info display |
?? |
2 |
display |
|
3 |
display expr |
|
4 |
display lfmt expr |
按格式輸出 |
5 |
del display N,undisplay N |
刪除display |
Disassembly
NO. |
command |
Details |
1 |
disas |
反彙編 |
2 |
disas expr |
指定地址出彙編代碼 |
3 |
disas expr,expr |
兩個地址之間的彙編 |
Memory (reading, writing, typing)
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
Information on Wine internals
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 |
異常信息 |
Debug channels
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 指向這裏
但是這裏有個問題是調用函數如何知道被調用函數有多少個參數
有兩種方法: 第一,記錄棧偏移 第二,