計(jì)算機(jī)執(zhí)行機(jī)器代碼,用字節(jié)序列編碼低級(jí)的操作,包括處理數(shù)據(jù),管理內(nèi)存,讀寫存儲(chǔ)設(shè)備上的數(shù)據(jù),以及利用網(wǎng)絡(luò)通信。匯編代碼是機(jī)器代碼的文本表示。
3.1?歷史觀點(diǎn)
????????intel處理器系列俗稱x86,它是第一代單芯片,16位微處理器之一。
? ? ? ? 以下列舉一些intel處理器的模型:

3.2 程序編碼
3.2.1?機(jī)器級(jí)代碼
對(duì)于機(jī)器級(jí)編程來(lái)說(shuō),有兩種抽象尤為重要。
第一種是由指令集體系結(jié)構(gòu)或者指令集架構(gòu)來(lái)定義機(jī)器集程序的格式和行為,它定義了處理器狀態(tài),指令的格式,以及每條指令對(duì)狀態(tài)的影響。
第二種抽象是,機(jī)器級(jí)程序使用的內(nèi)存地址是虛擬地址,提供的內(nèi)存模型看上去是非常大的字節(jié)數(shù)組。
x86-64的機(jī)器代碼和原始的c代碼差別非常大。一些通常對(duì)c語(yǔ)言程序員隱藏的處理器狀態(tài)都是可見的:


3.2.2?代碼示例

在命令行上使用“-S”選項(xiàng),就能看到C語(yǔ)言編譯器產(chǎn)生的匯編代碼:


如果我們使用“-c”命令行選項(xiàng),GCC會(huì)編譯并且匯編該代碼:

這樣就會(huì)產(chǎn)生目標(biāo)文件mstore.o,它是二進(jìn)制格式文件。

這就是上面列出的匯編指令對(duì)應(yīng)的目標(biāo)代碼。從中得到一個(gè)重要信息,即機(jī)器執(zhí)行的程序只是一個(gè)字節(jié)序列,它是對(duì)一系列指令的編碼。
要想查看機(jī)器代碼文件的內(nèi)容,需要使用反匯編器。


生成實(shí)際可執(zhí)行的代碼需要對(duì)一組目標(biāo)代碼文件運(yùn)行鏈接器,而這一組目標(biāo)代碼文件中必須含有一個(gè)main函數(shù)。



3.2.3?關(guān)于格式的注解


所有以“?!遍_頭的行都是指導(dǎo)匯編器和連接器工作的偽指令。我們通??梢院雎赃@些行。另一方面,也沒關(guān)于指令的用途以及它們與源代碼之間關(guān)系的解釋說(shuō)明。

3.3?數(shù)據(jù)格式

如圖所示,大多數(shù)GCC生成的匯編代碼指令都有一個(gè)字符的后綴,表明操作數(shù)的大小。例如,數(shù)據(jù)傳送指令有四個(gè)變種:movb(傳字節(jié)),movw(傳字),movl(傳雙字)和movq(傳送四字)。后綴‘l’用來(lái)表示雙字,因?yàn)?2位數(shù)被看成是“長(zhǎng)字(long?word)”。注意,匯編代碼也使用用后綴‘l’來(lái)表示4字節(jié)整數(shù)和8字節(jié)雙精度浮點(diǎn)數(shù)。
3.4?訪問(wèn)信息

3.4.1?操作數(shù)指示符

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


3.4.3?數(shù)據(jù)傳送示例
數(shù)據(jù)傳送指令的代碼示例。

3.4.4?壓入和彈出棧數(shù)據(jù)

pushq指令的功能是把數(shù)據(jù)壓入到棧上,而popq指令是彈出數(shù)據(jù)。這些指令都只有一個(gè)操作數(shù)——壓入的數(shù)據(jù)源和彈出的數(shù)據(jù)目的。
? ?將一個(gè)四字值壓入棧中,首先要將棧指針減8,然后將值寫入到新的棧頂?shù)刂贰R虼?,指令pushq %rbp的行為等價(jià)于下面兩條指令:

它們之間的區(qū)別是在機(jī)器代碼中pushq指令編碼為1個(gè)字節(jié),而上面那兩條指令一共需要8個(gè)字節(jié)。如下圖,當(dāng)%rsp為0x108,%rax為0x123時(shí),執(zhí)行指令pushq%rax的效果。首先%rsp會(huì)減8,得到0x100,然后會(huì)將0x123存放到內(nèi)存地址0x100處。

3.5?算術(shù)和邏輯操作

3.6?控制
3.6.1?條件碼

3.6.2 指令



3.7?過(guò)程
過(guò)程是軟件中一種很重要的抽象。它提供了一種封裝代碼的方式,用一組指定的參數(shù)和一個(gè)可選的返回值實(shí)現(xiàn)了某種功能。
3.7.1?運(yùn)行時(shí)棧

3.7.2? 轉(zhuǎn)移控制




3.7.3?棧上的局部存儲(chǔ)
到目前為止我們看到的大多數(shù)過(guò)程示例都不需要超出寄存器大小的本地存儲(chǔ)區(qū)域。不過(guò)有些時(shí)候,局部數(shù)據(jù)必須存放在內(nèi)存中,常見的情況包括:
1.?寄存器不足夠存放所有的本地?cái)?shù)據(jù)。
2.?對(duì)一個(gè)局部變量使用地址運(yùn)算符‘&’,因此必須能夠?yàn)樗a(chǎn)生一個(gè)地址。
3.?某些局部變量是數(shù)組或者結(jié)構(gòu),因此必須能夠通過(guò)數(shù)組或結(jié)構(gòu)引用被訪問(wèn)到。
3.7.4?遞歸過(guò)程

3.8?數(shù)組分配和訪問(wèn)
3.8.1?基本原則
對(duì)于數(shù)據(jù)類型T和整型常數(shù)N,聲明如下:
T A[N];
起始位置表示為Xa。這個(gè)聲明有兩個(gè)效果。首先,它在內(nèi)存中分配一個(gè)L*Z字節(jié)的連續(xù)區(qū)域,這里L(fēng)是數(shù)據(jù)類型T的大小。其次,它引入了標(biāo)識(shí)符A,可以用A來(lái)作為指向數(shù)組開頭的指針,這個(gè)指針的值就是XA。可以用0~N-1的整數(shù)索引來(lái)訪問(wèn)該數(shù)組元素。數(shù)組元素i會(huì)被存放在地址為Xa + L*i的地方。
作為示例,讓我們來(lái)看看下面這樣的聲明:
char A[12];
char *B[8];
int C[6];
double *D[5];
這些聲明會(huì)產(chǎn)生帶下列參數(shù)的數(shù)組:

3.8.2?指針運(yùn)算
C語(yǔ)言允許對(duì)指針進(jìn)行運(yùn)算,而計(jì)算出來(lái)的值會(huì)根據(jù)該指針引用的數(shù)據(jù)類型的大小進(jìn)行伸縮。也就是說(shuō),如果p是一個(gè)指向類型為T的數(shù)據(jù)的指針,p的值為Xp,那么表達(dá)式p+i的值為Xp + L *?i,?這里L(fēng)是數(shù)據(jù)類型T的大小。
單操作數(shù)操作符‘&’和‘*’可以產(chǎn)生指針和間接引用指針。也就是,對(duì)于一個(gè)表示某個(gè)對(duì)象的表達(dá)式Expr, &Expr是給出該對(duì)象地址的一個(gè)指針。對(duì)于一個(gè)表示地址的表達(dá)式AExpr, *AExpr給出該地址處的值。因此,表達(dá)式Expr與* &Expr是等價(jià)的??梢詫?duì)數(shù)組和指針應(yīng)用數(shù)組下標(biāo)操作。數(shù)組引用A[i]等同于表達(dá)式*(A+i)。它計(jì)算遞i個(gè)數(shù)組元素的地址,然后訪問(wèn)這個(gè)內(nèi)存位置。
3.8.3?嵌套的數(shù)組
嵌套的數(shù)組就是多維數(shù)組,但是多維數(shù)組也就是一維數(shù)組。
int A[2][2] = {0,0,0,0};
也可以?int A[2][2] = {{0,0},{0,0}};
3.9?異質(zhì)的數(shù)據(jù)結(jié)構(gòu)
C語(yǔ)言提供了兩種將不同類型的對(duì)象組合到一起創(chuàng)建數(shù)據(jù)類型的機(jī)制:結(jié)構(gòu)體,用關(guān)鍵字struct來(lái)聲明,將多個(gè)對(duì)象集合到一個(gè)單位中;聯(lián)合,用關(guān)鍵字union來(lái)聲明,允許用幾種不同的類型來(lái)引用一個(gè)對(duì)象。
3.9.1?結(jié)構(gòu)
C語(yǔ)言的struct聲明創(chuàng)建一個(gè)數(shù)據(jù)類型,將可能不同類型的對(duì)象聚合到一個(gè)對(duì)象中。用名字來(lái)引用結(jié)構(gòu)的各個(gè)組成部分。類似于數(shù)組的實(shí)現(xiàn),結(jié)構(gòu)的所有組成部分都存放在內(nèi)存中一段連續(xù)的區(qū)域內(nèi),而指向結(jié)構(gòu)的指針就是結(jié)構(gòu)第一個(gè)字節(jié)的地址。編譯器維護(hù)關(guān)于每個(gè)結(jié)構(gòu)類型的信息,指示每個(gè)字段的字節(jié)偏移。它以這些偏移作為內(nèi)存引用指令中的位移,從而產(chǎn)生對(duì)結(jié)構(gòu)元素的引用。

3.9.2?聯(lián)合
聯(lián)合提供一種方式,能夠規(guī)避C語(yǔ)言的類型系統(tǒng),允許以多種類型來(lái)引用一個(gè)對(duì)象。聯(lián)合聲明的語(yǔ)法與結(jié)構(gòu)的語(yǔ)法一樣,只不過(guò)語(yǔ)義相差比較大。它們是用不同的字段來(lái)引用相同的內(nèi)存塊。

3.10?在機(jī)器級(jí)程序中將控制與數(shù)據(jù)結(jié)合起來(lái)
3.10.1?理解指針
指針是C語(yǔ)言的一個(gè)核心特色。它們以一種統(tǒng)一方式,對(duì)不同數(shù)據(jù)結(jié)構(gòu)中的元素產(chǎn)生引用。對(duì)于編程新手來(lái)說(shuō),指針總是會(huì)帶來(lái)很多的困惑,但是基本概念其實(shí)非常簡(jiǎn)單。在此,我們重點(diǎn)介紹一些指針和它們映射到機(jī)器代碼的關(guān)鍵原則。

3.10.2?應(yīng)用:?使用GDB調(diào)試器
GUN的調(diào)試器GDB提供了許多有用的特性,支持機(jī)器級(jí)程序的運(yùn)行時(shí)評(píng)估和分析。對(duì)于本書中的示例和練習(xí),我們?cè)噲D通過(guò)閱讀代碼,來(lái)推斷出程序的行為。有了GDB,可以觀察正在運(yùn)行的程序,同時(shí)又對(duì)程序的執(zhí)行有相當(dāng)?shù)目刂?,這使得研究程序的行為變?yōu)榭赡堋?/p>
先運(yùn)行OBJ-DUMP來(lái)獲得程序的反匯編版本,是很有好處的。我們的示例都基于對(duì)文件prog運(yùn)行GDB。我們用下面的命令行來(lái)啟動(dòng)GDB:
linux> gdb prog
通常的方法是在程序中感興趣的地方附近設(shè)置斷點(diǎn)。斷點(diǎn)可以設(shè)置在函數(shù)入口后面,或是一個(gè)程序的地址處。程序在執(zhí)行過(guò)程中遇到一個(gè)斷點(diǎn)時(shí),程序會(huì)停下來(lái),并將控制返回給用戶。在斷點(diǎn)處,我們能夠以各種方式查看各個(gè)寄存器和內(nèi)存位置,我們也可以單步跟蹤程序,一次只執(zhí)行幾條指令,或是前進(jìn)到一個(gè)斷點(diǎn)。

3.10.3?內(nèi)存越界引用和緩沖區(qū)溢出


3.10.4?對(duì)抗緩沖區(qū)溢出攻擊
1.?棧隨機(jī)化
棧隨機(jī)化的思想使得棧的位置在程序每次運(yùn)行時(shí)都有變化。因此,即使許多機(jī)器運(yùn)行同樣的代碼,它們的棧地址都是不同的,實(shí)現(xiàn)的方式是:程序開始時(shí),在棧上分配一段0~n字節(jié)之間的隨機(jī)大小的空間。
2.?棧破壞檢測(cè)

GCC版本產(chǎn)生的代碼中加入了一種棧保護(hù)者機(jī)制,來(lái)檢測(cè)緩沖區(qū)越界。其思想是在棧幀中任何局部緩沖區(qū)與棧狀態(tài)之間存儲(chǔ)一個(gè)特殊的金絲雀值,也稱為哨兵值,是在程序每次運(yùn)行時(shí)隨機(jī)產(chǎn)生的。因此,攻擊者沒有簡(jiǎn)單的辦法能夠知道它是什么,在恢復(fù)寄存器狀態(tài)和從函數(shù)返回之前,程序檢查這個(gè)金絲雀值是否被該函數(shù)的某個(gè)操作或者該函數(shù)調(diào)用的某個(gè)函數(shù)的某個(gè)操作改變了,如果是的,那么程序異常中止。
3.?限制可執(zhí)行代碼區(qū)域
限制哪些內(nèi)存區(qū)域能夠存放可執(zhí)行代碼,消除攻擊者向系統(tǒng)中插入可執(zhí)行代碼的能力。
3.11?浮點(diǎn)代碼
處理器的浮點(diǎn)體系結(jié)構(gòu)包括多個(gè)方面,會(huì)影響對(duì)浮點(diǎn)數(shù)據(jù)操作的程序如何被映射到機(jī)器上,包括:
