[インデックス 15029] ファイルの概要
コミット
- コミットハッシュ:
dab268fac34baaa93c41e7239c4412b11ca1e567
- 作者: James Gray james@james4k.com
- コミット日時: 2013年1月30日 08:29:33 -0800
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/dab268fac34baaa93c41e7239c4412b11ca1e567
元コミット内容
cmd/cgo: allow for stdcall decorated dynimport names
To allow for stdcall decorated names on Windows, two changes were needed:
1. Change the symbol versioning delimiter '@' in cgo's dynimport output to a '#', and in cmd/ld when it parses dynimports.
2. Remove the "@N" decorator from the first argument of cgo's dynimport output (PE only).
Fixes #4607.
R=minux.ma, adg, rsc
CC=golang-dev
https://golang.org/cl/7047043
変更の背景
このコミットは、Go言語のcgo
ツールがWindows環境でstdcall
呼び出し規約を使用する関数を動的にインポートする際の問題を解決するために行われました。
Windowsでは、stdcall
呼び出し規約を使用する関数は、エクスポートされたシンボル名に引数のバイト数を表す@N
というサフィックス(デコレーション)が付加されることがあります。例えば、_MyFunction@8
のように、関数名に@
と引数の合計バイト数が続く形式です。
Goのcgo
は、C言語の関数をGoから呼び出すためのメカニズムを提供します。この際、外部の共有ライブラリ(DLLなど)から関数を動的にインポートするために、#pragma dynimport
ディレクティブを使用します。
元の実装では、cgo
が生成するdynimport
の出力において、シンボル名とバージョン情報を区切るデリミタとして@
が使用されていました。しかし、Windowsのstdcall
デコレーションも@
を使用するため、これが衝突し、リンカが正しいシンボル名を解析できない問題が発生していました。具体的には、_MyFunction@8
のようなstdcall
デコレーションされたシンボル名が、cgo
のdynimport
処理によって誤って解釈される可能性がありました。
この問題はGoのIssue #4607として報告されており、Windows環境でのstdcall
関数の動的インポートを正しくサポートするために、cgo
とGoリンカ(cmd/ld
)の挙動を修正する必要がありました。
前提知識の解説
cgo
cgo
は、Go言語のプログラムからC言語のコードを呼び出したり、逆にC言語のコードからGo言語の関数を呼び出したりするためのGoのツールです。GoとCの間の相互運用性(Foreign Function Interface, FFI)を提供します。cgo
を使用することで、既存のCライブラリをGoプロジェクトに統合したり、パフォーマンスが重要な部分をCで記述したりすることが可能になります。
dynimport (dynamic import)
dynimport
は、Goのリンカ(cmd/ld
)が外部の共有ライブラリ(WindowsではDLL、Linuxでは.so
、macOSでは.dylib
)からシンボル(関数や変数)を動的にインポートするためのメカニズムです。cgo
は、CコードをGoに統合する際に、このdynimport
メカニズムを利用して、必要な外部シンボルへの参照を生成します。具体的には、#pragma dynimport
という形式のディレクティブが生成され、リンカがこれを解釈して動的リンクを行います。
stdcall (Standard Call)
stdcall
は、主にMicrosoft Windows環境で使用される関数呼び出し規約の一つです。この規約では、呼び出し元が引数を右から左へスタックにプッシュし、呼び出された関数自身がスタックから引数をクリーンアップ(ポップ)します。
stdcall
の重要な特徴として、Windowsではエクスポートされた関数名に「名前のデコレーション(name decoration)」が施されることがあります。これは、関数名に_FunctionName@N
という形式で、アンダースコア、関数名、@
記号、そして引数の合計バイト数(N
)が付加されるものです。例えば、void MyFunction(int a, int b)
(int
が4バイトの場合)は、_MyFunction@8
としてエクスポートされることがあります。このデコレーションは、リンカが正しい関数シグネチャを持つ関数を見つけるのに役立ちます。
PE (Portable Executable)
PEは、Windowsオペレーティングシステムで使用される実行可能ファイル、オブジェクトコード、DLL、FONドライバなどのファイル形式です。PEファイルは、プログラムのコード、データ、リソース、およびリンカがプログラムをメモリにロードして実行するために必要な情報を含んでいます。stdcall
の名前デコレーションは、このPE形式の実行可能ファイルやDLL内でシンボルがどのように表現されるかに関連します。
cmd/ld
cmd/ld
は、Go言語のツールチェインにおけるリンカです。コンパイラによって生成されたオブジェクトファイル(.o
ファイル)を結合し、実行可能なバイナリファイルや共有ライブラリを生成する役割を担います。cgo
が生成する#pragma dynimport
ディレクティブを解釈し、外部ライブラリへの動的リンクを解決するのもcmd/ld
の機能の一部です。
技術的詳細
このコミットは、Windowsにおけるstdcall
デコレーションされたシンボル名の取り扱いを改善するために、cgo
の出力とGoリンカの解析ロジックに2つの主要な変更を加えました。
-
シンボルバージョン区切り文字の変更:
- 以前の
cgo
のdynimport
出力では、シンボル名とバージョン情報を区切るデリミタとして@
が使用されていました(例:symbol@version
)。 - しかし、Windowsの
stdcall
呼び出し規約では、関数名に引数のバイト数を示す@N
というデコレーションが付加されます(例:_MyFunction@8
)。 - この
@
記号の重複が問題を引き起こしていました。cgo
が生成したdynimport
情報が、stdcall
デコレーションと衝突し、リンカがシンボル名を正しく解析できない可能性がありました。 - このコミットでは、
cgo
のdynimport
出力におけるシンボルバージョン区切り文字を@
から#
に変更しました。これにより、symbol#version
のような形式になります。 - これに伴い、Goリンカ(
cmd/ld
)もdynimport
情報を解析する際に、新しい区切り文字#
を認識するように変更されました。
- 以前の
-
@N
デコレーションの除去(PE形式のみ):cgo
のdynimport
出力の最初の引数から、@N
形式のstdcall
デコレーションを除去するロジックが追加されました。これはPE形式(Windowsの実行ファイル形式)にのみ適用されます。#pragma dynimport
ディレクティブは、通常、#pragma dynimport <Goシンボル名> <外部シンボル名> <ライブラリ名>
という形式を取ります。- この変更により、
cgo
はstdcall
デコレーションされた外部シンボル名(例:_MyFunction@8
)を受け取った場合でも、dynimport
の最初の引数(Goシンボル名に対応する部分)からは@N
部分を取り除き、純粋な関数名(例:_MyFunction
)を使用するようにしました。これにより、Go側で参照するシンボル名がstdcall
デコレーションに依存しない、よりクリーンな形になります。 - この修正は、
cgo
が生成するdynimport
ディレクティブが、Windowsのリンカが期待する形式とより一致するように調整されたことを意味します。
これらの変更により、cgo
はWindows環境でstdcall
呼び出し規約を使用する外部関数を、その名前デコレーションに関わらず、正しく動的にインポートできるようになりました。
コアとなるコードの変更箇所
このコミットでは、以下の2つのファイルが変更されています。
src/cmd/cgo/out.go
src/cmd/ld/go.c
src/cmd/cgo/out.go
の変更
--- a/src/cmd/cgo/out.go
+++ b/src/cmd/cgo/out.go
@@ -176,7 +176,7 @@ func dynimport(obj string) {
for _, s := range sym {
targ := s.Name
if s.Version != "" {
- targ += "@" + s.Version
+ targ += "#" + s.Version
}
fmt.Fprintf(stdout, "#pragma dynimport %s %s %q\\n", s.Name, targ, s.Library)
}
@@ -218,7 +218,8 @@ func dynimport(obj string) {
}
for _, s := range sym {
ss := strings.Split(s, ":")
- fmt.Fprintf(stdout, "#pragma dynimport %s %s %q\\n", ss[0], ss[0], strings.ToLower(ss[1]))
+ name := strings.Split(ss[0], "@")[0]
+ fmt.Fprintf(stdout, "#pragma dynimport %s %s %q\\n", name, ss[0], strings.ToLower(ss[1]))
}
return
}
src/cmd/ld/go.c
の変更
--- a/src/cmd/ld/go.c
+++ b/src/cmd/ld/go.c
@@ -506,7 +506,7 @@ loaddynimport(char *file, char *pkg, char *p, int n)
}
name = expandpkg(name, pkg);
- q = strchr(def, '@');
+ q = strchr(def, '#');
if(q)
*q++ = '\0';
s = lookup(name, 0);
コアとなるコードの解説
src/cmd/cgo/out.go
の変更点
-
シンボルバージョン区切り文字の変更:
- targ += "@" + s.Version + targ += "#" + s.Version
dynimport
関数内で、シンボル名にバージョン情報を付加する際に使用されるデリミタが@
から#
に変更されました。これは、#pragma dynimport
ディレクティブの2番目の引数(外部シンボル名)に影響します。これにより、Windowsのstdcall
デコレーション(@N
)との衝突を避けます。 -
@N
デコレーションの除去:- fmt.Fprintf(stdout, "#pragma dynimport %s %s %q\\n", ss[0], ss[0], strings.ToLower(ss[1])) + name := strings.Split(ss[0], "@")[0] + fmt.Fprintf(stdout, "#pragma dynimport %s %s %q\\n", name, ss[0], strings.ToLower(ss[1]))
この変更は、PE形式(Windows)の
dynimport
出力に特化したものです。ss[0]
は、stdcall
デコレーションを含む可能性のある外部シンボル名(例:_MyFunction@8
)です。strings.Split(ss[0], "@")[0]
によって、@
記号で分割し、その最初の部分(つまり、@N
デコレーションを除去した部分)をname
変数に格納しています。- そして、
#pragma dynimport
の最初の引数(Go側で参照するシンボル名)として、このname
(デコレーションなしのシンボル名)を使用するように変更されました。2番目の引数(外部シンボル名)は引き続きss[0]
(デコレーションあり)を使用します。 - これにより、Goのコードからはデコレーションなしの名前で関数を参照できるようになり、Windowsのリンカはデコレーションありの名前で外部シンボルを解決できるようになります。
src/cmd/ld/go.c
の変更点
- q = strchr(def, '@');
+ q = strchr(def, '#');
Goリンカのloaddynimport
関数内で、dynimport
ディレクティブを解析する際に、シンボル名とバージョン情報を区切るデリミタとして検索する文字が@
から#
に変更されました。これは、cgo
の出力変更と同期するための修正であり、リンカが新しい形式のdynimport
情報を正しく解釈できるようにします。
これらの変更は、cgo
がWindowsのstdcall
呼び出し規約を持つ関数を正しく処理し、Goプログラムがこれらの関数を問題なく動的にリンクできるようにするために不可欠でした。
関連リンク
- Go Issue #4607: https://github.com/golang/go/issues/4607 (このコミットが修正した問題の報告)
- Gerrit Change List 7047043: https://golang.org/cl/7047043 (このコミットの元のコードレビューページ)
参考にした情報源リンク
- Go言語のcgoに関する公式ドキュメント: https://pkg.go.dev/cmd/cgo
- Microsoft Learn - Calling Conventions: https://learn.microsoft.com/en-us/cpp/build/calling-conventions?view=msvc-170 (特に
__stdcall
に関する情報) - Microsoft Learn - PE Format: https://learn.microsoft.com/en-us/windows/win32/debug/pe-format
- Go言語のリンカ (cmd/ld) に関する情報: Goのソースコードや関連する設計ドキュメント(例: Goの内部構造に関するブログ記事など)
- Stack Overflow / 技術ブログ:
stdcall
の名前デコレーションやcgo
のdynimport
に関する一般的な解説記事。- 例: "What is
__stdcall
?" や "How doescgo
work with dynamic libraries?" などの検索クエリで得られる情報。 - (具体的なURLは検索結果に依存するため、ここでは一般的な説明に留めます。)
- 例: "What is