到這里,我們已經(jīng)說(shuō)完了編譯器的技術(shù)細(xì)節(jié),也同時(shí)完成了一門新的編程語(yǔ)言。
(Here,We introduced a compiler's basic techniques, meanwhile completed a new program language.)
按照計(jì)算機(jī)領(lǐng)域的慣例,當(dāng)一門語(yǔ)言制作完成時(shí),要打印一行"hello world"。
打開(kāi)一個(gè)文本編譯器,輸入以下代碼:
int printf(const char* fmt, …);
int main()
{
printf("hello world\n");
return 0;
}
然后保存為一個(gè)文件hello.c。
之所以跟C語(yǔ)言用同樣的擴(kuò)展名,是因?yàn)檫@樣文本編譯器就可以顯示語(yǔ)法顏色。
當(dāng)然,文件名也可以隨便起,反正編譯器都會(huì)把它當(dāng)作一個(gè)文本文件。
我在語(yǔ)法上盡量保持了與C語(yǔ)言的類似,但是沒(méi)有支持宏,所以把printf的函數(shù)聲明直接寫在第1行,而不是include "stdio.h"。
接下來(lái)編譯它,Linux上的shell命令是:./a.out hello.c
(當(dāng)然,在這之前要先用gcc編譯scf框架的源碼。)
./a.out hello.c命令的輸出如下:
編譯器啟動(dòng)之后,初始化語(yǔ)法分析的各個(gè)模塊。
然后,開(kāi)始源代碼的語(yǔ)法分析,首先分析printf()的函數(shù)聲明,
然后,分析main()函數(shù)的代碼:
分析完main()函數(shù)之后,也就到達(dá)了文件的結(jié)尾eof。
到了這里,語(yǔ)法分析就結(jié)束了。
接下來(lái)是編譯器的后端流程:
1,中間代碼優(yōu)化的日志,可以看到中間代碼、基本塊、循環(huán)和分組信息:
中間代碼優(yōu)化
2,然后開(kāi)始生成機(jī)器碼,CPU平臺(tái)是x86_64,
機(jī)器碼生成
3,給gdb生成debug信息,生成.o目標(biāo)文件,
debug信息和目標(biāo)文件
到了這里,編譯就結(jié)束了。
接下來(lái)是連接,即昨天那篇文章的內(nèi)容。
4,這段代碼很簡(jiǎn)單,連接時(shí)只需要從動(dòng)態(tài)庫(kù)里查找printf這一個(gè)函數(shù),
下圖是查找時(shí)打印的日志。
查找printf
5,找到printf之后,完成動(dòng)態(tài)連接,并且生成可執(zhí)行文件。
連接完成
6,用readelf -a 1.out查看生成的可執(zhí)行文件,信息如下:
scf編譯的默認(rèn)文件名是1.out。
ELF頭
從ELF頭可以看出,文件類型是EXEC可執(zhí)行文件,平臺(tái)是x86_64,入口地址0x400817。
各節(jié)的情況
"hello world"字符串常量在.rodata節(jié),動(dòng)態(tài)連接的printf()需要plt和got,如上圖。
程序頭
所需的動(dòng)態(tài)庫(kù),以及動(dòng)態(tài)重定位的函數(shù)。
calloc和free函數(shù)是因?yàn)槟J(rèn)把自動(dòng)內(nèi)存管理的scf_object.c文件也連接進(jìn)去了。
動(dòng)態(tài)庫(kù)、重定位函數(shù)和符號(hào)表
7,運(yùn)行結(jié)果:
運(yùn)行結(jié)果
給一張main()函數(shù)的最終內(nèi)容:
main函數(shù)
可以看到,連接器已經(jīng)修改了加載"hello world"字符串的內(nèi)存地址,也修改了調(diào)用printf的內(nèi)存地址。
]]>