本文轉載自匯編語言基礎:寄存器和系統調用
寄存器
寄存器是處理器臨時保存數據指令的的一部分。在x86_64
架構中,寄存器能處理高達64位的數據。這意味着每個寄存器都可以保存該值
沒符號整數:0〜18,446,744,073,709,551,616
有符號整數:-9,223,372,036,854,775,808 - 9,223,372,036,854,775,807
這是x86_64
架構下的所有寄存器的邏輯圖表。
這是我們的寄存器列表,表中有有四列:
- 64位寄存器的列;
- 32位寄存器
- 16位寄存器
- 8位寄存器:
各個寄存器的關系
這里是rax
寄存器的圖,你可以看到我們的一個64位寄存器叫做rax
,還有eax 32
位寄存器,那實際上不是它自己的寄存器,它是rax
寄存器的一半,那么ax
是16位寄存器是eax
寄存器的一半。同理al
寄存器是ax
寄存器的一半。使用al
寄存器就好像是一個8位寄存器,但它是 實際上修改了ax
寄存器的低8位。以上說的並不需要刻意記憶,因為本文的立足點是讓你能夠讀懂別人或C/C++
編譯器轉譯的匯編代碼,你不記得的話,可以收藏本文已被你翻查。
不同數據長度的寄存器之間的關系
系統調用(Syscall)
系統調用是指程序向系統內核請求服務,系統調用因操作系統的不同而不同,因為不同的操作系統使用不同的內核。
-
所有系統調用都有一個與之關聯的ID(一個數字)
-
系統調用的輸入接受一個參數列表。
寄存器參數順序表
這是一個非常重要的表,當你使用系統調用時,它有許多輸入是基於存儲在寄存器中的值,並且是講求順序的,如果你想使用系統調用,你可以將對應系統調用的ID加載到rax
寄存器中 並按上表指定的順序加載參數在你的RAX
寄存器中,如果你在這里還沒有概念的話,可以跳過,往下看完一個示例,回頭看就明白了。
- 第一個參數是
rdi
寄存器, - 第二個參數是
rsi
寄存器, - 第三個參數是
rdx
寄存器 - 第四個參數是
r10
寄存器 - 第五個參數是
r8
寄存器 - 第六個參數是
r9
寄存器.
系統調用列表
我們這里只列出常用系統調用的ID,完整的系統調用ID列表,可以在網上都可以找得到。
系統調用ID示例表
- 正如你所見,文件操作的系統調用是和
Unix/Linux
交互的常用接口.ID列對應的數字和對應syscall
列對應系統調用名稱相關聯。 - 與系統調用相關的還有參數;我這里用顏色標記了,用'#'符號和紅色標記的表明是直接來自寄存器的一個數字,用'$'符號和綠色標記的也是來自寄存器,但它不是真實的值,而是寄存器里面緩存的內存地址所指向的位置保存的值(如果你有C指針的基礎,這就很容易理解了)。
sys_write系統調用示例
前文寫到Hello World
程序將"Hello World"字符串打印到屏幕,已經用到了sys_write
系統調用,我們作為示例來講解一下寄存器和系統調用之間是如何交互的。
sys_write
參數類型
- 第一個參數是文件描述符0代表文件輸入,1代表文件輸出,2代表文件錯誤。如果你只是將文本寫到屏幕,那么就使用標准輸出即可
- 第二個文件描述符是緩存,即字符串輸出到屏幕之前,首先寫入的是高速緩存的位置
- 第三個參數是字符串的長度
看完上面參數表,再回顧一下上面的寄存器ID參數表,看如何使用寄存器ID參數和下表的參數相關聯,其實很簡單,只需要將那些寄存器的值替換下表的參數列名即可。
所以,正如你所見
rax
寄存器應該處理值1(也即是sys_write
系統調用的ID),rdi
寄存器應該處理我們的文件描述符,rsi
寄存器應該處理字符串的緩存對象,rdx
寄存器應該處理字符串的長度.
最后,按照《參數類型表》的描述替換為各列寄存器對應的值,具體分析邏輯如下,這樣就生成我們最后關於Hello World程序關於系統調用的相關指令。
匯編語言常用關鍵字
mov
:表示move
的意思就是移動數據,所以諸如如下語句move rax,1
的意思就是將ID為1的sys_write
系統調用移動寄存器rax
,那么你應該記住,以后遇到類似move rax ID這樣格式的命令就應該記住這是通過寄存器執行某項系統調用
。- "move rsi text":的意思就是從text變量指定的內存位置加載字符串到rsi寄存器
syscall
:每調用move
指令后以syscall
結尾,表示執行一次系統調用,也就是說你執行一個不同的系統調用都需要以syscall
關鍵字結束。- 從上面的兩點理解,應該不難看出
mov rax 60
這條指令是關於系統調用ID等於60(從網上查到是sys_exit),關於告知操作系統,你的程序已經執行完畢,同理,move rdi 0是執行與描述符有關的操作,即告知程序退出的狀態 ,事實上這個狀態碼你可以任意定義的,按照程序設計的一般約定,0代表程序執行正常退出,其他為異常狀態
。 section
:所有x86_64
的匯編文件有三種section
- .
data
:該section下定義的所有變量,在匯編之前,編譯器會完成所有相關變量指向對應內存位置的值的替換。你可以理解為C/C++的變量相關的操作。 .bss
該section,為某些功能的使用而分配內存,和.data
section有些類似.text
該section,主要定義程序運行的邏輯和各種的系統調用。
- .
label
:標簽用於標記代碼的一部分。在編譯時,編譯將計算標簽在內存中的位置。 每次使用標簽的名稱后,該名稱將被編譯器替換為內存中的位置。如果你看看helloword定義text的位置,它基本上是相同的,除了我們可以在位置插入這些標簽。在我們的代碼中將會看到為什么它在將來會有用武之地。- _
start
:標簽對所有程序都至關重要。當您編寫程序並稍后執行時,它首先在“_start”的位置執行。如果鏈接器找不到“_start”標簽,則會拋出錯誤。 global
:當您希望鏈接器能夠知道某個標簽的地址時,使用global關鍵字。 生成的目標文件將包含指向“global”的每個標簽的鏈接。在helloworld這個示例中,我們必須將“_start”聲明為全局,因為代碼必須正確鏈接。
- _