(參考鏈接:https://zhuanlan.zhihu.com/p/69393545)
Linux
我們在Linux下嘗試編寫裸機程序,可能出現這樣的鏈接器錯誤:
error: linking with `cc` failed: exit code: 1 | = note: "cc" […] = note: /usr/lib/gcc/../x86_64-linux-gnu/Scrt1.o: In function `_start': (.text+0x12): undefined reference to `__libc_csu_fini' /usr/lib/gcc/../x86_64-linux-gnu/Scrt1.o: In function `_start': (.text+0x19): undefined reference to `__libc_csu_init' /usr/lib/gcc/../x86_64-linux-gnu/Scrt1.o: In function `_start': (.text+0x25): undefined reference to `__libc_start_main' collect2: error: ld returned 1 exit status
這里遇到的問題是,鏈接器將默認引用C語言運行時的啟動流程,或者也被描述為_start
函數。它將使用我們在no_std
下被排除的C語言標准庫實現庫libc
,因此鏈接器不能解析相關的引用,得到“undefined reference”問題。為解決這個問題,我們需要告訴鏈接器,它不應該引用C語言使用的啟動流程——我們可以添加-nostartfiles
標簽來做到這一點。
要通過cargo添加參數到鏈接器,我們使用cargo rustc
命令。這個命令的作用和cargo build
相同,但允許開發者向下層的Rust編譯器rustc
傳遞參數。另外,rustc
提供一個-C link-arg
標簽,它能夠傳遞所需的參數到鏈接器。綜上所述,我們可以編寫下面的命令:
cargo rustc -- -C link-arg=-nostartfiles
這樣之后,我們的包應該能成功編譯,作為Linux系統下的獨立式可執行程序了。這里我們沒有顯式指定入口點函數的名稱,因為鏈接器將默認使用函數名_start
。
Windows
在Windows系統下,可能有不一樣的鏈接器錯誤:
error: linking with `link.exe` failed: exit code: 1561 | = note: "C:\\Program Files (x86)\\…\\link.exe" […] = note: LINK : fatal error LNK1561: entry point must be defined
鏈接器錯誤提示“必須定義入口點”,意味着它沒有找到入口點。在Windows系統下,默認的入口點函數名由使用的子系統決定[1]。對CONSOLE
子系統,鏈接器將尋找名為mainCRTStartup
的函數;而對WINDOWS
子系統,它將尋找WinMainCRTStartup
。我們的_start
函數並非這兩個名稱——為了使用它,我們可以向鏈接器傳遞/ENTRY
參數:
cargo rustc -- -C link-arg=/ENTRY:_start
我們也能從這里的參數的格式中看到,Windows系統下的鏈接器在使用方法上,與Linux下的有較大不同。
運行命令,我們得到了另一個鏈接器錯誤:
error: linking with `link.exe` failed: exit code: 1221 | = note: "C:\\Program Files (x86)\\…\\link.exe" […] = note: LINK : fatal error LNK1221: a subsystem can't be inferred and must be defined
產生這個錯誤,是由於Windows可執行程序可以使用不同的子系統(subsystem)。對一般的Windows程序,使用的子系統將由入口點的函數名推斷而來:如果入口點是main
函數,將使用CONSOLE
子系統;如果是WinMain
函數,則使用WINDOWS
子系統。由於我們的_start
函數名稱與上兩者不同,我們需要顯式指定使用的子系統:
cargo rustc -- -C link-args="/ENTRY:_start /SUBSYSTEM:console"
這里我們使用CONSOLE
子系統,但WINDOWS
子系統也是可行的。這里,我們使用復數參數link-args
代替多個-C link-arg
,因為后者要求把所有參數依次列出,比較占用空間。
使用這行命令后,我們的可執行程序應該能在Windows下運行了。
macOS
如果使用macOS系統開發,我們可能遇到這樣的鏈接器錯誤:
error: linking with `cc` failed: exit code: 1 | = note: "cc" […] = note: ld: entry point (_main) undefined. for architecture x86_64 clang: error: linker command failed with exit code 1 […]
這個錯誤消息告訴我們,鏈接器不能找到默認的入口點函數,它被命名為main
——出於一些原因,macOS的所有函數名都被加以下划線_
前綴。要設置入口點函數到_start
,我們傳送鏈接器參數-e
:
cargo rustc -- -C link-args="-e __start"
-e
參數指定了入口點的名稱。由於每個macOS下的函數都有下划線_
前綴,我們應該命名入口點函數為__start
,而不是_start
。
運行這行命令,現在出現了這樣的鏈接器錯誤:
error: linking with `cc` failed: exit code: 1 | = note: "cc" […] = note: ld: dynamic main executables must link with libSystem.dylib for architecture x86_64 clang: error: linker command failed with exit code 1 […]
得到這個錯誤的原因是,macOS並不官方支持靜態鏈接的二進制庫[2],而要求程序默認鏈接到libSystem
庫。要鏈接到靜態二進制庫,我們把-static
標簽傳送到鏈接器:
cargo rustc -- -C link-args="-e __start -static"
運行修改后的命令。鏈接器似乎並不滿意,又給我們拋出新的錯誤:
error: linking with `cc` failed: exit code: 1 | = note: "cc" […] = note: ld: library not found for -lcrt0.o clang: error: linker command failed with exit code 1 […]
出現這個錯誤的原因是,macOS上的程序默認鏈接到crt0
(C runtime zero)庫。這和Linux系統上遇到的問題相似,我們可以添加一個-nostartfiles
鏈接器參數:
cargo rustc -- -C link-args="-e __start -static -nostartfiles"
現在,我們的程序應該能夠在macOS上成功編譯了。