青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

Impossible is nothing  
  愛過知情重醉過知酒濃   花開花謝終是空   緣份不停留像春風(fēng)來又走   女人如花花似夢
公告
日歷
<2025年9月>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011
統(tǒng)計(jì)
  • 隨筆 - 8
  • 文章 - 91
  • 評論 - 16
  • 引用 - 0

導(dǎo)航

常用鏈接

留言簿(4)

隨筆分類(4)

隨筆檔案(8)

文章分類(77)

文章檔案(91)

相冊

搜索

  •  

最新評論

閱讀排行榜

評論排行榜

 
內(nèi)存與進(jìn)程管理器
==========================

                                   But I fear tomorrow I'll be crying,
                                   Yes I fear tomorrow I'll be crying.

                                                  King Crimson'69 -Epitaph


關(guān)于Windows NT內(nèi)存管理器的高層次信息已經(jīng)夠多的了,所以這里不會再講什么FLAT模型、虛擬內(nèi)存之類的東西。這里我們只講具體的底層的東西。我假定大家都了解>i386的體系結(jié)構(gòu)。


目錄
==========
     00.內(nèi)核進(jìn)程線程結(jié)構(gòu)體
     01.頁表
     02.Hyper Space
     03.System PTE'S
     04.Frame data base (MmPfnDatabase)
     05.Working Set
     06.向pagefile換頁
     07.page fault的處理
     08.從內(nèi)存管理器角度看進(jìn)程的創(chuàng)建
     09.上下文切換
     0a.某些未公開的內(nèi)存管理器函數(shù)
     0b.結(jié)語

附錄
     0c.某些未公開的系統(tǒng)調(diào)用
     0d.附注及代碼分析草稿


00.內(nèi)核進(jìn)程線程結(jié)構(gòu)體
===================================

Windows NT中的每一個(gè)進(jìn)程都是EPROCESS結(jié)構(gòu)體。此結(jié)構(gòu)體中除了進(jìn)程的屬性之外還引用了其它一些與實(shí)現(xiàn)進(jìn)程緊密相關(guān)的結(jié)構(gòu)體。例如,每個(gè)進(jìn)程都有一個(gè)或幾 個(gè)線程,線程在系統(tǒng)中就是ETHREAD結(jié)構(gòu)體。我來簡要描述一下存在于這個(gè)結(jié)構(gòu)體中的主要的信息,這些信息都是由對內(nèi)核函數(shù)的研究而得知的。首先,結(jié)構(gòu) 體中有KPROCESS結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體中又有指向這些進(jìn)程的內(nèi)核線程(KTHREAD)鏈表的指針(分配地址空間),基優(yōu)先級,在內(nèi)核模式或是用戶模 式執(zhí)行進(jìn)程的線程的時(shí)間,處理器affinity(掩碼,定義了哪個(gè)處理器能執(zhí)行進(jìn)程的線程),時(shí)間片值。在ETHREAD結(jié)構(gòu)體中還存在著這樣的信息: 進(jìn)程ID、父進(jìn)程ID、進(jìn)程映象名、section指針。quota定義了所能使用的分頁和非分頁池的極限值。VAD (virtual  address  descriptors)樹定義了用戶地址空間內(nèi)存區(qū)的狀況。關(guān)于Working Set的信息定義了在給定時(shí)間內(nèi)有那些物理頁是屬于進(jìn)程的。同時(shí)還有l(wèi)imit與statistics。ACCESS TOKEN描述了當(dāng)前進(jìn)程的安全屬性。句柄表描述了進(jìn)程打開的對象的句柄。該表允許不在每一次訪問對象時(shí)檢查訪問權(quán)限。在EPROCESS結(jié)構(gòu)體中還有指 向PEB的指針。

ETHREAD結(jié)構(gòu)體還包含有創(chuàng)建時(shí)間和退出時(shí)間、進(jìn)程ID和指向EPROCESS的指針,啟動地址,I/O請求鏈表和KTHREAD結(jié)構(gòu)體。在 KTHREAD中包含有以下信息:內(nèi)核模式和用戶模式線程的創(chuàng)建時(shí)間,指向內(nèi)核堆棧基址和頂點(diǎn)的指針、指向服務(wù)表的指針、基優(yōu)先級與當(dāng)前優(yōu)先級、指向 APC的指針和指向TEB的指針。KTHREAD中包含有許多其它的數(shù)據(jù),通過觀察這些數(shù)據(jù)可以分析出KTHREAD的結(jié)構(gòu)。


01.頁表
==================

通常操作系統(tǒng)使用頁表來進(jìn)行內(nèi)存操作。在Windows NT中,每一個(gè)進(jìn)程都有自己私有的頁表(進(jìn)程的所有線程共享此頁表)。相應(yīng)的,在進(jìn)程切換時(shí)會發(fā)生頁表的切換。為了加快對頁表的訪問,硬件中有一個(gè) translation lookaside buffer(TLB)。在Windows NT中實(shí)現(xiàn)了兩級的轉(zhuǎn)換機(jī)制。在386+處理器上將虛擬地址轉(zhuǎn)換為物理地址過程(不考慮分段)如下:

Virtual Address
+-------------------+-------------------+-----------------------+
|3 3 2 2 2 2 2 2 2 2|2 2 1 1 1 1 1 1 1 1|1 1                    |
|1 0 9 8 7 6 5 4 3 2|1 0 9 8 7 6 5 4 3 2|1 0 9 8 7 6 5 4 3 2 1 0|
+-------------------+-------------------+-----------------------+
|  Directory index  |  Page Table index |    Offset in page     |
+-+-----------------+----+--------------+-----+-----------------+
  |                      |                    |
  |                      |                    |
  |  Page Directory (4Kb)|  Page Table (4Kb)  |    Frame(4Kb)
  |  +-------------+     |  +-------------+   |  +-------------+
  |  |     0       |     |  |     0       |   |  |             |
  |  +-------------+     |  +-------------+   |  |             |
  |  |     1       |     |  |     1       |   |  |             |
  |  +-------------+     |  +-------------+   |  |             |
  |  |             |     +->|    PTE      +-+ |  |             |
  |  +-------------+        +-------------+ | |  | ----------- |
  +->|    PDE      +-+      |             | | +->| byte        |
     +-------------+ |      +-------------+ |    | ----------- |
     |             | |      |             | |    |             |
     +-------------+ |      +-------------+ |    |             |
     |             | |      |             | |    |             |
        ...          |        ...           |    |             |
     |    1023     | |      |    1023     | |    |             |
CR3->+-------------+ +----->+-------------+ +--->+-------------+

Windows NT 4.0使用平面尋址。NT的地址空間為4G。這4G地址空間中,低2G(地址0-0x7fffffff)屬于當(dāng)前用戶進(jìn)程,而高2G (0x80000000-0xffffffff)屬于內(nèi)核。在上下文切換時(shí),要更新CR3寄存器的值,結(jié)果就更換了用戶地址空間,這樣就達(dá)到了進(jìn)程間相互 隔絕的效果。

注:在Windows NT中,從第4版起,除4Kb的頁之外同時(shí)還使用了4Mb的頁(Pentium及更高)來映射內(nèi)核代碼。但是在Windows NT中沒有實(shí)際對可變長的頁提供支持。

PTE和PDE的格式實(shí)際上是一樣的。

PTE
+---------------+---------------+---------------+---------------+
|3 3 2 2 2 2 2 2|2 2 2 2 1 1 1 1|1 1 1 1 1 1    |               |
|1 0 9 8 7 6 5 4|3 2 1 0 9 8 7 6|5 4 3 2 1 0 9 8|7 6 5 4 3 2 1 0|
+---------------------------------------+-----------------------+
|                                       |T P C U R D A P P U R P|
|  Base address 20 bits                 |R P W         C W S W  |
|                                       |N T           D T      |
+---------------------------------------+-----------------------+

一些重要的位在i386+下的定義如下:
---------------------------------------------------------------------------
P     - 存在位。此位如果未設(shè)置,則在地址轉(zhuǎn)換時(shí)會產(chǎn)生異常。一般說來,在一些情況下NT內(nèi)核會使用未設(shè)置此位的PTE。
        例如,如果向pagefile換出頁,保留這些位可以說明其在頁面文件中的位置和pagefile號。
U/S   - 是否能從user模式訪問頁。正是借助于此位提供了對內(nèi)核空間的保護(hù)(通常為高2G)。
RW    - 是否能寫入

NT使用的為OS設(shè)計(jì)者分配的空閑位
---------------------------------------------------------------------------
PPT   - proto pte
TRN   - transition pte

當(dāng)P位未設(shè)置時(shí),第5到第9位即派上用場(用于page fault處理)。它們叫做Protection Mask,樣子如下:
--------------------------------------------------------------------------------------

* MiCreatePagingFileMap

9 8 7 6 5
---------
| | | | |
| | | | +- Write Copy
| | | +--- Execute
| | +----- Write
| +------- NO CACHE
+--------- Guard

GUARD | NOCACHE組合就是NO ACCESS


* MmGetPhysicalAddress

函數(shù)很短,但能從中獲得很多信息。在虛地址0xc0000000  -  0xc03fffff上映射有進(jìn)程的頁表。并且,映射的機(jī)制非常精巧。在 Directory Table(以下稱DT)有1100000000b個(gè)表項(xiàng)(對應(yīng)于地址0xc000..-0xc03ff..)指向自己,也就是說對于這些地址DT用作了 頁表(Page Table)!如果我們使用,比如說,地址(為方便起見使用二進(jìn)制)

     1100000000.0000000101.0000001001.00b
        ---------- ---------- --------------
     0xc0...    頁表選擇   頁表內(nèi)偏移
     頁目錄      
            
通過頁表101b的1001b號,我們得到了PTE。但這還沒完——DT本身映射在地址0xc0300000-0xc0300ffc上。在MmSystemPteBase中有值0xc0300000。為什么這樣——看個(gè)例子就知道了:

     1100000000.1100000000.0000001001.00b
        ---------- ---------- --------------
     0xc0...    0xc0...    頁目錄偏移
     頁目錄     頁表-
                頁目錄
                選擇
                
最后,在c0300c00包含著用于目錄本身的PDE。這個(gè)PDE的基地址的值保存在MmSystemPageDirectory中。同時(shí)系統(tǒng)為映射物理頁MmSystemPageDirectory保留了一個(gè)PTE,這就是MmSystemPagePtes。

這樣做能簡化尋址操作。例如,如果有PTE的地址,則PTE描述的頁的地址就等于PTE<<10。反過來:PTE=(Addr>>10)+0xc0000000。

除此之外,在內(nèi)核中存在著全局變量MmKseg2Frame = 0x20000。該變量指示在從0x80000000開始的哪個(gè)地址區(qū)域直接映射到了物理內(nèi)存,也就是說,此時(shí)虛擬地址0x80000000 - 0x9fffffff映射到了物理地址00000000-1f000000。

還有幾個(gè)有意思的地方。從c0000000開始有個(gè)0x1000*0x200=0x200000=2M的描述地址的表(0-7fffffff)。描述這些 頁的PDE位于地址c0300000-0xc03007fc。對于i486,在地址c0200000-c027fffc應(yīng)該是描述80000000到 a0000000的512MB的表,但對于Pentium在區(qū)域0xc0300800-0xc03009fc是4MB的PDE,其描述了從0 到1fc00000的步長為00400000的4M的物理頁,也就是說選擇了4M的頁。對應(yīng)于這些PDE的虛地址為80000000, 9fffffff。

這樣我們就得到了頁表的分布:

范圍 c0000000 - c01ffffc  用于00000000-7fffffff的頁表
范圍 c0200000 - c027ffff  "吃掉" 4M地址頁的地址
范圍 c0280000 - c02ffffc  包含用于a0000000 - bfffffff的頁
范圍 c0300000 - c0300ffc  PD 本身 (描述范圍c0000000 - c03fffff)
范圍 c0301000 - c03013fc  c0400000 - c04fffff HyperSpace  (更準(zhǔn)確的說, 是1/4的hyper space)
范圍 c0301400 - c03fffff  包含用于c050000 - ffffffff的頁

注:在0xc0301000-0xc0301ffc包含有描述hyper space的頁表。這是內(nèi)核的地址空間,且對于不同的進(jìn)程映射的內(nèi)容是不同的(另一方面,內(nèi)核空間又總是在每個(gè)用戶進(jìn)程的上下文中)。這是進(jìn)程私有的區(qū) 域。例如,working set就位于hyper space中。頁表的前256個(gè)PTE(hyper space的前1/4)為內(nèi)核保留,而且在需要快速向frame中映射虛擬地址時(shí)使用。

我給出一個(gè)向區(qū)域0xc0200000-0xc027f000中一個(gè)地址進(jìn)行映射的例子。

1100000000.1000000000.000000000000 = 0xc0200000

1) 解析出 PDE #1100000000 (4k 頁) 并選出 PageDirectory
2) 在 Directory 中選出 PTE #1000000000 (c0300800)
   這是個(gè) 4MB 的 PDE - 但這里忽略位長度,
   因?yàn)?PDE 用作了 PTE. 結(jié)果 c0200000 - c0200fff 被映射為
   80000000-80000fff

   c0201000 映射到下面的 - 80400000- 80400fff.
   等等直到 c027f000 - 9fc00000

PTE, 位于c0200000到c027fffc - 描述了80000000 -  9ffffc00 (512m)

02.Hyper Space
==============

HyperSpace是內(nèi)核空間中的一塊區(qū)域 (4mb), 不同的進(jìn)程映射內(nèi)容不同。對于轉(zhuǎn)換,4MB足夠放下頁表完整的一頁。這個(gè)表位于地址0xc0301000 - 0xc0301ffc(PDE的第0個(gè)表項(xiàng)位于0xc0300c04)。在內(nèi)部,為向HyperSpace區(qū)域中映射物理頁(當(dāng)需要快速為某個(gè)frame 組織虛擬地址時(shí))要使用函數(shù):

DWORD MiMapPageInHyperSpace(DWORD BaseAddr,OUT PDWORD Irql);

它返回HyperSpace中的虛擬地址,這個(gè)虛擬地址被映射到所要的物理頁上。這個(gè)函數(shù)是如何工作的,工作的時(shí)候用到了什么?

在內(nèi)核中有這樣的變量:

MmFirstReservedMappingPte=0xc0301000
MmLastReservedMappingPte=0xc03013fc

這兩個(gè)變量描述了255個(gè)pte,這些pte描述了區(qū)域:

0xc0400000-0xc04fffff (1/4 HyperSpace)

在MmFirstReservedMappingPte處是一個(gè)pte,其中的基址扮演了計(jì)數(shù)器的角色(從0到255)(當(dāng)然,pte是無效的,p位無 效)。為所需地址添加pte時(shí)要依賴計(jì)數(shù)器當(dāng)前的值……并且計(jì)數(shù)器使用了下開口堆棧的原理,從ff開始。一般來說,頁表中的pte用作信息上的目的并不是 唯一的情況。


03.System PTE'S
===============

在內(nèi)核中有一塊這樣的內(nèi)存——系統(tǒng)pte。什么是系統(tǒng)pte,以及內(nèi)核如何使用系統(tǒng)pte?

*見函數(shù) MiReserveSystemPtes(...)

系統(tǒng)為空閑PTE維護(hù)了某些結(jié)構(gòu)體。首先為了快速滿足密集請求(當(dāng)內(nèi)核需要pte映射某些物理頁時(shí))系統(tǒng)中有個(gè)Sytem Ptes Pool。而且pool中有pte blocks(blocks表示請求是以block為單位來滿足的,一個(gè)block中有一些pte,1、2、4、8和16個(gè)pte)。

系統(tǒng)中有以下這些表:

BYTE MmSysPteTables[16]={0,0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4};
DWORD MmSysPteIndex[5]={1,2,4,8,16};
DWORD MmFreeSysPteListBySize[5];
PPTE  MmLastSysPteListBySize[5];
DWORD MmSysPteListBySizeCount[5];
DWORD MmSysPteMinimumFree[5]={100,50,30,20,20}
PVOID MmSystemPteBase;// 0xc0200000

在pool中的空閑PTE被組織成了鏈表(當(dāng)然,pte是位于頁表中,也就是說鏈表結(jié)構(gòu)體位于頁表中,這是真的)。鏈表的元素:

typedef struct _FREE_SYSTEM_PTES_BLOCK{
/*pte0*/ SYSPTE_REF NextRef;                 // 指向后面的block
/*pte1*/ DWORD FlushUnkn;                    // 在Flush時(shí)使用
/*pte2*/ DWORD ArrayOfNulls[ANY_SIZE_ARRAY]; // 空閑 PTE
     }FREE_SYSTEM_PTES_BLOCK PFREE_SYSTEM_PTES_BLOCK;

用作指向后面元素指針的PTE的地址可如此獲得:VA=(NextRef>>10)+MmSystemPteBase (低10位永遠(yuǎn)為0,相應(yīng)的p位也為0)。鏈表最后一個(gè)元素NextRef域的值為0xfffff000 (-1) 。相應(yīng)的,鏈表有5個(gè)(block大小分別為1,2,4,8和16個(gè)pte)。

*見函數(shù) MiReserveSystemPtes2(...) / MiInitializeSystemPtes

除pool外還有一個(gè)undocumented的空閑系統(tǒng)pte鏈表。

PPTE MmSystemPtesStart[2];
PPTE MmSystemPtesEnd[2];
SYSPTE_REF MmFirstFreeSystemPte[2];
DWORD MmTotalFreeSystemPtes[2];

在兩個(gè)鏈表中有兩個(gè)引用。鏈表的元素:

typedef struct _FREE_SYSTEM_PTES{
  SYSPTE_REF Next; // #define ONLY_ONE_PTE_FLAG 2, last = 0xfffff000
  DWORD NumOfFreePtes;
}FREE_SYSTEM_PTES PFREE_SYSTEM_PTES;

而且,1號鏈表原則上沒有組織。0號鏈表(MiReleaseSystemPtes)用于釋放的pte。pte有可能進(jìn)入System Ptes Pool。若在請求MiReserveSystemPtes(...)時(shí)pte的數(shù)目大于16,則同時(shí)pte從0號鏈表分配。也就是說,0號鏈表與 pool有關(guān)聯(lián),而1號則沒有。

為了使工作的結(jié)果不與TLB相矛盾,系統(tǒng)要么使用重載cr3,要么使用命令invlpg。“高級”函數(shù)

MiFlushPteList(PTE_LIST* PteList, BOOLEAN bFlushCounter, DWORD PteValue);

進(jìn)行以下工作:

初始化PTE并調(diào)用invlpg(匯編指令)。

typedef struct PTE_LIST{
     DWORD Counter; // max equ 15
     PVOID PtePointersInTable[15];
     PVOID PteMappingAddresses[15];
     };

如果Counter大于15,則調(diào)用KeFlushCurrentTb(只是重載CR3),并且如果設(shè)置了bFlushCounter,則向MmFlushCounter加0x1000。


04.Page Frame Number Data Base (MmPfnDatabase)
======================================

內(nèi)核將有關(guān)物理頁的信息保存在pfn數(shù)據(jù)庫中(MmPfnDatabase)。本質(zhì)上講,這只是個(gè)0x18字節(jié)長的結(jié)構(gòu)體塊。每一個(gè)結(jié)構(gòu)體對應(yīng)一個(gè)物理頁 (順序排列,所以元素常被稱為Pfn - page frame number)。結(jié)構(gòu)體的數(shù)量對應(yīng)于系統(tǒng)中4KB頁的數(shù)量(或者說是內(nèi)核可見的頁的數(shù)量,需要的話可以在boot.ini中使用相應(yīng)的選項(xiàng)來為NT內(nèi)核做 出這塊“壞”頁區(qū))。通常,結(jié)構(gòu)體形式如下:

typedef struct _PfnDatabaseEntry
    {
    union {
    DWORD NextRef; // 0x0 如果frame在鏈表中,則這個(gè)就是frame的號
                   // 最后的一個(gè)為 -1
    DWORD Misc;    // 同時(shí)另外一項(xiàng)信息, 依賴于上下文
                   // 見偽代碼 (通常 TmpPfn->0...)
                   // 通常這里有 *KTHREAD, *KPROCESS,
             // *PAGESUPPORT_BLOCK...
          };
    PPTE PtePpte;  // 0x4 指向 pte 或 ppte
    union {        // 0x8
          DWORD PrevRef;      // 前面的frame或 (-1, 第一個(gè))
          DWORD ShareCounter; // Share 計(jì)數(shù)器
          };
    WORD Flags;      // 0xc 見下面
    WORD RefCounter; // 0xe 引用計(jì)數(shù)
    DWORD Trans;     // 0x10 ?? 見下面. 用于 pagefile
    DWORD ContFrame;//ContainingFrame; // 14
    }PfnDatabaseEntry;
/*
Flags (名字取自windbg !pfn的結(jié)果)

掩碼   位     名字  值
-----  ----   ---   --------
0001   0      M     Modifyied
0002   1      R     Read In Progress
0004   2      W     WriteInProgress
0008   3      P     Shared
0070   [4:6]  Color Color (In fact Always null for x86)
0080   7      X     Parity Error
0700   [8:10] State 0- Zeroed
              /List 1- Free
                    2- StandBy
                    3- Modified
                    4- ModifiedNoWrite
                    5- BadPage
                    6- Active
                    7- Trans
0800   11     E     InPageError

Trans域的值用在frame的內(nèi)容位于PageFile中的時(shí)候或是frame的內(nèi)容位于與這個(gè)Page File PTE對應(yīng)的其它映象文件中的時(shí)候。

我給出未設(shè)置P位的PTE的例子(這種PTE不由平臺體系結(jié)構(gòu)確定,而由OS確定)。

* 取自 @MiReleasePageFileSpace (Trans)

Page File PTE
+---------------------------------------+-+-+---------+-------+-+
|3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1|1|1|0 0 0 0 0|0 0 0 0|0|
|1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2|1|0|9 8 7 6 5|4 3 2 1|0|
+---------------------------------------+-+-+---------+-------+-+
| offset                                |T|P|Protect. |page   |0|
|                                       |R|P|mask     |file   | |
|                                       |N|T|         |Num    | |
+---------------------------------------+-+-+---------+-------+-+

Transition PTE
+---------------------------------------+-+-+---------+-------+-+
|3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1|1|1|0 0 0 0 0|0 0 0 0|0|
|1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2|1|0|9 8 7 6 5|4 3 2 1|0|
+---------------------------------------+-+-+---------+-------+-+
| PFN                                   |T|P|Protect. |C W O W|0|
|                                       |R|P|mask     |D T    | |
|                                       |N|T|         |       | |
+---------------------------------------+-+-+---------+-------+-+

W - write
O - owner
WT - write throuth
CD - cache disable

可能所有這些現(xiàn)在還不很易懂,但是看完下面就能明白了。當(dāng)然,這個(gè)結(jié)構(gòu)體是未公開的。顯然,結(jié)構(gòu)體能夠組織成鏈表。frame由以下結(jié)構(gòu)體支持:

struct _MmPageLocationList{

PPfnListHeader ZeroedPageListhead;         //&MmZeroedPageListhead
PPfnListHeader FreePageListHead;           //&MmFreePageListHead
PPfnListHeader StandbyPageListHead;        //&MmStandbyPageListHead
PPfnListHeader ModifiedPageListHead;       //&MmModifiedPageListHead
PPfnListHeader ModifiedNoWritePageListHead;//&MmModifiedNoWritePageListHead
PPfnListHeader BadPageListHead;            //&MmBadPageListHead
}MmPageLocationList;

這其中包含了6個(gè)鏈表。各域的名字很好的說明了它們的用處。frame的狀態(tài)與這些鏈表密切關(guān)聯(lián)。下面列舉了frame的狀態(tài):

+---------------+----------------------------------------------------+------+
|狀態(tài)           |描述                                                 | 鏈表 |
+---------------+----------------------------------------------------+------+
|Zero           |清零的可用空閑頁                                     |  0   |
|Free           |可用空閑頁                                           |  1   |
|Standby        |不可用但可輕易恢復(fù)的頁                               |  2   |
|Modified       |要換出的dirty頁                                      |  3   |
|ModifiedNoWrite|不換出的dirty頁                                      |  4   |
|Bad             |不可用的頁(有錯(cuò)誤)                                 |  5   |
|Active         |活動頁,至少映射一個(gè)虛擬地址                         |  -   |
+---------------+----------------------------------------------------+------+

frame可能處在6個(gè)鏈表中的某一個(gè),也可能不在這些鏈表中(狀態(tài)為Active)。如果頁屬于某個(gè)進(jìn)程,則這個(gè)頁就被記錄在Working Set中(見后面)。同時(shí),如果frame由內(nèi)存管理器自己使用,則一般可以不考慮這些frame的位置。

每個(gè)鏈表的表頭都是下面這個(gè)樣子:

typedef struct _PfnListHeader{
     DWORD Counter; // 鏈表中frame的數(shù)目
     DWORD LogNum;  // 鏈表號.0 - zeroed, 1- Free etc...
     DWORD FirstFn; // MmPfnDatabase中的第一個(gè)frame號
     DWORD LastFn;  // --//--- 最后一個(gè).
     }PfnListHeader PPfnListHeader;

除此之外,可以用“color”(就是cache)來尋址空閑frame(zeroed或是free)。如果看一下附錄中的偽代碼就容易理解了。我給出兩個(gè)結(jié)構(gòu)體:

struct  {
     ColorHashItem* Zeroed; //(-1) нет
     ColorHashItem* Free;
     }MmFreePagesByColor;

typedef struct _ColorHashItem{
          DWORD FrameNum;
                PfnDatabaseEntry* Pfn;
                } ColorHashItem;

有一套函數(shù)使用color來處理frame(處理cache)。例如,MiRemovePageByColor(FrameNum, Color); 看一下這些函數(shù)及其參數(shù)返回值的名稱和函數(shù)的反匯編代碼,很容易猜到相應(yīng)的內(nèi)容,所以這里就不描述了,在說一句,這些函數(shù)都是未導(dǎo)出的。在使用color 的時(shí)候,要考慮color掩碼,最后選擇color。

Windows NT符合C2安全等級,所以應(yīng)該在為進(jìn)程分配頁的時(shí)候應(yīng)將頁清零。我們來看一下將frame清零的系統(tǒng)進(jìn)程的線程。最后,在 Phase1Initialization()中所作的是調(diào)用MmZeroPageThread。不難猜到——線程將空閑頁清零并將其移動到zeroed 頁的鏈表中。

MmZeroPageThread
{
//
//.... 沒意思的東西我們略過 ;)
//
while(1)
{
KeWaitForSingleObject(MmZeroingPageEvent,8,0,0,0); // 等待事件
while(!KeTryToAcquireSpinLock(MmPfnLock,&OldIrql)); // 獲取 PfnDatabase
while(MmFreePageListHead.Count){
            MiRemoveAnyPage(MmFreePageListHead.FirstFn&MmSecondaryColorMask);
               // 從空閑鏈表中取出頁
            Va=MiMapPageToZeroInHyperSpace(MmFreePageListHead.FirstFn);
            KeLowerIrql(OldIrql);

            memset(Va,0,0x1000); // clear page

            while(!KeTryToAcquireSpinLock(MmPfnLock,&OldIrql);
            MiInsertPageInList(&MmZeroedpageListHead,FrameNum);
                         // 將已清零的頁插入Zero鏈表
            }
  MmZeroingPageThreadActive=0; // 清標(biāo)志
  KeLowerIrql(OldIrql);
  }
// 永不退出
}

// 函數(shù)只是將frame映射到定義的地址上
// 以使其可被清零
DWORD MiMapPageToZeroInHyperSpace(FrameNum)
{
if(FrameNum<MmKseg2Frame)return ((FrameNum+0x80000)<<12); // 落入直接映射區(qū)域
                                      
TmpPte=0xc0301404;
TmpVa=0xc0501000;
*TmpPte=0;
invlpg((void*)TmpVa); // asm instruction in fact
*TmpPte=FrameNum<<12|ValidPtePte;
return TmpVa; // always 0xc0501000;
}

在何時(shí)MmZeroingPageEvent被激活?這發(fā)生在向空閑頁鏈表中添加frame的時(shí)候:

MiInsertPageInList()
{
.....
if(MmFreePageListHead.Count>=MmMinimumFreePagesToZero&&
       !MmZeroingPageThreadActive)
    {
     MmZeroingPageThreadActive=1;
     KeSetEvent(&MmZeroingPageEvent,0,0);
    }
....
}

注:內(nèi)核并不總是依賴這個(gè)線程,有時(shí)會遇到這樣的代碼,它獲取一個(gè)空閑頁,用過后自己將其清零。

05.Working Set
==============
Working Set——工作集,是屬于當(dāng)前進(jìn)程的物理頁集。內(nèi)存管理器使用一定的機(jī)制跟蹤進(jìn)程的工作集。working set有兩個(gè)限額:maximum  working set和minimum working set。這是工作集的最大值和最小值。內(nèi)存管理器以這兩個(gè)值為依據(jù)來維護(hù)進(jìn)程的工作集(工作集大小不小于最小值,不大于最大值)。在定義條件的時(shí)候,工作 集被裁減,這時(shí)工作集的frame落入空閑鏈表。內(nèi)核工作集是結(jié)構(gòu)體的總和。

在進(jìn)程結(jié)構(gòu)體的偏移0xc8(NT4.0)有以下結(jié)構(gòu)體。

typedef struct _VM{
/* C8*/   LARGE_INTEGER UpdateTime;              //0
/* D0*/   DWORD Pages;                           //8 called so, by S-Ice authors
/* D4*/   DWORD PageFaultCount                   //0c faults;
//                    in fact number of MiLocateAndReserveWsle calls
/* D8*/   DWORD PeakWorkingSetSize;              //10 all
/* DC*/   DWORD WorkingSetSize;                  //14  in
/* E0*/   DWORD  MinimumWorkingSet;              //18   pages, not in
/* E4*/   DWORD  MaximumWorkingSet;              //1c     bytes
/* E8*/   PWS_LIST WorkingSetList;               //20 data table
/* EC*/   LIST_ENTRY WorkingSetExpansion;        //24 expansion
/* F4*/   BYTE fl0; // Operation???              //2c
     BYTE fl1; // always 2???               //2d
     BYTE fl2; // reserved??? always 0      //2e
     BYTE fl3; //                           //2f
     }VM *PVM;

WinDbg !procfields的擴(kuò)展命令用到VM。這里重要的是,跟蹤page fault的數(shù)量(PageFaultCount),MaximumWorkingSet和MinimumWorkingSet,管理器以它們?yōu)榛A(chǔ)來支持工作集。

注:實(shí)際上,PageFaultCount并非是嚴(yán)格的計(jì)數(shù)。這個(gè)計(jì)數(shù)在MiLocateAndReserveWsle函數(shù)中被擴(kuò)大,因?yàn)檫@個(gè)函數(shù)不只在page fault時(shí)被調(diào)用,在某些其它情況下也會被調(diào)用(真的,很少見)。

下面這個(gè)結(jié)構(gòu)體描述了包含工作集頁的表。

typedef struct _WS_LIST{
DWORD        Quota;              //0 ??? i'm not shure....
DWORD        FirstFreeWsle;      // 4 start of indexed list of free items
DWORD        FirstDynamic;       // 8 Num of working set wsle entries in the start
                                 // FirstDynamic
DWORD        LastWsleIndex;      // c above - only empty items
DWORD        NextSlot;           // 10 in fact always == FirstDynamic
                                 // NextSlot
PWSLE        Wsle;               // 14 pointer to table with Wsle
DWORD        Reserved1           // 18 ???
DWORD        NumOfWsleItems;     // 1c Num of items in Wsle table
                       // (last initialized)
DWORD        NumOfWsleInserted;  // 20 of Wsle items inserted (WsleInsert/
                                 //                              WsleRemove)
PWSHASH_ITEM HashPtr;            // 24 pinter to hash, now we can get index of
                       //   Wsle item by address. Present only if
                                 //   NumOfWsleItems>0x180
DWORD        HashSize;           // 28 hash size
DWORD        Reserved2;          // 2c ???
}WS_LIST *PWS_LIST;

typedef struct _WSLE{ // 工作集表的元素
        DWORD PageAddress;
     }WSLE *PWSLE;

// PageAddress 本身是工作集頁的虛地址
// 低12位用作頁屬性(虛地址總是4K的倍數(shù))

#define WSLE_DONOTPUTINHASH 0x400 // 不放在cache中
#define WSLE_PRESENT 0x1 // 非空元素
#define WSLE_INTERNALUSE 0x2 // 被內(nèi)存管理器使用的frame

// 未設(shè)置WSLE_PRESENT的空閑WSLE本身是下一個(gè)空閑WSLE的索引。這樣,空閑的WSLE就組織成了鏈表。最后一個(gè)空閑WSLE表示為-1。

#define EMPTY_WSLE (next_emty_wsle_index) (next_emty_wsle_index<<4)
#define LAST_EMPTY_WSLE 0xfffffff0

typedef struct _WSHASH_ITEM{
     DWORD PageAddress; //Value
     DWORD WsleIndex; //index in Wsle table
}WSHASH_ITEM *PWSHASH_ITEM;

//cache函數(shù)很簡單。內(nèi)部函數(shù)的偽代碼:
//MiLookupWsleHashIndex(Value,WorkingSetList)
//{
//Val=value&0xfffff000;
//TmpPtr=WorkingSetList->HashPtr;
//Mod=(Val>>0xa)%(WorkingSetList->HashSize-1);
//if(*(TmpPtr+Mod*8)==Val)return Mod;
//while(*(TmpPtr+Mod*8)!=Val)){
//   Mod++;
//   if(WorkingSetList->HashSize>Mod)continue;
//   Mod=0;
//   if(fl)KeBugCheckEx(0x1a,0x41884,Val,Value,WorkingSetList);
//   fl=1;
//   }
//return Mod;
//}

我們來看一下典型的進(jìn)程working set。WorkingSetList位于地址MmWorkingSetList (0xc0502000)。這是hyper space的區(qū)域,所以在進(jìn)程切換時(shí),要更新這些虛地址,這樣,每個(gè)進(jìn)程都有自己的工作集結(jié)構(gòu)體。在地址MmWsle (0xc0502690)上是Wsle動態(tài)表的起始地址。表的結(jié)尾的地址總是0x1000的倍數(shù),也就是說表可以結(jié)束在地址0xc0503000、 0xc0504000等等上(這是為了簡化對Wsle表大小的操作)。Cache(如果有)位于一個(gè)偏移上,Wsle不會向這個(gè)偏移增長。我們來詳細(xì)看一 下這個(gè)表:

// WsList-0xc0502000---
// ....
// -------0xc0502030----
// pde 00 fault counter
// pde 01 fault counter
// pde 02 fault counter
//
// +-Wsle==0xc0502690---             +--Pde/pte     +-----Pfn[0]------
// |0 c0300000|403 Page Directory    |c0300c00 pde  |pProcess
// |4 c0301000|403 Hyper Space       |c0300c04 pte  |1
// |8 MmWorkingSetList(c0502000)|403 |c0301408 pte  |2
// |c MmWorkingSetList+0x1000 | 403  |.             |3
// |10 MmWorkingSetList+0x2000 | 403 |.              .
// |         ....
// |FirstDynamic*4 FrameN
// |....                             |.              .
//                                                   .
// |LastWsleIndex*4 FrameM
// +--------                         +------        +-------
// | free items
// ....
// | 0xfffffff0
// +-------------------


// Cache
// ....

這里有個(gè)有意思的地方,在表的起始部分有FirstDynamic的頁,用于建立Wsle,WorkingSetList和cache。同時(shí)這里還有頁目 錄frame,HyperSpace和某些其它的頁,這些頁是內(nèi)存管理器所需要的,不能從工作集中移出(標(biāo)志W(wǎng)SLE_INTERNALUSE)。之后, 我們還能看到兩種對Pfn frame域偏移0使用的變體。對于頁目錄frame,這是指向進(jìn)程的指針,對于通常的屬于工作集的頁,這是在表內(nèi)的索引。

在WorkingSetList和Wsle表的起始地址之間還有不大的0x660字節(jié)的空閑空間。關(guān)于如何分配這些空間的信息是沒有的,但是很快在 WorkingSetList開始有用于用戶空間(通常為低2GB)的page fault counter,也就是說如果,譬如說,索引0x100的元素有值3,則表示從3開始(如果不考慮可能的溢出)page fault用于范圍[0x40000000-0x403fffff]的頁。

工作集的限額在內(nèi)核模式下可以通過導(dǎo)出的未公開函數(shù)來修改:

NTOSKRNL MmAdjustWorkingSetSize(
          DWORD MinimumWorkingSet OPTIONAL, // if both == -1
          DWORD MaximumWorkingSet OPTIONAL, // empty working set
          PVM Vm OPTIONAL);

為處理WorkingSet,管理器使用了許多內(nèi)部函數(shù),了解了這些函數(shù)就能明白其工作的原理。


06.向pagefile換頁
========================================

frame可以是空閑的——當(dāng)RefCounter等于0且位于一個(gè)鏈表中時(shí)。frame可以屬于工作集。在缺少空閑frame時(shí)或是在達(dá)到treshhold時(shí),就會發(fā)生frame的換出。這方面的高層次函數(shù)是有的。這里的任務(wù)是用偽代碼來證實(shí)。

在NT中有最多16個(gè)pagefile。pagefile的創(chuàng)建發(fā)生于模塊SMSS.EXE。這時(shí)打開文件及其句柄向 PsInitialSystemProcess進(jìn)程的句柄表拷貝。我給出創(chuàng)建pagefile的未公開系統(tǒng)函數(shù)的原型(如果不從核心調(diào)用的話就必須有創(chuàng)建 這種文件的權(quán)限)。

NTSTATUS NTAPI NtCreatePagingFile(
     PUNICODE_STRING FileName,
     PLARGE_INTEGER MinLen, // 高位雙字應(yīng)為0
     PLARGE_INTEGER MaxLen, // minlen應(yīng)大于1M
     DWORD Reserved // 忽略
       );

每個(gè)pagefile都有一個(gè)PAGING_FILE結(jié)構(gòu)體。

typedef struct _PAGING_FILE{
       DWORD MinPagesNumber;      //0
       DWORD MaxPagesNumber;      //4
       DWORD MaxPagesForFlushing; //8 (換出頁的最大值)
       DWORD FreePages;           //c(Free pages in PageFile)
       DWORD UsedPages;           //10 忙著的頁
       DWORD MaxUsedPages;        //14
       DWORD CurFlushingPosition; //18 -???
       DWORD Reserved1;           //1c
       PPAGEFILE_MDL Mdl1; //       20 0x61 - empty ???
       PPAGEFILE_MDL Mdl2; //       24 0x61 - empty ???
       PRTL_BITMAP PagefileMap; //  28 0 - 空閑, 1 - 包含換出頁
       PFILE_OBJECT FileObject;   //2c
       DWORD NumberOfPageFile;    //30
       UNICODE_STRING FileName;   //34
       DWORD Lock;                //3d
     }PAGING_FILE *PPAGING_FILE;

DWORD MmNumberOfActiveMdlEntries;
DWORD MmNumberOfPagingFiles;

#define MAX_NUM_OF_PAGE_FILES 16
PPAGING_FILE MmPagingFile[MAX_NUM_OF_PAGE_FILES];

在內(nèi)存子系統(tǒng)啟動時(shí)(MmInitSystem(...))會啟動線程MiModifiedPageWriter,該線程進(jìn)行以下工作:初始化 MiPaging和 MiMappedFileHeader,在非換出域中創(chuàng)建并初始化MmMappedFileMdl,建立優(yōu)先級 LOW_REALTIME_PRIORITY+1,等待KEVENT,初始化MmMappedPageWriterEvent和 MmMappedPageWriterList鏈表,啟動MiMappedPageWriter線程,啟動函數(shù) MiModifiedPageWriterWorker。

在任務(wù)MiModifiedPageWriterWorker中會等待事件MmModifiedPageWriterEvent,處理鏈表 MmModifiedNoWritePageList和MmModifiedPageList并準(zhǔn)備實(shí)現(xiàn)向映象文件或pagefile的頁換出(調(diào)用 MiGatherMappedPages或是MiGatherPagefilePages)。

在MiGatherPagefilePages中使用IoAsynchronousPageWrite( )函數(shù)進(jìn)行frame的換出。而且不是一個(gè)frame,而是一簇(頁數(shù)目總和為MmModifiedWriteClasterSize)。向 pagefile換出頁是由PAGING_FILE結(jié)構(gòu)體中的PagefileMap來跟蹤的。

研究函數(shù)的偽代碼在appendix.txt中。這里描述偽代碼沒有什么意義——都很簡單。


07.page fault的處理
==============================

對于轉(zhuǎn)向?qū)agefault的研究,我們現(xiàn)在有了所有必須的信息了。轉(zhuǎn)換線性地址時(shí),當(dāng)線性地址(分頁機(jī)制打開)的所用的PDE/PTE的P (present)位無效或是違反了保護(hù)規(guī)則,在+i386處理器里會產(chǎn)生異常14。這時(shí),在堆棧中有錯(cuò)誤代號,包含有以下信息:用戶/內(nèi)核錯(cuò)誤位(異常 發(fā)生在ring3還是ring0?),讀寫錯(cuò)誤位(試圖讀還是寫?),頁存在位。除此之外,在CR2寄存器中存有產(chǎn)生異常的32位線性地址。內(nèi)核中處理 14號中斷的是_KiTrap0E。

當(dāng)要轉(zhuǎn)換的頁沒有相應(yīng)的物理頁時(shí),內(nèi)存管理器執(zhí)行確定好的工作來“修正”。這些是由異常處理函數(shù)調(diào)用高層函數(shù)MmAccessFault    (Wr,Addr,P);來完成的。在對偽代碼的進(jìn)行分析之前,想一下在什么樣的情況下會發(fā)生page fault是很有用的。

最顯然的就是訪問錯(cuò)誤,這時(shí)ring3的代碼試圖寫入PTE/PDE中未設(shè)置U位的頁或是寫入了只讀的頁(PTE/PDE中未設(shè)置W位)。再有,頁可以被 換出到頁面文件中,對應(yīng)于這些頁的PTE中未設(shè)置P位,但有信息指示在哪個(gè)頁面文件中尋找frame,以及frame的偏移。還有一個(gè)類似的情況—— frame屬于映象文件。除此之外,所轉(zhuǎn)換的頁可能只屬于已分配的內(nèi)存區(qū)(使用NtAllocateMemory),也可能轉(zhuǎn)換的是原先沒轉(zhuǎn)換過的頁,這 中情況下,VMM分配清零過的frame(這是C2的要求)。最后,異常還可能是由寫copy on write頁和轉(zhuǎn)換共享內(nèi)存引發(fā)。以上只列出了主要的情況。

處理的結(jié)果通常是向當(dāng)前進(jìn)程的Working Set中添加相應(yīng)的frame。

異常的每一種情況都相應(yīng)有一個(gè)內(nèi)部的結(jié)構(gòu)體與之相關(guān)聯(lián),VMM就處理這些結(jié)構(gòu)體。這些結(jié)構(gòu)體十分復(fù)雜,要對它們進(jìn)行完整的描述的話,需要反匯編大量的函 數(shù)。目前還沒有大部分結(jié)構(gòu)體的完整信息,但對于理解異常處理程序來說并不要求知道這些。我來大致描述一下VAD和PPTE的概念,研究異常處理程序的偽代 碼要用到。


VAD

操作虛擬地址需要用到VAD (Virtual Address Descriptor)。我們熟知的(有一個(gè)幾乎與之同名的Win32函數(shù)調(diào)用這個(gè)函數(shù))未公開函數(shù)NtAllocateVirtualMemory (ring0下是ZwAllocateVirtualMemory)操作這些結(jié)構(gòu)體。

每一個(gè)VAD都描述了虛地址空間中的區(qū)域,實(shí)際上,除了區(qū)域的起止地址外還有保護(hù)信息(見ZwAllocateVirualMemory函數(shù)的參數(shù))。而 同時(shí)還有其它一些特殊的信息(目前除了首部之外還沒有VAD的完整信息)。VAD結(jié)構(gòu)體只對用戶地址(低2GB)有意義,使用這些結(jié)構(gòu)體VMM可以捕獲到 發(fā)生異常的區(qū)域。VAD的結(jié)構(gòu)是一個(gè)平衡二叉樹(有內(nèi)部函數(shù)負(fù)責(zé)修整此樹),這是為查找而進(jìn)行的優(yōu)化。在VAD中有兩個(gè)指向后面元素——左右子樹——的指 針。樹的根位于EPROCESS結(jié)構(gòu)體的VadRoot域(NT 4.0下是偏移0x170)。當(dāng)然,每一個(gè)進(jìn)程都有自己的VAD樹。VAD的首部形式如下:

typedef struct vad_header {
     void *StartingAddress;
     void *EndingAddress;
     struct vad *ParentLink;
     struct vad *LeftLink;
     struct vad *RightLink;
     ULONG Flags;
}VAD_HEADER, *PVAD;


PPTE

Prototype Pte是又一級的線性地址轉(zhuǎn)換并用于共享內(nèi)存。假設(shè)有個(gè)文件映射到了幾個(gè)(3個(gè))進(jìn)程的地址空間。PPTE表包含有PPTE,這些PPTE描述了加載到內(nèi) 存的文件的物理頁。某些PPTE可以有P位(其位置與含義與PTE/PDE的相同),而某些則沒有,沒有P位的有信息用來決定是從頁來加載frame還是 從映象文件來加載文件。所有三個(gè)進(jìn)程的文件都映射在不同的地址上,對應(yīng)于這些頁的PTE的P位未設(shè)置,并且包含有文件頁的PPTE的引用。這樣,在轉(zhuǎn)換映 射到文件的線性地址的時(shí)候,在一號進(jìn)程中發(fā)生異常14,VMM找到PTE,得到對PPTE的引用,現(xiàn)在可以直接“修正”相應(yīng)的PTE,以使其指向?qū)儆谖募? 的frame,這時(shí)必需從文件中加載frame。我給出未設(shè)置P位PTE的格式,在頁表中其指向原型PTE。

PTE points to PPTE
+-----------------------------------------+-+---+-------------+-+
|3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1|1|0 0|0 0 0 0 0 0 0|0|
|1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1|0|9 8|7 6 5 4 3 2 1|0|
+-----------------------------------------+-+---+-------------+-+
| Address [7:27]                          |1|Un | Address     |0|
|                                         | |use|   [0:6]     | |
|                                         | |d  |             | |
+-----------------------------------------+-+---+-------------+-+


*MmAccessFault

我們開始來研究一下MmAccessFault的偽代碼。其原型:

NTSTATUS MmAccessFault (BOOL Wr,DWORD Addr, BOOL P)

參數(shù)的意義很明顯:寫入標(biāo)志,發(fā)生異常的地址和頁存在位。對于確定異常的原因,這些信息就足夠了。根據(jù)Addr是屬于內(nèi)核地址空間還是用戶地址空間,處理 程序從兩個(gè)執(zhí)行分支中選擇一個(gè)。第一種情況下的處理程序較為簡單,跟蹤ACCESS VIOLATION或是收回在Working Set中的頁(MiDispatchFault)。若是用戶空間的地址情況就就更為復(fù)雜一些。首先,如果PDE不在內(nèi)存中則執(zhí)行用于PDE的異常處理程 序。然后,出現(xiàn)了一個(gè)分支。第一個(gè)分支——頁存在。這表示要么是ACCESS VIOLATION,要么就是對copy on write的處理。第二個(gè)分支——處理清零頁請求、ACCESS VIOLATION、頁邊界(GUARD)(堆棧增長)以及必須的對working set中頁的回收。有趣的是,在大量發(fā)生page fault的時(shí)候,系統(tǒng)會增大working set的大小。在零PTE的情況下,為確定狀況,處理程序不得不使用VAD樹來確定試圖訪問區(qū)域的屬性。這些都是MiAccessCheck的工作,這個(gè) 函數(shù)返回訪問的狀態(tài)。

一般情況下,異常處理程序的主要奠基工作是由MiDispatchFault函數(shù)執(zhí)行的。它能更精確的確定狀況并決定下一步的工作。

輪到MiDispatchFault了,它主要是基于一些更低級的函數(shù):MiResolveTransitionFault、 MiResolveDemandZeroFault、MiResolveDemandZeroFault、MiResolveProtoPteFault 和MiResolvePageFileFault。從這些函數(shù)的名字可以明顯看出,這個(gè)函數(shù)用于確定更為具體的情況:狀態(tài)為'transition'(可 能會很快回收入Working Set)的頁應(yīng)該是空白的frame,PTE指向PPTE并且frame換出到相應(yīng)的頁面文件中。在與頁面文件有關(guān)的和某些與PPTE有關(guān)的情況下,接著 可能需要從文件中讀取frame,此時(shí)函數(shù)返回值為0xc0033333,表示必須從文件中讀取頁。這在MiDispatchFault中是靠 IoPageRead進(jìn)行的。我們來更仔細(xì)的研究一下所提到的函數(shù)。我們從MiResolveDemandZeroFault開始。

如果看一下這個(gè)函數(shù)的偽代碼,則可以輕易的明白它的工作邏輯。請求zero frame并且進(jìn)程得到這個(gè)frame。這時(shí)執(zhí)行函數(shù)MiRemoveZeroPage或是MiRemoveAnyPage。第一個(gè)函數(shù)從zero頁的鏈 表中取一頁。如果未能成功,則通過第二個(gè)函數(shù)選擇任何一頁。這樣的話,該頁就由MiZeroPhysicalPage來清零。最終,在 MiAddValidPageToWorkingSet中,該清零的頁被添加到工作集中(恰好,這個(gè)事實(shí)證明在分配內(nèi)存時(shí)進(jìn)程不能取得對未處理頁的訪 問)。現(xiàn)在我們來研究一下更為復(fù)雜的情況——頁位于頁面文件中。

前面的偽代碼需要一個(gè)結(jié)構(gòu)體。在準(zhǔn)備從文件中讀取頁的時(shí)候,會填充PAGE_SUPPORT_BLOCK結(jié)構(gòu)體。之后,對所有即將參與到操作中來的PFN 進(jìn)行以下操作:設(shè)置read in progress標(biāo)志并在Misc域中寫入PAGE_SUPPORT_BLOCK的地址(函數(shù) MiInitializeReadInProgressPfn)。最后,函數(shù)返回magic number 0xc0033333,表示隨后要在IoPageRead調(diào)用中使用此結(jié)構(gòu)體(恰巧,IoPageRead被導(dǎo)出了,但是未公開的。從其偽碼中可以很容易 地得到其原型)。

typedef struct _PAGE_SUPPORT_BLOCK{     //  size: 0x98
         DISPATCHER_HEADER DispHeader;  // 0 FastMutex
         IO_STATUS_BLOCK IoStatusBlock; // 0x10
         LARGE_INTEGER AddrInPageFile;  // 0x18 (file offset)
         DWORD RefCounter;              // 0x20 (0|1) ???
         KTHREAD Thread;                // 0x24
         PFILE_OBJECT FileObject;       // 0x28
         DWORD AddrPte;            // 0x2c
         PPFN pPfn;                // 0x30
         MDL Mdl;                       // 0x34
         DWORD MdlFrameBuffer[0x10];    // 0x50
         LIST_ENTRY PageSupportList;    // 0x90 與MmInPageSupportList有關(guān)的鏈表
}PAGE_SUPPORT_BLOCK *PAGE_SUPPORT_BLOCK;

struct _MmInPageSupportList{
     LIST_ENTRY PageSupportList;
     DWORD Count;
     }MmInPageSupportList;

函數(shù)MiResolvePageFileFault本身非常簡單,除了填充相應(yīng)的結(jié)構(gòu)體并返回0xc0033333之外什么也不干。剩下的就是執(zhí)行MiDispatchFault。這很合乎情理,如果還記得復(fù)用代碼的原則的話。

還有一個(gè)不太復(fù)雜的函數(shù)MiResolveTransitionFault。對于狀態(tài)為transition的frame還需要再多說幾句。從這個(gè)狀態(tài)中frame可以很快地返回到進(jìn)程的Working Set中。

于是,剩下了最后一種情況——PROTO PTE。這種情況的處理函數(shù)也不太復(fù)雜,而且支撐其的基礎(chǔ)我們已經(jīng)講過了。實(shí)際上還有一個(gè)函數(shù)與這種情況有關(guān),這就是 MiCompleteProtoPteFault,從MiDispatchFault中調(diào)用。要想理解這些函數(shù)的工作就去看一下偽代碼。


07. section 對象
================

NT 中的section對象就是一塊內(nèi)存,這塊內(nèi)存由一個(gè)進(jìn)程獨(dú)有或幾個(gè)進(jìn)程共享。在Win32子系統(tǒng)中section就是文件映射(file mapping object)。我們來看一下section對象到底是什么。

section是NT下非常常用的對象,執(zhí)行系統(tǒng)使用section來將可執(zhí)行映象加載到內(nèi)存中并用其來管理cache。section同時(shí)也用在向進(jìn)程 地址空間中映射文件。這時(shí)訪問文件就像訪問內(nèi)存。section對象,就像其它的對象一樣,是由對象管理器創(chuàng)建的。高層次的信息告訴我們,對象的body 中包含著以下類型的信息:section的最大值,保護(hù)屬性,其它屬性。什么是section的最大可訪問值,這不說也知道。保護(hù)屬性是用于 section頁的屬性。其它section屬性有表示是文件section還是為空值(映射入頁面文件)的標(biāo)志,以及section是否是base的。 base的section以相同的虛擬地址映射入所有進(jìn)程的地址空間。

為了得到此對象結(jié)構(gòu)的真實(shí)信息,我反匯編了一些用于section的內(nèi)存管理器函數(shù)。下面的信息可是在別的地方見不到的。我們先來看結(jié)構(gòu)體。

系統(tǒng)中的每一個(gè)文件都是對象(NTDDK.H中有描述)FILE_OBJECT。在這個(gè)結(jié)構(gòu)體中有SectionObjectPointer。NTDDK.H中同樣有它的結(jié)構(gòu)。

//
:
PSECTION_OBJECT_POINTERS SectionObjectPointer;
:
//

typedef struct _SECTION_OBJECT_POINTERS {
    PVOID DataSectionObject;
    PVOID SharedCacheMap;
    PVOID ImageSectionObject;
} SECTION_OBJECT_POINTERS;

在結(jié)構(gòu)體中有兩個(gè)指針——DataSectionObject 和 ImageSectionObject。NTDDK.H把它們寫成了PVOID,因?yàn)樗鼈円玫氖俏垂_的結(jié)構(gòu)體。DataSectionObject用 在將文件作為數(shù)據(jù)打開的時(shí)候。ImageSectionObject——此時(shí)當(dāng)作映象。這些指針的類型全都一樣,且可以稱之為 PCONTROL_AREA。所有下面這些結(jié)構(gòu)體都是Windows 2K的,較之NT 4.0的有些變化。

typedef struct _CONTROL_AREA { // for NT 5.0, size = 0x38
                    PSEGMENT  pSegment;      //00
                    PCONTROL_AREA  Flink;         //04
                    PCONTROL_AREA  Blink;         //08
                    DWORD          SectionRef;    //0c
                    DWORD          PfnRef;           //10
                    DWORD          MappedViews;   //14
                    WORD      Subsections;   //18
                    WORD      FlushCount;    //1a
                    DWORD          UserRef;       //1c
                    DWORD          Flags;         //20
                    PFILE_OBJECT   FileObject;    //24
                    DWORD          Unknown;       //28
                    WORD      ModWriteCount; //2c
                    WORD      SystemViews;   //2e
                    DWORD PagedPoolUsage;         //30
                    DWORD NonPagedPoolUsage; //34
                    } CONTROL_AREA, *PCONTROL_AREA;

我們可以看到,CONTROL_AREA形成了一個(gè)鏈表,結(jié)構(gòu)體中包含著統(tǒng)計(jì)值和標(biāo)志。為了理解標(biāo)志所代表的信息,我給出它們的值(用于NT5.0

/******************** nt5.0 ******************/
#define BeingDeleted               0x1
#define BeingCreated               0x2
#define BeingPurged                0x4
#define NoModifiedWriting          0x8
#define FailAllIo                  0x10
#define Image                      0x20
#define Based                      0x40
#define File                       0x80
#define Networked                  0x100
#define NoCache                    0x200
#define PhysicalMemory             0x400
#define CopyOnWrite                0x800
#define Reserve                    0x1000
#define Commit                     0x2000
#define FloppyMedia                0x4000
#define WasPurged                  0x8000
#define UserReference              0x10000
#define GlobalMemory               0x20000
#define DeleteOnClose              0x40000
#define FilePointerNull            0x80000
#define DebugSymbolsLoaded         0x100000
#define SetMappedFileIoComplete    0x200000
#define CollidedFlush              0x400000
#define NoChange                   0x800000
#define HadUserReference           0x1000000
#define ImageMappedInSystemSpace   0x2000000

緊隨CONTROL_AREA之后的是Subsection的數(shù)目Subsections。每一個(gè)Subsection都描述了關(guān)于具體的文件映射 section的信息。例如,read-only, read-write, copy-on-write等等的section。NT5.0的SUBSECTION結(jié)構(gòu)體:

typedef struct _SUBSECTION { // size=0x20 nt5.0
               // +0x10 if GlobalOnlyPerSession
               PCONTROL_AREA   ControlArea; //38, 00
               DWORD           Flags;       //3c, 04
               DWORD           StartingSector;//40, 08
               DWORD           NumberOfSectors; //44, 0c
               PVOID           BasePte;       //48, 10 pointer to start pte
               DWORD           UnusedPtes;    //4c, 14
               DWORD           PtesInSubsect; //50, 18
               PSUBSECTION     pNext;         //54, 1c
               }SUBSECTION, *PSUBSECTION;

在subsection中有指向CONTROL_AREA的指針,標(biāo)志,指向base Proto PTE的指針,Proto PTE的數(shù)目。StartingSector是4K block的編號,文件中的section起始于此。在標(biāo)志中還有額外的信息:

#define SS_PROTECTION_MASK               0x1f0
#define SS_SECTOR_OFFSET_MASK            0xfff00000 // (low 12 bits)
#define SS_STARTING_SECTOR_HIGH_MASK     0x000ffc00  //  (nt5 only) (in pages)

//other 5 bit(s)

#define ReadOnly       1
#define ReadWrite      2
#define CopyOnWrite    4
#define GlobalMemory   8
#define LargePages 0x200

我們來看剩下的最后一個(gè)結(jié)構(gòu)體SEGMENT,它描述了所有的映射和用于映射section的Proto PTE。SEGMENT的內(nèi)存是從paged pool中分配的。我給出SEGMENT結(jié)構(gòu)體(NT 5.0)

typedef struct  _SEGMENT {
          PCONTROL_AREA ControlArea;    //00
          DWORD         BaseAddr;       //04
          DWORD         TotalPtes;      //08
          DWORD         NonExtendedPtes;//0c
          LARGE_INTEGER SizeOfsegemnt;  //10
          DWORD        ImageCommit;     //18
          DWORD        ImageInfo;       //1c
          DWORD        ImageBase;       //20
          DWORD        Commited;        //24
          PTE          PteTemplate;     //28 or 64 bits if pae enabled
          DWORD BasedAddr;         //2c
          DWORD BaseAddrPae;       //30 if PAE enabled
          DWORD ProtoPtes;         //34
          DWORD ProtoPtesPae;           //38 if PAE enabled
          }SEGMENT,*PSEGMENT;

正如我所料,結(jié)構(gòu)體包含著對CONTROL_AREA的引用,指向Proto PTE的pool的指針和所有section的信息。有個(gè)東西需要解釋一下。結(jié)構(gòu)體的樣子依賴于是否支持PAE。PAE就是Physical Address Extenion。從第5版開始,Windows NT包含了支持PAE的內(nèi)核Ntkrnlpa.exe。總的來講,支持PAE就意味著在NT里可以使用的虛擬地址不是4GB而是64GB。在使用PAE時(shí) 的地址轉(zhuǎn)換又多了一級——所有的虛地址空間被分為4部分。在打開PAE時(shí)PTE和PDE的大小不是4B而是8B,這我們可以從SEGMENT結(jié)構(gòu)體中看 出。現(xiàn)在還不需要進(jìn)一步詳細(xì)的講PAE,畢竟很少用到,所以我們就此打住。

描述section的所有結(jié)構(gòu)體都介紹過了,而section對象結(jié)構(gòu)體本身還沒有提到。從直觀上可以想到,它應(yīng)該會引用到SEGMENT或是 CONTROL_AREA,因?yàn)橛辛诉@兩個(gè)結(jié)構(gòu)體后就可以得到保存的所有信息。通過反匯編得到的section對象的body為以下形式:

typedef struct _SECTION_OBJECT { // size 0x28
               VAD_HEADER VadHeader; // 0
               PSEGMENT pSegment;         //0x14 Segment
               LARGE_INTEGER SectionSize; //0x18
               DWORD ControlFlags;        //0x20
               DWORD PgProtection;        //0x24
               } SECTION_OBJECT, *SECTION_OBJECT;

#define PageFile      0x10000
#define MappingFile   0x8000000
#define Based         0x40
#define Unknown       0x800000 // not sure, in fact it's AllocAttrib&0x400000

我們看到,所得的結(jié)構(gòu)體完全符合現(xiàn)有的高層信息的描述。唯一可能有疑問的就是VAD_HEADER。它描述了base section在地址空間中的位置。VAD_HEADER位于頂點(diǎn)為_MmSectionBasedRoot的VAD樹中。我們再次體會到,要理解操作系 統(tǒng)的工作原理,就要理解其內(nèi)部的結(jié)構(gòu)。為了有一個(gè)總體上的把握,下面給出了描述section的結(jié)構(gòu)體間互相聯(lián)系的一個(gè)圖。

SECTION_OBJECT->SEGMENT<->CONTROL_AREA->FILE_OBJECT->SECTION_OBJECT_POINTERS+
                               ^                                            |
                               +--------------------------------------------+


08.從內(nèi)存管理器角度看進(jìn)程的創(chuàng)建
====================================================

前面我們從Win32角度介紹過進(jìn)程的創(chuàng)建,也講過內(nèi)存管理器和對象管理器的工作原理,以及section對象結(jié)構(gòu)體。現(xiàn)在最有意思的當(dāng)然就是在進(jìn)程創(chuàng)建中將內(nèi)存管理器也考慮進(jìn)來。

進(jìn)程是用未公開的系統(tǒng)調(diào)用NtCreateProcess()創(chuàng)建的。下面給出其偽代碼:

/*****************************************************************/
/* -- Here it is, just wrapper -- */
NtCreateProcess(
      OUT Handle,
      IN ACCESS_MASK Access,
      IN POBJECT_ATTRIBUTES ObjectAttrib,
      IN HANDLE Parent,
      IN BOOLEAN InheritHandles,
      IN HANDLE SectionHandle,
      IN HANDLE DebugPort,
      IN HANDLE ExceptionPort
      )
{
if(Parent)
        {
     ret=PspCreateProcess(Handle,
                    Access,
                    ObjectAttrib,
                    Parent,
                    InheritHandles,
                    SectionHandle,
                    DebugPort,
                    ExceptionPort);
        }
else ret=STATUS_INVALID_PARAMETER;
return ret;
}

我們看到,NtCreateProcess是對另一個(gè)內(nèi)部函數(shù)PspCreateProcess的封裝。NtCreateProcess進(jìn)行的唯一工作就 是檢查Parent(父進(jìn)程句柄)。但是接下來我們看到,對于NT來說這并沒有什么意義,因?yàn)榭偟膩碚f,進(jìn)程的繼承性本身沒有特別的意義。現(xiàn)在我們來看 PspCreateProcess()。

PspCreateProcess(
      OUT PHANDLE Handle,
      IN ACCESS_MASK Access,
      IN POBJECT_ATTRIBUTES ObjectAttrib,
      IN HANDLE Parent,
      IN BOOLEAN InheritHandles,
      IN HANDLE SectionHandle,
      IN HANDLE DebugPort,
      IN HANDLE ExceptionPort
      );

我很快注意到,函數(shù)中的Parent參數(shù)可以接受值0,這就表明在NtCreateProcess中檢驗(yàn)此參數(shù)是為了限制用戶模式。函數(shù)的參數(shù)中有對 section、debug port和exception port、父進(jìn)程的引用。通過調(diào)用ObReferenceObjectByHandle,可以得到指向這些對象的指針。實(shí)際上父進(jìn)程句柄通常傳遞的是- 1,這表示是當(dāng)前進(jìn)程。如果Parent等于0,則進(jìn)程的affinity就不從父進(jìn)程處取得,而是從系統(tǒng)變量中取得。

if(Parent)
      { //Get pointer to father's body
        ObReferenceObjectByHandle(Parent,0x80,PsProcessType,PrevMode,&pFather,0);
        AffinityMask=pFather->Affinity; // on witch processors will be executed
        Prior=8;
      }
else {
      pFather=0;
      AffinityMask=KeActiveProcessors;
      Prior=8;
     }

優(yōu)先級總是為8。隨后,創(chuàng)建進(jìn)程對象。NT4.0下其大小為504字節(jié)。

// size of process body - 504 bytes
// creating process object... (type object PsProcessType)
ObCreateObject(PrevMode,PsProcessType,ObjectAttrib,PrevMode,0,504,&pProcess);
// clear body
memset(pProcess,0,504);

初始化某些域和Quota Block(見對象管理器的相關(guān)介紹)。

pProcess->CreateProcessReported=0;
pProcess->DebugPort=pDebugPort;
pProcess->ExceptPort=pExceptPort;

// Inherit Quota Block, if pFather==NULL, PspDefaultQuotaBlock
PspInheritQuota(pProcess,pFather);

if(pFather){
           pProcess->DefaultHardErrorMode=pFather->DefaultHardErrorMode;
           pProcess->InheritedFromUniqueProcessId=pFather->UniqueProcessId;
           }
else {
       pProcess->InheritedFromUniqueProcessId=0;
       pProcess->DefaultHardErrorMode=1;
     }

之后,調(diào)用MmCreateProcessAddressSpace,創(chuàng)建地址上下文。參數(shù)是函數(shù)得到的指向進(jìn)程的指針、工作集的大小和指向結(jié)果結(jié)構(gòu)體的指針。這個(gè)結(jié)構(gòu)體形式如下:

struct PROCESS_ADDRESS_SPACE_RESULT{
dword Dt; // dict. table phys. addr.
dword HypSpace; // hyp space page phys. addr.
dword WorkingSet; // working set page phys. addr.
}CASResult;

MmCreateProcessAddressSpace(PsMinimumWorkingSet,pProcess,&CASResult);

我們看到,函數(shù)向我們返回的是頁表的物理地址描述符(用于新地址空間的CR3的內(nèi)容),Hyper Space的頁地址和工作集的頁地址。在此之后是初始化進(jìn)程對象的某些域:

pProcess->MinimumWorkingSet=MinWorkingSet;
pProcess->MaximumWorkingSet=MaximumWorkingSet;

KeInitializeProcess(pProcess,Prior,AffinityMask,&CASResult,pProcess->
                                             DefaultHardErrorProcessing&0x4);

pProcess->ForegroundQuantum=PspForegroundQuantum;

如果有父進(jìn)程且設(shè)置了標(biāo)志參數(shù),則會繼承父進(jìn)程的句柄表:

if(pFather) // if there is father and inherithandle, so, inherit handle db
      {
       pFather2=0;
       if(bInheritHandle)pFather2=pFather;
       ObInitProcess(pFather2,pProcess); // see info about ObjectManager
      }

下面的東西比較有意思,證明了NT執(zhí)行系統(tǒng)的靈活性,從表面上是看不出來的。如果在參數(shù)中有指定的section,則使用這個(gè)section來初始化進(jìn)程的地址空間,否則其工作就會像*UNIX中的fork()。

if(pSection)
     {
       MmInitializeProcessAddressSpace(pProcess,0,pSection);
       ObDereferenceObject(pSection);
       res=ObInitProcess2(pProcess); //work with unknown byte +0x22 in process
       if(res>=0)PspMapSystemDll(pProcess,0);
       Flag=1; //Created addr space
     }
else { // if there is futher, but no section, so, do operation like fork()
      if(pFatherProcess){
                   if(PsInitialSystemProcess==pFather){
                             MmRes=MmInitializeProcessAddressSpace(pProcess,0,0);
                                                   }
                   else {
                      pProcess->SectionBaseAddress=pFather->SectionBaseAddress;
                      MmRes=MmInitializeProcessAddressSpace(pProcess,pFather,0);
                      Flag=1; //created addr space
                         }
                  }
     }

接下來是使用PsActiveProcessHead將進(jìn)程插入Active Process鏈表,創(chuàng)建Peb和做其它輔助性的工作。我們不再贅述。最后,當(dāng)所有的工作都做完后,進(jìn)行安全子系統(tǒng)方面的工作。我們過去曾研究過安全子系 統(tǒng)(見對象管理器部分),所以這里只簡單的給出其偽代碼。只是我注意到,如果父進(jìn)程是system(句柄值等于 PspInitialSystemProcessHandle),則不對其安全性進(jìn)行檢驗(yàn)。

// finally, security operations
if(pFather&&PspInitialSystemProcessHandle!=Father)
           {
            ObGetObjectSecurity(pProcess,&SecurityDescriptor,&MemoryAllocated);
            pToken=PsReferencePrimaryToken(pProcess);
            AccessRes=SeAccessCheck(SecurityDescriptor,&SecurityContext,
                             0,0x2000000,
                             0,0,&PsProcessToken->GenericMapping,
                             PrevMode,pProcess->GrantedAccess,
                             &AccessStatus);
            ObDereferenceObject(pToken);
            ObReleaseObjectSecuryty(SecurityDescriptor,MemoryAllocated);
            if(!AccessRes)pProcess->GrantedAccess=0;
            pProcess->GrantedAccess|=0x6fb;
            }
   else{
        pProcess->GrantedAccess=0x1f0fff;
       }

   if(SeDetailedAuditing)SeAuditProcessCreation(pProcess,pFather);

最有意思的是函數(shù)KeInitializeProcess和MmCreateProcessAddressSpace。前一個(gè)函數(shù)除了初始化進(jìn)程對象的其它成員之外,還要初始化TSS中的IO位圖的偏移。

pProcess->IopmOffset=0x20ad; // IOMAP BASE!!!
                            // You can patch kernel here and
                            // got i/o port control ;)

偏移的選取是這樣的,它指向I/O位圖,這樣就能阻止進(jìn)程直接使用I/O端口。

在函數(shù)MmCreateProcessAddressSpace中進(jìn)行的是進(jìn)程地址空間的創(chuàng)建。我就不給出所有的偽代碼了,只簡要的寫寫主要的操作。它為 Hyper Space, Working Set和Page Directory選擇頁。反匯編后的代碼證實(shí)了,它們是從zero frame鏈表中選出或是由MiZeroPhysicalPage函數(shù)來清零的。之后初始化新創(chuàng)建的Page Directory。

pProcess->WorkingSetPage=Frame3; // WorkingSetPage
(MmPfnDatabase+0x18*Frame)->Pte=0xc0300000;
ValidPde_U=ValidPdePde&0xeff^Frame2; // HyperSpace

/**************IMPORTANT!!!!!!!!!!!!!!************************/
/* 重要! 這里初始化PD                                         */
/*************************************************************/

Va=MiMapPageInHyperSpace(Frame,&LastIrql);
// no we got Va of our new Page Directory
// Fill some fields
*(Va+0xc04)=ValidPde_U; // HyperSpace
ValidPde_U=ValidPde_U&0xfff^PhysAddr; // DT
*(Va+0xc00)=ValidPde_U; // self-pde

// copy from current process, kernel address mapping
memcpy(
     (MmVirtualBias+0x80000000)>>0x14+Va, // it's like that we found,
                                          // what MmVirtualBias is it ;)
     (MmVirtualBias+0x80000000)>>0x14+0xc0300000,
     0x80 // 32 pdes -> 4Mb*32=128Mb
     );

memcpy( // copy pdes, corresponding to NonPagedArea
     MmNonPagedSystemStart>>0x14+Va,
     MmNonPagedSystemStart>>0x14+0xc0300000,
     (0xc0300ffc-MmNonPagedSystemStart>>0x14+0xc0300000)&0xfffffffc+4);

memcpy(Va+0xc0c, // cache, forgot about it now, it's another story ;)
       0xc0300c0c,
       (MmSystemCacheEnd>>0x14)-0xc0c+4
       );

也就是將PDE拷貝到內(nèi)核地址空間中去(其對所有的進(jìn)程不變,Hyper Space除外),而且是拷貝到不可換出的區(qū)域。同時(shí)這個(gè)空間是屬于系統(tǒng)cache的。


09.上下文切換
==========================

知道了ETHREAD、EPROCESS結(jié)構(gòu)體和內(nèi)存管理器的工作原理,就不難猜到上下文切換時(shí)會發(fā)生什么。Windows NT的設(shè)計(jì)者使用線程,不關(guān)心共享的是誰的地址空間,也就是說有兩種可能:線程屬于當(dāng)前進(jìn)程——必需要切換到另一個(gè)線程(更新堆棧并更換GDT描述符), 而線程屬于另一個(gè)進(jìn)程,必需切換到那個(gè)進(jìn)程(重新加載CR3)。對此,為了證實(shí)我的推測,我反匯編了KeAttachProcess函數(shù)。這個(gè)函數(shù)是未公 開的,但所有已知的函數(shù)都用其來切換到另一進(jìn)程的地址空間。通過KeDetachProcess可以返回到當(dāng)前進(jìn)程。KeAttachProcess使用 下述內(nèi)部函數(shù):

KiAttachProcess - KeAttachProcess僅僅是對這個(gè)函數(shù)的封裝
KiSwapProcess   - 更換地址空間。(本質(zhì)上就是重新加載CR3)
SwapContext     - 更換上下文。一般不管地址空間的切換,只調(diào)整線程上下文。
KiSwapThred     - 切換到鏈表中的下一個(gè)線程(SwapContext)調(diào)用

下面給出這些內(nèi)部函數(shù)的偽代碼。
-----------------------------------------------------------------------------
/************************ KeAttachProcess ***************************/
// just wrapper
//
KeAttachProcess(EPROCESS *Process)
{
KiAttachProcess(Process,KeRaiseIrqlToSynchLevel);
}
/************************ KiAttachProcess ***************************/

KiAttachProcess(EPROCESS *Process,Irql){

//CurThread=fs:124h
//CurProcess=CurThread->ApcState.Process;

if(CurProcess!=Process){
     if(CurProcess->ApcStateIndex || KPCR->DpcRoutineActive)KeBugCheckEx...
     }

//if we already in process's context
if(CurProcess==Process){KiUnlockDispatcherDatabase(Irql);return;}

Process->StackCount++;
KiMoveApcState(&CurThread->ApcState,&CurThread->SavedApcState);

// init lists
CurThread->ApcState.ApcListHead[0].Blink=&CurThread->ApcState.ApcListHead[0];
CurThread->ApcState.ApcListHead[0].Flink=&CurThread->ApcState.ApcListHead[0];
CurThread->ApcState.ApcListHead[1].Blink=&CurThread->ApcState.ApcListHead[1];
CurThread->ApcState.ApcListHead[1].Flink=&CurThread->ApcState.ApcListHead[1];;

//fill curtheads's fields
CurThread->ApcState.Process=Process;

CurThread->ApcState.KernelApcInProgress=0;
CurThread->ApcState.KernelApcPending=0;
CurThread->ApcState.UserApcPending=0;

CurThread->ApcState.ApcStatePointer.SavedApcState=&CurThread->SavedApcState;
CurThread->ApcState.ApcStatePointer.ApcState=&CurThread->ApcState;

CurThread->ApcStateIndex=1;

//if process ready, just swap it...
if(!Process->State)//state==0, ready
     {
     KiSwapProcess(Process,CurThread->SavedApcState.Process);
     KiUnlockDispatcherDatabase(Irql);
     return;
     }

CurThread->State=1; //ready?
CurThread->ProcessReadyQueue=1;

//put Process in Thread's waitlist
CurThread->WaitListEntry.Flink=&Process->ReadyListHead.Flink;
CurThread->WaitListEntry.Blink=Process->ReadyListHead.Blink;

Process->ReadyListHead.Flink->Flink=&CurThread->WaitListEntry.Flink;
Process->ReadyListHead.Blink=&CurThread->WaitListEntry.Flink;

// else, move process to swap list and wait
if(Process->State==1){//idle?
     Process->State=2; //trans
     Process->SwapListEntry.Flink=&KiProcessInSwapListHead.Flink;
     Process->SwapListEntry.Blink=KiProcessInSwapListHead.Blink;
        KiProcessInSwapListHead.Blink=&Process->SwapListEntry.Flink;
     KiSwapEvent.Header.SignalState=1;
     if(KiSwapEvent.Header.WaitListHead.Flink!=&KiSwapEvent.Header.WaitListHead.
Flink)
                    KiWaitTest(&KiSwapEvent,0xa); //fastcall
     }

CurThread->WaitIrql=Irql;
KiSwapThread();
return;
}

從這個(gè)函數(shù)可以得到以下結(jié)論。進(jìn)程可以處于以下狀態(tài)——0(準(zhǔn)備),1(Idle),2(Trans——切換)。這證實(shí)了高層次的信息。KiAttachProcess使用了另外兩個(gè)函數(shù)KiSwapProcess和KiSwapThread。

/************************* KiSwapProcess ****************************/

KiSwapProcess(EPROCESS* NewProcess, EPROCESS* OldProcess)
{
// just reload cr3 and small work with TSS

     // TSS=KPCR->TSS;
     // xor eax,eax
     // mov gs,ax
TSS->CR3=NewProcess->DirectoryTableBase;//0x1c
     // mov cr3,NewProcess->DirectoryTableBase
TSS->IopmOffset=NewProcess->IopmOffset;//0x66
if(WORD(NewProcess->LdtDescriptor)==0){lldt 0x00; return;//}
     //GDT=KPCR->GDT;
(QWORD)GDT->0x48=(QWORD)NewProcess->LdtDescriptor;
(QWORD)GDT->0x108=(QWORD)NewProcess->Int21Descriptor;
lldt 0x48;
return;
}

切換進(jìn)程上下文。正如我所料,這個(gè)函數(shù)只是重新加載CR3寄存器,再加上一點(diǎn)相關(guān)的操作。例如,用IopmOffset域的值建立TSS中的I/O位圖的偏移。還必需將選擇子的值加載到ldt(只用于VDM session)。

/************************* SwapContext ******************************/

SwapContext(NextThread,CurThread,WaitIrql)
{

NextThread.State=ThreadStateRunning; //2
KPCR.DebugActive=NextThread.DebugActive;

cli();

//Save Stack
CurThread.KernelStack=esp;

//Set stack
KPCR.StackLimit=NextThread.StackLimit;
KPCR.StackBase=NextThread.InitialStack;

tmp=NextThread.InitialStack-0x70;
newcr0=cr0&0xfffffff1|NextThread.NpxState|*(tmp+0x6c);
if(newcr0!=cr0)reloadcr0();
if(!*(tmp-0x1c)&0x20000)tmp-=0x10;
TSS=KPCB.TSS;
TSS->ESP0=tmp;

//set pTeb
KPCB.Self=NextThread.pTeb;
esp=NextThread.KernelStack;
sti();

//correct GDT
GDT=KPCB.GDT;
WORD(GDT->0x3a)=NextThread.pTeb;
BYTE(GDT->0x3c)=NextThread.pTeb>>16;
BYTE(GDT->0x3f)=NextThread.pTeb>>24;

//if we must swap processes, do it (like KiSwapProcess)

if(CurThread.ApcState.Process!=NextThread.ApcState.Process)
     {
     //******** like KiSwapProcess
     }

NextThread->ContextSwitches++;

KPCB->KeContextSwitches++;

if(!NextThread->ApcState.KernelApcPending)return 0;

//popf;
// jnz HalRequestSoftwareInterrupt// return 0

return 1;
}

切換堆棧,修正GDT,以使FS寄存器指向TEB。如果線程屬于當(dāng)前進(jìn)程,則不進(jìn)行上下文切換。否則,進(jìn)行的操作和KiSwapProcess中的大致差不多。


為了一致,我給出KeDetachProcess的原型。

KeDetachProcess(EPROCESS *Process,Irql);

我們看到——這些函數(shù)的偽碼實(shí)際上完全描述出了操作系統(tǒng)的上下文切換。總的說來,代碼分析表明,理解OS的主要途徑就是要知道它的內(nèi)部結(jié)構(gòu)。


0a.某些未公開的內(nèi)存管理器函數(shù)
==========================================================
SP3的ntoskrnl.exe的內(nèi)存管理器導(dǎo)出了以下符號:

        467  1D0 00051080 MmAdjustWorkingSetSize
        468  1D1 0001EDFA+MmAllocateContiguousMemory
        469  1D2 00051A14+MmAllocateNonCachedMemory
        470  1D3 0001EAE8+MmBuildMdlForNonPagedPool
        471  1D4 000206BC MmCanFileBeTruncated
        472  1D5 0001EF5A+MmCreateMdl
        473  1D6 0002095C MmCreateSection
        474  1D7 00021224 MmDbgTranslatePhysicalAddress
        475  1D8 000224AC MmDisableModifiedWriteOfSection
        476  1D9 000230C8 MmFlushImageSection
        477  1DA 0001FA9C MmForceSectionClosed
        478  1DB 0001EEA0+MmFreeContiguousMemory
        479  1DC 00051AFE+MmFreeNonCachedMemory
        480  1DD 0001EEAC+MmGetPhysicalAddress
        481  1DE 00024028 MmGrowKernelStack
        482  1DF 0004E144 MmHighestUserAddress
        483  1E0 0002645A+MmIsAddressValid
        484  1E1 00026CD8+MmIsNonPagedSystemAddressValid
        485  1E2 0001F5D8 MmIsRecursiveIoFault
        486  1E3 00026D56+MmIsThisAnNtAsSystem
        487  1E4 000766C8+MmLockPagableDataSection
        488  1E5 000766C8 MmLockPagableImageSection
        489  1E6 0001F160+MmLockPagableSectionByHandle
        490  1E7 0001ED18+MmMapIoSpace
        491  1E8 0001EB74+MmMapLockedPages
        492  1E9 0001F5F6 MmMapMemoryDumpMdl
        493  1EA 00076A14 MmMapVideoDisplay
        494  1EB 0005206C MmMapViewInSystemSpace
        495  1EC 00079B0E MmMapViewOfSection
        496  1ED 0007A7EE+MmPageEntireDriver
        497  1EE 0001E758+MmProbeAndLockPages
        498  1EF 00026D50+MmQuerySystemSize
        499  1F0 00052A8A+MmResetDriverPaging
        500  1F1 0004E0A4 MmSectionObjectType
        501  1F2 00079D28 MmSecureVirtualMemory
        502  1F3 0001EFCE MmSetAddressRangeModified
        503  1F4 0007684E MmSetBankedSection
        504  1F5 0001EF2C+MmSizeOfMdl
        505  1F6 0004E0A0 MmSystemRangeStart
        506  1F7 0001F516+MmUnlockPagableImageSection
        507  1F8 0001EA16+MmUnlockPages
        508  1F9 0007669A+MmUnmapIoSpace
        509  1FA 0001ECA8+MmUnmapLockedPages
        510  1FB 00076A2E MmUnmapVideoDisplay
        511  1FC 00052284 MmUnmapViewInSystemSpace
        512  1FD 0007AFE4 MmUnmapViewOfSection
        513  1FE 0007A00A MmUnsecureVirtualMemory
        514  1FF 0004DDCC MmUserProbeAddress

這里的符號'+'表示函數(shù)在DDK中有記載。我這里給出某些未公開函數(shù)的原型。

// 調(diào)整working set的大小.

NTOSKRNL NTSTATUS MmAdjustWorkingSetSize(
          DWORD MinimumWorkingSet OPTIONAL, // if both == -1
          DWORD MaximumWorkingSet OPTIONAL, // empty working set
          PVM Vm OPTIONAL);

//can file be truncated???
NTOSKRNL BOOLEAN MmCanFileBeTruncated(
          PSECTION_OBJECT_POINTERS SectionPointer, // see FILE_OBJECT
             PLARGE_INTEGER NewFileSize
          );

// create section. NtCreateSection call this function...

NTOSKRNL NTSTATUS MmCreateSection (
    OUT PVOID               *SectionObject,
    IN ACCESS_MASK          DesiredAccess,
    IN POBJECT_ATTRIBUTES   ObjectAttributes OPTIONAL,
    IN PLARGE_INTEGER       MaximumSize,
    IN ULONG                SectionPageProtection,//PAGE_XXXX
    IN ULONG                AllocationAttributes,//SEC_XXX
    IN HANDLE               FileHandle OPTIONAL,
    IN PFILE_OBJECT         File OPTIONAL
);

typedef enum _MMFLUSH_TYPE {
    MmFlushForDelete,
    MmFlushForWrite
} MMFLUSH_TYPE;

NTOSKRNL BOOLEAN MmFlushImageSection (
    IN PSECTION_OBJECT_POINTERS     SectionObjectPointer,
    IN MMFLUSH_TYPE                 FlushType
);

NTOSKRNL DWORD MmHighestUserAddress; // 一般為0x7ffeffff


NTOSKRNL BOOLEAN MmIsRecursiveIoFault();
//其代碼
#define _MmIsRecursiveIoFault() ( \
    (PsGetCurrentThread()->DisablePageFaultClustering) | \
    (PsGetCurrentThread()->ForwardClusterOnly) \
)

NTOSKRNL POBJECT_TYPE MmSectionObjectType; //標(biāo)準(zhǔn)的Section對象

NTOSKRNL DWORD MmSystemRangeStart; //一般為0x80000000
NTOSKRNL DWORD MmUserProbeAddress; //一般為0x7fff0000

NTOSKRNL PVOID MmMapVideoDisplay( // для i386 враппер в MmMapIoSpace
       IN PHYSICAL_ADDRESS PhysicalAddress,
       IN ULONG NumberOfBytes,
       IN BOOLEAN CacheEnable
       );

NTOSKRNL VOID MmUnmapVideoDisplay ( // для i386 враппер в MmUnmapIoSpace
       IN PVOID BaseAddress,
       IN ULONG NumberOfBytes
       );

// 將frame的范圍標(biāo)記為更改并進(jìn)行相應(yīng)的操作
NTOSKRNL VOID MmSetAddressRangeModified(
     PVOID StartAddress,
     DWORD Length
     );

// 在NtMapViewOfSection中調(diào)用
typedef enum _SECTION_INHERIT {
ViewShare=1;
ViewUnmap=2;
}SECTION_INHERIT;

NTOSKRNL NTSTATUS MmMapViewOfSection(
     PVOID pSection,
     PEPROCESS pProcess,
     OUT PVOID *BaseAddress,
     DWORD ZeroBits,
     DWORD CommitSize,
     OUT PLARGE_INTEGER SectionOffset OPTIONAL,
     OUT PDWORD ViewSize,
     SECTION_INHERIT InheritDisposition,
     DWORD AllocationType,
     DWORD ProtectionType
     );

NTOSKRNL NTSTATUS MmUnmapViewOfSection(
     PEPROCESS Process,
     PVOID Address
     );

PVOID MmLockPagableImageSection(
     PVOID AddressWithinImageSection // same entry as MmLockPagableDataSection
     );

// 減少StackLimit(堆棧增長)
NTSTATUS MmGrowKernelStack(
     PVOID CurESP //棧頂?shù)牡刂?br>      );


                                   I talk to the wind
                              My words are all carried away
                                   I talk to the wind
                                  The wind does not hear
                                   The wind cannot hear.

                                            King Crimson'69 -I Talk to the Wind


0b.結(jié)語
=============

就到這里吧。如果綜合的來看所有這些描述,對內(nèi)存管理器多少可以得到一些概念。遺憾的是,這些東西還遠(yuǎn)不能稱之為完整。內(nèi)存管理器,大概是最復(fù)雜和最重要 的內(nèi)核組件,對其要進(jìn)行完整的描述,我還得深挖不止十個(gè)八個(gè)的函數(shù)。但是主要的基本的東西我這里都寫到了。對于進(jìn)一步反匯編內(nèi)核來說,這些應(yīng)該是很有幫助 的吧,誰知道呢... ;)

                         Best Regards, Peter Kosy aka Gloomy.
                                  Melancholy Coding '2001.

                            mailto:gl00my@mail.ru

P.S. 我知道我的“大作”不可避免的會有錯(cuò)誤。我將非常高興的聽取批評和建議。


附錄


0c.某些未公開的系統(tǒng)調(diào)用
==================================================

這里我描述了一些有用的Zw/Nt函數(shù),這些函數(shù)可以在USER模式下或是驅(qū)動程序中調(diào)用(Zw類的)。幾乎所有這些函數(shù)都來自于
Коберниченко的“Недокументированные возмождности Windows NT”一書。再加上Working   Set結(jié)構(gòu)體的值,就可以描述用于NtQueryVirtualMemory的MEMORY_WORKING_SET_INFORMATION。

NTSYSAPI NTSTATUS NTAPI NtAllocateVirtualMemory(
               HANDLE Process,
                        OUT PVOID *BaseAddr,
                        DWORD ZeroBits,
                        OUT PDWORD RegionSize,
                        DWORD AllocationType,// MEM_RESERVE|MEM_COMMIT|MEM_TOP_DOWN
                        DWORD Protect); // PAGE_XXXX...

NTSYSAPI NTSTATUS NTAPI NtFreeVirtualMemory(
               HANDLE Process,
                        OUT PVOID* BaseAddr,
                        OUT PULONG RegionSize,
                        DWORD FreeType //MEM_DECOMMIT|MEM_RELEASE
               );

NTSYSAPI NTSTATUS NTAPI NtCreateSection(
               OUT PHANDLE Section,
               ACCESS_MASK DesirdAccess, //SECTION_MAP_XXX...
               POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
               PLARGE_IBTEGER MaximumSize OPTIONAL,
               DWORD SectionPageProtection, //PAGE_...
               DWORD AllocationAttributes, //SEC_XXX
               HANDLE FileHandle OPTIONAL // NULL - pagefile
               );

typedef enum _SECTION_INHERIT {
ViewShare=1;
ViewUnmap=2;
}SECTION_INHERIT;

NTSYSAPI NTSTATUS NTAPI NtMapViewOfSection(
     HANDLE Section,
     HANDLE Process,
     OUT PVOID *BaseAddress,
     DWORD ZeroBits,
     DWORD CommitSize,
     OUT PLARGE_INTEGER SectionOffset OPTIONAL,
     OUT PDWORD ViewSize,
     SECTION_INHERIT InheritDisposition,
     DWORD AllocationType, //MEM_TOP_DOWN,MEM_LARGE_BAGE,MEM_AUTO_ALIGN=0x40000000
     DWORD ProtectionType // PAGE_...
     );

#define UNLOCK_TYPE_NON_PRIVILEGED 0x00000001L
#define UNLOCK_TYPE_PRIVILEGED          0x00000002L

NTSYSAPI NTSTATUS NTAPI NtLockVirtualMemory(
     IN HANDLE ProcessHandle,
     IN OUT PVOID *RegionAddress,
     IN OUT PULONG RegionSize,
     IN ULONG UnlockTypeRequired
     );

NTSYSAPI NTSTATUS NTAPI NtUnlockVirtualMemory(
     IN HANDLE ProcessHandle,
     IN OUT PVOID *RegionAddress,
     IN OUT PULONG RegionSize,
     IN ULONG UnlockTypeRequiested
     );

NTSYSAPI NTSTATUS NTAPI NtReadVirtualMemory(
     IN HANDLE ProcessHandle,
     IN PVOID StartAddress,
     OUT PVOID Buffer,
     IN ULONG BytesToRead,
     OUT PULONG BytesReaded OPTIONAL
     );

NTSYSAPI NTSTATUS NTAPI NtWriteVirtualMemory(
     IN HANDLE ProcessHandle,
     IN PVOID StartAddress,
     IN PVOID Buffer,
     IN ULONG BytesToWrite,
     OUT PULONG BytesWritten OPTIONAL
     );

NTSYSAPI NTSTATUS NTAPI NtProtectVirtualMemory(
     IN HANDLE ProcessHandle,
     IN OUT PVOID *RegionAddress,
     IN OUT PULONG RegionSize,
     IN ULONG DesiredProtection,
     OUT PULONG OldProtection
     );

NTSYSAPI NTSTATUS NTAPI NtFlushVirtualMemory(
     IN HANDLE ProcessHandle,
     IN PVOID* StartAddress,
     IN PULONG BytesToFlush,
     OUT PIO_STATUS_BLOCK StatusBlock
     );

typedef enum _MEMORYINFOCLASS {
     MemoryBasicInformation,
     MemoryWorkingSetInformation,

     // 還有class 2 - 這是VAD中的信息, 我目前還不完全了解VAD結(jié)構(gòu)體,也就不能寫出相應(yīng)的INFO結(jié)構(gòu)。

} MEMORYINFOCLASS;

typedef struct _MEMORY_BASIC_INFORMATION {
    PVOID BaseAddress;
    PVOID AllocationBase;
    ULONG AllocationProtect;
    ULONG RegionSize;
    ULONG State;
    ULONG Protect;
    ULONG Type;
} MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION;

#define WSFRAMEINFO_SHARED_FRAME 0x100
#define WSFRAMEINFO_INTERNAL_USE 0x4
#define WSFRAMEINFO_UNKNOWN 0x3

typedef struct _MEMORY_WORKING_SET_INFORMATION {
     ULONG SizeOfWorkingSet;
     DWORD WsEntries[ANYSIZE_ARRAY]; // is Page VA | WSFRAMEINFO...


} MEMORY_ENTRY_INFORMATION, *PMEMORY_ENTRY_INFORMATION;

NTSYSAPI NTSTATUS NTAPI NtQueryVirtualMemory(
     IN HANDLE ProcessHandle,
     IN PVOID RegionAddress,
     IN MEMORYINFOCLASS MemoryInformationClass,
     IN PVOID VirtualMemoryInfo,
     IN ULONG Length,
     OUT PULONG ActualLength OPTIONAL
     );

0d.附注及代碼分析草稿
==========================================

**** К MmCreateProcessAddressSpace ... ****
=============================================

__fastcall MiTotalCommitLimit(PVOID pProcess, DWORD NumOfPages); // edx:ecx
有statistic

dd MmTotalCommitLimit
dd MmTotalCommitedPages

如果NumOfPages+MmTotalCommitedPages不超過Limit - 一切OK,并只是簡單的修正statistic.

否則開始線程間的協(xié)作。

選擇time out值(如果請求>=10頁,則為20秒),否則為-1秒。接著填充某個(gè)結(jié)構(gòu)體,大概是這個(gè)樣子:

typedef struct _REQUEST_FOR_COMMITED_MEMORY{
     LIST_ENTRY ListEntry;
     DWORD PagesToCommit;
     DWORD Result;
     KSEMAPHORE Semaphore;
           }_REQUEST_FOR_COMMITED_MEMORY;

這個(gè)結(jié)構(gòu)體(或鏈表的元素)被插入到全局結(jié)構(gòu)體中的全局鏈表ListOfRequest:

       [Pre List Item]<->[Our List Item]<->[ListOfRequest]

typedef struct _COMMIT_MEMORY_REQUEST_LIST{
     KSEMAPHORE CommitMemorySemaphore;
     LIST_ENTRY ListOfRequest;
          }COMMIT_MEMORY_REQUEST_LIST;

之后對CommitMemorySemaphore使用KeReleaseSemaphore并等待REQUEST_FOR_COMMITED_MEMORY中帶有time out的信號量。

如果未超出time out并因此Result不為0,則再校驗(yàn)一次Limit并輸出OK(如果limit有問題——則所有都重新開始)。如果結(jié)果為0,MiCouseOverCommitPopup。如果發(fā)生了time out,分析如下:

如果ListOfReques.Flink==&ListOfReques.Flink,也就是說所有的請求都在隊(duì)列的尾部,則再一次等待信號量——并且已經(jīng)沒有time out了,因?yàn)椴皇俏覀兊膯栴};)

如果ListOfReques.Flink==&RequestForCommitedMemory.ListEntry,就是說隊(duì)列中的下一個(gè)是我們的請求(???)。則從隊(duì)列中收回請求,因?yàn)?br> 是從我們這里來的。

現(xiàn)在來看我們想看的幾個(gè)頁。如果>=10則MiCouseOverCommitPopup,否則MiChargeCommitmentCantExpand,之后輸出。

所有的操作都需要cli sti,同時(shí)使用FastMutex(進(jìn)程的10ch偏移),在進(jìn)程創(chuàng)建時(shí)調(diào)用這個(gè)函數(shù)不會進(jìn)行此操作。

現(xiàn)在,MiCouseOverCommitPopup(PagesNum,CommitTotalLimitDelta);又做些什么呢——如果我們想要 頁數(shù)大于128——則ExRaiseStatus(STATUS_COMMITMENT_LIMIT); 如果小于則IoRaiseInformationalHardError(STATUS_COMMITMENT_LIMIT,0,0);(這些函數(shù)都是公 開的)。如果成功調(diào)用最后一個(gè)函數(shù)——則累加statistic:

MiOverCommitCallCount++;
MmTotalCommitLimit+=CommitTotalLimitDelta;
MmExtendedCommit+=CommitTotalLimitDelta;
MmTotalCommittedPages+=PagesNum;
且不修正 MmPeakCommintment;

如果不成功但MiOverCommitCallCount==0,所有都等于statistic,否則ExRaiseStatus(STATUS_COMMITMENT_LIMIT);

輔助函數(shù):

DWORD NTOSKRNL RtlRandom(PDWORD Seed);

不奇怪,這個(gè)函數(shù)沒有公開。該函數(shù)使用一個(gè)128個(gè)DWORD的表。在操作之后被此表和Seed被修正。可以看到,這給出了最大周期。

如果有兩個(gè)event
MmAvailablePagesEventHigh 和
MmAvailablePagesEventHigh.

MiSectionInitialization:
MmDereferenceSegmentHeader: это структура описанная выша с добавленным
spinlock сверху.
創(chuàng)建線程MiDereferenceSegmentThread

PsChargePoolQuota(PVOID Process,DWORD Type(NP/P),DWORD Charge);

[TO DO] -->> MmInfoCounters!!!! 使用相應(yīng)的NtQueryInfo...可以獲得非常多有用的信息,ПОСМОТРЕТЬ!!!
posted on 2006-03-09 23:46 笑笑生 閱讀(1151) 評論(0)  編輯 收藏 引用

只有注冊用戶登錄后才能發(fā)表評論。
網(wǎng)站導(dǎo)航: 博客園   IT新聞   BlogJava   博問   Chat2DB   管理


 
Copyright © 笑笑生 Powered by: 博客園 模板提供:滬江博客
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            中文在线资源观看网站视频免费不卡| 免费成人毛片| 国产欧美一区二区精品性色| 亚洲天堂成人在线观看| 在线一区二区三区四区| 国产精品久久久久国产精品日日 | 久久精品亚洲精品| 亚洲福利av| 亚洲人成人99网站| 欧美日韩视频在线观看一区二区三区| 亚洲无吗在线| 久久国产99| 亚洲久久在线| 亚洲欧美日韩在线综合| 在线日韩欧美视频| 日韩五码在线| 韩国三级电影久久久久久| 欧美激情精品久久久六区热门| 欧美日韩国产小视频| 欧美在线免费观看| 欧美高清视频一区二区三区在线观看 | 欧美精品一区二区精品网| 亚洲综合99| 久久视频这里只有精品| 母乳一区在线观看| 欧美一级黄色录像| 欧美国产精品一区| 久久精品亚洲精品| 欧美黄色小视频| 久久久免费观看视频| 欧美精品综合| 免费亚洲电影在线| 国产精品嫩草久久久久| 亚洲第一免费播放区| 国产真实久久| 亚洲视频视频在线| 日韩手机在线导航| 久久亚洲国产精品一区二区 | 国内成人精品视频| 一区二区三区久久| 亚洲精品欧美一区二区三区| 午夜精品在线视频| 亚洲一区二区在线播放| 麻豆freexxxx性91精品| 久久九九99| 国产美女精品免费电影| 亚洲免费久久| 亚洲三级电影全部在线观看高清 | 亚洲欧洲日韩综合二区| 亚洲大片免费看| 久久国产精品99精品国产| 亚洲欧美日本精品| 欧美视频精品一区| 日韩视频专区| 亚洲午夜一区二区| 欧美日韩国内| 亚洲精品在线一区二区| 亚洲日本中文字幕区| 免费成人网www| 欧美成人免费全部| 亚洲成色www8888| 久久米奇亚洲| 美国十次成人| 亚洲高清毛片| 欧美h视频在线| 亚洲韩国青草视频| 一片黄亚洲嫩模| 欧美三级网页| 亚洲欧美日韩国产精品| 欧美亚洲一区二区三区| 国产日韩一级二级三级| 欧美一区=区| 麻豆精品在线视频| 亚洲国产精品va在线看黑人动漫| 久久久欧美精品sm网站| 亚洲高清123| 一区二区三区视频在线看| 欧美日在线观看| 亚洲在线成人| 久久久爽爽爽美女图片| 亚洲动漫精品| 欧美日韩久久久久久| 一区二区三欧美| 久久久久久久999精品视频| 经典三级久久| 欧美电影免费网站| 亚洲视频综合| 久久这里只有| 一区二区三区日韩在线观看 | 亚洲一区二区三区中文字幕| 午夜宅男久久久| 尤妮丝一区二区裸体视频| 欧美成人一区二区| 亚洲精品视频在线| 欧美自拍丝袜亚洲| 91久久一区二区| 国产精品裸体一区二区三区| 久久久青草婷婷精品综合日韩| 亚洲国产99| 欧美亚洲日本国产| 亚洲激情成人网| 国产精品露脸自拍| 免费观看成人鲁鲁鲁鲁鲁视频| 99ri日韩精品视频| 美女视频黄a大片欧美| 一区二区三区日韩欧美| 狠狠色香婷婷久久亚洲精品| 欧美日本在线观看| 久久久999精品| 亚洲无线一线二线三线区别av| 久久影院午夜片一区| 亚洲在线成人精品| 亚洲欧洲久久| 国产一区二区av| 欧美日本不卡视频| 久久精品人人做人人爽| 亚洲性视频网站| 亚洲人成小说网站色在线| 久久综合九色综合网站| 欧美一级二级三级蜜桃| 中文久久乱码一区二区| 亚洲精品老司机| 激情视频一区二区三区| 国产女人18毛片水18精品| 欧美精品一区三区在线观看| 久久久天天操| 香蕉尹人综合在线观看| 亚洲天堂视频在线观看| 亚洲精品资源美女情侣酒店| 欧美国内亚洲| 久热精品视频在线| 久久尤物视频| 久久久久久久久久久久久9999| 午夜伦欧美伦电影理论片| 一区二区高清视频| 日韩视频永久免费观看| 亚洲品质自拍| 亚洲日本中文字幕| 亚洲精品乱码久久久久久| 亚洲欧洲日产国产网站| 亚洲国产高清aⅴ视频| 亚洲国产精品va在线看黑人| 亚洲国产成人porn| 最新精品在线| 99视频在线精品国自产拍免费观看 | 国产亚洲激情| 国产一区欧美| 在线日韩电影| 亚洲日韩欧美视频| 一区二区不卡在线视频 午夜欧美不卡在| 亚洲国产日本| 亚洲免费不卡| 一区二区三区色| 亚洲欧美日韩精品久久亚洲区 | 在线一区视频| 亚洲一区观看| 久久精品国产精品亚洲精品| 久久久久久夜| 亚洲国产成人精品久久| 亚洲精品久久久久久一区二区| 日韩一区二区精品视频| 亚洲一区综合| 欧美大片免费久久精品三p | 欧美日韩aaaaa| 国产精品色网| 亚洲成人资源网| 99国内精品久久| 午夜在线成人av| 猛干欧美女孩| 99riav久久精品riav| 欧美一二三区在线观看| 欧美~级网站不卡| 国产精品乱人伦一区二区| 国产自产精品| 亚洲午夜精品视频| 久久影视三级福利片| 亚洲三级视频在线观看| 欧美亚洲专区| 欧美日韩不卡一区| 国产一区91精品张津瑜| 日韩视频在线观看国产| 久久精品视频va| 亚洲精品之草原avav久久| 欧美在线亚洲| 欧美视频一二三区| 亚洲国产精品第一区二区三区 | 在线看成人片| 亚洲欧美日韩国产成人精品影院| 免费看成人av| 亚洲欧美日韩中文在线制服| 欧美成人一区二区在线| 国产精品日韩一区| 一区二区三区不卡视频在线观看| 久久精品人人做人人综合| 日韩一级大片| 免费观看不卡av| 狠狠久久婷婷| 欧美在线一区二区| 亚洲视频免费看| 欧美人成在线视频|