1. 程式人生 > 實用技巧 >【CSAPP】第三章 程式的機器級表示

【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的高階特性

  1. 自動推導:make可以識別一個.o檔案,自動將對應的.c檔案加在依賴關係中。並且也會自動推匯出相關的編譯命令。因此上述的makefile檔案可以只包含前兩行

  2. 使用變數

    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迴圈

  1. 中間跳轉法
	goto test
loop:
	body-statement
test:
	t = test-expr
	if(t) goto loop
  1. 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的區域性變數