> For the complete documentation index, see [llms.txt](https://koshizuow.gitbook.io/compilerbook/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://koshizuow.gitbook.io/compilerbook/separate_compile_linking/step8/modify_makefile.md).

# 修改 Makefile

已經把程式分成數個檔案了，接著，也該來跟著改 Makefile 了。底下的 Makefile，會把現在所在資料夾的所有 .c 檔全部編譯並連結起來，做出 9cc 這個可執行檔。假設專案的標頭檔只有 9cc.h 這1個檔案，而這個標頭檔會被所有的 .c 檔所引入。

{% code title="Makefile" %}

```
CFLAGS=-std=c11 -g -static
SRCS=$(wildcard *.c)
OBJS=$(SRCS:.c=.o)

9cc: $(OBJS)
        $(CC) -o 9cc $(OBJS) $(LDFLAGS)

$(OBJS): 9cc.h

test: 9cc
        ./test.sh

clean:
        rm -f 9cc *.o *~ tmp*

.PHONY: test clean
```

{% endcode %}

注意 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`說：不論是否存在該目標的檔案都要執行該目標的規則。

&#x20;`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 檔都會被重編。

{% hint style="info" %}

#### 小知識：用編譯器製作後門

有種把系統裡的編譯器換成惡意的編譯器，用其編譯出來的程式會被擅自埋進惡意程式碼的攻擊方式。

舉例來說，螢幕保護程式本身是寫成以全螢幕顯示，直到輸入正確格式的密碼才會結束的程式。我們來想想要怎麼破解它。

假定攻擊者有編譯器和螢幕保護程式的原始程式碼。於是攻擊者就可以改造編譯器，判斷輸入的檔案和螢幕保護程式是否一致，當一致的時候就追加上特別的操作。

螢幕保護程式中，應該可以找到檢查輸入的密碼的程式碼。我們來想像一下，當編譯含有這段程式碼的檔案的時候，編譯器會輸出會擅自讓攻擊者的密碼通過的程式。如此，使用該編譯器編譯出來的螢幕保護程式，不只知道密碼的合法使用者，連攻擊者也可以成功解鎖。

像這樣的攻擊，再怎麼檢查螢幕保護程式的原始程式碼也檢查不出後門。因為後門並不是在那邊，而是藏在編譯器的二進制檔案裡。

這類攻擊不僅限於編譯器。例如針對 OS 做修改，我們可以想像，可以只在特定使用者執行螢幕保護程式的時候，默默執行別的惡意的螢幕保護程式的 OS。為了妨礙檢查，修改`objdump`或除錯器、OS 的檔案 IO，以達到隱藏後門的精巧架構也是可以想像的。

總而言之，軟體這種東西，除非使用可信賴的程式來製作和執行，都無法真正被相信。
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://koshizuow.gitbook.io/compilerbook/separate_compile_linking/step8/modify_makefile.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
