Go Wiki:GcToolchainTricks
此頁面記載了較不為人知(可能是進階)的 gc
工具鏈(以及 Go 工具)技巧。
沒有 cgo
的 C 程式碼
使用 syso
檔案來嵌入任意的自訂 C 程式碼
基本上,您使用 GNU as(1) 格式撰寫組合語言,但請確定所有介面函式都使用 Go 的 ABI(所有內容都在堆疊中,等等,請閱讀 Go 1.2 組合語言簡介 以取得更多詳細資料)。
最重要的步驟是將該檔案編譯為 file.syso(gcc -c -O3 -o file.syso file.S
),並將產生的 syso 放入套件原始碼目錄中。然後,假設您的組合語言函式名稱為 Func,您需要一個 stub 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 二進位檔。因此請務必使用像
file_amd64.syso
、file_386.syso
這樣的名稱來命名你的 syso 檔案。
將資料打包到 Go 二進位檔中
有很多方法可以將資料打包到 Go 二進位檔中,例如
zip
資料檔案,並將 zip 檔案附加到 Go 二進位檔的結尾,然後使用zip -A prog
來調整打包的 zip 標頭。你可以使用archive/zip
將程式開啟為 zip 檔案,並輕鬆存取其內容。有一些現有的套件可以協助處理這件事,例如,https://pkg.go.dev/bitbucket.org/tebeka/nrsc; 這需要對程式二進位檔進行後處理,這不適合需要靜態資料的非主套件。此外,你必須將所有資料檔案收集到一個 zip 檔案中,這表示不可能使用多個套件來使用這個方法。- 將二進位檔案嵌入到 Go 程式中的
string
或[]byte
中。不建議使用這個方法,不僅是因為產生的 Go 來源檔案遠大於二進位檔案本身,也因為靜態大型[]byte
會減慢套件的編譯速度,而且gc
編譯器會使用大量的記憶體來編譯它(這是gc
已知的錯誤)。例如,請參閱 tools/godoc/static 套件。 - 使用類似的
syso
技術來打包資料。使用 GNUas(1)
的.incbin
偽指令,將資料檔案預編譯為 syso 檔案。
第 3 個替代方案的關鍵技巧是,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:
另外兩個檔案,第一個是 Plan 9 C 原始碼檔案,用來組裝 Go 的切片
/* 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 字串變數中記錄任意資訊。格式為 -X 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 的一部分。