Linux系統調用實現原理舉例
Linux系統調用的原理很簡單:將系統調用號放在eax寄存器,將參數放在相關寄存器,然後通過int指令產生一個軟中斷便進入內核。從內核返回後,eax寄存器裏面存放的是返回值。以下針對常用的x86以及x86_64體系結構,用sys_write系統調用進行舉例說明:
x86版本
這個例子利用sys_write系統調用往標準輸出輸出一個"Hello world!"字符串。x86下,sys_write系統調用號是4,需要的參數分別是文件描述符,字符串地址,還有字符串長度,依次放在ebx、ecx以及edx寄存器。通過int指令產生一個軟中斷,從而進入內核。內核處理完系統調用後,將返回值放在eax寄存器。例子中實現了一個myprint函數,該函數實現了參數準備,激發系統調用以及結果處理等邏輯,功能層次跟libc類似。x86系統調用號請參看:
點擊打開鏈接。
#include <stdint.h> // ① 包含幾個頭文件,獲得64位變量定義,strlen定義,以及errno定義等基本功能
#include <string.h>
#include <errno.h>
int myprint(char *msg)
{
uint32_t syswrite_no=4, ret; // ② 參數聲明並初始化
uint32_t write_fd=1, msg_len=strlen(msg);
asm("int $0x80;" // ③ 準備參數(設置相關寄存器),int指令產生一個0x80軟中斷,進入內核
:"=a"(ret)
:"a"(syswrite_no), "b"(write_fd), "c"(msg), "d"(msg_len));
if (ret < 0)
{
errno = -ret; // ④ 如果系統調用出錯,設置errno,返回-1
return -1;
}
return 0;
}
int main(int argc, char *argv[])
{
char *msg = "Hello world!\n"; // ⑤ 準備參數,然後調用myprint,跟調用printf參不多
if (myprint(msg) == -1)
perror("myprint");
return 0;
}
說明:asm第一個冒號表示輸出寄存器,最後把eax寄存器的值輸出到ret變量;第二個冒號表示輸入寄存器,把syswrite_no、write_fd、msg,以及msg_len變量的值分別放到eax,ebx,ecx,以及edx寄存器。這裏由於x86的通用寄存器有限,就沒有進行展開,下面x86_64的例子有做展開。
x86_64版本
在x86_64機器下,跟x86差不多,只有一點小差別。系統調用號放在rax寄存器,參數依次放在rdi,rsi和rdx等寄存器裏面,最後用syscall指令進入內核。注意到,應該使用64位寄存器。從內核返回後,返回值就在rax寄存器裏面。再次注意,不能用int 0x80指令進入內核,x86_64下是使用一條更專用的指令:syscall。系統調用號跟x86下也有區別,具體請看這裏:
點擊打開鏈接。
#include <stdint.h> // ① 包含幾個頭文件,獲得64位變量定義,strlen定義,以及errno定義等基本功能
#include <string.h>
#include <errno.h>
int myprint(char *msg)
{
uint64_t syswrite_no=1, ret; // ② 參數聲明並初始化
uint64_t write_fd=1, msg_len = strlen(msg);
asm("movq %1, %%rax;" // ③ 準備參數(設置相關寄存器),syscall指令進行系統調用
"movq %2, %%rdi;"
"movq %3, %%rsi;"
"movq %4, %%rdx;"
"syscall;"
"movq %%rax, %0;"
: "=r"(ret)
: "r"(syswrite_no), "r"(write_fd), "r"(msg), "r"(msg_len)
: "%rax", "%rdi", "%rsi", "%rdx");
if (ret < 0)
{
errno = -ret; // ④ 如果系統調用出錯,設置errno,返回-1
return -1;
}
return 0;
}
int main(int argc, char *argv[])
{
char *msg = "Hello world!\n"; // ⑤ 準備參數,然後調用myprint,跟調用printf參不多
if (myprint(msg) == -1)
perror("myprint");
return 0;
}