• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            如果在Linux平臺(tái)可以用gdb進(jìn)行反匯編和調(diào)試。(轉(zhuǎn))

            2. 最簡(jiǎn)C代碼分析

                為簡(jiǎn)化問(wèn)題,來(lái)分析一下最簡(jiǎn)的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是一個(gè)ELF格式32位小端(Little Endian)的可執(zhí)行文件,動(dòng)態(tài)鏈接并且符號(hào)表沒(méi)有去除。
                這正是Unix/Linux平臺(tái)典型的可執(zhí)行文件格式。
                用mdb反匯編可以觀察生成的匯編代碼:

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

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

                問(wèn)題:誰(shuí)調(diào)用了 main函數(shù)?
                
                 在C語(yǔ)言的層面來(lái)看,main函數(shù)是一個(gè)程序的起始入口點(diǎn),而實(shí)際上,ELF可執(zhí)行文件的入口點(diǎn)并不是main而是_start。
                 mdb也可以反匯編_start:
                  
                > _start::dis                       ;從_start 的地址開(kāi)始反匯編
                _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
                >

                問(wèn)題:為什么用EAX寄存器保存函數(shù)返回值?
                實(shí)際上IA32并沒(méi)有規(guī)定用哪個(gè)寄存器來(lái)保存返回值。但如果反匯編Solaris/Linux的二進(jìn)制文件,就會(huì)發(fā)現(xiàn),都用EAX保存函數(shù)返回值。
                這不是偶然現(xiàn)象,是操作系統(tǒng)的ABI(Application Binary Interface)來(lái)決定的。
                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的棧是用來(lái)存放臨時(shí)數(shù)據(jù),而且是LIFO,即后進(jìn)先出的。棧的增長(zhǎng)方向是從高地址向低地址增長(zhǎng),按字節(jié)為單位編址。
                2) EBP是棧基址的指針,永遠(yuǎn)指向棧底(高地址),ESP是棧指針,永遠(yuǎn)指向棧頂(低地址)。
                3) PUSH一個(gè)long型數(shù)據(jù)時(shí),以字節(jié)為單位將數(shù)據(jù)壓入棧,從高到低按字節(jié)依次將數(shù)據(jù)存入ESP-1、ESP-2、ESP-3、ESP-4的地址單元。
                4) POP一個(gè)long型數(shù)據(jù),過(guò)程與PUSH相反,依次將ESP-4、ESP-3、ESP-2、ESP-1從棧內(nèi)彈出,放入一個(gè)32位寄存器。
                5) CALL指令用來(lái)調(diào)用一個(gè)函數(shù)或過(guò)程,此時(shí),下一條指令地址會(huì)被壓入堆棧,以備返回時(shí)能恢復(fù)執(zhí)行下條指令。
                6) RET指令用來(lái)從一個(gè)函數(shù)或過(guò)程返回,之前CALL保存的下條指令地址會(huì)從棧內(nèi)彈出到EIP寄存器中,程序轉(zhuǎn)到CALL之前下條指令處執(zhí)行
                7) ENTER是建立當(dāng)前函數(shù)的棧框架,即相當(dāng)于以下兩條指令:
                    pushl   %ebp
                    movl    %esp,%ebp
                8) LEAVE是釋放當(dāng)前函數(shù)或者過(guò)程的棧框架,即相當(dāng)于以下兩條指令:
                    movl ebp esp
                    popl  ebp

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

                這些語(yǔ)句就是用來(lái)創(chuàng)建和釋放一個(gè)函數(shù)或者過(guò)程的棧框架的。
                原來(lái)編譯器會(huì)自動(dòng)在函數(shù)入口和出口處插入創(chuàng)建和釋放棧框架的語(yǔ)句。
                函數(shù)被調(diào)用時(shí):
                1) EIP/EBP成為新函數(shù)棧的邊界
                函數(shù)被調(diào)用時(shí),返回時(shí)的EIP首先被壓入堆棧;創(chuàng)建棧框架時(shí),上級(jí)函數(shù)棧的EBP被壓入堆棧,與EIP一道行成新函數(shù)棧框架的邊界
                2) EBP成為棧框架指針SFP,用來(lái)指示新函數(shù)棧的邊界
                棧框架建立后,EBP指向的棧的內(nèi)容就是上一級(jí)函數(shù)棧的EBP,可以想象,通過(guò)EBP就可以把層層調(diào)用函數(shù)的棧都回朔遍歷一遍,調(diào)試器就是利用這個(gè)特性實(shí)現(xiàn) backtrace功能的
                3) ESP總是作為棧指針指向棧頂,用來(lái)分配棧空間
                棧分配空間給函數(shù)局部變量時(shí)的語(yǔ)句通常就是給ESP減去一個(gè)常數(shù)值,例如,分配一個(gè)整型數(shù)據(jù)就是 ESP-4
                4) 函數(shù)的參數(shù)傳遞和局部變量訪(fǎng)問(wèn)可以通過(guò)SFP即EBP來(lái)實(shí)現(xiàn)
                由于棧框架指針永遠(yuǎn)指向當(dāng)前函數(shù)的棧基地址,參數(shù)和局部變量訪(fǎng)問(wèn)通常為如下形式:
                    +8+xx(%ebp)         ; 函數(shù)入口參數(shù)的的訪(fǎng)問(wèn)
                    -xx(%ebp)           ; 函數(shù)局部變量訪(fǎng)問(wèn)
                       
                假如函數(shù)A調(diào)用函數(shù)B,函數(shù)B調(diào)用函數(shù)C ,則函數(shù)棧框架及調(diào)用關(guān)系如下圖所示:
               	+-------------------------+----> 高地址
            | EIP (上級(jí)函數(shù)返回地址) |
            +-------------------------+
            +--> | EBP (上級(jí)函數(shù)的EBP) | --+ <------當(dāng)前函數(shù)A的EBP (即SFP框架指針)
            | +-------------------------+ +-->偏移量A
            | | Local Variables | |
            | | .......... | --+ <------ESP指向函數(shù)A新分配的局部變量,局部變量可以通過(guò)A的ebp-偏移量A訪(fǎng)問(wèn)
            | f +-------------------------+
            | r | Arg n(函數(shù)B的第n個(gè)參數(shù)) |
            | a +-------------------------+
            | m | Arg .(函數(shù)B的第.個(gè)參數(shù)) |
            | e +-------------------------+
            | | Arg 1(函數(shù)B的第1個(gè)參數(shù)) |
            | o +-------------------------+
            | f | Arg 0(函數(shù)B的第0個(gè)參數(shù)) | --+ <------ B函數(shù)的參數(shù)可以由B的ebp+偏移量B訪(fǎng)問(wèn)
            | +-------------------------+ +--> 偏移量B
            | A | EIP (A函數(shù)的返回地址) | |
            | +-------------------------+ --+
            +--- | EBP (A函數(shù)的EBP) |<--+ <------ 當(dāng)前函數(shù)B的EBP (即SFP框架指針)
            +-------------------------+ |
            | Local Variables | |
            | .......... | | <------ ESP指向函數(shù)B新分配的局部變量
            +-------------------------+ |
            | Arg n(函數(shù)C的第n個(gè)參數(shù)) | |
            +-------------------------+ |
            | Arg .(函數(shù)C的第.個(gè)參數(shù)) | |
            +-------------------------+ +--> frame of B
            | Arg 1(函數(shù)C的第1個(gè)參數(shù)) | |
            +-------------------------+ |
            | Arg 0(函數(shù)C的第0個(gè)參數(shù)) | |
            +-------------------------+ |
            | EIP (B函數(shù)的返回地址) | |
            +-------------------------+ |
            +--> | EBP (B函數(shù)的EBP) | --+ <------ 當(dāng)前函數(shù)C的EBP (即SFP框架指針)
            | +-------------------------+
            | | Local Variables |
            | | .......... | <------ ESP指向函數(shù)C新分配的局部變量
            | +-------------------------+----> 低地址
            frame of C

            圖 1-1
                  
                再分析test1反匯編結(jié)果中剩余部分語(yǔ)句的含義:
                   
                # 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       ; 通過(guò)ESP-8來(lái)分配8字節(jié)堆棧空間
                main+6:       andl    $0xf0,%esp    ; 使棧地址16字節(jié)對(duì)齊
                main+9:       movl    $0,%eax       ; 無(wú)意義
                main+0xe:     subl    %eax,%esp     ; 無(wú)意義
                main+0x10:     movl    $0,%eax          ; 設(shè)置main函數(shù)返回值
                main+0x15:     leave                    ; 撤銷(xiāo)Stack Frame(棧框架)
                main+0x16:     ret                      ; main 函數(shù)返回
                >

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

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

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

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

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

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

                讓我們利用-mpreferred-stack-boundary=2來(lái)去除棧對(duì)齊指令:
                 
                # 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
                >

                可以看到,棧對(duì)齊指令沒(méi)有了,因?yàn)椋琁A32的棧本身就是4字節(jié)對(duì)齊的,不需要用額外指令進(jìn)行對(duì)齊。
                那么,棧框架指針SFP是不是必須的呢?
                # 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。
                  
                問(wèn)題:去除SFP后有什么缺點(diǎn)呢?
                  
                1)增加調(diào)式難度
                    由于SFP在調(diào)試器backtrace的指令中被使用到,因此沒(méi)有SFP該調(diào)試指令就無(wú)法使用。
                2)降低匯編代碼可讀性
                    函數(shù)參數(shù)和局部變量的訪(fǎng)問(wèn),在沒(méi)有ebp的情況下,都只能通過(guò)+xx(esp)的方式訪(fǎng)問(wèn),而很難區(qū)分兩種方式,降低了程序的可讀性。
                  
                問(wèn)題:去除SFP有什么優(yōu)點(diǎn)呢?
                  
                1)節(jié)省棧空間
                2)減少建立和撤銷(xiāo)棧框架的指令后,簡(jiǎn)化了代碼
                3)使ebp空閑出來(lái),使之作為通用寄存器使用,增加通用寄存器的數(shù)量
                4)以上3點(diǎn)使得程序運(yùn)行速度更快

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

                Calling Convention  調(diào)用約定對(duì)以上問(wèn)題作出了規(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)制程序的功能。
                詳見(jiàn)文章:
            關(guān)注: Solaris 10的10大新變化
                        
            3. 小結(jié)
                本文通過(guò)最簡(jiǎn)的C程序,引入以下概念:
                    SFP 棧框架指針
                    Stack aligned 棧對(duì)齊
                    Calling Convention  調(diào)用約定 和 ABI (Application Binary Interface) 應(yīng)用程序二進(jìn)制接口
                今后,將通過(guò)進(jìn)一步的實(shí)驗(yàn),來(lái)深入了解這些概念。通過(guò)掌握這些概念,使在匯編級(jí)調(diào)試程序產(chǎn)生的core dump、掌握C語(yǔ)言高級(jí)調(diào)試技巧成為了可能。

            Feedback

            # re: Linux平臺(tái)可以用gdb進(jìn)行反匯編和調(diào)試。  回復(fù)  更多評(píng)論   

            2012-04-02 20:57 by 胡海生
            樓主牛B

            # re: Linux平臺(tái)可以用gdb進(jìn)行反匯編和調(diào)試。  回復(fù)  更多評(píng)論   

            2016-05-18 09:08 by
            找LINUX下的硬件解密和反匯編工程師
            欧美一区二区三区久久综| 久久久综合香蕉尹人综合网| 一级a性色生活片久久无| 青青草原综合久久| 国产高潮国产高潮久久久91 | 久久精品成人影院| 久久久久夜夜夜精品国产| 国产成人久久激情91| 久久精品国产亚洲沈樵| 久久国产精品99久久久久久老狼| 无码伊人66久久大杳蕉网站谷歌| 久久久久亚洲av成人网人人软件 | 99久久99久久精品国产片| 久久免费高清视频| 久久er国产精品免费观看8| 久久免费大片| 亚洲国产精品无码久久| 粉嫩小泬无遮挡久久久久久| 久久精品国产69国产精品亚洲| 久久er热视频在这里精品| 久久国产成人亚洲精品影院| 亚洲精品美女久久久久99小说| 久久人妻少妇嫩草AV蜜桃| 久久久久亚洲AV无码麻豆| 亚洲午夜精品久久久久久人妖| 久久久久99精品成人片三人毛片| 久久综合亚洲色HEZYO社区| 成人国内精品久久久久一区| 精品无码人妻久久久久久| 久久久亚洲裙底偷窥综合| 国产成人精品久久二区二区| 亚洲日本久久久午夜精品| 99精品国产在热久久| 久久久这里只有精品加勒比| 久久噜噜电影你懂的| 久久久久亚洲AV成人网人人网站| 秋霞久久国产精品电影院| 久久久久亚洲av成人网人人软件| 亚洲一区二区三区日本久久九| 久久无码国产专区精品| 国产精自产拍久久久久久蜜|