LoginSignup
2
0

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-06-04

問題

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

実行環境

  • Ubuntu 18.04
  • Go 1.14

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

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

main.go
package main

func main() {}
$ go build main.go

main.mainを探す

ELFを調べてみる

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

$ 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。じゃ次はこのアドレスを見てみよう。

$ 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に見つけました。

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にジャンプしました。もうちょっと追ってみれば、これを見つけた

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·argsruntime·osinitruntime·schedinitを次々CALLしてから、runtime·mainPCを引数としてruntime·newprocCALLしました。このentryで注釈したruntime·mainPC実はruntime·mainとして定義したものです。

runtime.mainruntime.newproc

またソースにnewprocを検索したら、proc.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を見てみましょ。

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はもしかして。。。

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

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

まとめ

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

参考資料

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0