Go Wiki:GcToolchainTricks
本頁文件介紹了 `gc` 工具鏈(以及 Go 工具)一些不太為人所知(可能比較高階)的技巧。
不使用 `cgo` 的 C 程式碼
使用 `syso` 檔案嵌入任意獨立的 C 程式碼
基本上,你可以用 GNU as(1) 格式編寫你的組合語言,但要確保所有介面函式都使用 Go 的 ABI(一切都在棧上,等等,請閱讀 Go 1.2 Assembler Introduction 瞭解更多細節)。
最重要的一步是將該檔案編譯為 file.syso(`gcc -c -O3 -o file.syso file.S`),並將生成的 syso 檔案放在包的原始碼目錄中。然後,假設你的彙編函式名為 Func,你需要一個 cmd/asm 彙編檔案作為存根來呼叫它。
TEXT ·Func(SB),$0-8 // please set the correct parameter size (8) here
JMP Func(SB)
然後你只需在你的包中宣告 Func 並使用它,`go build` 就可以拾取 syso 檔案並將其連結到包中。
注意事項
- 生成的二進位制檔案不會使用 cgo,開銷只是一個可以完美進行分支預測的無條件 JMP。但是,請注意,因為它不使用 cgo,所以你的彙編函式執行在 Go 棧上,並且它不應該使用過多的棧空間(安全值為 100 位元組以下),否則會發生可怕的事情。對於計算核心,這個要求並不算太苛刻。
- 請確保你在 C 程式碼中包含了所有庫依賴項。`libc` 不可用,最值得注意的是,`libgcc` 也不可用(尤其是在你使用 gcc `__builtin_funcs` 時,請使用 `nm(1)` 來仔細檢查你的檔案是否包含任何未定義符號)。
- 也可以從 C 程式碼呼叫 Go 函式,但這留給讀者作為練習。
- 此技巧在所有 Go 1.x 版本中都受支援。
- Go 連結器功能非常強大,你只需要為每種架構準備 .syso 檔案,而不需要為每個作業系統/架構組合都準備(假設你沒有使用特定於作業系統的構造,顯然),並且 Go 連結器完全能夠將例如 Mach-O 物件檔案連結到 ELF 二進位制檔案中。因此,請確保你的 syso 檔案命名為 `file_amd64.syso`、`file_386.syso` 等。
將資料打包到 Go 二進位制檔案中
有很多方法可以將資料打包到 Go 二進位制檔案中,例如:
- 將資料檔案 `zip` 起來,並將 zip 檔案附加到 Go 二進位制檔案的末尾,然後使用 `zip -A prog` 來調整打包的 zip 頭。你可以使用 `archive/zip` 將程式作為 zip 檔案開啟,並輕鬆訪問其內容。有一些現有的包可以幫助完成這個任務,例如 https://pkg.go.dev/bitbucket.org/tebeka/nrsc; 這需要對程式二進位制檔案進行後處理,不適合需要靜態資料的非 main 包。此外,你必須將所有資料檔案收集到一個 zip 檔案中,這意味著無法使用多個利用此方法的包。
- 將二進位制檔案作為 `string` 或 `[]byte` 嵌入到 Go 程式中。不推薦使用此方法,不僅因為生成的 Go 原始檔比二進位制檔案本身大得多,還因為靜態的 `[]byte` 會減慢包的編譯速度,並且 `gc` 編譯器在編譯時會消耗大量記憶體(這是 `gc` 的一個已知 bug)。例如,請參閱 tools/godoc/static 包。
- 使用類似的 `syso` 技術來打包資料。使用 GNU `as(1)` 的 `.incbin` 偽指令預編譯資料檔案作為 syso 檔案。
第三種選擇的關鍵技巧是,`gc` 工具鏈的連結器能夠輕鬆地將不同架構的 COFF 物件檔案連結到二進位制檔案中,因此你不需要為所有支援的架構提供 syso 檔案。只要 syso 檔案不包含指令,你就可以只用一個來嵌入資料。
生成 COFF .syso 檔案的彙編模板
/* data.S, as -o data.syso */
.section .rdata,"dr" /* put in COFF section .rdata */
.globl _bindataA /* no longer need to prepend package name here */
.globl _ebindataA
_bindataA:
.incbin "dataA"
_ebindataA:
.globl _bindataB /* no longer need to prepend package name here */
.globl _ebindataB
_bindataB:
.incbin "dataB"
_ebindataB:
以及另外兩個檔案,第一個是用於將切片組裝給 Go 的 Plan 9 C 原始檔。
/* slice.c */
#include "runtime.h"
extern byte _bindataA[], _bindataB[], _ebindataA, _ebindataB;
void ·getDataSlices(Slice a, Slice b) {
a.array = _bindataA;
a.len = a.cap = &_ebindataA - _bindataA;
b.array = _bindataB;
b.len = b.cap = &_ebindataB - _bindataB;
FLUSH(&a);
FLUSH(&b);
}
最後,是使用嵌入切片 的 Go 檔案。
/* data.go */
package bindata
func getDataSlices() ([]byte, []byte) // defined in slice.c
var A, B = getDataSlices()
注意:你需要一個能夠生成 COFF syso 檔案的 `as(1)`,你可以在 Unix 上輕鬆構建一個。
wget http://ftp.gnu.org/gnu/binutils/binutils-2.22.tar.bz2 # any newer version also works
tar xf binutils-2.22.tar.bz2
cd binutils-2.22
mkdir build; cd build
../configure --target=i386-foo-pe --enable-ld=no --enable-gold=no
make
# use gas/as-new to assemble your data.S
# all the other file could be discarded.
缺點是,此方法似乎與 cgo 不相容,因此至少目前為止,僅在你未使用 cgo 時使用它。我(minux)正在努力弄清楚它們為什麼不相容。
在可執行檔案中包含構建資訊
`gc` 工具鏈連結器 `cmd/link` 提供了一個 `-X` 選項,可用於在連結時將任意資訊記錄到 Go 字串變數中。格式為 `importpath.name=val`。其中 `importpath` 是匯入語句中使用的包名(或主包使用 `main`),`name` 是包中定義的字串變數名,`val` 是你想要設定給該變數的字串。在使用 go 工具時,請使用其 `-ldflags` 選項將 `-X` 選項傳遞給連結器。
假設此檔案是 `company/buildinfo` 包的一部分。
package buildinfo
var BuildTime string
你可以使用 `go build -ldflags="-X 'company/buildinfo.BuildTime=$(date)'"` 來構建使用此包的程式,以記錄構建時間。 (`$(date)` 的使用假設你使用的是類 Unix shell。)
字串變數必須存在,它必須是一個變數,而不是常量,並且其值不能由函式呼叫初始化。使用 `-X` 選項中的錯誤名稱不會有任何警告。你通常可以透過執行程式的 `go tool nm` 來找到要使用的名稱,但如果包名包含任何非 ASCII 字元、`"` 或 `%` 字元,則會失敗。
此內容是 Go Wiki 的一部分。