进程线程005 SwapContext函数分析
文章目錄
- 線程切換與TSS
- 內(nèi)核堆棧
- 內(nèi)核堆棧結(jié)構(gòu)
- 調(diào)用API進零環(huán)
- SwapContext代碼分析
- 線程切換與FS
- SwapContext代碼分析
- SwapContext的其他問題
- SwapContext有幾個參數(shù) 怎么判斷出來的?
- SwapContext在哪里實現(xiàn)了線程切換?
- 0環(huán)的ExceptionList是在哪里備份的
線程切換與TSS
SwapContext這個函數(shù)是Windows線程切換的核心,無論是主動切換還是系統(tǒng)時鐘導(dǎo)致的線程切換,最終都會調(diào)用這個函數(shù)
在這個函數(shù)除了切換堆棧以外,還做了很多其他事情,下面就來學(xué)習(xí)一下線程切換與TSS的關(guān)系
內(nèi)核堆棧
每一個線程都有一個內(nèi)核堆棧,當API調(diào)用進零環(huán)的時候,必然要切換堆棧這個堆棧就是當前線程的零環(huán)堆棧。那這個線程零環(huán)的堆棧去哪里找呢?
KTHREAD結(jié)構(gòu)體中有三個成員:InitialStack是當前堆棧的棧底,KernelStack是當前堆棧的棧頂,StackLimit是堆棧的邊界。也就是說如果我們找到了這三個成員也就找到了內(nèi)核堆棧。
內(nèi)核堆棧結(jié)構(gòu)
內(nèi)核堆棧從結(jié)構(gòu)上大體分成兩部分,第一部分從InitialStack開始的0x210個字節(jié)存儲的是當前線程用到的浮點寄存器的值。
從0x210再往后就是Trap_Frame結(jié)構(gòu)體。完整結(jié)構(gòu)如圖:
調(diào)用API進零環(huán)
普通調(diào)用:通過TSS.ESP0得到零環(huán)堆棧
快速調(diào)用:從MSR得到一個臨時的0環(huán)棧,代碼執(zhí)行后仍然通過TSS.ESP0得到當前線程的0環(huán)棧
我們找到KiFastCallEntry的代碼,0FFDFF000的位置是KPCR,首先找到KPCR偏移為0x40的位置TSS,然后再找到TSS偏移4的位置ESP0,把這個值賦給了當前的esp。然后才開始往堆棧里壓入值。
那么問題來了,**TSS中的ESP0來自于哪?**答案在SwapContext的代碼里。
SwapContext代碼分析
Intel設(shè)計TSS的目的是為了任務(wù)切換(線程切換),但Windows與Linux并沒有使用,而是采用堆棧來保存線程的各種寄存器。
那么這里就有一個問題,**一個CPU只有一個TSS,但是線程很多,如何用一個TSS來保存所有線程的ESP0呢?**答案都在SwapContext的代碼里
找到SwapContext中與TSS相關(guān)的代碼,ebx就是當前CPU對應(yīng)的結(jié)構(gòu)體KPCR,通過KPCR找到TSS存到ecx里,
TSS偏移4的位置是ESP0,接著將eax存儲到ESP0,繼續(xù)往上找一下eax的值。
首先將目標線程的棧底存儲到eax
此時eax指向上圖的的InitStack
接著減去0x210
此時eax指向Trap_Frame結(jié)構(gòu)
接著再減去0x10,也就是4個成員
Trap_Frame最底下的四個成員是給虛擬8086模式用的。通過剛才的計算得出,當前的eax指向的是0x078的位置SS
.text:00469B1C mov ecx, [ebx+40h] ; 通過KPCR取出TSS .text:00469B1F mov [ecx+4], eax ; 將Trap_Frame.ESP0存到TSS.ESP0這就是SwapContext函數(shù)對TSS的使用,它會將Trap_Frame.ESP0存到TSS.ESP0。
到這里,解決了之前提出的兩個問題
**TSS中的ESP0來自于哪?**來自于0環(huán)的Tram_Frame結(jié)構(gòu)體
**一個CPU只有一個TSS,但是線程很多,如何用一個TSS來保存所有線程的ESP0呢?**在發(fā)生線程切換的時候,SwapContext會將目標線程的ESP0存到TSS中,然后開始切換線程。就是說TSS永遠存儲的是當前線程的ESP0
TSS中除了ESP0之外還用到了一個值就是CR3,SwapContext會將當前TSS中的CR3修改為目標進程的CR3,然后切換CR3。
下面一行代碼將當前線程的IO權(quán)限位圖存到了TSS里。這個成員在Windows2000以后不用了
**結(jié)論:**Intel提供的TSS在Windows里只有三個成員是有意義的:ESP0 CR3和IO權(quán)限位圖
線程切換與FS
FS:[0]寄存器在3環(huán)的時候指向TEB,進入0環(huán)后FS:[0]指向KPCR。
系統(tǒng)中同時存在很多個線程,這就意味著FS:[0]在3環(huán)的時候指向的TEB要有多個,有一個線程就要有一個TEB,但是在實際使用中我們發(fā)現(xiàn)在3環(huán)查看不同線程的FS寄存器時,FS的段選擇子都是相同的,那么是如何實現(xiàn)通過一個FS寄存器指向多個TEB呢?
答案依然在SwapContext函數(shù)的代碼里。
SwapContext代碼分析
找到SwapContext與TEB相關(guān)的代碼
首先取出目標線程的TEB 放到eax里
.text:00469B67 mov ecx, [ebx+3Ch] ; 通過KPCR找到GDT表接著通過KPCR找到GDT表,存到ecx
.text:00469B6A mov [ecx+3Ah], ax這里的ax是TEB的低16位,
而ecx+3A就是段描述符低4個字節(jié)的16-31位Base Address,也就是將TEB的低16位寫到段描述符的16-31位。
.text:00469B6E shr eax, 10h將eax右移16位,這樣就能得到TEB地址的高16位。因為低16位已經(jīng)寫到段描述符里了。
.text:00469B71 mov [ecx+3Ch], al ; 將低8位寫到段描述符0-7的位置 .text:00469B74 mov [ecx+3Fh], ah ; 將高8位寫到段描述符31-24的位置高16位又分成兩部分寫到段描述符中,先將低8位寫到段描述符0-7的位置,再將高8位寫到段描述符31-24的位置。
通過這幾行代碼就將新的線程的TEB的地址寫到了當前GDT表的段描述符的基址中。
這就回答了剛才的問題:如何實現(xiàn)通過一個FS寄存器指向多個TEB?
因為FS段選擇子雖然沒有發(fā)生變化,但是在線程切換的時候,會修改段選擇子所指向的段描述符的基址為新的線程的TEB的地址。
SwapContext的其他問題
SwapContext有幾個參數(shù) 怎么判斷出來的?
四個參數(shù),但真正有用的只有三個,分別是:
- esi:當前線程結(jié)構(gòu)體ETHREAD指針
- edi:要切換的線程結(jié)構(gòu)體ETHREAD指針
- ebx:KPCR
首先找到SwapContext的父函數(shù)KiSwapContext
0FFDFF01Ch存儲的就是KPCR,所以參數(shù)ebx就是KPCR
.text:004699CE mov edi, [ebx+124h] ; KPCR+0x124是當前線程的KTHREAD結(jié)構(gòu)體ebx是KPCR,KPCR+0x124的位置就是當前線程的KTHREAD結(jié)構(gòu)體
.text:004699CC mov esi, ecx ; ecx是上一層調(diào)用的函數(shù)傳進來的 是要切換線程的KTHREAD而esi來自于ecx,ecx是上一層調(diào)用的函數(shù)傳進來的 是要切換線程的KTHREAD
找到上一層的函數(shù),ecx來自于eax,是KiFindReadyThread的返回值,這個函數(shù)會查找一個就緒線程返回KTHREAD結(jié)構(gòu)體
SwapContext在哪里實現(xiàn)了線程切換?
線程切換的本質(zhì)就是切換堆棧
這行代碼將目標線程的KernelStack存到ESP里,這行代碼以后另一個線程復(fù)活
0環(huán)的ExceptionList是在哪里備份的
這行代碼會將ExceptionList存儲到ecx,然后將ExceptionList保存到堆棧
總結(jié)
以上是生活随笔為你收集整理的进程线程005 SwapContext函数分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 进程线程004 Windows线程切换的
- 下一篇: 进程线程007 进程挂靠与跨进程读写内存