1. 程式人生 > >CSAPP 第三章 讀書筆記

CSAPP 第三章 讀書筆記

程式的機器級表示

AT&T與Intel格式的彙編程式碼

我們的表述是ATT(根據“AT&T”命名的, AT&T是運營貝爾實驗室多年的公 司)格式的彙編程式碼,這是GCC、 OBJDUMP和其他一些我們使用的工具的預設格式。 其他一些程式設計工具,包括Microsoft的工具,以及來自Intel的文件,其彙編程式碼都是Intel格式的。這兩種格式在許多方面有所不同。比如下面的幾點:

  • intel程式碼省略了指示大小的字尾。比如使用push和pop,而不是pushl和popl。
  • intel程式碼省略了暫存器前面的“%”符號,用的是rbx而不是%rbx。
  • intel程式碼用不同的方式來描述記憶體中的位置,例如是“QWORD PTR[rbx]”,而不是“(%rbx)”。
  • 在帶有多個運算元的指令情況下,列出運算元的順序相反。當在兩種格式之間進 行轉換的時候,這一點非常令人困惑。

資料格式

由於是從16位體系結構擴充套件成32位的, Intel用術語“字(word)”表示16位資料型別。因此,稱32位數為“雙字(double words)”,稱64位數為“四字(quad words)”。 在x86-64中。標準int值儲存為雙字(32位)。指標(在此用char *表示)儲存為8位元組的四字, 64位機器本來就預期如此。

資訊訪問

一個x86-64的中央處理單元(CPU)包含一組16個儲存64位值的通用目的暫存器。 這些暫存器用來儲存整數資料和指標。

定址模式

有多種定址模式,如下圖所示:

資料傳送 MOV類指令

這些指令把資料從源位置複製到目的位置,不做任何變化。MOV類由四條指令組成‥ movb、 movb、 movl和 movq。這些指令都執行同樣的操作;主要區別在於它們操作的資料大小不同:分別是1、2、4和8位元組。

算數邏輯操作

這種操作可以分為四種::載入有效地址、一元操作、二元操作和移位。

其中,leaq指令能執行加法和有限形式的乘法,在編譯如上簡單的算術表示式時,是很有用處的。

控制

機器程式碼提供兩種基本的低階機制來實現有條件的 行為:測試資料值,然後根據測試的結果來改變控制流或者資料流。

條件碼

除了整數暫存器, CPU還維護著一組單個位的條件碼(condition code)暫存器,它們描述了最近的算術或邏輯操作的屬性。可以檢測這些暫存器來執行條件分支指令。最常用的條件碼有:

  • CF:進位標誌。最近的操作使最高位產生了進位。可用來檢查無符號操作的溢位。
  • ZF:零標誌。最近的操作得出的結果為0。
  • SF:符號標誌。最近的操作得到的結果為負數。
  • OF:溢位標誌。最近的操作導致一個補碼溢位一正溢位或負溢位。

對於邏輯操作,例如xoR,進位標誌和溢位標誌會設定成0。對於移位操作,進位標誌將設定為最後一個被移出的位,而溢位標誌設定為0。INC和DEC指令會設定溢位和零標誌,但是不會改變進位標誌。

跳轉指令

跳轉(jump)指令會導致執行切換到程式中一個全新的位置。在彙編程式碼中,這些跳轉的目的地通常用一個標號(lable)指明。常用的跳轉指令如下:

迴圈

C語言提供了多種迴圈結構,即do-While、While和for。彙編中沒有相應的指令存在,可以用條件測試和跳轉組合起來實現迴圈的效果。

執行時棧

C語言過程呼叫機制的一個關鍵特性(大多數其他語言也是如此)在於使用了棧資料結構提供的後進先出的記憶體管理原則。在過程P呼叫過程Q的例子中,可以看到當Q在執行時, P以及所有在向上追溯到p的呼叫鏈中的過程,都是暫時被掛起的。當Q執行時,它只需要為區域性變數分配新的儲存空間,或者設定到另一個過程的呼叫。 另一方面,當Q返回時,任何它所分配的區域性儲存空間都可以被釋放。因此,程式可以用棧來管理它的過程所需要的儲存空間,棧和程式暫存器存放著傳遞控制和資料、分配記憶體所需要的資訊。當P呼叫Q時,控制和資料資訊新增到棧尾。當p返回時,這些資訊會釋放掉。通用的堆疊示意圖如下:

棧上的區域性儲存

有些時候,區域性資料必須存放在記憶體中,常見的情況包括:

  • 暫存器不足夠存放所有的本地資料。
  • 對一個區域性變數使用地址運算子“&”,因此能夠為它產生一個地址。
  • 某些區域性變數是陣列或結構,因此必須能夠通過陣列或結構引用被訪問到。在描述陣列和結構分配時,我們會討論這個間題。

一般來說,過程通過減小棧指標在棧上分配空間。分配的結果作為棧幀的一部分,標 號為“區域性變數”。

陣列的分配和訪問

C語言中的陣列是一種將標量資料聚整合更大資料型別的方式。C語言實現陣列的方式非常簡單,因此很容易翻譯成機器程式碼。C語言的一個不同尋常的特點是可以產生指向陣列中元素的指標,並對這些指標進行運算。在機器程式碼中,這些指標會被翻譯成地址計算。優化編譯器非常善於簡化陣列索引所使用的地址計算。

指標運算

C語言允許對指標進行運算,而計算出來的值會根據該指標引用的資料型別的大小進行伸縮。單運算元操作符‘&’和‘*’可以產生指標和間接引用指標。對應的彙編程式碼如圖所示。

變長陣列

ISO C99引人了一種功能,允許陣列的維度是表示式,在陣列被分配的時候才計算出來。 在變長陣列的C版本中,我們可以將一個數組宣告如下:

int A[exp1][exp2];

它可以作為一個區域性變數,也可以作為一個函式的引數,然後在遇到這個宣告的時候,通過對錶達式exp1和exp2求值來確定陣列的維度。因此,例如要訪問n×n的陣列的元素i,j,我們可以寫一個如下的函式:

對應的彙編程式碼如下:

異構的資料結構

結構

C語言的struct宣告建立一個數據型別,將可能不同型別的物件聚合到一個物件中。用名字來引用結構的各個組成部分。類似於陣列的實現,結構的所有組成部分都存放在記憶體中一段連續的區域內,而指向結構的指標就是結構第一個位元組的地址。編譯器維護關於每個結構型別的資訊,指示每個欄位(field)的位元組偏移。它以這些偏移作為記憶體引用指令中的位移,從而產生對結構元素的引用。

聯合

聯合提供了一種方式,能夠規避C語言的型別系統,允許以多種型別來引用一個物件。聯合宣告的語法與結構的語法一樣,只不過語義相差比較大。它們是用不同的欄位來引用相同的記憶體塊。

資料對齊

許多計算機系統對基本的資料型別的合法地址做出了一些限制,要求某種型別物件的地址必須是某個值K(通常是2,4,8)的倍數。這種對其限制簡化了形成處理器和記憶體系統之間介面的硬體設計。無論資料是否對齊,x86-64的硬體都能正確工作。不過,intel還是建議要對其資料以提高系統性能。

理解指標

  • 每個指標都對應一個型別。
  • 每個指標都有一個值
  • 指標用&運算子建立。
  • *操作符用於間接引用指標。
  • 陣列與指標緊密聯絡。一個數組的名字可以像一個指標變數一樣引用。
  • 將指標從一種型別轉換成另一種型別不會改變它的值。
  • 指標也可以指向函式。

GDB偵錯程式常用命令

記憶體越界引用和緩衝區溢位

c對陣列不進行任何邊界檢查,因此當對越界的陣列元素進行寫操作會破壞儲存在棧中的資訊。一種特別常見的狀態破壞稱為緩衝區溢位。

緩衝區溢位一個很致命的使用就是讓程式執行它本來不想執行函式,這是一種常見的通過計算機網路攻擊系統安全的方法。輸入給程式一個字串,這個字串包含一些可執行程式碼的位元組編碼,稱為攻擊程式碼,另外還有一些位元組會用一個指向攻擊程式碼的指標覆蓋返回區域。那麼執行ret指令的效果就是跳轉到攻擊程式碼。

對抗緩衝區溢位可以採取以下方法:

  • 棧隨機化。使得棧的位置在程式每次執行時都有變化。
  • 棧破壞檢測。能夠檢測到棧何時已被破壞。最新的GCC版本加入了一種棧保護者機制來檢測緩衝區越界。其思想是在任何區域性緩衝區與棧狀態之間儲存一個特殊的金絲雀值。
  • 限制可執行程式碼區域。消除攻擊者向系統中插入可執行程式碼的能力。