使用庫函數API和C代碼中嵌入彙編代碼兩種方式使用同一個系統調用
《Linux內核分析》MOOC課程:http://mooc.study.163.com/course/USTC-1000029000
第四講 扒開系統調用的三層皮(上)
@2015.03
一、理論知識
所謂的系統調用三層皮,結合下圖解釋如下:
API:第一層是指Libc中定義的API,這些API封裝了系統調用,使用int 0x80觸發一個系統調用中斷;當然,並非所有的API都使用了系統調用,如完成數學加減運算的API就沒有使用系統調用;也有可能某個API使用了多個系統調用;這一層存在的價值就是為應用程序員提供易於使用的API來調用系統調用;
system_call:運行於內核態。system_call是所有系統調用在內核的入口點,在其中的開始處保護用戶態程序執行上下文,結束處恢復用戶態程序執行上下文,在中間根據傳入的系統調用號對應的中斷服務程序;
sys_xyz 系統調用封裝例程:執行具體的系統調用操作,完成用戶的系統調用請求;每個系統調用都對應一個封裝例程;
由上面的分析可以知道,理論上要請求一個系統調用,我們即可以使用Libc提供的API,也可以直接在C中內嵌彙編代碼觸發0x80中斷來完成,這次實驗,我們就用實際的例子來演示這兩種方法使用同一個系統的調用。我們選擇的是比較簡單的系統調用sys_write,這屏幕上打印輸出“hello world”,對應的API就是printf。
實驗環境:實驗樓(http://www.shiyanlou.com/courses/195)
二、方法一:使用API在屏幕上顯示“hello world”
這個其實也是C語言經典的入門程序,源代碼如下
#include "stdio.h"
#include "string.h"
int main()
{
char* msg = "Hello World";
printf("%s", msg);
return 0;
}
不解釋了,大家主要用來和下面的內嵌彙編代碼做個比較就好! 在實驗樓中,打開此次實驗鏈接(http://www.shiyanlou.com/courses/running/731),雙擊Xfce終端,cd Code目錄下,gedit helloworld.c,新建並打開helloworld.c文件,在其中輸入上面的代碼,保存退出;
然後使用下面的指令編譯鏈接程序:
gcc -o helloworld helloworld.c -m32
接著,運行編譯好的程序,
./helloworld
效果如下:
三、方法二:使用C內嵌彙編代碼在屏幕上輸出helloworld
Linux中內嵌彙編代碼的語法,視頻中有詳細介紹,這裡略去,直接給出代碼和註釋如下:
int main()
{
char* msg = "Hello World";
int len = 11;
int result = 0;
__asm__ __volatile__("movl %2, %%edx;\n\r" /*傳入參數:要顯示的字符串長度*/
"movl %1, %%ecx;\n\r" /*傳入參賽:文件描述符(stdout)*/
"movl $1, %%ebx;\n\r" /*傳入參數:要顯示的字符串*/
"movl $4, %%eax;\n\r" /*系統調用號:4 sys_write*/
"int $0x80" /*觸發系統調用中斷*/
:"=m"(result) /*輸出部分:本例並未使用*/
:"m"(msg),"r"(len) /*輸入部分:綁定字符串和字符串長度變量*/
:"%eax");
return 0;
}
使用gedit helloworld_asm.c新建文件,並輸入上面的代碼,使用下面的命令編譯
gcc -o helloworld_asm helloworld_asm.c -m32
使用下面的命令運行
./helloworld_asm
運行效果如下
四、總結
即便是最簡單的程序,也難免要用到諸如輸入、輸出以及退出等操作,而要進行這些操作則需要調用操作系統所提供的服務,也就是系統調用。除非你的程序只完成加減乘除等數學運算,否則將很難避免使用系統調用。在 Linux 平臺下有兩種方式來使用系統調用:利用封裝後的 C 庫(libc)或者通過彙編直接調用。
Linux 下的系統調用是通過中斷(int 0x80)來實現的。在執行 int 80 指令時,寄存器 eax 中存放的是系統調用的功能號,而傳給系統調用的參數則必須按順序放到寄存器 ebx,ecx,edx,esi,edi 中,當系統調用完成之後,返回值可以在寄存器 eax 中獲得。
所有的系統調用功能號都可以在文件 /usr/include/bits/syscall.h 中找到,為了便於使用,它們是用 SYS_
或許你已經發現,在進行系統調用時至多隻有 5 個寄存器能夠用來保存參數,難道所有系統調用的參數個數都不超過 5 嗎?當然不是,例如 mmap 函數就有 6 個參數,這些參數最後都需要傳遞給系統調用 SYS_mmap: void mmap(void start, size_t length, int prot , int flags, int fd, off_t offset); 當一個系統調用所需的參數個數大於 5 時,執行int 0x80 指令時仍需將系統調用功能號保存在寄存器 eax 中,所不同的只是全部參數應該依次放在一塊連續的內存區域裡,同時在寄存器 ebx 中保存指向該內存區域的指針。系統調用完成之後,返回值仍將保存在寄存器 eax 中。
由於只是需要一塊連續的內存區域來保存系統調用的參數,因此完全可以像普通的函數調用一樣使用棧(stack)來傳遞系統調用所需的參數。但要注意一點,Linux 採用的是 C 語言的調用模式,這就意味著所有參數必須以相反的順序進棧,即最後一個參數先入棧,而第一個參數則最後入棧。如果採用棧來傳遞系統調用所需的參數,在執行int 0x80 指令時還應該將棧指針的當前值複製到寄存器 ebx中。