Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 15603] ファイルの概要

このコミットは、Go言語のランタイムにおけるAMD64アーキテクチャの起動規約(startup convention)の変更に関するものです。具体的には、プログラムのエントリポイントと引数(argcargv)の渡し方を標準C言語のmain関数のセマンティクスに合わせるように修正しています。これにより、Goプログラムを標準Cライブラリとリンクする際の互換性が向上します。

コミット

commit 36b414f6397cadffab1a945eed645a3213f39d8a
Author: Russ Cox <rsc@golang.org>
Date:   Wed Mar 6 15:03:04 2013 -0500

    runtime: change amd64 startup convention
    
    Now the default startup is that the program begins at _rt0_amd64_$GOOS,
    which sets DI = argc, SI = argv and jumps to _rt0_amd64.
    
    This makes the _rt0_amd64 entry match the expected semantics for
    the standard C "main" function, which we can now provide for use when
    linking against a standard C library.
    
    R=golang-dev, devon.odell, minux.ma
    CC=golang-dev
    https://golang.org/cl/7525043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/36b414f6397cadffab1a945eed645a3213f39d8a

元コミット内容

このコミットの元の内容は以下の通りです。

runtime: change amd64 startup convention

Now the default startup is that the program begins at _rt0_amd64_$GOOS,
which sets DI = argc, SI = argv and jumps to _rt0_amd64.

This makes the _rt0_amd64 entry match the expected semantics for
the standard C "main" function, which we can now provide for use when
linking against a standard C library.

変更の背景

この変更の背景には、Goプログラムが起動する際のエントリポイントと、コマンドライン引数(argcargv)の処理方法を、より標準的なC言語のmain関数の規約に合わせるという目的があります。

Goプログラムは、コンパイルされると実行可能なバイナリになりますが、その内部ではGoランタイムが初期化処理を行います。この初期化処理の開始点となるのが、各OS・アーキテクチャ固有の_rt0_amd64_$GOOSのようなアセンブリコードです。

従来のGoランタイムの起動規約では、_rt0_amd64関数に引数を渡す方法が、標準Cライブラリのmain関数が期待する引数渡し方と異なっていました。具体的には、C言語のmain関数は通常、第一引数にargc(引数の数)、第二引数にargv(引数の文字列配列)を受け取ります。これらは通常、特定のレジスタ(AMD64ではDIとSI)に配置されるか、スタックを通じて渡されます。

GoプログラムがC言語で書かれたライブラリ(Cgoを通じて利用される場合など)と連携する場合、Goランタイムの起動規約がCの規約と一致しないと、引数の解釈に問題が生じたり、リンケージが複雑になったりする可能性があります。このコミットは、この不一致を解消し、GoプログラムがCライブラリとよりシームレスに連携できるようにすることを目的としています。

前提知識の解説

このコミットを理解するためには、以下の前提知識が必要です。

  1. Goランタイム (Go Runtime): Go言語のプログラムは、単独で実行可能なバイナリとしてコンパイルされますが、その内部にはGoランタイムと呼ばれる非常に重要な部分が含まれています。Goランタイムは、ガベージコレクション、スケジューラ(ゴルーチンの管理)、メモリ管理、システムコールインターフェースなど、Goプログラムの実行に必要な低レベルの機能を提供します。プログラムの起動時、最初に実行されるのはこのランタイムの初期化コードです。

  2. アセンブリ言語 (Assembly Language): CPUが直接実行できる機械語を人間が読める形式で記述したものです。Goランタイムの起動コードや、パフォーマンスが重要な一部のコードは、アセンブリ言語で書かれています。AMD64アーキテクチャでは、レジスタ(AX, BX, CX, DX, DI, SI, SP, BPなど)やメモリ操作、ジャンプ命令などが使われます。

  3. AMD64アーキテクチャ (x86-64 Architecture): IntelやAMDの64ビットプロセッサのアーキテクチャです。このアーキテクチャでは、関数呼び出し規約(Calling Convention)が重要になります。これは、関数がどのように引数を受け取り、結果を返し、レジスタを保存するかを定めたものです。

  4. System V AMD64 ABI (Application Binary Interface): Linux、macOS、FreeBSDなどのUnix系システムで広く採用されているAMD64アーキテクチャの標準的なABIです。このABIでは、関数の最初の6つの整数またはポインタ引数は、特定のレジスタ(RDI, RSI, RDX, RCX, R8, R9)に渡されると定められています。特に、main関数のargcargvは、それぞれRDIとRSIレジスタに渡されることが一般的です。

  5. _rt0_amd64_rt0_amd64_$GOOS: Goランタイムの起動コードにおけるエントリポイントです。

    • _rt0_amd64_$GOOS: 各OS(例: _rt0_amd64_linux, _rt0_amd64_darwin)固有の最初のエントリポイント。OSから制御が渡された後、この関数が実行されます。
    • _rt0_amd64: OS固有の初期化の後、共通のランタイム初期化処理を行うためのエントリポイント。
  6. argcargv: C言語のmain関数でよく使われる引数です。

    • argc (argument count): コマンドライン引数の数。
    • argv (argument vector): コマンドライン引数の文字列(ポインタ)の配列。argv[0]は通常、実行ファイル自身のパスです。
  7. レジスタ (Registers): CPU内部にある高速な記憶領域です。

    • DI (Destination Index) / RDI: 汎用レジスタ。System V AMD64 ABIでは、第一引数を渡すためによく使われます。
    • SI (Source Index) / RSI: 汎用レジスタ。System V AMD64 ABIでは、第二引数を渡すためによく使われます。
    • SP (Stack Pointer) / RSP: スタックの現在のトップを指すレジスタ。
    • AX (Accumulator) / RAX: 汎用レジスタ。関数の戻り値を格納するためによく使われます。

技術的詳細

このコミットの核心は、Goランタイムの起動シーケンスにおけるargcargvの渡し方を変更し、標準C言語のmain関数の規約に合わせる点にあります。

変更前は、OS固有の_rt0_amd64_$GOOSエントリポイントから_rt0_amd64を呼び出す際に、argcargvの情報をスタックポインタ(SP)から直接取得していました。具体的には、_rt0_amd64関数内で0(DI)DIはスタックポインタを指していた)からargcを、8(DI)からargvを取得していました。これは、OSがプログラム起動時にスタックにargcargvを特定のオフセットで配置するという前提に基づいています。

変更後は、_rt0_amd64_$GOOS関数内で、OSがスタックに配置したargcargvの値を、それぞれDIレジスタとSIレジスタに明示的にロードするようにしました。そして、_rt0_amd64関数は、これらのレジスタから直接argcargvを受け取るように修正されました。

具体的には、以下の変更が行われました。

  1. _rt0_amd64関数の変更:

    • 変更前: MOVQ 0(DI), AX (argcをスタックから取得), LEAQ 8(DI), BX (argvをスタックから取得)
    • 変更後: MOVQ DI, AX (argcをDIレジスタから取得), MOVQ SI, BX (argvをSIレジスタから取得) これは、_rt0_amd64がCのmain関数のように、DIargcSIargvが渡されることを期待するように変更されたことを意味します。
  2. 各OS固有の_rt0_amd64_$GOOS関数の変更:

    • 各OS(Darwin, FreeBSD, Linux, NetBSD, OpenBSD, Plan9, Windows)の_rt0_amd64_$GOOSアセンブリファイルにおいて、プログラム起動時にOSがスタックに配置するargcargvの値を、それぞれDIレジスタとSIレジスタに移動させる処理が追加されました。
    • 例えば、Linux/Darwinの場合、LEAQ 8(SP), SI (スタックポインタ+8の場所にargvがあるため、それをSIにロード), MOVQ 0(SP), DI (スタックポインタの場所にargcがあるため、それをDIにロード) といった命令が追加されています。
    • その後、_rt0_amd64ではなく、新たに導入されたmain(SB)というシンボルにジャンプするように変更されています。このmain(SB)が、最終的に_rt0_amd64を呼び出します。これにより、Goランタイムの初期化パスがよりCの起動規約に近づきました。

この変更により、Goプログラムの起動時の引数渡しがSystem V AMD64 ABIの規約に準拠するようになり、C言語のmain関数との互換性が向上しました。これは、GoプログラムがCgoを通じてCライブラリと連携する際に、より自然なリンケージと引数処理を可能にするための重要なステップです。

コアとなるコードの変更箇所

このコミットでは、主に以下のファイルが変更されています。

  • src/pkg/runtime/asm_amd64.s
  • src/pkg/runtime/rt0_darwin_amd64.s
  • src/pkg/runtime/rt0_freebsd_amd64.s
  • src/pkg/runtime/rt0_linux_amd64.s
  • src/pkg/runtime/rt0_netbsd_amd64.s
  • src/pkg/runtime/rt0_openbsd_amd64.s
  • src/pkg/runtime/rt0_plan9_amd64.s
  • src/pkg/runtime/rt0_windows_amd64.s

以下に、主要な変更箇所の差分を示します。

src/pkg/runtime/asm_amd64.s の変更:

--- a/src/pkg/runtime/asm_amd64.s
+++ b/src/pkg/runtime/asm_amd64.s
@@ -6,8 +6,8 @@
 
 TEXT _rt0_amd64(SB),7,$-8
 	// copy arguments forward on an even stack
-	MOVQ	0(DI), AX		// argc
-	LEAQ	8(DI), BX		// argv
+	MOVQ	DI, AX		// argc
+	MOVQ	SI, BX		// argv
 	SUBQ	$(4*8+7), SP		// 2args 2auto
 	ANDQ	$~15, SP
 	MOVQ	AX, 16(SP)

src/pkg/runtime/rt0_darwin_amd64.s の変更例 (他のOSも同様のパターン):

--- a/src/pkg/runtime/rt0_darwin_amd64.s
+++ b/src/pkg/runtime/rt0_darwin_amd64.s
@@ -2,9 +2,12 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.\n
 
-// Darwin and Linux use the same linkage to main
-\n
 TEXT _rt0_amd64_darwin(SB),7,$-8
+\tLEAQ\t8(SP), SI // argv
+\tMOVQ\t0(SP), DI // argc
+\tMOVQ\t$main(SB), AX
+\tJMP\tAX
+\n
+TEXT main(SB),7,$-8
 	MOVQ	$_rt0_amd64(SB), AX
-\tMOVQ	SP, DI
 	JMP	AX

コアとなるコードの解説

src/pkg/runtime/asm_amd64.s の変更

このファイルは、GoランタイムのAMD64アーキテクチャにおける共通の初期化コードを含んでいます。_rt0_amd64関数は、OS固有の初期化コードから呼び出される共通のエントリポイントです。

  • 変更前:

    • MOVQ 0(DI), AX: DIレジスタが指すアドレス(スタックポインタ)から0バイトオフセットにある値をAXレジスタに移動させます。これはargc(引数の数)をスタックから取得する意図です。
    • LEAQ 8(DI), BX: DIレジスタが指すアドレスから8バイトオフセットにあるアドレスをBXレジスタにロードします。これはargv(引数配列の先頭アドレス)をスタックから取得する意図です。 このコードは、DIがスタックポインタを指しており、スタック上にargcargvが特定のオフセットで配置されていることを前提としていました。
  • 変更後:

    • MOVQ DI, AX: DIレジスタの値を直接AXレジスタに移動させます。これは、DIレジスタにすでにargcが設定されていることを期待しています。
    • MOVQ SI, BX: SIレジスタの値を直接BXレジスタに移動させます。これは、SIレジスタにすでにargvが設定されていることを期待しています。 この変更により、_rt0_amd64関数は、System V AMD64 ABIの規約に従って、DIargcSIargvが渡されることを前提とするようになりました。

各OS固有の src/pkg/runtime/rt0_GOOS_amd64.s ファイルの変更

これらのファイルは、各OS(Darwin, Linux, FreeBSDなど)がGoプログラムを起動する際のエントリポイントとなるアセンブリコードを含んでいます。OSはプログラム起動時に、コマンドライン引数などの情報をスタックに配置します。

  • 変更前:

    • TEXT _rt0_amd64_darwin(SB),7,$-8 のようなエントリポイントから、直接 _rt0_amd64(SB) を呼び出し、その際にスタックポインタ SPDI レジスタに渡していました。_rt0_amd64 はこの DI を使ってスタックから引数を読み取っていました。
  • 変更後:

    • TEXT _rt0_amd64_darwin(SB),7,$-8 のようなエントリポイントで、まずOSがスタックに配置したargcargvの値を、System V AMD64 ABIの規約に従ってDISIレジスタにロードします。
      • LEAQ 8(SP), SI // argv: スタックポインタSPから8バイトオフセットにあるアドレス(argvの先頭)をSIレジスタにロードします。
      • MOVQ 0(SP), DI // argc: スタックポインタSPが指すアドレス(argcの値)をDIレジスタにロードします。
    • 次に、MOVQ $main(SB), AXJMP AX を使って、新たに導入されたmain(SB)というシンボルにジャンプします。
    • TEXT main(SB),7,$-8 という新しいエントリポイントが定義され、このmain関数が最終的に_rt0_amd64(SB)を呼び出します。 この変更により、OSから制御が渡された直後にargcargvが適切なレジスタに配置され、その後のGoランタイムの初期化処理が標準Cの規約に沿って行われるようになりました。これにより、GoプログラムがCライブラリと連携する際の互換性が向上します。

関連リンク

  • Go言語の公式リポジトリ: https://github.com/golang/go
  • このコミットのChange-ID: https://golang.org/cl/7525043 (GoのコードレビューシステムGerritのリンク)

参考にした情報源リンク