久久久久久久999_99精品久久精品一区二区爱城_成人欧美一区二区三区在线播放_国产精品日本一区二区不卡视频_国产午夜视频_欧美精品在线观看免费

標(biāo)題: X86匯編語言學(xué)習(xí)手記 [打印本頁]

作者: 51黑tt    時間: 2016-3-6 00:15
標(biāo)題: X86匯編語言學(xué)習(xí)手記
最近發(fā)現(xiàn)了幾個錯誤,更新一下,需要的話,我的blog有參考:

抱歉,文中的一些鏈接參考文檔在轉(zhuǎn)帖中丟失,另外,中間的棧的插圖也亂了,文章排版也有些混亂,如果需要參考可以看我blog上的原文:



版權(quán)所有: 轉(zhuǎn)載時請務(wù)必以超鏈接形式標(biāo)明文章原始出處、作者信息及本聲明


這是作者在學(xué)習(xí)X86匯編過程中的學(xué)習(xí)筆記,難免有錯誤和疏漏之處,歡迎指正。
作者將隨時修改錯誤并將新的版本發(fā)布在自己的Blog站點(diǎn)上。
嚴(yán)格說來,本篇文檔更側(cè)重于C語言和C編譯器方面的知識,如果涉及到具體匯編語言
的內(nèi)容,可以參考相關(guān)文檔。


1. 編譯環(huán)境

   OS: Solaris 9 X86
   Compiler: gcc 3.3.2
   Linker: Solaris Link Editors 5.x
   Debug Tool: mdb
   Editor: vi

   注:關(guān)于編譯環(huán)境的安裝和設(shè)置,可以參考文章:Solaris 上的開發(fā)環(huán)境安裝及設(shè)置。
       mdb是Solaris提供的kernel debug工具,這里用它做反匯編和匯編語言調(diào)試工具。
       如果在Linux平臺可以用gdb進(jìn)行反匯編和調(diào)試。

2. 最簡C代碼分析

    為簡化問題,來分析一下最簡的c代碼生成的匯編代碼:
    # vi test1.c
      
    int main()
    {
        return 0;
    }   
     
    編譯該程序,產(chǎn)生二進(jìn)制文件:
    # 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)的可執(zhí)行文件,動態(tài)鏈接并且符號表沒有去除。
    這正是Unix/Linux平臺典型的可執(zhí)行文件格式。
    用mdb反匯編可以觀察生成的匯編代碼:

    # mdb test1
    Loading modules: [ libc.so.1 ]
    >; main::dis                       ; 反匯編main函數(shù),mdb的命令一般格式為  <地址>;::dis
    main:          pushl   %ebp       ; ebp寄存器內(nèi)容壓棧,即保存main函數(shù)的上級調(diào)用函數(shù)的棧基地址
    main+1:        movl    %esp,%ebp  ; esp值賦給ebp,設(shè)置main函數(shù)的;
    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    ; 設(shè)置函數(shù)返回值0
    main+0x15:     leave              ; 將ebp值賦給esp,pop先前棧內(nèi)的上級函數(shù)棧的基地址給ebp,恢復(fù)原棧基址
    main+0x16:     ret                ; main函數(shù)返回,回到上級調(diào)用
    >;  

    注:這里得到的匯編語言語法格式與Intel的手冊有很大不同,Unix/Linux采用AT&T匯編格式作為匯編語言的語法格式
         如果想了解AT&T匯編可以參考文章:Linux AT&T 匯編語言開發(fā)指南  

    問題:誰調(diào)用了 main函數(shù)?
      
     在C語言的層面來看,main函數(shù)是一個程序的起始入口點(diǎn),而實(shí)際上,ELF可執(zhí)行文件的入口點(diǎn)并不是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>;              ;在這里調(diào)用了main函數(shù)
    _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寄存器保存函數(shù)返回值?
    實(shí)際上IA32并沒有規(guī)定用哪個寄存器來保存返回值。但如果反匯編Solaris/Linux的二進(jìn)制文件,就會發(fā)現(xiàn),都用EAX保存函數(shù)返回值。
    這不是偶然現(xiàn)象,是操作系統(tǒng)的ABI(Application Binary Interface)來決定的。
    Solaris/Linux操作系統(tǒng)的ABI就是Sytem V ABI。


    概念:SFP (Stack Frame Pointer) 棧框架指針  

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

    如我們所知:
    1)IA32的棧是用來存放臨時數(shù)據(jù),而且是LIFO,即后進(jìn)先出的。棧的增長方向是從高地址向低地址增長,按字節(jié)為單位編址。
    2) EBP是;返闹羔,永遠(yuǎn)指向棧底(高地址),ESP是棧指針,永遠(yuǎn)指向棧頂(低地址)。
    3) PUSH一個long型數(shù)據(jù)時,以字節(jié)為單位將數(shù)據(jù)壓入棧,從高到低按字節(jié)依次將數(shù)據(jù)存入ESP-1、ESP-2、ESP-3、ESP-4的地址單元。
    4) POP一個long型數(shù)據(jù),過程與PUSH相反,依次將ESP-4、ESP-3、ESP-2、ESP-1從棧內(nèi)彈出,放入一個32位寄存器。
    5) CALL指令用來調(diào)用一個函數(shù)或過程,此時,下一條指令地址會被壓入堆棧,以備返回時能恢復(fù)執(zhí)行下條指令。
    6) RET指令用來從一個函數(shù)或過程返回,之前CALL保存的下條指令地址會從棧內(nèi)彈出到EIP寄存器中,程序轉(zhuǎn)到CALL之前下條指令處執(zhí)行
    7) ENTER是建立當(dāng)前函數(shù)的?蚣,即相當(dāng)于以下兩條指令:
        pushl   %ebp
        movl    %esp,%ebp
    8) LEAVE是釋放當(dāng)前函數(shù)或者過程的?蚣埽聪喈(dāng)于以下兩條指令:
        movl ebp esp
        popl  ebp

    如果反匯編一個函數(shù),很多時候會在函數(shù)進(jìn)入和返回處,發(fā)現(xiàn)有類似如下形式的匯編語句:  
         
        pushl   %ebp            ; ebp寄存器內(nèi)容壓棧,即保存main函數(shù)的上級調(diào)用函數(shù)的棧基地址
        movl    %esp,%ebp       ; esp值賦給ebp,設(shè)置 main函數(shù)的;
        ...........             ; 以上兩條指令相當(dāng)于 enter 0,0
        ...........
        leave                   ; 將ebp值賦給esp,pop先前棧內(nèi)的上級函數(shù)棧的基地址給ebp,恢復(fù)原棧基址
        ret                     ; main函數(shù)返回,回到上級調(diào)用

    這些語句就是用來創(chuàng)建和釋放一個函數(shù)或者過程的?蚣艿摹
    原來編譯器會自動在函數(shù)入口和出口處插入創(chuàng)建和釋放?蚣艿恼Z句。
    函數(shù)被調(diào)用時:
    1) EIP/EBP成為新函數(shù)棧的邊界
    函數(shù)被調(diào)用時,返回時的EIP首先被壓入堆棧;創(chuàng)建?蚣軙r,上級函數(shù)棧的EBP被壓入堆棧,與EIP一道行成新函數(shù)棧框架的邊界
    2) EBP成為?蚣苤羔楽FP,用來指示新函數(shù)棧的邊界
    ?蚣芙⒑螅珽BP指向的棧的內(nèi)容就是上一級函數(shù)棧的EBP,可以想象,通過EBP就可以把層層調(diào)用函數(shù)的棧都回朔遍歷一遍,調(diào)試器就是利用這個特性實(shí)現(xiàn) backtrace功能的
    3) ESP總是作為棧指針指向棧頂,用來分配?臻g
    棧分配空間給函數(shù)局部變量時的語句通常就是給ESP減去一個常數(shù)值,例如,分配一個整型數(shù)據(jù)就是 ESP-4
    4) 函數(shù)的參數(shù)傳遞和局部變量訪問可以通過SFP即EBP來實(shí)現(xiàn)  
    由于棧框架指針永遠(yuǎn)指向當(dāng)前函數(shù)的;刂,參數(shù)和局部變量訪問通常為如下形式:
        +8+xx(%ebp)         ; 函數(shù)入口參數(shù)的的訪問
        -xx(%ebp)           ; 函數(shù)局部變量訪問
            
    假如函數(shù)A調(diào)用函數(shù)B,函數(shù)B調(diào)用函數(shù)C ,則函數(shù)?蚣芗罢{(diào)用關(guān)系如下圖所示:

下圖有點(diǎn)亂,因此刪去部分內(nèi)容,要看原圖可參考我的blog
+----------------------------+---->; 高地址
| EIP (上級函數(shù)返回地址)  |     
+----------------------------+   
| EBP (上級函數(shù)的EBP)     |  
+----------------------------+
| Local Variables            |   
| ..........                          |  
+-----------------------------+   
| Arg n(函數(shù)B的第n個參數(shù)) |   
+-----------------------------+
| Arg .(函數(shù)B的第.個參數(shù))   |  
+-----------------------------+  
| Arg 1(函數(shù)B的第1個參數(shù)) |  
+-----------------------------+   
| Arg 0(函數(shù)B的第0個參數(shù)) |  
+-----------------------------+  
EIP (A函數(shù)的返回地址)      |
+-----------------------------+  
| EBP (A函數(shù)的EBP)          |
+-----------------------------+   
| Local Variables             |   
| ..........                           |   
+-----------------------------+  
| Arg n(函數(shù)C的第n個參數(shù)) |   
+-----------------------------+   
| Arg .(函數(shù)C的第.個參數(shù))   |   
+-----------------------------+   
| Arg 1(函數(shù)C的第1個參數(shù)) |  
+-----------------------------+   
| Arg 0(函數(shù)C的第0個參數(shù)) |   
+-----------------------------+   
| EIP (B函數(shù)的返回地址)     |  
+-----------------------------+  
| EBP (B函數(shù)的EBP)          |  
+-----------------------------+
| Local Variables             |
| ..........                           |  
+-----------------------------+--->; 低地址

   圖 1-1  
        
    再分析test1反匯編結(jié)果中剩余部分語句的含義:
         
    # mdb test1
    Loading modules: [ libc.so.1 ]
    >; main::dis                        ; 反匯編main函數(shù)
    main:          pushl   %ebp                             
    main+1:        movl    %esp,%ebp        ; 創(chuàng)建Stack Frame(棧框架)
    main+3:       subl    $8,%esp       ; 通過ESP-8來分配8字節(jié)堆?臻g
    main+6:       andl    $0xf0,%esp    ; 使棧地址16字節(jié)對齊
    main+9:       movl    $0,%eax       ; 無意義
    main+0xe:     subl    %eax,%esp     ; 無意義
    main+0x10:     movl    $0,%eax          ; 設(shè)置main函數(shù)返回值
    main+0x15:     leave                    ; 撤銷Stack Frame(?蚣)
    main+0x16:     ret                      ; main 函數(shù)返回
    >;

    以下兩句似乎是沒有意義的,果真是這樣嗎?
        movl    $0,%eax  
        subl     %eax,%esp
        
    用gcc的O2級優(yōu)化來重新編譯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      ; 設(shè)置main返回值,使用xorl異或指令來使eax為0
    main+0xb:     leave
    main+0xc:     ret
    >;  
    新的反匯編結(jié)果比最初的結(jié)果要簡潔一些,果然之前被認(rèn)為無用的語句被優(yōu)化掉了,進(jìn)一步驗(yàn)證了之前的猜測。
    提示:編譯器產(chǎn)生的某些語句可能在程序?qū)嶋H語義上沒有用處,可以用優(yōu)化選項(xiàng)去掉這些語句。

    問題:為什么用xorl來設(shè)置eax的值?
    注意到優(yōu)化后的代碼中,eax返回值的設(shè)置由 movl $0,%eax 變?yōu)?xorl %eax,%eax ,這是因?yàn)镮A32指令中,xorl比movl有更高的運(yùn)行速度。

    概念:Stack aligned 棧對齊
    那么,以下語句到底是和作用呢?
        subl    $8,%esp
       andl    $0xf0,%esp     ; 通過andl使低4位為0,保證棧地址16字節(jié)對齊
        
    表面來看,這條語句最直接的后果是使ESP的地址后4位為0,即16字節(jié)對齊,那么為什么這么做呢?
    原來,IA32 系列CPU的一些指令分別在4、8、16字節(jié)對齊時會有更快的運(yùn)行速度,因此gcc編譯器為提高生成代碼在IA32上的運(yùn)行速度,默認(rèn)對產(chǎn)生的代碼進(jìn)行16字節(jié)對齊

        andl $0xf0,%esp 的意義很明顯,那么 subl $8,%esp 呢,是必須的嗎?
    這里假設(shè)在進(jìn)入main函數(shù)之前,棧是16字節(jié)對齊的話,那么,進(jìn)入main函數(shù)后,EIP和EBP被壓入堆棧后,棧地址最末4位二進(jìn)制位必定是 1000,esp -8則恰好使后4位地址二進(jìn)制位為0000?磥恚@也是為保證棧16字節(jié)對齊的。

    如果查一下gcc的手冊,就會發(fā)現(xiàn)關(guān)于棧對齊的參數(shù)設(shè)置:
    -mpreferred-stack-boundary=n    ; 希望棧按照2的n次的字節(jié)邊界對齊, n的取值范圍是2-12

    默認(rèn)情況下,n是等于4的,也就是說,默認(rèn)情況下,gcc是16字節(jié)對齊,以適應(yīng)IA32大多數(shù)指令的要求。

    讓我們利用-mpreferred-stack-boundary=2來去除棧對齊指令:
      
    # gcc -mpreferred-stack-boundary=2 test1.c -o test1
        
    >; main::dis
    main:       pushl   %ebp
    main+1:     movl    %esp,%ebp
    main+3:     movl    $0,%eax
    main+8:     leave
    main+9:     ret
    >;  

    可以看到,棧對齊指令沒有了,因?yàn)椋琁A32的棧本身就是4字節(jié)對齊的,不需要用額外指令進(jìn)行對齊。
    那么,?蚣苤羔楽FP是不是必須的呢?
    # gcc -mpreferred-stack-boundary=2 -fomit-frame-pointer test1.c -o test
    >; main::dis
    main:       movl    $0,%eax
    main+5:     ret
    >;  

    由此可知,-fomit-frame-pointer 可以去除SFP。
        
    問題:去除SFP后有什么缺點(diǎn)呢?
        
    1)增加調(diào)式難度
        由于SFP在調(diào)試器backtrace的指令中被使用到,因此沒有SFP該調(diào)試指令就無法使用。
    2)降低匯編代碼可讀性
        函數(shù)參數(shù)和局部變量的訪問,在沒有ebp的情況下,都只能通過+xx(esp)的方式訪問,而很難區(qū)分兩種方式,降低了程序的可讀性。
        
    問題:去除SFP有什么優(yōu)點(diǎn)呢?
        
    1)節(jié)省棧空間
    2)減少建立和撤銷棧框架的指令后,簡化了代碼
    3)使ebp空閑出來,使之作為通用寄存器使用,增加通用寄存器的數(shù)量
    4)以上3點(diǎn)使得程序運(yùn)行速度更快

    概念:Calling Convention  調(diào)用約定和 ABI (Application Binary Interface) 應(yīng)用程序二進(jìn)制接口
         
        函數(shù)如何找到它的參數(shù)?
        函數(shù)如何返回結(jié)果?
        函數(shù)在哪里存放局部變量?
        那一個硬件寄存器是起始空間?
        那一個硬件寄存器必須預(yù)先保留?

    Calling Convention  調(diào)用約定對以上問題作出了規(guī)定。Calling Convention也是ABI的一部分。
    因此,遵守相同ABI規(guī)范的操作系統(tǒng),使其相互間實(shí)現(xiàn)二進(jìn)制代碼的互操作成為了可能。
    例如:由于Solaris、Linux都遵守System V的ABI,Solaris 10就提供了直接運(yùn)行Linux二進(jìn)制程序的功能。
    詳見文章:關(guān)注: Solaris 10的10大新變化  
              
3. 小結(jié)
    本文通過最簡的C程序,引入以下概念:
        SFP ?蚣苤羔
        Stack aligned 棧對齊
        Calling Convention  調(diào)用約定 和 ABI (Application Binary Interface) 應(yīng)用程序二進(jìn)制接口
    今后,將通過進(jìn)一步的實(shí)驗(yàn),來深入了解這些概念。通過掌握這些概念,使在匯編級調(diào)試程序產(chǎn)生的core dump、掌握C語言高級調(diào)試技巧成為了可能。





歡迎光臨 (http://www.zg4o1577.cn/bbs/) Powered by Discuz! X3.1
主站蜘蛛池模板: 在线观看视频中文字幕 | 视频一二区 | 97视频免费 | 日韩中文字幕在线免费 | 在线欧美亚洲 | 999精彩视频 | 精品视频一区二区 | av永久免费 | 一级在线观看 | 国产真实精品久久二三区 | 日韩精品免费播放 | 四虎网站在线观看 | 国产精品久久久久久久久久久新郎 | 国产精品久久久 | av网站免费| 国产精品一区二区在线 | 99精品国产一区二区青青牛奶 | 日韩午夜影院 | 在线观看亚洲精品视频 | 免费美女网站 | 亚洲成人av | 亚洲久久一区 | 久久久噜噜噜久久中文字幕色伊伊 | 国产精品资源在线 | 精品国产免费一区二区三区五区 | 中文字幕高清av | 黄视频网站在线 | 精品国产区 | 毛片黄| 国产一区不卡 | 日本三级线观看 视频 | 中文字幕国产高清 | 亚洲精品在线观看网站 | 在线91| 免费观看一级特黄欧美大片 | 久久精品视频在线免费观看 | 国产欧美日韩一区 | 精品久久中文 | 激情小视频 | 欧美午夜一区 | 亚洲播放一区 |