跳轉到內容

x86 彙編/與 Linux 互動

來自華夏公益教科書

系統呼叫

[編輯 | 編輯原始碼]

系統呼叫是使用者程式和 Linux 核心之間的介面。它們用於讓核心執行各種系統任務,例如檔案訪問、程序管理和網路。在 C 程式語言中,您通常會呼叫一個包裝函式來執行所有必需的步驟,甚至使用高階功能,例如標準 IO 庫。

在 Linux 上,有幾種方法可以發出系統呼叫。此頁面將重點介紹透過使用 int $0x80syscall 呼叫軟體中斷來發出系統呼叫。這是一種在純彙編程式中發出系統呼叫的簡單直觀的方法。

發出系統呼叫

[編輯 | 編輯原始碼]

為了透過中斷髮出系統呼叫,您必須透過將所有必需的資訊複製到 GPRs 中來傳遞它們給核心。

每個系統呼叫都有一個固定的編號。Linux 永久保證向後相容性,因此一旦一個編號被分配給一個系統呼叫,它就不會再改變。永遠。

Warning int $0x80syscall 的編號不同!

您可以透過將編號寫入 eax/rax 暫存器來指定系統呼叫。

大多數系統呼叫都採用引數來執行其任務。這些引數透過在發出實際呼叫之前將它們寫入相應的暫存器來傳遞。每個引數索引都有一個特定的暫存器。請檢視小節中的表格,因為 int $0x80syscall 之間的對映不同。引數按它們在相應 C 包裝函式的函式簽名中出現的順序傳遞。您可以在每個 Linux ABI 文件中找到系統呼叫函式及其簽名,例如參考手冊(鍵入 man 2 open 檢視 open 系統呼叫的簽名)。

在一切設定正確後,您可以使用 int $0x80syscall 呼叫中斷,核心將執行任務。

系統呼叫的返回值/錯誤值將寫入 eax/rax

核心使用自己的堆疊來執行操作。使用者堆疊不會以任何方式被觸碰。

透過中斷

[編輯 | 編輯原始碼]

在 Linux x86 和 Linux x86_64 系統上,您可以透過使用 int 指令呼叫中斷 $0x80 來發出系統呼叫。引數透過設定如下通用暫存器來傳遞

使用 int $0x80 發出系統呼叫的暫存器對映
系統呼叫編號 第一個引數 第二個引數 第三個引數 第四個引數 第五個引數 第六個引數 結果
eax ebx ecx edx esi edi ebp eax

系統呼叫編號在 Linux 生成的檔案 $build/‌arch/‌x86/‌include/‌generated/‌uapi/‌asm/‌unistd_32.h$build/‌usr/‌include/‌asm/‌unistd_32.h 中描述。後者也可能存在於您的 Linux 系統中,只需省略 $build

在使用 int $0x80 發出系統呼叫期間,所有暫存器都會被保留,除了 eax,返回值將儲存在那裡。

透過專用系統呼叫指令

[編輯 | 編輯原始碼]

x86_64 架構引入了一條專用指令來發出系統呼叫。它不訪問中斷描述符表,並且速度更快。引數透過設定如下 GPRs 來傳遞

使用 syscall 發出系統呼叫的暫存器對映
系統呼叫編號 第一個引數 第二個引數 第三個引數 第四個引數 第五個引數 第六個引數 結果
rax rdi rsi rdx r10 r8 r9 rax

系統呼叫編號在 Linux 生成的檔案 $build/‌usr/‌include/‌asm/‌unistd_64.h 中描述。此檔案也可能存在於您的 Linux 系統中,只需省略 $build

在使用 syscall 發出系統呼叫期間,所有暫存器都會被保留,除了 rcxr11(以及返回值 rax)。

為了實現最大相容性,在 64 位平臺上,Linux 會剪裁使用中斷方法的系統呼叫的輸入輸出。這意味著,例如,您不能在使用 int $0x80 方法的 x86-64 平臺上傳遞或接收(完整的)64 位地址指標,因為所有引數和結果的高 32 位將被清零。這通常與 syscall 的一般偏好一致,因為它比中斷更快。

庫呼叫

[編輯 | 編輯原始碼]

在 x86-64 Linux 的 C 庫函式呼叫中,引數 6 在 r9 上傳遞,後續引數在堆疊上傳遞(以相反順序)。

庫呼叫暫存器對映
第一個引數 第二個引數 第三個引數 第四個引數 第五個引數 第六個引數
rdi rsi rdx rcx r8 r9

呼叫者可以預期在 rax 暫存器中找到子例程的返回值。

為了總結和澄清資訊,讓我們來看一個非常簡單的例子:hello world 程式。它將使用 write 系統呼叫將文字 "Hello World" 寫入標準輸出,並使用 _exit 系統呼叫退出程式。

系統呼叫簽名

ssize_t write(int fd, const void *buf, size_t count);
void _exit(int status);

這是在下面彙編中實現的 C 程式

#include <unistd.h>

int main(int argc, char *argv[])
{
    write(1, "Hello World\n", 12); /* write "Hello World" to stdout */
    _exit(0);                      /* exit with error code 0 (no error) */
}

兩個示例的開頭都一樣:一個儲存在資料段中的字串和作為全域性符號的 _start

.data
msg: .ascii "Hello World\n"

.text
.global _start

int $0x80

[編輯 | 編輯原始碼]

$build/usr/include/asm/unistd_32.h 中定義的那樣,write_exit 的系統呼叫號為

#define __NR_exit 1
#define __NR_write 4

引數的傳遞方式與在 C 程式中一樣,使用正確的暫存器。設定好一切後,使用 int $0x80 進行系統呼叫。

_start:
    movl $4, %eax   ; use the `write` [interrupt-flavor] system call
    movl $1, %ebx   ; write to stdout
    movl $msg, %ecx ; use string "Hello World"
    movl $12, %edx  ; write 12 characters
    int $0x80       ; make system call
    
    movl $1, %eax   ; use the `_exit` [interrupt-flavor] system call
    movl $0, %ebx   ; error code 0
    int $0x80       ; make system call

系統呼叫

[編輯 | 編輯原始碼]

$build/usr/include/asm/unistd_64.h 中,系統呼叫號定義如下

#define __NR_write 1
#define __NR_exit 60

引數的傳遞方式與 int $0x80 示例中相同,只是暫存器的順序不同。系統呼叫使用 syscall 完成。

_start:
    movq $1, %rax   ; use the `write` [fast] syscall
    movq $1, %rdi   ; write to stdout
    movq $msg, %rsi ; use string "Hello World"
    movq $12, %rdx  ; write 12 characters
    syscall         ; make syscall
    
    movq $60, %rax  ; use the `_exit` [fast] syscall
    movq $0, %rdi   ; error code 0
    syscall         ; make syscall

庫呼叫

[編輯 | 編輯原始碼]

這是一個示例庫函式的 C 原型。

Window XCreateWindow(display, parent, x, y, width, height, border_width, depth,
                       class, visual, valuemask, attributes)

引數的傳遞方式與 int $0x80 示例中相同,只是暫存器的順序不同。

庫函式在原始檔開頭宣告(庫路徑在編譯連結時宣告)。

extern XCreateWindow
		mov rdi, [xserver_pdisplay]
		mov rsi, [xwin_parent]
		mov rdx, [xwin_x]
		mov rcx, [xwin_y]
		mov r8, [xwin_width]
		mov r9, [xwin_height]
		mov rax, attributes
		push rax				; ARG 12
		sub rax, rax
		mov eax, [xwin_valuemask]
		push rax				; ARG 11
		mov rax, [xwin_visual]
		push rax				; ARG 10
		mov rax, [xwin_class]
		push rax				; ARG 9
		mov rax, [xwin_depth]
		push rax				; ARG 8
		mov rax, [xwin_border_width]
		push rax				; ARG 7
		call XCreateWindow
		mov [xwin_window], rax

注意函式的最後幾個引數,它們以相反的順序被壓入堆疊。

華夏公益教科書