cgoを使ったCとGoのリンクの裏側 (1)

[cgo](https://golang.org/wiki/cgo)を用いるとCのライブラリをGoバイナリにリンクしたり、Goパッケージの一部をCで書いたりできる。更にGo 1.5以降では、GoのパッケージをC用の静的ライブラリまたは動的ライブラリにまとめておいて、Cからリンクすることもできる。

これらの機能はすべて`go build`コマンドに統合されているので、普段は特にcgoを使っていることを意識することは少ない。しかし、pure goのコードのビルドにしたところでその裏側ではコンパイラ、アセンブラ、リンカが走っているわけである。ではcgoの場合をこの水準で見るとどのような処理が行われているのだろうか。
要は、gcc(1)の裏ではcc1, as, collect2なんかが走ってるよね、cgoではどうなってるの?　という話が本稿の話題である。

なお、Goのオブジェクトファイルがプラットフォーム独立な(ELFとかではない)フォーマットであることや、Goのパッケージをビルドすると独自メタデータ付きのarアーカイブにまとめられることなどは既知とする。

まず今回はCライブラリをGoにリンクする場合について見てみよう。なお検証はGo 1.6で行っている。

# サンプルコード

次のような２つのファイルから成るGoパッケージ[`github.com/yugui/cgo-explained/example1`](https://github.com/yugui/cgo-explained/tree/master/example1)を考える。
この程度であれば実際にはファイルを分ける必要はないが、あとのリンクプロセスを見るために意図的にcgoを含まないgoファイル`main.go`を作った。

```go:import_example.go
package main

import (
	//#cgo LDFLAGS: -lm
	//#include <math.h>
	"C"
	"fmt"
)

func printSqrt(n int) {
	fmt.Println(C.sqrt(C.double(n)))
}

```

```go:main.go
package main

func main() {
	printSqrt(10)
}

```

まあ、ソースコードの依存関係は下記の要領である。
![deps.png](https://qiita-image-store.s3.amazonaws.com/0/1806/0311eba9-c0c7-cbf3-9326-fe21d924efc1.png)


# ビルドプロセス
上のパッケージを`go build github.com/yugui/cgo-explained/example1`とビルドする場合、次のようなプロセスが走る。
各ステップの詳細については後述する。

1. コード生成
   * `go tool cgo import_example.go`
2. Cコードのコンパイル
   * `gcc -c SOURCES`
3. Cコードのリンク
   * `gcc -o _cgo_.o OBJS`
4. インポート宣言の生成
   * `go tool cgo -dynimport ....`
5. Goコードのコンパイル
   * `go tool compile -o example1.a -pack GO_FILES`
6. Cコードの再リンク
   * `gcc -o _all.o OBJS`
7. Cオブジェクトをアーカイブに追加
   * `go tool pack r example1.a _all.o`
8. アーカイブ内のオブジェクトをリンク
   * `go tool link -o example example1.a`

データフローは次のようになっている。各ファイルについては追々見ていく。

![flow.png](https://qiita-image-store.s3.amazonaws.com/0/1806/de5a7a2b-5f23-cfd9-acf3-e42ac52ea810.png)

## コード生成  
まず`go build`はパッケージ内の`.go`ファイルのうちcgoを利用しているファイルのみを`cgo`コマンドに渡す。
上例では対象は`import_example.go`だけで、概ね次のようなコマンドが走る。
複数のファイルがcgoを利用している場合はすべてまとめてコマンドライン引数として引き渡す。

```console
$ env CGO_LDFLAGS=-lm go tool cgo -objdir $tmpobjdir \
>  -importpath github.com/yugui/cgo-explained/example1 \
>  import_example.go
```
 
このコマンドは`$tmpobjdir`ディレクトリに次のファイルを生成する。

* `_cgo_export.c`: 後で登場するが、今回は大した情報は入っていない。
* `_cgo_export.h`: 同上
* `_cgo_flags`:  環境変数から収集した`CGO_LDFLAGS`などを保存する。cgoのstraceやソースを読む限り、保存しただけで誰も使ってない気がひしひしする。
* `_cgo_gotypes.go`: Cの関数を呼び出すためのthunkが入っている。
* `_cgo_main.c`: 後述の`_cgo_.o`をリンクするためのdummyに見える。
* `import_example.cgo1.go`:   
  元の`import_example.go`からGo部分を抜き出したもの。Cの型や関数は`_cgo_gotypes.go`で定義されたものに置き換えられている。
* `import_example.cgo2.c`:
  `_cgo_gotypes.go`に対応するC側のラッパーがある。
  元の`import_example.go`で`import "C"`の前にコメントとして書いたCコード片もここに来るらしい。

なお、この`cgo`コマンドは内部で更にCプリプロセッサを呼び出して`import "C"`のところのCコード片を処理する。これによってGo側で呼び出した関数やGo側で参照した型が実際にはマクロであるケースや、それらが`#if`節内で宣言されていてもきちんと処理できるわけである。
  
```sh:_cgo_flags
_CGO_CFLAGS=
_CGO_LDFLAGS=-lm
```
 
```go:_cgo_gotypes.go
//go:cgo_ldflag "-lm"
// Created by cgo - DO NOT EDIT

package main

import "unsafe"

import _ "runtime/cgo"

import "syscall"

var _ syscall.Errno
func _Cgo_ptr(ptr unsafe.Pointer) unsafe.Pointer { return ptr }

//go:linkname _Cgo_always_false runtime.cgoAlwaysFalse
var _Cgo_always_false bool
//go:linkname _Cgo_use runtime.cgoUse
func _Cgo_use(interface{})
type _Ctype_double float64

type _Ctype_void [0]byte

//go:linkname _cgo_runtime_cgocall runtime.cgocall
func _cgo_runtime_cgocall(unsafe.Pointer, uintptr) int32

/* 中略 */

//go:cgo_import_static _cgo_5df64109ab52_Cfunc_sqrt
//go:linkname __cgofn__cgo_5df64109ab52_Cfunc_sqrt _cgo_5df64109ab52_Cfunc_sqrt
var __cgofn__cgo_5df64109ab52_Cfunc_sqrt byte
var _cgo_5df64109ab52_Cfunc_sqrt = unsafe.Pointer(&__cgofn__cgo_5df64109ab52_Cfunc_sqrt)

func _Cfunc_sqrt(p0 _Ctype_double) (r1 _Ctype_double) {
        _cgo_runtime_cgocall(_cgo_5df64109ab52_Cfunc_sqrt, uintptr(unsafe.Pointer(&p0)))
        if _Cgo_always_false {
                _Cgo_use(p0)
        }
        return
}
``` 

```c:_cgo_main.c
int main() { return 0; }
void crosscall2(void(*fn)(void*, int), void *a, int c) { }
void _cgo_wait_runtime_init_done() { }
char* _cgo_topofstack(void) { return (char*)0; }
void _cgo_allocate(void *a, int c) { }
void _cgo_panic(void *a, int c) { }
void _cgo_reginit(void) { }
```
  
```go:import_example.cgo1.go
// Created by cgo - DO NOT EDIT

//line /home/vagrant/go/src/github.com/yugui/cgo-explained/example1/import_example.go:1
package main
//line /home/vagrant/go/src/github.com/yugui/cgo-explained/example1/import_example.go:4

//line /home/vagrant/go/src/github.com/yugui/cgo-explained/example1/import_example.go:3
import (
//line /home/vagrant/go/src/github.com/yugui/cgo-explained/example1/import_example.go:7
        "fmt"
)
//line /home/vagrant/go/src/github.com/yugui/cgo-explained/example1/import_example.go:11

//line /home/vagrant/go/src/github.com/yugui/cgo-explained/example1/import_example.go:10
func printSqrt(n int) {
        fmt.Println(_Cfunc_sqrt(_Ctype_double(n)))
}
```  
  
```c:import_example.cgo2.c
#line 4 "/home/vagrant/go/src/github.com/yugui/cgo-explained/example1/import_example.go"

#include <math.h>

/* (中略) */

void
_cgo_5df64109ab52_Cfunc_sqrt(void *v)
{
        struct {
                double p0;
                double r;
        } __attribute__((__packed__, __gcc_struct__)) *a = v;
        char *stktop = _cgo_topofstack();
        __typeof__(a->r) r = sqrt(a->p0);
        a = (void*)((char*)a + (_cgo_topofstack() - stktop));
        a->r = r;
}
```

## Cコードのコンパイル/リンク
これは通常通りの手順で、先に生成された`_cgo_export.c`, `_cgo_main.c`, `import_example.cgo2.c`を各々コンパイルした後、リンクして実行ファイル`_cgo_.o`を生成する。`import_example.go`で指定した`LDFLAGS`の`-lm`はここで`gcc`に引き渡されているので、無事に`sqrt(3)`を解決できる。

ここで重要なのはCのリンカにシンボルの解決を丸投げしてローダブルなオブジェクトを作ることである。実行ファイルをビルドしているのはその手段にすぎない。要は一度リンクさせてみてから`ldd`的なことをしたいのだろう。

## インポート宣言の生成
上の`_cgo_.o`を入力として次のように`cgo`コマンドを呼び出している。

```console
$ go tool cgo -objdir $tmpobjdir -dynpackage main \
>  -dynimport $tmpobjdir/_cgo_.o -dynout $tmpobjdir/_cgo_import.go
```

生成されたファイルは次のようである。
どうもGoのリンカに渡すディレクティブのようだ。

```go:_cgo_import.go
package main
//go:cgo_import_dynamic __libc_start_main __libc_start_main#GLIBC_2.2.5 "libc.so.6"
//go:cgo_import_dynamic sqrt sqrt#GLIBC_2.2.5 "libm.so.6"
//go:cgo_import_dynamic _ _ "libm.so.6"
//go:cgo_import_dynamic _ _ "libc.so.6"
```

## Goコードのコンパイル

パッケージ内でcgoを含まない.goファイルと、最初のステップで生成されたGoコードと、先ほど作った`_cgo_import.go`をまとめてコンパイルする。

```console
$ go tool compile -o example1.a \
>  -p github.com/yugui/cgo-explained/example1 -pack \
>  _cgo_gotypes.go import_example.cgo1.go _cgo_import.go main.go
```

この時点では`example1.a`の中身は下記の通り

```console
$ ar x example1.a
__.PKGDEF
_go_.o
```

## Cコードの再リンク  
先ほどコンパイルしたCのオブジェクトファイルをリンクし直して別のファイル`_all.o`を作る。

先ほどの`_cgo_.o`との違いは`_cgo_main.o`や依存ライブラリは一切リンクしないことだ。`-Wl,-r`を指定しているので、ほぼ`_cgo_export.o`と`import_example.cgo2.o`を１つのファイルにまとめているだけである。

```console
$ gcc -nostdlib -o _all.o _cgo_export.o import_example.cgo2.o -Wl,-r
```

## Cオブジェクトをアーカイブに追加
`_all.o`を`example.a`に追加する。

他のステップと違って`go build`はサブプロセスを呼び出してはいないようだが、概ね次のようなコマンドに相当するだろう。

```console
$ go tool pack r example1.a $tmpobjdir/_all.o
```

## アーカイブ内のオブジェクトをリンク
さて、最後にアーカイブ内のオブジェクトや依存パッケージのオブジェクトをリンクする。

```console
$ go tool link -o example1 example1.a
```

このGoリンカはcgoを使っていない場合は自分で最後までバイナリをリンクする。
しかし、今回はC由来の`_all.o`をリンクするために一度ELFに落としてからgccに丸投げする。　

結果として次のようなサブプロセスが走る。

```console
$ gcc -m64 -gdwarf-2 -o example1 -rdynamic \
>  /tmp/go-link-978656076/go.o /tmp/go-link-978656076/000000.o \
>  /tmp/go-link-978656076/000001.o -lm -g -O2 -lpthread
```

# まとめ
先のデータフローをもう一度見てみよう。

![flow.png](https://qiita-image-store.s3.amazonaws.com/0/1806/de5a7a2b-5f23-cfd9-acf3-e42ac52ea810.png)

go buildはうまくCコンパイラツールチェインを利用していることが分かる。

まずグルーコードを生成する。それからCの世界はCコンパイラ/リンカに任せてすべての依存関係を解決させ、`_cgo_.o`を作る。次に、`_cgo_.o`を解析して`_cgo_import.go`のためのデータを抽出する。
さらに、その後もGoとCは分離したまま、それぞれ1つずつのオブジェクトファイルにまとめる。そして最後に実行ファイルを作る段になって両者をリンクする。ここでも`libm`みたいなCの世界とやりとりする必要があるのでライブラリのロードパスやらはすべてCのリンカに丸投げである。

# 次回予告
以上でめでたく実行ファイル`example1`を得られた。[次回](http://qiita.com/yugui/items/cc490d080e0297251090)はGoの関数をCのソースコードが利用する場合について見てみたい。