1. 程式人生 > >程式語言的底層描述(1)——彙編基礎概念的開始之入門

程式語言的底層描述(1)——彙編基礎概念的開始之入門

該主題預設在類unix作業系統中進行討論

gcc編譯器可通過不同的引數,生成不同階段的編譯檔案,比如,我們想生成可執行檔案,就用gcc -o;要生成目的碼.o,就用gcc -c;要生成彙編程式碼.s,就用gcc -S,

程式編譯的順序是,編譯器先根據.c原始檔產生.s彙編檔案,然後彙編器會把.s轉換成目標檔案.o,最後由連結器將.o檔案與unix庫函式合併,產生可執行檔案。

當我們得到.o時,還可以用objdump -d  *.o,對目標檔案進行反彙編,螢幕會打印出十六進位制與彙編檔案的對應解釋。

以上說的都是操作方法,接下來的幾章我們將淺析組合語言的內幕。

首先來看組合語言的基礎知識

一:資料格式

由於計算機經歷從16位機擴充套件到32位機的過程,intel沿用的以前的屬於,即將16位(即2位元組)定義為“字”,32位定義為“雙字”。而8位仍成為“位元組”,用符號表示如下:

        字:w   16位

    雙字:l     32位

    位元組:b    8位

單精度:s    8位

雙精度:l     32位

組合語言在操作上沿用了這樣的命名方式,比如資料傳送命令movl,我們就能看出這是對32位4位元組的操作

二:暫存器

組合語言簡單說就是遊走於暫存器、儲存器之間進行運算和操作的語言,因此暫存器的概念是至關重要的。

下面是整數暫存器示意圖:

31                                         15                                 7                             0

%eax                           %ax
%ah %al
%ecx                           %cx
%ch %cl
%edx                           %dx
%dh %dl
%ebx                           %bx
%bh %bl
%esi                           %si
%edi                           %di
%esp                         %sp 棧指標
%ebp                         %bp 幀指標

從示意圖來看,八個暫存器中,每個暫存器都是32位,每個暫存器都有直接引用前16位的暫存器標識,比如%esi的前16位暫存器可以用%si來直接訪問。同樣的道理,%bh、%bl還可以直接訪問%bx的高八位和第八位。

還有,%esp預設是棧指標,%ebp是幀指標,這些在後面講還有論述。

三:運算元指示符

常見運算元指示符有$、Imm、(),s

  • $後面跟一個整數,表示“立即數”,類似C語言裡的常量數,如我們要把一個5賦給一個暫存器, 在語句中用$5表示;
  • Imm稱為立即數偏移,顧名思義,是在定址時進行地址偏移的
  • ()類似間接定址,類似C語言裡的“*”
  • s伸縮因子,必須是1、2、4、8

通過下面這個例子方便理解:

假設下面五個地址代表的儲存器中分別儲存這五個值:

地址
0x100 0xFF
0x104 0xAB
0x108 0x13
0x10C 0x11
 

一下是三個暫存器所儲存的值:

暫存器
%eax 0x100
%ecx 0x1
%edx 0x3

當彙編語句中引用如下運算元所表示的值、對應的暫存器、以及分析如下:

運算元 註釋 備註
%eax  0x100  %eax 呼叫暫存器,直接出暫存器內的值
0x104 0xAB 絕對地址0x104 儲存器的絕對地址,直接出地址內的值
$0x108 0x108 立即數 相當於常量
(%eax) 0xFF 絕對地址0x100 間接定址,通過%eax引用地址0x100,得到值
4(%eax) 0xAB 絕對地址0x104 間接定址,%eax值偏置4引用地址0x104,得值
9(%eax,%edx) 0x11 絕對地址0x10C 間接定址,%eax、%edx值相加,再偏置9
260(%ecx,%edx) 0x13 絕對地址0x108 同上
0xFC(,%ecx,4) 0xFF 絕對地址0xFF 間接定址,%ecx值伸縮4倍(0x1*4),再偏置0xFC
(%eax,%edx,4) 0x11 絕對地址0x11 兩個例子的結合

倒數第二個例子比較有意思,很容易看成0xFC+4=0xFF,算數沒過關吧,嚯嚯,正確的應該是0xFC+4=0x100,而地址0x100裡面存的是0xFF,哇咔咔。

組合語言指令基礎

一:資料傳送指令mov

注意,組合語言的賦值語句順序和C語言是相反的。

mov實質上有兩種操作,一種是值傳送,比如movl  $4, %eax,意思是把4這個數值傳送(覆蓋)給暫存器eax,也可以mov %edx, %eax實現暫存器之間的數值傳遞

另一種是將物件理解成地址,並根據這個地址找到相應的儲存器位置,讀出裡面的值並傳送,即間接定址 。還是以上面儲存器中的值為例,執行movl:

movl  (%eax), %ecx    //  間接定址,先獲取暫存器eax裡的值0x100,得到儲存器0x100裡的值0xFF。再賦給暫存器ecx

movl  4(%eax), %ecx  //同上,先獲取4+0x100=0x104的值,再到儲存器0x104中取出0xAB,最後賦值給ecx

下面的例子是複雜的間接定址,可以參照上面的表格分析事例:

movl  257(%ecx, %edx, 2) , %ecx  //  257 + 0x1 + 0x3*2 = 0x108,然後將0x108中的值0x13賦給暫存器ecx

通過上面兩條語句,暫存器ecx裡的值先賦成0xFF,再賦成0xAB,最後覆蓋成0x13。

二:棧指標%esp和幀指標%ebp

指令pushl %ebp,是壓棧,將%ebp裡的值壓入棧中,而棧指標是%esp,即棧頂,於是該語句等價於

subl  $4, %esp    //將棧指標的值減4——棧底是高地址,逐個往低地址擴充套件,所以要減4

movl %ebp, (%esp)    //將%ebp裡的值,賦給新的棧指標中的地址所表示的儲存空間

指令pop %ebp,是出棧,將%esp裡的地址對應的空間中的值賦值給%ebp,等價於這樣兩條指令:

movl (%esp), %ebp

addl  $4, %esp

……彙編裡(暫存器)的 操作只有兩種可能,將暫存器裡的值理解成儲存空間後,要麼往空間裡塞資料,要麼從空間裡把資料取出來……

%ebp和%eax兩個暫存器常被用於函式操作,比如將如下C程式碼對應到彙編程式碼:

int exchange(int *xp, int y)    // movl  8(%ebp), %eax ——%ebp偏置8得到第一個形參xp,存放在%eax

{                                  // movl  12(%ebp), %edx ——%ebp偏置12得到第二個形參y,存放在%edx

    int x = *xp;             // movl  (%eax), %ecx ——%eax儲存的地址儲存的值賦值給x,x區域性變數由%ecx臨時儲存

    *xp = y;                  // movl  %edx, (%eax) ——將y的值賦值給%eax間接引用的*xp

    return x;                // movl  %ecx, %eax ——返回值也由儲存第一個形參的%eax儲存

}

至於為什麼如此分配暫存器,以後有機會討論過程連結時再細講。

三:特殊mov

假設%dh=AB, %eax=12345678

movb      %dh,%al        //  %eax=123456AB

movsbl   %dh,%eax    //   %eax=FFFFFFAB

movzbl    %dh,%eax    //   %eax=000000AB

movb好理解,按照位元組來賦值,al是eax的低位,所以從78變成AB

賦值給%eax,剩下三個自己如何處理呢?

movsbl類似於算術擴充套件,要保留符號位,因此剩下位全置1

movzbl類似於邏輯擴充套件,因此剩下的全置0。

我查了半天也沒查到sbl和zbl的命名出處,自我理解是,b和l的解釋就像本文開頭表上說的那樣,代表單位元組和4位元組,兩個命令都是執行從單位元組傳送資料到四位元組。而s和z是區分剩下的三個位元組的處理策略。這裡可以舉個很簡單的例子:

char cC;

unsigned char uC;

int  *cP;

unsigned int  *uP;

……cC和uC賦值……;

*cp=(char  )uC;//movzbl  %al  %edx  ……單位元組無符數轉四位元組有符數

*uP=(unsigned char )cC;//movsbl  %al  %edx   ……單位元組有符數轉四位元組無符數

上面這個例子很典型,當單位元組無符數強轉四位元組有符數時,無符數原本沒有帶符號位資訊,因此在轉成有符數時,其餘三個位元組位就用’0‘來填充,用movzbl。

                                    當單位元組有符數強轉四位元組無符數時,有符數原本是帶有符號位資訊的,因此在轉成無符數時,其餘三個位元組也要保留符號,用movsbl。

movzbl的操作是百分之百填充’0‘的,而movsbl則不一定。如果被操作的有符數本身就是正數,則符號擴展出來也還是是’0‘,只有被操作有符數本身是負數時,才會出現“1”填充。