今天调试一个bug,用pageheap解决,在此记录一下。
1:不确定性崩溃,用vs调试启动每次崩溃地址都在crt分配或者释放堆的地方
2:崩溃时vs看到的调用栈可能不同
3:output输出HEAP: Free Heap block 388c58 modified at 388c88 after it was freed
依据vs的输出,确定问题是在一块堆上分配的内存在释放后被改写了。因为CRT只能在下次做堆操作检查时才会暴露出问题,所以程序崩溃的调用栈是不确定的。
折腾了2个小时后,启用pageheap缩小了程序出错到崩溃之间的距离,解决了问题。过程如下:
1:启动pageheap
pageheap /enable mybug.exe 0x01
2:调试启动mybug.exe
目前程序崩溃的调用栈每次都相同,并且都在相同的线程中,依据调用栈信息非常轻松的锁定了bug。
因为上面的例子过于复杂,下面写了一些微信小程序剖析了pageheap的原理
char* buffer = new char[19]; // 1
buffer[19] = 0;// 2
delete [] buffer;// 3
这是一个非常简单的堆内存越界的例子,在未启动pageheap的状况下,大家来看看buffer的内存状况:
buffer = 0x00388C80
第一行实行后,buffer的内存
0x00388C80 cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd................
0x00388C90 cd cd cd fd fd fd fd ab ab ab ab ab ab ab ab fe ................
简单说明一下,调试模式下堆上未初始化的内存为cd,并且在内存结束处有4个fd的边界,用于debug模式下crt做内存检查,实行第2行之后,buffer的内存为
0x00388C80 cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd ................
0x00388C90 cd cd cd 00 fd fd fd ab ab ab ab ab ab ab ab fe ................
可以看到4个fd的内存边界中第一个fd被破坏了。但这时程序并没崩溃,继续实行第3行,程序崩溃,提示堆错误,可以看到,假如第2行和第3行之间有非常长的代码逻辑,那样也只能在第3行实行之后程序才会崩溃。这给调式程序带来了很大的不便。
假如第2行改为:buffer[24] = 0 程序同样不会崩溃
假如启用了pageheap,再来看看在debug模式下buffer的内存分配状况:
第一行分配内存后,buffer的内存状况:
0x01675FE8 cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd ................
0x01675FF8 cd cd cd fd fd fd fd d0 ?? ?? ?? ?? ?? ?? ?? ?? ................
可以看到,和上面一样,在内存结束加上了4个fd的边界,d0是用于填补4字节对齐,注意buffer后面的地址为0x01675FF8+8 = 0x01676000,这是一个4k对齐的PAGE|NOACCESS页面,这时大家实行第2行代码
buffer[19] = 0; 同样不会崩溃,即便是修改buffer[19|23]的值,和未启动pageheap一样,程序都只能在实行第3行的时候崩溃。假如修改buffer[24]则程序会崩溃。
通过这个例子,可以得出一个结论:启用pageheap后,堆内存分配在页面的末尾,后面紧跟了一个4k的PAGE|NOACCESS属性的页面,这样的情况下,启用pageheap有哪些好处是能在一定量上检查内存越界。
再来看一个例子
char* buffer = new char[20]; // 1
delete [] buffer;// 2
buffer[1] = 1;// 3
这个例子演示了操作delete释放后的内存,在未启动pageheap的状况下,程序不会崩溃,缘由同上一个例子,启用pageheap后,buffer内存为:
第一行实行后:
0x01675FE8 cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd ................
0x01675FF8 cd cd cd cd fd fd fd fd ?? ?? ?? ?? ?? ?? ?? ?? ................
第2行实行后:
0x01675FE8 ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ................
0x01675FF8 ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ................
可以看到,启用pageheap后delete内存,分配该内存的整个页面都被设置为PAGE|NOACCESS属性,如此操作delete后的任何内存程序立刻就会崩溃。
结论2:启用pageheap比较容易检查操作delete后的内存的错误(包含2次delete)
总结:
1:启用pageheap后,系统的堆管理器会把内存分配到4k页面的末尾(注意需要4字节对齐,debug模式下还存在边界检查的4字节fd)
2:紧伴随的下一个页面被设置为PAGE|NOACCESS属性
3:启用pageheap后,释放内存把整个页面设置为PAGE|NOACCESS属性
4:内存越界和非法操作依赖非法访问PAGE|NOACCESS属性的页面暴露问题
5:因为每块内存都至少需要2个页面(1个页面分配,1个页面PAGE|NOACCESS),在内存消耗较大的环境下会占用很大的内存资源。
6:把pageheap和crt的堆检查函数结合起来,可以更好的暴露堆有关bug
ps.pageheap有哪些用途是在注册表地方HKLM/SOFTWARE/Microsoft/Windows NT/CurrentVersion/Image File Execution Options下生成一个项
TAG标签:内存(2)
转载请说明来源于谷普下载站(https://www.muerya.com)
本文地址:https://www.muerya.com/soft/10737.html
郑重声明:文章来源于网络作为参考,本站仅用于分享不存储任何下载资源,如果网站中图片和文字侵犯了您的版权,请联系我们处理!邮箱3450399331@qq.com