一
前言
二
线程栈的关键特性
push
一个值的时候,栈底指针esp
的值会减小。A
调用了B
,B
又调用了C
,C
又调用了D
。那么B
返回到A
的地址在线程栈的高处,C
返回到B
的地址在线程栈的低处,D
返回到C
的地址在线程栈的最低处。三
查看方法
.kframes
设置默认显示的栈帧数量dps
,自己识别调用栈k
命令的时候指定StackPtr
0x70800000
(大概1.75 GB
)。#include <iostream> void Recursive(int depth)
{
if (depth > 0)
{
Recursive(--depth);
}
}void CallRecursive()
{
Recursive(0x7fffffff);
}int main()
{
CallRecursive();
}
四
方法1:使用.kframes设置默认显示的栈帧数量
windbg
的帮助文档中发现可以通过.kframes
命令来设置k
命令默认显示的栈帧数量。但是也不是可以显示无限多个栈帧。.kframes
可以设置的最大栈帧数是多少呢?通过几次尝试,我发现.kframes
可以接受的最大值是32
位的带符号整数的最大值,也就是0x7fffffff
(对应的十进制是2147483647
)。.kframes
命令把栈帧数设置为0x7fffffff
后,再执行k
命令,发现windbg
会直接提示内存分配失败。0x1000000
,再执行k
命令,发现windbg
的内存占用非常高,高峰期大概消耗了20GB
的物理内存(下图中的Working Set
列),经过将近两分钟的努力,最终还是以内存分配失败告终。24GB
物理内存)设置为0x1000000
时可以输出结果,但是因为数据量太大了,等了半个多小时也没执行完。.kframes
没能成功,但这绝对是一个非常值得了解的命令,可以处理绝大多数情况下的线程栈查看问题。k
命令默认显示效果(仅限当前调试会话,windbg
重启后会自动失效)k
命令可能会非常非常非常慢(对于示例程序,半个小时还没执行完)五
方法2:使用.kframes设置默认显示的栈帧数量
windbg
中可以通过dps
以指针长度为单位打印出指定内存范围的值,同时会输出匹配的符号。!teb
指令找到栈顶(StackBase
)的位置,然后减去一定的值(比如64kb
)得到一个较低的地址A
。dps A StackBase
。如果输出结果中没有包含感兴趣的函数,可以减去一个更大的值,再次执行dps
并查看输出结果,直到输出结果中包含感兴趣的函数为止。dps
的输出内容手动识别调用栈。!teb
命令获取栈顶位置(0000002a33800000
)然后减去64KB
(0x10000
,也可以换成其它值,一般情况下64KB
足够了)得到地址0000002a337f0000
,然后执行dps 0000002a337f0000 0000002a33800000
。或者可以直接直接输入dps 0000002a33800000-0x10000 0000002a33800000
。dps
的结果可知,已经包含了关键的递归函数 ——TestDeepRecursive!Recursive
,可以根据此次dps
的输出结果手动识别调用栈。拉到输出结果的最下方,可以看到输出结果如下图:main
函数,CallRecursive
函数,Recursive
函数。而且与vs
中的调用栈完美匹配。说明:输出结果中极有可能包含很多无关的信息(比如上图中的黄色高亮部分),需要仔细甄别。
六
方法3:使用k命令的时候指定StackPtr
k
命令的时候指定正确的StackPtr
,windbg
会自动帮我们识别调用栈。StackPtr
(调试x64
程序时需要传递rsp
,调试x86
程序时需要传递ebp
,也叫BasePtr
),也可以同时指定要显示的栈帧数量。k
命令的帮助文档可以参考下图(截取自windbg
帮助文档):StackPtr
不对,那么输出结果很可能是错误的。比如,我使用一个错误的值执行k=0x0000002a337ff938
输出结果如下:StackPtr
是必须的。那么,该如何获取一个正确的StackPtr
呢?有两个方法:k
命令的时候,最左侧那一列就是rsp
(x86
程序对应着ebp
)。可以这样处理:先通过.kframes
设置一个相对合理的值,然后执行k
命令,等命令执行完,取最后一条输出结果的rsp
的值,假设是00000029c3004040
,然后执行k=00000029c3004040 3
,就可以继续显示后续的三条调用栈了。重复此过程即可。dps
的输出结果中“猜”一个ebp
或者rsp
的值。说是猜,其实是有规律的。rsp
的值是0x0000002a337ffbd0
。在windbg
中输入k=0x0000002a337ffbd0 0x100
,可以得到下图完美的调用栈:k
命令一样输出调用栈StackPtr
,不能随便指定x86/x64
程序的调用栈,这样才能比较快速准确的找到合法的StackPtr
dps
+
k=StackPtr [FrameCount]
是最高效,最优雅的解决方案。说明:如果知道了一个合法的 StackPtr
,也可以先通过r rsp = StackPtr
修改rsp
寄存器的值,然后再执行k
命令显示调用栈。但是这个方法有一个特别不好的地方,rsp
会被修改,后续用到rsp
寄存器的命令都会受影响。因此,不推荐使用。
七
总结
.kframes
可以设置默认显示的栈帧数,可以突破默认最多显示0xffff
个栈帧的限制,但是注意如果设置的值太大,会非常消耗内存;dps
可以按指针打印一系列的值,并且会显示匹配的符号。务必记住此命令,非常有用;k
命令时,可以指定StackPtr
来从指定位置开始显示调用栈;x86
的程序,ebp
保存了调用者的ebp
,ebp+4
的位置保存了返回地址。x64
的程序,rsp-8
的位置保存了返回地址。看雪ID:编程难
https://bbs.kanxue.com/user-home-873494.htm
# 往期推荐
2、在Windows平台使用VS2022的MSVC编译LLVM16
3、神挡杀神——揭开世界第一手游保护nProtect的神秘面纱
球分享
球点赞
球在看
点击阅读原文查看更多