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