• 堆学习


      在系统内部堆就是一块预定的地址空间区域。刚开始堆的大部分页面都没有调拨物理存储器。随着我们不断的从堆中分配内存,堆管理器会给堆调拨越来越多的物理存储器。这些物理存储器始终是从页交换文件中分配的。释放堆中的内存时,堆管理器会撤销已调拨的物理存储器。

       进程初始化时,系统会在进程的地址空间中创建一个堆。这个堆被称为进程的默认堆。默认情况下,这个堆的地址空间区域大小是1MB。程序员可以控制这个大小。我们可以在创建应用程序时用/HEAP连接器开关来改变默认堆的大小。由于DLL没有与之关联的堆,因此在创建DLL时,不应该使用/HEAP开关。默认堆的句柄会被保存在进程环境块_PEB的ProcessHeap字段中。

      通过.process获得_PEB的地址

      ProcessHeap字段即为进程默认堆。其上的HeapSegmentReserve是进程堆的预订(默认为1MB)大小。HeapSegmentCommit是进程堆的初始提交大小,默认值为2个内存页大小(x86内存页为4KB),NumberOfHeaps字段用来介绍堆的总数。ProcessHeaps是一个数组,用来记录每个堆的句柄。

      在windbg中可以使用!heap扩展显示堆使用信息,控制堆管理器中的断点,检测泄露的堆块,搜索堆块或者显示页堆(page heap)信息。

      !heap -h列出当前进程的所有堆

      这些都是进程分配的默认堆,除了进程的默认堆以外,程序也可以调用HeapCreate函数创建自己的堆,这样创建的堆被称为私有堆,3b0000就是私有堆。

      !heap -a 3b0000显示堆的信息

      Segment at 3b0000 to 3c0000 (00001000 bytes committed)指明堆的内存范围和提交字节数。Granularity: 8 bytes指明堆块分配粒度。在0号堆段的3b0680为空闲堆。

      从_PEB中的堆数组可以知道,进程中可以存在多个堆。在每个堆内部又可以分为多个堆段,每个堆段又可以分为多个堆块。堆管理器在创建堆时创建的第一个段,我们将其称为0号段。堆是可增长的,当一个段不能满足要求时,堆管理器会继续创建其他段。但最多可以有64个段。每个堆使用_HEAP结构来描述,用命令dt _HEAP 3b0000来查看私有堆3b0000来查看堆结构。

       _HEAP_ENTRY  用于存放堆管理数据结构的堆块结构

      VirtualMemoryThreshold : 0xfe00   以分配粒度为单位的堆块阀值,表示可以在段中分配的堆块的最大有效(即应用程序可以实际使用的)值,该值为508kB

      SegmentReserve   堆段保留大小,SegmentReserve字段的值为0x100000 = 1MB。表示我们请求创建的堆的最大大小。

      SegmentCommit   堆段提交大小,SegmentCommit为0x2000,表示仅仅提交两个页面为8KB。

      ProcessHeapsListIndex PEB中ProcessHeaps的索引

      VirtualAllocdBlocks 虚拟内存分配块链表,超过堆块阀值将在此分配。当应用程序从堆中分配的堆块的最大大小大于堆块阀值,堆管理器会直接从内存管理器中分配,并不会从从空闲链表申请。同时将此空间添加到VirtualAllocdBlocks结构所指向的链表中。

      Segments是一个数组,它记录着堆拥有的所有段。每个元素类型为_HEAP_SEGMENT结构。

      FreeLists是一个双向链表的头指针,该链表记录着所有空闲堆块的地址。链表元素为FREE_LIST结构,该链表为双向链表,每个链表中都保存着一些空闲堆块。各个链表项都指向_HEAP_FREE_ENTRY结构中的FreeList字段。当应用程序申请新的空间时,堆管理器会首先遍历这个链表,如果找到满足需要的堆块就分配出去。否则便要考虑建立新的堆块或从内存管理器申请空间。在释放时,当不满足解除提交条件时,大多数情况下也是将要释放的堆块加入到该空闲链表中。Free_list数组,该数组有128个元素,用来存储各个空闲链表的表头。空闲链表的元素为_HEAP_FREE_LIST类型。

      FrontEndHeap该字段为指针指向前端分配器

      LastSegmentIndex表示堆中最后一个段的序号,加1便是总段数。

      上面LastSegmentIndex为0说明只有一个段,在看下堆段的结构,每个段使用_HEAP_SEGMENT结构描述

      Entry字段是一个数组,存储着该段所有的堆块。由于每个堆块使用_HEAP_ENTRY结构描述,因此该数组元素类型为_HEAP_ENTRY。

      Heap字段维护该块块所属的堆的_HEAP结构的首地址。

      BaseAddress字段维护该段的基地址。

      FirstEntry表示该段中第一个堆块的地址。

      LastEntryInSegment表示最后一个堆块。

      现在执行HeapAlloc函数在3b0000申请一个堆块,再执行!heap -a 3b0000显示堆的信息

      3b0680已在使用,也是我们刚申请的堆块,3b06a8成为了新的空闲堆,再执行dt _HEAP_SEGMENT 3b0640可以看到最后一个堆块的位置变成了3b06a8

      可以看出段内部又可以分为多个堆块。堆块使用 _HEAP_ENTYR结构来描述,该结构占8 Byte。_HEAP_ENTRY结构之后就是供应用程序使用的区域。调用HeapAlloc函数将返回HEAP_ENTRY之后的地址。此地址减去8Byte便可以得到_HEAP_ENTRY结构。查看堆块结构

      Size,堆块的大小,以分配粒度为单位  。前面知道粒度为8 bytes,且UnusedBytes为0x18,所以真正的大小为5*8-0x18=0x10字节

      PreviousSize,前一个堆块的大小   

      Flags,标志  

      

      UnusedBytes,因为补齐而多分配的字节数  

      SegmentIndex,这个堆块所在堆段的序号

     

      该结构只比_HEAP_ENTRY多了个FreeList字段,用来存储空闲链表的一个链表项,所以_HEAP_FREE_ENTRY大小为16字节。多个链表项构成一个空闲链表。因为该链表仅仅只有一个空闲堆块,因此上述_LIST_ENTRY的Flink和Blink 字段均指向空闲链表的头结点。

     

      HeapAlloc申请的空间:

      调用HeapFree释放后:

      可以看出释放后修改了_HEAP_ENTRY的Flags字段,后8字节添加了FreeList链表。

      FreeList链表中的3b01a0地址保存的值为3b0688,也是我们调用HeapFree释放空间的地址。

      当再次调用HeapFree释放时,FreeList会将释放的地址加入链表

      在进行堆溢出的时候,通常会覆盖_HEAP_ENTRY或者_HEAP_FREE_ENTRY结构,但一般程序中会进行多次的堆分配,所以大多都是覆盖_HEAP_ENTRY。

  • 相关阅读:
    (05)ElasticSearch 倒排索引
    (04)ElasticSearch 安装Kibana
    (03)ElasticSearch 安装ElasticSearch-Head插件
    (02)ElasticSearch 安装
    (004)Linux http命令curl访问url
    (01)ElasticSearch概述
    (16)mongodb mapReduce分布式统计示例遇到的一个未解问题,求平均值不对,希望哪位大神给指点一下
    (15)mongodb mapReduce的概念及用法
    (14)mongodb aggregate聚集框架
    CodeForces
  • 原文地址:https://www.cnblogs.com/kgsdy/p/5895805.html
Copyright © 2020-2023  润新知