X86匯編語言學習手記(2)
[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的狀態:
#g
clearcase/" 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永久无码天堂影院_久久婷婷综合色丁香五月
|