GOプログラムの起動手順について

## 問題

GO のプログラムは`main`という package の`main()`方法で始めるのがよくご存知でしたが、その前に何が起こったのかな？これをちょっと調べて行きます。

## 実行環境

* Ubuntu 18.04
* Go 1.14

## もっともシンプルな GO プログラム

まずはこの何もしないプログラムを用意します

```go:main.go
package main

func main() {}
```

```sh
$ go build main.go
```

## `main.main`を探す

### ELFを調べてみる

まずはコンパイルされたバイナリを`readelf`でみてみま

```sh
$ readelf -h main
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x4552c0
  Start of program headers:          64 (bytes into file)
  Start of section headers:          456 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         7
  Size of section headers:           64 (bytes)
  Number of section headers:         25
  Section header string table index: 3
```

その中の`Entry point address`は実行の入口です、そのアドレスは`0x4552c0`。じゃ次はこのアドレスを見てみよう。

```sh
$ objdump -d main | grep 4552c0
00000000004552c0 <_rt0_amd64_linux>:
  4552c0:	e9 4b c4 ff ff       	jmpq   451710 <_rt0_amd64>
```

`0x4552c0`のとこは`_rt0_amd64_linux`という関数ですね、そしてすぐ`_rt0_amd64`に移行するようです。

### `_rt0_amd64`はどこ？

ソースコードに`_rt0_amd64`を検索したら、[`asm_amd64.s`](https://github.com/golang/go/blob/dev.boringcrypto.go1.14/src/runtime/asm_amd64.s#L14)に見つけました。

```asm:src/runtime/asm_amd64.s
TEXT _rt0_amd64(SB),NOSPLIT,$-8
    MOVQ    0(SP), DI   // argc
    LEAQ    8(SP), SI   // argv
    JMP runtime·rt0_go(SB)
```

そしてすぐ[`runtime·rt0_go`](https://github.com/golang/go/blob/dev.boringcrypto.go1.14/src/runtime/asm_amd64.s#L87)にジャンプしました。もうちょっと追ってみれば、これを見つけた

```asm:src/runtime/asm_amd64.s
...
    MOVL    16(SP), AX      // copy argc
    MOVL    AX, 0(SP)
    MOVQ    24(SP), AX      // copy argv
    MOVQ    AX, 8(SP)
    CALL    runtime·args(SB)
    CALL    runtime·osinit(SB)
    CALL    runtime·schedinit(SB)

    // create a new goroutine to start program
    MOVQ    $runtime·mainPC(SB), AX     // entry
    PUSHQ   AX
    PUSHQ   $0          // arg size
    CALL    runtime·newproc(SB)
    POPQ    AX
    POPQ    AX

    // start this M
    CALL    runtime·mstart(SB)
...
DATA	runtime·mainPC+0(SB)/8,$runtime·main(SB)
GLOBL	runtime·mainPC(SB),RODATA,$8
...
```

`runtime·args`、`runtime·osinit`と`runtime·schedinit`を次々`CALL`してから、`runtime·mainPC`を引数として`runtime·newproc`を`CALL`しました。この`entry`で注釈した`runtime·mainPC`実は`runtime·main`として定義したものです。

### `runtime.main`と`runtime.newproc`

またソースに`newproc`を検索したら、[`proc.go`](https://github.com/golang/go/blob/master/src/runtime/proc.go#L3490)にたどり着いた

```go:src/runtime/proc.go
...
// Create a new g running fn with siz bytes of arguments.
// Put it on the queue of g's waiting to run.
// The compiler turns a go statement into a call to this.
//
// The stack layout of this call is unusual: it assumes that the
// arguments to pass to fn are on the stack sequentially immediately
// after &fn. Hence, they are logically part of newproc's argument
// frame, even though they don't appear in its signature (and can't
// because their types differ between call sites).
//
// This must be nosplit because this stack layout means there are
// untyped arguments in newproc's argument frame. Stack copies won't
// be able to adjust them and stack splits won't be able to copy them.
//
//go:nosplit
func newproc(siz int32, fn *funcval) {
...
```

コメントによると、`runtime.newproc`は一つの goroutine を起動して`fn`を実行するということですね。そしてここの`fn`は先の`runtime.main`のようです。次は[`runtime.main`](https://github.com/golang/go/blob/master/src/runtime/proc.go#L114)を見てみましょ。

```go:src/runtime/proc.go
...
// The main goroutine.
func main() {
    g := getg()
...
    fn := main_main // make an indirect call, as the linker doesn't know the address of the main package when laying down the runtime
    fn()
...
```

`runtime.main`はいろんなことをやっていて、`main_main`をコールした。この[`main_main`](https://github.com/golang/go/blob/master/src/runtime/proc.go#L102)はもしかして。。。

```go:src/runtime/proc.go
...
//go:linkname main_main main.main
func main_main()
...
```

そうか、ここは`go:linkname`という Compiler Directive に通して`main_main`を`main.main`に変換しました。

## まとめ

* GO プログラムのほんとのエントリーポイントは`_rt0_amd64_linux`です（Linuxの場合）。
* いろんな初期化は`xxxinit`でやっています。特に`runtime.schedinit`。
* `main.main`の実行も goroutine の一つです

## 参考資料

* [How programs get run: ELF binaries [LWN.net]](https://lwn.net/Articles/631631/)
* [Golang Internals, Part 5: the Runtime Bootstrap Process | Altoros](https://www.altoros.com/blog/golang-internals-part-5-the-runtime-bootstrap-process/)
* [compile - The Go Programming Language](https://golang.org/cmd/compile/)
* [A Quick Guide to Go's Assembler - The Go Programming Language](https://golang.org/doc/asm)