跟踪随笔一则
首先预祝张老师5月份即将出版的新书《软件调试》(Debugging Principle) 能获得巨大的成功!^_^ 其实就张老师十余年的技术积累、沉淀来看该书的成功也是必然。怎奈鄙人才疏学浅,许多方面只是一知半解(Machine Checked Architect / JTAG Debugging / KD Engine etc...),并不敢妄加评论... 所以我还是力所能及,先写点小文章道个喜吧。
也确实好久没写随笔了,一直忙于各种杂事和内核跟踪。今天整理一些平时的跟踪涂鸦贴过来,请各位前辈指正!
C的代码就不完整列了,毕竟微$不让... 感兴趣的话需要自己对照着查阅 WRK1.2 或是 ReactOS0.44,所以下面的信息都是汇编的,而且我每一行都写了注释。
不过也不是什么函数都值得往下面列,大部分情况下编译器生成的代码都中规中矩,看C足矣。细节看多了除了能让你更加清楚过程调用和强身健体以外没什么特别的好处;但也有时编译器会给你些惊喜(当然,好的或抓狂的都有),这些惊喜会让你看到C语言不易被察觉的地方。正好这两天在研究 \ob 文件夹,我选了nt!ObpAllocateObject 这个 ASM 较为特别的函数入手。
; 平台:Win-XP SP2
; 下面的注释缩小显示, 需要研究细节的朋友请拷贝到适当的编辑环境下查看
nt!ObpAllocateObject:
805b6008 8bff mov edi,edi ; ┓
805b600a 55 push ebp ; ┣ Prologue
805b600b 8bec mov ebp,esp ; ┛
805b600d 51 push ecx ;
805b600e 8b4d08 mov ecx,dword ptr [ebp+8] ; ECX <-- IN POBJECT_CREATE_INFORMATION ObjectCreateInfo
805b6011 53 push ebx ;
805b6012 56 push esi ;
805b6013 8b7514 mov esi,dword ptr [ebp+14h] ; ESI <-- IN PUNICODE_STRING ObjectName
805b6016 33d2 xor edx,edx ; ┓ if(ObjectCreateInfo == NULL)
805b6018 3bca cmp ecx,edx ; ┛
805b601a 57 push edi ;
805b601b 8b7d10 mov edi,dword ptr [ebp+10h] ; EDI <-- IN POBJECT_TYPE ObjectType OPTIONAL
805b601e 750e jne nt!ObpAllocateObject+0x26 (805b602e) ; else 的执行流 (我们目前不走这个执行流)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~; 下面这段代码编译器生成的很绝!
805b6020 6a10 push 10h ; ┓ sizeof(OBJECT_HEADER_NAME_INFO) == sizeof(OBJECT_HEADER_CREATOR_INFO)
805b6022 5b pop ebx ; ┛
805b6023 8955fc mov dword ptr [ebp-4],edx ; QuotaInfoSize = 0;
805b6026 895510 mov dword ptr [ebp+10h],edx ; HandleInfoSize = 0;
805b6029 895d14 mov dword ptr [ebp+14h],ebx ; CreatorInfoSize = sizeof(OBJECT_HEADER_CREATOR_INFO);
805b602c eb63 jmp nt!ObpAllocateObject+0x89 (805b6091) ; NameInfoSize = sizeof(OBJECT_HEADER_NAME_INFO); 这句最绝,用 EBX 寄存器保存 NameInfoSize,连局部变量都不要
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
805b602e 8b4110 mov eax,dword ptr [ecx+10h] ; ┓
805b6031 3b8784000000 cmp eax,dword ptr [edi+84h] ; ┣ (ObjectCreateInfo->PagedPoolCharge != ObjectType->TypeInfo.DefaultPagedPoolCharge)
805b6037 7514 jne nt!ObpAllocateObject+0x45 (805b604d) ; ┛
805b6039 8b4114 mov eax,dword ptr [ecx+14h] ; ┓
805b603c 3b8788000000 cmp eax,dword ptr [edi+88h] ; ┣ (ObjectCreateInfo->NonPagedPoolCharge != ObjectType->TypeInfo.DefaultNonPagedPoolCharge)
805b6042 7509 jne nt!ObpAllocateObject+0x45 (805b604d) ; ┛
805b6044 81791800080000 cmp dword ptr [ecx+18h],800h ; (ObjectCreateInfo->SecurityDescriptorCharge > SE_DEFAULT_SECURITY_QUOTA)
805b604b 7611 jbe nt!ObpAllocateObject+0x56 (805b605e) ; ┓
805b604d 64a124010000 mov eax,dword ptr fs:[00000124h] ; ┃
805b6053 8b4044 mov eax,dword ptr [eax+44h] ; ┣ (PsGetCurrentProcess() != PsInitialSystemProcess)
805b6056 3b0554a35580 cmp eax,dword ptr [nt!PsInitialSystemProcess (8055a354)] ; ┃
805b605c 7508 jne nt!ObpAllocateObject+0x5e (805b6066) ; ┛
805b605e f60120 test byte ptr [ecx],20h ; (ObjectCreateInfo->Attributes & OBJ_EXCLUSIVE)
805b6061 8955fc mov dword ptr [ebp-4],edx ; ┓ QuotaInfoSize = 0;
805b6064 7407 je nt!ObpAllocateObject+0x65 (805b606d) ; ┛
805b6066 c745fc10000000 mov dword ptr [ebp-4],10h ; QuotaInfoSize = sizeof(OBJECT_HEADER_QUOTA_INFO);
805b606d 8a477d mov al,byte ptr [edi+7Dh] ; --[2 if(ObjectType->TypeInfo.MaintainHandleCount)
805b6070 8b5e04 mov ebx,dword ptr [esi+4] ; --[3 if(ObjectName->Buffer != NULL)
805b6073 f6d8 neg al ; --[2 如果执行NEG指令时操作数为0,则CF将被置0;如果操作数为非零,则CF将被置1
805b6075 1bc0 sbb eax,eax ; --[2 (ObjectType->TypeInfo.MaintainHandleCount)为零时,EAX为0;非零时EAX为-1
805b6077 83e008 and eax,8 ; --[2 得出结果
805b607a f7db neg ebx ; --[3 如果执行NEG指令时操作数为0,则CF将被置0;如果操作数为非零,则CF将被置1
805b607c 894510 mov dword ptr [ebp+10h],eax ; --[2 保存结果
805b607f 8a477e mov al,byte ptr [edi+7Eh] ; --[4 if(ObjectType->TypeInfo.MaintainTypeList)
805b6082 1bdb sbb ebx,ebx ; --[3 if(ObjectName->Buffer != NULL)为零时,EBX为0;非零时EBX为-1
805b6084 83e310 and ebx,10h ; --[3 得出结果 (就保存在EBX)
805b6087 f6d8 neg al ; --[4 如果执行NEG指令时操作数为0,则CF将被置0;如果操作数为非零,则CF将被置1
805b6089 1bc0 sbb eax,eax ; --[4 if(ObjectType->TypeInfo.MaintainTypeList)为零时,EAX为0;非零时EAX为-1
805b608b 83e010 and eax,10h ; --[4 得出结果
805b608e 894514 mov dword ptr [ebp+14h],eax ; --[4 保存结果
--------------------------------------------------------------------------------------------
805b6091 8b4514 mov eax,dword ptr [ebp+14h] ; CreatorInfoSize == 10h
805b6094 8b4dfc mov ecx,dword ptr [ebp-4] ; QuotaInfoSize == 00h
805b6097 03c3 add eax,ebx ; NameInfoSize == 10h
805b6099 034510 add eax,dword ptr [ebp+10h] ; HandleInfoSize == 00h
805b609c 3bfa cmp edi,edx ; (ObjectType == NULL)
805b609e 8d4c0818 lea ecx,[eax+ecx+18h] ; 这句对象头累加操作也非常绝!
805b60a2 740b je nt!ObpAllocateObject+0xa7 (805b60af) ; 我们的执行流 o-----------------------------+
805b60a4 399780000000 cmp dword ptr [edi+80h],edx ; || (ObjectType->TypeInfo.PoolType == NonPagedPool) |
805b60aa 7403 je nt!ObpAllocateObject+0xa7 (805b60af) ; 我们的执行流 o-----------------------------+
805b60ac 33d2 xor edx,edx ; ┓ PoolType = PagedPool; |
805b60ae 42 inc edx ; ┛ |
805b60af 85ff test edi,edi ; ObjectType == NULL ? <-----------------------------/
805b60b1 b84f626a54 mov eax,546A624Fh ; 1. 'TjbO' 我们的执行流 o------------------+
805b60b6 7406 je nt!ObpAllocateObject+0xb6 (805b60be) ; |
805b60b8 8b87ac000000 mov eax,dword ptr [edi+0ACh] ; 2. ObjectType->Key o------------------+
805b60be 0d00000080 or eax,80000000h ; | PROTECTED_POOL <------------------/
805b60c3 50 push eax ; (ARG3) : (ObjectType == NULL ? 'TjbO' : ObjectType->Key) | PROTECTED_POOL
805b60c4 8b4518 mov eax,dword ptr [ebp+18h] ; ┓
805b60c7 03c8 add ecx,eax ; ┣ (ARG2) : HeaderSize + ObjectBodySize
805b60c9 51 push ecx ; ┛
805b60ca 52 push edx ; (ARG1) : PoolType
805b60cb e8b0eff8ff call nt!ExAllocatePoolWithTag (80545080) ; {FUN} : ExAllocatePoolWithTag()
805b60d0 8bd0 mov edx,eax ; ┓
805b60d2 85d2 test edx,edx ; ┣ if(ObjectHeader == NULL)
805b60d4 750a jne nt!ObpAllocateObject+0xd8 (805b60e0) ; ┛
805b60d6 b89a0000c0 mov eax,0C000009Ah ; return STATUS_INSUFFICIENT_RESOURCES;
805b60db e925010000 jmp nt!ObpAllocateObject+0x1fd (805b6205) ; -=[ 程序错误退出 ]=-
--------------------------------------------------------------------------------------------
805b60e0 8b4dfc mov ecx,dword ptr [ebp-4] ; ┓
805b60e3 85c9 test ecx,ecx ; ┣ 1. 我们没有 _OBJECT_HEADER_QUOTA_INFO 结构
805b60e5 7421 je nt!ObpAllocateObject+0x100 (805b6108) ; ┛
805b60e7 8b4508 mov eax,dword ptr [ebp+8] ; ┓
805b60ea 8b4010 mov eax,dword ptr [eax+10h] ; ┣ QuotaInfo->PagedPoolCharge = ObjectCreateInfo->PagedPoolCharge;
805b60ed 8902 mov dword ptr [edx],eax ; ┛
805b60ef 8b4508 mov eax,dword ptr [ebp+8] ; ┓
805b60f2 8b4014 mov eax,dword ptr [eax+14h] ; ┣ QuotaInfo->NonPagedPoolCharge = ObjectCreateInfo->NonPagedPoolCharge;
805b60f5 894204 mov dword ptr [edx+4],eax ; ┛
805b60f8 8b4508 mov eax,dword ptr [ebp+8] ; ┓
805b60fb 8b4018 mov eax,dword ptr [eax+18h] ; ┣ QuotaInfo->SecurityDescriptorCharge = ObjectCreateInfo->SecurityDescriptorCharge;
805b60fe 83620c00 and dword ptr [edx+0Ch],0 ; QuotaInfo->ExclusiveProcess = NULL;
805b6102 894208 mov dword ptr [edx+8],eax ; ┛
805b6105 83c210 add edx,10h ; 对象头指针跳过可选头 _OBJECT_HEADER_QUOTA_INFO
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
805b6108 837d1000 cmp dword ptr [ebp+10h],0 ; ┓ 2. 我们没有 _OBJECT_HEADER_HANDLE_INFO 结构
805b610c 7407 je nt!ObpAllocateObject+0x10d (805b6115) ; ┛
805b610e 83620400 and dword ptr [edx+4],0 ; HandleInfo->SingleEntry.HandleCount = 0;
805b6112 83c208 add edx,8 ; 对象头指针跳过可选头 _OBJECT_HEADER_HANDLE_INFO
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
805b6115 85db test ebx,ebx ; ┓ 3. 我们存在 _OBJECT_HEADER_NAME_INFO 结构
805b6117 7418 je nt!ObpAllocateObject+0x129 (805b6131) ; ┛
805b6119 8b06 mov eax,dword ptr [esi] ; EAX <-- IN PUNICODE_STRING ObjectName
805b611b 894204 mov dword ptr [edx+4],eax ; ┓
805b611e 8b4604 mov eax,dword ptr [esi+4] ; ┣ NameInfo->Name = *ObjectName;
805b6121 832200 and dword ptr [edx],0 ; NameInfo->Directory = NULL;
805b6124 894208 mov dword ptr [edx+8],eax ; ┛
805b6127 c7420c01000000 mov dword ptr [edx+0Ch],1 ; NameInfo->QueryReferences = 1;
805b612e 83c210 add edx,10h ; 对象头指针跳过可选头 _OBJECT_HEADER_NAME_INFO
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
805b6131 837d1400 cmp dword ptr [ebp+14h],0 ; ┓ 4. 我们存在 _OBJECT_HEADER_CREATOR_INFO 结构
805b6135 741f je nt!ObpAllocateObject+0x14e (805b6156) ; ┛
805b6137 6683620c00 and word ptr [edx+0Ch],0 ; CreatorInfo->CreatorBackTraceIndex = 0;
805b613c 64a124010000 mov eax,dword ptr fs:[00000124h] ; ┓
805b6142 8b4044 mov eax,dword ptr [eax+44h] ; ┣ PsGetCurrentProcess()->UniqueProcessId;
805b6145 8b8084000000 mov eax,dword ptr [eax+84h] ; ┛
805b614b 894208 mov dword ptr [edx+8],eax ; CreatorInfo->CreatorUniqueProcess = PsGetCurrentProcess()->UniqueProcessId;
805b614e 895204 mov dword ptr [edx+4],edx ; ┓ InitializeListHead(&CreatorInfo->TypeList);
805b6151 8912 mov dword ptr [edx],edx ; ┛
805b6153 83c210 add edx,10h ; 对象头指针跳过可选头 _OBJECT_HEADER_CREATOR_INFO
--------------------------------------------------------------------------------------------
805b6156 85c9 test ecx,ecx ; ┓ 1. 我们没有 _OBJECT_HEADER_QUOTA_INFO 结构
805b6158 740d je nt!ObpAllocateObject+0x15f (805b6167) ; ┛ if(QuotaInfoSize != 0)
805b615a 024d10 add cl,byte ptr [ebp+10h] ; ┓
805b615d 02cb add cl,bl ; ┃ ObjectHeader->QuotaInfoOffset = (UCHAR)(QuotaInfoSize + \
805b615f 024d14 add cl,byte ptr [ebp+14h] ; ┣ HandleInfoSize + \
805b6162 884a0e mov byte ptr [edx+0Eh],cl ; ┃ NameInfoSize + \
805b6165 eb04 jmp nt!ObpAllocateObject+0x163 (805b616b) ; ┛ CreatorInfoSize);
805b6167 c6420e00 mov byte ptr [edx+0Eh],0 ; ObjectHeader->QuotaInfoOffset = 0;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
805b616b 837d1000 cmp dword ptr [ebp+10h],0 ; ┓ 2. 我们没有 _OBJECT_HEADER_HANDLE_INFO 结构
805b616f 740d je nt!ObpAllocateObject+0x176 (805b617e) ; ┛ if(HandleInfoSize != 0)
805b6171 8a4510 mov al,byte ptr [ebp+10h] ; ┓
805b6174 02c3 add al,bl ; ┃ ObjectHeader->HandleInfoOffset = (UCHAR)(HandleInfoSize + \
805b6176 024514 add al,byte ptr [ebp+14h] ; ┣ NameInfoSize + \
805b6179 88420d mov byte ptr [edx+0Dh],al ; ┃ CreatorInfoSize);
805b617c eb04 jmp nt!ObpAllocateObject+0x17a (805b6182) ; ┛
805b617e c6420d00 mov byte ptr [edx+0Dh],0 ; ObjectHeader->HandleInfoOffset = 0;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
805b6182 33c9 xor ecx,ecx ; ┓ 3. 我们存在 _OBJECT_HEADER_NAME_INFO 结构
805b6184 3bd9 cmp ebx,ecx ; ┛ if(NameInfoSize != 0)
805b6186 7408 je nt!ObpAllocateObject+0x188 (805b6190) ; ┓
805b6188 025d14 add bl,byte ptr [ebp+14h] ; ┣ ObjectHeader->NameInfoOffset = (UCHAR)(NameInfoSize + \
805b618b 885a0c mov byte ptr [edx+0Ch],bl ; ┃ CreatorInfoSize);
805b618e eb04 jmp nt!ObpAllocateObject+0x18c (805b6194) ; ┛
805b6190 c6420c00 mov byte ptr [edx+0Ch],0 ; ObjectHeader->NameInfoOffset = 0;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
805b6194 394d14 cmp dword ptr [ebp+14h],ecx ; ┓ 4. 我们存在 _OBJECT_HEADER_CREATOR_INFO 结构,但没有单独的域表示 CreatorInfoOffset
805b6197 c6420f01 mov byte ptr [edx+0Fh],1 ; ObjectHeader->Flags = OB_FLAG_NEW_OBJECT;
805b619b 7404 je nt!ObpAllocateObject+0x199 (805b61a1) ; ┛
805b619d c6420f05 mov byte ptr [edx+0Fh],5 ; OB_FLAG_NEW_OBJECT | OB_FLAG_CREATOR_INFO
805b61a1 394d10 cmp dword ptr [ebp+10h],ecx ; ┓ if(HandleInfoSize != 0) {
805b61a4 7404 je nt!ObpAllocateObject+0x1a2 (805b61aa) ; ┣ ObjectHeader->Flags |= OB_FLAG_SINGLE_HANDLE_ENTRY;
805b61a6 804a0f40 or byte ptr [edx+0Fh],40h ; ┛ }
805b61aa 807d0c00 cmp byte ptr [ebp+0Ch],0 ; ┓ if(OwnershipMode == KernelMode)
805b61ae c70201000000 mov dword ptr [edx],1 ; ObjectHeader->PointerCount = 1; <<<<
805b61b4 894a04 mov dword ptr [edx+4],ecx ; ObjectHeader->HandleCount = 0; <<<<
805b61b7 897a08 mov dword ptr [edx+8],edi ; ObjectHeader->Type = ObjectType; <<<<
805b61ba 7504 jne nt!ObpAllocateObject+0x1b8 (805b61c0) ; ┣ ObjectHeader->Flags |= OB_FLAG_KERNEL_OBJECT;
805b61bc 804a0f02 or byte ptr [edx+0Fh],2 ; ┛
805b61c0 8b4508 mov eax,dword ptr [ebp+8] ; ┓
805b61c3 3bc1 cmp eax,ecx ; ┣ (ObjectCreateInfo != NULL)
805b61c5 7412 je nt!ObpAllocateObject+0x1d1 (805b61d9) ; ┛
805b61c7 f60010 test byte ptr [eax],10h ; && (ObjectCreateInfo->Attributes & OBJ_PERMANENT)
805b61ca 7404 je nt!ObpAllocateObject+0x1c8 (805b61d0) ; ┓ ObjectHeader->Flags |= OB_FLAG_PERMANENT_OBJECT;
805b61cc 804a0f10 or byte ptr [edx+0Fh],10h ; ┛
805b61d0 f60020 test byte ptr [eax],20h ; && (ObjectCreateInfo->Attributes & OBJ_EXCLUSIVE)
805b61d3 7404 je nt!ObpAllocateObject+0x1d1 (805b61d9) ; ┓ ObjectHeader->Flags |= OB_FLAG_EXCLUSIVE_OBJECT;
805b61d5 804a0f08 or byte ptr [edx+0Fh],8 ; ┛
805b61d9 3bf9 cmp edi,ecx ; ┓ if(ObjectType != NULL)
805b61db 894210 mov dword ptr [edx+10h],eax ; ObjectHeader->ObjectCreateInfo = ObjectCreateInfo; <<<<
805b61de 894a14 mov dword ptr [edx+14h],ecx ; ObjectHeader->SecurityDescriptor = NULL; <<<<
805b61e1 741b je nt!ObpAllocateObject+0x1f6 (805b61fe) ; ┛
805b61e3 8d7750 lea esi,[edi+50h] ; ┓
805b61e6 89750c mov dword ptr [ebp+0Ch],esi ; ┃
805b61e9 b801000000 mov eax,1 ; ┣ InterlockedIncrement((PLONG)&ObjectType->TotalNumberOfObjects);
805b61ee 8b4d0c mov ecx,dword ptr [ebp+0Ch] ; ┃
805b61f1 0fc101 xadd dword ptr [ecx],eax ; ┃
805b61f4 8b36 mov esi,dword ptr [esi] ; ┛
805b61f6 3b7758 cmp esi,dword ptr [edi+58h] ; if(ObjectType->TotalNumberOfObjects > ObjectType->HighWaterNumberOfObjects) {
805b61f9 7603 jbe nt!ObpAllocateObject+0x1f6 (805b61fe) ; ObjectType->HighWaterNumberOfObjects = ObjectType->TotalNumberOfObjects;
805b61fb 897758 mov dword ptr [edi+58h],esi ; }
--------------------------------------------------------------------------------------------
805b61fe 8b451c mov eax,dword ptr [ebp+1Ch] ; ┓ *ReturnedObjectHeader = ObjectHeader;
805b6201 8910 mov dword ptr [eax],edx ; ┛
805b6203 33c0 xor eax,eax ; return STATUS_SUCCESS;
805b6205 5f pop edi ; ┓
805b6206 5e pop esi ; ┃
805b6207 5b pop ebx ; ┣ Epilogue
805b6208 c9 leave ; ┃
805b6209 c21800 ret 18h ; ┛
对于Win内核程序员来说“对象管理器”应该是一门必修课。众所周知,在对象体的负向偏移处,还有一个大小为18字节的 _OBJECT_HEADER 以及 4个(至多)大小不等的可选头部:
+-----------------------------------------------------------------+
+------->| ( _OBJECT_HEADER_QUOTA_INFO ) |
| +---->| ( _OBJECT_HEADER_HANDLE_INFO ) |
| | +->| ( _OBJECT_HEADER_NAME_INFO ) |
| | | | ( _OBJECT_HEADER_CREATOR_INFO ) |
| | | +------------------------[ Object Header ]------------------------+
| | | | nt!_OBJECT_HEADER |
| | | | +0x000 PointerCount : Int4B |
| | | | +0x004 HandleCount : Int4B |
| | | | +0x004 NextToFree : Ptr32 Void |
| | | | +0x008 Type : Ptr32 _OBJECT_TYPE |
| | +-o| +0x00c NameInfoOffset : UChar |
| +----o| +0x00d HandleInfoOffset : UChar |
+-------o| +0x00e QuotaInfoOffset : UChar |
| +0x00f Flags : UChar |
| +0x010 ObjectCreateInfo : Ptr32 _OBJECT_CREATE_INFORMATION |
| +0x010 QuotaBlockCharged : Ptr32 Void |
| +0x014 SecurityDescriptor : Ptr32 Void |
| +0x018 Body : _QUAD |
+-------------------------[ Object Body ]-------------------------+
| OBJECT_DIRECTORY, DRIVER_OBJECT, DEVICE_OBJECT, FILE_OBJECT... |
+-----------------------------------------------------------------+
那么你是否好奇过,这些结构是在哪里诞生的? 相互关系怎样? 顺序怎样? 等等等等,Well,这些答案都在函数 nt!ObpAllocateObject 里。它的大致内容是这样的:
1. 根据函数的参数一等 (IN POBJECT_CREATE_INFORMATION ObjectCreateInfo) 确定4个可选头部是否存在。
2. 计算首部大小,并根据函数的参数五 (IN ULONG ObjectBodySize) 分配整个对象的空间(包括可选头部、头部和对象体)
3. 如空间分配成功,依次给可选头部初始化
4. 初始化 _OBJECT_HEADER
5. 返回 _OBJECT_HEADER 的指针,程序结束,外层的封装函数继续初始化对象体
可以想见,这个函数的调用会非常的频繁,因为每一个对象(包括头部)都是由它来分配空间并初始化(对象体是由外部的封装函数初始化)的,所以,在这里 Inline Hook 一下我们可以干坏事... 先打住~
以 Type Object Type 为例,它的头结构是这样的(_OBJECT_BODY 由外层封装 nt!ObCreateObjectType 初始化):
kd> dt nt!_OBJECT_HEADER_NAME_INFO 82ded5d0-20
+0x000 Directory : (null)
+0x004 Name : _UNICODE_STRING "Type"
+0x00c QueryReferences : 1
kd> dt nt!_OBJECT_HEADER_CREATOR_INFO 82ded5d0-10
+0x000 TypeList : _LIST_ENTRY [ 0x82ded5c0 - 0x82ded5c0 ] ; 这个域是可以遍历的 ObpTypeObjectType
+0x008 CreatorUniqueProcess : (null)
+0x00c CreatorBackTraceIndex : 0
+0x00e Reserved : 0
kd> dt nt!_OBJECT_HEADER 82ded5d0
+0x000 PointerCount : 1 ; 指针引用计数
+0x004 HandleCount : 0 ; 句柄引用计数
+0x004 NextToFree : (null)
+0x008 Type : (null) ; 注意这里,目前仍是 NULL
+0x00c NameInfoOffset : 0x20 ' '
+0x00d HandleInfoOffset : 0 ''
+0x00e QuotaInfoOffset : 0 ''
+0x00f Flags : 0x7 '' ; OB_FLAG_NEW_OBJECT | OB_FLAG_CREATOR_INFO | OB_FLAG_KERNEL_OBJECT
+0x010 ObjectCreateInfo : (null)
+0x010 QuotaBlockCharged : (null)
+0x014 SecurityDescriptor : (null)
+0x018 Body : _QUAD
那么,这个函数的汇编代码有什么特别的地方吗? 其实也什么... 只是看惯了中规中矩的代码后这里有几个地方值得讲一讲:
1. 在确定4个可选头部是否存在的时候,汇编代码混合使用了 NEG / SBB / AND 指令(1个用普通方法,3个用 NEG / SBB),而我个人比较偏爱后者
C的代码是这样的:
..............
1) /* Check if we have quota */
if (((ObjectCreateInfo->PagedPoolCharge != ObjectType->TypeInfo.DefaultPagedPoolCharge ||
ObjectCreateInfo->NonPagedPoolCharge != ObjectType->TypeInfo.DefaultNonPagedPoolCharge ||
ObjectCreateInfo->SecurityDescriptorCharge > SE_DEFAULT_SECURITY_QUOTA) &&
PsGetCurrentProcess() != PsInitialSystemProcess) ||
(ObjectCreateInfo->Attributes & OBJ_EXCLUSIVE)) {
/* Set quota size */
QuotaInfoSize = sizeof( OBJECT_HEADER_QUOTA_INFO );
} else {
/* No Quota */
QuotaInfoSize = 0;
}
2) /* Check if we have a handle database */
if (ObjectType->TypeInfo.MaintainHandleCount) {
/* Set handle database size */
HandleInfoSize = sizeof( OBJECT_HEADER_HANDLE_INFO );
} else {
/* None */
HandleInfoSize = 0;
}
..............
1) 和 2) 的语义基本一致,可生成的汇编代码却大相径庭:1) 的中规中矩;2) 的简洁高效(连选择分支的跳转语句都没有):
1) 805b602e 8b4110 mov eax,dword ptr [ecx+10h]
805b6031 3b8784000000 cmp eax,dword ptr [edi+84h]
805b6037 7514 jne nt!ObpAllocateObject+0x45 (805b604d)
805b6039 8b4114 mov eax,dword ptr [ecx+14h]
805b603c 3b8788000000 cmp eax,dword ptr [edi+88h]
805b6042 7509 jne nt!ObpAllocateObject+0x45 (805b604d)
805b6044 81791800080000 cmp dword ptr [ecx+18h],800h
805b604b 7611 jbe nt!ObpAllocateObject+0x56 (805b605e)
805b604d 64a124010000 mov eax,dword ptr fs:[00000124h]
805b6053 8b4044 mov eax,dword ptr [eax+44h]
805b6056 3b0554a35580 cmp eax,dword ptr [nt!PsInitialSystemProcess (8055a354)]
805b605c 7508 jne nt!ObpAllocateObject+0x5e (805b6066)
805b605e f60120 test byte ptr [ecx],20h
805b6061 8955fc mov dword ptr [ebp-4],edx
805b6064 7407 je nt!ObpAllocateObject+0x65 (805b606d)
805b6066 c745fc10000000 mov dword ptr [ebp-4],10h
2) 805b606d 8a477d mov al,byte ptr [edi+7Dh]
805b6073 f6d8 neg al
805b6075 1bc0 sbb eax,eax
805b6077 83e008 and eax,8
805b607c 894510 mov dword ptr [ebp+10h],eax
熟悉反汇编的人应该经常能看到 neg / sbb 对,这种代码在处理某些条件语句上往往有很好的效率体现。此时您一定还会说我偷梁换柱,因为 1) 的入口条件过于复杂,是不可以用 neg 指令影响 CF 做简单判定的 —— 嘿嘿,我这里只是示意一下编译上的差别,而这也正是 Win 编译器在编译上述代码时生成了3个简单句1个复杂句的原因。neg / sbb 指令对的原理请参见代码注释,这里不再累述。
2. nt!ObpAllocateObject 函数局部变量的使用经过了优化
C的代码:
ULONG QuotaInfoSize;
ULONG HandleInfoSize;
ULONG NameInfoSize;
ULONG CreatorInfoSize;
.........
//
// Now compute the total header size
//
HeaderSize = QuotaInfoSize +
HandleInfoSize +
NameInfoSize +
CreatorInfoSize +
FIELD_OFFSET( OBJECT_HEADER, Body );
.........
四个本应该在局部变量空间上的变量被优化成:1个在局部(栈)上、2个在参数空间上、一个在寄存器里,非常和谐...
805b6091 8b4514 mov eax,dword ptr [ebp+14h] ; CreatorInfoSize == 10h - 占用了参数空间!
805b6094 8b4dfc mov ecx,dword ptr [ebp-4] ; QuotaInfoSize == 00h
805b6097 03c3 add eax,ebx ; NameInfoSize == 10h - 占用了 EBX 寄存器!
805b6099 034510 add eax,dword ptr [ebp+10h] ; HandleInfoSize == 00h - 占用了参数空间!
805b609e 8d4c0818 lea ecx,[eax+ecx+18h] ; 这句对象头累加操作也非常绝!
既然聊到了对象管理器,那下面显然还应该聊聊句柄表。句柄表是由函数 nt!ExpAllocateHandleTable 创建的,它的第一次调用位于 nt!ObInitSystem,目标是 SYSTEM 进程。
nt!ExpAllocateHandleTable:
8060325c 8bff mov edi,edi ; ┓
8060325e 55 push ebp ; ┣ Prologue
8060325f 8bec mov ebp,esp ; ┛
80603261 56 push esi ; : HandleTable
80603262 684f627462 push 6274624Fh ; (ARG) : 'btbO'
80603267 6a44 push 44h ; (ARG) : sizeof(HANDLE_TABLE)
80603269 6a01 push 1 ; (ARG) : PagedPool
8060326b e8101ef4ff call nt!ExAllocatePoolWithTag (80545080) ; {FUN} : ExAllocatePoolWithTag()
80603270 8bf0 mov esi,eax ; ┓
80603272 85f6 test esi,esi ; ┣ ExAllocatePoolWithTag() 返回值判断
80603274 0f84b1000000 je nt!ExpAllocateHandleTable+0xcf (8060332b) ; ┛
8060327a 53 push ebx ; : Process
8060327b 8b5d08 mov ebx,dword ptr [ebp+8] ; ┓ EBX <-- Process
8060327e 85db test ebx,ebx ; ┣ if(ARGUMENT_PRESENT(Process))
80603280 741b je nt!ExpAllocateHandleTable+0x41 (8060329d) ; ┛
80603282 6a44 push 44h ; (ARG) : sizeof(HANDLE_TABLE)
80603284 53 push ebx ; (ARG) : Process
80603285 e8c641f2ff call nt!PsChargeProcessPagedPoolQuota (80527450) ; {FUN} : PsChargeProcessPagedPoolQuota()
8060328a 85c0 test eax,eax ; ┓ 函数 ExpAllocateHandleTable() 的返回值判断
8060328c 7d0f jge nt!ExpAllocateHandleTable+0x41 (8060329d) ; ┛ 正确的程序执行流
8060328e 6a00 push 0 ; (ARG) : 0
80603290 56 push esi ; (ARG) : HandleTable
80603291 e85017f4ff call nt!ExFreePoolWithTag (805449e6) ; {FUN} : ExFreePoolWithTag()
80603296 33c0 xor eax,eax ; return NULL;
80603298 e98d000000 jmp nt!ExpAllocateHandleTable+0xce (8060332a) ; -=[ 程序错误退出 ]=-
--------------------------------------------------------------------------------------------
8060329d 57 push edi ; : 字符串操作目的地址
8060329e 6a11 push 11h ; ┓ sizeof(HANDLE_TABLE)
806032a0 59 pop ecx ; ┛ 循环计数 44h
806032a1 6800100000 push 1000h ; (ARG) : TABLE_PAGE_SIZE (编译器优化-对应下面的函数)
806032a6 33c0 xor eax,eax ; ZERO it
806032a8 8bfe mov edi,esi ; 字符串操作目的地址-刚才分配的句柄表
806032aa 53 push ebx ; (ARG) : Process (编译器优化-对应下面的函数)
806032ab f3ab rep stos dword ptr es:[edi] ; {FUN} : RtlZeroMemory()
806032ad e81afeffff call nt!ExpAllocateTablePagedPool (806030cc) ; {FUN} : ExpAllocateTablePagedPool() 注意!这里和C代码不同
806032b2 85c0 test eax,eax ; ┓ 函数 ExpAllocateTablePagedPool() 的返回值判断
806032b4 7517 jne nt!ExpAllocateHandleTable+0x71 (806032cd) ; ┛ 正确的程序执行流
806032b6 50 push eax ; (ARG) : 0
806032b7 56 push esi ; (ARG) : HandleTable
806032b8 e82917f4ff call nt!ExFreePoolWithTag (805449e6) ; {FUN} : ExFreePoolWithTag()
806032bd 85db test ebx,ebx ; ┓
806032bf 7408 je nt!ExpAllocateHandleTable+0x6d (806032c9) ; ┃
806032c1 6a44 push 44h ; ┣ {FUN} : PsChargeProcessPagedPoolQuota()
806032c3 53 push ebx ; ┃
806032c4 e89542f2ff call nt!PsReturnProcessPagedPoolQuota (8052755e) ; ┛
806032c9 33c0 xor eax,eax ; return NULL;
806032cb eb5c jmp nt!ExpAllocateHandleTable+0xcd (80603329) ; -=[ 程序错误退出 ]=-
--------------------------------------------------------------------------------------------
806032cd 6a04 push 4 ; ┓ 下面的代码循环初始化刚刚创建的0级句柄表
806032cf 5a pop edx ; ┛ 这里的 EDX 是循环的累加基数
806032d0 8d4804 lea ecx,[eax+4] ; 取0级句柄表第一项 NextFreeTableEntry 域的地址
806032d3 8906 mov dword ptr [esi],eax ; 核心!HandleTable->TableCode = (ULONG_PTR)HandleTableTable;
806032d5 8bfa mov edi,edx ; EDI <-- 要初始化成的初值 4
806032d7 8bd9 mov ebx,ecx ; EBX <-- 要初始化的首地址 (见上面)
806032d9 893b mov dword ptr [ebx],edi ; 赋值操作
806032db 03fa add edi,edx ; [下一次操作准备] 下一个值
806032dd 83c308 add ebx,8 ; [下一次操作准备] 下一个地址
806032e0 81ff04080000 cmp edi,804h ; ┓ 循环次数,共 200h 次,一个页的大小
806032e6 72f1 jb nt!ExpAllocateHandleTable+0x7d (806032d9) ; ┛
--------------------------------------------------------------------------------------------
806032e8 c701feffffff mov dword ptr [ecx],0FFFFFFFEh ; ECX(NextFreeTableEntry) = EX_ADDITIONAL_INFO_SIGNATURE;
806032ee 33c9 xor ecx,ecx ; ┓ 清空0级句柄表最后一项表示结束标志
806032f0 8988fc0f0000 mov dword ptr [eax+0FFCh],ecx ; ┛
806032f6 8b4508 mov eax,dword ptr [ebp+8] ; 取栈上的参数 Process
806032f9 895630 mov dword ptr [esi+30h],edx ; HandleTable->FirstFree = HANDLE_VALUE_INC;
806032fc c7463800080000 mov dword ptr [esi+38h],800h ; HandleTable->NextHandleNeedingPool = LOWLEVEL_COUNT * HANDLE_VALUE_INC;
80603303 894604 mov dword ptr [esi+4],eax ; HandleTable->QuotaProcess = Process;
80603306 64a124010000 mov eax,dword ptr fs:[00000124h] ; EAX <-- PsGetCurrentThread()
8060330c 8b4044 mov eax,dword ptr [eax+44h] ; EAX <-- PsGetCurrentProcess()
8060330f 8b8084000000 mov eax,dword ptr [eax+84h] ; EAX <-- PsGetCurrentProcess()->UniqueProcessId;
80603315 894608 mov dword ptr [esi+8],eax ; HandleTable->UniqueProcessId = PsGetCurrentProcess()->UniqueProcessId;
80603318 33c0 xor eax,eax ; 为下面的 ExInitializePushLock() 准备
8060331a 894e40 mov dword ptr [esi+40h],ecx ; HandleTable->Flags = 0;
8060331d 8d7e0c lea edi,[esi+0Ch] ; ┓ #define HANDLE_TABLE_LOCKS 4
80603320 ab stos dword ptr es:[edi] ; ┃
80603321 ab stos dword ptr es:[edi] ; ┣ for (Idx = 0; Idx < HANDLE_TABLE_LOCKS; Idx++) {
80603322 ab stos dword ptr es:[edi] ; ┃ ExInitializePushLock (&HandleTable->HandleTableLock[Idx]);
80603323 ab stos dword ptr es:[edi] ; ┛ }
80603324 894e24 mov dword ptr [esi+24h],ecx ; ExInitializePushLock(&HandleTable->HandleContentionEvent);
80603327 8bc6 mov eax,esi ; return HandleTable;
80603329 5f pop edi ; ┓
8060332a 5b pop ebx ; ┣ 清除栈上的局部变量
8060332b 5e pop esi ; ┛
8060332c 5d pop ebp ; 还原局部堆栈指针
8060332d c20400 ret 4 ; 注意参数!WRK1.2 的C代码是这样的:HandleTable = ExpAllocateHandleTable( Process, TRUE );
我们都清楚对象是基于进程的,为了进程间更好的共享内核对象、更好的控制对象的生命周期,Windows 设计了引用计数域。除了这个域以外 Windows 还做了更多的封装 —— 引申出 _OBJECT_HEADER 的概念(这是 Linux 所没有的),之后,针对于对象头的操作(即方法),Windows 则更是抽象出了所谓的 OB 模块。
既然对象是基于进程的,那么设计一套关联二者的机制便是理所应当。Windows 的设计方案是“句柄表”,查表的 KEY 就是大家经常要打交道的“句柄值”。可以想见 —— 32位的句柄值如何解释一定和句柄表的设计密切相关 —— 而历来这也是微软隐藏最好的地方...
句柄表的设计不是一蹴而就的,它涉及到效率、内存使用量等多个方面。从 Windows 98 到 Windows 2000、windows XP 句柄表一直在变,而这句话的潜台词就是“32位句柄值”的解释也是一直在改变的。
Win-2000 的句柄表设计上颇有 Intel 分页机制的影子,以 Intel 4KB 页大小的映射粒度为例:
CR3寄存器定基址、2^10寻址PDE、2^10寻址PTE、最后2^12寻址实际的物理地址,设计非常精巧,整个32位(线性地址)没有一点浪费,给人感觉很和谐...
而 M$ 再次 Copy /* Steve Jobs : You've Got to Find What You Love */ 了别人的设计思路,搞了 Win-2000 的句柄表蓝本,他们空出了8位多余位(而这8位的“多余位”还是打散的,以3:1的比例分布在句柄值的开头和结尾处),用剩下的24位做句柄表的索引,因此32位的句柄值必须先除以4(空出2个多余位),然后2^8索引第0级句柄表;2^8索引第1级句柄表;最后2^8索引实际的句柄项。
那么您有没有想过 Win-2000 下一个进程的最少句柄表开销是多少? 答案是:256*4 + 256*4 + 256*8 = 1KB。1KB的空间只能存放 256个 句柄项,您不觉得其利用率少的可笑吗?
也许微软也发现了,所以他们在 Win-XP 时彻底改变了句柄表的设计 —— 改变的重点就是句柄表的内存利用率。新的级联机制真正做到按需划分,默认情况下只有包含句柄项的第0级。此时,在同样占内存1KB的情况下,HANDLE-ENTRY 的个数是 512,比 Win-2000 的利用率整整提高了100%。
但这并不值得称赞!我真正想说的是,连句柄转换这套机制其实都是多余,这套机制本身就是在浪费效率,而最终的目的只有微软自己心里清楚 —— 他们比 Linux 内核有着更多的考虑 —— 他们需要隐藏信息、他们不想公开数据结构、他们不想给程序员指针、他们想把内核数据结构完全的封装起来,而操作数据结构的任务由他们来代理,程序员得到的只有那该死的“令牌” —— 句柄...... 显然,他们太有才了。
OK,OK,言归正传,函数 nt!ExpAllocateHandleTable 跑完之后,生成的第0级句柄表在内存里是这样的:
至于原因请参看下面的注释
//
// Now setup the free list. We do this by chaining together the free
// entries such that each free entry give the next free index (i.e.,
// like a fat chain). The chain is terminated with a 0. Note that
// we'll skip handle zero because our callers will get that value
// confused with null.
//
kd> dd e1002000 e1002ffc
e1002000 00000000 fffffffe 00000000 00000008
e1002010 00000000 0000000c 00000000 00000010
e1002020 00000000 00000014 00000000 00000018
e1002030 00000000 0000001c 00000000 00000020
e1002040 00000000 00000024 00000000 00000028
e1002050 00000000 0000002c 00000000 00000030
e1002060 00000000 00000034 00000000 00000038
e1002070 00000000 0000003c 00000000 00000040
e1002080 00000000 00000044 00000000 00000048
e1002090 00000000 0000004c 00000000 00000050
e10020a0 00000000 00000054 00000000 00000058
e10020b0 00000000 0000005c 00000000 00000060
e10020c0 00000000 00000064 00000000 00000068
e10020d0 00000000 0000006c 00000000 00000070
e10020e0 00000000 00000074 00000000 00000078
e10020f0 00000000 0000007c 00000000 00000080
e1002100 00000000 00000084 00000000 00000088
e1002110 00000000 0000008c 00000000 00000090
e1002120 00000000 00000094 00000000 00000098
e1002130 00000000 0000009c 00000000 000000a0
e1002140 00000000 000000a4 00000000 000000a8
e1002150 00000000 000000ac 00000000 000000b0
e1002160 00000000 000000b4 00000000 000000b8
e1002170 00000000 000000bc 00000000 000000c0
e1002180 00000000 000000c4 00000000 000000c8
e1002190 00000000 000000cc 00000000 000000d0
e10021a0 00000000 000000d4 00000000 000000d8
e10021b0 00000000 000000dc 00000000 000000e0
e10021c0 00000000 000000e4 00000000 000000e8
e10021d0 00000000 000000ec 00000000 000000f0
e10021e0 00000000 000000f4 00000000 000000f8
e10021f0 00000000 000000fc 00000000 00000100
e1002200 00000000 00000104 00000000 00000108
e1002210 00000000 0000010c 00000000 00000110
e1002220 00000000 00000114 00000000 00000118
e1002230 00000000 0000011c 00000000 00000120
e1002240 00000000 00000124 00000000 00000128
e1002250 00000000 0000012c 00000000 00000130
e1002260 00000000 00000134 00000000 00000138
e1002270 00000000 0000013c 00000000 00000140
e1002280 00000000 00000144 00000000 00000148
e1002290 00000000 0000014c 00000000 00000150
e10022a0 00000000 00000154 00000000 00000158
e10022b0 00000000 0000015c 00000000 00000160
e10022c0 00000000 00000164 00000000 00000168
e10022d0 00000000 0000016c 00000000 00000170
e10022e0 00000000 00000174 00000000 00000178
e10022f0 00000000 0000017c 00000000 00000180
e1002300 00000000 00000184 00000000 00000188
e1002310 00000000 0000018c 00000000 00000190
e1002320 00000000 00000194 00000000 00000198
e1002330 00000000 0000019c 00000000 000001a0
e1002340 00000000 000001a4 00000000 000001a8
e1002350 00000000 000001ac 00000000 000001b0
e1002360 00000000 000001b4 00000000 000001b8
e1002370 00000000 000001bc 00000000 000001c0
e1002380 00000000 000001c4 00000000 000001c8
e1002390 00000000 000001cc 00000000 000001d0
e10023a0 00000000 000001d4 00000000 000001d8
e10023b0 00000000 000001dc 00000000 000001e0
e10023c0 00000000 000001e4 00000000 000001e8
e10023d0 00000000 000001ec 00000000 000001f0
e10023e0 00000000 000001f4 00000000 000001f8
e10023f0 00000000 000001fc 00000000 00000200
e1002400 00000000 00000204 00000000 00000208
e1002410 00000000 0000020c 00000000 00000210
e1002420 00000000 00000214 00000000 00000218
e1002430 00000000 0000021c 00000000 00000220
e1002440 00000000 00000224 00000000 00000228
e1002450 00000000 0000022c 00000000 00000230
e1002460 00000000 00000234 00000000 00000238
e1002470 00000000 0000023c 00000000 00000240
e1002480 00000000 00000244 00000000 00000248
e1002490 00000000 0000024c 00000000 00000250
e10024a0 00000000 00000254 00000000 00000258
e10024b0 00000000 0000025c 00000000 00000260
e10024c0 00000000 00000264 00000000 00000268
e10024d0 00000000 0000026c 00000000 00000270
e10024e0 00000000 00000274 00000000 00000278
e10024f0 00000000 0000027c 00000000 00000280
e1002500 00000000 00000284 00000000 00000288
e1002510 00000000 0000028c 00000000 00000290
e1002520 00000000 00000294 00000000 00000298
e1002530 00000000 0000029c 00000000 000002a0
e1002540 00000000 000002a4 00000000 000002a8
e1002550 00000000 000002ac 00000000 000002b0
e1002560 00000000 000002b4 00000000 000002b8
e1002570 00000000 000002bc 00000000 000002c0
e1002580 00000000 000002c4 00000000 000002c8
e1002590 00000000 000002cc 00000000 000002d0
e10025a0 00000000 000002d4 00000000 000002d8
e10025b0 00000000 000002dc 00000000 000002e0
e10025c0 00000000 000002e4 00000000 000002e8
e10025d0 00000000 000002ec 00000000 000002f0
e10025e0 00000000 000002f4 00000000 000002f8
e10025f0 00000000 000002fc 00000000 00000300
e1002600 00000000 00000304 00000000 00000308
e1002610 00000000 0000030c 00000000 00000310
e1002620 00000000 00000314 00000000 00000318
e1002630 00000000 0000031c 00000000 00000320
e1002640 00000000 00000324 00000000 00000328
e1002650 00000000 0000032c 00000000 00000330
e1002660 00000000 00000334 00000000 00000338
e1002670 00000000 0000033c 00000000 00000340
e1002680 00000000 00000344 00000000 00000348
e1002690 00000000 0000034c 00000000 00000350
e10026a0 00000000 00000354 00000000 00000358
e10026b0 00000000 0000035c 00000000 00000360
e10026c0 00000000 00000364 00000000 00000368
e10026d0 00000000 0000036c 00000000 00000370
e10026e0 00000000 00000374 00000000 00000378
e10026f0 00000000 0000037c 00000000 00000380
e1002700 00000000 00000384 00000000 00000388
e1002710 00000000 0000038c 00000000 00000390
e1002720 00000000 00000394 00000000 00000398
e1002730 00000000 0000039c 00000000 000003a0
e1002740 00000000 000003a4 00000000 000003a8
e1002750 00000000 000003ac 00000000 000003b0
e1002760 00000000 000003b4 00000000 000003b8
e1002770 00000000 000003bc 00000000 000003c0
e1002780 00000000 000003c4 00000000 000003c8
e1002790 00000000 000003cc 00000000 000003d0
e10027a0 00000000 000003d4 00000000 000003d8
e10027b0 00000000 000003dc 00000000 000003e0
e10027c0 00000000 000003e4 00000000 000003e8
e10027d0 00000000 000003ec 00000000 000003f0
e10027e0 00000000 000003f4 00000000 000003f8
e10027f0 00000000 000003fc 00000000 00000400
e1002800 00000000 00000404 00000000 00000408
e1002810 00000000 0000040c 00000000 00000410
e1002820 00000000 00000414 00000000 00000418
e1002830 00000000 0000041c 00000000 00000420
e1002840 00000000 00000424 00000000 00000428
e1002850 00000000 0000042c 00000000 00000430
e1002860 00000000 00000434 00000000 00000438
e1002870 00000000 0000043c 00000000 00000440
e1002880 00000000 00000444 00000000 00000448
e1002890 00000000 0000044c 00000000 00000450
e10028a0 00000000 00000454 00000000 00000458
e10028b0 00000000 0000045c 00000000 00000460
e10028c0 00000000 00000464 00000000 00000468
e10028d0 00000000 0000046c 00000000 00000470
e10028e0 00000000 00000474 00000000 00000478
e10028f0 00000000 0000047c 00000000 00000480
e1002900 00000000 00000484 00000000 00000488
e1002910 00000000 0000048c 00000000 00000490
e1002920 00000000 00000494 00000000 00000498
e1002930 00000000 0000049c 00000000 000004a0
e1002940 00000000 000004a4 00000000 000004a8
e1002950 00000000 000004ac 00000000 000004b0
e1002960 00000000 000004b4 00000000 000004b8
e1002970 00000000 000004bc 00000000 000004c0
e1002980 00000000 000004c4 00000000 000004c8
e1002990 00000000 000004cc 00000000 000004d0
e10029a0 00000000 000004d4 00000000 000004d8
e10029b0 00000000 000004dc 00000000 000004e0
e10029c0 00000000 000004e4 00000000 000004e8
e10029d0 00000000 000004ec 00000000 000004f0
e10029e0 00000000 000004f4 00000000 000004f8
e10029f0 00000000 000004fc 00000000 00000500
e1002a00 00000000 00000504 00000000 00000508
e1002a10 00000000 0000050c 00000000 00000510
e1002a20 00000000 00000514 00000000 00000518
e1002a30 00000000 0000051c 00000000 00000520
e1002a40 00000000 00000524 00000000 00000528
e1002a50 00000000 0000052c 00000000 00000530
e1002a60 00000000 00000534 00000000 00000538
e1002a70 00000000 0000053c 00000000 00000540
e1002a80 00000000 00000544 00000000 00000548
e1002a90 00000000 0000054c 00000000 00000550
e1002aa0 00000000 00000554 00000000 00000558
e1002ab0 00000000 0000055c 00000000 00000560
e1002ac0 00000000 00000564 00000000 00000568
e1002ad0 00000000 0000056c 00000000 00000570
e1002ae0 00000000 00000574 00000000 00000578
e1002af0 00000000 0000057c 00000000 00000580
e1002b00 00000000 00000584 00000000 00000588
e1002b10 00000000 0000058c 00000000 00000590
e1002b20 00000000 00000594 00000000 00000598
e1002b30 00000000 0000059c 00000000 000005a0
e1002b40 00000000 000005a4 00000000 000005a8
e1002b50 00000000 000005ac 00000000 000005b0
e1002b60 00000000 000005b4 00000000 000005b8
e1002b70 00000000 000005bc 00000000 000005c0
e1002b80 00000000 000005c4 00000000 000005c8
e1002b90 00000000 000005cc 00000000 000005d0
e1002ba0 00000000 000005d4 00000000 000005d8
e1002bb0 00000000 000005dc 00000000 000005e0
e1002bc0 00000000 000005e4 00000000 000005e8
e1002bd0 00000000 000005ec 00000000 000005f0
e1002be0 00000000 000005f4 00000000 000005f8
e1002bf0 00000000 000005fc 00000000 00000600
e1002c00 00000000 00000604 00000000 00000608
e1002c10 00000000 0000060c 00000000 00000610
e1002c20 00000000 00000614 00000000 00000618
e1002c30 00000000 0000061c 00000000 00000620
e1002c40 00000000 00000624 00000000 00000628
e1002c50 00000000 0000062c 00000000 00000630
e1002c60 00000000 00000634 00000000 00000638
e1002c70 00000000 0000063c 00000000 00000640
e1002c80 00000000 00000644 00000000 00000648
e1002c90 00000000 0000064c 00000000 00000650
e1002ca0 00000000 00000654 00000000 00000658
e1002cb0 00000000 0000065c 00000000 00000660
e1002cc0 00000000 00000664 00000000 00000668
e1002cd0 00000000 0000066c 00000000 00000670
e1002ce0 00000000 00000674 00000000 00000678
e1002cf0 00000000 0000067c 00000000 00000680
e1002d00 00000000 00000684 00000000 00000688
e1002d10 00000000 0000068c 00000000 00000690
e1002d20 00000000 00000694 00000000 00000698
e1002d30 00000000 0000069c 00000000 000006a0
e1002d40 00000000 000006a4 00000000 000006a8
e1002d50 00000000 000006ac 00000000 000006b0
e1002d60 00000000 000006b4 00000000 000006b8
e1002d70 00000000 000006bc 00000000 000006c0
e1002d80 00000000 000006c4 00000000 000006c8
e1002d90 00000000 000006cc 00000000 000006d0
e1002da0 00000000 000006d4 00000000 000006d8
e1002db0 00000000 000006dc 00000000 000006e0
e1002dc0 00000000 000006e4 00000000 000006e8
e1002dd0 00000000 000006ec 00000000 000006f0
e1002de0 00000000 000006f4 00000000 000006f8
e1002df0 00000000 000006fc 00000000 00000700
e1002e00 00000000 00000704 00000000 00000708
e1002e10 00000000 0000070c 00000000 00000710
e1002e20 00000000 00000714 00000000 00000718
e1002e30 00000000 0000071c 00000000 00000720
e1002e40 00000000 00000724 00000000 00000728
e1002e50 00000000 0000072c 00000000 00000730
e1002e60 00000000 00000734 00000000 00000738
e1002e70 00000000 0000073c 00000000 00000740
e1002e80 00000000 00000744 00000000 00000748
e1002e90 00000000 0000074c 00000000 00000750
e1002ea0 00000000 00000754 00000000 00000758
e1002eb0 00000000 0000075c 00000000 00000760
e1002ec0 00000000 00000764 00000000 00000768
e1002ed0 00000000 0000076c 00000000 00000770
e1002ee0 00000000 00000774 00000000 00000778
e1002ef0 00000000 0000077c 00000000 00000780
e1002f00 00000000 00000784 00000000 00000788
e1002f10 00000000 0000078c 00000000 00000790
e1002f20 00000000 00000794 00000000 00000798
e1002f30 00000000 0000079c 00000000 000007a0
e1002f40 00000000 000007a4 00000000 000007a8
e1002f50 00000000 000007ac 00000000 000007b0
e1002f60 00000000 000007b4 00000000 000007b8
e1002f70 00000000 000007bc 00000000 000007c0
e1002f80 00000000 000007c4 00000000 000007c8
e1002f90 00000000 000007cc 00000000 000007d0
e1002fa0 00000000 000007d4 00000000 000007d8
e1002fb0 00000000 000007dc 00000000 000007e0
e1002fc0 00000000 000007e4 00000000 000007e8
e1002fd0 00000000 000007ec 00000000 000007f0
e1002fe0 00000000 000007f4 00000000 000007f8
e1002ff0 00000000 000007fc 00000000 00000000
嘿嘿,列多了~
最后再添个代码的截图,小玩意~ 我把解析对象头(包括可选头部)的代码封成了一个lib,以后调用着方便。
NUPT WANGyu