• <ruby id="5koa6"></ruby>
    <ruby id="5koa6"><option id="5koa6"><thead id="5koa6"></thead></option></ruby>

    <progress id="5koa6"></progress>

  • <strong id="5koa6"></strong>
  • X86匯編語言學習手記(2)

    發表于:2007-05-25來源:作者:點擊數: 標簽:X86bea66ddae0語言學習匯編手記
    [b:bea66ddae0]抱歉,文中的一些鏈接參考文檔在轉帖中丟失,文章排版也有些混亂,如果需要參考可以看我blog上的原文,另外X86匯編語言學習手記(1)也更新了[/b:bea66ddae0] [b:bea66ddae0]X86匯編語言學習手記(2)[/b:bea66ddae0] 作者:Badcoffee Email:blog.o

    [b:bea66ddae0]抱歉,文中的一些鏈接參考文檔在轉帖中丟失,文章排版也有些混亂,如果需要參考可以看我blog上的原文,另外X86匯編語言學習手記(1)也更新了[/b:bea66ddae0]

    [b:bea66ddae0]X86匯編語言學習手記(2)[/b:bea66ddae0]

    作者: Badcoffee
    Email: blog.oliver@gmail.com
    2004年11月

    原文出處: [url]http://blog.csdn.net/yayong[/url]
    版權所有: 轉載時請務必以超鏈接形式標明文章原始出處、作者信息及本聲明

    這是作者在學習X86匯編過程中的學習筆記,難免有錯誤和疏漏之處,歡迎指正。作者將隨時修改錯誤并將新的版本發布在自己的Blog站點上。嚴格說來,本篇文檔更側重于C語言和C編譯器方面的知識,如果涉及到基本的匯編語言的內容,可以參考相關文檔。
    自X86 匯編語言學習手記(1)在作者的Blog上發布以來,得到了很多網友的肯定和鼓勵,并且還有熱心網友指出了其中的錯誤,[b:bea66ddae0]作者已經將文檔中已發現的錯誤修正后更新在Blog上。[/b:bea66ddae0]

        上一篇文章通過分析一個最簡的C程序,引出了以下概念:
            Stack Frame ??蚣?nbsp;和 SFP ??蚣苤羔?
            Stack aligned 棧對齊
            Calling Convention  調用約定 和 ABI (Application Binary Interface) 應用程序二進制接口
        本章中,將通過進一步的實驗,來深入了解這些概念。如果還不了解這些概念,可以參考 X86匯編語言學習手記(1)。
            
    1. 局部變量的棧分配

        上篇文章已經分析過一個最簡的C程序,
        下面我們分析一下C編譯器如何處理局部變量的分配,為此先給出如下程序:

        #vi test2.c 

        int main()
        {
            int i;
            int j=2;
            i=3;
            i=++i;
            return i+j;
        }

        編譯該程序,產生二進制文件,并利用mdb來觀察程序運行中的stack的狀態:
        #gclearcase/" target="_blank" >cc test2.c -o test2
        #mdb test2
        Loading modules: [ libc.so.1 ]
        > main::dis
        main:           pushl   %ebp
        main+1:         movl    %esp,%ebp          ; main至main+1,創建Stack Frame
        main+3:         subl    $8,%esp            ; 為局部變量i,j分配??臻g,并保證棧16字節對齊
        main+6:         andl    $0xf0,%esp
        main+9:         movl    $0,%eax
        main+0xe:       subl    %eax,%esp          ; main+6至main+0xe,再次保證棧16字節對齊 
        main+0x10:      movl    $2,-8(%ebp)        ; 初始化局部變量j的值為2
        main+0x17:      movl    $3,-4(%ebp)        ; 給局部變量i賦值為3
        main+0x1e:      leal    -4(%ebp),%eax      ; 將局部變量i的地址裝入到EAX寄存器中
        main+0x21:      incl    (%eax)             ; i++
        main+0x23:      movl    -8(%ebp),%eax      ; 將j的值裝入EAX
        main+0x26:      addl    -4(%ebp),%eax      ; i+j并將結果存入EAX,作為返回值
        main+0x29:      leave                    ; 撤銷Stack Frame 
        main+0x2a:      ret                      ; main函數返回
        > 
        > main+0x10:b         ; 在地址 main+0x10處設置斷點
        > main+0x1e:b         ; 在main+0x1e設置斷點
        > main+0x29:b         ; 在main+0x1e設置斷點
        > main+0x2a:b         ; 在main+0x1e設置斷點
            
        下面的mdb的4個命令在一行輸入,中間用分號間隔開,命令的含義在注釋中給出:
        > :r;<esp,10/nap;<ebp=X;<eax=X    ; 運行程序(:r 命令)
        mdb: stop at main+0x10               ; 以ESP寄存器為起始地址,指定格式輸出16字節的棧內容(<esp,10/nap 命令)
        mdb: target stopped at:                ; 在最后輸出EBP和EAX寄存器的值(<ebp=X 命令 和<eax=X 命令)
        main+0x10:      movl    $2,-8(%ebp)    ; 程序運行后在main +0x10處指令執行前中斷,此時棧分配后還未初始化
        0x8047db0:      
        0x8047db0:      0xddbebca0             ; 這是變量j,4字節,未初始化,此處為棧頂,ESP的值就是0x8047db0   
        0x8047db4:      0xddbe137f             ; 這是變量i, 4字節,未初始化
        0x8047db8:      0x8047dd8              ; 這是_start的SFP(_start的EBP),4字節,由main 的SFP指向它
        0x8047dbc:      _start+0x5d            ; 這是_start調用main之前壓棧的下條指令地址,main返回后將恢復給EIP
        0x8047dc0:      1               
        0x8047dc4:      0x8047de4       
        0x8047dc8:      0x8047dec       
        0x8047dcc:      _start+0x35     
        0x8047dd0:      _fini           
        0x8047dd4:      ld.so.1`atexit_fini
        0x8047dd8:      0                      ; _start的SFP指向的內容為0,證明_start是程序的入口
        0x8047ddc:      0               
        0x8047de0:      1               
        0x8047de4:      0x8047eb4       
        0x8047de8:      0               
        0x8047dec:      0x8047eba       
                        8047db8              ; 這是main當前EBP寄存器的值,即main的SFP
                        0                  ; EAX的值,當前為0

        > :c;<esp,10/nap;<ebp=X;<eax=X    ; 繼續運行程序(:c 命令),其余3命令同上,打印16字節棧和EBP,EAX內容
        mdb: stop at main+0x1e
        mdb: target stopped at:
        main+0x1e:      leal    -4(%ebp),%eax  ; 程序運行到斷點main+0x1e處停止,此時局部變量i,j賦值已完成
        0x8047db0:      
        0x8047db0:      2                      ; 這是變量j,4字節,值為2,此處為棧頂,ESP的值就是0x8047db0
        0x8047db4:      3                      ; 這是變量i,4字節,值為3 
        0x8047db8:      0x8047dd8              ; 這是_start的SFP,4字節
        0x8047dbc:      _start+0x5d            ; 這是返回_start后的EIP
        0x8047dc0:      1               
        0x8047dc4:      0x8047de4       
        0x8047dc8:      0x8047dec       
        0x8047dcc:      _start+0x35     
        0x8047dd0:      _fini           
        0x8047dd4:      ld.so.1`atexit_fini
        0x8047dd8:      0               
        0x8047ddc:      0               
        0x8047de0:      1               
        0x8047de4:      0x8047eb4       
        0x8047de8:      0               
        0x8047dec:      0x8047eba       
                        8047db8              ; 這是main當前EBP寄存器的值,即main的SFP
                        0                  ; EAX的值,當前為0
        > :c;<esp,10/nap;<ebp=X;<eax=X    ; 繼續運行程序,打印16字節棧和EBP,EAX內容
        mdb: stop at main+0x29
        mdb: target stopped at:
        main+0x29:      leave                  ; 運行到斷點main+0x29處停止,計算已經完成,即將撤銷Stack Frame
        0x8047db0:      
        0x8047db0:      2                      ; 這是變量j,4字節,值為2,此處為棧頂,ESP的值就是0x8047db0       
        0x8047db4:      4                      ; 這是i++以后的變量i,4字節,值為3
        0x8047db8:      0x8047dd8              ; 這是_start的SFP,4字節
        0x8047dbc:      _start+0x5d            ; 這是返回_start后的EIP
        0x8047dc0:      1               
        0x8047dc4:      0x8047de4       
        0x8047dc8:      0x8047dec       
        0x8047dcc:      _start+0x35     
        0x8047dd0:      _fini           
        0x8047dd4:      ld.so.1`atexit_fini
        0x8047dd8:      0               
        0x8047ddc:      0               
        0x8047de0:      1               
        0x8047de4:      0x8047eb4       
        0x8047de8:      0               
        0x8047dec:      0x8047eba       
                        8047db8              ; 這是main當前EBP寄存器的值,即main的SFP        
                        6                  ; EAX的值,即函數的返回值,當前為6               
        > :c;<esp,10/nap;<ebp=X;<eax=X    ; 繼續運行程序,打印16字節棧和EBP,EAX內容
        mdb: stop at main+0x2a
        mdb: target stopped at:
        main+0x2a:      ret                  ; 運行到斷點main+0x2a處停止,Stack Frame已被撤銷,main即將返回
        0x8047dbc:      
        0x8047dbc:      _start+0x5d            ; Stack Frame已經被撤銷,棧頂是返回_start后的EIP,main的棧已被釋放
        0x8047dc0:      1               
        0x8047dc4:      0x8047de4       
        0x8047dc8:      0x8047dec       
        0x8047dcc:      _start+0x35     
        0x8047dd0:      _fini           
        0x8047dd4:      ld.so.1`atexit_fini
        0x8047dd8:      0               
        0x8047ddc:      0               
        0x8047de0:      1               
        0x8047de4:      0x8047eb4       
        0x8047de8:      0               
        0x8047dec:      0x8047eba       
        0x8047df0:      0x8047ed6       
        0x8047df4:      0x8047edd       
        0x8047df8:      0x8047ee4       
                        8047dd8            ; _start的SFP,之前存儲在地址0x8047db8處,main的Stack Frame撤銷時恢復                            6                 ; EAX的值,即函數的返回值,當前為6               
        > :s;<esp,10/nap;<ebp=X;<eax=X   ; 單步執行下條指令(:s 命令),打印16字節棧和EBP,EAX內容
        mdb: target stopped at:
        _start+0x5d:    addl    $0xc,%esp     ; 此時main已經返回,_start+0x5d曾經存儲在地址0x8047dbc處
        0x8047dc0:      
        0x8047dc0:      1                      ; main已經返回,_start +0x5d已經被彈出
        0x8047dc4:      0x8047de4       
        0x8047dc8:      0x8047dec       
        0x8047dcc:      _start+0x35     
        0x8047dd0:      _fini           
        0x8047dd4:      ld.so.1`atexit_fini
        0x8047dd8:      0                      ; _start的SFP指向的內容為0,證明_start是程序的入口               
        0x8047ddc:      0               
        0x8047de0:      1               
        0x8047de4:      0x8047eb4       
        0x8047de8:      0               
        0x8047dec:      0x8047eba       
        0x8047df0:      0x8047ed6       
        0x8047df4:      0x8047edd       
        0x8047df8:      0x8047ee4       
        0x8047dfc:      0x8047ef3       
                        8047dd8            ; _start的SFP,之前存儲在地址0x8047db8處,main的Stack Frame撤銷時恢復  
                        6                 ; EAX的值為6,還是main函數的返回值                
        > 

        通過mdb對程序運行時的寄存器和棧的觀察和分析,可以得出局部變量在棧中的訪問和分配及釋放方式:
            1.局部變量的分配,可以通過esp減去所需字節數
                subl    $8,%esp
            2.局部變量的釋放,可以通過leave指令 
                leave       
            3.局部變量的訪問,可以通過ebp減去偏移量
                movl    -8(%ebp),%eax
                addl    -4(%ebp),%eax

        問題:當存在2個以上的局部變量時,如何進行棧對齊?
        在上篇文章中,提到subl $8,%esp語句除了分配??臻g外,還有一個作用就是棧對齊。那么本例中,由于i和j正好是8字節,那么如果存在2個以上的局部變量時,如何同時滿足空間分配和棧對齊呢?

    2. 兩個以上的局部變量的棧分配

        在之前的C程序中,增加局部變量定義k,程序如下:
        # vi test3.c

        int main()
        {
            int i, j=2, k=4;
            i=3;
            i=++i;
            k=i+j+k;
            return k;
        }

        編譯該程序后,用mdb反匯編得出如下結果:
        # gcc test3.c -o test3    
        # mdb test3
        Loading modules: [ libc.so.1 ]
        > main::dis
        main:               pushl   %ebp
        main+1:             movl    %esp,%ebp            ; main至main+1,創建Stack Frame
        main+3:            subl   $0x18,%esp         ; 為局部變量i,j,k分配??臻g,并保證棧16字節對齊
        main+6:             andl    $0xf0,%esp
        main+9:             movl    $0,%eax
        main+0xe:           subl    %eax,%esp            ; main+6至main+0xe,再次保證棧16字節對齊
        main+0x10:          movl    $2,-8(%ebp)          ; j=2
        main+0x17:          movl    $4,-0xc(%ebp)        ; k=4
        main+0x1e:          movl    $3,-4(%ebp)          ; i=3
        main+0x25:          leal    -4(%ebp),%eax        ; 將i的地址裝入到EAX
        main+0x28:          incl    (%eax)               ; i++
        main+0x2a:          movl    -8(%ebp),%eax        ; 將j的值裝入到 EAX
        main+0x2d:          movl    -4(%ebp),%edx        ; 將i的值裝入到 EDX
        main+0x30:          addl    %eax,%edx            ; j+i,結果存入EDX
        main+0x32:          leal    -0xc(%ebp),%eax      ; 將k的地址裝入到EAX
        main+0x35:          addl    %edx,(%eax)          ; i+j+k,結果存入地址ebp-0xc即k中
        main+0x37:          movl    -0xc(%ebp),%eax      ; 將k的值裝入EAX,作為返回值
        main+0x3a:          leave                        ; 撤銷Stack Frame
        main+0x3b:          ret                          ; main函數返回
        > 
      

        問題:為什么3個變量分配了0x18字節的??臻g?
        在2個變量的時候,分配??臻g的指令是:subl $8,%esp
        而在3個局部變量的時候,分配??臻g的指令是:subl $0x18,%esp
        3個整型變量只需要0xc字節,為何實際上分配了0x18字節呢?
        答案就是:保持16字節棧對齊。

        在X86 匯編語言學習手記(1)里,已經說明過gcc默認的編譯是要16字節棧對齊的,subl $8,%esp會使棧16字節對齊,而8字節空間只能滿足2個局部變量,如果再分配4字節滿足第3個局部變量的話,那棧地址就不再16字節對齊的,而同時滿足空間需要而且保持16字節棧對齊的最接近的就是0x18。

        如果,各定義一個50字節和100字節的字符數組,在這種情況下,實際分配多少??臻g呢?答案是0x8+0x40+0x70,即184字節。
        下面動手驗證一下:

        # vi test4.c
        int main()
        {
            char str1[50];
            char str2[100];
            return 0;
        }
        # mdb test4
        Loading modules: [ libc.so.1 ]
        > main::dis
        main:               pushl   %ebp
        main+1:             movl    %esp,%ebp
        main+3:            subl   $0xb8,%esp   ; 為兩個字符數組分配??臻g,同時保證16字節對齊
        main+9:             andl    $0xf0,%esp
        main+0xc:           movl    $0,%eax
        main+0x11:          subl    %eax,%esp
        main+0x13:          movl    $0,%eax
        main+0x18:          leave
        main+0x19:          ret
        > 0xb8=D                              ; 16進制換算10進制
                        184             
        > 0x40+0x70+0x8=X                     ; 表達式計算,結果指定為16進制
                        b8              
        > 

        問題:定義了多個局部變量時,棧分配順序是怎樣的?
        局部變量棧分配的順序是按照變量聲明先后的順序,同一行聲明的變量是按照從左到右的順序入棧的,在test2.c中,變量聲明如下:
            int i, j=2, k=4;
        而反匯編的結果中:

            movl    $2,-8(%ebp)          ; j=2
            movl    $4,-0xc(%ebp)        ; k=4
            movl    $3,-4(%ebp)          ; i=3
        其中不難看出,i,j,k的棧中的位置如下圖:


    +--------------------------------+------> 高地址
    | EIP (_start函數的返回地址) | 
    +--------------------------------+ 
    | EBP (_start函數的EBP)      | <-- main函數的EBP指針(即SFP框架指針) 
    +--------------------------------+ 
    | i (EBP-4)                           |
    +--------------------------------+
    | j (EBP-8)                           | 
    +--------------------------------+ 
    | k (EBP-0xc)                       |
    +--------------------------------+------> 低地址 
      圖 2-1

    3. 小結
        這次通過幾個試驗程序,進一步了解了局部變量在棧中的分配和釋放以及位置,并再次回顧了上篇文章中涉及到的以下概念:
            SFP ??蚣苤羔?
            Stack aligned 棧對齊
        并且,利用Solaris提供的mdb工具,直觀的觀察到了棧在程序運行中的動態變化,以及Stack Frame的創建和撤銷,根據給出的圖例的內容(圖 2-1和圖 1-1),可以更清晰的了解IA32架構中棧在內存中的布局(Stack Layer)。


    相關文檔:
        X86 匯編語言學習手記(1)
        Solaris 上的開發環境安裝及設置
        Linux AT&T 匯編語言開發指南
        ELF動態解析符號過程(修訂版)
        關注: Solaris 10的10大新變化

     aero 回復于:2004-11-22 09:19:21
    好文,堅決支持!blog寫的很好,偶做你的鏈接了,繼續努力。

     converse 回復于:2004-11-22 10:18:16
    確實寫的不錯,支持原創,也支持這種打破沙鍋問到底的精神!
    我的博客上的【CSAPP讀書筆記】過程及其相關操作的分析其實也談到相應的問題,只是我對棧對齊的問題沒有深究,重點討論的是過程建立和恢復的時候相應的指令和造成的影響,不過這篇文章對leave和ret這幾個恢復堆棧的指令也沒有詳細說,兩篇文章這樣相互補充就比較的完整了。

     converse 回復于:2004-11-22 10:26:23
    剛才看了這個系列的(1)已經講述上面的問題呵呵,沒有仔細看作者的博客,確實不錯。

     aero 回復于:2004-11-22 11:18:12
    linux下gcc編譯器對比學習了一下。pity,linux下main函數執行之前不僅僅是調用_start。有好多。沒搞明白。不過思路還是這文章的思路。

     Solaris12 回復于:2004-11-22 11:22:54
    [quote:0805473b24="aero"]好文,堅決支持!blog寫的很好,偶做你的鏈接了,繼續努力。[/quote:0805473b24]

    在友情鏈接中加你了

     Solaris12 回復于:2004-11-22 11:25:11
    [quote:0d7be5f351="aero"]在linux下gcc編譯器對比學習了一下。pity,linux下main函數執行之前不僅僅是調用_start。有好多。沒搞明白。不過思路還是這文章的思路。[/quote:0d7be5f351]

    to aero:

    謝謝你的肯定,Linux和Solaris差別還是很大的,
    因為我正在學Solaris,所以就把平臺選為Solaris了,
    我本來打算也寫個Linux版本,后來覺得太費時間,而且,
    其實思路一樣的,如果在Linux有問題的話,可以交流一下

    原文轉自:http://www.kjueaiud.com

    評論列表(網友評論僅供網友表達個人看法,并不表明本站同意其觀點或證實其描述)
    老湿亚洲永久精品ww47香蕉图片_日韩欧美中文字幕北美法律_国产AV永久无码天堂影院_久久婷婷综合色丁香五月

  • <ruby id="5koa6"></ruby>
    <ruby id="5koa6"><option id="5koa6"><thead id="5koa6"></thead></option></ruby>

    <progress id="5koa6"></progress>

  • <strong id="5koa6"></strong>