程序的位址空間
以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),比如:檔案