如何通过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 命令输出的是一致的。
于是我们就可以返回到怎么找到 ntdll!NtCreateUserProcess
的参数值了。
直接去内存上去找
由于ntdll!NtCreateUserProcess
没有官方文档来描述它的接口定义,所以这里不用它来验证了。采用有文档可以验证的方法:KERNELBASE!CreateProcessW
。其 Microsoft Docs 地址:CreateProcessW function (processthreadsapi.h) - Win32 apps | Microsoft Docs
从文档中把 KERNELBASE!CreateProcessW
的定义抄下来:
BOOL CreateProcessW(
[in, optional] LPCWSTR lpApplicationName,
[in, out, optional] LPWSTR lpCommandLine,
[in, optional] LPSECURITY_ATTRIBUTES lpProcessAttributes,
[in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes,
[in] BOOL bInheritHandles,
[in] DWORD dwCreationFlags,
[in, optional] LPVOID lpEnvironment,
[in, optional] LPCWSTR lpCurrentDirectory,
[in] LPSTARTUPINFOW lpStartupInfo,
[out] LPPROCESS_INFORMATION lpProcessInformation
);
我们先把断点断在 KERNELBASE!CreateProcessW
,然后再来看栈和内存。这里我们以找 lpCommandLine (第二个参数)为例:
对于 32bit 的应用
x86 的 Win32 应用采用的是 stdcall 的调用约束。所以我们需要去栈中找:
0:000> bu KERNELBASE!CreateProcessW
0:000> g
Breakpoint 0 hit
KERNELBASE!CreateProcessW:
76fc4eb0 8bff mov edi,edi
0:000> k L
# ChildEBP RetAddr
00 00cffa04 008c1915 KERNELBASE!CreateProcessW
01 00cffb88 008c2213 CreateProcessWithCpp!main+0xb5
02 00cffba8 008c2067 CreateProcessWithCpp!invoke_main+0x33
03 00cffc04 008c1efd CreateProcessWithCpp!__scrt_common_main_seh+0x157
04 00cffc0c 008c2298 CreateProcessWithCpp!__scrt_common_main+0xd
05 00cffc14 76267ba9 CreateProcessWithCpp!mainCRTStartup+0x8
06 00cffc24 771eb7db KERNEL32!BaseThreadInitThunk+0x19
07 00cffc7c 771eb75f ntdll!__RtlUserThreadStart+0x2b
08 00cffc8c 00000000 ntdll!_RtlUserThreadStart+0x1b
然后我们先看看寄存器上的值。
0:000> r
eax=00cffb24 ebx=00a03000 ecx=00cffb3c edx=00cffb04 esi=00cffa34 edi=00cffb88
eip=76fc4eb0 esp=00cffa08 ebp=00cffb88 iopl=0 nv up ei pl nz ac po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000212
KERNELBASE!CreateProcessW:
76fc4eb0 8bff mov edi,edi
再来看看内存上的值
0:000> dp esp
00cffa08 008c1915 00000000 **00cffb04** 00000000
00cffa18 00000000 00000000 00000000 00000000
00cffa28 00000000 00cffb3c 00cffb24 008c1023
00cffa38 008c1023 00a03000 008c1023 00a03000
00cffa48 00cffa60 e8824047 00cffa5c 689407f5
00cffa58 68a24080 00cffa9c 00000000 00cffa70
00cffa68 008c1023 008c1023 00a03000 00cffa98
00cffa78 6890c88f 00cffa88 689407f5 68a24080
0:000> dc **00cffb04 L8**
00cffb04 006f006e 00650074 00610070 002e0064 n.o.t.e.p.a.d...
00cffb14 00780065 00000065 cccccccc cccccccc e.x.e...........
对于 64bit 应用
X64 应用中,调用约束采用的是 X64 的约束。也就是前四个参数会分别存在 ecx, edx, r8, r9 中。我们这里要找的是第二个参数,所以我们直接去看 edx(rdx) 就可以了(当然,这里断点需要断在栈帧首,避免被修改)
0:000> k L
# Child-SP RetAddr Call Site
00 000000ef`4073f768 00007ffc`728560c4 KERNELBASE!CreateProcessW
01 000000ef`4073f770 00007ff6`e3f91960 KERNEL32!CreateProcessWStub+0x54
02 000000ef`4073f7d0 00007ff6`e3f92419 CreateProcessWithCpp!main+0xe0
03 000000ef`4073f9f0 00007ff6`e3f922be CreateProcessWithCpp!invoke_main+0x39
04 000000ef`4073fa40 00007ff6`e3f9217e CreateProcessWithCpp!__scrt_common_main_seh+0x12e
05 000000ef`4073fab0 00007ff6`e3f924ae CreateProcessWithCpp!__scrt_common_main+0xe
06 000000ef`4073fae0 00007ffc`7285244d CreateProcessWithCpp!mainCRTStartup+0xe
07 000000ef`4073fb10 00007ffc`740cdf88 KERNEL32!BaseThreadInitThunk+0x1d
08 000000ef`4073fb40 00000000`00000000 ntdll!RtlUserThreadStart+0x28
0:000> r
rax=0000000000000000 rbx=0000000000000000 rcx=0000000000000000
rdx=000000ef4073f8e8 rsi=00007ff6e3f99d58 rdi=000000ef4073f900
rip=00007ffc71873290 rsp=000000ef4073f768 rbp=000000ef4073f820
r8=0000000000000000 r9=0000000000000000 r10=00007ffba21c0000
r11=000000ef4073f7c8 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0 nv up ei pl nz na pe nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
KERNELBASE!CreateProcessW:
00007ffc`71873290 4c8bdc mov r11,rsp
0:000> dc 000000ef4073f8e8 L8
000000ef`4073f8e8 006f006e 00650074 00610070 002e0064 n.o.t.e.p.a.d...
000000ef`4073f8f8 00780065 00000065 cccccccc cccccccc e.x.e...........
于是我们就找到了 notepad.exe 也就是第二个参数。
其他参数类似。
总结
要想看方法的参数:
- 通常情况下,可以通过 kp 直接查看到。但需要有符号且有参数信息。
- 对于参数个数在三个以内的,可以通过 kv 显示前三个参数。
- 对于多个参数的,只能手动通过 dp 去看 ebp/esp 所在地址,通过内存分布,手动推算。
- 对于 fastcall/x64 这种会通过寄存器来传参的,需要特别注意,避免寄存器被修改。
其他方式
总体下来,用 WinDbg 来查看参数还是相对复杂了些。还有些其他工具,用起来就会直观许多。
OllyDbg
OD 也是一款非常经典的 Debugger,因为它有比较好的 UI 交互,所以用来看函数参数值就相对比较简单。这里简单介绍下:
打开文件。(因为 OD 支持 X86,所以这里只用 X86 的执行文件做演示,X64 的还是乖乖的用 windbg 吧)
鼠标右键→search for→All intermodular calls→找到 KERNEL32.CreateProcessW 的调用。然后双击。
于是,就能看到一个这样的界面。可以看到 [LOCAL.7] 就是我们要找的 CommandLine(第二个参数)
然后我们把光标放在 KERNEL32.CreateProcessW 那一行,F4一下。看看右下角的栈:
于是,我们就很快的看出来第二个参数的值。
附录
- CreateProcessWithCpp.cpp
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
void main(int argc, TCHAR* argv[])
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
ZeroMemory(&pi, sizeof(pi));
si.cb = sizeof(si);
wchar_t cmd[] = L"notepad.exe";
if (!CreateProcess(NULL, cmd, NULL, NULL, false, 0, NULL, NULL, &si, &pi))
{
printf("CreateProcess failed (%d).\n", GetLastError());
return;
}
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
CreateProcessW
定义:https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw
如何通过WinDbg获取方法参数值的更多相关文章
- js获取url参数值的方法
index.htm?参数1=数值1&参数2=数值2&参数3=数据3&参数4=数值4&...... 静态html文件js读取url参数 根据获取html的参数值控制htm ...
- ASP.NET Core MVC中的IActionFilter.OnActionExecuting方法,可以获取Controller的Action方法参数值
用过ASP.NET Core MVC中IActionFilter拦截器的开发人员,都知道这是一个非常强大的MVC拦截器.最近才发现IActionFilter的OnActionExecuting方法,甚 ...
- js获取url参数值的方法总结
1.方式一:通过字符串截取的方式获取参数值: 1).函数一:获取URL中的参数名及参数值的集合 /** * [获取URL中的参数名及参数值的集合] * 示例URL:http://htmlJsTest/ ...
- js获取url参数值的两种方式
js获取url参数值的方法有很多,下面也为大家介绍两种. 方法一:正则分析法 function getQueryString(name) { var reg = new RegExp(" ...
- c#图像处理入门(-bitmap类和图像像素值获取方法)
c#图像处理入门 -bitmap类和图像像素值获取方法 一.Bitmap类 Bitmap对象封装了GDI+中的一个位图,此位图由图形图像及其属性的像素数据组成.因此Bitmap是用于处理由像素数据定义 ...
- C#获取URL参数值
原文:C#获取URL参数值 在写程序的时候,我们经常需要对页面进行传参数,比如page?id=1234,那么在page这个页面中就直接可以使用string id = Request.QueryStri ...
- js获取url参数值的几种方式
一.原生js获取URL参数值: 比如当前URL为:http://localhost:8080/#/page2?id=100&name=guanxy <template> <d ...
- monkeyrunner之坐标或控件ID获取方法-续
在之前的文章中,介绍过控件坐标和ID的获取方法,这里,我们再介绍一个新的工具-uiautomatorviewer. Uiautomatorviewer是Android sdk自带的工具,位置在sdk/ ...
- AspectJ获取方法注解的信息
在使用Aspectj获取方法注解信息的时候,可以使用下面的代码片段: /** * Get value of annotated method parameter */ private <T ex ...
随机推荐
- JavaWeb学习day2-web入门&随笔
Tomcat详解: 1默认端口号: Tomcat:8080 Mysql:3306 http:80 https:443 2默认主机名:localhost 地址:127.0.0.1 3网站应用默认存放位置 ...
- uniapp-h5之canvans上文本的展示
ctx.font = 'bold 14px arial';ctx.fillStyle = '#e9e6e6';ctx.fillText('长按图片保存到相册', (this.pwidth -250/e ...
- 生成二维码,并且保存,指定位置的view成图片,并且保存到本地相册
效果图: 保存的图片效果是: 保存到本地的,是整个视图,不只是单单的二维码的图片, 在了解的一番过程之后,我知道了, 1.首先要去获取保存图片的写入权限:(使用 https://github.com/ ...
- Mybaitis入门基础(一)MyBatis的概念引入及工作原理
阅读目录 一:对原生态JDBC问题的总结 二:MyBatis框架 三:mybatis入门程序 四:mybatis和Hibernate的本质区别与应用场景 五:小结 一:框架前言的那些事 良将难求 胜铁 ...
- js字符串操作方法集合
1.字符方法: str.charAt(): 可以访问字符串中特定的字符,可以接受0至字符串长度-1的数字作为参数,返回该位置下的字符,如果参数超出该范围,返回空字符串,如果没有参数,返回位置为0的字符 ...
- C/C++游戏项目:中国程序员一定要会的中国象棋教程
中国象棋是中国一种流传十分广泛的游戏. 下棋双方根据自己对棋局形式的理解和对棋艺规律的掌握,调动车马,组织兵力,协调作战在棋盘这块特定的战场上进行着象征性的军事战斗. 象棋,亦作"象碁&qu ...
- 劳动节快乐!手写个核心价值观编码工具 - Python实现
前言 今天是五一劳动节,祝各位无产阶级劳动者节日快乐! 然后来整活分享一些有趣的东西~ 这个小工具是我大学时做着玩的,对于各位接班人来说,12个词的核心价值观这东西,大家都非常熟悉了,这工具可以实现将 ...
- 关于VR(虚拟现实)的探讨
从外部来看:一个完整的系统由输入和输出组成,人体也不例外.人的输入系统一般称为感官系统,主要由口耳眼鼻舌和皮肤组成,它们对应于味觉.听觉.视觉.嗅觉和触觉.生而为人,我们对于外部世界的感知主要来自于上 ...
- CVE-2021-35042
CVE-2021-35042 漏洞介绍 Django 是 Python 语言驱动的一个开源模型-视图-控制器(MVC)风格的 Web 应用程序框架. 漏洞影响版本:django 3.1.3.2 202 ...
- C#/VB.NET 实现Word和ODT文档相互转换
ODT文档格式一种开放文档格式(OpenDocument Text).通常,ODT格式的文件可以使用LibreOffice Writer.MS Word或其他一些文档编辑器来打开.我们在处理文档时,可 ...