从 C# 崩溃异常 中研究页堆布局
一:背景
1.讲故事
最近遇到一位朋友的程序崩溃,发现崩溃点在富编辑器 msftedit
上,这个不是重点,重点在于发现他已经开启了 页堆
,看样子是做了最后的挣扎。
0:000> !analyze -v
EXCEPTION_RECORD: (.exr -1)
ExceptionAddress: 82779a9e (msftedit!CCallMgrCenter::SendAllNotifications+0x00000123)
ExceptionCode: c0000005 (Access violation)
ExceptionFlags: 00000000
NumberParameters: 2
Parameter[0]: 00000001
Parameter[1]: 8351af28
Attempt to write to address 8351af28
...
STACK_TEXT:
00ffe0dc 827bda2a 8351ae88 00000000 00ffe174 msftedit!CCallMgrCenter::SendAllNotifications+0x123
00ffe110 827bd731 00ffe324 00ffe174 00ffe300 msftedit!CCallMgrCenter::ExitContext+0xda
00ffe120 827bde71 8351ae88 827232dc 28112f80 msftedit!CCallMgr::~CCallMgr+0x17
00ffe300 8290281f 00000102 00000067 00220001 msftedit!CTxtEdit::TxSendMessage+0x201
00ffe374 7576110b 00f20268 00000102 00000067 msftedit!RichEditWndProc+0x9cf
00ffe3a0 757580ca 82901e50 00f20268 00000102 user32!_InternalCallWinProc+0x2b
...
SYMBOL_NAME: system_windows_forms+1c45e7
MODULE_NAME: System_Windows_Forms
IMAGE_NAME: System.Windows.Forms.dll
0:000> !heap -p
Active GlobalFlag bits:
vrf - Enable application verifier
hpa - Place heap allocations at ends of pages
StackTraceDataBase @ 04c20000 of size 01000000 with 00001b18 traces
PageHeap enabled with options:
ENABLE_PAGE_HEAP
COLLECT_STACK_TRACES
active heaps:
+ 5c20000
ENABLE_PAGE_HEAP COLLECT_STACK_TRACES
NormalHeap - 5d90000
HEAP_GROWABLE
+ 5e90000
ENABLE_PAGE_HEAP COLLECT_STACK_TRACES
NormalHeap - 4960000
HEAP_GROWABLE HEAP_CLASS_1
...
由于 页堆
和 NT堆
的内存布局完全不一样,这一篇结合我的了解以及 windbg 验证来系统的介绍下 页堆
。
二:对 页堆 的研究
1. 案例演示
为了方便讲述,先上一段测试代码。
int main()
{
HANDLE h = HeapCreate(NULL, 0, 100);
int* ptr = (int*)HeapAlloc(h, 0, 9);
printf("ptr= %x", ptr);
DebugBreak();
}
接下来用 gflags
开启下页堆。
PS C:\Users\Administrator\Desktop> gflags -i ConsoleApplication1.exe +hpa
Current Registry Settings for ConsoleApplication1.exe executable are: 02000000
hpa - Enable page heap
然后将程序跑起来,可以看到返回的 handle 句柄。
2. 页堆布局研究
接下来用 windbg 的 !heap -p
命令观察页堆。
0:000> !heap -p
Active GlobalFlag bits:
hpa - Place heap allocations at ends of pages
StackTraceDataBase @ 042e0000 of size 01000000 with 0000000e traces
PageHeap enabled with options:
ENABLE_PAGE_HEAP
COLLECT_STACK_TRACES
active heaps:
+ 5b0000
ENABLE_PAGE_HEAP COLLECT_STACK_TRACES
NormalHeap - 710000
HEAP_GROWABLE
+ 810000
ENABLE_PAGE_HEAP COLLECT_STACK_TRACES
NormalHeap - 510000
HEAP_GROWABLE HEAP_CLASS_1
+ 56e0000
ENABLE_PAGE_HEAP COLLECT_STACK_TRACES
NormalHeap - 5aa0000
HEAP_CLASS_1
稍微解读下上面的输出。
+ 56e0000**
表示 页堆 的堆句柄。
NormalHeap - 5aa0000
表示 页堆
关联的 NT堆
,可能有朋友要问了,既然都开启页堆
了, 还要弄一个 ntheap 干嘛? 大家不要忘了,windows 的一些系统api会用到这个堆。
接下来有一个问题,如何观察这两个 heap 之间的关联关系呢? 要回答这个问题,需要了解 页堆
的布局结构,画个简图如下:
从图中可以看到,离句柄偏移 4k
的位置有一个 DPH_HEAP_ROOT
结构,它相当于 NTHEAP 的_HEAP
,我们拿 56e0000
举个例子。
0:000> dt nt!_DPH_HEAP_ROOT 56e0000+0x1000
ntdll!_DPH_HEAP_ROOT
...
+0x0b4 NormalHeap : 0x05aa0000 Void
+0x0b8 CreateStackTrace : 0x042f4d94 _RTL_TRACE_BLOCK
+0x0bc FirstThread : (null)
上面输出的 NormalHeap: 0x05aa0000
就是它关联的 ntheap 句柄。
3. 堆块布局研究
对页堆
有了一个整体认识,接下来继续研究堆块句柄,我们发现 ptr=0x56e5ff0
是落在 56e0000
这个页堆上,接下来我们导出这个页堆的详细信息。
0:000> !heap -p -h 56e0000
_DPH_HEAP_ROOT @ 56e1000
Freed and decommitted blocks
DPH_HEAP_BLOCK : VirtAddr VirtSize
Busy allocations
DPH_HEAP_BLOCK : UserAddr UserSize - VirtAddr VirtSize
056e1f70 : 056e5ff0 00000009 - 056e5000 00002000
unknown!fillpattern
_HEAP @ 5aa0000
No FrontEnd
_HEAP_SEGMENT @ 5aa0000
CommittedRange @ 5aa04a8
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
05aa04a8 0167 0000 [00] 05aa04b0 00b30 - (free)
* 05aa0fe0 0004 0167 [00] 05aa0fe8 00018 - (busy)
VirtualAllocdBlocks @ 5aa009c
上面的信息如何解读呢?我们逐一来聊一下吧。
_DPH_HEAP_ROOT @ 56e1000
这个已经和大家聊过了,它和 _HEAP
结构是一致的。
DPH_HEAP_BLOCK :
从字面意思就能看出来和 ntheap
的 heap_entry
是一致的,都是用来描述堆块信息, 不过有一点要注意,这个堆块是落在上图中的 DPH_HEAP_BLOCK Pool
池链表结构中的,言外之意就是它不会作为 heap_entry
的头部附加信息,接下来我们 dt 导出来看看。
0:000> dt ntdll!_DPH_HEAP_BLOCK 056e1f70
+0x000 pNextAlloc : 0x056e1020 _DPH_HEAP_BLOCK
+0x000 AvailableEntry : _LIST_ENTRY [ 0x56e1020 - 0x0 ]
+0x000 TableLinks : _RTL_BALANCED_LINKS
+0x010 pUserAllocation : 0x056e5ff0 "???"
+0x014 pVirtualBlock : 0x056e5000 "???"
+0x018 nVirtualBlockSize : 0x2000
+0x01c nVirtualAccessSize : 0x20
+0x020 nUserRequestedSize : 9
+0x024 nUserActualSize : 0x56e1f60
+0x028 UserValue : 0x056e1fc8 Void
+0x02c UserFlags : 0x3f18
+0x030 StackTrace : 0x042f4dcc _RTL_TRACE_BLOCK
+0x034 AdjacencyEntry : _LIST_ENTRY [ 0x56e1010 - 0x56e1010 ]
+0x03c pVirtualRegion : (null)
从字段信息看,它记录了堆块的分配首地址,栈信息等等,比如用 dds 观察一下 StackTrace。
0:000> dds 0x042f4dcc
042f4dcc 00000000
042f4dd0 00006001
042f4dd4 000d0000
042f4dd8 78aba8b0 verifier!AVrfDebugPageHeapAllocate+0x240
042f4ddc 77e0ef8e ntdll!RtlDebugAllocateHeap+0x39
042f4de0 77d76150 ntdll!RtlpAllocateHeap+0xf0
042f4de4 77d757fe ntdll!RtlpAllocateHeapInternal+0x3ee
042f4de8 77d753fe ntdll!RtlAllocateHeap+0x3e
042f4dec 00ad1690 ConsoleApplication1!main+0x30 [D:\net6\ConsoleApp1\ConsoleApplication1\DisplayGreeting.cpp @ 14]
042f4df0 00ad1bc3 ConsoleApplication1!invoke_main+0x33 [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 78]
042f4df4 00ad1a17 ConsoleApplication1!__scrt_common_main_seh+0x157 [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 288]
042f4df8 00ad18ad ConsoleApplication1!__scrt_common_main+0xd [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 331]
042f4dfc 00ad1c48 ConsoleApplication1!mainCRTStartup+0x8 [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_main.cpp @ 17]
042f4e00 7646fa29 KERNEL32!BaseThreadInitThunk+0x19
042f4e04 77d975f4 ntdll!__RtlUserThreadStart+0x2f
042f4e08 77d975c4 ntdll!_RtlUserThreadStart+0x1b
...
接下来再回答一个问题,页堆的堆块有没有头部附加信息呢?当然是有的,叫做 DPH_BLOCK_INFORMATION
,即在 UserPtr-0x20
的位置,我们可以用 dt 显示一下。
0:000> ?? sizeof(ntdll!_DPH_BLOCK_INFORMATION)
unsigned int 0x20
0:000> dt ntdll!_DPH_BLOCK_INFORMATION 056e5ff0-0x20
+0x000 StartStamp : 0xabcdbbbb
+0x004 Heap : 0x056e1000 Void
+0x008 RequestedSize : 9
+0x00c ActualSize : 0x1000
+0x010 FreeQueue : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x010 FreePushList : _SINGLE_LIST_ENTRY
+0x010 TraceIndex : 0
+0x018 StackTrace : 0x042f4dcc Void
+0x01c EndStamp : 0xdcbabbbb
...
根据上面两个输出,在脑海中应该可以绘出如下图:
这里要稍微解释下 栅栏页
的概念。
4. 栅栏页
每一个 heap_entry 都会占用 8k 的空间,第一个 4k 是用户区,第二个 4k 是栅栏区,为了就是当代码越界时访问了这个 栅栏页 会立即报错,因为栅栏页是禁止访问的,我们可以提取 UserAddr
附近的内存,看看 056e6000= 056e5000+0x1000
后面是不是都是问号。
0:000> dp 056e5ff0
056e5ff0 c0c0c0c0 c0c0c0c0 d0d0d0c0 d0d0d0d0
056e6000 ???????? ???????? ???????? ????????
056e6010 ???????? ???????? ???????? ????????
056e6020 ???????? ???????? ???????? ????????
056e6030 ???????? ???????? ???????? ????????
056e6040 ???????? ???????? ???????? ????????
056e6050 ???????? ???????? ???????? ????????
056e6060 ???????? ???????? ???????? ????????
0:000> !address 056e5000+0x1000
Usage: PageHeap
Base Address: 056e6000
End Address: 057e0000
Region Size: 000fa000 (1000.000 kB)
State: 00002000 MEM_RESERVE
Protect: <info not present at the target>
Type: 00020000 MEM_PRIVATE
Allocation Base: 056e0000
Allocation Protect: 00000001 PAGE_NOACCESS
More info: !heap -p 0x56e1000
More info: !heap -p -a 0x56e6000
Content source: 0 (invalid), length: fa000
三:总结
这就是对 页堆
的一个研究,总的来说 页堆
是一种专用于调试的堆,优缺点如下:
- 优点:
因为 栅栏页 紧邻 用户页,一旦代码越界进入了 栅栏页,会立即报 访问违例 异常,这样我们就可以获取第一现场错误。
- 缺点:
对空间造成了巨大浪费,即使 1byte 的内存分配,也需要至少 2 个内存页 的内存占用 (8k)。
哈哈,对调试程序崩溃类问题,非常值得一试!
从 C# 崩溃异常 中研究页堆布局的更多相关文章
- QT中使用google breakpad捕获程序崩溃异常
今天给大家介绍一个在linux下如何捕获程序崩溃异常的方法 一.google breakpad源码的下载和编译 1.https://github.com/google/breakpad.git,源码地 ...
- 【转】Android 中处理崩溃异常并重启程序出现页面重叠的问题
原文地址:http://blog.csdn.net/jiang547860818/article/details/53641113 android开发中经常会遇到程序异常,而已常常会遇到一出现异常AP ...
- Android中处理崩溃异常和记录日志(转)
现在安装Android系统的手机版本和设备千差万别,在模拟器上运行良好的程序安装到某款手机上说不定就出现崩溃的现象,开发者个人不可能购买所有设备逐个调试,所以在程序发布出去之后,如果出现了崩溃现象,开 ...
- 关于c语言中栈和堆释放的问题
#include<iostream> #include<string> using namespace std; int main() { string st; cin> ...
- linux 中的页缓存和文件 IO
本文所述是针对 linux 引入了虚拟内存管理机制以后所涉及的知识点.linux 中页缓存的本质就是对于磁盘中的部分数据在内存中保留一定的副本,使得应用程序能够快速的读取到磁盘中相应的数据,并实现不同 ...
- C++异常中的堆栈跟踪
C++语言的运行时环境是基于栈的环境,堆栈跟踪(trace stack)就是程序运行时能够跟踪并打印所调用的函数.变量及返回地址等,C++异常中的堆栈跟踪就是当程序抛出异常时,能够把导致抛出异常的语句 ...
- Android开发之处理崩溃异常
众所周知,android的设备千差万别,难免会发生崩溃异常等现象,这个时候就需要捕获哪些崩溃异常了,也就是捕获崩溃异常的相关信息,并记录下来,这样一来方便开发人员和测试人员的分析与调试. 1.首先我们 ...
- Android程序崩溃异常收集框架
最近在写Android程序崩溃异常处理,完成之后,稍加封装与大家分享. 我的思路是这样的,在程序崩溃之后,将异常信息保存到一个日志文件中,然后对该文件进行处理,比如发送到邮箱,或发送到服务器. 所以, ...
- Android application捕获崩溃异常
Java代码 .收集所有 avtivity 用于彻底退出应用 .捕获崩溃异常,保存错误日志,并重启应用 , intent, , restartIntent); // 关闭当前应用 finishAllA ...
随机推荐
- AtCoder Beginner Contest 249 F - Ignore Operations // 贪心 + 大根堆
传送门:F - Keep Connect (atcoder.jp) 题意: 给定长度为N的操作(ti,yi). 给定初值为0的x,对其进行操作:当t为1时,将x替换为y:当t为2时,将x加上y. 最多 ...
- 在 macOS 上搭建 Flutter 开发环境
下载 Flutter SDK flutter官网下载:https://flutter.io/sdk-archive/#macos 若上述链接无法访问,可通过GitHub下载 https://githu ...
- Web Worker: Shared Worker的使用
Web Worker: Shared Worker的使用 参考资料: JavaScript高级程序第四版 https://juejin.cn/post/7064486575916187656 http ...
- 异常分类和异常的产生过程解析和Objects非空判断
java.lang.Throwable类是java语言中所有错误的异常的超类. Exception:编译期异常,进行编译(写代码)java程序出现的问题 RuntimeExeption:运行期异常,j ...
- CMake库搜索函数居然不搜索LD_LIBRARY_PATH
摘要: 本文通过编译后运行找不到库文件的问题引入,首先分析了find_package(JNI)的工作流程,而后针对cmake不搜索LD_LIBRARY_PATH的问题,提出了一种通用的解决办法. 本文 ...
- YII类的映射表机制
<?php /** * Created by PhpStorm. * Date: 2016/5/25 * Time: 19:09 * * YII的类的映射表 */ namespace front ...
- win10系统下把玩折腾DockerToolBox以及更换国内镜像源(各种神坑)
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_149 2020年,这年头如果出去面试和面试官不聊几句Docker,都不好意思说自己是搞开发的.之前玩儿Docker都是在Mac系统 ...
- Lua 语言
# Lua是一种轻量.小巧的脚本语言,用标准C语言编写并以源码形式开发.设计的摸底是为了嵌入到其他应用程序中,从而为应用程序提供灵活的拓展和定制功能. # Lua安装 # 官网:https://www ...
- 重构、插件化、性能提升 20 倍,Apache DolphinScheduler 2.0 alpha 发布亮点太多!
点击上方 蓝字关注我们 社区的小伙伴们,好消息!经过 100 多位社区贡献者近 10 个月的共同努力,我们很高兴地宣布 Apache DolphinScheduler 2.0 alpha 发布.这是 ...
- PySpark 大数据处理
本文主要介绍Spark的一些基本算子,PySpark及Spark SQL 的使用方法. 虽然我从2014年就开始接触Spark,但几年来一直没有真正地学以致用,时间一久便忘了如何使用,直到在工作中用到 ...