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

[インデックス 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デコレーションされたシンボル名が、cgodynimport処理によって誤って解釈される可能性がありました。

この問題は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つの主要な変更を加えました。

  1. シンボルバージョン区切り文字の変更:

    • 以前のcgodynimport出力では、シンボル名とバージョン情報を区切るデリミタとして@が使用されていました(例: symbol@version)。
    • しかし、Windowsのstdcall呼び出し規約では、関数名に引数のバイト数を示す@Nというデコレーションが付加されます(例: _MyFunction@8)。
    • この@記号の重複が問題を引き起こしていました。cgoが生成したdynimport情報が、stdcallデコレーションと衝突し、リンカがシンボル名を正しく解析できない可能性がありました。
    • このコミットでは、cgodynimport出力におけるシンボルバージョン区切り文字を@から#に変更しました。これにより、symbol#versionのような形式になります。
    • これに伴い、Goリンカ(cmd/ld)もdynimport情報を解析する際に、新しい区切り文字#を認識するように変更されました。
  2. @Nデコレーションの除去(PE形式のみ):

    • cgodynimport出力の最初の引数から、@N形式のstdcallデコレーションを除去するロジックが追加されました。これはPE形式(Windowsの実行ファイル形式)にのみ適用されます。
    • #pragma dynimportディレクティブは、通常、#pragma dynimport <Goシンボル名> <外部シンボル名> <ライブラリ名>という形式を取ります。
    • この変更により、cgostdcallデコレーションされた外部シンボル名(例: _MyFunction@8)を受け取った場合でも、dynimportの最初の引数(Goシンボル名に対応する部分)からは@N部分を取り除き、純粋な関数名(例: _MyFunction)を使用するようにしました。これにより、Go側で参照するシンボル名がstdcallデコレーションに依存しない、よりクリーンな形になります。
    • この修正は、cgoが生成するdynimportディレクティブが、Windowsのリンカが期待する形式とより一致するように調整されたことを意味します。

これらの変更により、cgoはWindows環境でstdcall呼び出し規約を使用する外部関数を、その名前デコレーションに関わらず、正しく動的にインポートできるようになりました。

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

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

  1. src/cmd/cgo/out.go
  2. 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 の変更点

  1. シンボルバージョン区切り文字の変更:

    -				targ += "@" + s.Version
    +				targ += "#" + s.Version
    

    dynimport関数内で、シンボル名にバージョン情報を付加する際に使用されるデリミタが@から#に変更されました。これは、#pragma dynimportディレクティブの2番目の引数(外部シンボル名)に影響します。これにより、Windowsのstdcallデコレーション(@N)との衝突を避けます。

  2. @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言語の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の名前デコレーションやcgodynimportに関する一般的な解説記事。
    • 例: "What is __stdcall?" や "How does cgo work with dynamic libraries?" などの検索クエリで得られる情報。
    • (具体的なURLは検索結果に依存するため、ここでは一般的な説明に留めます。)