轉Linux平台可以用gdb進行反組譯碼和調試。

來源:互聯網
上載者:User
Linux平台可以用gdb進行反組譯碼和調試。

如果在Linux平台可以用gdb進行反組譯碼和調試。(轉)

2. 最簡C程式碼分析

    為簡化問題,來分析一下最簡的c代碼產生的彙編代碼:
    # vi test1.c
     
    int main()
    {
        return 0;
    }  
   
    編譯該程式,產生二進位檔案:
    # gcc test1.c -o test1
    # file test1  
    test1: ELF 32-bit LSB executable 80386 Version 1, dynamically linked, not stripped

    test1是一個ELF格式32位小端(Little Endian)的可執行檔,動態連結並且符號表沒有去除。
    這正是Unix/Linux平台典型的可執行檔格式。
    用mdb反組譯碼可以觀察產生的彙編代碼:

    # mdb test1
    Loading modules: [ libc.so.1 ]
    > main::dis                       ; 反組譯碼main函數,mdb的命令一般格式為  <地址>::dis
    main:          pushl   %ebp       ; ebp寄存器內容壓棧,即儲存main函數的上級調用函數的棧基地址
    main+1:        movl    %esp,%ebp  ; esp值賦給ebp,設定main函數的棧基址
    main+3:          subl    $8,%esp
    main+6:          andl    $0xf0,%esp
    main+9:          movl    $0,%eax
    main+0xe:        subl    %eax,%esp
    main+0x10:     movl    $0,%eax    ; 設定函數傳回值0
    main+0x15:     leave              ; 將ebp值賦給esp,pop先前棧內的上級函數棧的基地址給ebp,恢複原棧基址
    main+0x16:     ret                ; main函數返回,回到上級調用
    >

    註:這裡得到的組合語言文法格式與Intel的手冊有很大不同,Unix/Linux採用AT&T彙編格式作為組合語言的文法格式
         如果想瞭解AT&T彙編可以參考文章:Linux AT&T 組合語言開發指南

    問題:誰調用了 main函數?
    
     在C語言的層面來看,main函數是一個程式的起始進入點,而實際上,ELF可執行檔的進入點並不是main而是_start。
     mdb也可以反組譯碼_start:
      
    > _start::dis                       ;從_start 的地址開始反組譯碼
    _start:              pushl   $0
    _start+2:            pushl   $0
    _start+4:            movl    %esp,%ebp
    _start+6:            pushl   %edx
    _start+7:            movl    $0x80504b0,%eax
    _start+0xc:          testl   %eax,%eax
    _start+0xe:          je      +0xf            <_start+0x1d>
    _start+0x10:         pushl   $0x80504b0
    _start+0x15:         call    -0x75           <atexit>
    _start+0x1a:         addl    $4,%esp
    _start+0x1d:         movl    $0x8060710,%eax
    _start+0x22:         testl   %eax,%eax
    _start+0x24:         je      +7              <_start+0x2b>
    _start+0x26:         call    -0x86           <atexit>
    _start+0x2b:         pushl   $0x80506cd
    _start+0x30:         call    -0x90           <atexit>
    _start+0x35:         movl    +8(%ebp),%eax
    _start+0x38:         leal    +0x10(%ebp,%eax,4),%edx
    _start+0x3c:         movl    %edx,0x8060804
    _start+0x42:         andl    $0xf0,%esp
    _start+0x45:         subl    $4,%esp
    _start+0x48:         pushl   %edx
    _start+0x49:         leal    +0xc(%ebp),%edx
    _start+0x4c:         pushl   %edx
    _start+0x4d:         pushl   %eax
    _start+0x4e:         call    +0x152          <_init>
    _start+0x53:         call    -0xa3           <__fpstart>
    _start+0x58:        call    +0xfb        <main>              ;在這裡調用了main函數
    _start+0x5d:         addl    $0xc,%esp
    _start+0x60:         pushl   %eax
    _start+0x61:         call    -0xa1           <exit>
    _start+0x66:         pushl   $0
    _start+0x68:         movl    $1,%eax
    _start+0x6d:         lcall   $7,$0
    _start+0x74:         hlt
    >

    問題:為什麼用EAX寄存器儲存函數傳回值?
    實際上IA32並沒有規定用哪個寄存器來儲存傳回值。但如果反組譯碼Solaris/Linux的二進位檔案,就會發現,都用EAX儲存函數傳回值。
    這不是偶然現象,是作業系統的ABI(Application Binary Interface)來決定的。
    Solaris/Linux作業系統的ABI就是Sytem V ABI。

    概念:SFP (Stack Frame Pointer) 棧架構指標 

    正確理解SFP必須瞭解:
        IA32 的棧的概念
        CPU 中32位寄存器ESP/EBP的作用
        PUSH/POP 指令是如何影響棧的
        CALL/RET/LEAVE 等指令是如何影響棧的

    如我們所知:
    1)IA32的棧是用來存放臨時資料,而且是LIFO,即後進先出的。棧的增長方向是從高地址向低地址增長,按位元組為單位編址。
    2) EBP是棧基址的指標,永遠指向棧底(高地址),ESP是棧指標,永遠指向棧頂(低地址)。
    3) PUSH一個long型資料時,以位元組為單位將資料壓入棧,從高到低按位元組依次將資料存入ESP-1、ESP-2、ESP-3、ESP-4的地址單元。
    4) POP一個long型資料,過程與PUSH相反,依次將ESP-4、ESP-3、ESP-2、ESP-1從棧內彈出,放入一個32位寄存器。
    5) CALL指令用來調用一個函數或過程,此時,下一條指令地址會被壓入堆棧,以備返回時能恢複執行下條指令。
    6) RET指令用來從一個函數或過程返回,之前CALL儲存的下條指令地址會從棧內彈出到EIP寄存器中,程式轉到CALL之前下條指令處執行
    7) ENTER是建立當前函數的棧架構,即相當於以下兩條指令:
        pushl   %ebp
        movl    %esp,%ebp
    8) LEAVE是釋放當前函數或者過程的棧架構,即相當於以下兩條指令:
        movl ebp esp
        popl  ebp

    如果反組譯碼一個函數,很多時候會在函數進入和返回處,發現有類似如下形式的彙編語句:
       
        pushl   %ebp            ; ebp寄存器內容壓棧,即儲存main函數的上級調用函數的棧基地址
        movl    %esp,%ebp       ; esp值賦給ebp,設定 main函數的棧基址
        ...........             ; 以上兩條指令相當於 enter 0,0
        ...........
        leave                   ; 將ebp值賦給esp,pop先前棧內的上級函數棧的基地址給ebp,恢複原棧基址
        ret                     ; main函數返回,回到上級調用

    這些語句就是用來建立和釋放一個函數或者過程的棧架構的。
    原來編譯器會自動在函數入口和出口處插入建立和釋放棧架構的語句。
    函數被調用時:
    1) EIP/EBP成為新函數棧的邊界
    函數被調用時,返回時的EIP首先被壓入堆棧;建立棧架構時,上級函數棧的EBP被壓入堆棧,與EIP一道行成新函數棧架構的邊界
    2) EBP成為棧架構指標SFP,用來指示新函數棧的邊界
    棧架構建立後,EBP指向的棧的內容就是上一級函數棧的EBP,可以想象,通過EBP就可以把層層調用函數的棧都回朔遍曆一遍,調試器就是利用這個特性實現 backtrace功能的
    3) ESP總是作為棧指標指向棧頂,用來分配棧空間
    棧分配空間給函數局部變數時的語句通常就是給ESP減去一個常數值,例如,分配一個整型資料就是 ESP-4
    4) 函數的參數傳遞和局部變數訪問可以通過SFP即EBP來實現
    由於棧架構指標永遠指向當前函數的棧基地址,參數和局部變數訪問通常為如下形式:
        +8+xx(%ebp)         ; 函數入口參數的的訪問
        -xx(%ebp)           ; 函數局部變數訪問
           
    假如函數A調用函數B,函數B調用函數C ,則函數棧架構及調用關係如所示:

   +-------------------------+----> 高地址
| EIP (上級函數返回地址) |
+-------------------------+
+--> | EBP (上級函數的EBP) | --+ <------當前函數A的EBP (即SFP架構指標)
| +-------------------------+ +-->位移量A
| | Local Variables | |
| | .......... | --+ <------ESP指向函數A新分配的局部變數,局部變數可以通過A的ebp-位移量A訪問
| f +-------------------------+
| r | Arg n(函數B的第n個參數) |
| a +-------------------------+
| m | Arg .(函數B的第.個參數) |
| e +-------------------------+
| | Arg 1(函數B的第1個參數) |
| o +-------------------------+
| f | Arg 0(函數B的第0個參數) | --+ <------ B函數的參數可以由B的ebp+位移量B訪問
| +-------------------------+ +--> 位移量B
| A | EIP (A函數的返回地址) | |
| +-------------------------+ --+
+--- | EBP (A函數的EBP) |<--+ <------ 當前函數B的EBP (即SFP架構指標)
+-------------------------+ |
| Local Variables | |
| .......... | | <------ ESP指向函數B新分配的局部變數
+-------------------------+ |
| Arg n(函數C的第n個參數) | |
+-------------------------+ |
| Arg .(函數C的第.個參數) | |
+-------------------------+ +--> frame of B
| Arg 1(函數C的第1個參數) | |
+-------------------------+ |
| Arg 0(函數C的第0個參數) | |
+-------------------------+ |
| EIP (B函數的返回地址) | |
+-------------------------+ |
+--> | EBP (B函數的EBP) | --+ <------ 當前函數C的EBP (即SFP架構指標)
| +-------------------------+
| | Local Variables |
| | .......... | <------ ESP指向函數C新分配的局部變數
| +-------------------------+----> 低地址
frame of C

圖 1-1

      
    再分析test1反組譯碼結果中剩餘部分語句的含義:
       
    # mdb test1
    Loading modules: [ libc.so.1 ]
    > main::dis                        ; 反組譯碼main函數
    main:          pushl   %ebp                           
    main+1:        movl    %esp,%ebp        ; 建立Stack Frame(棧架構)
    main+3:       subl    $8,%esp       ; 通過ESP-8來分配8位元組堆棧空間
    main+6:       andl    $0xf0,%esp    ; 使棧地址16位元組對齊
    main+9:       movl    $0,%eax       ; 無意義
    main+0xe:     subl    %eax,%esp     ; 無意義
    main+0x10:     movl    $0,%eax          ; 設定main函數傳回值
    main+0x15:     leave                    ; 撤銷Stack Frame(棧架構)
    main+0x16:     ret                      ; main 函數返回
    >

    以下兩句似乎是沒有意義的,果真是這樣嗎?
        movl    $0,%eax
        subl     %eax,%esp
      
    用gcc的O2級最佳化來重新編譯test1.c:
    # gcc -O2 test1.c -o test1
    # mdb test1
    > main::dis
    main:         pushl   %ebp
    main+1:       movl    %esp,%ebp
    main+3:       subl    $8,%esp
    main+6:       andl    $0xf0,%esp
    main+9:       xorl    %eax,%eax      ; 設定main傳回值,使用xorl異或指令來使eax為0
    main+0xb:     leave
    main+0xc:     ret
    >
    新的反組譯碼結果比最初的結果要簡潔一些,果然之前被認為無用的語句被最佳化掉了,進一步驗證了之前的猜測。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.