[インデックス 10532] ファイルの概要
コミット
このコミットは、Go言語のWindows環境におけるシステムコール機能の拡張に関するものです。具体的には、Syscall15
という新しいシステムコールラッパーを実装し、最大15個の引数を持つWindows API関数をGoから呼び出せるようにします。これにより、以前のSyscall12
では対応できなかった、より多くの引数を必要とするWindows APIの利用が可能になります。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6d4c18a4af447dab97c10e4bd6c8ce5fbb3bcb13
元コミット内容
commit 6d4c18a4af447dab97c10e4bd6c8ce5fbb3bcb13
Author: Alex Brainman <alex.brainman@gmail.com>
Date: Tue Nov 29 10:24:19 2011 +1100
syscall: implement Syscall15
Fixes #2251.
R=golang-dev, rsc
CC=golang-dev, jp
https://golang.org/cl/5440050
変更の背景
この変更の背景には、Go言語がWindowsプラットフォーム上でより多くのネイティブAPIを効率的に利用できるようにするという目的があります。Windows APIには、12個を超える引数を取る関数が多数存在します。Goのsyscall
パッケージは、これらのAPIを呼び出すためのインターフェースを提供しますが、このコミット以前は最大12個の引数しか扱えないSyscall12
が最も多くの引数をサポートするラッパーでした。
Fixes #2251
という記述から、このコミットが特定のバグや機能不足を解決するために行われたことがわかります。Issue #2251は、おそらく12個を超える引数を持つWindows APIをGoから呼び出せないという問題、またはその必要性に関するものであったと推測されます。Syscall15
の実装により、この制限が緩和され、GoプログラムがWindowsの低レベル機能をより広範に利用できるようになりました。
前提知識の解説
システムコール (System Call)
システムコールとは、オペレーティングシステム (OS) のカーネルが提供するサービスを、ユーザー空間のプログラムが利用するためのインターフェースです。ファイル操作、メモリ管理、プロセス制御、ネットワーク通信など、OSの根幹に関わる処理は、直接ハードウェアにアクセスするのではなく、システムコールを通じてOSに依頼することで実行されます。
Windows API (Application Programming Interface)
Windows APIは、Microsoft Windowsオペレーティングシステムが提供する関数、データ構造、定数などの集合体です。アプリケーション開発者は、これらのAPIを呼び出すことで、Windowsの機能(GUI、ファイルシステム、ネットワーク、セキュリティなど)を利用できます。Windows APIの関数には、引数の数が多岐にわたるものがあります。
Go言語の syscall
パッケージ
Go言語の標準ライブラリには、syscall
パッケージが含まれています。このパッケージは、OS固有のシステムコールや低レベルのプリミティブへのアクセスを提供します。これにより、GoプログラムはOSのネイティブ機能を直接利用できます。Windows環境では、このパッケージを通じてWindows APIを呼び出すためのラッパー関数(例: Syscall
, Syscall6
, Syscall9
, Syscall12
など)が提供されています。
uintptr
型
Go言語のuintptr
型は、ポインタを保持するのに十分な大きさの符号なし整数型です。これは、ポインタ演算を行うためではなく、C言語のポインタやOSのハンドルなど、Goの型システムでは直接表現できない低レベルの値を扱う際に使用されます。システムコールでは、引数や戻り値としてメモリのアドレスやハンドルが渡されることが多いため、uintptr
が頻繁に用いられます。
アセンブリ言語 (.s
ファイル)
Go言語のランタイムや低レベルのシステムコールラッパーの一部は、パフォーマンスやOSとの直接的なインターフェースのためにアセンブリ言語で記述されています。src/pkg/runtime/windows/amd64/sys.s
のようなファイルは、WindowsのAMD64アーキテクチャ向けのアセンブリコードを含んでおり、Goの関数呼び出し規約とOSのシステムコール規約の間の橋渡しをします。
mksyscall_windows.pl
スクリプト
mksyscall_windows.pl
は、Goのsyscall
パッケージでWindows APIを呼び出すためのGoコードとアセンブリコードを自動生成するためのPerlスクリプトです。このスクリプトは、Windows APIの定義を読み込み、それに対応するSyscall
ラッパー関数(Syscall
, Syscall6
, Syscall9
など)を生成します。これにより、手動で大量のラッパーコードを書く手間を省き、一貫性を保つことができます。
技術的詳細
このコミットの主要な目的は、Go言語のWindows向けsyscall
パッケージが、より多くの引数を持つWindows API関数を呼び出せるようにすることです。これまでのSyscall12
では最大12個の引数しか扱えませんでしたが、Syscall15
の導入により、最大15個の引数を持つAPIに対応できるようになります。
技術的な変更点は以下の通りです。
Syscall15
関数の追加:src/pkg/runtime/windows/syscall.goc
に、15個のuintptr
型引数を受け取るSyscall15
関数が追加されました。この関数は、Goのランタイムが提供する低レベルのアセンブリ関数runtime·asmstdcall
を呼び出し、実際のシステムコールを実行します。maxargs
の拡張:src/pkg/runtime/windows/amd64/sys.s
内のmaxargs
マクロが12
から15
に増やされました。これは、アセンブリレベルでのシステムコール処理が、最大15個の引数を扱えるようにするための変更です。syscall/dll_windows.go
の更新:syscall
パッケージのGoコードにSyscall15
の宣言が追加され、Proc.Call
メソッド内で引数の数に応じてSyscall15
を呼び出すロジックが追加されました。これにより、syscall.NewProc("SomeApi").Call(...)
のような形で、引数が多いAPIも透過的に呼び出せるようになります。mksyscall_windows.pl
の更新: システムコールラッパーを生成するPerlスクリプトmksyscall_windows.pl
が修正され、引数の数が15個以下の場合にSyscall15
を使用するようにロジックが追加されました。これにより、将来的に新しいWindows APIが追加された際にも、自動生成プロセスが適切にSyscall15
を利用できるようになります。
これらの変更により、GoプログラムはWindowsのより多様なAPIを直接呼び出すことが可能になり、Windowsプラットフォーム上でのGoアプリケーションの機能性が向上します。
コアとなるコードの変更箇所
このコミットで変更された主要なファイルとコードスニペットは以下の通りです。
-
src/pkg/runtime/windows/amd64/sys.s
:--- a/src/pkg/runtime/windows/amd64/sys.s +++ b/src/pkg/runtime/windows/amd64/sys.s @@ -4,7 +4,7 @@ #include "amd64/asm.h" -#define maxargs 12 +#define maxargs 15 // void runtime·asmstdcall(void *c); TEXT runtime·asmstdcall(SB),7,$0
maxargs
マクロの値が12から15に変更されています。これは、アセンブリレベルで処理できる引数の最大数を定義しています。 -
src/pkg/runtime/windows/syscall.goc
:--- a/src/pkg/runtime/windows/syscall.goc +++ b/src/pkg/runtime/windows/syscall.goc @@ -117,3 +117,29 @@ func Syscall12(fn uintptr, nargs uintptr, a1 uintptr, a2 uintptr, a3 uintptr, a4 r1 = c.r1; r2 = c.r2; } + +func Syscall15(fn uintptr, nargs uintptr, a1 uintptr, a2 uintptr, a3 uintptr, a4 uintptr, a5 uintptr, a6 uintptr, a7 uintptr, a8 uintptr, a9 uintptr, a10 uintptr, a11 uintptr, a12 uintptr, a13 uintptr, a14 uintptr, a15 uintptr) (r1 uintptr, r2 uintptr, err uintptr) { + WinCall c; + + USED(a2); + USED(a3); + USED(a4); + USED(a5); + USED(a6); + USED(a7); + USED(a8); + USED(a9); + USED(a10); + USED(a11); + USED(a12); + USED(a13); + USED(a14); + USED(a15); + c.fn = (void*)fn; + c.n = nargs; + c.args = &a1; + runtime·cgocall(runtime·asmstdcall, &c); + err = c.err; + r1 = c.r1; + r2 = c.r2; +}
Syscall15
関数が追加されています。この関数は、15個のuintptr
引数を受け取り、runtime·asmstdcall
を呼び出して実際のシステムコールを実行します。USED
マクロは、引数が使用されていることをコンパイラに伝えるためのものです。 -
src/pkg/syscall/dll_windows.go
:--- a/src/pkg/syscall/dll_windows.go +++ b/src/pkg/syscall/dll_windows.go @@ -37,6 +37,7 @@ func Syscall(trap, nargs, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno)\n func Syscall6(trap, nargs, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno)\n func Syscall9(trap, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2 uintptr, err Errno)\n func Syscall12(trap, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12 uintptr) (r1, r2 uintptr, err Errno)\n +func Syscall15(trap, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2 uintptr, err Errno)\n func loadlibrary(filename *uint16) (handle, err Errno)\n func getprocaddress(handle uintptr, procname *uint8) (proc uintptr, err Errno)\n \n@@ -147,6 +148,12 @@ func (p *Proc) Call(a ...uintptr) (r1, r2 uintptr, err error) {\n return Syscall12(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], 0)\n case 12:\n return Syscall12(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11])\n +\tcase 13:\n +\t\treturn Syscall15(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], 0, 0)\n +\tcase 14:\n +\t\treturn Syscall15(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], 0)\n +\tcase 15:\n +\t\treturn Syscall15(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14])\n default:\n panic("Call " + p.Name + " with too many arguments " + itoa(len(a)) + ".")\n }\n ``` `Syscall15`の関数シグネチャが追加され、`Proc.Call`メソッド内で引数の数に応じて`Syscall15`を呼び出す新しい`case`文が追加されています。これにより、`Proc.Call`が可変長引数を受け取り、適切な`Syscall`ラッパーにディスパッチできるようになります。
-
src/pkg/syscall/mksyscall_windows.pl
:--- a/src/pkg/syscall/mksyscall_windows.pl +++ b/src/pkg/syscall/mksyscall_windows.pl @@ -190,6 +190,11 @@ while(<>) {\n while(@args < 12) {\n push @args, "0";\n }\n +\t} elsif(@args <= 15) {\n +\t\t$asm = "${syscalldot}Syscall15";\n +\t\twhile(@args < 15) {\n +\t\t\tpush @args, "0";\n +\t\t}\n } else {\n print STDERR "$ARGV:$.: too many arguments to system call\\n";\n }\n ``` `mksyscall_windows.pl`スクリプトに、引数の数が15個以下の場合に`Syscall15`を使用し、不足する引数をゼロで埋めるロジックが追加されています。
コアとなるコードの解説
src/pkg/runtime/windows/amd64/sys.s
の変更
#define maxargs 15
の変更は、GoランタイムがWindowsシステムコールを呼び出す際に、最大15個の引数を処理できることをアセンブリコードに伝えます。これは、システムコール呼び出し規約(calling convention)において、引数がどのようにスタックに積まれるか、あるいはレジスタに渡されるかといった低レベルの詳細に影響します。この値を増やすことで、より多くの引数を安全にシステムコールに渡せるようになります。
src/pkg/runtime/windows/syscall.goc
の Syscall15
関数
このファイルは、Goのランタイムの一部としてC言語で書かれたコード(実際にはGoの内部的なCGoのようなもの)を含んでいます。Syscall15
関数は、Goのユーザーコードから呼び出されるシステムコールラッパーの本体です。
fn uintptr
: 呼び出すWindows API関数のエントリポイントアドレス。nargs uintptr
: 渡す引数の数。a1
からa15
までのuintptr
引数: Windows API関数に渡される実際の引数。WinCall c;
:WinCall
構造体は、システムコールに必要な情報(関数ポインタ、引数の数、引数へのポインタ、戻り値、エラーコードなど)を保持するためのものです。USED(aX);
: これらのマクロは、コンパイラに対して、たとえ直接使用されていなくても引数aX
が意図的に使用されていることを示し、最適化による削除を防ぎます。これは、引数がc.args = &a1;
のようにポインタとして渡されるため、個々の引数が直接参照されない場合があるためです。c.fn = (void*)fn;
: 呼び出す関数のアドレスを設定します。c.n = nargs;
: 引数の数を設定します。c.args = &a1;
: 最初の引数a1
のアドレスをc.args
に設定します。これにより、WinCall
構造体を通じて、すべての引数に連続したメモリとしてアクセスできるようになります。runtime·cgocall(runtime·asmstdcall, &c);
: これはGoランタイムの内部関数で、CGoのメカニズムに似た方法で、アセンブリで書かれたruntime·asmstdcall
関数を呼び出します。runtime·asmstdcall
が実際のシステムコールを実行し、結果をc
構造体に格納します。err = c.err; r1 = c.r1; r2 = c.r2;
: システムコール実行後、c
構造体から戻り値とエラーコードを取得し、Goの戻り値として返します。
src/pkg/syscall/dll_windows.go
の変更
このファイルは、Goのsyscall
パッケージの公開APIの一部です。
func Syscall15(...)
:Syscall15
関数のGo言語での宣言が追加されています。これにより、Goのユーザーコードからこの関数を直接呼び出すことが可能になります。func (p *Proc) Call(a ...uintptr) (...)
:Proc
構造体のCall
メソッドは、Windows DLLから取得したプロシージャ(関数)を呼び出すための汎用的なインターフェースです。case 13:
,case 14:
,case 15:
の追加: 渡された引数の数(len(a)
)が13、14、15の場合に、新しく追加されたSyscall15
を呼び出すようにロジックが追加されています。Syscall15(p.Addr(), uintptr(len(a)), a[0], ..., a[12], 0, 0)
のように、引数の数が15に満たない場合は、残りの引数を0
で埋めてSyscall15
を呼び出しています。これは、Syscall15
が常に15個の引数を期待するためです。
src/pkg/syscall/mksyscall_windows.pl
の変更
このPerlスクリプトは、Goのsyscall
パッケージのソースコードを自動生成するために使用されます。
elsif(@args <= 15)
ブロックの追加: このブロックは、Windows API関数の引数の数が15個以下の場合に実行されます。$asm = "${syscalldot}Syscall15";
: 生成されるGoコードがSyscall15
を呼び出すように設定されます。while(@args < 15) { push @args, "0"; }
: 引数の数が15個に満たない場合、残りの引数を0
で埋めます。これは、Syscall15
が常に15個の引数を必要とするため、生成されるコードが正しい数の引数を渡すようにするためです。
これらの変更が連携することで、Go言語から最大15個の引数を持つWindows API関数を透過的かつ効率的に呼び出すメカニズムが確立されます。
関連リンク
- Go言語の
syscall
パッケージのドキュメント (当時のバージョンに基づく): https://pkg.go.dev/syscall (現在のドキュメントは変更されている可能性があります) - Go言語のIssueトラッカー: https://github.com/golang/go/issues (Issue #2251の詳細は、当時のアーカイブを参照する必要があるかもしれません)
参考にした情報源リンク
- Go言語のソースコード (GitHub): https://github.com/golang/go
- Windows APIの公式ドキュメント (Microsoft Learn): https://learn.microsoft.com/en-us/windows/win32/api/
- Go言語のシステムコールに関する一般的な情報 (当時のGoの設計思想や実装に関する記事など)
- Go言語の
uintptr
型に関する解説 - Go言語のビルドプロセスにおける
mksyscall
スクリプトの役割に関する情報