淺析Windows NT/2000環境切換(http://webcrazy.yeah.net)

來源:互聯網
上載者:User
淺析Windows NT/2000環境切換
                  WebCrazy(tsu00@263.net)
                       注:本文最初見於www.nsfocus.com

    本文假設您已經瞭解Windows NT/2000系統體系,對Windows NT/2000內部KPEB/KTEB等資料結構與核心工作方式已有一定的概念,對80x86保護模式,Intel/AT&T格式組合語言有過學習,能熟練使用SoftICE for Windows NT,且曾經接觸過Microsoft Visual Studio及其附帶工具,翻閱過Linux核心代碼,如果您對這些方面不甚瞭解,請自行參閱相關書籍。

    環境切換(Context Switch)牽涉到很多方面的內容,本文僅對與其有關的幾個資料進行詳細的討論,並給出取得這些資料的部分程式段,還列出Windows 2000的少量環境切換代碼。另外文中討論的系統內部資料均未來自Microsoft官方文檔,在Windows NT/2000的下個版本甚至目前各版本間均會有差別,所以我盡量詳細的將文中所涉及的軟硬體列於下面,所有因硬體體系、軟體版本不同等因素引起的差異,請自行根據您的情況予以調整。

⊙ x86平台單處理機Windows 2000 Server Build 2195
⊙ Numega SoftICE 4.05 for Windows NT/2000 Build 334
⊙ Linux 2.0.30核心
⊙ Datarescue IDA 4.0.4.362
⊙ Microsoft Visual Studio 6.0 SP3
⊙ Windows 2000 DDK

    80x86產生的環境切換有以下幾種可能:
    1.當前任務執行一個FAR CALL或JMP指令,而選取器指向一個TSS描述符或一個任務門。
    2.當前任務執行IRET指令返回先前任務,IRET只在EFLAGS寄存器中的NT位置1時產生切換。
    3.發生一個中斷或異常情況,並且IDT項是個任務門。

    Linux核心中有如下代碼:

    /*
    /usr/src/linux/include/asm-i386/system.h
    僅列出單一處理器實現代碼
    */

    #define switch_to(prev,next) do { /
    __asm__("movl %2,"SYMBOL_NAME_STR(current_set)"/n/t" /
    "ljmp %0/n/t" /
    "cmpl %1,"SYMBOL_NAME_STR(last_task_used_math)"/n/t" /
    "jne 1f/n/t" /
    "clts/n" /
    "1:" /
    : /* no outputs */ /
    :"m" (*(((char *)&next->tss.tr)-4)), /
    "r" (prev), "r" (next)); /
    /* Now maybe reload the debug registers */ /
        .
        .
        .
    } /
    } while (0)

    這段代碼使用了上面討論的第一種情況。

    眾所周知,Linux是個開放原始碼的作業系統,而Microsoft則沒如此“大方”,但我們仍能對其進行些逆向工程,可喜的是網上目前已經有很多人對此有過研究,現摘錄Mark Russinovich部分成果(http://www.sysinternals.com/tips.htm):

    //
    // NT's main
    // NTOSKRNL main
    //
    int main( boot parameters )
    {
        //
        // Fire up NT!
        //
        KiSystemStartup();
        return 0;
    }

    從中可看出ntoskrnl.exe(PE格式,可方便的使用反組譯碼工具進行分析)是NT OSLOADER真正調用核心的開始,其對檔案對象(File)、工作物件(Job)、進程對象(Process)、線程對象(Thread)、纖程對象(Fiber)、檔案對應物件(FileMapping)、事件對象(Event)、互斥對象(Mutex)、訊號對象(Semaphore)等許多核心對象進行管理,其也負責線程調度,記憶體管理,處理序間通訊等所有作業系統功能,讓它們協調工作,我們要討論的線程切換代碼也在此模組中。

    用IDA等對ntoskrnl.exe進行反組譯碼所得的結果,其分析的工作量恐怕大家都是可想而知的。在我們討論Windows 2000環境切換詳細代碼前,還是先讓我們看看以下幾個重要的與環境切換有關的系統資料:

    1 進程Context
    進程Context是指80x86在保護模式下記憶體分頁機制中當前進程的頁目錄所在的物理地址,其存放在系統CR3寄存器中,在Windows 2000中所處的位置為KPEB位移後18h處,看看SoftICE的輸出結果吧(限於篇幅,我對輸出結果進行了刪減,但仍對重要資料進行註解,應注意的是與您當前啟動並執行程式等系統內容密切相關,隨機性很強,下同):

    :cpu //顯示當前cpu的寄存器值

    Processor 00 Registers
    ----------------------
    CS:EIP=0008:80069582 SS:ESP=0010:8046FD98
    EAX=8046BDF0 EBX=FFDFF000 ECX=FFDFF878 EDX=0000BA5A
    ESI=8046BDF0 EDI=8046BB60 EBP=FFDFF800 EFL=00000213
    DS=0023 ES=0023 FS=0030 GS=0000

    CR0=8000003B PE MP TS ET NE PG
    CR2=76EE18EC
    CR3=00030000
           |
           |_當前進程的CR3
    CR4=000002D1 VME PSE MCE PGE
        .
        .
        .

    :proc idle

    Process KPEB    PID Threads Pri User Time  Krnl Time  Status
    *Idle   8046BB60 0    1      0  00000000   0000BA5A   Running
      |        |
      |        |_Idle進程的KPEB
      |_系統中當前進程(SoftICE中用不同顏色突出,且前面有個*)

    :dd 8046bb60+18 l 4 //dd Idle's KPEB+18h
    0010:8046BB78 00030000 00000000 00000000 00000000 ................
                     |
                     |_Idle進程Context
    :addr
    CR3      LDT Base:Limit KPEB Addr PID Name
    00030000                FE4E1C60  0008 System
    02D59000                FF8E6540  0090 smss
    01D41000                FF8E17E0  00AC csrss
    00686000                FE51BAE0  00C0 winlogon
    0095D000                FF8A7AE0  00DC services
    0276E000                FF8A5D60  00E8 lsass
    00394000                FF881020  0180 svchost
    02CAE000                FF884020  01A4 SPOOLSV
    00882000                FF85B560  01D0 msdtc
    02993000                FF83F020  0238 svchost
    00D2F000                FF83D760  024C llssrv
    0063A000                FF837860  0274 regsvc
    02EFA000                FF6ECD60  0318 dfssvc
    00A5E000                FF823A20  0328 inetinfo
    03612000                FF6AF860  0384 explorer
    003A2000                FF68E460  03B4 internat
    003A7000                FF68CD60  0130 OSA
    008C1000                FF6769A0  03E8 svchost
    01BAA000                FF65A020  01C0 cmd
    00822000                FF86C960  038C conime
    03362000                FF6B3540  0388 notepad
    *00030000               8046BB60  0000 Idle
        |
        |__當前進程Context,是不是與上一命令輸出結果一致。

    可以用同樣的方法進行再次進行驗證。

    2.Context Switches Times 線程已被作業系統調度次數
    當每次作業系統調用線程時,都會將這個值加一的。Visual Studio所附的工具Spy++,在Thread視窗中Thread Properties中Context Switches指出系統中該線程已調度的次數(Spy++只有在Windows NT/2000中運行時才會顯示出這個值,9x中則沒有)。Switch Times在系統中所處的位置在KTEB的位移4ch處。

    :thread idle
    TID  Krnl TEB StackBtm StkTop   StackPtr User TEB Process(Id)
    0000 8046BDF0 8046D040 80470040 8046FD90 00000000 Idle(00)
    :dd 8046bdf0+4c l 4
    0010:8046BE3C 0000E778 00000000 00000002 00000000 x...............
                    |
                    |_指出當前Idle線程已經被系統調用0E778h(十進位59256)次了。用e命令改改再看看Spy++的輸出結果!

    3.線程所屬進程的KPEB與進程名 //分別位於KTEB+44h與KPEB+1fch處
    具體見我在《再談Windows NT/2000內部資料結構》(Nsfocus Magazine 11)一文所述。

    應該指出的是,以上討論只是針對Windows 2000 Server Build 2195的,如果您的系統不是的話或想知道如何得到這些值的具體位置,請參閱我以下的敘述的方法:

    通過找突破口,正像上面我所描述的Context Switches Times在Spy++中顯示的一樣,然後可以用逆向工程法,我也是用這個方法來取得這個具體位置的。

    舉個例子吧!我曾經對SoftICE for NT中的addr命令輸出結果(包含進程Context)來自何處感到困惹,也曾經在國外的一些著名的新聞群組中提問過,不過至今仍沒人應答(可能是我的英文水平太差,人家看不懂什麼意思吧!)

    addr命令輸出結果見上。

    後來無奈之下我還是想到CR3(存放進程頁目錄物理地址的寄存器)應與特定的進程有關,其應該存放在KPEB結構中(實際上的確是這樣的)。而如果真是這樣的話,不是只要枚舉(Enum)出系統中所有的KPEB,則能得到所有的CR3值(當然前提是找出其相對KPEB的位移值),相應的使用我在《再談Windows NT/2000內部資料結構》(Nsfocus Magazine 11)的方法就可以取出所有進程的進程名了嗎?(PID也是一樣的)。

    我在通過分析PSAPI.DLL中枚舉系統進程的函數後(EnumProcesses等),發現系統啟動後的第一個進程system的KPEB是存放在ntoskrnl.exe匯出的PsInitialSystemProcess指出的地址處的,而系統中各個KPEB由一鏈表連接著,至於鏈表的定義在Windows 2000 DDK中的ntdef.h中如下定義的:

    typedef struct _LIST_ENTRY {
        struct _LIST_ENTRY *Flink;
        struct _LIST_ENTRY *Blink;
    } LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;

    有了KPEB,跟蹤相應的代碼(這段反組譯碼代碼我將在下面列出),就能找出位移地址18h處的CR3值。

    以上操作,在Windows 9x中可由Kernel32.dll中序號為1的Undocumented函數(NONAME,Softice Export列表中顯示為ORD_0001)實現,但因為Windows 9x與NT/2000的核心的不同,Softice for 9x與NT的addr命令輸出的格式也完全不同。至於在Windows NT/2000中不知道是否有現成的函數可以得到結果,至少現在我也沒找到,這可是題外話。

    因為上面的敘述還是比較抽象,我還是將SoftICE的輸出結果列於此,更利於理解:

    :dd PsInitialSystemProcess l 4
    0008:8046A844 FE4E1C60 E1000968 00000000 00000000 `.N.h...........
                    |
                    |_System進程的KPEB

    :dd @PsInitialSystemProcess+18 l 4
    0008:FE4E1C78 00030000 00000000 00000000 00000000 ................
                    |
                    |_System進程的Context

    :dd @PsInitialSystemProcess+9c l 4 //9ch是PID相對KPEB的位置
    0008:FE4E1CFC 00000008 FF8E65E0 8046A180 00000000 .....e....F.....
                     |
                     |_System進程PID

    :dd @PsInitialSystemProcess+1fc l 10 //1fch是Process Name相對KPEB的位置
    0008:FE4E1E5C 74737953 00006D65 00000000 00000000 System..........
                                                        |
                                 System進程Process Name_|
    @(@PsInitialSystemProcess+a0)-a0
    //計算System指向的下一個進程的KPEB,0a0h是鏈狀結構相對KPEB的位移
    FF8E6540 4287522112 (-7445184) "巈@"
      |
      |_System進程KPEB指向的下一個KPEB,從Process Name可知為smss.exe(見下)

    :dd @(@PsInitialSystemProcess+a0)-a0+18 l 4
    0008:FF8E6558 02D59000 02D5A000 00000000 00000000 ................
                    |
                    |_smss.exe的進程Context

    :dd @(@PsInitialSystemProcess+a0)-a0+9c l 4
    0008:FF8E65DC 00000090 FF8E1880 FE4E1D00 00000518 ..........N.....
                     |
                     |_smss.exe的PID

    :dd @(@PsInitialSystemProcess+a0)-a0+1fc l 10
    0008:FF8E673C 73736D73 6578652E 00000000 00000000 smss.exe........
                                                        |
                               smss.exe進程Process Name_|
    :dd @(@(@PsInitialSystemProcess+a0))-a0+18 l 4

            .
            . (可以用Softice用同樣的方法一直跟蹤到鏈表結束)
            .

    實現程式碼片段如下:

    /*
    由於以下程式碼片段均要求擷取系統資料結構,即要求在Ring 0狀態下運行,所
    以必須位於NT/2000裝置驅動程式中。因裝置驅動程式的架構,決定的代碼的長
    度較長,此處僅列出關鍵程式碼片段,您可以找本WDM的書,將此程式段置入您的代碼中,
    或是直接聯絡我(tsu00@263.net)。
    */

        .
        .
        .
    PLIST_ENTRY KPEBListHead, KPEBListPtr;          //PLIST_ENTRY定義見上
    ULONG KPEBListOffset=0xa0;                      //定義鏈表相對KPEB的位移值
    ULONG ProcessNameOffset=0x1fc;                  //定義ProcessName相對KPEB的位移值
    ULONG ProcessContextOffset=0x18;                //定義Process Context相對KPEB的位移值
    ULONG PIDOffset=0x9c;                           //定義PID相對KPEB的位移值

    if(((USHORT)NtBuildNumber)!=2195){
        DbgPrint("Only test on Windows 2000 Server Build 2195!/n");
        return;
    }

    DbgPrint("/n CR3/t/tKPEB Addr/tPID/t Name");
    DbgPrint("/n ---/t/t-------- /t---/t ----/n");

    KPEBListHead=KPEBListPtr=(PLIST_ENTRY)(((char *)PsInitialSystemProcess)+KPEBListOffset);
    while (KPEBListPtr->Flink!=KPEBListHead) {
        void *kpeb;
        char ProcessName[16];
        ULONG ProcessContext;
        ULONG PID;

        //取KPEB
        kpeb=(void *)(((char *)KPEBListPtr)-KPEBListOffset);

        //取ProcessName
        memset(ProcessName, 0, sizeof(ProcessName));
        memcpy(ProcessName, ((char *)kpeb)+ProcessNameOffset, 16);

        //取Process Context
        ProcessContext=*(ULONG *)(((char *)kpeb)+ProcessContextOffset);

        //取PID
        PID=*(ULONG *)(((char *)kpeb)+PIDOffset);

        //向Debugger輸出結果
        DbgPrint(" %08X/t%08X/t%04X/t %s/n",ProcessContext, kpeb,PID,ProcessName);

        //指向下一鏈表
        KPEBListPtr=KPEBListPtr->Flink;
    }

            .
            .
            .

    使用Checked方式編譯運行後調試器輸出結果如下(儼然就是一個最底層的EnumProcesses實現方法):

    CR3      KPEB Addr PID  Name
    ---      --------  ---  ----
    00030000 FE4E1C60  0008 System
    02D59000 FF8E7920  0090 smss.exe
    003C1000 FE520520  00AC csrss.exe
    026C6000 FE51A020  00A8 winlogon.exe
    03209000 FF8A8D60  00DC services.exe
            .
            . 略
            .

    我之所以花如此大的篇幅去講述進程Context的獲得,似乎與環境切換的主題不相一致,主要是由於Windows NT/2000的封閉性,我覺得真正要明白環境切換,Linux平台就可以比較容易理解,在NT中重要是知道如何取得些與此有關的重要資料結構,然後再與x86平台體繫結構聯絡在一起,就能更好的協助自己理解。使用上面所述的類似方法,還可以找出很多KPEB/KTEB重要訊息,如進程優先順序(KPEB+62h)、進程在核心態與使用者態所使用的時間(KPEB+38h與KPEB+3ch)、線程ID(KTEB+1e4h)等等,可與linux的task_struct等結構比較比較。

    好了談了這麼多,我還是簡單說說Windows 2000中的環境切換代碼吧。

    那麼Windows NT/2000什麼情況下發生環境切換呢?曾見過一DDK FAQ中是這樣描述的:

    Q:What are the causes of a context switch in Windows NT?
    A:There are only two ways that a thread context is switched.
        1.The thread yields it's quantum by blocking on something(event,semaphore,etc.).
        2.The time period is up.This is caused by a timer interrupt.

    KiDispatchInterrupt是NT/2000中定時進行環境切換常式,以下列出其部分代碼:

    ;Linux中實作類別似功能的代碼位於/usr/src/linux/kernel/sched.c
    00403A58 KiDispatchInterrupt proc near
    00403A58
    00403A58 var_C = dword ptr -0Ch
    00403A58 var_8 = dword ptr -8
    00403A58 var_4 = dword ptr -4
    00403A58
    00403A58 mov ebx, ds:0FFDFF01Ch
    00403A5E lea eax, [ebx+800h]
    00403A64 cli
    00403A65 cmp eax, [eax]
    00403A67 jz short loc_403A86
    00403A69 push ebp
    00403A6A push dword ptr [ebx]
    00403A6C mov dword ptr [ebx], 0FFFFFFFFh
    00403A72 mov edx, esp
    00403A74 mov esp, [ebx+81Ch]
    00403A7A push edx
    00403A7B mov ebp, eax
    00403A7D call sub_460BA4
    00403A82 pop esp
    00403A83 pop dword ptr [ebx]
    00403A85 pop ebp
   
            .
            .(限於篇幅,此處略去部分,感興趣的自己步步跟蹤)
            .

    ;另CR3切換代碼:

    ;Linux中實現此功能的代碼位於/usr/src/linux/include/asm-i386/pgtable.h
    ;由宏定義SET_PAGE_DIR實現,請參考之。

    ;此時EDI儲存KPEB(自己用SoftICE跟跟),執行後EAX則為進程Context
    ;這句結合mov cr3,eax是不是可以跟蹤到CR3在KPEB的具體位置的呢,我就是從這兒跟蹤到的。
    00403B87 mov eax, [edi+18h]
    00403B8A mov ebp, [ebx+40h]
    00403B8D mov ecx, [edi+30h]
    00403B90 mov [ebp+1Ch], eax
    00403B93 mov cr3, eax ;EAX->CR3
    00403B96 mov [ebp+66h], cx
    00403B9A xor eax, eax
    00403B9C cmp [edi+20h], ax
    ;轉去錯誤處理,必要時還會調用KeBugCheck,出現可怕的藍色當機畫面.
    00403BA0 jnz short loc_403BCE
    ;LDTR置空選取器
    00403BA2 lldt ax
    00403BA5 lea ecx, [ecx]
    00403BA7
    00403BA7 loc_403BA7: ; CODE XREF: .text:00403B7Dj
    00403BA7 ; .text:00403BFAj
    ;將Context Switches Times加一
    00403BA7 inc dword ptr [esi+4Ch]
    00403BAA inc dword ptr [ebx+5C0h]
    00403BB0 pop ecx
    00403BB1 mov [ebx], ecx
    00403BB3 cmp byte ptr [esi+49h], 0
    00403BB7 jnz short loc_403BBD
    00403BB9 popf
    00403BBA xor eax, eax
    00403BBC retn
    00403BBD loc_403BBD: ; CODE XREF: .text:00403BB7j
    00403BBD popf
    00403BBE jnz short loc_403BC3
    00403BC0 mov al, 1
    00403BC2 retn
    00403BC3 loc_403BC3: ; CODE XREF: .text:00403BBEj
    00403BC3 mov cl, 1
    00403BC5 call ds:HalRequestSoftwareInterrupt
    00403BCB xor eax, eax
    00403BCD retn

            .
            .(略)
            .

    部分代碼我尚未進行註解,主要是一些代碼與代碼運行環境有關,如處理NT執行體的錯誤檢查(包括有效性、安全性等),而且我這兒也未列出代碼,如果你有興趣的話用SoftICE步步跟蹤可以發現很多NT內部機制。

    Windows 2000是搶佔式多線程作業系統,文中並未涉及到線程調度的具體方法。真正線程調度切換,還要考慮很多因素,如線程狀態(是否可調度等)、線程優先順序(Priority)、線程的親緣性(Affinity)等,這些具體的重要訊息,也都由ntoskrnl.exe模組處理,我也不可能都詳細的在此列出。本文只討論如何獲得這資訊,不過若想知道得更多,仍可以根據文中的討論對其進行進一步的分析。

    我曾經接觸過單片機,也曾經對其似乎從頭開始設計一個OS(可能只是幾條指令,單片機高手千萬別見笑)感到不解,但當我接觸過NT Kernel後才覺得自己是多麼的可笑。不過在接觸NT核心時,可大量參照Linux代碼,畢竟她們原理應該是一樣的,雖然Linux不是一個微核心OS,而NT/2000是。個人認為linux的task_struct與NT的KPEB,linux中的Bottom half機制與NT的DPC(延時程序呼叫)等有其相似的地方(雖然在機制上實現方法上仍有很大的不同)。由於Microsoft未提供任何官方文檔且NT核心的複雜性(曾有人批評NT的微核心比Linux還要大呢),本文所討論的,我也不能保證其絕對的正確性,如果您發現任何錯誤之處或是有什麼建議,請予以告之,謝謝!

    參考資料:
    1.Jeffrey Richter
        <<Programming Applications for Microsoft Windows,Fourth Edition>>
    2.Linux相關文檔
    3.Mark Russinovich相關文檔
    4.Intel Corp<<Intel Architecture Software Developer's Manual,Volume 3>>

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.