天天看點

[Windows] 程序和線程

程序的位址空間

以32位系統為例,程序的4GB虛拟記憶體中有一半屬于使用者空間,一半屬于核心空間。使用者空間位址範圍是​

​64kb---->0x80000000-64kb​

​​(是以才會有空指針異常,因為0x0這個位址使用者空間不能通路,0~64kb屬于保留區),系統位址範圍是​

​0x80000000---->0xffffffff​

​。

另外核心空間的​

​0xffdf0000​

​​ 與使用者空間的​

​0x7ffe0000​

​區域共享。

具體分布情況如下(從低到高):

位置 内容
64kb 保留區/禁區
64kb 環境變量塊
64kb 參數塊
1MB 主線程的棧
其它空間
一般在0x00400000處 exe檔案各個節
其他空間
n*4kb 各teb
4kb peb
4kb 核心使用者共享區
60kb 無效區
64kb 隔離區
0x80000000開始 系統空間

程序的建立過程

  • 打開目标可執行檔案

    若是exe檔案,先檢查‘映像劫持’鍵,然後打開檔案,建立一個section,等候映射

    若是 bat、cmd腳本檔案,則啟動的是cmd.exe程序,腳本檔案作為指令行參數

    若是DOS的exe、com檔案,啟動ntvdm.exe v86程序,原檔案作為指令行參數

    若是posix、os2檔案,啟動對應的子系統服務程序

  • 建立、初始化程序對象;建立初始化位址空間;加載映射exe和ntdll檔案;配置設定一個PEB
  • 建立、初始化主線程對象;建立TEB;構造初始的運作環境(核心初始棧幀)
  • 通知windows子系統(csrss.exe程序)新程序建立事件(csrss.exe程序含有絕大多數程序的句柄)

    這樣,程序、主線程都建立起來了,隻需等待得到cpu排程便可投入運作。

相關核心結構

// 每個線程的初始核心棧幀
typedef struct _KUINIT_FRAME  
{
    KSWITCHFRAME CtxSwitchFrame;  //切換幀
    KSTART_FRAME StartFrame;    //KiThreadStartup函數的參數幀
    KTRAP_FRAME TrapFrame;      //trap現場幀
    FX_SAVE_AREA FxSaveArea;    //浮點儲存區
} KUINIT_FRAME, *PKUINIT_FRAME;

//KiThreadStartup的參數幀
typedef struct _KSTART_FRAME 
{
    PKSYSTEM_ROUTINE SystemRoutine;  //使用者線程為PspUserThreadStartup
    PKSTART_ROUTINE StartRoutine;    //使用者線程為NULL(表示使用公共總入口)
    PVOID StartContext;          //入口參數
    BOOLEAN UserThread;          //标志
} KSTART_FRAME, *PKSTART_FRAME;

//切換幀
typedef struct _KSWITCHFRAME  
{
  PVOID ExceptionList;    //儲存線程切換時的核心she連結清單(不是使用者空間中的seh)
  Union
  {
  BOOLEAN ApcBypassDisable;  //用于首次排程
  UCHAR WaitIrql;        //用于儲存切換時的WaitIrql
  };
      PVOID RetAddr;      //儲存發生切換時的斷點位址(以後切換回來時從這兒繼續執行)
} KSWITCHFRAME, *PKSWITCHFRAME;

//處理器控制塊(核心中的fs寄存器總是指向這個結構體的基址)
Struct KPCR 
{
   KPCR_TIB Tib;
   KPCR* self;        //友善尋址
   KPRCB* Prcb;
   KIRQL irql;        //實體上表示cpu的目前中斷級,邏輯上了解為目前線程的中斷級更好
   USHORT* IDT;        //本cpu的中斷描述符表的位址
   USHORT* GDT;        //本cpu的全局描述符表的位址
   KTSS* TSS;        //本cpu上目前線程的資訊(ESP0)
   …
}

Struct KPCR_TIB
{
   Void* ExceptionList;  //目前線程的核心seh連結清單頭結點位址
   Void* StackBase;    //核心棧底位址
   Void* StackLimit;  //棧的送出邊界
   …
   KPCR_TIB* self;    //友善尋址
}

Struct KPRCB
{
   …
    KTHREAD* CurrentThread;    //本cpu上目前正在運作的線程
    KTHREAD* NextThread;    //将剝奪(即搶占)目前線程的下一個線程
    KTHREAD* IdleThread;    //空轉線程
    BOOL QuantumEnd;      //重要字段。指目前線程的時間片是否已經用完。
    LIST_ENTRY WaitListHead;  //本cpu的等待線程隊列
    ULONG ReadSummary;      //各就緒隊列中是否為空的标志
    ULONG SelectNextLast;
    LIST_ENTRY DispatcherReadyListHead[32];    //對應32個優先級的32個就緒線程隊列
    FX_SAVE_AREA NpxSaveArea;
  …
}

//切換幀(用來儲存切換線程)
typedef struct _KSWITCHFRAME  
{
  PVOID ExceptionList;    //儲存線程切換時的核心she連結清單(不是使用者空間中的seh)
  Union
  {
  BOOLEAN ApcBypassDisable;  //用于首次排程
  UCHAR WaitIrql;        //用于儲存切換時的WaitIrql
  };
  
  //實際上首次時為KiThreadStartup,以後都固定為call KiSwapContextInternal後面的那條指令
    PVOID RetAddr;//儲存發生切換時的斷點位址(以後切換回來時從這兒繼續執行)
} KSWITCHFRAME, *PKSWITCHFRAME;

//Trap現場幀
typedef struct _KTRAP_FRAME 
{
   ------------------這些是KiSystemService儲存的---------------------------
    ULONG DbgEbp;
    ULONG DbgEip;
    ULONG DbgArgMark;
    ULONG DbgArgPointer;
    ULONG TempSegCs;
    ULONG TempEsp;
    ULONG Dr0;
    ULONG Dr1;
    ULONG Dr2;
    ULONG Dr3;
    ULONG Dr6;
    ULONG Dr7;
    ULONG SegGs;
    ULONG SegEs;
    ULONG SegDs;
    ULONG Edx;      //xy 這個位置不是用來儲存edx的,而是用來儲存上個Trap幀,因為Trap幀是可以嵌套的
    ULONG Ecx;      //中斷和異常引起的自陷要儲存eax,系統調用則不需儲存ecx
    ULONG Eax;      //中斷和異常引起的自陷要儲存eax,系統調用則不需儲存eax
    ULONG PreviousPreviousMode;
    struct _EXCEPTION_REGISTRATION_RECORD FAR *ExceptionList;//上次seh連結清單的開頭位址
    ULONG SegFs;
    ULONG Edi;
    ULONG Esi;
    ULONG Ebx;
ULONG Ebp;
----------------------------------------------------------------------------------------
ULONG ErrCode;//發生的不是中斷,而是異常時,cpu還會自動在棧中壓入對應的具體異常碼在這兒
-----------下面5個寄存器是由int 2e内部本身儲存的或KiFastCallEntry模拟儲存的現場---------
    ULONG Eip;
    ULONG SegCs;
    ULONG EFlags;
    ULONG HardwareEsp;
ULONG HardwareSegSs;
---------------以下用于用于儲存V86模式的4個寄存器也是cpu自動壓入的-------------------
    ULONG V86Es;
    ULONG V86Ds;
    ULONG V86Fs;
ULONG V86Gs;
} KTRAP_FRAME, *PKTRAP_FRAME;      

線程排程與切換

每個 cpu 都有一個TSS(任務狀态段),TSS 記錄着目前運作的線程以及該線程的資訊,其中 ESP0 記錄着該線程的核心棧位置,IO權限位圖記錄着目前線程的IO空間權限。

每當一個線程内部,從使用者模式進入核心模式時,需要将cpu中的esp換成該線程的核心棧(各線程的核心棧是不同的)每當進入核心模式時,cpu就自動從TSS中找到ESP0,然後MOV ESP, TSS.ESP0,換成核心棧後,cpu然後在核心棧中壓入浮點寄存器和标準的5個寄存器:原cs、原eip、原ss、原esp、原eflags。

是以說每當切換線程時,就要修改 TSS 中的 ESP0 和 IO權限位圖。

Windows嚴格按優先級排程線程(優先級分成32個),當一個線程得到排程執行時,如果一直沒有任何其他就緒線程的優先級高于本線程,本線程就可以暢通無阻地一直執行下去,直到本次的時間片用完。但是如果本次執行的過程中,如果有個就緒線程的優先級突然高于了本線程,那麼本線程将被搶占,cpu将轉去執行那個線程。但是,這種搶占可能不是立即性的,隻有在目前線程的irql在DISPATCH_LEVEL以下(不包括),才會被立即搶占,否則,推遲搶占(即把那個高優先級的就緒線程暫時記錄到目前cpu的KPCR結構中的NextThread字段中,标記要将搶占)。

總結,線程切換的時機:

1.時間片耗盡

2.被搶占

3.因等待事件、資源、信号時主動放棄cpu(WaitForSingleObject / Sleep)

4.主動切換(SwitchToThread)

總結,線程狀态:

1.Ready就緒态(挂入相應的就緒隊列)

2.某一時刻得到排程變成Running運作态

3.因等待某一事件、信号、資源等變成Waiting等待狀态

4.Standby狀态。指處于搶占者狀态(NextThread就是自己)

5.DeferredReady狀态。指‘将’進入就緒态

程序挂靠

當父程序要建立一個子程序時:會在父程序中調用CreateProcess。這個函數本身是運作在父程序的位址空間中的,但是由它建立了子程序,建立了子程序的位址空間,建立了子程序的PEB。當要初始化子程序的PEB結構時,由于PEB本身位于子程序的位址空間中,如果直接通路PEB那是不對的,那将會映射到不同的實體記憶體。是以必須挂靠到子程序的位址空間中,去讀寫PEB結構體中的值。

程序挂靠的實質工作,就是将cr3寄存器改為目标寄存器的位址空間,這樣,線程的所有有關記憶體的操作,操作的都是目标程序的位址空間。

線程同步

一個線程可以等待一個對象或多個對象,而一個對象也可以同時被N個線程等待,是以線程與等待對象之間是多對多的關系,他們之間的等待關系由一個隊列和一個 等待塊 來控制。

  • 可直接等待對象(存在 DISPATCHER_HEADER),比如:互斥、事件、信号量、程序、線程
  • 可間接等待對象 (不存在 DISPATCHER_HEADER),比如:檔案