1. 程式人生 > >20189220 餘超《Linux核心原理與分析》第五週作業

20189220 餘超《Linux核心原理與分析》第五週作業

扒開系統呼叫的三層皮?(上)

第4章的基礎知識

  • Linux系統呼叫的三層機制:xyz()(API函式)、system_call(系統呼叫處理入口) 、 sys_xyz()(系統呼叫核心處理函式)。
  • 32位的X86機器上在使用者態的時候只能訪問0x00000000—0xbfffffff的地址空間,而在核心態下可以訪問所有的地址空間。
  • 中斷,系統呼叫是最常用的兩種方式,從使用者態切換到核心態。
  • int指令觸發中斷機制會在堆疊上儲存一些暫存器的值,會儲存使用者態棧頂地址,當時的狀態字,當時的CS:EIP的值。
  • 中斷髮生後的第一件事就是儲存現場,中斷結束的最後一件事就是恢復現場。
  • 系統呼叫的意義可以把使用者從底層的硬體程式設計中解放出來,極大的提高了系統的安全性,使使用者程式具有可移植性。
  • API(應用程式程式設計介面)是函式定義,一個API可以對應多個系統呼叫,他們之間是多對多的關係。
  • 使用EAX暫存器傳遞一個名為系統呼叫號的引數。
  • Linux作業系統中採用了0和3兩個特權級別,分別對應核心態和使用者態。
  • 在Linux中,系統呼叫是使用者空間訪問核心的惟一手段;除異常和中斷外,它們是核心惟一的合法入口。   而kernel留給使用者層的介面其實就只有一個:軟中斷(int 0x80)。使用者態通過它來陷入核心態,完成系統呼叫。為了方便使用,kernel與使用者層之間又增加了API與一些庫(例如libc庫)來封裝這一過程。因此,目前的資料裡大部分都寫,呼叫一個系統呼叫有三種方法:
           (1)通過 glibc 提供的庫函式
           (2)使用 syscall 函式直接呼叫
           (3)通過 int 0x80指令陷入
  • API/libc與系統呼叫的關係:既然API與libc對軟中斷進行了封裝,那我們先一起看看它們之間的具體關係。一般情況下,應用程式通過應用程式設計介面(API)而不是直接通過系統呼叫來程式設計。這點很重要,因為應用程式使用的這種程式設計介面實際上並不需要和核心提供的系統呼叫一一對應。一個API定義了一組應用程式使用的程式設計介面。從程式設計師的角度看,系統呼叫無關緊要,他們只需要跟API打交道就可以了。相反,核心只跟系統呼叫打交道;庫函式及應用程式是怎麼使用系統呼叫不是核心所關心的。

系統呼叫實現機制

與呼叫函式一樣,系統呼叫也需要輸入輸出引數。每個系統呼叫至少有一個引數,即系統呼叫號(由eax傳遞),其他引數依次由ebx、ecx、edx、esi、edi、ebp傳入。 由於使用暫存器傳遞引數,因此對引數的長度做了限制:
(1)每個引數的長度不能超過暫存器的長度,即32位
(2)在系統呼叫號(eax)之外,引數的個數不能超過6個(ebx、ecx、edx、esi、edi、ebp)如果超過六個,可以傳入一個地址,地址所在地存放多個引數
系統呼叫的三個層次依次是:xyz函式(API)、system_ call(中斷向量)和 sys_ xyz(中斷服務程式)。

庫函式API和嵌入彙編程式碼來觸發一個系統呼叫

1.API函式的方式
首先我選取的是20號的getpid系統呼叫

編譯的結果如下:

2.嵌入式彙編方式

編譯執行完的結果

3.呼叫庫函式syscall

編譯完執行的結果

4.用gdb進行除錯看API的呼叫是否就是像我們之前分析的對系統呼叫的封裝那樣執行的
在getpid處設定斷點,執行到斷點處。

使用ni命令,對彙編指令逐條執行,前面的清理暫存器的值此處先不討論,直接一直ni執行到傳遞系統呼叫號處(箭頭所指的為下一條將要執行的指令, 利用info r命令來檢視當前暫存器的值,系統呼叫號還未傳入,eax還為0)。

ni一下,再次檢視,發現系統呼叫號已經傳入,確實是利用eax的。

再ni一下,此時該系統呼叫的實際工作已經完成了,結果(也就是pid)儲存在eax中。

實驗分析

  • getpid是一種函式,功能是取得程序識別碼。
  • 函式原型:舊的原型為pid_t getpid(void);,推薦使用int _getpid( void );這種形式。注意,函式名第一個字元是下劃線。
  • 函式說明:getpid函式用來取得目前程序的程序ID,許多程式利用取到的此值來建立臨時檔案,以避免臨時檔案相同帶來的問題。
  • 返回值:目前程序的程序ID。
  • 在Linux系統中是通過啟用0x80中斷來觸發系統呼叫的,需要呼叫的系統呼叫號實現賦值給eax儲存器,如果有傳入引數可賦值給ebx暫存器,如果多於1個則按順序賦值給ebx、ecx、edx、esi、edi、ebp,如果超過6個則通過指標變數指向另一片堆疊區,如果無引數傳入則賦值為0。
    彙編程式碼的分析

#include <stdio.h>
#include<unistd.h>
int main()
{
    pid_t pid;
    asm volatile(
        "movl $0,%%ebx\n\t"//將ebx暫存器清零,系統呼叫傳遞第一個引數使用ebx,這裡是null
        "movl $0x14,%%eax\n\t"//將0xd放入eax中,0x14為20,傳遞系統呼叫號20
        "int $0x80\n\t"
        "movl %%eax,$0\n\t"//通過eax這個暫存器返回系統呼叫值,和普通函式一樣
        :"=m"(pid)
    );
    printf("this process's pid is : %u\n",pid);
    return 0;
}

遇到的問題
1.我最開始在做書上給的38號系統呼叫的例子的時候,出現了下面的問題

後面再網上搜索資料可以得知是因為在64位系統下去編譯32位的目標檔案,這樣是非法的。必須用”-m32”強制用32位ABI去編譯,即可編譯通過。

2.我在改寫syscall函式的的時候,出現了下面的問題

最後發現是因為缺少必要的標頭檔案,新增上 1 #include <unistd.h>2 #include <sys/syscall.h>3 #include <sys/types.h>即可

本章總結

  • 在Linux中我們可以通過三種方式也進行系統的呼叫分為:API方式,C程式碼中嵌入式組合語言,和庫函式syscall。API方法實現系統呼叫實現非常便捷,只需知道函式原型即可。
  • 經過這次實驗,我們可以看到:
          (1)要檢視系統資源(如pid等),只有處於核心態(0級)的時候才可以。
           (2)使用者要從使用者態(3級)切換到核心態(0級),就要通過軟中斷(int 0x80)來進行系統呼叫。
           (3)通過eax傳遞系統呼叫號,然後由system_call交給system_service完成工作。
           (4)system_service完成工作後,結果又由eax傳遞給使用者態堆疊。
    我對API系統呼叫的理解:
  • 總之使用者呼叫API函式,系統呼叫號和引數儲存到 eax,ebx ,等暫存器中,通過 0x80 中斷向量觸發中斷陷入核心態,中斷服務程式根據系統呼叫號呼叫並執行對應的系統呼叫函式,執行完畢後將結果存放的 eax 中並返回給程式,程式返回使用者態。