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,你需要一个存根 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 文件,而不是为每个 OS/Arch 组合准备(显然,假设你没有使用特定于操作系统的构造),并且 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 文件中,这意味着不可能使用多个利用此方法的包。- 将二进制文件作为
string
或[]byte
嵌入到 Go 程序中。不推荐使用此方法,不仅是因为生成的 Go 源文件远大于二进制文件本身,还因为静态大型[]byte
会减慢包的编译速度,并且gc
编译器使用大量内存来编译它(这是gc
的已知错误)。例如,请参阅 tools/godoc/static 包。 - 使用类似的
syso
技术来捆绑数据。使用 GNUas(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 字符串变量中。格式为 -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 的一部分。