Golang 的構建模式
Golang 的構建模式(buildmode)指的是編譯器如何編譯源碼構建出相關的對象文件,最常見的情況下就是生成一個可執行的二進制文件。然而,其實 golang 的 buildmode 還有很多有趣的用法……
在 go build 和 go install 命令中,我們可以指定 -buildmode 參數來讓編譯器構建出特定的對象文件。通過命令 go help buildmode,可以看到其支持的選項:
-buildmode=archive
-buildmode=c-archive
-buildmode=c-shared
-buildmode=default
-buildmode=shared
-buildmode=exe
-buildmode=pie
-buildmode=plugin
排除掉最常用的 default,接下來我們就挑選幾個比較有意思的來詳細講解一下。
說明:
Build the listed main package, plus all packages it imports, into a C archive file. The only callable symbols will be those functions exported using a cgo //export comment. Requires exactly one main package to be listed.
c-archive 也就是將 package main 中導出的方法(// export 標記)編譯成 .a 文件,這樣其它 c 程序就可以靜態鏈接該文件,並調用其中的方法。
Go 示例:
首先寫一個簡單 add.go :
package main
import "fmt"
import "C"
func main(){}
//export Add
func Add(a, b int) int{
fmt.Printf("%d + %d = %d\n", a, b, a+ b)
return a+b
}
然後使用 -buildmode=c-archive 編譯:
$ go build -buildmode=c-archive add.go
生成兩個文件: add.a 和 add.h,查看一下文件的類型:
$ file add.a add.h
// output
add.a: current ar archive random library
add.h: c program text, ASCII text
在 add.h 中我們看到 Add() 函數的定義:
#ifdef __cplusplus
extern "C" {
#endif
extern GoInt Add(GoInt p0, GoInt p1);
#ifdef __cplusplus
}
#endif
C 示例:
接下來,我們再寫一個簡單的 C 程序:
# include "add.h"
int main(void) {
Add(1, 2);
return 0;
}
加上 add.a 文件編譯一下:
$ cc myadd.c add.a
生成一個可執行文件 a.out,執行 ./a.out:
$ ./a.out
// output
1 + 2 = 3
說明:
Build the listed main package, plus all packages it imports, into a C shared library. The only callable symbols will
be those functions exported using a cgo //export comment. Requires exactly one main package to be listed.
c-shared 也就是將 package main 中導出的方法(// export 標記)編譯成一個動態鏈接庫(.so 或 .dll 文件),這樣其它 c 程序就可以調用其中的方法。
Go 示例:
繼續使用上面的 add.go,改用 -buildmode=c-shared 來編譯:
$ go build -buildmode=c-shared -o add.so add.go
這次生成了 add.so 和 add.h 兩個文件:
$ file add.so add.h
// output
add.so: Mach-O 64-bit dynamically linked shared library x86_64
add.h: c program text, ASCII text
C 示例:
繼續使用上面的 myadd.c 進行編譯:
$ cc myadd.c add.so
同樣是生成了 a.out 可執行文件,但是留意一下前後兩次的體積:
- 使用 add.so 編譯生成的 a.out 才 8.2KB;
- 使用 add.a 編譯生成的 a.out 有 1.8MB;
如果我們將 add.so 刪除,再運行 a.out:
dyld: Library not loaded: add.so
Referenced from: /Users/jachua/go/src/buildmode/c-archive/./a.out
Reason: image not found
[1] 94945 abort ./a.out
另外,我們也可以指定環境變量 LD_LIBRARY_PATH 告訴操作系統到哪裏去尋找動態鏈接庫:
// Linux
$ LD_LIBRARY_PATH=./lib ./out
// macOS
$ DYLD_LIBRARY_PATH=./lib ./out
版本要求:
G0 1.5
For the amd64 architecture only, the compiler has a new option,
-dynlink
, that assists dynamic linking by supporting references to Go symbols defined in external shared libraries.
G0 1.6
The implementation of build modes started in Go 1.5 has been expanded to more systems. This release adds support for the
c-shared
mode onandroid/386
,android/amd64
,android/arm64
,linux/386
, andlinux/arm64
; for theshared
mode onlinux/386
,linux/arm
,linux/amd64
, andlinux/ppc64le
; and for the newpie
mode (generating position-independent executables) onandroid/386
,android/amd64
,android/arm
,android/arm64
,linux/386
,linux/amd64
,linux/arm
,linux/arm64
, andlinux/ppc64le
. See the design document for details.
Go 1.10
The various build modes have been ported to more systems. Specifically,
c-shared
now works onlinux/ppc64le
,windows/386
, andwindows/amd64
;pie
now works ondarwin/amd64
and also forces the use of external linking on all systems; andplugin
now works onlinux/ppc64le
anddarwin/amd64
.
Go 1.11
The build modes
c-shared
andc-archive
are now supported onfreebsd/amd64
.
說明:
Combine all the listed non-main packages into a single shared library that will be used when building with the -linkshared option. Packages named main are ignored.
shared 與 c-shared 類似,不過它是用來給 golang 構建動態鏈接庫的。它將 非 main 的 package 編譯爲動態鏈接庫,並在構建其他 go 程序時使用 -linkshared 參數指定。
示例:
首先我們寫一個非常簡單的 hello.go:
package main
import "fmt"
func main(){
fmt.Println("hello world")
}
然後,我們需要將 golang 的所有標準庫 std 編譯安裝爲 shared:
// -buildmode=shared 暫不支持 macOS
$ go install -buildmode=shared std
接着再用 -linkshared 編譯 hello.go:
$ go build -linkshared hello.go
可以看到生成的可執行文件體積才 20KB ,相比正常的 go build hello.go 生成的 1.9MB 小非常多。我們可以使用 ldd 命令來查看調用鏈:
$ ldd hello
linux-vdso.so.1 (0x00007ffcb0db9000)
libstd.so => /usr/local/go/pkg/linux_amd64_dynlink/libstd.so (0x00007f2d5c1cb000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f2d5bdda000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f2d5bbd6000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f2d5b9b7000)
/lib64/ld-linux-x86-64.so.2 (0x00007f2d5eb96000)
當然如果缺少了其中某個鏈接庫或者版本不匹配,都將導致無法正常運行,所以一般情況下這種構建模式很少使用。
說明:
_Build the listed main packages, plus all packages that they _import, into a Go plugin. Packages not named main are ignored.
plugin 模式是 golang 1.8 才推出的一個特殊的構建方式,它將 package main 編譯爲一個 go 插件,並可在運行時動態加載。
示例:
首先,我們來寫一個簡單的 greeting:
package main
import "fmt"
type greeting string
func (g greeting) Greet() {
fmt.Println("hello world")
}
var Greeter greeting
然後將其編譯爲一個 go 插件:
$ go build -buildmode=plugin -o greeter.so greeter.go
$ file greeter.so
greeter.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=f5729749535c706a6095994f9510da92ccc8f9c6, not stripped
再使用 golang 官方的 plugin 庫來調用這個插件:
package main
import (
"fmt"
"os"
"plugin"
)
type Greeter interface {
Greet()
}
func main() {
plug, err := plugin.Open("./greet/greeter.so")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
symGreeter, err := plug.Lookup("Greeter")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
var greeter Greeter
greeter, ok := symGreeter.(Greeter)
if !ok {
fmt.Println(err)
os.Exit(1)
}
greeter.Greet()
}
版本說明:
Go 1.8
Go now provides early support for plugins with a “
plugin
” build mode for generating plugins written in Go, and a newplugin
package for loading such plugins at run time. Plugin support is currently only available on Linux. Please report any issues.
G0 1.10
The various build modes have been ported to more systems. Specifically,
c-shared
now works onlinux/ppc64le
,windows/386
, andwindows/amd64
;pie
now works ondarwin/amd64
and also forces the use of external linking on all systems; andplugin
now works onlinux/ppc64le
anddarwin/amd64
.
參考:
- https://medium.com/learning-the-go-programming-language/calling-go-functions-from-other-languages-4c7d8bcc69bf
- https://docs.google.com/document/d/1nr-TQHw_er6GOQRsF6T43GGhFDelrAP0NqSS_00RgZQ/edit?pli=1#
- https://blog.csdn.net/linuxandroidwince/article/details/78723441
- https://blog.lab99.org/post/golang-2017-10-01-video-go-build-mode.html
- https://medium.com/learning-the-go-programming-language/writing-modular-go-programs-with-plugins-ec46381ee1a9
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://chenjiehua.me/golang/golang-buildmode.html