3.2程序編碼
簡單描述程序的編譯過程:
1. 預編譯階段:將#include包含的文件合併到一個文件裏,將#define宏定義的宏名替換
2. 編譯階段:將源程序翻譯爲彙編程序(以.s結尾)
3. 彙編階段:將彙編代碼翻譯成目標代碼(以.o結尾)。注:目標代碼是機器代碼的一種(二進制代碼),它包含所有指令的二進制表示,但是還沒有填入全局值的地址
4. 鏈接器將多個源程序生成的目標代碼(.o文件)與庫函數的代碼合併。鏈接器的任務之一就是爲函數的調用找到匹配的函數,也就是匹配的函數的可執行代碼的地址
一.機器級代碼(面向機器的代碼)
1.機器級編程的兩種抽象
- 由指令集體系結構或指令集架構(ISA)來定義機器級程序的格式和行爲。
大多數ISA將程序的行爲描述成好像每條指令都是按順序執行的,一條指令結束,下一條指令再開始。處理器的硬件併發地執行許多指令,但是可以採取措施保證整體行爲與ISA指定的順序執行的行爲完全一致。
- 機器級程序使用的內存地址是虛擬地址,提供的內存模型看上去是一個非常大的字節數組。
2.可見的處理器狀態
- 程序計數器(PC),在x86-64中使用%rip表示。程序計數器給出將要執行的下一條指令的地址。
- 整數寄存器文件,包含16個命名的位置,分別存儲64位的整數值,用來存儲地址和整型數據
- 條件寄存器,保存最近執行的算術或邏輯指令的狀態信息。用來實現控制和數據流中的條件變化。比如可以實現if和while
- 向量寄存器,一組向量寄存器可以存放一個或多個整數或浮點數
3.程序運行時所需要的內存
- 程序的可執行代碼
- 操作系統需要的信息
- 管理過程調用的運行時棧
- 用戶分配的內存塊(例如調用malloc分配的內存)
4.小結
機器執行的程序只是一個字節序列,它是對一系列指令的編碼。
二.代碼示例
有如下的C代碼
/**
* mstore.c
**/
long mult2(long, long);
void multstore(long x, long y, long *dest){
long t = mult2(x, y);
*dest = t;
}
通過gcc -0g -S mstore.c編譯爲彙編代碼。 -0g表示一種優化等級,告訴編譯器生成符合原始C代碼整體結構的彙編代碼。
.file "mstore.c"
.text
.globl multstore
.def multstore; .scl 2; .type 32; .endef
.seh_proc multstore
multstore:
pushq %rbx
.seh_pushreg %rbx
subq $32, %rsp
.seh_stackalloc 32
.seh_endprologue
movq %r8, %rbx
call mult2
movl %eax, (%rbx)
addq $32, %rsp
popq %rbx
ret
.seh_endproc
.ident "GCC: (tdm64-1) 4.9.2"
.def mult2; .scl 2; .type 32; .endef
以“.”開頭的行都是直到編譯器和鏈接器工作的僞指令,通常可以忽略這些行,刪減之後的彙編代碼如下
multstore:
pushq %rbx
subq $32, %rsp
movq %r8, %rbx
call mult2
movl %eax, (%rbx)
addq $32, %rsp
popq %rbx
ret
被單獨標記的那句mov指令與書上的例子有些出入,書上的這句話爲
movq %rdx,%rbx
源程序中,multstore的第三個參數dest,根據書上的描述應該被放在%rdx中,但是我自己編譯後第三個參數卻放在了%r8中,這與後面描述過程(函數)參數傳遞時的寄存器順序不符,可能是我自己平臺的問題。
機器代碼的特徵
- -x86-64的指令長度從1-15個字節不等。
- 設計指令格式的方式是:從某個給定位置開始,可以將字節唯一地翻譯成機器指令,例如,只有指令push %rbx是以字節值53開頭。
三.將彙編代碼合併到C代碼的兩種方法
1.通過鏈接器和彙編器
將需要使用匯編指令編寫的代碼單獨寫到一個彙編文件中(*.s),在彙編和鏈接階段與其他C代碼編譯成的彙編代碼進行彙編與鏈接。
2.利用GCC的支持
利用GCC的支持,直接在C代碼中嵌入彙編代碼。