第四周 扒開系統調用的三層皮(上)

  • 用戶態,內核態和中斷

和系統調用打交道的方式:經過庫函數,把系統調用給封裝起來ubuntu

用戶態vs內核態:安全

      通常現代CPU都有幾種不一樣的指令執行級別數據結構

      在高級別的狀態下,代碼能夠執行特權指令,訪問任意的物理地址,這種CPU執行級別對應着內核態函數

      在相應的低級別執行狀態下,代碼的掌控範圍會受到限制,只能在對應級別容許的範圍內活動ui

      爲何有權限級別的劃分:爲了防止系統崩潰以及惡意代碼的入侵,經過劃分權限級別來讓系統更穩定spa

      舉例:Intel x86 CPU有四種不一樣的執行級別0-3,Linux只使用了其中的0級和3級分別來表示內核態和用戶態操作系統

      區分:在Linux中,地址空間是一個顯著的標誌,0xc0000000以上的地址空間只能在內核態下訪問,0x00000000-0xbfffffff的地址空間在兩種狀態下均可以訪問3d

             (地址空間指的是邏輯地址而不是物理地址,邏輯地址:進程的地址空間裏邊的)指針

中斷處理是從用戶態進入內核態主要的方式code

系統調用只是一種特殊的中斷

當用戶態切換到內核態時,必須保存用戶態的寄存器上下文

中斷/int指令會在堆棧上保存一些寄存器的值,如:用戶態棧頂地址,當時的狀態字,當時的cs:eip的值

中斷髮生後第一件事:保存現場(進入中斷程序,保存須要用到的寄存器的數據)

SAVE_ALL:把其餘的寄存器的值給push到內核堆棧裏邊去

中斷處理結束前最後一件事:恢復現場(退出中斷程序,恢復保存寄存器的數據)

RESTOTRE_ALL:把用戶態保存的寄存器再popl出來

Iret指令與中斷信號(包括int指令)發生時的CPU作的動做相反

  • 系統調用概述

系統調用概述:

      系統調用的意義

     

      API和系統調用

     

     

      應用程序,封裝程序,系統調用處理程序及系統調用服務例程之間的關係

     

系統調用的三層皮: xyz(API), system_call(中斷向量對應的中斷服務程序), sys_xyz (不一樣種類的服務程序)

系統調用的服務歷程:

     

系統調用的參數傳遞方法:

      系統調用也須要輸入輸出參數,例如

            實際的值

            用戶態進程地址空間的變量的地址

            包含指向用戶態函數的指針的數據結構的地址

           

  • 使用庫函數API和C代碼中嵌入彙編代碼觸發同一個系統調用

使用庫函數API獲取當前系統時間

   代碼:  

time.c
#include <stdio.h>
#include <time.h>
int main()
{
    time_t tt;//int型數值
    struct tm *t;
    tt = time(NULL);
    t = localtime(&tt);//強制類型轉換,便於輸出
    printf("time:%d:%d:%d:%d:%d:%d:\n",t->tm_year+1960,t->tm_mon,t->tm_mda,t->tm_hour,t->tm_min,t->tm_sec);
    return 0;
}

   經過gcc time.c -o time -m32編譯後打印出當前系統的時間

用匯編方式處罰系統調用獲取當前系統時間

   代碼:

time_asm.c
#include <stdio.h>
#include <time.h>
int main()
{
    time_t tt;//int型數值
    struct tm *t;
    asm volatile(
        "mov $0,%%ebx\n\t"//系統調用傳遞第一個參數使用ebx,這裏是null
        "mov $0xd,%%eax\n\t"//傳遞系統調用號13(16進制即0xd)
        "int $0x80\n\t"
        "mov %%eax,$0\n\t"//經過eax這個寄存器返回系統調用值,和普通函數同樣
        :"=m"(tt)
    );
    t = localtime(&tt);
    printf("time:%d:%d:%d:%d:%d:%d:\n",t->tm_year+1960,t->tm_mon,t->tm_mda,t->tm_hour,t->tm_min,t->tm_sec);
    return 0;
}

      系統調用返回值使用eax存儲

  • 實驗:使用庫函數API和C代碼中嵌入彙編代碼兩種方式使用同一個系統調用

選擇24號和47號系統調用,分別獲取當前用戶uid(用戶ID)和gid(組ID),即模擬Linux系統「id」命令。

編寫兩段代碼,分別使用庫函數API和C代碼中嵌入彙編代碼,源碼以下:

uidgid.c(使用庫函數API方式):

程序中經過調用getuid()和getgid()函數來獲取當前執行用戶uid和gid

uidgid_asm.c(使用C代碼中嵌入彙編代碼方式):

內嵌彙編代碼版本源碼中將原來兩行經過API函數獲取uid和gid的代碼註釋掉,用匯編代碼替換。

首先將ebx寄存器清零,表示無參數傳入。

而後分別將0x18和0x2f(十進制24和47)賦值給eax寄存器,表示須要調用的系統調用號,24爲getuid,47爲getgid。

執行int 0x80來執行系統調用。

以後eax寄存器保存了返回值,將它分別賦值給輸出uid或gid變量。

完成整個彙編代碼的系統調用。

分別編譯兩個源碼文件:

分別執行系統id命令以及兩個編譯好的程序:

上面的截圖分別表示普通用戶ubuntu和管理員用戶root分別執行系統自帶命令id,庫函數API方式uidgid,內嵌彙編方式uidgid_asm這三種方式運行獲得的結果是同樣的。

經過實驗執行結果可知,程序成功完成了系統調用獲取當前用戶uid和gid的操做,經過內嵌彙編代碼能夠清晰的看出調用系統調用的工做過程。

首先將ebx寄存器清零,表示無參數傳入。

而後分別將0x18和0x2f(十進制24和47)賦值給eax寄存器,表示須要調用的系統調用號,24爲getuid,47爲getgid。

執行int 0x80來執行系統調用。

以後eax寄存器保存了返回值,將它分別賦值給輸出uid或gid變量。

完成整個彙編代碼的系統調用。

在Linux系統中是經過激活0x80中斷來觸發系統調用的,須要調用的系統調用號實現賦值給eax存儲器,若是有傳入參數可賦值給ebx寄存器,若是多於1個則按順序賦值給ebx、ecx、edx、esi、edi、ebp,若是超過6個則經過指針變量指向另外一片堆棧區,若是無參數傳入則賦值爲0。

  • 總結

雖然Intel X86 CPU有4種執行級別0~3,可是在Linux系統中僅使用了0和3級,分別表示內核態和用戶態。

一些涉及底層、硬件、核心的操做必須在內核態下才容許執行,爲操做系統程序和驅動程序專享,普通程序僅能執行在用戶態下。若是普通程序須要涉及內核態的操做,就須要經過系統調用來實現。這樣作的好處是屏蔽平臺相關操做下降了軟件開發難度,加強了系統安全性,使程序具備更好的移植性(Linux系統及其餘Unix系統遵循統一標準,系統調用基本同樣)。

相關文章
相關標籤/搜索