核心概念
在 WinDbg 中,查看函数参数的关键在于理解函数调用在 x86 和 x64 架构下的不同约定。

-
x86 (32位) 架构:
- 栈帧: 函数参数通过
push指令压入栈中,然后调用函数,函数返回地址也压入栈。 - 查看位置: 假设函数有 3 个参数,它们相对于栈帧指针 (
EBP) 的位置是固定的:[EBP+4]: 返回地址[EBP+8]: 第一个参数[EBP+C]: 第二个参数[EBP+10]: 第三个参数
- 寄存器: 前几个整数/指针参数也可能通过寄存器传递(如
ECX,EDX)。
- 栈帧: 函数参数通过
-
x64 (64位) 架构):
- 寄存器优先: 前 4 个整数/指针参数通过寄存器传递,效率更高。
RCX: 第一个参数RDX: 第二个参数R8: 第三个参数R9: 第四个参数
- 栈补充: 如果参数超过 4 个,多余的参数才会通过栈传递。
- 查看位置: 对于通过栈传递的参数,它们位于
[RSP+0x28]之后的位置。
- 寄存器优先: 前 4 个整数/指针参数通过寄存器传递,效率更高。
使用 k 命令(最常用、最直接)
k (kb, kn) 命令是查看调用堆栈的利器,WinDbg 能够根据调试符号(PDB)自动解析并显示每个函数的参数值。
步骤:

-
触发断点: 在函数入口或你感兴趣的代码行设置断点。
// 在 MyFunction 函数入口断住 bp MyFunction g // 继续执行,直到断点命中
-
查看调用堆栈: 当断点命中后,使用
k命令。k
示例分析:
x64 示例

假设我们有如下 C++ 代码:
void MyFunction(int a, int b, int c, int d, int e) {
// 函数体
}
void Caller() {
MyFunction(10, 20, 30, 40, 50);
}
在 MyFunction 的入口断住后,执行 k 命令:
0:000> k # Child-SP RetAddr Call Site 00 fffffa80`00001be8 fffff80e`xxxxxxxx MyFunction+0x0 01 fffffa80`00001bf0 fffff80e`xxxxxxxx Caller+0x20 02 fffffa80`00001bf8 fffff80e`xxxxxxxx some_other_function+... ...
看起来 k 命令没有直接显示参数,这是因为你需要一个更详细的 k 命令变体。
// 使用 kb 命令,它会显示参数
0:000> kb
# Child-SP RetAddr Call Site
00 fffffa80`00001be8 fffff80e`xxxxxxxx MyFunction+0x0
[a] 0000000c
[b] 00000014
[c] 0000001e
[d] 00000028
[e] 00000032
01 fffffa80`00001bf0 fffff80e`xxxxxxxx Caller+0x20
02 fffffa80`00001bf8 fffff80e`xxxxxxxx some_other_function+...
...
解释:
kb命令会尝试解析当前栈帧(MyFunction)的参数。[a] 0000000c: 这是第一个参数a的值(十进制是 12,但传进来的是 0xC),WinDbg 显示的是十六进制。[b] 00000014: 第二个参数b的值(十进制 20)。- 以此类推,前 4 个参数来自寄存器,第 5 个参数
e来自栈。
x86 示例
同样的代码在 32 位环境下:
void MyFunction(int a, int b) {
// ...
}
void Caller() {
MyFunction(100, 200);
}
在 MyFunction 入口断住,执行 kb:
0:001> kb
# Child-SP RetAddr Call Site
0012ff88 0040101a MyFunction+0x0
[a] 00000064
[b] 000000c8
0012ff8c 0040100d Caller+0xd
...
解释:
[a] 00000064: 第一个参数a的值(十进制 100)。[b] 000000c8: 第二个参数b的值(十进制 200)。- 在 x86 中,参数值直接显示在
kb的输出中。
手动检查寄存器和栈(适用于无符号或符号损坏时)
当 kb 无法显示参数(没有 PDB 符号,或符号已损坏)时,你需要手动查看。
x64 手动查看步骤:
-
查看寄存器: 前 4 个参数在寄存器里。
0:000> r rcx, rdx, r8, r9 rcx=0000000c 0000000c rdx=00000014 00000014 r8=0000001e 0000001e r9=00000028 00000028
这和
kb的输出结果一致。 -
查看栈: 第 5 个及以后的参数在栈上。
// RSP 是当前栈顶,参数通常在 RSP+0x28 之后 0:000> dq rsp L5 fffffa80`00001be8 00000000`00000032 fffffa80`00001bf0 <-- 第5个参数 e=0x32 fffffa80`00001bf0 fffff80e`xxxxxxxx fffffa80`00001bf8 <-- 返回地址 ...
x86 手动查看步骤:
-
查看栈: 参数相对于
EBP的位置是固定的。// 假设 EBP 指向当前栈帧 0:001> r ebp ebp=0012ff88 // 参数在 EBP+8 和 EBP+C 0:001> dd ebp+8 L2 0012ff88 0040101a 00000064 <-- 返回地址 0x0040101a, 参数 a=0x64 0012ff8c 0040100d 000000c8 <-- 参数 b=0xc8
dd是以双字(32位)格式查看内存。L2表示显示 2 个双字。
使用 dt 命令查看结构体/类参数
如果函数的参数是一个结构体或 C++ 对象,kb 可能只显示其地址,你可以使用 dt (Display Type) 命令来查看该地址处的具体内容。
示例:
struct MY_STRUCT {
int x;
int y;
};
void ProcessData(MY_STRUCT* pData) {
// ...
}
在 ProcessData 入口断住:
0:000> kb
# Child-SP RetAddr Call Site
00 fffffa80`00001be8 fffff80e`xxxxxxxx ProcessData+0x0
[pData] fffffa80`00001234 <-- 参数是一个指针
01 fffffa80`00001bf0 fffff80e`xxxxxxxx Caller+0x20
...
kb 只告诉我们 pData 的地址是 fffffa8000001234,现在用 dt 查看这个地址的内容:
0:000> dt MY_STRUCT fffffa80`00001234 MY_STRUCT +0x000 x : 0x1 +0x004 y : 0x2
这样你就能看到结构体内部的值了。
使用 uf 命令反汇编并查看
如果你想看到函数调用指令本身,uf (Unassemble Function) 是个好工具,它能显示函数的完整汇编代码,包括调用者是如何传递参数的。
示例:
// 反汇编 Caller 函数 0:000> uf Caller ... 0040100c 8b442410 mov eax,dword ptr [esp+10h] // 获取参数 e 00401010 8b4c2414 mov ecx,dword ptr [esp+14h] // 获取参数 d 00401014 51 push ecx // 将参数 d 压栈 00401015 50 push eax // 将参数 e 压栈 00401016 6a1e push 1eh // 将参数 c 压栈 (0x1e = 30) 00401018 6a14 push 14h // 将参数 b 压栈 (0x14 = 20) 0040101a 6a0c push 0ch // 将参数 a 压栈 (0x0c = 10) 0040101c e8xxxxxxxx call MyFunction // 调用 MyFunction ...
通过这个输出,你可以清晰地看到:
push 0ch: 第一个参数a(10) 被压入栈。push 14h: 第二个参数b(20) 被压入栈。- ...以此类推。
call MyFunction: 执行函数调用。
总结与最佳实践
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
kb / k |
最简单、最直接,自动解析符号 | 依赖 PDB 符号,符号损坏时无效 | 日常调试的首选,90% 的情况都用这个。 |
| 手动检查 | 不依赖符号,最可靠 | 需要手动计算地址,繁琐 | 符号缺失、损坏或想深入理解调用机制时。 |
dt |
能查看结构体/对象的内部细节 | 需要知道类型定义 | 函数参数是复杂结构体时。 |
uf |
能看到汇编层面的调用细节 | 信息量可能过大,不够直观 | 想要精确分析参数传递过程或优化代码时。 |
工作流程建议:
- 首先尝试
kb,这是最快、最干净的方法。 kb输出不理想(只显示地址),检查符号是否已加载(使用.sym命令)。- 如果符号确实有问题或不存在,切换到手动检查方法,根据是 x64 还是 x86 查看
RCX/RDX/R8/R9或[EBP+8]等位置。 - 如果参数是结构体/对象,使用
dt来展开其内容。 - 如果需要底层细节,使用
uf来反汇编调用者代码。
掌握这些方法,你就能在 WinDbg 中游刃有余地查看和分析函数参数了。
