信息安全系統設計基礎第四周學習總結

#第三章 程序的機器級表示

1. 尋址方式歷史

(1)DOS時代的平坦模式

不區分用戶空間和內核空間,很不安全html

(2)8086的分段模式

(3)IA32的帶保護模式的平坦模式

2. 程序編碼

(1)編譯

gcc -01 -o p p1.c

-01:表示使用第一級優化。一般提升優化級別會使最終程序運行得更快,可是編譯時間可能會變長,用調試工具對代碼進行調試會更困難。(實際中,第二級優化-02被認爲是較好的選擇)程序員

gcc命令調用一系列程序將源代碼轉換成可執行代碼:
    - C預處理器 擴展源代碼,插入#include命令指定的文件,擴展#define聲明指定的宏。(.i)
    - 編譯器 產生兩個源代碼的彙編代碼。(.s)
    - 彙編器 將彙編代碼轉化成二進制目標代碼(.o)
    - 鏈接器 將兩個目標代碼與實現庫函數的代碼合併,併產生最終的可執行代碼文件。

(2)兩種抽象

參考資料3:體系結構-指令集結構編程

計算機系統使用了多種不一樣形式的抽象,利用更簡單的抽象模型來隱藏實現的細節。對於機器級編程來講,兩種抽象尤其重要:數組

  • 機器級程序的格式和行爲,定義爲 ISA :指令集體系結構。安全

    ISA,定義了處理器狀態,指令格式,以及每條指令對狀態的影響。
      處理器的硬件併發的執行許多指令,可是能夠採起措施保證總體行爲與ISA指定的順序整形徹底一致。
    
      解決的問題:
          指令的編碼方式(即如何編碼)
          操做數和操做結構的存放位置
          數據的類型和大小
          支持哪些操做
          下一條指令的地址
  • 機器級程序使用的存儲器地址是虛擬地址,提供的存儲器模型看上去是一個很是大的字節數組。數據結構

(3)IA32機器代碼

一些一般對C語言程序員隱藏的機器代碼在IA32中是可見的:併發

  • 程序計數器(在IA32中,一般稱爲「PC」,用%eip表示)

指示將要執行的下一條指令在存儲器中的地址。less

  • 整數寄存器

包含8個命名的位置,分別存儲32位的數值,這些寄存器能夠存儲地址(對應C語言的指針)或整數數據,有的寄存器被用來記錄某些重要的程序狀態,其餘的寄存器用來保存臨時數據,例如過程的局部變量和函數的返回值。函數

  • 條碼寄存器

保存着最近執行的算術或邏輯指令的狀態信息,他們用來實現控制或數據流中的條件變化。工具

  • 浮點寄存器

一組浮點寄存器存放浮點數據

一條機器指令只執行一個很是基本的操做

(4)代碼示例

參考資料4:函數調用過程棧幀變化詳解

教材代碼:

  • 得到彙編代碼:

    gcc -S xxx.c -o xxx.s
  • 反彙編:

    objdump -d xxx

  • 彙編代碼(函數前兩條和後兩條彙編代碼,全部函數都有,創建函數調用棧幀):

    前兩條:
          pushl %ebp          將寄存器%ebp的內容壓入程序棧
          movl %esp,%ebp      獲得新棧低,將當前棧頂賦予棧低
      後兩條:
          popl %ebp            過程調用結束,恢復舊棧低
          ret                 子程序的返回指令

注意

  • 64位機器上想要獲得32代碼:gcc -m32 -S xxx.c
  • MAC OS中沒有objdump, 有基本等價的命令otool
  • Ubuntu中 gcc -S code.c (不帶-O1) 產生的代碼更接近教材中代碼(刪除"."開頭的語句)
  • 實驗樓中:

  • 二進制文件能夠用od 命令查看,也能夠用gdb的x命令查看。
    有些輸出內容過多,可使用 more或less命令結合管道查看,也可使用輸出重定向來查看。

    od code.o | more
      od code.o > code.txt

(5)機器代碼和它的反彙編表示的一些特性

  • IA32指令長度從1到15個字節不等
  • 設計指令格式的方式是,從某個給定位置開始,能夠將字節惟一的解碼成機器指令
  • 反彙編器只是基於機器代碼文件中的字節序列來肯定彙編代碼,不須要訪問程序的源代碼或彙編代碼
  • 反彙編器使用的指令命名規則與GCC生成的彙編代碼使用的有些差異

3. 數據格式

(1)Intel(由16位體系擴展爲32位)

字(word)              16位
雙字(double words)    32位
四字(quad words)      64位

(2)C語言數據類型在IA32中的大小

  • IA32不支持64位整數運算
  • 大多數GCC生成的彙編代碼指令都有一個字符後綴,代表操做數的大小。

4. 訪問信息

(1)IA32的整數寄存器

一個IA32的中央處理器單元包含一組8個存儲32位數值的寄存器。全部八個寄存器均可以做爲16位(字)或32位(雙字)來訪問。

  • %esi,%edi能夠用來操縱數組
  • %esp,%ebp用來操縱棧幀。
  • 能夠獨立訪問前四個寄存器的兩個低位字節(後向兼容)。
  • 32位的%eax,16位的%ax,8位的%ah,%al都是獨立的

(2)操做數指示符

  • 操做數三種類型
  • 當即數,即常數值
  • 寄存器,表示某個寄存器的內容
  • 存儲器,根據計算出來的地址(有效地址)訪問某個存儲器位置。

  • 操做數格式:

  • 有效地址的計算方式

    Imm(Eb,Ei,s) = Imm + R[Eb] + R[Ei]*s

(3)數據傳送指令

參考資料5:彙編(五)——數據傳送指令一
參考資料6:彙編(八)——數據傳送類指令三

符號                      意義
r8          任意一個8位通用寄存器AH/AL/BH/BL/CH/CL/DH/DL
r16         任意一個16通用寄存器AX/BX/CX/DX/SI/DI/BP/SP
reg         表明r8或r16
seg         段寄存器CS/DS/ES/SS
m8          一個8位存儲器操做數單元(包括全部主存尋址方式)
m16         一個16位存儲器操做數單元(包括全部主存尋址方式)
mem         表明m8或m16
i8          一個8位當即數
i16         一個16位當即數
imm         表明i8或i16
dest        目的操做數
src         源操做數
  • mov指令(指令的第一個是源操做數,第二個是目的操做數)

  • MOV reg/mem,imm     當即數送寄存器或是存儲器
         MOV reg/mem/seg,reg 寄存器送寄存器(包括段寄存器)或貯存
         MOV reg/seg,mem     主存送寄存器(包括段寄存器)
         MOV reg/mem,seg     段寄存器送主存或寄存器
  • IA32的限制:兩個操做數都不能指向存儲器。

  • 不能從內存地址直接MOV到另外一個內存地址,要用寄存器中轉一下。

  • push與pop

    • 先進後出:push將數據壓入棧中,pop彈出,彈出的永遠是最近被壓入的。用數組實現棧,進行操做的一端爲棧頂。
    • 棧向下增加,棧頂元素的地址是全部棧中元素地址中最低的。棧指針%esp保存棧頂元素的地址。

      進棧指令PUSH:
               PUSH reg/mem/seg       SP←SP-2,SS←reg/mem/seg
          - 進棧指令先使堆棧指令SP減2,而後把一個字操做數存入堆棧頂部。
          - 堆棧操做的對象只能是字操做數。
          - 進棧時,底字節存放於低地址,高字節存放在高地址,SP相應向低地址移動兩個字節單元。
      
          出棧指令POP:
              POP reg/seg/mem         reg/seg/mem←SS:[SP],SP←SP+2
          - 出棧指令把棧頂的一個字傳送至指定的目的操做數,而後堆棧指針SP加2。
          - 目的操做數應爲字操做數。
          - 字從棧頂彈出時,低地址字節送低字節,高地址字節送高字節。

(4)數據傳送與C語言

  • C語言中「指針」其實就是地址。間接引用指針就是將該指針放在一個寄存器中,而後在存儲器引用中使用這個寄存器。
  • 局部變量一般是保存在寄存器中,而不是存儲器中。寄存器訪問比存儲器訪問要快得多。

5. 算術和邏輯操做

(1)加載有效地址指令

是movl指令的變形

  • 指令形式:從存儲器讀取到寄存器
  • 其實是將有效地址寫入目的操做數。
  • leal指令一般用來執行簡單的算術操做
  • 整數算術操做:

(2)一元操做和二元操做

  • 一元操做

    - INC       加1
      - DEC       減1
      - NEG       取負
      - NOT       取補
  • 只有一個操做數,既是源又是目的,能夠是一個寄存器,或者存儲器位置。
  • 二元操做

    - ADD       加
      - SUB       減
      - IMUL      乘
      - XOR       異或
      - OR        或
      - AND       與
  • 第一個操做數能夠是當即數、寄存器或者存儲器位置
  • 第二個操做數既是源也是又是目的。能夠是寄存器或者存儲器位置,可是不能同時是存儲器位置。
  • 注意操做的順序:

    第二個操做數 操做符 第一個操做數

(3)移位操做

  • 先給出移位量,第二項給出要移位的數值。

    - SAL       左移
      - SHL       左移(等同於SAL)
      - SAR       算術右移    
      - SHR       邏輯右移
  • 源操做數(移位量):當即數或者放在單字節寄存器元素%cl中。
  • 目的操做數:一個寄存器或是一個存儲器位置。

(4)特殊操做

  • 乘法
  • 乘積截斷

    imull   雙操做數
         - 從兩個32位操做數產生一個32位的乘積。
  • 乘積不截斷

    mull    無符號數乘法
         imull   有符號數乘法
         - 要求一個參數必須在寄存器%eax中,另外一個做爲指令的源操做數給出。
         - 乘積的高32位在%edx中,低32位在%eax中。
  • 除法
  • 有符號除法

    idivl 操做數
         - 將DX:AX中的64位數做爲被除數,操做數中爲除數
         - 結果:商在AX中,餘數在DX中。
  • 無符號除法

    divl指令
         - 一般會事先設定寄存器%edx爲0.

6. 控制

(1)狀態寄存器(條件碼寄存器)

  • 經常使用條件碼:

    • CF:進位標誌。最近操做使最高位產生進位。用於檢查無符號操做數的溢出。
    • ZF:零標誌。最近操做結果爲0。
    • SF:符號標誌。最近操做獲得的結果爲負數。
    • OF:溢出標誌。最近操做致使一個補碼溢出。
  • leal 不改變條件碼寄存器
  • 比較和測試指令:不修改任何寄存器的值,只設置條件碼

    比較指令cmp和減法指令sub有何不一樣?
      - sub d,s   是d-s,結果送回d中,即送回目的操做數中。
      - cmp d,s   也是d-s,但結果不送回目的操做數中,是利用減法進行兩個數值的比較。

(2)訪問條件碼

  • 經常使用的使用方法:
  • 根據條件碼的某個組合,將一個字節設置爲0或1。

    SET指令:執行比較指令,根據計算t=a-b的結果設置條件碼

  • 能夠條件跳轉到程序的某個其餘部分
  • 能夠有條件的傳送數據

(3)跳轉指令

  • 無條件跳轉
  • 直接跳轉:跳轉目標是做爲指令的一部分編碼的。
  • 間接跳轉:跳轉目標是從寄存器或存儲器位置中讀出的。

    例:
             jmp *%eax   用寄存器%eax中的值做爲跳轉目標。
             jmp *(%eax) 以%eax中的值做爲讀地址,從存儲器中讀出跳轉目標。
  • 其它跳轉指令:
  • 一些底層的機器指令有多個名字,條件跳轉只能是直接跳轉。

  • 跳轉指令編碼
  • 最經常使用的是PC相關的,它們會將目標指令的地址與緊跟在跳轉指令後面那條指令的地址之間的差做爲編碼。
  • 第二種編碼方法是給出「絕對」地址,用四個字節直接指定目標。
  • 執行與PC相關的尋址時,程序計數器的值是跳轉指令後面的那條指令的地址,而不是跳轉指令自己的地址。

(4)翻譯條件分支

參考資料7:20135202閆佳歆——信息安全系統設計基礎第四周學習總結

  • 將條件表達式和語句從c語言翻譯成機器語言,最經常使用的方式就是結合有條件和無條件跳轉。
  • if-else 的彙編結構
  • 通用形式模板

    if(test-expr)
             then-statement
         else
             else-statement
    
         (注:test-expr    整數表達式[假/真])
  • 彙編實現形式

    t = test-expr;
         if (!t)
             goto false;
         then-statement
         goto done;
         false: 
             else-statement
         done:

(5)循環

  • do-while循環
  • 通用形式:

    do
             body-statement
             while(test-expr);
  • 翻譯成以下條件和goto語句:

    loop:
             body-statement
             t = test-expr;
             if(t)
                 goto loop;
  • while循環
  • 通用形式:

    while (test-expr)
             body-statement
  • 轉換成 do-while 形式:

    if(!test-expr)
             goto done;
         do
                 body-statement
                 while(test-expr);
         done:
  • 翻譯成 goto 形式:

    t = test-expr;
             if(!t)
                 goto done:
         loop:
             body-statement
             t = test-expr;
             if(t)
                 goto loop;
         done:
  • for循環
  • 通用形式

    for(init-expr;test-expr;update-expr)
             body-statement
  • 同 while:

    init-expr;
         while(test-expr){
             body-statement
             update-expr;
         }
  • 對應 do-while 形式:

    init-expr;
         if(!test-expr)
             goto done;
         do{
             body-statement
             update-expr;
         }while(test-expr);
         done;
  • 轉換成 goto 代碼:

    init-expr
             t = test-expr;
             if(!t)
                 goto done:
         loop:
             body-statement
             t = test-expr;
             if(t)
                 goto loop;
         done:

(6)條件傳送指令

(7)switch語句

  • switch語句能夠根據一個整數索引值進行多重分支。處理具備多種可能結果的測試時,這種語句特別有用。
  • 優勢:提升了代碼的可讀性;使用跳轉表這個數據結構使用實現更加高效。
  • 跳轉表:是一個數組,表項i是一個代碼段的地址,這個代碼段實現當switch索引值等於i時程序應該執行的動做。程序代碼用於索引值來執行一個跳轉表內的數組引用,肯定跳轉指令的目標。和使用一組很長的if-else相比,使用跳轉表的優勢是執行switch語句的時間與switch的case數量無關。GCC根據switch語句中case的數量和case中值的稀少程序來翻譯開關語句。當case數據比較多(例如4個以上),而且值的範圍跨度比較小時,就會使用跳轉表。
  • 執行switch語句的關鍵步驟是經過跳轉表來訪問代碼位置。

7. 過程

(1)概述

  • 一個過程調用包括將數據和控制從代碼的一部分傳遞到另外一部分,須要在進入時爲過程的局部變量分配空間,並在退出時釋放這些空間。
  • 數據傳遞、局部變量的分配和釋放經過操縱程序棧來實現。

(2)棧幀結構

棧幀: 爲單個過程分配的那部分棧

  • 最頂端的棧幀以兩個指針界定:

    - 寄存器%ebp爲幀指針
      - 寄存器%esp爲棧指針
  • 程序執行時,棧指針能夠移動,大多數信息的訪問都是相對於幀指針的。
  • 棧向低地址方向增加,棧指針%esp指向棧頂元素:

    - 棧指針值適當減少能夠分配沒有指定初始值的數據的空間
      - 相似的,能夠經過增長棧指針來釋放空間

(3)轉移控制

  • 支持過程調用和返回的指令:

  • call指令
  • 目標:指明被調用過程起始的指令地址。
  • 效果:將返回地址入棧,並跳轉到被調用過程的起始處。

  • ret指令
  • 從棧中彈出地址,並跳轉到這個位置.
  • 使用這個指令時,棧指針要指向前面call指令存儲返回地址的位置。

  • leave
  • 這個指令可使棧作好返回的準備
  • 等價於:

    movl %ebp,%esp
         popl %ebp

(4)寄存器使用慣例

  • 程序寄存器組是惟一能被全部過程共享的資源。
  • 爲何必須遵照慣例:必須保證一個過程(調用者)在調用另外一個過程(被調用者)時,被調用者不會覆蓋某個調用者寄存器中的值。
  • 慣例:

    - %eax,%edx,%ecx        劃分爲調用者保存寄存器
      - %ebx,%esi,%edi        劃分爲被調用者保存寄存器
      - %ebp,%esp             保持寄存器
      - %eax                  保存函數返回值

(5)遞歸過程

  • 當過程被調用時分配局部存儲,返回時釋放存儲。

8. GDB調試器

  • GDB命令:

#做業

  • main.c:

  • 彙編代碼:

  • 用vi查看編譯器指令:

  • 刪除gcc產生代碼中以"."開頭的編譯器指令後:

  • 分析:
  • main函數保存%ebp,並設置新的幀指針。

    pushl   %ebp
         movl    %esp,%ebp
  • 分配4字節的棧空間

    subl    $4,%esp
  • 設置 arg1=8

    movl    $8,(%esp)
  • call調用fh
  • fh被調用,初始化幀指針,分配棧空間。
  • 將(%esp)中的8給 %eax,即存入棧中

    movl    %eax,(%esp)
  • call調用gh
  • gh被調用,初始化棧指針,分配棧空間
  • 將 %eax 與當即數 3 相加

    add     $3,%eax
  • 在gh結束前彈棧

    popl    %ebp
  • ret返回fh中call的調用位置
  • fh也結束,return返回main中call調用的位置
  • main繼續 %eax 加1的操做

    addl    $1,%eax
  • leave爲返回準備棧,至關於%ebp出棧,最後ret結束。

參考資料


參考資料1:深刻理解計算機系統(第二版)
參考資料2:教材導讀與每週考試重點---不斷更新
參考資料3:體系結構-指令集結構
參考資料4:函數調用過程棧幀變化詳解
參考資料5:彙編(五)——數據傳送指令一
參考資料6:彙編(八)——數據傳送類指令三
參考資料7:20135202閆佳歆——信息安全系統設計基礎第四周學習總結

相關文章
相關標籤/搜索