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:

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 on android/386android/amd64android/arm64linux/386, and linux/arm64; for the shared mode on linux/386linux/armlinux/amd64, and linux/ppc64le; and for the new pie mode (generating position-independent executables) on android/386android/amd64android/armandroid/arm64linux/386linux/amd64linux/armlinux/arm64, and linux/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 on linux/ppc64lewindows/386, and windows/amd64pie now works on darwin/amd64 and also forces the use of external linking on all systems; and plugin now works on linux/ppc64le and darwin/amd64.

Go 1.11

The build modes c-shared and c-archive are now supported on freebsd/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 new plugin 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 on linux/ppc64lewindows/386, and windows/amd64pie now works on darwin/amd64 and also forces the use of external linking on all systems; and plugin now works on linux/ppc64le and darwin/amd64.

參考:

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://chenjiehua.me/golang/golang-buildmode.html