【CSAPP】第三章 程式的機器級表示
第三章 程式的機器級表示
目錄
1. 程式編碼
1.1 gcc編譯C程式
編譯過程:
- 預處理:巨集定義展開、標頭檔案展開、條件編譯等,同時將程式碼中的註釋刪掉,這裡並不會檢查語法;
- 編譯:檢查語法,將預處理後的檔案編譯成彙編檔案;
- 彙編: 將彙編檔案生成目標檔案(二進位制檔案);
- 連結: C語言寫的程式是需要依賴各種庫的,所以編譯之後還需要把庫連結到可執行程式中去。
命令:
步驟 | 命令 |
---|---|
預處理 | gcc -E hello.c -o hello.i |
編譯 | gcc -S hello.i -o hello.s |
彙編 | gcc -c hello.s -o hello.o |
連結 | gcc hello.o -o hello_elf |
-
如果要一步到位直接生成可執行檔案,命令為
gcc hello.c -o hello
-
可以在gcc後指定 -Og/-O1/-O2設定編譯優化級別
以一下程式碼為例:
//main.c #include<stdio.h> #include<stdlib.h> void multstore(double, double, double*); int main(){ double d; multstore(2, 3, &d); printf("2 * 3-->%ld\n", d); system("pause"); return 0; } double mult2(double a, double b){ double s = a * b; return s; }
//mstore.c
double mult2(double, double);
void multstore(double x, double y, double *dest){
double t = mult2(x,y);
*dest = t;
}
預處理:
gcc main.c -o main.i
gcc mstore.c -o mstore.i
編譯:
gcc -S main.i -o main.s
gcc -S mstore.i -o mstore.s
彙編:
gcc -c main.s -o main.o
gcc -c mstore.s -o mstore.o
連結:
gcc main.o mstore.o //不指定名稱,預設為a.exe
一步:
gcc main.c mstore.c -o test//指定名稱為test.exe
1.2 Cmake使用
makefile的格式
target ... : prerequisites ...
command
...
...
target:輸出檔案的名稱
prerequisites:輸入的檔名稱
command:一系列的gcc命令
test : main.o mstore.o
gcc main.o mstore.o -o test
main.o : main.s
gcc main.s -o main.o
mstore.o : mstore.s
gcc mstore.s - mstore.o
main.s : main.c
gcc main.c -o main.s
mstore.s : mstore.c
gcc mstore.c -o mstore.s
make的高階特性
-
自動推導:make可以識別一個
.o
檔案,自動將對應的.c
檔案加在依賴關係中。並且也會自動推匯出相關的編譯命令。因此上述的makefile檔案可以只包含前兩行 -
使用變數
objs = main.o mstore.o test : $(objs) gcc $(objs) -o test
1.3 反彙編
機器程式碼反彙編到彙編:
objdump -d target.o
如:
彙編到C?
2. 資料表示
- 針對不同大小,資料傳送指令分為movb(位元組), movw(字), movl(雙字), movq(四字)
- 字尾 l 同時表示4位元組整數和8位元組雙精度浮點數,根據指令和暫存器進行區分
3. 資料訪問
3.1 整數暫存器
x86-64處理器包含了16個64bit的通用目的暫存器,儲存整數和指標
明明規則:
- 前8組以16bit暫存器為主,64 32和8bit分別帶r e 和l的字首或者字尾;
- 後8組以64bit暫存器為主,命名為r8~r15,32 16 8bit分別帶d w b字尾
功能:
- 函式返回值:ax
- 函式引數:di si dx cx r8 r9共9個,分別儲存1到6個函式引數,超出6個的引數被放在被調函式的棧幀上
- 被呼叫者儲存暫存器:剩餘的都是,不管哪個程式都可以使用,但是在使用前需要儲存暫存器原始資料,使用完要恢復(在函式呼叫時,儲存現場和恢復現場由被調函式完成)
3.2 定址
共三類:
- 立即數:只能是整數,浮點數沒有立即數
- 暫存器:16個暫存器的低位1 2 4 8位元組
- 記憶體引用:M[Addr]
定址方式:有效地址=Imm + R[rb] + R[ri]*s,表示為Imm(rb, ri, s)
3.3 資料傳送指令
簡單傳送指令
- movabsq的目的地之只能是64位暫存器
- movl會將暫存器高32位置0
擴充傳送(將少位數的擴充後傳送到多位數的)
3.4 出棧入棧
- 對rsp暫存器操作
- 向下生長
- 按位元組編址
3.5 算邏運算
-
結果都儲存在第二個暫存器處
-
除了leaq外,其餘指令都有 q l w b四個變種
-
特殊算數運算:針對8字(128位)的運算
rax存乘數、乘積低位、商,rdx存乘積高位、餘數
3.6 控制
控制碼
進位:CF
零標誌:ZF
負數標誌:SF
溢位標誌:OF
比較和測試
cmp S1, S2:基於S2-S1進行
test S1, S2:基於S1&S2
- 都有b l w q四種變種
- 只設置條件碼,不改變暫存器
訪問條件碼
set指令
總結:需要區分有符號數和無符號數的指令有移位、特殊除法、set指令
跳轉指令
- 直接跳轉:jump Label
- 間接跳轉:jump *Adrr
if語句實現
C語言模板:
if(test-expr){
then-statement
}else{
else-statement
}
彙編控制流:
t = test-expr
if(!t)
goto false;
then-statement
goto done
false:
else-statement
done:
用條件傳送實現分支
-
實現:計算出兩種操作的結果,然後根據條件選擇傳送一種
例如對於條件賦值表示式:
v = test-expr ? then-expr : else-expr
條件傳送的控制流為:
v = then-expr ve = else-expr t = test-expr if(!t)v = ve
-
副作用:需要對then-expr和else-expr都求值,一方面增加了計算量,另一方面可能導致非法行為(非法地址訪問)
迴圈實現
do-while迴圈
loop:
body-staement
t = test-expr
if(t) goto loop
while迴圈
- 中間跳轉法
goto test
loop:
body-statement
test:
t = test-expr
if(t) goto loop
- guarded-do
t = test-expr
if(!t) goto done
loop:
body-statement
t = test-expr
if(t) goto loop
done:
for迴圈,類似於while迴圈的實現
switch實現
跳轉表+間接跳轉
3.7 函式呼叫
函式呼叫的實現機制(P呼叫Q):
- 控制轉移:將Q的入口地址讀到rsp中,將P的斷點存到棧中
- 傳遞資料:P將Q需要的引數傳入,優先傳到rdi rsi rdx rcx r8 r9
- 分配和釋放記憶體:Q的區域性變數