Go build したバイナリには何がはいっているのか

Go がバイナリのみで動く理由


Posted on Mon, Jan 27, 2020
Tags golang, binary

Go build したバイナリの中身を知りたい

Go は go build すると実行可能なシングルバイナリが生成される。これは、各OS別にビルドしていて、サイズは小さくとも数MBになる(Go 1.13)。

What is included in a Golang binary? - Quora にてすでに簡単な答えがある。

Go のバイナリには
1. Go の runtime
2. goroutine scheduler
3. kernel とのインターフェース
4. reflection で利用する runtime の型情報
5. ファイルや行などスタックトレースに使われる情報

では、実際にその割合は見れるのだろうか。

go tool nm でバイナリを解析する

nm - The Go Programming Language という tool を使う(name の略だろうか)。

Nm lists the symbols defined or used by an object file, archive, or executable.

T text (code) segment symbol
t static text segment symbol
R read-only data segment symbol
r static read-only data segment symbol
D data segment symbol
d static data segment symbol
B bss segment symbol
b static bss segment symbol
C constant address
U referenced but undefined symbol

hello world のみのバイナリの内訳を見てみた。

package main

import (
    "fmt"
)

func main() {
    fmt.Println("Hello Go!")
}
$ ls
2.0M  1 20 00:13 simple*

$ go tool nm -size -sort size simple | head -n 100
           16781312 U _mach_absolute_time
           16781312 U _usleep
           16781312 U _sysctl
           16781312 U _stat64
           16781312 U _sigaltstack
           16781312 U _sigaction
           16781312 U _setitimer
           16781312 U _readdir_r$INODE64
           16781312 U _read
           16781312 U _raise
           16781312 U _pthread_sigmask
           16781312 U _pthread_mutex_unlock
           16781312 U _pthread_mutex_lock
           ...
           16781312 U _mach_timebase_info
           16781312 U _exit
           16781312 U _pthread_attr_init

 10ee080     481753 R runtime.pclntab
 1099560     223232 R runtime.types
 1099560     223232 R type.*
 1099560     223232 R runtime.rodata
 10d7123      68357 R go.func.*
 1183840      65744 B runtime.trace
 10cfd60      29635 R go.string.*
 1169380      25920 D runtime.firstmoduledata
 1066600      20080 T unicode.init
 117f980      16064 B runtime.semtable
 1171dc0      14720 D fmt.ppFree
 1096890      10224 T fmt.(*pp).printValue
 117d280       9984 B runtime.mheap_
 1083f60       8336 T time.Time.AppendFormat
 117b280       8192 B runtime.timers
 1179300       8064 B runtime.cpuprof
 1047940       6720 T runtime.gentraceback
 116f8c0       6712 D strconv.powersOfTen
 11947a0       5992 B runtime.memstats
 107deb0       5264 T internal/fmtsort.compare
 1087070       4640 T time.LoadLocationFromTZData
 10823d0       4464 T time.nextStdChunk
 1176fa0       4112 D runtime.itabTableInit
 104ca30       3936 T runtime.typesEqual
 10e7c28       3835 R runtime.gcbits.*
 103ee30       3344 T runtime.newstack
 10ed300       3328 R runtime.typelink
 1088440       3312 T time.loadTzinfoFromZip
 1070fd0       3088 T reflect.FuncOf
 105b6a0       3056 T internal/reflectlite.haveIdenticalUnderlyingType
 106ffb0       3056 T reflect.haveIdenticalUnderlyingType
 10ec5e0       3050 R runtime.findfunctab
 102fba0       3024 T runtime.findrunnable
 1072450       2928 T reflect.funcLayout
 1017b80       2896 T runtime.gcMarkTermination
 10620c0       2880 T strconv.appendEscapedRune
 108db30       2848 T os.Getwd

pclntab とはなにか

src/debug/gosym/pclntab.go - The Go Programming Language

このデータは go のプログラムがクラッシュしたときに stack trace を生成するためのもの。

Program Counter Line Table の略語のようだ。関数がふえるとこのシンボルは増加するため、大規模な Go プログラムは pclntab が大きくなる。

Go1.2 からもともと圧縮していた文字列を展開した状態でバイナリに同梱されるというのだ。

なぜ変えたのかというと、Go 1.1 ではバイナリを起動するときに、圧縮した文字列を展開するのに時間がかかっていた。 そこで、圧縮を止めることで、バイナリが即座に起動できるようにしたということだそうだ。

参考