第三章 程式的機器級表示
教材知識總結
本章學習內容是組合語言,一定要能讀懂
X86 定址方式經歷三代:
- 1 DOS時代的平坦模式,不區分使用者空間和核心空間,很不安全
- 2 8086的分段模式
- 3 IA32的帶保護模式的平坦模式
- CPU包含一組8個儲存32位值的暫存器
- 存整數資料和指標:eax,ecx,edx,ebx,esi,edi,esp,ebp。
- 大多數情況下前六個都用作通用暫存器,eax,ecx,edx的儲存和恢復慣例不同於ebx,edi,esi(前三者為被呼叫者儲存,後三者為呼叫者儲存)。最後兩個用於儲存指標,由於在過處理中非常重要,分別指向棧幀的頂部和底部,必須保持。
gcc -S xxx.c -o xxx.s 獲得彙編程式碼,也可以用objdump -d xxx 反彙編
注意: 64位機器上想要得到32程式碼:gcc -m32 -S xxx.c
Ubuntu中 gcc -S code.c (不帶-O1) 產生的程式碼更接近教材中程式碼(刪除"."開頭的語句)二進位制檔案可以用od 命令檢視,也可以用gdb的x命令檢視。 有些輸出內容過多
當一個原始檔生成了'.o'的目標二進位制檔案後,無法直接檢視。
但是還是有個檢視目的碼檔案內容的方法,就是對'.o'目標檔案使用反彙編器。它的輸出還是二進位制檔案,但是,反彙編器將這些二進位制按照指令進行了分段。讓我們知道哪一段是一個指令(格式上與彙編器產生的彙編檔案一樣,分行的)
表中不同資料的彙編程式碼字尾
資料傳送指令有三個變種:movb(傳送位元組)movw(傳送字)movl(傳送雙字)
暫存器:
esi edi可以用來操縱陣列
esp ebp用來操縱棧幀
對於暫存器,特別是通用暫存器中的eax,ebx,ecx,edx,要理解32位的eax,16位的ax,8位的ah,al都是獨立的
運算元的三種型別:立即數、暫存器、儲存器
立即數:即常數值
暫存器:表示某個暫存器內容
儲存器:根據計算出來的地址(通常稱有效地址)訪問某個儲存器位置
有效地址的計算方式
- 符號擴充套件指令
pushl指令等價於:
subl $4,%esp movl %ebp,(%esp) //注意這裡的括號引起的差別
popl指令等價於:
movl (%esp),%eax addl $4,%esp
棧頂元素的地址是所有棧中元素地址中最低的
- 一元操作和二元操作
1.一元操作
只有一個運算元,既是源又是目的,可以是一個暫存器,或者儲存器位置。 例:加法運算子(++)和減1運算子
2.二元操作
第二個數既是源又是目的
第一個運算元可以是立即數、暫存器或者儲存器位置
第二個運算元可以是暫存器或者儲存器位置
兩個運算元但是不能同時是儲存器位置。
控制
一、條件碼
CF:進位標誌 ZF:零標誌 SF:符號標誌 OF:溢位標誌
注意:
leal不改變條件碼暫存器
CMP與SUB的區別:CMP也是根據兩個運算元之差設定條件碼,但只設定條件碼而不更新目標暫存器
有條件跳轉的條件看狀態暫存器(教材上叫條件碼暫存器)
控制中最核心的是跳轉語句:
有條件跳轉(實現if,switch,while,for)
無條件跳轉jmp(實現goto)
當執行PC相關的定址時,程式計數器的值是跳轉指令後面那條指令的地址,而不是跳轉指令本身的地址。
- (七) 過程
棧幀結構
為單個過程分配的棧叫做棧幀,暫存器%ebp為幀指標,而暫存器指標%esp為棧指標,程式執行時棧指標移動,大多數資訊的訪問都是相對於幀指標。
棧向低地址方向增長,而棧指標%esp指向棧頂元素。
轉移控制
call:目標是指明被呼叫過程起始的指令地址,效果是將返回地址入棧,並跳轉到被呼叫過程的起始處。
ret:從棧中彈出地址,並跳轉到這個位置。
函式返回值存在%eax中
leave
這個指令可以使棧做好返回的準備,等價於: movl %ebp,%esp popl %ebp
檢視函式呼叫棧資訊的GDB命令
backtrace/bt nn是一個正整數,表示只列印棧頂上n層的棧資訊。
n表一個負整數,表示只列印棧底下n層的棧資訊。
frame nn是一個從0開始的整數,是棧中的層編號。這個指令的意思是移動到n指定的棧幀中去,並列印選中的棧的資訊。如果沒有n,則列印當前幀的資訊。
up n表示向棧的上面移動n層,可以不打n,表示向上移動一層。
down n表示向棧的下面移動n層,可以不打n,表示向下移動一層。
教材中遇到的問題及解決以及感悟
3.1練習中有一道題是
260(%eax,%edx)
,我覺得這個好像題目並沒有給出,後來對比下面發現這個260原來是十進位制,然後轉化為0x104就做出了答案3.2練習中關於改變
push $0xff
的指令字尾,開始我考慮根據立即數$0xff
應該是pushw
,後來我發現對於棧操作都是雙字操作,所以不管是pop
還是push
都應該使用pushl
和popl
- 3.4練習是關於型別轉化的練習,有幾個注意的地方
- 有符號擴充套件為無符號數使用符號擴充套件而不是零擴充套件
- 無論是有符號數還是無符號數在往更小的型別進行縮小的時候只需要使用低八位的暫存器。
3.7練習注意有幾個二元操作的指令的用法與出現運算元的數量有關。
3.8練習移位量只能儲存在單位元組暫存器元素%cl中。
3.10練習是一個十分有趣的練習,他告訴了我們另外一種把暫存器的值置為0的操作,對比於更直接的
movl $0,%edx
,xorl的優勢在於只需要兩個位元組,而movl需要五個位元組。P122頁最下面有一段
movl 8(%ebp),%(edx)
、sarl $31,%edx
,這裡做的是將16位的x進行符號擴充套件到32位,其中高16位放在%edx
中,低16位放在%eax
中。3.12練習,剛開始做我有點懵,因為都做慣了雙字的資料,沒想到y竟然是四字的資料。要注意的是x*y_l乘積應該使用無符號乘法,而對y_h的低32位使用無符號和有符號都沒有關係。
3.15練習我覺得很重要,做會這個基本就瞭解指令重定位的基本做法。
3.7節講述的是函式在呼叫時,棧地址如何變化,如何使用特定的暫存器保護原來的數值。
程式碼情況
- 本週的學習基本都在書上,並沒有進行程式碼的編寫的。