MOOCOS李志軍——L5系統調用的實現

L5系統調用的實現

實現一個whoami系統調用

爲何不能直接訪問——操做系統安全

目標:用戶程序調用whoami,一個字符串「lizhijun」放在內核中 (系統引導時載入),利用系統調用打印該字符串安全

首先不能隨意jmp到內核代碼或取用內核中的字符串。函數

由於要防止操做系統數據泄露。操做系統中的數據可能有:操作系統

  • root用戶密碼
  • word文檔寫入時數據也會通過操做系統,某段時間在操做系統中存儲,若是其餘用戶程序能取得這個數據,那麼會形成數據泄露

如何實現這種限制——內核(用戶)態,內核(用戶)段

那麼如何實現這種限制呢?設計

經過區分程序執行在用戶態仍是內核態,隔離內核態和用戶態的代碼和數據。3d

這種限制經過爲段設置特權級實現。指針

具體來講,訪問時,經過硬件比較CPL(當前的特權級)和DPL(目標段的特權級)code

DPL:標識一個段的特權級blog

CPL:標識正在執行的程序的特權級文檔

用戶程序(代碼+數據)位於用戶段,執行在用戶態(CPL=3 , DPL=3)字符串

內核程序位於內核段,執行在內核態(CPL=0 ,DPL=0)

用戶程序位於用戶段(CPL=3),調用系統調用進入內核(CPL=0),系統調用的具體代碼位於內核段。

CS的最低兩位(0,1,2,3)表示當前的特權級(0內核態/3用戶態)

系統初始化時(即head.s執行的時候),會創建GDT表項,表項全部的DPL都是0,這些表項對應的段是內核段。

那若是想進入內核,如何進入?答:硬件提供了「主動進入內核的方法」——中斷(int 0x80)

只有中斷能進入內核,並且不是全部中斷都能進入內核

以用戶程序調用printf()的過程爲例說明系統調用的過程

//一、應用程序調用的printf
printf(格式化輸入){

    //C庫函數printf,負責把參數轉換爲庫函數write()須要的格式
    printf(write須要格式的參數);

}
//二、C庫函數printf
printf(write須要格式的參數){
    //writeC庫函數
    write(write須要格式的參數);
}
//三、C庫函數write
write(write須要格式的參數){
    //中斷代碼,int 0x80
    int 0x80
}
//四、int 0x80調用特定中斷處理程序,即系統調用write()
//系統調用write,位於內核區
write(){
    ...
}

關於庫函數write的具體實現

  1. 宏展開

write.c中的_syscall3()按照unistd.h中定義的格式,將參數一次填入表示爲

int write(int fd,const char *buf,off_t count)

但注意只有這個宏定義只適用於3個參數的。

  1. 內聯彙編

「int 0x80」這一句表示嵌入的彙編代碼

**「=a」(__res)** 這一句表示彙編向C的輸出,其中a爲eax,這句的意思爲,將eax置給__res。因爲eax存放的是返回值,因此表示返回值置給 _res

**""(__NR_##name) **這一句表示C向彙編的輸入,「」若是裏邊沒有東西,則代表默認和輸出時選擇的同樣(eax)。__NR_##name中將name替換爲write。這一句表示將__NR_write輸入到eax,。

"b"((long)(a)) 同上,「b」"c"d"分別表示eax,ebx,ecx。((long)(a))中的a表示第一個參數。

總結

  • 「」內的東西是有關彙編的。()裏的東西是有關C語言的

  • 內聯彙編的執行過程是,先輸入,而後執行"int 0x80",執行結束後輸出
  • __NR_write這個值表明的是系統調用號用它找到系統調用write函數(做爲中斷處理函數),系統調用write()纔算進入內核。以後返回int 0x80後的語句,從內核返回用戶態。

  1. **後邊的if語句以及前面的long __res;就是簡單的C語言**

關於int 0x80中斷

[](https://img2018.cnblogs.com/blog/1735814/201910/1735814-20191029200013610-1343595035.png

  1. sched_init(void)是系統初始化執行的函數

  2. set_system_gate(0x80,&system_call);用於設置中斷處理門(IDT中的每一個表項就對應一箇中斷處理門),將中斷0x80交給system_call()處理

    具體實現分析

    • **_set_gate(&idt[n],15,3,addr)**經過宏定義

      **#define _set_gate(gate_addr,type,dp1,addr)**展開

      • gate_addr=&idt[n]表示idt是中斷向量表基址(是個全局變量)

      • idt[n]找到IDT中0x80對應的表項

      • type=15表示

      • dpl=3表示中斷向量表

      • 「a」(0x0008 0000)的做用是最後截取0x0008 0000的高16位放入段選擇符,即段選擇符爲8。

      • addr爲中斷處理函數入口的偏移地址

      • "movl %%eax,%1\n\t"%1表示C向彙編輸入的第2個變量即*((char*)(gate_addr))。完成了將addr的低4位放入eax。

        同理"movl %%edx,%2"將高4位放入edx。剩下的細節不講,最終實現將addr組裝至IDT表中

    總結

    • DPL=3的做用: 中斷描述符表中int0x80中斷的DPL=3,使得用戶態下的程序能夠進入。根據段選擇符和中斷處理函數入口的偏移地址能夠跳轉到中斷函數入口
    • 這裏注意段選擇符爲8,即0000 0000 0000 1000,末兩位爲00,DPL=0,跳轉後的CPL=0,經過這種方式進入內核態,以後執行中斷處理函數。
    • 中斷返回後CS最後兩位又會變成3,回到用戶態。後面再具體講
  3. 中斷處理函數system_call

    • movl $0x10,%edx mov %dx,%ds mov %dx,%es 用於將ds和es都置爲0x10,將數據段的段選擇符置爲0x10,數據段也在內核態中

      • **call _sys_call_table(,%eax,4)** 這句進入系統調用處理函數。

        這句中,%eax內是__NR_write。

        **_sys_call_table+4*%eax**就是相應的系統調用處理函數(sys_wirte)的入口 。__NR_write至關於系統調用的編號。*4是由於每一個系統調用佔4個字節。

        _sys_call_table是一個函數指針表。

    總結

    • system_call調用系統調用處理函數sys_write,sys_write纔是真正執行打印內核數據的函數。具體是怎麼實現的設計到操做系統IO,後面再講。
    • 進入system_call後纔算進入內核態

大總結

  1. 程序用戶態不能直接訪問內核態。

  2. printf的過程

庫函數:printf。包含int 0x80調用中斷。

中斷處理函數system_call。調用系統調用處理函數sys_write

系統調用處理函數sys_write。執行真正的對內核數據的操做。

  1. 實現用戶態進入內核態的關鍵

    查IDT調用中斷處理程序時,經過IDT中DPL=3,且跳轉到段選擇符爲8的內核段,讓程序得以從用戶態進入內核態。

相關文章
相關標籤/搜索