当前位置 : 主页 > 编程语言 > 其它开发 >

如何通过WinDbg获取方法参数值

来源:互联网 收集:自由互联 发布时间:2022-06-30
引入 我们在调试的过程中,经常会通过查看方法的输入与输出来确定这个方法是否异常。那么我们要怎么通过 WinDbg 来获取方法的参数值呢? WinDbg 中主要包含三种命令:标准命令、元
引入

我们在调试的过程中,经常会通过查看方法的输入与输出来确定这个方法是否异常。那么我们要怎么通过 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 能获取到,那我们是不是也可以在内存中找到对应的参数。

在找参数在内存中的位置之前,我们需要了解方法调用的一些约定,针对这些约定,我们叫它:调用协定

调用协定 定义
  • 函数调用约定,是指当一个函数被调用时,函数的参数会被传递给被调用的函数和返回值会被返回给调用函数。
  • 函数的调用约定就是描述参数是怎么传递和由谁平衡堆栈的,当然还有返回值
分类 cdecl 约定

c/c++ 默认的调用约定。

规则:

  • 参数采用栈传递
  • 从右到左入栈
  • 参数由调用方清理
  • 由 eax 作为方法返回值
stdcall 约定

startard call 的缩写。微软的标准约定,大多数 Win32 api 采用的都是 stdcall

规则:

  • 参数采用栈传递
  • 从右到左入栈
  • 参数由被调用方清理
  • 由 eax 作为方法返回值
fastCall 约定

fastCall 采用 ecx 和 edx 两个寄存器来传递参数,优化效率

规则:

  • 前两个参数分别采用 ecx edx 传递,其他参数仍然采用栈传递
  • 从右到左入栈
  • 参数由被调用方清理
  • 由 eax 作为方法返回值
X64 约定

针对 64 位平台的 fastcall 变种,采用 ecx, edx, r8, r9 四个寄存器来传递方法的前四个参数

规则:

  • 前四个参数分别采用 ecx, edx, r8, r9 传递,其他参数仍然采用栈传递
  • 从右到左入栈
  • 参数由被调用方清理
  • 由 eax 作为方法返回值
内存布局

我们调试一下代码,将代码停在 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 命令输出的是一致的。

上一篇:深入理解 volatile 关键字
下一篇:没有了
网友评论