修改 Makefile
已經把程式分成數個檔案了,接著,也該來跟著改 Makefile 了。底下的 Makefile,會把現在所在資料夾的所有 .c 檔全部編譯並連結起來,做出 9cc 這個可執行檔。假設專案的標頭檔只有 9cc.h 這1個檔案,而這個標頭檔會被所有的 .c 檔所引入。
注意 Makefile 的縮排必須要用 tab 而不能用空白。
make
是非常強力的工具,精通不是必要的,但是熟悉到可以讀懂上面的 Makefile 的程度的話,在很多地方都能派上用場。我們在此針對上面的 Makefile 進行說明。
Makefile 中,1條規則是由以冒號分隔的行,和以 tab 縮排的0行以上的指令所構成。冒號前的名字稱為「目標」(target)。冒號後面0個以上的檔案名稱被稱為相依檔案。
執行make foo
,make
就會試著做出 foo 的檔案。如果所指定的目標檔案已經存在的話,如果目標檔案比相依檔案舊的話,make
就會重新執行該條目標的規則。因此,就可以只在程式碼有變更的時候才進行重編。
.PHONY
代表不是真正的目標的特殊名字。make test
或make clean
並不是為了做出 test 或 clean 的檔案而執行的,但make
並不知道這件事,如果剛好有 test 或 clean 的檔案在的話,make test
或make clean
就不會做任何事了。只要在在.PHONY
裡指定像這樣的假目標,就不會真的想要做出該檔案,告訴make
說:不論是否存在該目標的檔案都要執行該目標的規則。
CFLAGS
或SRCS
、OBJS
為變數。
CFLAGS
是make
內嵌的規則中會讀取的變數,是用來寫要給C編譯器下的指令參數。此處我們給出以下幾個 flags:
-std=c11
: 告訴編譯器我們的程式碼是用C最新的標準C11寫的-g
: 輸出除錯資訊-static
: 靜態連結
SRCS
的右側使用的wildcard
是make
所提供的函式,會把和其引數符合的檔名展開。$(wildcard *.c)
在現階段,會展開為main.c parse.c codegen.c container.c
。
OBJS
的右側使用變數的替換規則,生成把SRCS
中的.c
換成.o
的值。SRCS
為main.c parse.c codegen.c container.c
,所以OBJS
會是main.o parse.o codegen.o container.o
。
有這些認識之後,我們來追看看執行make 9cc
會發生什麼事。make
會想要生成引數所指定的目標,所以生出 9cc 這個檔案就是指令的最終目標(如果沒有引數就會是最初的目標,所以在此處不指定9cc
也沒關係)。make
會確認相依性,看有沒有少檔案,或是檔案太舊都會執行指令編出檔案。
9cc
相依的檔案為當下資料夾內 .c 檔所對應的 .o 檔。如果上次make
執行後有留下 .o 檔,並且其時間戳記(timestamp)比 .c 檔新,make
就不會多此一舉重新執行一樣的指令。只有在 .o 不存在,或是 .c 檔比較新的時候,編譯器才會執行產生 .o 檔。
$(OBJS): 9cc.h
這條規則表示所有的 .o 檔案都相依於 9cc.h。所以如果 9cc.h 有變更,所有的 .o 檔都會被重編。
小知識:用編譯器製作後門
有種把系統裡的編譯器換成惡意的編譯器,用其編譯出來的程式會被擅自埋進惡意程式碼的攻擊方式。
舉例來說,螢幕保護程式本身是寫成以全螢幕顯示,直到輸入正確格式的密碼才會結束的程式。我們來想想要怎麼破解它。
假定攻擊者有編譯器和螢幕保護程式的原始程式碼。於是攻擊者就可以改造編譯器,判斷輸入的檔案和螢幕保護程式是否一致,當一致的時候就追加上特別的操作。
螢幕保護程式中,應該可以找到檢查輸入的密碼的程式碼。我們來想像一下,當編譯含有這段程式碼的檔案的時候,編譯器會輸出會擅自讓攻擊者的密碼通過的程式。如此,使用該編譯器編譯出來的螢幕保護程式,不只知道密碼的合法使用者,連攻擊者也可以成功解鎖。
像這樣的攻擊,再怎麼檢查螢幕保護程式的原始程式碼也檢查不出後門。因為後門並不是在那邊,而是藏在編譯器的二進制檔案裡。
這類攻擊不僅限於編譯器。例如針對 OS 做修改,我們可以想像,可以只在特定使用者執行螢幕保護程式的時候,默默執行別的惡意的螢幕保護程式的 OS。為了妨礙檢查,修改objdump
或除錯器、OS 的檔案 IO,以達到隱藏後門的精巧架構也是可以想像的。
總而言之,軟體這種東西,除非使用可信賴的程式來製作和執行,都無法真正被相信。
Last updated