色婷婷AⅤ一区二区三区|亚洲精品第一国产综合亚AV|久久精品官方网视频|日本28视频香蕉

          新聞中心

          EEPW首頁 > 嵌入式系統(tǒng) > 設計應用 > 匯編技術內(nèi)幕(2)

          匯編技術內(nèi)幕(2)

          作者: 時間:2016-11-24 來源:網(wǎng)絡 收藏
          問題:為什么用EAX寄存器保存函數(shù)返回值?

          實際上IA32并沒有規(guī)定用哪個寄存器來保存返回值。但如果反匯編Solaris/Linux的二進制文件,就會發(fā)現(xiàn),都用EAX保存函數(shù)返回值。這不是偶然現(xiàn)象,是操作系統(tǒng)的ABI(Application Binary Interface)來決定的。Solaris/Linux操作系統(tǒng)的ABI就是Sytem V ABI。

          本文引用地址:http://cafeforensic.com/article/201611/320808.htm


          概念:SFP (Stack Frame Pointer) ??蚣苤羔?br /> 正確理解SFP必須了解:
          IA32 的棧的概念
          CPU 中32位寄存器ESP/EBP的作用
          PUSH/POP 指令是如何影響棧的
          CALL/RET/LEAVE 等指令是如何影響棧的


          如我們所知:
          1)IA32的棧是用來存放臨時數(shù)據(jù),而且是LIFO,即后進先出的。棧的增長方向是從高地址向低地址增長,按字節(jié)為單位編址。
          2) EBP是?;返闹羔?,永遠指向棧底(高地址),ESP是棧指針,永遠指向棧頂(低地址)。
          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ù)或過程,此時,下一條指令地址會被壓入堆棧,以備返回時能恢復執(zhí)行下條指令。
          6) RET指令用來從一個函數(shù)或過程返回,之前CALL保存的下條指令地址會從棧內(nèi)彈出到EIP寄存器中,程序轉到CALL之前下條指令處執(zhí)行
          7) ENTER是建立當前函數(shù)的??蚣?,即相當于以下兩條指令:
          pushl %ebp
          movl %esp,%ebp
          8) LEAVE是釋放當前函數(shù)或者過程的棧框架,即相當于以下兩條指令:
          movl ebp esp
          popl ebp


          如果反匯編一個函數(shù),很多時候會在函數(shù)進入和返回處,發(fā)現(xiàn)有類似如下形式的匯編語句:
          pushl %ebp ; ebp寄存器內(nèi)容壓棧,即保存main函數(shù)的上級調(diào)用函數(shù)的?;刂?br /> movl %esp,%ebp ; esp值賦給ebp,設置 main函數(shù)的?;?br /> ........... ; 以上兩條指令相當于 enter 0,0
          ...........
          leave ; 將ebp值賦給esp,pop先前棧內(nèi)的上級函數(shù)棧的基地址給ebp,恢復原棧基址
          ret ; main函數(shù)返回,回到上級調(diào)用
          這些語句就是用來創(chuàng)建和釋放一個函數(shù)或者過程的??蚣艿摹?br /> 原來編譯器會自動在函數(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)試器就是利用這個特性實現(xiàn) backtrace功能的
          3) ESP總是作為棧指針指向棧頂,用來分配??臻g
          棧分配空間給函數(shù)局部變量時的語句通常就是給ESP減去一個常數(shù)值,例如,分配一個整型數(shù)據(jù)就是 ESP-4
          4) 函數(shù)的參數(shù)傳遞和局部變量訪問可以通過SFP即EBP來實現(xiàn)
          由于??蚣苤羔樣肋h指向當前函數(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)用關系如下圖所示:
          +-------------------------+----> 高地址
          | EIP (上級函數(shù)返回地址) |
          +-------------------------+
          +--> | EBP (上級函數(shù)的EBP) | --+ <------當前函數(shù)A的EBP (即SFP框架指針)
          | +-------------------------+ +-->偏移量A
          | | Local Variables | |
          | | .......... | --+ <------ESP指向函數(shù)A新分配的局部變量,局部變量可以通過A的ebp-偏移量A訪問
          | f +-------------------------+
          | r | Arg n(函數(shù)B的第n個參數(shù)) |
          | a +-------------------------+
          | m | Arg .(函數(shù)B的第.個參數(shù)) |
          | e +-------------------------+
          | | Arg 1(函數(shù)B的第1個參數(shù)) |
          | o +-------------------------+
          | f | Arg 0(函數(shù)B的第0個參數(shù)) | --+ <------ B函數(shù)的參數(shù)可以由B的ebp+偏移量B訪問
          | +-------------------------+ +--> 偏移量B
          | A | EIP (A函數(shù)的返回地址) | |
          | +-------------------------+ --+
          +--- | EBP (A函數(shù)的EBP) |<--+ <------ 當前函數(shù)B的EBP (即SFP框架指針)
          +-------------------------+ |
          | Local Variables | |
          | .......... | | <------ ESP指向函數(shù)B新分配的局部變量
          +-------------------------+ |
          | Arg n(函數(shù)C的第n個參數(shù)) | |
          +-------------------------+ |
          | Arg .(函數(shù)C的第.個參數(shù)) | |
          +-------------------------+ +--> frame of B
          | Arg 1(函數(shù)C的第1個參數(shù)) | |
          +-------------------------+ |
          | Arg 0(函數(shù)C的第0個參數(shù)) | |
          +-------------------------+ |
          | EIP (B函數(shù)的返回地址) | |
          +-------------------------+ |
          +--> | EBP (B函數(shù)的EBP) | --+ <------ 當前函數(shù)C的EBP (即SFP框架指針)
          | +-------------------------+
          | | Local Variables |
          | | .......... | <------ ESP指向函數(shù)C新分配的局部變量
          | +-------------------------+----> 低地址
          frame of C

          圖 1-1

          再分析test1反匯編結果中剩余部分語句的含義:

          # 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 ; 設置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 ; 設置main返回值,使用xorl異或指令來使eax為0
          main+0xb: leave
          main+0xc: ret
          >
          新的反匯編結果比最初的結果要簡潔一些,果然之前被認為無用的語句被優(yōu)化掉了,進一步驗證了之前的猜測。
          提示:編譯器產(chǎn)生的某些語句可能在程序實際語義上沒有用處,可以用優(yōu)化選項去掉這些語句。


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


          概念:Stack aligned 棧對齊
          那么,以下語句到底是和作用呢?
          subl $8,%esp
          andl $0xf0,%esp ; 通過andl使低4位為0,保證棧地址16字節(jié)對齊

          表面來看,這條語句最直接的后果是使ESP的地址后4位為0,即16字節(jié)對齊,那么為什么這么做呢?
          原來,IA32 系列CPU的一些指令分別在4、8、16字節(jié)對齊時會有更快的運行速度,因此gcc編譯器為提高生成代碼在IA32上的運行速度,默認對產(chǎn)生的代碼進行16字節(jié)對齊
          andl $0xf0,%esp 的意義很明顯,那么 subl $8,%esp 呢,是必須的嗎?
          這里假設在進入main函數(shù)之前,棧是16字節(jié)對齊的話,那么,進入main函數(shù)后,EIP和EBP被壓入堆棧后,棧地址最末4位二進制位必定是1000,esp -8則恰好使后4位地址二進制位為0000??磥?,這也是為保證棧16字節(jié)對齊的。
          如果查一下gcc的手冊,就會發(fā)現(xiàn)關于棧對齊的參數(shù)設置:
          -mpreferred-stack-boundary=n ; 希望棧按照2的n次的字節(jié)邊界對齊, n的取值范圍是2-12
          默認情況下,n是等于4的,也就是說,默認情況下,gcc是16字節(jié)對齊,以適應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
          >
          可以看到,棧對齊指令沒有了,因為,IA32的棧本身就是4字節(jié)對齊的,不需要用額外指令進行對齊。
          那么,??蚣苤羔楽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后有什么缺點呢?
          1)增加調(diào)式難度
          由于SFP在調(diào)試器backtrace的指令中被使用到,因此沒有SFP該調(diào)試指令就無法使用。
          2)降低匯編代碼可讀性
          函數(shù)參數(shù)和局部變量的訪問,在沒有ebp的情況下,都只能通過+xx(esp)的方式訪問,而很難區(qū)分兩種方式,降低了程序的可讀性。


          問題:去除SFP有什么優(yōu)點呢?
          1)節(jié)省??臻g
          2)減少建立和撤銷??蚣艿闹噶詈?,簡化了代碼
          3)使ebp空閑出來,使之作為通用寄存器使用,增加通用寄存器的數(shù)量
          4)以上3點使得程序運行速度更快


          概念:Calling Convention 調(diào)用約定和 ABI (Application Binary Interface) 應用程序二進制接口
          函數(shù)如何找到它的參數(shù)?
          函數(shù)如何返回結果?
          函數(shù)在哪里存放局部變量?
          那一個硬件寄存器是起始空間?
          那一個硬件寄存器必須預先保留?
          Calling Convention 調(diào)用約定對以上問題作出了規(guī)定。Calling Convention也是ABI的一部分。
          因此,遵守相同ABI規(guī)范的操作系統(tǒng),使其相互間實現(xiàn)二進制代碼的互操作成為了可能。例如:由于Solaris、Linux都遵守System V的ABI,Solaris 10就提供了直接運行Linux二進制程序的功能。



          關鍵詞: 匯編技術內(nèi)

          評論


          技術專區(qū)

          關閉