天天看點

最簡單的AT&T彙程式設計式

最簡單的AT&T彙程式設計式

把這個程式儲存成檔案start.s(彙程式設計式通常以.s作為檔案名字尾),用彙編器(Assembler)as把彙程式設計式中的助記符翻譯成機器指令,生成目标檔案start.o:

    $as start.s -o start.o

然後用連結器(Linker,或Link Editor)ld把目标檔案start.o連結成可執行檔案start:

    $ld start.o -o start

為什麼用彙編器翻譯成機器指令了還不行,還要有一個連結的步驟呢?

連結主要有兩個作用,一是修改目标檔案中的資訊,對位址做重定位;而是把多個目标檔案合并成一個可執行檔案。雖然上面的程式隻有一個目标檔案,但也需要經過連結才能成為可執行檔案。

現在執行這個程式,它隻做了一件事就是退出,退出狀态是4(在Shell中可以用特殊變量$?得到上一條指令的退出狀态):

    $./start

    $echo $?

    4

是以這段彙編代碼相當于在C程式的main函數中retrun 4;。

下面逐行分析這個彙程式設計式。首先,#号表示單行注釋,類似于C語言的//注釋。

    .section .data

彙程式設計式中以.開頭的名稱并不是指令的助記符,不會被翻譯成機器指令,而是給彙編器一些特殊訓示,稱為彙編訓示(Assembler Directive)或僞操作(Pseudo-operation),由于他不是真正的指令是以加個“僞”字。 .section訓示把代碼劃分成若幹個段(Section),程式被作業系統加載執行時,每個段被加載到不同的位址,作業系統對不同的頁面設定不同的讀、寫、執行權限。 .data段儲存程式的資料,是可讀可寫的,相當于C程式的全局變量。本程式中沒有定義資料,是以.data段是空的。

    .section .text

.text段儲存代碼,是隻讀和可執行的,後面那些指令都屬于.text段。

    .globl _start

_start是一個符号(Symbol),符号在彙程式設計式中代表一個位址,可以用在指令中,彙程式設計式經過彙編器的處理後,所有的符号都被替換成它所代表的位址值。在C語言中我們通過變量名通路一個變量,其實就是讀寫某個位址的記憶體單元,我們通過函數名調用一個函數,其實就是跳到該函數第一條指令所在的位址,是以變量名和函數名都是符号,本質上是代表記憶體位址的。

.globl隻是告訴彙編器,_start這個符号要被連結器用到,是以要在目标檔案的符号表中标記它是一個全局符号。start就像C程式的main函數一樣特殊,是整個程式的入口,連結器在連接配接時會查找目标檔案中的_start符号代表的位址,把它設定為整個程式的入口位址,是以每個彙程式設計式都要提供一個_start符号并且用.globl聲明。如果一個符号沒有用.globl聲明,就表示這個符号不會被連結器用到。

    _start:

這裡定義了_start符号,彙編器在翻譯彙程式設計式是會計算每個資料對象和每條指令的位址,當看到這樣一個符号定義時,就把他後面一條指令的位址作為這個符号所代表的位址。而_start這個符号又比較特殊,它所代表的位址是整個程式的入口位址,是以下一條指令movl $1, %eax就成了程式中第一條被執行的指令。

    movl $1, %eax

這是一條資料傳送指令,這條指令要求CPU内部産生一個數字1并儲存到exa寄存器中。mov的字尾l表示long,說明是32位的傳送指令。這條指令不要求CPU讀記憶體,1這個數是CPU内部産生的,稱為立即數(Immediate)。在彙程式設計式中,立即數前面要加$,寄存器名前面要加%,以便跟符号名區分開。以後我們會看到mov指令還有另外幾種形式,但資料傳送方向是一樣的,第一個操作數總是源操作數,第二個操作數總是目标操作數。

    movl $4, %ebx

和上一條指令類似,生成一個立即數4并儲存到ebx寄存器中。

    int $0x80

前兩條指令都是為這條指令做準備的,執行這條指令時發生以下動作:

    1. int指令稱為軟中斷指令,可以用這條指令故意産生一個異常,異常的處理和中斷類似,CPU從使用者模式切換到特權模式,然後跳轉到核心代碼中執行異常處理程式。

    2. int指令中的立即數0x80是一個參數,在異常處理程式中要根據這個參數決定如何處理,在Linux核心中int $0x80這種異常稱為系統調用(System Call)。核心提供了很多系統服務工使用者程式使用,但這些系統服務不能像庫函數(比如printf())那樣調用,因為在執行使用者程式時CPU處于使用者使用者模式,不能直接調用核心函數,是以需要通過系統調用切換CPU模式,經由異常處理程式進入核心,使用者程式隻能通過寄存器傳幾個參數,之後就要按核心設計好的代碼路線走,而不能由使用者程式随心所欲,想調哪個核心函數就調哪個核心函數,這樣可以保證系統 服務被安全地調用。在調用結束之後,CPU再切換回使用者模式,繼續執行int $0x80的下一條指令,在使用者程式看來就像函數調用和傳回一樣。

    3. eax和ebx的值是傳遞給系統調用的兩個參數。eax的值是系統調用号,Linux的各種系統調用都是由int $0x80指令引發的,核心需要通過eax判斷使用者要調哪個系統調用,exit的系統調用号是1。ebx的訓示傳給exit的參數,表示退出狀态。大多數系統調用完成之後會傳回使用者空間繼續執行後面的指令,而exit系統調用比較特殊,它會終止掉目前程序,而不是傳回使用者空間繼續執行。

繼續閱讀