什么是機(jī)器碼?
機(jī)器碼是對(duì)諸如操作數(shù)據(jù)、管理內(nèi)存、讀寫在存儲(chǔ)設(shè)備上的數(shù)據(jù)、通過網(wǎng)絡(luò)通信等底層操作進(jìn)行編碼的字節(jié)序列。
什么是匯編碼?
匯編碼是機(jī)器碼的文本表示,給出了程序中的各個(gè)指令。
什么是X86-64?
X86-64是一種機(jī)器語(yǔ)言,用于今天的臺(tái)式電腦、筆記本電腦、大型數(shù)據(jù)中心、超級(jí)計(jì)算機(jī)等機(jī)器上的大部分處理器上。這種語(yǔ)言有很長(zhǎng)的歷史,開始于1978年的Intel公司的第一個(gè)16位的處理器,然后拓展到32位,最近的不部分都是64位。沿著這條線,為了更好的利用半導(dǎo)體技術(shù)、滿足市場(chǎng)需要,添加了許多特征。雖然主要是由Intel公司驅(qū)動(dòng)的,但是Intel的競(jìng)爭(zhēng)對(duì)手AMD也作出了重要的貢獻(xiàn)。導(dǎo)致現(xiàn)在的X86-64具有獨(dú)特的功能設(shè)計(jì),只有從歷史的角度看才有意義。X86-64還具有向后兼容的能力,這種特性是不被現(xiàn)代編譯器和操作系統(tǒng)使用的。
32位機(jī)器和64位機(jī)器
計(jì)算機(jī)工業(yè)最近已從32位機(jī)器遷移到了64位機(jī)器。32位機(jī)器只能利用大約4GB的隨機(jī)訪問內(nèi)存。隨著內(nèi)存價(jià)格不斷下降,計(jì)算需求和數(shù)據(jù)量不斷增加,突破32位機(jī)器能訪問的內(nèi)存限制已變得經(jīng)濟(jì)上可行且技術(shù)上可期?,F(xiàn)在的64位機(jī)器可使用多達(dá)256TB(字節(jié)),且很容易拓展到使用16EB(
字節(jié))。雖然很難想象有那么大內(nèi)存的機(jī)器的樣子,但是要記?。涸?2位機(jī)器很常見的20世紀(jì)70年代和80年代,4GB的內(nèi)存似乎也是很大的內(nèi)存。
計(jì)算機(jī)執(zhí)行的是機(jī)器碼。
基于編程語(yǔ)言的規(guī)則、目標(biāo)機(jī)器的指令集、操作系統(tǒng)遵守的約定,編譯器通過一系列的階段來生成機(jī)器碼。比如GCC的C編譯器先生成匯編碼,后調(diào)用匯編器和鏈接器來從匯編碼中生成可執(zhí)行機(jī)器碼。
在這一章里,我們將仔細(xì)地研究機(jī)器碼和匯編碼。
當(dāng)使用諸如C語(yǔ)言和Java等高級(jí)語(yǔ)言編程時(shí),程序的機(jī)器級(jí)別詳細(xì)實(shí)現(xiàn)是向程序員屏蔽的。而當(dāng)使用匯編語(yǔ)言編程時(shí),程序員必須指定用于執(zhí)行一次計(jì)算所需的底層指令。
在多數(shù)情況下,在由高級(jí)語(yǔ)言提供的更高級(jí)的抽象上工作要更有效率和更可靠。由編譯器提供的類型檢查幫助檢測(cè)許多編程錯(cuò)誤,確保以一致的方式引用和操作數(shù)據(jù)。現(xiàn)代有優(yōu)化功能的編譯器生成的代碼通常跟一個(gè)有經(jīng)驗(yàn)的匯編程序員手寫的代碼效率一樣。用高級(jí)語(yǔ)言編寫的程序能被編譯和在多臺(tái)不同的機(jī)器上執(zhí)行,而匯編碼是高度機(jī)器相關(guān)的。
為什么要花費(fèi)時(shí)間研究機(jī)器碼?
- 即使編譯器做了生成匯編碼的大部分工作,但能閱讀和理解機(jī)器碼也是程序員的一項(xiàng)重要技能。
- 使用合適的命令行參數(shù)來調(diào)用編譯器,編譯器會(huì)生成一個(gè)匯編碼形式的輸出文件。通過閱讀匯編碼,我們能理解編譯器的優(yōu)化功能和分析潛在的低效率代碼。
- 如我們將在第5章里體會(huì)到的那樣,尋求最大化關(guān)鍵代碼的性能的程序員通常會(huì)嘗試對(duì)源代碼的做不同的修改,每編譯一次并檢查匯編代碼來了解程序的運(yùn)行效率。
- 程序員學(xué)習(xí)匯編代碼的需求隨著時(shí)間的推移發(fā)生了變化,以前是要求能直接用匯編碼編寫程序,現(xiàn)在是能閱讀和理解編譯器生成的代碼。
而且,由高級(jí)語(yǔ)言提供的抽象有時(shí)會(huì)隱藏程序的運(yùn)行時(shí)信息。
- 當(dāng)使用線程包來寫并發(fā)程序時(shí),理解不同線程如何共享程序數(shù)據(jù)和保持?jǐn)?shù)據(jù)私有,怎樣精確地訪問共享數(shù)據(jù)是非常重要的。這些信息在機(jī)器碼中是可見的。
- 程序遭受攻擊的的許多方式都涉及程序存儲(chǔ)運(yùn)行時(shí)控制信息的細(xì)節(jié)。許多攻擊都利用了系統(tǒng)程序中的弱點(diǎn)來覆蓋信息,從而獲取了系統(tǒng)的控制權(quán)。理解這些漏洞如何出現(xiàn),以及如何防御攻擊需要具備程序的機(jī)器碼表示的知識(shí)。
在這一章里,我們將學(xué)習(xí)一種特定的匯編語(yǔ)言,了解C程序是如何被編譯成這種形式的機(jī)器碼的。
閱讀由編譯器生成的匯編碼需要具備不同于手工編寫匯編碼不同的什么技能?
能理解經(jīng)典編譯器在將C結(jié)構(gòu)轉(zhuǎn)換成機(jī)器碼時(shí)都做了哪些事?
相對(duì)于用C代碼表示的計(jì)算,帶優(yōu)化功能的編譯器可重排執(zhí)行順序、消除不必要的計(jì)算、用更快的操作替換較慢的操作、甚至能將遞歸計(jì)算編程迭代計(jì)算等。
理解源代碼和匯編碼之間的關(guān)系通常是一個(gè)挑戰(zhàn),有點(diǎn)像拼一個(gè)跟包裝盒上的圖片稍微不同的圖。這是一個(gè)逆向工程,通過研究系統(tǒng)并反向推導(dǎo)系統(tǒng)是如何被創(chuàng)建的。在這里,前述的系統(tǒng)就是一個(gè)機(jī)器生成的匯編程序。這就簡(jiǎn)化了逆向工程的任務(wù),因?yàn)樯傻拇a遵循相當(dāng)規(guī)則的模式,且我們可以運(yùn)行實(shí)驗(yàn),讓編譯器為不同的程序生成匯編碼。
在本章里,我們給出了許多示例,提供了許多練習(xí)來說明匯編語(yǔ)言和編譯器的不同方面。在這里,掌握細(xì)節(jié)是理解更深入和更基礎(chǔ)的概念的前提。
花時(shí)間來研究這些示例、做這些練習(xí)、測(cè)試你的答案跟參考答案是很重要的。
我們的展示是基于X86-64,將集中在被GCC和Linux使用的特征子集上。這就使得我們避免了X86-64的許多復(fù)雜性和奧秘版的特征。
我們的展示集中在編譯C代碼生成的機(jī)器碼程序的類型上。因此,我們不會(huì)去介紹X86-64的許多支持早期微處理器編碼的特征。
本章的主要內(nèi)容
首先,介紹C語(yǔ)言、匯編碼、機(jī)器碼之間的關(guān)系。
然后,介紹X86-64的細(xì)節(jié)
- 從數(shù)據(jù)的表示和操作、控制的實(shí)現(xiàn)等開始。我們會(huì)看到在C語(yǔ)言中諸如
if、while、switch語(yǔ)句等控制結(jié)構(gòu)是如何實(shí)現(xiàn)的。 - 然后,我們介紹函數(shù)的實(shí)現(xiàn),包括程序是如何維護(hù)一個(gè)運(yùn)行時(shí)棧來支持函數(shù)間的數(shù)據(jù)和控制等信息的傳遞,以及局部變量的存儲(chǔ)等。
接著,考慮諸如數(shù)組、結(jié)構(gòu)體、聯(lián)合體等數(shù)據(jù)結(jié)構(gòu)在機(jī)器級(jí)別是如何實(shí)現(xiàn)的。有了這些機(jī)器級(jí)別編程的背景后,我們討論內(nèi)存訪問越界、系統(tǒng)容易遭受緩沖區(qū)溢出攻擊等問題。
最后,介紹使用GDB調(diào)試器來檢查機(jī)器碼程序的運(yùn)行時(shí)行為,以及展示有關(guān)浮點(diǎn)數(shù)數(shù)據(jù)和操作的機(jī)器碼程序。