[インデックス 15603] ファイルの概要
このコミットは、Go言語のランタイムにおけるAMD64アーキテクチャの起動規約(startup convention)の変更に関するものです。具体的には、プログラムのエントリポイントと引数(argc
とargv
)の渡し方を標準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プログラムが起動する際のエントリポイントと、コマンドライン引数(argc
とargv
)の処理方法を、より標準的な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ライブラリとよりシームレスに連携できるようにすることを目的としています。
前提知識の解説
このコミットを理解するためには、以下の前提知識が必要です。
-
Goランタイム (Go Runtime): Go言語のプログラムは、単独で実行可能なバイナリとしてコンパイルされますが、その内部にはGoランタイムと呼ばれる非常に重要な部分が含まれています。Goランタイムは、ガベージコレクション、スケジューラ(ゴルーチンの管理)、メモリ管理、システムコールインターフェースなど、Goプログラムの実行に必要な低レベルの機能を提供します。プログラムの起動時、最初に実行されるのはこのランタイムの初期化コードです。
-
アセンブリ言語 (Assembly Language): CPUが直接実行できる機械語を人間が読める形式で記述したものです。Goランタイムの起動コードや、パフォーマンスが重要な一部のコードは、アセンブリ言語で書かれています。AMD64アーキテクチャでは、レジスタ(AX, BX, CX, DX, DI, SI, SP, BPなど)やメモリ操作、ジャンプ命令などが使われます。
-
AMD64アーキテクチャ (x86-64 Architecture): IntelやAMDの64ビットプロセッサのアーキテクチャです。このアーキテクチャでは、関数呼び出し規約(Calling Convention)が重要になります。これは、関数がどのように引数を受け取り、結果を返し、レジスタを保存するかを定めたものです。
-
System V AMD64 ABI (Application Binary Interface): Linux、macOS、FreeBSDなどのUnix系システムで広く採用されているAMD64アーキテクチャの標準的なABIです。このABIでは、関数の最初の6つの整数またはポインタ引数は、特定のレジスタ(RDI, RSI, RDX, RCX, R8, R9)に渡されると定められています。特に、
main
関数のargc
とargv
は、それぞれRDIとRSIレジスタに渡されることが一般的です。 -
_rt0_amd64
と_rt0_amd64_$GOOS
: Goランタイムの起動コードにおけるエントリポイントです。_rt0_amd64_$GOOS
: 各OS(例:_rt0_amd64_linux
,_rt0_amd64_darwin
)固有の最初のエントリポイント。OSから制御が渡された後、この関数が実行されます。_rt0_amd64
: OS固有の初期化の後、共通のランタイム初期化処理を行うためのエントリポイント。
-
argc
とargv
: C言語のmain
関数でよく使われる引数です。argc
(argument count): コマンドライン引数の数。argv
(argument vector): コマンドライン引数の文字列(ポインタ)の配列。argv[0]
は通常、実行ファイル自身のパスです。
-
レジスタ (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ランタイムの起動シーケンスにおけるargc
とargv
の渡し方を変更し、標準C言語のmain
関数の規約に合わせる点にあります。
変更前は、OS固有の_rt0_amd64_$GOOS
エントリポイントから_rt0_amd64
を呼び出す際に、argc
とargv
の情報をスタックポインタ(SP
)から直接取得していました。具体的には、_rt0_amd64
関数内で0(DI)
(DI
はスタックポインタを指していた)からargc
を、8(DI)
からargv
を取得していました。これは、OSがプログラム起動時にスタックにargc
とargv
を特定のオフセットで配置するという前提に基づいています。
変更後は、_rt0_amd64_$GOOS
関数内で、OSがスタックに配置したargc
とargv
の値を、それぞれDI
レジスタとSI
レジスタに明示的にロードするようにしました。そして、_rt0_amd64
関数は、これらのレジスタから直接argc
とargv
を受け取るように修正されました。
具体的には、以下の変更が行われました。
-
_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
関数のように、DI
にargc
、SI
にargv
が渡されることを期待するように変更されたことを意味します。
- 変更前:
-
各OS固有の
_rt0_amd64_$GOOS
関数の変更:- 各OS(Darwin, FreeBSD, Linux, NetBSD, OpenBSD, Plan9, Windows)の
_rt0_amd64_$GOOS
アセンブリファイルにおいて、プログラム起動時にOSがスタックに配置するargc
とargv
の値を、それぞれ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の起動規約に近づきました。
- 各OS(Darwin, FreeBSD, Linux, NetBSD, OpenBSD, Plan9, Windows)の
この変更により、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
がスタックポインタを指しており、スタック上にargc
とargv
が特定のオフセットで配置されていることを前提としていました。
-
変更後:
MOVQ DI, AX
:DI
レジスタの値を直接AX
レジスタに移動させます。これは、DI
レジスタにすでにargc
が設定されていることを期待しています。MOVQ SI, BX
:SI
レジスタの値を直接BX
レジスタに移動させます。これは、SI
レジスタにすでにargv
が設定されていることを期待しています。 この変更により、_rt0_amd64
関数は、System V AMD64 ABIの規約に従って、DI
にargc
、SI
にargv
が渡されることを前提とするようになりました。
各OS固有の src/pkg/runtime/rt0_GOOS_amd64.s
ファイルの変更
これらのファイルは、各OS(Darwin, Linux, FreeBSDなど)がGoプログラムを起動する際のエントリポイントとなるアセンブリコードを含んでいます。OSはプログラム起動時に、コマンドライン引数などの情報をスタックに配置します。
-
変更前:
TEXT _rt0_amd64_darwin(SB),7,$-8
のようなエントリポイントから、直接_rt0_amd64(SB)
を呼び出し、その際にスタックポインタSP
をDI
レジスタに渡していました。_rt0_amd64
はこのDI
を使ってスタックから引数を読み取っていました。
-
変更後:
TEXT _rt0_amd64_darwin(SB),7,$-8
のようなエントリポイントで、まずOSがスタックに配置したargc
とargv
の値を、System V AMD64 ABIの規約に従ってDI
とSI
レジスタにロードします。LEAQ 8(SP), SI // argv
: スタックポインタSP
から8バイトオフセットにあるアドレス(argv
の先頭)をSI
レジスタにロードします。MOVQ 0(SP), DI // argc
: スタックポインタSP
が指すアドレス(argc
の値)をDI
レジスタにロードします。
- 次に、
MOVQ $main(SB), AX
とJMP AX
を使って、新たに導入されたmain(SB)
というシンボルにジャンプします。 TEXT main(SB),7,$-8
という新しいエントリポイントが定義され、このmain
関数が最終的に_rt0_amd64(SB)
を呼び出します。 この変更により、OSから制御が渡された直後にargc
とargv
が適切なレジスタに配置され、その後のGoランタイムの初期化処理が標準Cの規約に沿って行われるようになりました。これにより、GoプログラムがCライブラリと連携する際の互換性が向上します。
関連リンク
- Go言語の公式リポジトリ: https://github.com/golang/go
- このコミットのChange-ID:
https://golang.org/cl/7525043
(GoのコードレビューシステムGerritのリンク)
参考にした情報源リンク
- System V Application Binary Interface AMD64 Architecture Processor Supplement: https://refspecs.linuxfoundation.org/elf/x86-64-abi-0.99.pdf (特にセクション 3.2.3 "Parameter Passing")
- Go Assembly Language (Goのアセンブリ言語に関する公式ドキュメント): https://go.dev/doc/asm
- Go Runtime Source Code (Goランタイムのソースコード): https://github.com/golang/go/tree/master/src/runtime
- C言語の
main
関数と引数argc
,argv
に関する一般的な情報源 (例: Wikipedia, C言語のチュートリアルサイトなど)