通過彙編一個簡單的C程序,分析彙編代碼理解計算機是如何工作的


通過彙編一個簡單的C程序,分析彙編代碼理解計算機是如何工作的





計算機的工作方式:


現代計算機的基本體系結構都是採用馮諾依曼結構,馮諾依曼的設計思想最重要之處是"存儲程序"的這個概念。計算機的工作過程,就是執行程序的過程。首先編寫需要執行的程序,然後通過輸入設備送到存儲器保存起來,即程序存儲。根據馮諾依曼的設計,計算機應能自動執行程序,而執行程序又歸結爲逐條執行指令。執行一條指令又可分爲以下4個基本操作:

  1. 取出指令:從存儲器某個地址中取出要執行的指令送到CPU內部的指令寄存器暫存。

  2. 分析指令:把保存在指令寄存器中的指令送到指令譯碼器,譯出該指令對應的微操作。

  3. 執行指令:根據指令譯碼,向各個部件發出相應控制信號,完成指令規定的各種操作。

  4. 爲執行下一條指令作好準備,即取出下一條指令地址。



接下來通過一個簡單的c程序來分析一下,程序的執行過程


這裏是一個非常簡單的c程序,源代碼如下:


輸入:gcc S o main.s main.c m32 來生成彙編代碼


整潔一下彙編代碼以後,查看彙編代碼:

我在我的虛擬機中的ubuntu系統與實驗樓ubuntu系統的彙編代碼有點不一樣


下面通過gdb單步執行來分析棧上寄存器的情況:


首先我們從main函數開始。(前兩條語句在gdb執行時設置不了斷點,但是執行函數的語句都有這2條,放到其他函數來說明):

在main函數上先設置一個斷點,然後運行:

此時查看寄存器的值:

他們的值:esp和ebp都是0xbffff568,eip是0x8048409(正好是下一條要執行的指令的地址)

接下來繼續執行:

把2壓到棧上

此時,esp的減4了,而ebp不變,eip繼續指向下一條指令

下一條要執行call指令,這裏再對函數f設置一個斷點,繼續執行:

此時,程序跳到函數f中去了


call  f

調用函數 f,其實這條指令等價於

pushl %eip

movl  $f, %eip

eip的值被保存在esp-4的位置上,保存eip的目的是函數調用返回時能夠繼續執行call f下面的語句:

此時,esp的值爲0xbffff560,ebp都是0xbffff568

跳轉到函數f後,前兩條語句和 main 函數相同,都是保存堆棧狀態,這裏詳細來說明一下:

先把ebp的值保存咋esp-4的位置上

再把esp的值賦給sbp,此時esp和ebp的值都爲0xbffff55c

然後繼續執行,把ebp+8的內容即2這個值壓棧:

此時esp繼續-4

查看寄存器的值

寄存器的值:esp的值爲0xbffff558,ebp的值爲0xbffff55c,

接下來要跳轉到函數g了,因此再對函數g設置一個斷點,然後繼續執行:

觀察寄存器的值:

同理:寄存器eip的值繼續被保存了在esp-4的位置上,以便能夠返回到函數f

進入g函數老的ebp的值也被保存了,新的esp和ebp相同

此時,esp和ebp的值都是0xbffff550

接下來繼續執行,把ebp+8的值給eax

查看寄存器,此時esp和ebp的值都是0xbffff550,eax的值是2

繼續執行:

把3和eax的值相加結果再保存到eax中

查看寄存器

寄存器eax的值變成5了,esp和ebp的值都是0xbffff550

然後繼續執行:

把esp指向的值給ebp,查看寄存器

寄存器的值:esp的值爲0xbffff554,ebp的值0xbffff55c,eax還是5

然後繼續執行:

指令ret相當於指令popl %eip

esp的值爲0xbffff558,ebp的值0xbffff55c,eax還是5

這樣又返回到函數f繼續執行:

繼續執行,然後查看寄存器:

Esp和ebp的值都是0xbffff55c,eax還是5


繼續執行,leave,這條指令相當於下面兩條指令:

movl  %ebp, %esp

popl  %ebp

查看寄存器

esp的值爲0xbffff560,ebp的值0xbffff568,eax還是5


繼續執行ret,彈出保存的eip的值,返回到main函數執行:

查看寄存器的值,esp的值爲0xbffff564,ebp的值0xbffff568,eax還是5

連續執行2步,繼續執行

此時eax的值變成了6,esp和ebp的值0xbffff568

然後繼續執行2步,main函數就返回了



總結


通過分析對應的彙編代碼和觀察運行棧的變化,加深了對程序執行過程的瞭解,也明白了計算機的工作方式:

  1. 根據eip 指指令執行,同時eip自增;

  2. 如果執行的是跳轉語句時,先把eip壓棧,然後將需要跳轉的目的地址賦給 eip,實現跳轉;

  3. 若執行函數調用時,將 eip 壓棧,同時將ebp壓棧,然後將相應函數地址賦給 eip;

  4. 若爲其他指令,則繼續從 eip 指向的地址取指令執行。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章