ふわっとテック日記

テック系のことをメインに書いていきます。go言語/typescript/javascriptが好きです。たまに趣味(筋トレ)の話や日常系の記事も書きたいな〜と思っています。

Go言語のコンパイル/アセンブリ周りについて

Go言語はコンパイル言語なのでソースコードコンパイルしてバイナリを実行するという形になるのですが、 コンパイル → 実行ファイルまでの流れをよく理解できていなかったのでサラッと学んでみました。

こちらの記事を要所要所で翻訳しつつ、噛み砕いたものを記事にしています。

go.dev

間違い等あればご指摘いただければ幸いですm( )m

コンパイル

ソースをコンパイルしてgoアセンブリを作成し、それをアセンブラが解釈してオブジェクトファイルを作成します。

最終的にはオブジェクトファイルやアーカイブライブラリをリンカーがまとめてリンクし、実行可能バイナリファイルを作成するという手順です。

アセンブリを覗いてみる

以下のようなgoファイルを用意します

package calculate

func add(m int, n int) int {
        return m + n
}

func sum(m int, n int) int {
        return m - n
}

以下サンプルでコンパイルしてオブジェクトファイルを作ります。

ソースコードアセンブリ → オブジェクトファイルという作成の流れを踏むのですが、 -Sオプションによって標準出力にアセンブリが出力されます。

go tool compile -S add.go
↓
"".add STEXT size=16 args=0x10 locals=0x0 funcid=0x0 align=0x0 leaf
    0x0000 00000 (add.go:3) TEXT    "".add(SB), LEAF|NOFRAME|ABIInternal, $0-16
    0x0000 00000 (add.go:3) FUNCDATA    ZR, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x0000 00000 (add.go:3) FUNCDATA    $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x0000 00000 (add.go:3) FUNCDATA    $5, "".add.arginfo1(SB)
    0x0000 00000 (add.go:3) FUNCDATA    $6, "".add.argliveinfo(SB)
    0x0000 00000 (add.go:3) PCDATA  $3, $1
    0x0000 00000 (add.go:4) ADD R1, R0, R0
    0x0004 00004 (add.go:4) RET (R30)
    0x0000 00 00 01 8b c0 03 5f d6 00 00 00 00 00 00 00 00  ......_.........
"".sum STEXT size=16 args=0x10 locals=0x0 funcid=0x0 align=0x0 leaf
    0x0000 00000 (add.go:7) TEXT    "".sum(SB), LEAF|NOFRAME|ABIInternal, $0-16
    0x0000 00000 (add.go:7) FUNCDATA    ZR, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x0000 00000 (add.go:7) FUNCDATA    $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x0000 00000 (add.go:7) FUNCDATA    $5, "".sum.arginfo1(SB)
    0x0000 00000 (add.go:7) FUNCDATA    $6, "".sum.argliveinfo(SB)
    0x0000 00000 (add.go:7) PCDATA  $3, $1
    0x0000 00000 (add.go:8) SUB R1, R0, R0
    0x0004 00004 (add.go:8) RET (R30)
    0x0000 00 00 01 cb c0 03 5f d6 00 00 00 00 00 00 00 00  ......_.........
go.cuinfo.packagename. SDWARFCUINFO dupok size=0
    0x0000 63 61 6c 63 75 6c 61 74 65                       calculate
gclocals·33cdeccccebe80329f1fdbee7f5874cb SRODATA dupok size=8
    0x0000 01 00 00 00 00 00 00 00                          ........
"".add.arginfo1 SRODATA static dupok size=5
    0x0000 00 08 08 08 ff                                   .....
"".add.argliveinfo SRODATA static dupok size=2
    0x0000 00 00                                            ..
"".sum.arginfo1 SRODATA static dupok size=5
    0x0000 00 08 08 08 ff                                   .....
"".sum.argliveinfo SRODATA static dupok size=2
    0x0000 00 00           

以下のようにgo buildコマンドに-gcflags -Sオプションをつけても、同様にアセンブリが出力されます。

go build -gcflags -S add.go

goアセンブリには色々なディレクティブが存在するわけですが、以下の2つはガベージコレクタが使用するもので、コンパイラによってアセンブリに挿入されます。

  • FUNCDATA
  • PBDATA

またアーキテクチャによってアセンブリのシンボルは異なるのですが、以下の仮想レジスタを指すシンボルは全アーキテクチャで統一されています。

  • FP: Frame pointer: arguments and locals.
  • PC: Program counter: jumps and branches.
  • SB: Static base pointer: global symbols.
  • SP: Stack pointer: the highest address within the local stack frame.

goアセンブリは基本的に疑似命令で構成されています。

命令にはアセンブル後の機械語と1対1で対応しているものもあれば、直接対応していないものもあります。

関連コマンドetc

go tool compile

goパッケージをコンパイルしてオブジェクトファイルを生成します。 -Sオプションで、生成過程のアセンブリを標準出力に出すことができます。

compile command - cmd/compile - Go Packages

オブジェクトファイルやアーカイブライブラリをリンカーに渡し、実行ファイルを生成(リンク)します。

link command - cmd/link - Go Packages

go tool nm

オブジェクトファイル、アーカイブライブラリ、実行ファイルで定義、使用されているシンボルのリストを返します。

アドレス / 型 / シンボル名という書式になっています。

未定義シンボル(U)のアドレスは省略されます。

nm command - cmd/nm - Go Packages

go tool objdump

実行ファイルを逆アセンブルします。

出力されるのはリンク後の実行ファイルのアセンブリのため、オブジェクトファイルをgo tool compile -Sした時とは別の結果となります。