1. 程式人生 > 其它 >【windwos 作業系統】關鍵的Windows核心資料結構一覽(下)

【windwos 作業系統】關鍵的Windows核心資料結構一覽(下)

I/O管理器

nt!_IRP

IRP表示一個I/O請求包結構體,它用來封裝執行一個特定I/O操作所需要的所有引數以及I/O操作的狀態。IRP的表現也類似於一個執行緒獨立呼叫棧因此它可以從一個執行緒傳遞到另一個執行緒,也可以通過驅動實現的佇列傳遞給一個DPC例程。IRPs是Windows非同步I/O處理模型的關鍵所在,應用程式可以發出一連串的I/O請求並繼續執行其他程式碼,而與此同時I/O請求會被驅動或硬體裝置處理。這種非同步模型允許相當大的吞吐量且可以實現最佳資源利用。IRPs通過I/O管理器元件分配,由Win32 I/O APIs進行操縱。在核心中IRPs也可以通過裝置驅動為I/O請求而分配。IRPs流穿過驅動的棧,在這裡驅動在傳遞IRP給下方的驅動之前會先增加它自身的值到IRP上,這是通過呼叫IoCallDriver()

完成的。一般來說在最下方的驅動棧上會通過呼叫IoCompleteRequest()來完成該IRP,該呼叫會引起棧上的每個驅動被通知I/O已完成並給這些驅動一個在此IRP上執行後向處理操作的機會。

IRPs包含一個固定大小的頭部,即IRP資料結構和一個可變的堆疊位置,儲存在StackCount欄位,其中每個堆疊位置都通過IO_STACK_LOCATION結構來表示。IRPs必須包含足夠多的堆疊位置,該數量不應少於裝置棧上互相疊羅漢的裝置物件個數,這將用於處理IRP。驅動棧上的裝置物件數量儲存在DEVICE_OBJECT.StackSize欄位。IRPs一般由支援固定大小分配器的前向連結串列分配。因此,由I/O管理器分配的IRPs有10或4個I/O堆疊位置,這取決於IRP目標所在的堆疊上的裝置物件數量。

一些IRPs通過ThreadListEntry欄位被鏈入到執行緒中,連結串列頭為ETHREAD.IrpList

Tail.Overlay.ListEntry欄位被驅動用來儲存內部佇列的IRP,典型的固定在一個有LIST_ENTRY結構的裝置擴充套件結構體中,它由驅動建立DEVICE_OBJECT時建立。

Tail.CompletionKey在IRP鏈入到一個I/O完成埠時被使用。

偵錯程式命令!irp將顯示一個IRP的細節描述。!irpfind命令可以通過掃描non-paged pool來找到系統中所有的或者特定的IRPs集合。

APIs:

  • IoAllocateIrp()
  • IoBuildDeviceIoControlRequest()
  • IoFreeIrp()
  • IoCallDriver()
  • IoCompleteRequest()
  • IoBuildAsynchronousFsdRequest()
  • IoBuildSynchronousFsdRequest()
  • IoCancelIrp()
  • IoForwardAndCatchIrp()
  • IoForwardIrpSynchronously()
  • IoIsOperationSynchronous()
  • IoMarkIrpPending()

nt!_IO_STACK_LOCATION

IO_STACK_LOCATION包含有關一個I/O操作的資訊,該操作要求在一組驅動中執行一個特定的驅動程式。IRP包含多個內嵌的I/O堆疊位置,它們都是在IRP被分配時分配的。裝置棧上有多少個驅動IRP中就至少有多少個I/O堆疊位置。I/O堆疊位置歸裝置所有,它們逆序出現在裝置堆疊上,也就是說堆疊上最頂層的裝置擁有最底層的堆疊位置,反之亦然。I/O管理器負責填充最頂層裝置的I/O堆疊位置,每個裝置都負責填充鏈中下一個裝置的I/O堆疊位置。

Parameters欄位是一個多重結構體的共用體,每個結構代表了一個對應驅動必須執行的I/O操作。共用體Parameters中特定結構的選擇依賴於MajorFunction欄位。該欄位可用的值由IRP_MJ_XXX在wdm.h中定義。具體的主函式(major function)還擁有和它們相關聯的副函式(minor function)。這些副函式的數量儲存在MinorFunction欄位中。例如IRP_MN_START_DEVICE是一個和主函式IRP_MJ_PNP相關的副函式碼。

APIs:

  • IoGetCurrentIrpStackLocation()
  • IoGetNextIrpStackLocation()
  • IoCopyCurrentIrpStackLocationToNext()
  • IoSkipCurrentIrpStackLocation()
  • IoMarkIrpPending()

nt!_DRIVER_OBJECT

DRIVER_OBJECT表示一個載入到記憶體中的驅動映像。I/O管理器會在驅動載入到記憶體前建立驅動物件,此後DriverEntry()例程會收到一個指向該驅動物件的指標。驅動被卸載出記憶體後驅動物件的釋放與此相似。

MajorFunction欄位是個陣列,每個元素指向一個由驅動所知的分發入口指標提供的函式。這些入口指標被I/O管理器用來分發IRPs給驅動處理。

DriverName欄位包含物件管理器名稱空間內驅動物件的名稱。

DriverStart欄位是核心虛擬地址空間中驅動載入的地址,DriverSize包含驅動對映的位元組數,按最近頁邊界向上取整。

FastIoDispatch指向了FAST_IO_DISPATCH型別的結構,它包含檔案系統驅動提供的例程指標。

DriverSection指向一個LDR_DATA_TABLE_ENTRY型別的資料結構,供載入器在記憶體中找到驅動映像。

偵錯程式命令!drvobj顯示了一個驅動物件的資訊。

APIs:

  • IoAllocateDriverObjectExtension()
  • IoGetDriverObjectExtension()

nt!_DEVICE_OBJECT

DEVICE_OBJECT用於表示一個系統中的邏輯或物理裝置。和驅動物件不同的是,驅動物件在驅動載入前由I/O管理器建立,而裝置物件則是由驅動本身來建立。I/O請求的目標總是裝置物件而不是驅動物件。一個指向裝置物件的指標會傳遞給驅動的分發例程以識別哪個裝置物件是此I/O請求的目標。

驅動物件在建立時懷有一個裝置連結串列。該連結串列固定在DRIVER_OBJECT.DeviceObject中,使用NextDevice欄位來把所有的裝置驅動鏈在一起。裝置物件,同樣的,有一個DriverObject欄位用以指回擁有它的驅動物件。儘管裝置物件是系統定義的資料結構它們也可以擁有驅動特定的擴充套件結構。該擴充套件資料結構也一起放在裝置物件中,它在non-paged pool中分配,大小是一個特定的宣告的尺寸,而用於指向擴充套件結構的指標就是裝置物件的DeviceExtension欄位。

裝置物件可以互相疊羅漢來形成一組裝置物件。StackSize欄位標誌了有多少個裝置物件在該裝置物件的下方。該欄位也被I/O管理器用來為IRPs分配合適數量的I/O堆疊位置。CurrentIrpDeviceQueue欄位只有當驅動為裝置物件使用系統管理的I/O時才有用,這非常罕見,因此CurrentIrp欄位在大多數情況下常常被設定成NULL。AttachedDevice指向了下一個裝置棧上更高層的裝置物件。

偵錯程式命令!devobj可以顯示一個裝置物件的資訊。

APIs:

  • IoCreateDevice()
  • IoDeleteDevice()
  • IoCreateDeviceSecure()
  • IoCreateSymbolicLink()
  • IoCallDriver()
  • IoCreateFileSpecifyDeviceObjectHint()
  • IoAttachDevice()
  • IoAttachDeviceToDeviceStack()
  • IoDetachDevice()
  • IoGetAttachedDevice()
  • IoGetAttachedDeviceReference()
  • IoGetLowerDeviceObject()
  • IoGetDeviceObjectPointer()
  • IoGetDeviceNumaNode()

nt!_DEVICE_NODE

DEVICE_NODE用於表示一個已經被PnP管理器列舉到的物理或邏輯裝置。裝置節點(nodes)是電源管理和PnP操作的目標。系統中完整的硬體裝置樹源於具有層級結構的裝置節點。裝置節點有一個父子與兄弟的關係網。使用者模式在CfgMgr32.h/CfgMgr32.lib定義的配置管理器APIs用於處理這些裝置節點。

DEVICE_NODE結構體的Child,Sibling,Parent以及LastChild欄位用於存放系統中所有的裝置節點,形成一個層級結構。裝置節點包含至少一個由PhysicalDeviceObject欄位指向的物理裝置物件(PDO)和一個功能裝置物件(FDO),以及一到多個過濾裝置物件(FDOs)。ServiceName欄位指向了一個字串,標誌了建立FDO並驅動該裝置的功能驅動。InstancePath指向了一個字串,該字串唯一的特定標誌一個裝置例項,系統中可以有多個相同的裝置例項。State,PreviousState,StateHistory欄位的組合用來確定裝置節點在其當前狀態設定之前所經歷的狀態。

偵錯程式命令!devnode可以顯示DEVICE_NODE結構體的資訊。!devstack命令顯示了所有的裝置物件,它們是單一devnode的一部分。

nt!_FILE_OBJECT

FILE_OBJECT表示了一個開啟的裝置物件例項。檔案物件會在一個使用者模式程序呼叫CreateFile()或本地(native)APINtCreateFile()或核心模式驅動呼叫ZwCreateFile()時被建立。多個檔案物件可以指向一個裝置物件除非該裝置被標記被設定為排他屬性,這是通過設定DEVICE_OBJECT標記中的DO_EXCLUSIVE位實現的。

DeviceObject欄位指向了開啟該檔案物件例項的裝置物件。Event欄位包含一個內嵌的事件結構體,它用於阻塞一個在裝置物件上已經請求過非同步I/O操作的執行緒以便於其所有的驅動程式執行非同步IO。

FsContextFsContext2被檔案系統驅動(FSDs)所使用,它們儲存檔案物件特定的上下文資訊。當被一個檔案系統驅動使用時,FsContext欄位指向了一個型別為FSRTL_COMMON_FCB_HEADERFSRTL_ADVANCED_FCB_HEADER的結構體,它包含一個檔案或流的資訊。多重FILE_OBJECTFsContext欄位表示相同的檔案(或流)的開啟例項指向同一個檔案控制塊(FCB)。FsContext2欄位指向了一個快取控制塊,FSD用它來儲存有關檔案或流的特定例項資訊。

CompletionContext,IrpList以及IrpListLock用於檔案物件和I/O完成埠相關聯的場景。CompletionContext欄位由NtSetInformationFile()初始化,呼叫引數為資訊類FileCompletionInformationCompletionContext.Port欄位指向一個KQUEUE型別的結構體,它包含一個已完成並等待被取回的IRP連結串列。IoCompleteRequest()通過欄位IRP.Tail.Overlay.ListEntry查詢該連結串列中的IRP。

除錯命令!fileobj顯示了一個檔案物件的資訊。

APIs:

  • IoCreateFile()
  • IoCreateFileEx()
  • IoCreateFileSpecifyDeviceObjectHint()
  • IoCreateStreamFileObject()
  • IoCreateStreamFileObjectEx()
  • ZwCreateFile()
  • ZwReadFile()
  • ZwWriteFile()
  • ZwFsControlFile()
  • ZwDeleteFile()
  • ZwDeviceIoControlFile()
  • ZwFlushBuffersFile()
  • ZwOpenFile()
  • ZwFsControlFile()
  • ZwLockFile()
  • ZwQueryDirectoryFile()
  • ZwQueryEaFile()
  • ZwCancelIoFile()
  • ZwQueryFullAttributesFile()
  • ZwQueryInformationFile()
  • ZwQueryVolumeInformationFile()
  • ZwSetEaFile()
  • ZwSetInformationFile()
  • ZwSetQuotaInformationFile()
  • ZwSetVolumeInformationFile()
  • ZwUnlockFile()
  • ZwWriteFile()

物件和控制代碼

nt!_OBJECT_HEADER

Windows核心中的物件(Object)資料結構用來表示通用的設施比如檔案、登錄檔鍵、程序、執行緒、裝置等等。它由物件管理器管理,物件管理器是Windows核心的一個元件。所有這樣的物件內部都有一個先驅的OBJECT_HEADER結構體,它包含物件相關的資訊並用來維護物件的生命週期,允許物件有一個專有名字,通過應用訪問控制來保護物件,呼叫物件特定型別方法以及追蹤分配器的配額使用。

物件中部署在OBJECT_HEADER後方的資料結構與OBJECT_HEADER存在著部分的重疊。實際上物件的資料存放位置是從OBJECT_HEADERBody欄位開始而不是從其尾部開始。

物件頭部包含了引用計數HandleCountPointerCount,它們被物件管理器用來儲存物件直到沒有外部的引用指向該物件。HandleCount是控制代碼的數量,PointerCount是控制代碼和核心模式物件引用的數量。

物件頭可以由一個可選的物件頭引導,類似OBJECT_HEADER_PROCESS_INFO,OBJECT_HEADER_QUOTA_INFO,OBJECT_HEADER_HANDLE_INFO,OBJECT_HEADER_NAME_INFO以及OBJECT_HEADER_CREATOR_INFO結構,它們描述了關於物件額外的屬性。InfoMask欄位是一個位掩碼,它決定了當前是哪一種前面描述的可選頭結構。

SecurityDescriptor欄位指向一個型別為SECURITY_DESCRRIPTOR的結構體,它包含了任意訪問控制列表(DACL)和系統訪問控制列表(SACL)。DACL用於檢查程序的tokens是否有訪問物件的許可權。SACL用於審計訪問物件的許可權。

核心不能在IRQL高於PASSIVE_LEVEL的級別上刪除物件。ObpDeferObjectDeletion()把物件鏈在一起,其刪除操作需要被延遲到一個連結串列中,該連結串列在核心變數ObpRemoveObjectList中。NextToFree欄位為此而生。

QuotaBlockCharged欄位指向了EPROCESS.QuotaBlockEPROCESS_QUOTA_BLOCK結構體,它被PsChargeSharedPoolQuota()PsReturnSharedPoolQuota()函式用來追蹤一個使用Non-Paged Pool和Paged Pool的特定程序。分配物件時配額總是會被分配。

偵錯程式命令!object顯示了儲存在物件頭的資訊。!obtrace命令顯示了根據物件引用trace到的資料。如果物件引用追蹤在一個物件上可用,!obja命令可以顯示任何物件的屬性資訊。

APIs:

  • ObReferenceObject()
  • ObReferenceObjectByPointer()
  • ObDereferenceObject()

nt!_OBJECT_TYPE

對物件管理器管理的每種型別的物件來說,都有一個“型別物件”結構體用於儲存該型別物件的通用屬性。這種“型別物件”結構體由OBJECT_TYPE表示。Windows 7上有大概42種不同的物件型別結構體。核心變數ObTypeIndexTable是一個指標陣列,它的每個成員都指向一種物件型別的OBJECT_TYPE結構體。對於每種物件型別核心也會儲存一個指向相關物件型別結構體的全域性變數。例如,變數nt!IoDeviceObjectType指向了DEVICE_OBJECTSOBJECT_TYPE結構體。

OBJECT_TYPETypeInfo欄位指向了一個OBJECT_TYPE_INITIALIZER結構體,該結構體包含了物件型別特定的函式,它們被物件管理器用來在物件上執行各種各樣的操作。

CallbackList欄位一個特定物件型別的驅動安裝的回撥函式列表的頭。當前只有程序和執行緒物件支援回撥函式,它們由TypeInfo.SupportsObjectCallbacks欄位指定。

關鍵欄位包含了池標籤(pool tag),它用於分配該型別的物件。

偵錯程式命令!object ObjectTypes用於顯示系統中所有的型別物件。

nt!_HANDLE_TABLE_ENTRY

Windows中的程序有它們自己的私有控制代碼表,它儲存在核心虛擬地址空間中。HANDLE_TABLE_ENTRY表示了一個程序控制代碼表中 個體表項。控制代碼表在分頁記憶體池中分配。當一個程序終止時,函式ExSweepHandleTable()關閉該程序控制代碼表中所有的控制代碼。

Object欄位指向了一個物件結構體,比如File, Key, Kevent等,它們的控制代碼均已建立。

GrantedAccess欄位是一個型別為ACCESS_MASK的位掩碼,它決定了物件上特定控制代碼所允許的操作集合。該欄位的值由SeAccessCheck()計算,基於呼叫者(可信訪問)的訪問請求以及物件安全描述符的DACL中的ACEs。

除錯命令!handle可以用來檢視任何程序的控制代碼表。!htrace命令可以用來顯示堆疊上跟蹤控制代碼的資料,如果控制代碼跟蹤可用的話。

APIs:

  • ObReferenceObjectByHandle()
  • ObReferenceObjectByHandleWithTag()

記憶體管理

nt!_MDL

MDL表示一個記憶體描述符列表結構,它描述那些已被鎖定的使用者或核心模式記憶體。它由一個固定長度的頭和可變的頁幀數(PFNs)組成。每一頁都由MDL描述。

MDL結構包含了它描述的虛擬地址和緩衝區尺寸,對使用者模式緩衝區來說它也指向了擁有該緩衝區的程序。裝置驅動程式使用MDLs對硬體裝置進行程式設計,執行DMA的傳輸,同時也對映使用者模式緩衝區到核心模式,反之亦可。

某些型別的驅動程式,例如網路棧,在Windows支援的鏈式MDLs,多個MDL中描述了實質上分散的緩衝區,它們由Next欄位鏈在一起。

對MDLs來說那描述了使用者模式緩衝區,Process欄位指向了程序的EPROCESS結構體,它的虛擬地址空間被MDL鎖住。

如果被MDL描述的緩衝區被對映到了核心虛擬地址空間,MappedSystemVa就指向了核心模式的緩衝區地址。該欄位僅在位MDL_MAPPED_TO_SYSTEM_VAMDL_SOURCE_IS_NONPAGED_POOLMdlFlags欄位中被設定時,才是有效的。

Size欄位包含MDL資料結構以及MDL後面跟隨的整個PFN陣列的尺寸。

StartVa欄位和ByteOffset欄位一起定義了MDL中被鎖住的原始緩衝區的起始位置。StartVa指向了頁起始,ByteOffset包含了從StartVa計算緩衝區實際起始地址的偏移位元組。

ByteCount欄位描述了MDL鎖住的緩衝區尺寸。

APIs:

  • IoAllocateMdl()
  • IoBuildPartialMdl()
  • IoFreeMdl()
  • MmInitializeMdl()
  • MmSizeOfMdl()
  • MmPrepareMdlForReuse()
  • MmGetMdlByteCount()
  • MmGetMdlByteOffset()
  • MmGetMdlVirtualAddress()
  • MmGetSystemAddressForMdl()
  • MmGetSystemAddressForMdlSafe()
  • MmGetMdlPfnArray()
  • MmBuildMdlForNonPagedPool()
  • MmProbeAndLockPages()
  • MmUnlockPages()
  • MmMapLockedPages()
  • MmMapLockedPagesSpecifyCache()
  • MmUnmapLockedPages()
  • MmAllocatePagesForMdl()
  • MmAllocatePagesForMdlEx()
  • MmFreePagesFromMdl()
  • MmMapLockedPagesWithReservedMapping()
  • MmUnmapReservedMapping()
  • MmAllocateMappingAddress()
  • MmFreeMappingAddress()

nt!_MMPTE

MMPTE用來表示記憶體管理的頁表項(PTE),它被CPU的記憶體管理單元(MMU)用來轉換虛擬地址(VA)到實體地址(PA)。對映一個VA到PA所需要的轉換級別取決於CPU型別。對x86來說它使用2級轉換(PDE和PTE),x86在PAE模式下使用3級轉換(PPE,PDE和PTE),x64 CPU則使用4級轉換(PXE,PPE,PDE,PTE)。不同級別結構的格式,諸如PXE,PPE,PDE和PTE都是相似的,MMPTE不僅可以用於表示PTE,還可以表示這些其他轉換結構。

MMPTE結構是一個多重子結構的聯合體,它們被Windows記憶體管理器的頁錯誤處理機制用來搜尋由PTE表示的頁位置。例如,當一個PTE包含了一個頁的有效的實體地址且MMU可以使用這個PTE完成地址轉換,那麼使用的子結構就是u.Hard欄位。

當一個頁從程序的工作集中被移除時,Windows記憶體管理器從一個硬體視角來看會標記這個頁的PTE為無效的,然後,記憶體管理器重新試圖用該PTE來儲存OS關於頁的特定資訊。這會導致CPU記憶體管理單元(MMU)不能再使用該PTE來進行地址轉換。當程序試圖去訪問這樣一個頁時,CPU生成一個頁錯誤來呼叫Windows的頁錯誤例程。PTE中編碼的資訊現在用來定位該頁並將它返還給程序的工作集,這樣就解決了頁錯誤。有個這樣的例子就是PTE轉換,該PTE表示一個處於待命或修改狀態的頁。在這種情形下,u.Transition子結構用來儲存該頁的資訊。

當物理頁的內容被儲存在頁檔案中時,Windows記憶體管理器會修改PTE來指向頁檔案中的頁位置,此時使用的是u.Soft子結構。u.Soft.PageFileLow欄位決定了Windows支援的16個頁檔案中哪一個包含了該頁,而u.Soft.PageFileHigh則包含了在該頁檔案中頁的索引。

偵錯程式命令!pte可以顯示給定虛擬地址的所有級別的頁表內容。

nt!_MMPFN

Windows記憶體管理持有系統中每個物理頁的資訊,放置在一個叫PFN資料庫的陣列中。MMPFN結構體表示了該資料庫中的每一個獨立條目,它包含了單一物理頁的資訊。

變數nt!MmPfnDatabase指向了MMPN接面構體陣列,它們組成了PFN資料庫。PFN資料庫的條目數量為nt!MmPfnSize,這其中有一些額外的條目來處理熱插拔記憶體。為了儲存記憶體,MMPFN結構被塞得很滿。每個欄位的解析都是不同的,而這取決於每個頁的狀態。

物理頁的狀態儲存在u3.e1.PageLocation,由列舉型別nt!_MMLISTS中的一個條目標識。

u2.ShareCount欄位包含程序指向該頁的PTE數量,如果存在共享頁則應該比1大。

u3.e2.ReferenceCount包含該頁的引用數量,如果頁被鎖住則也包含鎖的數量。該引用計數會在u2.ShareCount變成0時遞減。

除錯命令!pfn可以顯示給定物理頁的MMPFN結構體的所有內容。

nt!_MMPFNLIST

記憶體管理器持有處於相同狀態的鏈在一起的物理頁。這可以加速查詢某種給定狀態的一個或多個頁的過程,例如,查詢空閒頁或被零化換出(zeroed out)的頁。MMPFNLIST結構體持有這些連結串列的頭。系統中有多個MMPFNLIST結構體,它們中的每個都包含一個特定狀態的頁並被儲存在核心變數nt!Mm<PageState>ListHead中,這裡的PageState可以表示待命(Standby)、已修改(Modified)、無寫修改(ModifiedNoWrite),空閒(Free),只讀(Rom),損壞(Bad),零化(Zeroed)。活躍態的頁面例如當前屬於一個程序工作集的頁面不會再任何一個連結串列中。

在Windows的新版本中,nt!MmStandbyPageListHead不再使用,取而代之的是一個具有優先順序的8連結串列集合,存在nt!MmStandbyPageListByPrioritynt!MmFreePageListHeadnt!MmModifiedPageListHead也不再被使用,轉而使用的是nt!MmFreePagesByColornt!MmModifiedProcessListByColornt!MmModifiedPageListByColor

MMPFN.u1.FlinkMMPFN.u2.Blink欄位對一個特定頁來說,用於儲存該頁到一個雙向連結串列中。這些連結串列的頭儲存在對應的MMPFNLIST結構體的FlinkBlink欄位。

ListName欄位是列舉型別MMLISTS中的一個,它標識了該連結串列中頁的型別。

偵錯程式命令!memusage 8命令顯示了某個特定狀態的頁的數量。

nt!_MMWSL

Windows中每個程序都有一個和它關聯的工作集,它由那些程序不會觸發頁錯誤的頁組成。工作集整理者(WST),是記憶體管理器的一個元件,它執行在KeBalanceSetManager()執行緒的上下文,盡力去移除程序不再使用的頁並重新分配它們到有需求的其他程序。為了執行這一任務,WST需要儲存關於系統中每個程序的工作集資訊。該資訊被儲存在MMWSL結構體。每個程序的EPROCESS.Vm.VmWorkingSetList都指向了它的MMWSL結構。

系統中每個程序的MMWSL都在核心虛擬地址空間的超空間(HyperSpace)上一個完全相同的地址。超空間是核心虛擬地址空間的一部分,每個程序都有一個自己的程序對映,並不是所有程序共享一個單一的對映。因此,記憶體管理器,對任何給定的例項,只能訪問當前程序的MMWSL即目前在CPU上執行程序的執行緒。

MMWSL結構體的Wsle欄位指向程序工作集列表項陣列的基址。陣列中條目的有效值為EPROCESS.Vm.WorkingSetSize

nt!_MMWSLE

MMWSLE資料結構表示一個程序工作集中單一頁面的工作集列表,因此每一個在程序工作集中的頁都有一個MMWSLE結構。該結構被工作集整理者用來判定該特定頁是否是一個潛在的可整理候選頁,也就是說從程序的工作集中移除。

當一個程序試圖訪問一個在工作集中的頁面時,CPU記憶體管理單元MMU會在該頁對應的PTE中設定MMPTE.Hard.Accessed位。工作集整理者有規律的喚醒並掃描一個程序的WSLEs。在掃描期間他會檢查從上一次來是否已訪問過一個特定的頁,這是通過檢查PTE的訪問位來實現的。如果該頁從上次掃描以來從未被訪問過,該頁就通過遞增u1.e1.Age欄位逐漸老化。如果頁被訪問過,那麼u1.e1.Age欄位就被重置為0。當u1.e1.Age漲到7時,該頁就被認為是一個可以被換出的候選頁。

u1.e1.VirtualPageNumber是頁虛擬地址的高20位(在x86)或52位(在x64)。

除錯命令!wsle可以顯示一個特定程序的工作集列表條目。

nt!_POOL_HEADER

核心中動態記憶體分配是由非頁池、分頁池和會話池組成的。根據請求分配的大小,池分配分為小池分配(尺寸少於4K)和大池分配(尺寸大於等於4k)。池分配大小在x86系統上總是向上取整到8位元組,在x64系統上向上取整到16位元組。

每個小池分配都由一個池頭、資料區域組成。資料區域用於儲存資料,裡面還有一些用於對其的padding。池頭用POOL_HEADER結構表示,該結構包含了關於後面跟隨的資料區的資訊。

BlockSize欄位包含了池塊的尺寸,囊括頭和任何padding位元組。PreviousSize欄位包含了前一塊的尺寸(低地址毗鄰的塊)。這兩個欄位在x86上都是8的倍數,x64上都是16的倍數。PoolTag欄位包含4位元組標記,它用於標識池分配的所有者,常常用於除錯。如果最高位(31位)池標籤被設定,則該池分配被標記為受保護。

大池分配沒有內嵌的POOL_HEADER,取而代之的是,池頭部儲存了一個分離的叫做nt!PoolBigTable的表。因為大池分配需要在一個頁邊界(4K)對齊,所以這是有必要的。

除錯命令!pool可以顯示一個給定任何地址所在的池頁中的所有池塊。!vm顯示池消耗資訊。!poolused顯示消耗的位元組數以及所有池標籤的塊數。!poolfind定位一個特定標籤的池分配。!poolval檢查池頭是否被汙染,注意到他不會檢查實際的池中資料是否被汙染。!frag顯示非分頁池的外部池碎片資訊。輸出顯示了系統中所有累計可用的非分頁池頁空閒塊(碎片)的個數以及他們佔用的記憶體量。

APIs:

  • ExAllocatePoolWithTag()
  • ExAllocatePoolWithQuotaTag()
  • ExFreePool()

nt!_MMVAD

MMVAD結構代表虛擬地址描述符(vad)並用於描述一個程序使用者模式虛擬地址空間中幾乎連續的虛擬地址空間。每當程序虛擬地址的一部分通過VirtualAlloc()MapViewOfSection()分配時,都有一個MMVAD結構被建立。MMVADs從非分頁記憶體池分配並被組織成AVL樹。每個程序都有它自己的VAD樹,它僅僅用於描述使用者模式虛擬地址空間,也就是說核心虛擬地址空間並沒有VADs。

StartingVpnEndingVpn欄位包含高20位(x86)或高52位(x64)虛擬地址的起始和終止,通過VAD的區域描述。LeftChildRightChild欄位指向下一個VAD樹底層的節點。

偵錯程式命令!vad顯示了程序的VAD結構體資訊。

APIs:

  • ZwAllocateVirtualMemory()
  • ZwMappedViewOfSection()
  • MmMapLockedPagesSpecifyCache()

快取管理器

nt!_VACB

系統快取虛擬地址空間被分割成256K(由ntifs.h中常量VACB_MAPPING_GRANULARITY定義)大小的檢視(views)塊。這一數值也決定了檔案被對映到系統快取時的粒度和對齊。每個檢視都持有一個包含關乎此檢視資訊的虛擬地址控制塊(VACB)。

核心全域性變數CcNumberOfFreeVacbsCcNumberOfFreeHighPriorityVacbs一起決定了可以分配的VACB的數量。所有這樣的VACB都被儲存在一個CcVacbFreeListCcVacbFreeHighPriorityList連結串列中。這一連結欄位就是為此而準備的。

BaseAddress欄位指向了系統快取中檢視的起始地址,描述它的VACB由函式MmMapViewInSystemCache()生成。

SharedCacheMap欄位指向了共享快取對映結構,它包含該VACB並描述了VACB對映到該檢視中的檔案對映的區段。

ArrayHead欄位指向VACB_ARRAY_HEADER結構體,它包含了VACB。

偵錯程式命令!filecache顯示了使用中的VACB資料結構資訊。

nt!_VACB_ARRAY_HEADER

4095個VACB塊是一起被分配的,同時分配了一個頭VACB_ARRAY_HEADER,它用於管理VACB。VACB_ARRAY_HEADER結構附著在VACB陣列的後面。

單個單元的VACB陣列頭分配尺寸為128K,它包含了VACB_ARRAY_HEADER以及跟隨的4095個VACB結構體。因此每個單元可以對映最大1023MB的系統快取虛擬地址空間(單個VACB對映256K)。VACB_ARRAY_HEADER結構以及嵌入的VACB的最大數量由系統全部的系統快取虛擬地址空間限制,也就是說在x64上是1TB,在x86上是2GB。因此在x86系統上至多不會超過2個VACB_ARRAY_HEADER結構體。

核心變數CcVacbArrays指向了一個指標陣列,這些指標指向VACB_ARRAY_HEADER結構體。VacbArrayIndex欄位是特定VACB_ARRAY_HEADER結構體在陣列中的索引值。變數CcVacvArraysHighestUsedIndex包含陣列中上一次使用的那個索引值。該陣列受CcVacbSpinLock這個佇列自旋鎖(queued spinlock)保護。

VACB_ARRAY_HEADER頭結構的數量被儲存在全域性變數CcVacbArraysAllocated中,它包含了當前整個系統分配的頭結構,它們由CcVacbArrays指向。

nt!_SHARED_CACHE_MAP

SHARED_CACHE_MAP被快取管理器用來儲存關於檔案當前被快取到系統快取虛擬地址空間的那些部分的資訊。對一個快取檔案來說,有一個包含所有該檔案已開啟的例項的單一SHARED_CACHE_MAP結構體物件。所有的FILE_OBJECT表示了所有已開啟的特定檔案的例項,它們都通過FILE_OBJECTSectionObjectPointersSharedCacheMap欄位指向同一個SHARED_CACHE_MAP

通過同一個SHARED_CACHE_MAP結構體可以訪問相同檔案流的所有對映的VACB。共享快取對映結構保證了該檔案的特定區段永遠不會在快取中除此之外的對映。

全域性變數CcDirtySharedCacheMapList包含了所有的SHARED_CACHE_MAP結構體連結串列,它們包含攜帶髒資料的檢視。連結串列中有一個特殊的項—全域性變數CcLazyWriterCursor,以它開始的SHARED_CACHE_MAP結構體的子鏈都是懶回寫(lazy written)。在每個懶回寫完成後,CcLazyWriterCursor都在CcDirtySharedCacheMapList中移動。

包含了沒有任何髒頁的檢視的SHARED_CACHE_MAP結構體被儲存在全域性連結串列CcCleanSharedCacheMapList中。SharedCacheMapLinks欄位用於組織這兩種佇列(dirty or clean)。

SectionSize欄位決定了通過SHARED_CACHE_MAP對映的區段的大小。

InitialVacbs欄位是一個內建的4 VACB指標陣列,它用於對映那些小於1MB的檔案區段。如果區段大小超過了1MB,一個128 VACB的指標陣列會被分配並存儲在Vacbs欄位中,它指向了當前可以描述檔案到32MB(也就是128*256K)大小的VACBs。如果區段尺寸超過32MB,則指標陣列的128個指標都用來指向另一個128 VACB指標陣列。這種額外的層級設計使得區段大小可以達到4GB(128*128*256K)。一共可以有7層VACB,尺寸也就是(128^7*256K),這將遠高於快取管理器提供的最大區段尺寸(2^63)。

函式CcCreateVacbArray()層級了VACB陣列,所有的VACB陣列都由一個推鎖(VacbLock欄位)保護。

PrivateList是一個連結串列的頭,它用於儲存和每個檔案開啟例項,也就是FILE_OBJECT相關聯的PRIVATE_CACHE_MAP結構體。PRIVATE_CACHE_MAP.PrivateLinks欄位用於形成連結串列。

除錯命令!fileobj可以顯示關於SECTION_OBJECT_POINTER結構體的資訊,它包含一個指向SHARED_CACHE_MAP的指標。

nt!_PRIVATE_CACHE_MAP

快取管理器執行一個檔案的智慧預閱讀來提升效能。這些預閱讀在每個特定檔案開啟的例項上都獨立執行。和每個檔案開啟例項相關聯的PRIVATE_CACHE_MAP結構體,儲存了一個上次檔案閱讀操作的歷史記錄,它被快取管理器用於執行智慧預閱讀操作。

FILE_OBJECT.PrivateCacheMap指向了和檔案開啟例項相關聯的PRIVATE_CACHE_MAP結構體。當該檔案快取被啟用時該欄位由CcInitializeCacheMap()初始化,同樣當快取被清掉時則通過CcUninitializeCacheMap()完成。

FileObject欄位,指向了和PRIVATE_CACHE_MAP相關聯的FILE_OBJECT。預閱讀操作僅在FILE_OBJECT.Flags欄位的FO_RANDOM_ACCESS位未置位時才會執行。

SHARED_CACHE_MAP.PrivateList指向所有開啟的特定檔案的例項的PRIVATE_CACHE_MAP結構體。特定檔案的開啟例項的PRIVATE_CACHE_MAP結構體通過PrivateLinks欄位鏈在一起。

FileOffset1,FileOffset2,BeyondLastBye,BeyondLastByte2四個欄位一起用於決定在特定FILE_OBJECT對應的檔案上閱讀的模式。快取管理器函式CcUpdateReadHistory()用來更新這些讀操作的計數。

Flags.ReadAheadEnabled欄位決定了預讀操作對該檔案開啟例項來說是否是需要的。Flags.ReadAheadActive欄位由CcScheduleReadAhead()設定,標誌了ReadAheadWorkItem欄位的讀工作例程當前是否是活躍的(active)。

除錯命令!fileobj顯示了關於PRIVATE_CACHE_MAP的資訊。

APIs:

  • CcInitializeCacheMap()
  • CcUninitializeCacheMap()
  • CcIsFileCached()

nt!_SECTION_OBJECT_POINTERS

SECTION_OBJECT_POINTERS和檔案物件相關聯,它指向了檔案對映以及該檔案的快取相關資訊。一個單一檔案可以有2個分離的對映,一個為可執行檔案,另一個為資料檔案。

DataSectionObject欄位指向了控制區域,它是一種服務於記憶體管理器和檔案系統之間的一個連結,它用於記憶體對映資料檔案。

ImageSectionObject欄位指向另一個控制區域結構,它用於記憶體對映可執行檔案。一個數據對映由單一連續的具有相同保護屬性的VA組成,一個映像對映則由可執行體不同的區段對映的多個不同保護屬性的區域組成。

SharedCacheMap欄位指向了檔案的SHARED_CACHE_MAP結構,它描述了該檔案被快取在那裡,快取了哪些部分。

上面提到的SECTION_OBJECT_POINTERS結構體中的欄位,是由記憶體管理器和檔案系統驅動配合該檔案物件的SECTION_OBJECT_POINTERS設定的。

APIs:

  • CcFlushCache()
  • CcPurgeCacheSection()
  • CcCoherencyFlushAndPurgeCache()
  • MmFlushImageSection()
  • MmForceSectionClosed()
  • MmCanFileBeTruncated()
  • MmDoesFileHaveUserWritableReferences()
  • CcGetFileObjectFromSectionPtrsRef()
  • CcSetFileSizes()

本文標題:關鍵的Windows核心資料結構一覽(下)

文章作者:r00tk1t

釋出時間:2018年01月14日 - 09時25分

最後更新:2020年10月20日 - 21時01分

原始連結:https://r00tk1ts.github.io/2018/01/14/關鍵的Windows核心資料結構一覽(下)/

程式設計是個人愛好