我們在偵錯的過程中,經常會通過檢視方法的輸入與輸出來確定這個方法是否異常。那麼我們要怎麼通過 WinDbg 來獲取方法的引數值呢?
WinDbg 中主要包含三種命令:標準命令、元命令(以 . 開始)和擴充套件命令(以 ! 開始)。
k 命令可以獲取棧回溯。
其中 kP 可以把引數和引數值都以函數原型格式顯示出來,但是需要有符號。如下:
0:000> kP
# Child-SP RetAddr Call Site
00 0000001b`7b0fdb78 00007ffc`718366fb ntdll!NtCreateUserProcess
01 0000001b`7b0fdb80 00007ffc`718732f6 KERNELBASE!CreateProcessInternalW+0x115b
02 0000001b`7b0ff510 00007ffc`728560c4 KERNELBASE!CreateProcessW+0x66
03 0000001b`7b0ff580 00007ff6`14a61960 KERNEL32!CreateProcessWStub+0x54
04 0000001b`7b0ff5e0 00007ff6`14a62419 CreateProcessWithCpp!main(
int argc = 0n1,
wchar_t ** argv = 0x00000208`0b637d00)+0xe0 [C:\Users\frend\source\repos\debug-test\AdavageDebug\CreateProcessWithCpp\CreateProcessWithCpp.cpp @ 20]
05 0000001b`7b0ff800 00007ff6`14a622be CreateProcessWithCpp!invoke_main(void)+0x39 [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 79]
06 0000001b`7b0ff850 00007ff6`14a6217e CreateProcessWithCpp!__scrt_common_main_seh(void)+0x12e [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 288]
07 0000001b`7b0ff8c0 00007ff6`14a624ae CreateProcessWithCpp!__scrt_common_main(void)+0xe [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 331]
08 0000001b`7b0ff8f0 00007ffc`7285244d CreateProcessWithCpp!mainCRTStartup(
void * __formal = 0x0000001b`7aeca000)+0xe [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_main.cpp @ 17]
09 0000001b`7b0ff920 00007ffc`740cdf88 KERNEL32!BaseThreadInitThunk+0x1d
0a 0000001b`7b0ff950 00000000`00000000 ntdll!RtlUserThreadStart+0x28
0:000> dc 0x00000208`0b637d00
00000208`0b637d00 0b637d10 00000208 00000000 00000000 .}c.............
00000208`0b637d10 555c3a43 73726573 6572665c 735c646e C:\Users\frend\s
00000208`0b637d20 6372756f 65725c65 5c736f70 75626564 ource\repos\debu
00000208`0b637d30 65742d67 415c7473 61766164 65446567 g-test\AdavageDe
00000208`0b637d40 5c677562 5c343678 75626544 72435c67 bug\x64\Debug\Cr
00000208`0b637d50 65746165 636f7250 57737365 43687469 eateProcessWithC
00000208`0b637d60 652e7070 fd006578 abfdfdfd abababab pp.exe..........
00000208`0b637d70 abababab abababab feababab feeefeee ................
可以看到,部分方法的引數和對應的值都顯示出來了,這裡用 CreateProcessWithCpp!main
為例。
同時,也可以看到部分方法儘管有有符號,也不一定能顯示出來。比如 ntdll!NtCreateUserProcess
。
如果我們就要看 ntdll!NtCreateUserProcess
的引數值呢?
還可以通過 kv 命令 顯示出前面的三個引數。例如:
0:000> kv L
# Child-SP RetAddr : Args to Child : Call Site
00 0000001b`7b0fdb78 00007ffc`718366fb : 0000001b`7b0fe1f8 0000001b`7b0fe3f0 0000001b`00000001 0000001b`7b0fdf34 : ntdll!NtCreateUserProcess
01 0000001b`7b0fdb80 00007ffc`718732f6 : 00000000`00000000 00000000`00000000 00007ff6`14a610eb 580000ff`ec77c5b6 : KERNELBASE!CreateProcessInternalW+0x115b
02 0000001b`7b0ff510 00007ffc`728560c4 : 0000001b`7b0ff588 00760065`0044005c 005c0065`00630069 00640072`00610048 : KERNELBASE!CreateProcessW+0x66
03 0000001b`7b0ff580 00007ff6`14a61960 : 00007ff6`14a710ac 00620065`0064005c 0074002d`00670075 005c0074`00730065 : KERNEL32!CreateProcessWStub+0x54
04 0000001b`7b0ff5e0 00007ff6`14a62419 : 00007891`00000001 00000208`0b637d00 00000000`00000000 00007ff6`14a63aed : CreateProcessWithCpp!main+0xe0
05 0000001b`7b0ff800 00007ff6`14a622be : 00007ff6`14a69000 00007ff6`14a69220 00000000`00000000 00000000`00000000 : CreateProcessWithCpp!invoke_main+0x39
06 0000001b`7b0ff850 00007ff6`14a6217e : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : CreateProcessWithCpp!__scrt_common_main_seh+0x12e
07 0000001b`7b0ff8c0 00007ff6`14a624ae : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : CreateProcessWithCpp!__scrt_common_main+0xe
08 0000001b`7b0ff8f0 00007ffc`7285244d : 0000001b`7aeca000 00000000`00000000 00000000`00000000 00000000`00000000 : CreateProcessWithCpp!mainCRTStartup+0xe
09 0000001b`7b0ff920 00007ffc`740cdf88 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x1d
0a 0000001b`7b0ff950 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x28
於是我們可以看到所有方法的引數值了。但遺憾的是:只能看到三個引數。
既然 WinDbg 能獲取到,那我們是不是也可以在記憶體中找到對應的引數。
在找引數在記憶體中的位置之前,我們需要了解方法呼叫的一些約定,針對這些約定,我們叫它:呼叫協定。
c/c++ 預設的呼叫約定。
規則:
startard call 的縮寫。微軟的標準約定,大多數 Win32 api 採用的都是 stdcall
規則:
fastCall 採用 ecx 和 edx 兩個暫存器來傳遞引數,優化效率
規則:
針對 64 位平臺的 fastcall 變種,採用 ecx, edx, r8, r9 四個暫存器來傳遞方法的前四個引數
規則:
我們偵錯一下程式碼,將程式碼停在 getSum → auto sum = a + b
,我們看看當前棧和引數,以及目前 ebp 所在記憶體地址的值。
0:000> kv L
# ChildEBP RetAddr Args to Child
00 0111f708 010119a0 0000000a 0000000c 01011023 Example_4_1_2!getsum+0x25 (FPO: [Non-Fpo]) (CONV: cdecl)
01 0111f808 01012173 00000001 013db990 013dc6f8 Example_4_1_2!main+0x40 (FPO: [Non-Fpo]) (CONV: cdecl)
02 0111f828 01011fc7 037e2288 01011023 01011023 Example_4_1_2!invoke_main+0x33 (FPO: [Non-Fpo]) (CONV: cdecl)
03 0111f884 01011e5d 0111f894 010121f8 0111f8a4 Example_4_1_2!__scrt_common_main_seh+0x157 (FPO: [Non-Fpo]) (CONV: cdecl)
04 0111f88c 010121f8 0111f8a4 76267ba9 00e8c000 Example_4_1_2!__scrt_common_main+0xd (FPO: [Non-Fpo]) (CONV: cdecl)
05 0111f894 76267ba9 00e8c000 76267b90 0111f8fc Example_4_1_2!mainCRTStartup+0x8 (FPO: [Non-Fpo]) (CONV: cdecl)
06 0111f8a4 771eb7db 00e8c000 f2ae73dd 00000000 KERNEL32!BaseThreadInitThunk+0x19 (FPO: [Non-Fpo])
07 0111f8fc 771eb75f ffffffff 7721869e 00000000 ntdll!__RtlUserThreadStart+0x2b (FPO: [Non-Fpo])
08 0111f90c 00000000 01011023 00e8c000 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])
0:000> dp ebp
0111f708 0111f808 010119a0 0000000a 0000000c
0111f718 01011023 01011023 00e8c000 010118b1
0111f728 01011023 01011023 00e8c000 0111f750
0111f738 0111f750 5cb4259c cb13e9ed fffffffe
0111f748 0111f758 5cb3fa93 0edf5aca 0000001d
0111f758 0111f774 0111f774 5cb4259c 0111f77c
0111f768 5cb42c02 76fad650 0111f788 771e0559
0111f778 0111f788 5cb3fa93 0edf5aca 0000001d
可以看到,ebp 在記憶體中對應的值即是呼叫方的 ChildEBP
,也就是其中的0111f808
;ebp + 4 即對應著當前方法的返回地址,也就是 010119a0
;而後面則是當前方法的引數值,也是跟 kv 命令輸出的是一致的。