1. 程式人生 > >彙編程式碼的簡單分析

彙編程式碼的簡單分析

  • 內容說明

    本次的內容,是一次 MOOC 課程的作業。具體的,是在 Linux 下對一段簡單的 C 程式碼生成的彙編程式碼進行分析,進而瞭解計算機、CPU 的工作機制。

  • 作業宣告

         qianyizhou17 + 原創作品轉載請註明出處 + 《Linux 核心分析》MOOC 課程 
         http://mooc.study.163.com/course/USTC-1000029000 
    
  • 實驗準備

    • 環境
      Linux
      需要介紹一下的是本次 MOOC 提供的實驗樓的環境,可以直接訪問 Linux 的環境,一系列操作也十分簡單,十分贊!
    • 原始碼
      main.c
int func_a(int x)
{
  return
x + 1000; } int func_b(int x) { return func_a(x); } int main(void) { return func_b(8) + 1; }
  • 生成彙編指令
$ gcc –S –o main.s main.c -m32
  • 生成如下
    .file   "main.c"
    .text
    .globl  func_a
    .type   func_a, @function
func_a:
.LFB0:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
.cfi_offset 5, -8 movl %esp, %ebp .cfi_def_cfa_register 5 movl 8(%ebp), %eax addl $1000, %eax popl %ebp .cfi_restore 5 .cfi_def_cfa 4, 4 ret .cfi_endproc .LFE0: .size func_a, .-func_a .globl func_b .type func_b, @function func_b: .LFB1:
.cfi_startproc pushl %ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp .cfi_def_cfa_register 5 subl $4, %esp movl 8(%ebp), %eax movl %eax, (%esp) call func_a leave .cfi_restore 5 .cfi_def_cfa 4, 4 ret .cfi_endproc .LFE1: .size func_b, .-func_b .globl main .type main, @function main: .LFB2: .cfi_startproc pushl %ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp .cfi_def_cfa_register 5 subl $4, %esp movl $8, (%esp) call func_b addl $1, %eax leave .cfi_restore 5 .cfi_def_cfa 4, 4 ret .cfi_endproc .LFE2: .size main, .-main .ident "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2" .section .note.GNU-stack,"",@progbits

這裡寫圖片描述
其中以 “.” 開頭的為輔助資訊,在分析程式碼時可以刪除,整理後的彙編資訊如下:

func_a:
    pushl   %ebp
    movl    %esp, %ebp
    movl    8(%ebp), %eax
    addl    $1000, %eax
    popl    %ebp
    ret
func_b:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $4, %esp
    movl    8(%ebp), %eax
    movl    %eax, (%esp)
    call    func_a
    leave
    ret
main:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $4, %esp
    movl    $8, (%esp)
    call    func_b
    addl    $1, %eax
    leave
    ret
  • 程式碼分析前提

    • 什麼是棧
      在我的理解,棧是一片記憶體,用於暫時快取程式的指令。那麼為何不使用普通的連續結構,而使用 “棧” 結構?
      利用棧的特性(後入先出),與函式呼叫的性質一致(最近呼叫的最先返回)。
      棧底是高地址:即,逐漸壓棧,會使得訪問的棧地址逐漸變小,可能發生錯誤——棧溢位。
      我們使用 ebp 來標識棧底,esp 標識當前的棧指標。
    • 必要的彙編說明
      • push
        pushl X:
        subl $4, %esp
        movl X, (%esp)
      • pop
        popl Y:
        movl (%esp), Y
        addl %4, %esp
      • call
        call ADDR
        pushl %eip(*)
        movl ADDR, %eip(*)
      • enter
        pushl %ebp
        movl %esp, %ebp
      • leave
        movl %ebp, %esp
        popl %ebp
      • ret
        popl %eip(*)
    • 必要的暫存器說明
      • ebp
        標識棧底
      • esp
        標識當前的棧指標
      • eip
        指令暫存器:儲存當前執行的指令地址
      • eax
        一般的,預設用於儲存函式返回值
  • 程式碼分析
    首先程式碼從 17 main 開始執行

執行 18、19 行,即執行 call 指令,儲存上一次的棧資訊:將上次的棧底入棧,並將棧底、棧指標重新指向下一地址,完成當前函式棧的初始化:
執行前

         |         | 
ebp ->    --------- 5000
.        |         |
.
.
         |         | 
esp ->    --------- 4000

執行後

         |         | 
          --------- 5000
.        |         |
.
.
         |         | 
          --------- 4000
         | ebp 5000| 
esp/ebp-> --------- 3996

執行 20、21 行,將立即數 8 儲存到 esp 所指向的空間中

         |         | 
          --------- 5000
.        |         |
.
.
         |         | 
          --------- 4000
         | ebp 5000| 
ebp->     --------- 3996
         |    8    | 
esp->     --------- 3992

執行 22 行:call func_b


         |         | 
          --------- 5000
.        |         |
.
.
         |         | 
          --------- 4000
         | ebp 5000| 
ebp->     --------- 3996
         |    8    | 
          --------- 3992
         | eip 23  | 
esp->     --------- 3988

同時 eip 指向了 func_b 的地址:8。繼續執行時,將從 func_b 的下一個地址 9 開始執行。
執行第 9、10 行後:

         |         | 
          --------- 5000
.        |         |
.
.
         |         | 
          --------- 4000
         | ebp 5000|  
          --------- 3996
         |    8    | 
          --------- 3992
         | eip 23  | 
          --------- 3988
         | ebp 3996| 
ebp/esp-> --------- 3984

以此類推,呼叫 call func_a,執行 func_a,第 5 行 addl $1000, %eax 執行後,eax 中儲存的值變為 1008。
執行 6 行,popl %ebp
執行前

eip      1
eax      1008
         |         | 
          --------- 5000
.        |         |
.
.
         |         | 
          --------- 4000
         | ebp 5000|  
          --------- 3996
         |    8    | 
          --------- 3992
         | eip 23  | 
          --------- 3988
         | ebp 3996| 
          --------- 3984
         |    8    | 
          --------- 3980
         | eip 15  | 
          --------- 3976
         | ebp 3984| 
ebp/esp-> --------- 3972

執行 popl %ebp 後

eip      1              
eax      1008           
         |         |    
          --------- 5000
.        |         |    
.                       
.                       
         |         |    
          --------- 4000
         | ebp 5000|    
          --------- 3996
         |    8    |    
          --------- 3992
         | eip 23  |    
          --------- 3988
         | ebp 3996|    
ebp    -> --------- 3984
         |    8    |    
          --------- 3980
         | eip 15  |    
esp    -> --------- 3976
         | ebp 3984|    
          --------- 3972

執行 ret 之後

eip      15 
eax      1008
         |         | 
          --------- 5000
.        |         |
.
.
         |         | 
          --------- 4000
         | ebp 5000|  
          --------- 3996
         |    8    | 
          --------- 3992
         | eip 23  | 
          --------- 3988
         | ebp 3996| 
ebp    -> --------- 3984
         |    8    | 
esp    -> --------- 3980
         | eip 15  | 
          --------- 3976
         | ebp 3984| 
          --------- 3972

注意,此時 eip 的值為 15,因此繼續執行的指令為 func_b 的 leave 指令
執行後

eip      15             
eax      1008           
         |         |    
          --------- 5000
.        |         |    
.                       
.                       
         |         |    
          --------- 4000
         | ebp 5000|    
ebp    -> --------- 3996
         |    8    |    
          --------- 3992
         | eip 23  |    
esp    -> --------- 3988
         | ebp 3996|    
          --------- 3984
         |    8    |    
          --------- 3980
         | eip 15  |    
          --------- 3976
         | ebp 3984|    
          --------- 3972

繼續執行 ret 後

eip      23             
eax      1008           
         |         |    
          --------- 5000
.        |         |    
.                       
.                       
         |         |    
          --------- 4000
         | ebp 5000|    
ebp    -> --------- 3996
         |    8    |    
esp    -> --------- 3992
         | eip 23  |    
          --------- 3988
         | ebp 3996|    
          --------- 3984
         |    8    |    
          --------- 3980
         | eip 15  |    
          --------- 3976
         | ebp 3984|    
          --------- 3972

addl $1, %eax 之後,eax 中儲存 1009。

繼續執行 leave,將當前的棧資訊恢復。

before leave                    after leave, before ret 
eip      23                     eip      23             
eax      1009                   eax      1009           
         |         |                     |         |    
          --------- 5000        ebp    -> --------- 5000
.        |         |            .        |         |    
.                               .                       
.                               .                       
         |         |                     |         |    
          --------- 4000        esp    -> --------- 4000
         | ebp 5000|                     | ebp 5000|    
ebp    -> --------- 3996                  --------- 3996
         |    8    |                     |    8    |    
esp    -> --------- 3992                  --------- 3992
         | eip 23  |                     | eip 23  |    
          --------- 3988                  --------- 3988
         | ebp 3996|                     | ebp 3996|    
          --------- 3984                  --------- 3984
         |    8    |                     |    8    |    
          --------- 3980                  --------- 3980
         | eip 15  |                     | eip 15  |    
          --------- 3976                  --------- 3976
         | ebp 3984|                     | ebp 3984|    
          --------- 3972                  --------- 3972

呼叫 ret,恢復 eip:將 esp 所指向的指令值寫入 eip,main 函式結束。

  • 總結
    • 進入函式時,一般首先pushl %ebp; movl %esp, %ebp來進行棧資訊的儲存,包括 1)將上次的棧底資訊壓入棧; 2)將棧底、棧指標重置為下一個棧地址。
    • 呼叫函式時,將 eip 的當前資訊壓入棧中,並更新 eip 使指向新的指令。
    • 函式返回的過程,即是從棧中讀取 eip、ebp 的舊有資訊並恢復上一個函式棧的過程
    • 其中,計算機可以使用機械的邏輯(依據 eip 讀取下一條指令,並逐條eip所指向指令即可,周而復始),而通過精緻的資料結構(棧)和基礎的CPU指令,便能夠實現複雜的邏輯!
    • 函式執行、並呼叫新的函式時,其棧資訊基本如下:
 --------- 4000
| ebp DATA|    
 --------- XXXX
|         |    
|         |
|         |
 --------- XXXX
| eip DATA|    
 --------- 39XX