Programming 板


LINE

1F:推 purpose:t_printf 的实作应该是关键吧..但作者没给 124.8.128.11 04/06 10:26
2F:推 bob123:t_printf应该只是用__attribute__让编译器111.255.18.40 04/06 21:54
3F:→ bob123:检查printf的参数吧, 看了一下重点应该在 111.255.18.40 04/06 21:55
4F:→ bob123:t_push()和t_pop()...111.255.18.40 04/06 21:57
5F:→ bob123:看了一下网站上的data-stack.c 感觉像是用 111.255.18.40 04/06 22:02
6F:→ bob123:double linked list实作mem pool来当堆叠用111.255.18.40 04/06 22:05
7F:推 purpose:您说的是,我好像懂了。把 malloc 都改用124.8.135.61 04/06 23:19
8F:→ purpose:t_malloc 在配置空间的同时跟 Heap 管理器124.8.135.61 04/06 23:20
9F:→ purpose:一样,用链结串列把最後於 t_pop 时要一起 124.8.135.61 04/06 23:21
10F:→ purpose:free 的 heap blocks 串起来..124.8.135.61 04/06 23:21
11F:推 EdisonX:p 大指的是这篇吗? http://ppt.cc/aoSW 180.177.76.161 04/08 03:12
以上推文是来自 ptt.cc 的这串主题,其他转信站可能之前没看到,在此补充。 而我这篇回文是针对 EdisonX 版友的推文,稍微聊聊,可能有些偏离主题了, 标题改成「在记忆体 Heap 区里,大家都爱用的链结串列」也许比较合适... (其实还是很烂对吗? =_=") To EdisonX 版友: 您这篇我之前没看过,其内容我不是完全了解, 但我觉得其中对於「Linked List」的使用,大家的理念应该都是很接近的。 在 data-stack.c 里面使用链结串列,是为了将每一次於 Runtime 做 Memory Allcation 所回传的起始位址记录下来,比如: add1 = malloc(10) add2 = malloc(20) add3 = malloc(30) 使用链结串列记录下来,最後得到:add1->add2->add3 故执行 t_pop() 函数时,可以达到:free(add1) -> free(add2) -> free(add3) 一次性将已配置记忆体都释放掉,免除 Memory Leak 的情况。 ● data-stack.c 使用链结串列目的:使 free() 一次到位,永绝 Memory Leak。 而在 EdisonX 版友文章中,每次 malloc() 时,有两笔额外的资讯会被记录: (1) 触发该配置的原始码档案,比如 1.c, 2.cpp (2) 该原始码档案中,对应此次配置的程式码行号 将这两笔额外资讯,合成链结串列的 node (节点)。 也就是说,最终得到的链结串列,其实跟 data-stack.c 的内容差不多, 都是把连续的记忆体配置动作记录下来,只是这次记载的资讯更丰富而已。 明显的差别在於,释放记忆体区块的方法。 在 data-stack.c,是使用 t_pop() 一次性查询链结串列,然後释放全部区块; 在 EdisonX 版友文章中,还是维持传统 C 语言的 malloc-free 使用模式,在 malloc() 所多记录的那个 node,就在相应的 free() 时,多进行移除该 node 的动作, 则最终留下的链结串列里,都是没被 free 的区块。 ● dbg_malloc (VC) 使用链结串列的目的:侦测可能发生 Memory Leak 的记忆体区块 而在我上方推文中提到:「跟 Heap 管理器一样,把 Heap block 串起来...」 当初推文空间有限,讲得比较含糊,其实我指的是 Windows Heap Manager 的行为, 下面我把这段重新写详细一点。(Linux 的状况,就待强者补充了) 所谓的 Heap Manager 是指 ntdll.dll 里的程式码。 一个 Windows 行程 (Process),其记忆体空间中,有三大类的 Heap: (1) Default Process Heap (预设 1MB 大) (2) C Runtime Heap (3) Application Specific Heap 每个行程都有「Heap-(1)」,使用 GetProcessHeap() 可以取得,不管它。 而「Heap-(2)」就是用 C/C++ 写好一个程式,不管你的入口函数是 main, wmain, tmain, WinMain, wWinMain...在进去入口函数前,所谓的 C 标准函式库,会用 HeapCreate() 函数去建立这个「Heap-(2)」,之後你在原始码里面,不管写 malloc 还是写 new,其底层都是透过这样的 Windows API 来达成: HeapAlloc(CRuntimeHeap, .., 配置大小); 至於「Heap-(3)」就是排除「Heap-(1) 跟 Heap-(2)」之後,在记忆体空间里, 不知道是谁偷偷用 HeapCreate() 去建立出来的 Heap 区域,也不必管它, 谁建的谁才需要关注它。 我们用 C/C++ 编译器去建立的 DLL 档,需要执行 malloc/new 吗? 有时候需要,有时候不需要,不一定有用到 Heap。 所以 DLL 在进去入口函数 DllMain() 之前, 也会有个「C 标准函式库」先执行: HANDLE dllheap = HeapCreate(..); 这也算是一种「Heap-(2)」。 所以在 C/C++ 行程中,执行完 ptr = malloc(8); 然後丢给某个 dll 函数,并且 期待该 dll 函数会帮忙做 free(ptr) 是不智之举,其底层会变成: HeapFree(dllheap, .., ptr); 然後 Heap Manager 会发现在 dllheap 对应的这一大块记忆体里, 根本就没有 ptr 可以释放,抱错小孩了! Heap Manager 有四个重点: I. Heap Manager 的功能、任务为何 II. Heap Block 更具体一点是什麽 III. 为何 free(addr) 之後,就可以释放当初配置的大小,这笔 Size 记录在哪 IV. Heap Manager 学别人用 Linked List 干嘛 其中 II. 跟 III. 是同一件事,而 I. 跟 IV. 也差不多是同一件事。 Heap Manager 即负责 HeapCreate, HeapAlloc, HeapFree, HeapDestroy 这四大任务 的管理者。等於「建立/摧毁 Heap」与「配置/释放 Heap Block」四大任务。 比如上面说的 dllheap 就是被建立出来的 Heap (堆); 而 ptr = malloc(8); 所建立出来的东西称之 Heap Block (堆块)。 这中间其实还有一个东西叫 Heap Segment (堆段)一个 Heap 内,会有好几个堆段;而每个堆段内,又分成好几个堆块。 已知虚拟记忆体位址有三大状态:Free, Reserved, Committed 而刚建立好的堆可能 4MB 这麽大,但是很长一段时间内,都只用了几百位元组而已, 为了要节省物理记忆体,一开始只会在 Heap 内建立比较小的堆段,比如 4KB, 之後的 mallc 请求都从此堆段中找寻堆块来回应,当 4KB 堆段不够用了, 则 Heap Manager 才会建立新的堆段,每次放大两倍,也就是接着建立 8KB 堆段。 在这 4MB 中,只要还没被拿去建立成堆段者,都是 Reserved 状态,换言之,不占 实体记忆体空间。堆段的地位主要是用来节省记忆体用的,通常不必管它。 当「C 标准函式库」在进入 main() 前,就是像 SNG 车记者,要把现场还给棚内主播 (C Programmer) 前,会先用 HeapCreate() 跟一个叫「堆经理」的家伙,申请在 记忆体空间里要一个连续的位址空间,将其保留。 假设此位址是 0x5000~0x6FFF 总共 0x2000 大。 之後当 "棚内主播" 写了像 malloc(7); 这样的要求後, 就等於跟「堆经理」去要一个大小至少保证有 7+ Bytes 的堆块。 堆经理就会进行查询,看要回传回应哪个堆块的起始位址给主播用。 这样的「malloc/free」要求是连绵不绝的,堆经理不能拿张纸,画 0x2000 个格子,然後 把已配置的用铅笔打勾,没配置的保留空白,这样的资料结构很难用... 比如当有人要求配置 11 Bytes 堆块时,就要从格子最左边开始找,一直到至少有 连续 11 个空格出现为止,才有办法回应申请。 所以 Windows 的「堆经理」,为了能快速解决这些从早烦到晚的申请,找了两套机器, 叫前堆分配器 (Front-End Allocator) 跟後端分配器 (Back-End Allocator)。 前端分配器,处理申请的速度很快,但不是每个 malloc(..) 请求它都能处理, 当它处理不了的,就交给後端分配器去搞,後端分配器一定能完成任务,但速度比较慢。 後端分配器不管它。 前端分配器在 Windows Vista 之前,使用的型号都叫「Look Aside List」,简称 LAL。 到了 Vista 之後,型号则是「LOW Fragmentation」,简称 LF。 在 XP 可以用 HeapSetInformation 使分配器变成 LF。 LF (低碎片) 分配器还是不管它。 LAL (对应单) 分配器就是不管堆的总体有多大,都把它区分成 128 大类, 也就是建立 LAL[128] 阵列,其中 LAL[0] 没在用,所以不管它。 LAL[1] -> 16 Bytes LAL[2] -> 24 Bytes LAL[3] -> 32 Bytes . . . LAL[127] ->1024 Bytes 也就是说,如果中共打过来,政府有 18 套剧本可以对应, 如果有人要求 malloc,那堆经理就有 127 套剧本可以对应,记录在 LAL 对应单上面。 每个堆块的组成,都要求至少得在最前方留下 8 Bytes Metadata,里面会记录 此堆块的大小,所以 free(ptr) 这样跑之所以能成立,是因为 "堆经理" 会到 「(char *) ptr - 8」这个位址去查询此堆块的 Metadata, 以得知此次共需释放多大的空间。 从 LAL 对应清单中得知,最小的堆块都有 16 Bytes 这麽大,扣掉 Metadata 後, 该次 malloc 的申请者最少也能得到 8 bytes 的空间,所以那些以为申请 malloc(1) 要省记忆体的人,请认清楚:堆块的大小是 8 Bytes 起跳的 你去自助餐夹一根花椰菜难道还期待老板算你几块钱吗? 至少都是「5块/10块」或「一份/半份」这样算钱的... 当然老板讲人情的话可以不计较,但 ntdll.dll 是没有人性的。 ※ Windows Internal 的说法,好像「64位元程式」的 Heap Block 最小单位 不是 16 Bytes 而是 24 Bytes?没有写得很详细,但这不影响大局,没关系。 malloc(1~8) 的申请,都是使用对应单里的 LAL[1] malloc(9~16) 的申请,都使使用对应单里的 LAL[2] . . 以此类推。 而 LAL 阵列的每一项,都是一个链结串列,记录所有可以被 malloc() 的堆块。 假设下面这三个连续位址内,都是等待配置的 16-Byte-Size 堆块。 0x5000~0x500F 0x5010~0x501F 0x5020~0x502F 则 LAL[1]->0x5000->0x5010->0x5020 当发生 ptr1 = malloc(1) 时,会得到 ptr1 = 0x5008; 且对应单变成 LAL[1]->0x5010->0x5020 同理,若又发生 ptr2 = malloc(8) 时,会得到 ptr2 = 0x5018; 且对应单变成 LAL[1]->0x5020 然後若发生 malloc(8) 将使 LAL[1] 的链结串列清空。 接着再一次 malloc(8) 就会使前端分配器束手无策,改用後端分配器去处理。 後端分配器如何解决 16-Bytes 堆块不足的问题呢? 其实是找比 16 Bytes 大的堆块,也就是 24, 32, 40... Bytes 这些堆块,找到 还闲置的就把它拿来分割成小的堆块,比如把 32 毁掉一个,创造两个 16 Bytes 堆块。 ● Heap Manager 使用链结串列的目的:快速回应 malloc 的请求 回头检视「ptr1 = malloc(1) 得到 ptr1 = 0x5008」这笔配置的底层。 如果用 WinDbg 指鹿为马的 dt 功能的话,那就是透过以下指令: dt _HEAP_ENTRY 0x5008 - 0x8 就可以解读此 Heap Block 的 Metadata 内容: +0x00 Size : 0x0002 (乘以「堆粒度:8」之後才是真实大小) +0x02 PreviousSize : 不一定 +0x04 SmallTagIndex : 不知道,不管它 +0x05 Flags : 旗标 +0x06 UnusedBytes : 0x07 +0x07 SegmentIndex : 堆段索引,表明它属於哪个堆段 这个 Heap Block 之前说过是 16 Bytes,其中 malloc 只有请求 1 Byte, 所以共有 7 Bytes 在此堆块中是 Unused 的。 PreviousSize 假设是 0x0008 好了,表示紧邻的前一个堆块大小为 64 Bytes。 因为有这个记录,所以可以从目前堆块,去索引到前一个 Heap Block 的起始点; 又因为有自己 Heap Block 的 Size,所以又可以向後索引到下一个 HeapBlock 的起点。 这也是一种链结串列的利用,方便任意往前、往後遍历所有的 Heap Blocks。 至於 Flags 详细如下: 0x01 - HEAP_ENTRY_BUSY 0x02 - HEAP_ENTRY_EXTRA_PRESENT 0x04 - HEAP_ENTRY_FILL_PATTERN 0x08 - HEAP_ENTRY_VIRTUAL_ALLOC 0x10 - HEAP_ENTRY_LAST_ENTRY 0x20 - HEAP_ENTRY_SETTABLE_FLAG1 0x40 - HEAP_ENTRY_SETTABLE_FLAG2 0x80 - HEAP_ENTRY_SETTABLE_FLAG3 只有黄色的需要关注,这里的 BUSY 不代表已配置给 "棚内主播" 那种配置, 有两种可能,一种是被前堆分配器拿去用,另一种是真的配置给 C Programmer 了。 反正使用者不会直接查看 Metadata 来决定堆块可以不可以拿去用,而是会询问 LAL, 而 LAL 也不会去看 Metadata,而是查自己的「对应单」,当对应单找不到对应方案时, 才把处理权转交给後端分配器。 一直到当後端分配器把某个 Heap Block 拿去配置後,才会 去 Set 该堆块的 HEAP_ENTRY_BUSY 旗标。 当 Flags 值为偶数时,也就是 unset BUSY 时,就代表堆块才刚被 HeapFree 完而已。 HEAP_ENTRY_VIRTUAL_ALLOC 告诉我们,该堆块是直接由 VirtualAlloc 所配置而来。 C Programmer 有时候请求的 malloc() 大小是很超过的,随便就好几 MB 当基本单位。 而 Metadata 栏位才 2 Bytes 宽,顶天了也就 0xFFFF,就算乘以堆粒度 8 之後, 也只有 511 KiB 可以记录。 这种过度的要求丢给 Heap Manager 时,Heap Manager 会又丢给上层的 虚拟记忆体管理员,总之就是 VirtualAlloc 系列函数。 (该系列函数的粒度 (granularity) 是 64KB 当基本单位) 就好像酒店客人拿钱给服务生,说要买烤鸭一样...都是外包的业务。 当一个 malloc(几MB) 的要求丢给 Heap Manager 时,虽然会转交给 VirtualAlloc 来配置,该块配置记忆体还是会被添加 Heap Block 的 Metadata,并标明此 FLAG 来注记:「这是一个不位於 Heap 区域内的记忆体区块」。 Heap Corruption 最常见的情况,就是写入 Heap 记忆体时,破坏了 Metadata。 比如你 vadd = malloc(8),可是却从 vadd 开始连续写入远过八位元组的资料, 使相邻的下一个 Heap Block 资讯被破坏掉,无法正常使用。 资料来源: Advanced Windows Debugging http://advancedwindowsdebugging.com/ --



※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 124.8.128.253 ※ 编辑: purpose 来自: 124.8.128.253 (04/08 14:06)
12F:推 LPH66:推一个 140.112.230.62 04/08 14:49
补充一下 Heap Block 的 Metadata 在 VISTA 之後似乎有变动,我之前是在 XP 观察的 最精确的解读方式是使用 WinDbg 的 !Heap 这个 Extension,新的手动解读方式: http://advdbg.org/blogs/advdbg_system/articles/5152.aspx 简单来说,有经过编码,要先做 XOR 解码後才是正确的值,防止 shellcoder 用的。 ※ 编辑: purpose 来自: 124.8.138.253 (04/08 18:44)
13F:推 bob123:这不推不行XD 111.255.13.213 04/08 20:29
14F:推 EdisonX:p 大解释真清楚,书看得超多!!推一个!! 180.177.76.161 04/08 21:49
15F:推 damody:神解释呀~~~ 140.118.175.35 08/18 02:52
16F:推 rosemary0401:push111.240.152.202 08/18 11:00







like.gif 您可能会有兴趣的文章
icon.png[问题/行为] 猫晚上进房间会不会有憋尿问题
icon.pngRe: [闲聊] 选了错误的女孩成为魔法少女 XDDDDDDDDDD
icon.png[正妹] 瑞典 一张
icon.png[心得] EMS高领长版毛衣.墨小楼MC1002
icon.png[分享] 丹龙隔热纸GE55+33+22
icon.png[问题] 清洗洗衣机
icon.png[寻物] 窗台下的空间
icon.png[闲聊] 双极の女神1 木魔爵
icon.png[售车] 新竹 1997 march 1297cc 白色 四门
icon.png[讨论] 能从照片感受到摄影者心情吗
icon.png[狂贺] 贺贺贺贺 贺!岛村卯月!总选举NO.1
icon.png[难过] 羡慕白皮肤的女生
icon.png阅读文章
icon.png[黑特]
icon.png[问题] SBK S1安装於安全帽位置
icon.png[分享] 旧woo100绝版开箱!!
icon.pngRe: [无言] 关於小包卫生纸
icon.png[开箱] E5-2683V3 RX480Strix 快睿C1 简单测试
icon.png[心得] 苍の海贼龙 地狱 执行者16PT
icon.png[售车] 1999年Virage iO 1.8EXi
icon.png[心得] 挑战33 LV10 狮子座pt solo
icon.png[闲聊] 手把手教你不被桶之新手主购教学
icon.png[分享] Civic Type R 量产版官方照无预警流出
icon.png[售车] Golf 4 2.0 银色 自排
icon.png[出售] Graco提篮汽座(有底座)2000元诚可议
icon.png[问题] 请问补牙材质掉了还能再补吗?(台中半年内
icon.png[问题] 44th 单曲 生写竟然都给重复的啊啊!
icon.png[心得] 华南红卡/icash 核卡
icon.png[问题] 拔牙矫正这样正常吗
icon.png[赠送] 老莫高业 初业 102年版
icon.png[情报] 三大行动支付 本季掀战火
icon.png[宝宝] 博客来Amos水蜡笔5/1特价五折
icon.pngRe: [心得] 新鲜人一些面试分享
icon.png[心得] 苍の海贼龙 地狱 麒麟25PT
icon.pngRe: [闲聊] (君の名は。雷慎入) 君名二创漫画翻译
icon.pngRe: [闲聊] OGN中场影片:失踪人口局 (英文字幕)
icon.png[问题] 台湾大哥大4G讯号差
icon.png[出售] [全国]全新千寻侘草LED灯, 水草

请输入看板名称,例如:BabyMother站内搜寻

TOP