参考演讲视频:
【C++】调试专题辅导_哔哩哔哩_bilibili --看了下日期是2015.8,时间真快,10年过去了。不过思想是不会过时的。
Windows 32 bit的程序,以winmine.exe为例,说明一个理念。
1. 内存,32bit,4G, 低2G为用户空间,高2G为内核空间
windbg打开winmine.exe
!address可以看到32bit程序的内存分布
0:009> !address
Mapping file section regions...
Mapping module regions...
Mapping PEB regions...
Mapping TEB and stack regions...
Mapping heap regions...
Mapping page heap regions...
Mapping other regions...
Mapping stack trace database regions...
Mapping activation context regions...
BaseAddr EndAddr+1 RgnSize Type State Protect Usage
-----------------------------------------------------------------------------------------------
+ 0 10000 10000 MEM_FREE PAGE_NOACCESS Free
+ 10000 11000 1000 MEM_IMAGE MEM_COMMIT PAGE_READONLY <unknown> [MZ..............]
11000 14000 3000 MEM_IMAGE MEM_COMMIT PAGE_EXECUTE_READ <unknown> [........q.VV....]
14000 15000 1000 MEM_IMAGE MEM_COMMIT PAGE_READONLY <unknown> [.P.......P......]
15000 16000 1000 MEM_IMAGE MEM_COMMIT PAGE_READWRITE <unknown> [.ML.4......p....]
16000 17000 1000 MEM_IMAGE MEM_COMMIT PAGE_READONLY <unknown> [.........E......]
17000 18000 1000 MEM_IMAGE MEM_COMMIT PAGE_EXECUTE_READ <unknown> [..p..3...A......]
18000 1a000 2000 MEM_IMAGE MEM_COMMIT PAGE_READONLY <unknown> [................]
+ 1a000 20000 6000 MEM_FREE PAGE_NOACCESS Free
+ 20000 23000 3000 MEM_MAPPED MEM_COMMIT PAGE_READONLY MappedFile "\Device\HarddiskVolume3\Windows\System32\l_intl.nls"
+ 23000 30000 d000 MEM_FREE PAGE_NOACCESS Free
+ 30000 40000 10000 MEM_MAPPED MEM_COMMIT PAGE_READWRITE MappedFile "PageFile"
+ 40000 5f000 1f000 MEM_MAPPED MEM_COMMIT PAGE_READONLY Other [API Set Map]
+ 5f000 60000 1000 MEM_FREE PAGE_NOACCESS Free
+ 60000 93000 33000 MEM_PRIVATE MEM_RESERVE <unknown>
93000 96000 3000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE|PAGE_GUARD <unknown>
96000 a0000 a000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE <unknown> [................]
+ a0000 d9000 39000 MEM_PRIVATE MEM_RESERVE Stack [~0; 87ec.127e4]
d9000 db000 2000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE|PAGE_GUARD Stack [~0; 87ec.127e4]
db000 e0000 5000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE Stack [~0; 87ec.127e4]
+ e0000 e4000 4000 MEM_MAPPED MEM_COMMIT PAGE_READONLY Other [System Default Activation Context Data]
+ e4000 f0000 c000 MEM_FREE PAGE_NOACCESS Free
+ f0000 f2000 2000 MEM_MAPPED MEM_COMMIT PAGE_READONLY Other [Activation Context Data]
+ f2000 100000 e000 MEM_FREE PAGE_NOACCESS Free
先说第一段:0-10000,000代表4K,10代表16,16页内存,PAGE_NOACCESS代表不可访问,这时操作系统为支持C++的内存0不可访问的定义,支持程序员的编程习惯,这段区域代表空指针。从上面的分布可以看到,不只是0时空指针,小于64K都是空指针。为什么?这时因为写代码经常定义结构体struct或者class,访问时访问结构的字段偏移,这样访问,struct头上为空,加上偏移就不是0了,也是不可访问。这个知识很重要。内存意外写时最难调试的BUG,这个机制就预防了空指针。这也告诉我们定义struct不要超过64K,容易把其他内容写掉。操作系统用心良苦,避免程序的低级错误。
还有栈空间:
栈上面有reserve和free,不放应用数据,这样就不容易被栈溢出踩掉。
+ a0000 d9000 39000 MEM_PRIVATE MEM_RESERVE Stack [~0; 87ec.127e4]
d9000 db000 2000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE|PAGE_GUARD Stack [~0; 87ec.127e4]
db000 e0000 5000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE Stack [~0; 87ec.127e4]
设计非常好,~0,表示0号线程的栈。后面是进程ID.线程ID,栈分为3个部分,PAGE_READWRITE是正在用的,PAGE_GUARD这时保护的,用完了碰到GUARD触发缺页异常,会增长栈grow。RESERVE都用完了,栈就会溢出。
dll注入,taobao.dll会注入,还会有些不靠谱的软件勾到winmine进程里面,我的机器上也有,就不说是谁了,以安全软件的名义勾进来。做什么不得而知。
winmine.exe的位置:我们VC编译的放到4000位置,微软写的放到10 00000,16M的位置。exe是不能重定位的,在vsdutio link的时候有个选项,可以改。
+ 1000000 1001000 1000 MEM_IMAGE MEM_COMMIT PAGE_READONLY Image [winmine; "winmine.exe"]
1001000 1005000 4000 MEM_IMAGE MEM_COMMIT PAGE_EXECUTE_READ Image [winmine; "winmine.exe"]
1005000 1006000 1000 MEM_IMAGE MEM_COMMIT PAGE_READWRITE Image [winmine; "winmine.exe"]
1006000 1020000 1a000 MEM_IMAGE MEM_COMMIT PAGE_READONLY Image [winmine; "winmine.exe"]
000:4K, 00000:1M 10加5个0,16M
观察堆:
如何看到堆的位置:!heap 有14个堆,不能debug
0:001> !heap
Failed to read heap keySEGMENT HEAP ERROR: failed to initialize the extention
Index Address Name Debugging options enabled
1: 00410000 tail checking free checking validate parameters
2: 00ba0000 tail checking free checking validate parameters
3: 00d00000 tail checking free checking validate parameters
4: 00dd0000 tail checking free checking validate parameters
5: 00fb0000 tail checking free checking validate parameters
6: 00f40000 tail checking free checking validate parameters
7: 02a20000 tail checking free checking validate parameters
8: 02ba0000 tail checking free checking validate parameters
9: 02d30000 tail checking free checking validate parameters
10: 03060000 tail checking free checking validate parameters
11: 03b00000 tail checking free checking validate parameters
12: 03c70000 tail checking free checking validate parameters
13: 03e20000 tail checking free checking validate parameters
14: 03bb0000 tail checking free checking validate parameters
向上搜索可以找到:
bac000 baf000 3000 MEM_PRIVATE MEM_RESERVE Heap [ID: 1; Handle: 00ba0000; Type: Segment]
查看其中一个堆:!heap 00ba0000 -A
heap中再细分segment, segment上busy表示占用了。
0:001> !heap 00ba0000 -A
Failed to read heap keySEGMENT HEAP ERROR: failed to initialize the extention
Index Address Name Debugging options enabled
2: 00ba0000
Segment at 00ba0000 to 00baf000 (0000c000 bytes committed)
Flags: 40001062
ForceFlags: 40000060
Granularity: 8 bytes
Segment Reserve: 00100000
Segment Commit: 00002000
DeCommit Block Thres: 00000200
DeCommit Total Thres: 00002000
Total Free Size: 00000a3b
Max. Allocation Size: 7ffdefff
Lock Variable at: 00ba0258
Next TagIndex: 0000
Maximum TagIndex: 0000
Tag Entries: 00000000
PsuedoTag Entries: 00000000
Virtual Alloc List: 00ba009c
Uncommitted ranges: 00ba008c
00bac000: 00003000 (12288 bytes)
FreeList[ 00 ] at 00ba00c0: 00ba7120 . 00ba5f68
00ba5f60: 00020 . 00010 [104] - free
00ba6c60: 00230 . 00018 [104] - free
00ba65a8: 00058 . 00018 [104] - free
00ba5be0: 00020 . 00018 [104] - free
00ba5cf8: 00028 . 00018 [104] - free
00ba3290: 00098 . 00018 [104] - free
00ba6cb0: 00038 . 00068 [104] - free
00ba6d78: 00028 . 00100 [104] - free
00ba68d8: 00028 . 00120 [104] - free
00ba7118: 00230 . 04ec8 [104] - free
Segment00 at 00ba0000:
Flags: 00000000
Base: 00ba0000
First Entry: 00ba04a8
Last Entry: 00baf000
Total Pages: 0000000f
Total UnCommit: 00000003
Largest UnCommit:00000000
UnCommitted Ranges: (1)
Heap entries for Segment00 in Heap 00ba0000
address: psize . size flags state (requested size)
00ba0000: 00000 . 004a8 [101] - busy (4a7)
00ba04a8: 004a8 . 00118 [107] - busy (117), tail fill Internal
00ba05c0: 00118 . 00230 [107] - busy (214), tail fill
00ba07f0: 00230 . 00498 [107] - busy (480), tail fill
00ba0c88: 00498 . 00070 [107] - busy (54), tail fill
00ba0cf8: 00070 . 00098 [107] - busy (80), tail fill
00ba0d90: 00098 . 00098 [107] - busy (80), tail fill
00ba0e28: 00098 . 00098 [107] - busy (80), tail fill
00ba0ec0: 00098 . 00038 [107] - busy (20), tail fill
00ba0ef8: 00038 . 00020 [107] - busy (4), tail fill
00ba0f18: 00020 . 00028 [107] - busy (10), tail fill
00ba0f40: 00028 . 00028 [107] - busy (10), tail fill
00ba0f68: 00028 . 00038 [107] - busy (1a), tail fill
00ba0fa0: 00038 . 00098 [107] - busy (80), tail fill
00ba1038: 00098 . 00098 [107] - busy (80), tail fill
00ba10d0: 00098 . 00230 [107] - busy (214), tail fill
00ba1300: 00230 . 00230 [107] - busy (214), tail fill
00ba1530: 00230 . 00048 [107] - busy (30), tail fill
00ba1578: 00048 . 00048 [107] - busy (30), tail fill
00ba15c0: 00048 . 00048 [107] - busy (30), tail fill
00ba1608: 00048 . 00048 [107] - busy (30), tail fill
00ba1650: 00048 . 00048 [107] - busy (30), tail fill
00ba1698: 00048 . 00048 [107] - busy (30), tail fill
00ba16e0: 00048 . 00028 [107] - busy (c), tail fill
00ba1708: 00028 . 00028 [107] - busy (c), tail fill
00ba1730: 00028 . 00030 [107] - busy (c), tail fill
00ba1760: 00030 . 00040 [107] - busy (25), tail fill
重新跑这个程序:
0:001> .restart /f
进程初始状态的堆栈:
0:000> k
# ChildEBP RetAddr
00 000df820 77b92cf1 ntdll!LdrpDoDebuggerBreak+0x2b
01 000dfa68 77b3e831 ntdll!LdrpInitializeProcess+0x1a8e
02 000dfab8 77b6d103 ntdll!_LdrpInitialize+0xd5
03 000dfcf0 77b3e750 ntdll!LdrpInitializeInternal+0xc7
04 000dfd04 77b3e6f1 ntdll!LdrpInitialize+0x3b
05 000dfd10 00000000 ntdll!LdrInitializeThunk+0x11
还可以更早停下来,加载ntdll的时候就停下来,把模块加载事件enable就可以。今天先到这里,未完待续。周末出去happy一下。