ReactOS-Freeldr注册表HIVE文件格式

来源:互联网 发布:中国资本主义知乎 编辑:程序博客网 时间:2024/06/06 09:10
ReactOS的注册表信息存储在ReactOS/System32/CONFIG/SYSTEM文件中。注册表文件使用的一种特殊的格式——HIVE。
HIVE文件主要由BASE_BLOCK/BIN/CELL三部分组成的。BASE_BLOCK是文件头,大小为4KB,里面存储了整个文件的一些全局信息。BIN是以4KB对其的一段空间,里面管理了若干个CELL。CELL是一段用户自定义大小的空间,注册表的键值信息就是存储在一个个CELL里面。
整个HIVE文件就是一个BASE_BLOCK头和若干BIN,每个BIN中有若干CELL。当用户需要分配一个CELL,而HIVE文件中没有足够大的CELL时,需要在文件末尾增长一个BIN,再从这个BIN里面分别需要的CELL。

我们来看看整个HIVE文件在内存中的结构
  1. typedef struct _HHIVE
  2. {
  3.     ULONG Signature;                            // HV_SIGNATURE(0x66676572) 标志位
  4.     PGET_CELL_ROUTINE GetCellRoutine;           // 根据index获得cell的函数指针
  5.     PRELEASE_CELL_ROUTINE ReleaseCellRoutine;   // 释放cell指针
  6.     PALLOCATE_ROUTINE Allocate;                 // 申请内存的指针
  7.     PFREE_ROUTINE Free;                         // 释放内存的指针
  8.     PFILE_READ_ROUTINE FileRead;                // 读HIVE文件指针
  9.     PFILE_WRITE_ROUTINE FileWrite;              // 写HIVE文件
  10.     PFILE_SET_SIZE_ROUTINE FileSetSize;         // 获得HIVE文件大小
  11.     PFILE_FLUSH_ROUTINE FileFlush;              // 刷新HIVE
  12.     PHBASE_BLOCK BaseBlock;                     // BASE_BLOCK的内存指针
  13.     RTL_BITMAP DirtyVector;                     // 标志哪个块(4kb)被修改过的位图
  14.     ULONG DirtyCount;                           // 被修改块的数量?
  15.     ULONG DirtyAlloc;                           // ?
  16.     ULONG BaseBlockAlloc;                       // ?
  17.     ULONG Cluster;                              // BaseBlock占用的Cluster数量, 总为1
  18.     BOOLEAN Flat;                               // 初始化HIVE时是否使用了HINIT_FLAT标志
  19.     BOOLEAN ReadOnly;                           // 是否只读
  20.     BOOLEAN Log;                                // ?
  21.     BOOLEAN DirtyFlag;                          // ?
  22.     ULONG HvBinHeadersUse;
  23.     ULONG HvFreeCellsUse;
  24.     ULONG HvUsedcellsUse;
  25.     ULONG CmUsedCellsUse;
  26.     ULONG HiveFlags;                            // Hive标志
  27.     ULONG LogSize;
  28.     ULONG RefreshCount;
  29.     ULONG StorageTypeCount;                     // Storage数组的大小(2)
  30.     ULONG Version;
  31.     DUAL Storage[HTYPE_COUNT];                  // 管理Block的数组, Stable和Volatile各一个元素
  32. } HHIVE, *PHHIVE;
在freeldr中整个HIVE文件使用HHIVE数据结构来表示。这里面有一组函数指针,当HIVE需要分配内存、写文件等操作时会调用相应的指针。这些指针在HIVE的初始化函数中被赋值。GetCellRoutine可以根据CELL索引获取CELL指针,ReleaseCellRoutine会释放CELL指针。这两个指针在freeldr中没有使用。
初始化后BaseBlock指针将指向HIVE的文件头BASE_BLOCK结构。
DirtyVector是一个位图,标志着HIVE文件中的哪个块(4KB)被修改过了。
Cluster是BaseBlock占用的簇的数量,在reactos系统中这个值永远为1.
Flat代表初始化时是否使用了HINIT_FLAT标志,使用这个标志说明HHIVE所指向的内存都是初始化时分配好的,所以不需要它释放内存。ReadOnly代表注册表只读。
最后一个比较重要的域是StorageTypeCount和Storage数组。

HIVE中的数据分为两大类,易失的(Volatile)和非易失去的(Stable),StorageTypeCount为2。Stable数据最后会被刷新到硬盘文件中,而Volatile数据关机之后就丢失了。HHIVE中Storage数组有两个元素,分别管理这两种数据的全部数据块,这里面存储了数据块位置,是否空闲等信息。之后我们会看到用户通过CELL索引获取CELL地址,这个转换就使用到了这个数组里面的信息。

下面我们看一个HIVE的初始化函数HvpInitializeMemoryHive
函数有两个参数:
Hive是HHIVE结构的指针
ChunkBase是从硬盘中读取的HIVE文件内容。

lib/cmlib/hiveinit.c
  1. NTSTATUS CMAPI HvpInitializeMemoryHive(PHHIVE Hive, PVOID ChunkBase)
  2. {
  3.    SIZE_T BlockIndex;
  4.    PHBIN Bin, NewBin;
  5.    ULONG i;
  6.    ULONG BitmapSize;
  7.    PULONG BitmapBuffer;
  8.    SIZE_T ChunkSize;
  9.    /* 得到HIVE文件大小, 并且将HBASE_BLOCK.Length改为块大小4kb */
  10.    ChunkSize = ((PHBASE_BLOCK)ChunkBase)->Length;
  11.    ((PHBASE_BLOCK)ChunkBase)->Length = HV_BLOCK_SIZE;
  12.    /* 如果文件小于HBASE_BLOCK或者头部校验失败, 数据不合法 */
  13.    if (ChunkSize < sizeof(HBASE_BLOCK) ||
  14.        !HvpVerifyHiveHeader((PHBASE_BLOCK)ChunkBase))
  15.    {
  16.       return STATUS_REGISTRY_CORRUPT;
  17.    }
  18.    /* 生成Hive->BaseBlock中生成空间, 将ChunkBase的头部复制进去 */
  19.    Hive->BaseBlock = Hive->Allocate(sizeof(HBASE_BLOCK), FALSE, TAG_CM);
  20.    if (Hive->BaseBlock == NULL)
  21.    {
  22.       return STATUS_NO_MEMORY;
  23.    }
  24.    RtlCopyMemory(Hive->BaseBlock, ChunkBase, sizeof(HBASE_BLOCK));
  25.    /* 除文件头的每个BLOCK(4kb)准备一个HMAP_ENTRY结构, 存放在Hive.Storage[Stable]中 */
  26.    Hive->Storage[Stable].Length = (ULONG)(ChunkSize / HV_BLOCK_SIZE) - 1;
  27.    Hive->Storage[Stable].BlockList =
  28.       Hive->Allocate(Hive->Storage[Stable].Length *
  29.                      sizeof(HMAP_ENTRY), FALSE, TAG_CM);
  30.    if (Hive->Storage[Stable].BlockList == NULL)
  31.    {
  32.       Hive->Free(Hive->BaseBlock, 0);
  33.       return STATUS_NO_MEMORY;
  34.    }
  35.    /* 每个BIN循环一次 这个循环初始化HMAP_ENTRY数组 */
  36.    for (BlockIndex = 0; BlockIndex < Hive->Storage[Stable].Length; )
  37.    {
  38.       /* 获得BIN结构 */
  39.       Bin = (PHBIN)((ULONG_PTR)ChunkBase + (BlockIndex + 1) * HV_BLOCK_SIZE);
  40.       if (Bin->Signature != HV_BIN_SIGNATURE ||
  41.           (Bin->Size % HV_BLOCK_SIZE) != 0)
  42.       {
  43.          Hive->Free(Hive->BaseBlock, 0);
  44.          Hive->Free(Hive->Storage[Stable].BlockList, 0);
  45.          return STATUS_REGISTRY_CORRUPT;
  46.       }
  47.       /* 赋值一份BIN到本地 */
  48.       NewBin = Hive->Allocate(Bin->Size, TRUE, TAG_CM);
  49.       if (NewBin == NULL)
  50.       {
  51.          Hive->Free(Hive->BaseBlock, 0);
  52.          Hive->Free(Hive->Storage[Stable].BlockList, 0);
  53.          return STATUS_NO_MEMORY;
  54.       }
  55.       Hive->Storage[Stable].BlockList[BlockIndex].BinAddress = (ULONG_PTR)NewBin;
  56.       Hive->Storage[Stable].BlockList[BlockIndex].BlockAddress = (ULONG_PTR)NewBin;
  57.       RtlCopyMemory(NewBin, Bin, Bin->Size);
  58.       /* 每个BIN管理若干block */
  59.       if (Bin->Size > HV_BLOCK_SIZE)
  60.       {
  61.          for (i = 1; i < Bin->Size / HV_BLOCK_SIZE; i++)
  62.          {
  63.             Hive->Storage[Stable].BlockList[BlockIndex + i].BinAddress = (ULONG_PTR)NewBin;
  64.             Hive->Storage[Stable].BlockList[BlockIndex + i].BlockAddress =
  65.                ((ULONG_PTR)NewBin + (i * HV_BLOCK_SIZE));
  66.          }
  67.       }
  68.      
  69.       /* 下一个BIN */
  70.       BlockIndex += Bin->Size / HV_BLOCK_SIZE;
  71.    }
  72.    /* 初始化空闲CELL表 */
  73.    if (HvpCreateHiveFreeCellList(Hive))
  74.    {
  75.       HvpFreeHiveBins(Hive);
  76.       Hive->Free(Hive->BaseBlock, 0);
  77.       return STATUS_NO_MEMORY;
  78.    }
  79.    /* 初始化Hive->DirtyVector位图 */
  80.    BitmapSize = ROUND_UP(Hive->Storage[Stable].Length,
  81.                          sizeof(ULONG) * 8) / 8;
  82.    BitmapBuffer = (PULONG)Hive->Allocate(BitmapSize, TRUE, TAG_CM);
  83.    if (BitmapBuffer == NULL)
  84.    {
  85.       HvpFreeHiveBins(Hive);
  86.       Hive->Free(Hive->BaseBlock, 0);
  87.       return STATUS_NO_MEMORY;
  88.    }
  89.    RtlInitializeBitMap(&Hive->DirtyVector, BitmapBuffer, BitmapSize * 8);
  90.    RtlClearAllBits(&Hive->DirtyVector);
  91.    return STATUS_SUCCESS;
  92. }
HvpInitializeMemoryHive先生成HBASE_BLOCK所需空间赋值给HHIVE中的BaseBlock指针,并拷贝ChunkBase的头部到这段区域(9-24行)。
这里有个地方需要注意,在这里传入的HBASE_BLOCK.Length存储的是整个文件大小(HvLoadHive函数为它赋值),而在HHIVE中的HBASE_BLOCK.Length应该是一个BLOCK的大小4kb,10-17行做了转换。reactos中称这是一个hack,可见这种做法并不标准。
之后26-34行需要为每一个BLOCK(4kb)提供一个HMAP_ENTRY结构,存储在Hive.Storage[Stable]中。

Hive.Storage是Dual结构数组,Stable和Volatile各占用一个元素。
  1. typedef struct _DUAL
  2. {
  3.     ULONG Length;
  4.     PHMAP_DIRECTORY Map;
  5.     PHMAP_ENTRY BlockList; // PHMAP_TABLE SmallDir;
  6.     ULONG Guard;
  7.     HCELL_INDEX FreeDisplay[24]; //FREE_DISPLAY FreeDisplay[24];
  8.     ULONG FreeSummary;
  9.     LIST_ENTRY FreeBins;
  10. } DUAL, *PDUAL;
freeldr中主要用到了Length、BlockList和FreeDIsplay三个元素。FreeDisplay用作快速查找空闲CELL,这个一会儿再说。Length代表这个DUAL维护了多少个Block。每个Block会有一个对应的HMAP_ENTRY结构。BlockList指向HMAP_ENTRY数组的地址。
HMAP_ENTRY结构为
  1. typedef struct _HMAP_ENTRY
  2. {
  3.     ULONG_PTR BlockAddress;
  4.     ULONG_PTR BinAddress;
  5.     struct _CM_VIEW_OF_FILE *CmView;
  6.     ULONG MemAlloc;
  7. } HMAP_ENTRY, *PHMAP_ENTRY;
BlockAddress就是一个Block的开始地址, BinAddress是这个Block所在的Bin结构的开始地址,一个Block的大小是4kb,所以这里不需要类似Length的数据。
我们回到HvpInitializeMemoryHive的26-67行。这里初始化了Storage[Stable]对应的DUAL结构。
26、27行首先计算出除HBASE_BLOCK外,还有多少个Block,Dual.Length记录了Block的数量,之后为每一个Block生成HMAP_ENTRY,数组的首地址赋值给BlockList指针。

36行开始一个循环,初始化刚刚生成的HMAP_ENTRY数组。
在HIVE文件中一个BIN占用若干,紧接着HBASE_BLOCK后应是一个个的HBIN结构,这是BIN的头部。
  1. typedef struct _HBIN
  2. {
  3.    ULONG Signature;
  4.    HCELL_INDEX FileOffset;   // BIN在文件中的偏移
  5.    ULONG Size;               // BIN的大小, 是4kb的整倍数
  6.    ULONG Reserved1[2];
  7.    LARGE_INTEGER TimeStamp;
  8.    ULONG Spare;
  9. } HBIN, *PHBIN;
FileOffset说明了该BIN在HIVE文件中的偏移,Size是该BIN的大小,这个大小肯定是4kb的整倍数。
39-46行首先检测了HBIN的Signature和Size,确定这时一个合法的BIN结构。
之后生成空间并复制BIN的全部内容(48-57)。
由于BIN可能会占用几个BLOCK,所以之后有一个循环(59-67)初始化这个BIN占用的所有BlOCK的HMAP_ENTRY结构,BlockAddress是该Block的起始地址,BinAddress是该BIN的起始地址。70行获得下一个BIN的地址,再次开始循环。
可以看出BIN占用的内存空间是同时生成的,所以BIN中所有BLOCK的内存也是连续的。

循环完毕后HMAP_ENTRY被初始化完成,函数调用HvpCreateHiveFreeCellList初始化DUAL.FreeDisplay看先CELL链表,这个我们稍后看。
最后82-90初始化HIVE.DirtyVector,如果之后某一个块被更改了,位图中的相应位会被置位,注意每一个位都是按照Block(4kb)为单位进行管理的。初始化时这个位图全为0,说明没有任何更改。


现在来说说刚刚略过的HvpCreateHiveFreeCellList函数。
BIN是一大块连续的内存(4kb的整倍数),这段内存又被分为了一个个叫做CELL的结构。
一个CELL就是一个CELL头HCELL和紧接着的一段内存。
  1. typedef struct _HCELL
  2. {
  3.    /* <0 if used, >0 if free */
  4.    LONG Size;
  5. } HCELL, *PHCELL;
HCELL只有一个元素Size,代表这个CELL的大小(包括HCELL本身和紧接着的内存)。不过这个Size有点特殊,当Size小于0时说明这个CELL已经被使用,它的绝对值是真正的大小。当Size大于0说明这个CELL空闲,Size本身就是大小。
所有空闲的CELL都被放到了Dual.FreeDisplay中,HvpCreateHiveFreeCellList就是这个初始化过程。
HvpCreateHiveFreeCellList以Hive指针作为参数,这时HIVE.Storage.BlockList已经初始化好了。

lib/cmlib/Hivecell.c
  1. NTSTATUS CMAPI HvpCreateHiveFreeCellList(PHHIVE Hive)
  2. {   ......
  3.    /* 初始化Storage中的FreeDisplay为空 */
  4.    for (Index = 0; Index < 24; Index++)
  5.    {
  6.       Hive->Storage[Stable].FreeDisplay[Index] = HCELL_NIL;
  7.       Hive->Storage[Volatile].FreeDisplay[Index] = HCELL_NIL;
  8.    }
  9.    BlockOffset = 0;
  10.    BlockIndex = 0;
  11.    /* 按Block开始循环 */
  12.    while (BlockIndex < Hive->Storage[Stable].Length)
  13.    {  /* 获得BIN结构 */
  14.       Bin = (PHBIN)Hive->Storage[Stable].BlockList[BlockIndex].BinAddress;
  15.       /* 一个BIN里面每个CELL循环一次 */
  16.       FreeOffset = sizeof(HBIN);
  17.       while (FreeOffset < Bin->Size)
  18.       {  /* 获得HCELL指针 */
  19.          FreeBlock = (PHCELL)((ULONG_PTR)Bin + FreeOffset);
  20.          /* Size > 0 说明空闲, 调用HvpAddFree把这个CELL加入空闲列表 */
  21.          if (FreeBlock->Size > 0)
  22.          {
  23.             Status = HvpAddFree(Hive, FreeBlock, Bin->FileOffset + FreeOffset);
  24.             if (!NT_SUCCESS(Status))
  25.                return Status;
  26.             /* 下一个HCELL的地址 */
  27.             FreeOffset += FreeBlock->Size;
  28.          }
  29.          else
  30.          {  /* 下一个HCELL的地址 */
  31.             FreeOffset -= FreeBlock->Size;
  32.          }
  33.       }
  34.       /* 下一个BIN */
  35.       BlockIndex += Bin->Size / HV_BLOCK_SIZE;
  36.       BlockOffset += Bin->Size;
  37.    }
  38.    return STATUS_SUCCESS;
  39. }
首先初始化Storage.FreeDisplay,全部赋值为空(4-8行)。
12行开始遍历Storage[Stable].BlockList列表。
获得BIN结构后遍历里面一个个CELL,如果CELL.Size > 0 说明空闲,调用HvpAddFree加入空闲列表(14-33)。
完成后继续遍历下一个BIN(35-36)。

函数的关键在HvpAddFree中,这个函数有三个参数
Hive为HHIVE指针,
FreeBlock为空闲HCELL的指针,
FreeIndex是HCELL在文件中的偏移,这个参数之所以叫做FreeIndex是因为它也被用户当作这个CELL的索引(或句柄?)使用。
lib/cmlib/hivelcell.c
  1. static NTSTATUS CMAPI HvpAddFree(PHHIVE RegistryHive, PHCELL FreeBlock, HCELL_INDEX FreeIndex)
  2. {
  3.    PHCELL_INDEX FreeBlockData;
  4.    HSTORAGE_TYPE Storage;
  5.    ULONG Index;
  6.    /* index的最高位代表Storage类型, 1为Volatile, 0为Stable */
  7.    Storage = (FreeIndex & HCELL_TYPE_MASK) >> HCELL_TYPE_SHIFT;
  8.    /* 计算出应该插入的FreeDisplay表项 */
  9.    Index = HvpComputeFreeListIndex((ULONG)FreeBlock->Size);
  10.    /* 插入以FreeDisplay[index]为表头的链表 */
  11.    FreeBlockData = (PHCELL_INDEX)(FreeBlock + 1);
  12.    *FreeBlockData = RegistryHive->Storage[Storage].FreeDisplay[Index];
  13.    RegistryHive->Storage[Storage].FreeDisplay[Index] = FreeIndex;
  14.    /* FIXME: Eventually get rid of free bins. */
  15.    /* 这里应该去除所有CELL都是free的BIN */
  16.    return STATUS_SUCCESS;
  17. }
第三个参数还有一个特殊含义, 最高位表示freecell的类型,因为HvpCreateHiveFreeCellList调用这个函数时是Stable类型的cell,所以最高位为0, 索引正好等于CELL在文件中开始的位置。
函数之后调用HvpComputeFreeListIndex根据大小计算出需要插入的FreeDisplay表头,这是是一个哈希函数,根据大小不同可以返回不同的Index值,但是唯一的Size每次返回的Index都相同,而且Index是随着Size递增的。这里就不仔细看了。 这个函数把不同大小的空闲CELL按策略分配在24个FreeDisplay链表中。
之后把空闲的CELL插入到Storage[Storage].FreeDisplay[Index]中,这是一个单项链表, 连表中的空闲CELL需要存储下一个空闲CELL的Index, 最后一个空闲CELL将存储HCELL_NIL为结束标志。

至此初始化完毕,初始化有几大部分
1. HHIVE结构
2. HBASE_BLOCK
3. HMAP_ENTRY数组
4. 空闲CELL列表FreeDisplay

下一节我们就将看到如何分配CELL,如何通过INDEX找到CELL,HIVE文件如何增长等等。