程序機械級表示

Tiny-people

在實際編程過程中诅岩,我們大多是使用高級語言如 Java 語言編程。多數(shù)時候带膜,高級語言因其高度封裝吩谦,使得程序易于編寫,亦是擁有較高的可讀性膝藕,因而廣受歡迎式廷。但由于屏蔽了程序在機械級的具體實現(xiàn)過程,我們如隔層玻璃觀物一般芭挽,無法觸及底層實質滑废。

當然,我們無法直接和機器交流袜爪。不過我們可以通過一種迂回的方式蠕趁,達到交流的目的——讓它們知道我們想要它做什么。那怎么讓那“榆木腦袋”知道我們想要什么呢辛馆?我們可以使用匯編來理解程序的底層邏輯俺陋。

在我們啟動程序的時候,編譯器會產生一個匯編代碼文件,而這個匯編代碼就十分接近于計算機實際過程中執(zhí)行的機器代碼腊状。和機械代碼那種二進制數(shù)字相比诱咏,匯編使用的是更易于讀的字符,方便我們理解底層邏輯寿酌。我們知道它是怎么想的,那它想做什么不就由我們掌控了嘛硕蛹。

有些時候醇疼,高級語言所提供的抽象層會隱藏一些我們想要理解的信息。就像英譯中法焰,閱讀英文原本和譯本的差別是挺大的秧荆,有時候原本里某段很精彩的描述翻譯過來不過寥寥幾筆勾畫,雖然看起來不影響整體埃仪,不過缺失的那部分感覺挺可惜的乙濒。

回到編碼,我們常常望“洋”興嘆卵蛉,怎么就和我們想的不一樣呢颁股?這時候,我們可以通過閱讀匯編代碼傻丝,理解程序其中可做的優(yōu)化甘有,分析代碼中潛在的低效率問題等。而且在用線程包寫并發(fā)程序的時候葡缰,知道用什么存儲器 (storage) 來保存各種變量是很重要的亏掀。這些信息在匯編代碼就一覽無余啦。

閱讀和理解匯編代碼能幫助我們進入另一個層次看待程序泛释。接下來滤愕,讓我們進入小人國的世界。


機械級程序

在類似 C 語言的高級語言中大多提供了一種模型怜校,可以在存儲器中聲明和分配各種數(shù)據(jù)類型的對象间影。但在匯編代碼中,它們只是一個很大的茄茁、按字節(jié)尋址的數(shù)組宇智。像在 C 中,我們所熟知的數(shù)組和結構胰丁,在匯編代碼中是用連續(xù)的字節(jié)表示的随橘。

我們生來沒有什么不同,只是隔了層地址锦庸,最終老死不相往來机蔗。

訪問信息

下圖為寄存器,它用以存儲數(shù)據(jù)和指針。在大多數(shù)情況下萝嘁,前六個寄存器是通用寄存器梆掸,對于它們的使用沒有嚴格限制。最后兩個寄存器 ( %ebp 和 %esp ) 保存著指向程序棧中重要位置的指針牙言, %ebp 指向棧幀開始處酸钦, %esp 指向棧頂,只有根據(jù)棧管理的標準慣例才能修改這兩個寄存器的值咱枉。

大多數(shù)指令有一個或多個操作數(shù)卑硫,指示出執(zhí)行一個操作中要引用的源數(shù)據(jù)值,以及放置結果的位置蚕断。而各種操作數(shù)依各自特性分為三種類型:
立即數(shù) 即常數(shù)值欢伏,依規(guī)定,立即數(shù)的書寫方式是“$”后面跟一個整數(shù)亿乳。
寄存器表示某個寄存器的內容硝拧。對雙字操作來說,可以是八個32位寄存器中的一個葛假,如 %eax障陶。
存儲器引用它會根據(jù)計算出的地址訪問某個存儲器的位置。

寄存器

棧幀結構

棧用來傳遞過程參數(shù)聊训、存儲返回信息咸这、保存寄存器以供以后恢復只用,以及用于本地存儲魔眨。為單個過程分配的那部分棧稱為棧幀媳维。下圖描繪了棧幀的通用結構。

假設函數(shù) P (調用者) 調用函數(shù) Q (被調用者)遏暴。 Q 的參數(shù)放在 P 的棧幀中侄刽。當 P 調用 Q 時, P 中的返回地址被壓入棧中朋凉,形成 P 的棧幀的末尾州丹,返回地址就是當程序從 Q 返回時應該繼續(xù)執(zhí)行的地方。Q 的棧幀從保存的幀指針的值 (如 %ebp)開始杂彭,后面是保存的其他寄存器的值墓毒。

或許說的有些抽象,你可以將函數(shù)調用想象成俄羅斯套娃亲怠,大的套小的所计,層層遞進。而棧也是如此团秽,棧向下增長主胧,高地址在上叭首,低地址在下, 即棧頂元素在最底部踪栋,而 %esp 一直指向棧頂元素焙格。我們可以通過 pushl 和 popl 指令將數(shù)據(jù)存入棧中和從棧中取出。當然夷都,也可以通過將 %esp 的值減小適當?shù)闹祦矸峙湮粗付ǔ跏贾档臄?shù)據(jù)的空間眷唉。相反,也可以通過增加 %esp 來釋放空間囤官。

棧幀結構

因為寄存器是一個資源共享區(qū)冬阳,而我們在調用函數(shù)的時候,為了避免當一個函數(shù)調用另一個函數(shù)時治拿,被調用者覆蓋某個調用者等一下會使用到的寄存器的值摩泪。在這里有一個寄存器使用慣例:

寄存器 %eax笆焰、%edx劫谅、%ecx 被劃分為調用者保存( caller save ) 寄存器。當函數(shù) P 調用 Q 時嚷掠, Q 可以覆蓋這些寄存器捏检,而不會破壞任何 P 所需要的數(shù)據(jù)。另外不皆,寄存器 %ebx贯城、%esi、%edi 被劃分為被調用者保存( callee save ) 寄存器霹娄。即 Q 必須在覆蓋它們之前能犯,將這些寄存器的值保存到棧中,并在返回前恢復它們犬耻。

術語不好記的話踩晶,我們可以想象以下場景:

int P() {
int x = f();;
Q();
return x;
}

函數(shù) P 希望它計算出來的 x 值在調用了 Q 之后任然有效,如果 x 放在一個調用者保存寄存器中枕磁,而 P 必須在調用 Q 之前保存這個值渡蜻,并在 Q 返回后恢復這個值。如果 x 在一個被調用者保存寄存器中计济,Q 想使用這個寄存器茸苇,那么 Q 在使用這個寄存器之前,必須保存這個值沦寂,并在返回前恢復学密。在這兩種情況中,保存就是將寄存器值壓入棧中传藏,而恢復時從棧中彈出到寄存器则果。


接下來幔翰,我們簡單說下匯編指令:

匯編指令注釋

數(shù)據(jù)傳送指令

數(shù)據(jù)傳送指令在底層是最頻繁使用的指令。通常一條簡單的傳送指令能完成許多機器中需要好幾條指令才能完成的操作西壮,而最常用的莫過于傳送雙字的 movl 指令遗增。

movl $0x3051,%eax        //將 0x3051 這個值放入寄存器 eax

將一個值從一個存儲器位置考到另一個存儲器位置需要兩條指令——第一條指令將源值加載到寄存器中,第二條將寄存器值寫入目的位置款青。源操作數(shù)指定一個值做修,它可以是立即數(shù),可以存放在寄存器中抡草,也可以放在存儲器中饰及。目的操作數(shù)指定一個位置康震,它可以是寄存器,也可以是存儲器地址屏箍。

加載有效地址

加載有效地址指令 leal 實際上是 movl 指令的變形赴魁。它的第一個操作數(shù)看上去是一個存儲器引用颖御,但該指令并不是從指定的位置讀入數(shù)據(jù)凝颇,而是將有效地址寫入目的操作數(shù) (如寄存器)拧略。

leal -12(%ebp) %eax       //將 %ebp 減去12得到的地址辑鲤,放入 eax 寄存器

了解部分匯編代碼含義之后月褥,我們就可以綜合實踐一下了宁赤。接下來,看看程序機械層次是如何運行的决左。

下面是 C 語言編寫的一段小程序:

int demo(){
    int x = 10;
    int y = 20;
    int sum = add(&x,  &y);
    printf(“the sum is %d\n”,sum);
    return sum;
}
int add(int *xp, int *yp){
    int x = *xp;
    int y = *yp;
    return x+y;
}

讓我們將它們轉成匯編代碼來看:

demo:
1    pushl  %ebp                //將寄存器 ebp 的值壓人棧中
2    movl   %esp %ebp           //將寄存器 esp 的值放入 ebp
3    subl   %24 esp             //將 esp 寄存器的值減去24
4    movl   $10 -4(%ebp)        //將10這個值存放到 ebp 寄存器地址減去4的地方
5    movl   $20 -8(%ebp)        //將20這個值存放到 ebp 寄存器地址減去8的地方
6    leal   -8(%ebp) %eax       //將 ebp 減去8得到的地址,放到 eax 寄存器當中
7    movl   %eax 4(%esp)        //將 eax 的值存放到 esp 寄存器地址增加4的地方
8    leal   -4(%ebp)  %eax      //將 ebp 減去4得到的地址坠狡,放到 eax 寄存器當中
9    movl   %eax esp            //將 eax 的值存放到 esp 寄存器
10   call add                   //調用 add 函數(shù)遂跟,將返回值地址壓人棧中
打印結果(略)
入棧

如圖幻锁,1-2兩條代碼是將棧幀開始處 ebp 寄存器的地址壓入棧頂,esp 寄存器的地址自動減去4個字節(jié)假消,地址為 800富拗。之后將指向棧頂?shù)?esp 寄存器的地址放入 ebp 寄存器媒峡,ebp 寄存器的地址被 esp 寄存器的地址取代,即地址也為 800酬滤。


對齊

第3行代碼是將 esp 寄存器的地址減去24個字節(jié),相當于給棧幀分配一定的自由空間体捏。為什么會減去24個字節(jié)呢糯崎?因為系統(tǒng)的編程指導方針為了嚴格的數(shù)據(jù)對齊所至,即一個函數(shù)使用的椢帜兀空間必須是16個字節(jié)的整數(shù)倍年栓。

存值

4-5行代碼是將兩個立即數(shù) 10 和 20 分別放入棧中薄霜,需要注意的是此時 ebp 寄存器中的地址不會改變纸兔。之后 leal 指令代碼是將 ebp 減去8得到的地址,即792否副,放到 eax 寄存器當中。接著 movl 指令代碼是將 eax 的值备禀,存放到 esp 寄存器地址增加4的地方负甸,即780。這兩段指令就是將變量 x 的值的地址放到棧中痹届,即是指針。8-9段代碼同上队腐,你可以檢驗一下柴淘。

返回地址

第10行代碼是函數(shù)調用为严,調用 add 函數(shù)第股。執(zhí)行這一行指令的時候应民,會將返回地址寫入棧中,指向棧頂?shù)?esp 寄存器會自動的減去4個字節(jié)涉馅,指向返回地址归园。至此,調用者的函數(shù)棧幀完成稚矿。

接下來庸诱,進入 add 函數(shù)調用。我們會在上面棧幀的下面開辟調用者棧幀的空間晤揣,以便于函數(shù)值的存儲桥爽。

下面是 add 函數(shù)的匯編代碼:

add:
1    pushl  %ebp
2    movl   %esp %ebp
3    pushl  %ebx
4    movl   8(%ebp)  %edx
5    movl   12(%ebp) %ecx
6    movl   (%edx)   %ebx
7    movl   (%ecx)   %eax
8    add    %ebx     %eax
9    popl   %ebx
10   popl   %ebp
11   ret

由于和上面代碼類似,具體解析就此省略碉渡,你可以當作練習聚谁,試著解析代碼。

附:本文大多來自《深入理解計算機系統(tǒng)》一書以及劉欣老師的編程課程內容滞诺。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末形导,一起剝皮案震驚了整個濱河市环疼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌朵耕,老刑警劉巖炫隶,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異阎曹,居然都是意外死亡伪阶,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門处嫌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來栅贴,“玉大人,你說我怎么就攤上這事熏迹¢苁恚” “怎么了?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵注暗,是天一觀的道長坛缕。 經常有香客問我,道長捆昏,這世上最難降的妖魔是什么赚楚? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮骗卜,結果婚禮上宠页,老公的妹妹穿的比我還像新娘。我一直安慰自己膨俐,他們只是感情好勇皇,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布罩句。 她就那樣靜靜地躺著焚刺,像睡著了一般。 火紅的嫁衣襯著肌膚如雪门烂。 梳的紋絲不亂的頭發(fā)上乳愉,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天,我揣著相機與錄音屯远,去河邊找鬼蔓姚。 笑死,一個胖子當著我的面吹牛慨丐,可吹牛的內容都是我干的坡脐。 我是一名探鬼主播,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼房揭,長吁一口氣:“原來是場噩夢啊……” “哼备闲!你這毒婦竟也來了晌端?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤恬砂,失蹤者是張志新(化名)和其女友劉穎咧纠,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體泻骤,經...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡漆羔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了狱掂。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片演痒。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖趋惨,靈堂內的尸體忽然破棺而出嫡霞,到底是詐尸還是另有隱情,我是刑警寧澤希柿,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布诊沪,位于F島的核電站,受9級特大地震影響曾撤,放射性物質發(fā)生泄漏端姚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一挤悉、第九天 我趴在偏房一處隱蔽的房頂上張望渐裸。 院中可真熱鬧,春花似錦装悲、人聲如沸昏鹃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽洞渤。三九已至,卻和暖如春属瓣,著一層夾襖步出監(jiān)牢的瞬間载迄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工抡蛙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留护昧,地道東北人。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓粗截,卻偏偏與公主長得像惋耙,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內容