圖解分析彙編代碼以理解計算機是如何工作的

《Linux內核分析》MOOC課程:在線課程鏈接http://mooc.study.163.com/course/USTC-1000029000

第一講 計算機是如何工作的? 課堂筆記

@2015.03

馮諾依曼體系結構的計算機,又叫存儲程序計算機,從硬件的角度來看,其工作模型是CPU依次讀取內存中的指令來完成工作。但它具體是如何完成程序員編寫的非線性執行的程序呢?本次課的實驗,以一段彙編代碼為例,詳細介紹了CPU計算模塊、寄存器和內存是如何配合工作的!

1 知識準備

1.1 彙編語言的五種尋址模式

  • 寄存器尋址 registermode: %寄存器 例如:%edx 訪問寄存器edx
  • 立即尋址 immediate: $數字例如:$0x123 數值0x123
  • 直接尋址 direct:數字 例如:0x123訪問地址0x123指向的內存
  • 間接尋址 indirect: (%寄存器) (%ebx) 例如:訪問寄存器ebx中的地址指向的內存
  • 變址尋址 displaced:偏移量(%寄存器) 4(%ebx):訪問寄存器ebx中的地址再加4指向的內存;

1.2 幾個重要的彙編指令

Example instruction

What it does

Pushl %eax

Subl $4, %esp   //棧頂指針減4,棧在向下生長一個位置

Movl %eax, (%esp) //將eax中的值放入棧頂指針指向的內存位置

Popl %eax

Movl (%esp), %eax //從棧頂指針指向的內存中的值放入eax中

Addl $4, %esp //棧頂指針加4,棧在向上收縮

Call 0x12345

Pushl %eip //ip壓棧

Movl $0x12345, %eip //將0x12345放入eip中

Ret

Popl %eip //ip出棧

2. 實驗

2.1 實驗環境

Ubuntu 12.4 LTS 操作系統,vi 編輯器,gcc 編譯器 2.2 實驗步驟

vi main.c 輸入以下代碼

int g(int x)
{
  return x + 3;
}

int f(int x)
{
  return g(x);
}

int main(void)
{
  return f(8) + 1;
}

保存退出。 在命令行使用下面的命令,將這段c代碼編譯成彙編代碼

gcc –S –o main.s main.c -m32

vi main.s 打開彙編代碼文件,刪除其中以符號點 開頭的語句,得到如下可被計算機執行的彙編代碼:

接下來,就以圖解的方式來分析這段分析代碼。

  1. 彙編代碼分析

對照c代碼,很容易就知道,這段彙編代碼也有三個函數,代碼的執行從main函數的第一條語句開始。彙編代碼共有22行可執行的代碼,下面就用22張圖來展示每一條語句執行後,主要寄存器的指向和堆棧的狀態。其中,第0張圖,是程序還未執行時的狀態;第1張圖,是程序執行完第一條語句後的情形;第2張圖,是程序執行完第22條語句後的情形;依次類圖,直到第21張圖。最後一條語句是執行main函數中的 ret 語句,即將跳出了main函數,沒有給出示意圖。

在開始看圖之前,這裡先給出圖示中的一些說明:

  • 內存中堆棧是向下生長的,即堆棧丁的地址<=堆棧底的地址;這裡為了方便描述,在程序開始執行時的,我們假定堆棧底和堆棧頂對應的地址編號是0,堆棧向下每增加一個位置,編號加1;
  • EAX 寄存器用於存儲函數的返回值,圖示中會標出其值;其他三個寄存器EIP、EBP、ESP分別用箭頭來表示其當前值;EIP就是指令寄存器,太總是指向當前正在執行的彙編指令的下一條彙編指令;EBP、ESP分別指向當前堆棧的底部和頂部;
  • 圖片中左邊為彙編源代碼,其中加粗的一行,表示當前正在執行的語句;圖0中沒有加粗的代碼行,那是當然的,圖0是程序準備執行第一條語句時的初始狀態;
  • 圖片有點長,謝謝您的耐心 :) 再囉嗦下:旁白已經夠多了,如果還不是很明白,強烈推薦您去看在線視頻http://mooc.study.163.com/course/USTC-1000029000,那裡是動態的,而且孟老師講得非常詳細。

好了,上圖: