• <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>
            Impossible is nothing  
              愛過知情重醉過知酒濃   花開花謝終是空   緣份不停留像春風(fēng)來又走   女人如花花似夢
            公告
            日歷
            <2025年8月>
            272829303112
            3456789
            10111213141516
            17181920212223
            24252627282930
            31123456
            統(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中的每一個進(jìn)程都是EPROCESS結(jié)構(gòu)體。此結(jié)構(gòu)體中除了進(jìn)程的屬性之外還引用了其它一些與實(shí)現(xiàn)進(jìn)程緊密相關(guān)的結(jié)構(gòu)體。例如,每個進(jìn)程都有一個或幾 個線程,線程在系統(tǒng)中就是ETHREAD結(jié)構(gòu)體。我來簡要描述一下存在于這個結(jié)構(gòu)體中的主要的信息,這些信息都是由對內(nèi)核函數(shù)的研究而得知的。首先,結(jié)構(gòu) 體中有KPROCESS結(jié)構(gòu)體,這個結(jié)構(gòu)體中又有指向這些進(jìn)程的內(nèi)核線程(KTHREAD)鏈表的指針(分配地址空間),基優(yōu)先級,在內(nèi)核模式或是用戶模 式執(zhí)行進(jìn)程的線程的時(shí)間,處理器affinity(掩碼,定義了哪個處理器能執(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)核堆?;泛晚旤c(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中,每一個進(jìn)程都有自己私有的頁表(進(jìn)程的所有線程共享此頁表)。相應(yīng)的,在進(jìn)程切換時(shí)會發(fā)生頁表的切換。為了加快對頁表的訪問,硬件中有一個 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個表項(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。為什么這樣——看個例子就知道了:

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

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

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

            還有幾個有意思的地方。從c0000000開始有個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)核空間又總是在每個用戶進(jìn)程的上下文中)。這是進(jìn)程私有的區(qū) 域。例如,working set就位于hyper space中。頁表的前256個PTE(hyper space的前1/4)為內(nèi)核保留,而且在需要快速向frame中映射虛擬地址時(shí)使用。

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

            1100000000.1000000000.000000000000 = 0xc0200000

            1) 解析出 PDE #1100000000 (4k 頁) 并選出 PageDirectory
            2) 在 Directory 中選出 PTE #1000000000 (c0300800)
               這是個 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足夠放下頁表完整的一頁。這個表位于地址0xc0301000 - 0xc0301ffc(PDE的第0個表項(xiàng)位于0xc0300c04)。在內(nèi)部,為向HyperSpace區(qū)域中映射物理頁(當(dāng)需要快速為某個frame 組織虛擬地址時(shí))要使用函數(shù):

            DWORD MiMapPageInHyperSpace(DWORD BaseAddr,OUT PDWORD Irql);

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

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

            MmFirstReservedMappingPte=0xc0301000
            MmLastReservedMappingPte=0xc03013fc

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

            0xc0400000-0xc04fffff (1/4 HyperSpace)

            在MmFirstReservedMappingPte處是一個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)中有個Sytem Ptes Pool。而且pool中有pte blocks(blocks表示請求是以block為單位來滿足的,一個block中有一些pte,1、2、4、8和16個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)。鏈表最后一個元素NextRef域的值為0xfffff000 (-1) 。相應(yīng)的,鏈表有5個(block大小分別為1,2,4,8和16個pte)。

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

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

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

            在兩個鏈表中有兩個引用。鏈表的元素:

            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ì)上講,這只是個0x18字節(jié)長的結(jié)構(gòu)體塊。每一個結(jié)構(gòu)體對應(yīng)一個物理頁 (順序排列,所以元素常被稱為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在鏈表中,則這個就是frame的號
                               // 最后的一個為 -1
                DWORD Misc;    // 同時(shí)另外一項(xiàng)信息, 依賴于上下文
                               // 見偽代碼 (通常 TmpPfn->0...)
                               // 通常這里有 *KTHREAD, *KPROCESS,
                         // *PAGESUPPORT_BLOCK...
                      };
                PPTE PtePpte;  // 0x4 指向 pte 或 ppte
                union {        // 0x8
                      DWORD PrevRef;      // 前面的frame或 (-1, 第一個)
                      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)容位于與這個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)然,這個結(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個鏈表。各域的名字很好的說明了它們的用處。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             |不可用的頁(有錯誤)                                 |  5   |
            |Active         |活動頁,至少映射一個虛擬地址                         |  -   |
            +---------------+----------------------------------------------------+------+

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

            每個鏈表的表頭都是下面這個樣子:

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

            除此之外,可以用“color”(就是cache)來尋址空閑frame(zeroed或是free)。如果看一下附錄中的偽代碼就容易理解了。我給出兩個結(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)核并不總是依賴這個線程,有時(shí)會遇到這樣的代碼,它獲取一個空閑頁,用過后自己將其清零。

            05.Working Set
            ==============
            Working Set——工作集,是屬于當(dāng)前進(jìn)程的物理頁集。內(nèi)存管理器使用一定的機(jī)制跟蹤進(jìn)程的工作集。working set有兩個限額:maximum  working set和minimum working set。這是工作集的最大值和最小值。內(nèi)存管理器以這兩個值為依據(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ù)。這個計(jì)數(shù)在MiLocateAndReserveWsle函數(shù)中被擴(kuò)大,因?yàn)檫@個函數(shù)不只在page fault時(shí)被調(diào)用,在某些其它情況下也會被調(diào)用(真的,很少見)。

            下面這個結(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本身是下一個空閑WSLE的索引。這樣,空閑的WSLE就組織成了鏈表。最后一個空閑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í),要更新這些虛地址,這樣,每個進(jìn)程都有自己的工作集結(jié)構(gòu)體。在地址MmWsle (0xc0502690)上是Wsle動態(tài)表的起始地址。表的結(jié)尾的地址總是0x1000的倍數(shù),也就是說表可以結(jié)束在地址0xc0503000、 0xc0504000等等上(這是為了簡化對Wsle表大小的操作)。Cache(如果有)位于一個偏移上,Wsle不會向這個偏移增長。我們來詳細(xì)看一 下這個表:

            // 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
            // ....

            這里有個有意思的地方,在表的起始部分有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且位于一個鏈表中時(shí)。frame可以屬于工作集。在缺少空閑frame時(shí)或是在達(dá)到treshhold時(shí),就會發(fā)生frame的換出。這方面的高層次函數(shù)是有的。這里的任務(wù)是用偽代碼來證實(shí)。

            在NT中有最多16個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 // 忽略
                   );

            每個pagefile都有一個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的換出。而且不是一個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í),在堆棧中有錯誤代號,包含有以下信息:用戶/內(nèi)核錯誤位(異常 發(fā)生在ring3還是ring0?),讀寫錯誤位(試圖讀還是寫?),頁存在位。除此之外,在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是很有用的。

            最顯然的就是訪問錯誤,這時(shí)ring3的代碼試圖寫入PTE/PDE中未設(shè)置U位的頁或是寫入了只讀的頁(PTE/PDE中未設(shè)置W位)。再有,頁可以被 換出到頁面文件中,對應(yīng)于這些頁的PTE中未設(shè)置P位,但有信息指示在哪個頁面文件中尋找frame,以及frame的偏移。還有一個類似的情況—— 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)有一個內(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)。我們熟知的(有一個幾乎與之同名的Win32函數(shù)調(diào)用這個函數(shù))未公開函數(shù)NtAllocateVirtualMemory (ring0下是ZwAllocateVirtualMemory)操作這些結(jié)構(gòu)體。

            每一個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)是一個平衡二叉樹(有內(nèi)部函數(shù)負(fù)責(zé)修整此樹),這是為查找而進(jìn)行的優(yōu)化。在VAD中有兩個指向后面元素——左右子樹——的指 針。樹的根位于EPROCESS結(jié)構(gòu)體的VadRoot域(NT 4.0下是偏移0x170)。當(dāng)然,每一個進(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è)有個文件映射到了幾個(3個)進(jìn)程的地址空間。PPTE表包含有PPTE,這些PPTE描述了加載到內(nèi) 存的文件的物理頁。某些PPTE可以有P位(其位置與含義與PTE/PDE的相同),而某些則沒有,沒有P位的有信息用來決定是從頁來加載frame還是 從映象文件來加載文件。所有三個進(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)核地址空間還是用戶地址空間,處理 程序從兩個執(zhí)行分支中選擇一個。第一種情況下的處理程序較為簡單,跟蹤ACCESS VIOLATION或是收回在Working Set中的頁(MiDispatchFault)。若是用戶空間的地址情況就就更為復(fù)雜一些。首先,如果PDE不在內(nèi)存中則執(zhí)行用于PDE的異常處理程 序。然后,出現(xiàn)了一個分支。第一個分支——頁存在。這表示要么是ACCESS VIOLATION,要么就是對copy on write的處理。第二個分支——處理清零頁請求、ACCESS VIOLATION、頁邊界(GUARD)(堆棧增長)以及必須的對working set中頁的回收。有趣的是,在大量發(fā)生page fault的時(shí)候,系統(tǒng)會增大working set的大小。在零PTE的情況下,為確定狀況,處理程序不得不使用VAD樹來確定試圖訪問區(qū)域的屬性。這些都是MiAccessCheck的工作,這個 函數(shù)返回訪問的狀態(tài)。

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

            輪到MiDispatchFault了,它主要是基于一些更低級的函數(shù):MiResolveTransitionFault、 MiResolveDemandZeroFault、MiResolveDemandZeroFault、MiResolveProtoPteFault 和MiResolvePageFileFault。從這些函數(shù)的名字可以明顯看出,這個函數(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開始。

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

            前面的偽代碼需要一個結(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ù)用代碼的原則的話。

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

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


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

            NT 中的section對象就是一塊內(nèi)存,這塊內(nèi)存由一個進(jìn)程獨(dú)有或幾個進(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)中的每一個文件都是對象(NTDDK.H中有描述)FILE_OBJECT。在這個結(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)體中有兩個指針——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形成了一個鏈表,結(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。每一個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

            我們來看剩下的最后一個結(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的信息。有個東西需要解釋一下。結(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)體中看 出?,F(xiàn)在還不需要進(jìn)一步詳細(xì)的講PAE,畢竟很少用到,所以我們就此打住。

            描述section的所有結(jié)構(gòu)體都介紹過了,而section對象結(jié)構(gòu)體本身還沒有提到。從直觀上可以想到,它應(yīng)該會引用到SEGMENT或是 CONTROL_AREA,因?yàn)橛辛诉@兩個結(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)。為了有一個總體上的把握,下面給出了描述section的結(jié)構(gòu)體間互相聯(lián)系的一個圖。

            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)體?,F(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是對另一個內(nèi)部函數(shù)PspCreateProcess的封裝。NtCreateProcess進(jìn)行的唯一工作就 是檢查Parent(父進(jìn)程句柄)。但是接下來我們看到,對于NT來說這并沒有什么意義,因?yàn)榭偟膩碚f,進(jìn)程的繼承性本身沒有特別的意義?,F(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)體的指針。這個結(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,則使用這個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。前一個函數(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í)這個空間是屬于系統(tǒng)cache的。


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

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

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

            從這個函數(shù)可以得到以下結(jié)論。進(jìn)程可以處于以下狀態(tài)——0(準(zhǔn)備),1(Idle),2(Trans——切換)。這證實(shí)了高層次的信息。KiAttachProcess使用了另外兩個函數(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)程上下文。正如我所料,這個函數(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)的上下文切換??偟恼f來,代碼分析表明,理解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)行完整的描述,我還得深挖不止十個八個的函數(shù)。但是主要的基本的東西我這里都寫到了。對于進(jìn)一步反匯編內(nèi)核來說,這些應(yīng)該是很有幫助 的吧,誰知道呢... ;)

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

                                        mailto:gl00my@mail.ru

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


            附錄


            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秒。接著填充某個結(jié)構(gòu)體,大概是這個樣子:

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

            這個結(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ì)列中的下一個是我們的請求(???)。則從隊(duì)列中收回請求,因?yàn)?br> 是從我們這里來的。

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

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

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

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

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

            輔助函數(shù):

            DWORD NTOSKRNL RtlRandom(PDWORD Seed);

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

            如果有兩個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 笑笑生 閱讀(1147) 評論(0)  編輯 收藏 引用
             
            Copyright © 笑笑生 Powered by: 博客園 模板提供:滬江博客
            久久久国产视频| 久久久中文字幕日本| 精品久久久久中文字幕日本| 99精品国产在热久久| 久久97久久97精品免视看| 亚洲av伊人久久综合密臀性色 | 91精品国产综合久久婷婷| 久久久久久久综合日本亚洲| 亚洲精品国产综合久久一线| WWW婷婷AV久久久影片| 欧美日韩精品久久免费| 日本免费一区二区久久人人澡| 伊人伊成久久人综合网777| 97r久久精品国产99国产精| 老男人久久青草av高清| 久久国产成人亚洲精品影院| 久久青青草原精品国产| 久久青青草视频| 欧美性猛交xxxx免费看久久久| 国产精品欧美久久久天天影视| 亚洲色大成网站www久久九| 久久这里的只有是精品23| 人妻中文久久久久| 久久精品国产色蜜蜜麻豆| 精品久久人人妻人人做精品 | 久久99热狠狠色精品一区| 无码超乳爆乳中文字幕久久 | 91精品国产乱码久久久久久| 亚洲精品无码久久久久sm| 国内高清久久久久久| 国产精品久久久久免费a∨| 久久毛片一区二区| 国产精品中文久久久久久久| 波多野结衣久久精品| 中文字幕精品久久| 2021国内精品久久久久久影院| 日韩亚洲国产综合久久久| 亚洲国产香蕉人人爽成AV片久久| 人妻中文久久久久| 无码精品久久久久久人妻中字 | 激情久久久久久久久久|