[インデックス 13541] ファイルの概要
本コミットは、Go言語のCgoツールにおけるデバッグ情報生成に関する修正です。具体的には、__cgodebug_data
セクションの最終エントリに0ではなく1を使用するように変更することで、LLVMベースのGCCが生成するオブジェクトファイルがGoのデバッグツール(特にdebug/macho
パッケージ)で正しく処理されるようにします。
コミット
commit dd62bb4753147ff832eb769af892054fb1562c7c
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Mon Jul 30 18:44:48 2012 -0400
cmd/cgo: use 1 as last entry for __cgodebug_data
LLVM-based gcc will place all-zero data in a zero-filled
section, but our debug/macho can't handle that.
Fixes #3821.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/6444049
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/dd62bb4753147ff832eb769af892054fb1562c7c
元コミット内容
cmd/cgo: use 1 as last entry for __cgodebug_data
LLVM-based gcc will place all-zero data in a zero-filled section, but our debug/macho can't handle that.
Fixes #3821.
変更の背景
この変更は、GoのCgoツールが生成するデバッグ情報が、LLVMベースのGCCコンパイラでコンパイルされた際に問題を引き起こすというバグ(Issue #3821)を修正するために行われました。
従来のCgoは、__cgodebug_data
というデバッグ情報セクションの最終エントリに0を書き込んでいました。しかし、LLVMベースのGCC(例えばClang)は、全てがゼロで初期化されたデータ(all-zero data)を、最適化の一環として「ゼロフィルセクション(zero-filled section)」、特にMach-O形式では__DATA.__common
のようなセクションに配置する傾向があります。
Goのdebug/macho
パッケージは、Mach-O形式の実行ファイルやライブラリからデバッグ情報を読み取るためのものです。このパッケージが、ゼロフィルセクションに配置された__cgodebug_data
セクションを正しく処理できないという問題がありました。具体的には、ゼロフィルセクションはファイルサイズを削減するために、実際にゼロが書き込まれているわけではなく、実行時にOSがゼロで埋めることを期待する領域としてマークされます。debug/macho
がこの特殊なセクションの扱いを考慮していなかったため、デバッグ情報の読み取りに失敗していました。
この問題を解決するため、__cgodebug_data
の最終エントリを0ではなく1にすることで、データが全てゼロであるという条件を満たさなくなり、LLVMベースのGCCがこのセクションをゼロフィルセクションに配置するのを防ぎます。これにより、debug/macho
が期待する形式でデバッグ情報が生成され、正しく読み取れるようになります。
前提知識の解説
- Cgo: Go言語の機能の一つで、C言語のコードをGoプログラムから呼び出すことを可能にします。Cgoは、GoとCの間のインターフェースコードを生成し、コンパイルプロセスを管理します。
- デバッグ情報: プログラムの実行中にデバッガがソースコードレベルで情報を表示したり、変数の値を検査したりするために必要な情報です。これには、ソースファイル名、行番号、変数名、型情報などが含まれます。
- Mach-O (Mach Object file format): macOS、iOS、watchOS、tvOSなどのAppleのオペレーティングシステムで使用される実行可能ファイル、オブジェクトコード、共有ライブラリ、ダイナミックロード可能バンドル、およびコアダンプのファイル形式です。
- セクション (Section): 実行可能ファイルやオブジェクトファイルは、コード、データ、デバッグ情報などを格納するために複数のセクションに分割されます。例えば、
.text
セクションは実行可能なコードを、.data
セクションは初期化されたデータを、.bss
セクションは初期化されていないデータを格納します。 - ゼロフィルセクション (Zero-filled section): プログラムの実行開始時にOSによってゼロで埋められることが保証されているセクションです。これにより、ファイルサイズを削減できます。Mach-Oでは、
__DATA.__common
や__BSS
などがこれに該当します。 - LLVM (Low Level Virtual Machine): コンパイラ技術のオープンソースプロジェクトです。LLVMは、コンパイラのバックエンド、最適化、コード生成など、コンパイラツールチェーンの様々な側面を提供します。
- GCC (GNU Compiler Collection): GNUプロジェクトによって開発されたコンパイラ群です。C、C++、Objective-C、Fortran、Ada、Goなどの多くのプログラミング言語をサポートしています。
- LLVMベースのGCC: GCC自体はLLVMとは異なるコンパイラですが、この文脈では、LLVMの技術(例えばClangフロントエンド)を利用してGCCの機能を提供するようなコンパイラ、あるいはLLVMツールチェーンの一部としてGCCの機能が提供される環境を指している可能性があります。特にmacOSでは、Xcodeに付属する
gcc
コマンドが実質的にClang(LLVMベースのC/C++/Objective-Cコンパイラ)のラッパーである場合が多いです。Clangは、ゼロ初期化されたデータを効率的に扱うために、ゼロフィルセクションに配置する最適化を行います。 - Issue #3821: Goプロジェクトのバグトラッカーで報告された問題の識別子です。このコミットがこの特定のバグを修正したことを示します。
技術的詳細
GoのCgoツールは、CコードとGoコードの連携を可能にするために、いくつかの内部的な処理を行います。その一つが、デバッグ情報の生成です。Cgoは、GoのランタイムがCgo呼び出しに関するデバッグ情報を取得できるように、__cgodebug_data
という名前のカスタムセクションを生成します。このセクションには、Cgo呼び出しに関連するメタデータが含まれます。
問題は、この__cgodebug_data
セクションの構造にありました。Cgoは、このセクションの終端を示すマーカーとして、全ての要素がゼロであるエントリを配置していました。
// 簡略化した概念的な表現
struct CgoDebugEntry {
// ... デバッグ情報 ...
};
struct CgoDebugEntry __cgodebug_data[] = {
{ /* エントリ1 */ },
{ /* エントリ2 */ },
// ...
{ 0, 0, 0, ... } // 終端マーカー (全てゼロ)
};
LLVMベースのコンパイラ(特にClang)は、コンパイル時に最適化を行います。その最適化の一つに、全てがゼロで初期化された静的データを、実行可能ファイル内の物理的な領域を占有しない「ゼロフィルセクション」に配置するというものがあります。これにより、実行可能ファイルのサイズを削減できます。Mach-O形式では、__DATA.__common
セクションがこのような目的でよく使用されます。
Goのdebug/macho
パッケージは、Mach-Oファイルを解析してデバッグ情報を読み取るためのライブラリです。このライブラリは、通常のデータセクションから情報を読み取ることを想定していましたが、ゼロフィルセクションに配置されたデータ(特に__DATA.__common
)の特殊な扱いを考慮していませんでした。ゼロフィルセクションは、ファイル内では単にその領域が存在することを示すだけで、実際のゼロバイトは含まれていません。実行時にローダーがその領域をゼロで埋めます。
したがって、__cgodebug_data
セクションが全てゼロの終端マーカーを持つためにゼロフィルセクションに配置されると、debug/macho
がそのセクションを読み取ろうとした際に、期待するデータ(ゼロバイト)が見つからず、デバッグ情報の解析に失敗していました。
このコミットでは、この問題を回避するために、__cgodebug_data
セクションの最終エントリを0ではなく1に設定するように変更しました。
// 変更後の概念的な表現
struct CgoDebugEntry __cgodebug_data[] = {
{ /* エントリ1 */ },
{ /* エントリ2 */ },
// ...
{ 1, 0, 0, ... } // 終端マーカー (最初の要素が1)
};
これにより、最終エントリが「全てゼロ」ではなくなるため、LLVMベースのGCCはこれをゼロフィルセクションに配置する最適化を行わなくなります。結果として、__cgodebug_data
セクションは通常のデータセクションに配置され、debug/macho
が正しく読み取れるようになります。この変更は、デバッグ情報の意味論的な内容には影響を与えず、単にコンパイラの最適化挙動を回避するためのテクニックです。
コアとなるコードの変更箇所
変更は src/cmd/cgo/gcc.go
ファイルの loadDWARF
関数内で行われています。
--- a/src/cmd/cgo/gcc.go
+++ b/src/cmd/cgo/gcc.go
@@ -508,7 +508,12 @@ func (p *Package) loadDWARF(f *File, names []*Name) {
fmt.Fprintf(&b, "\t0,\n")
}
}
- fmt.Fprintf(&b, "\t0\n")
+ // for the last entry, we can not use 0, otherwise
+ // in case all __cgodebug_data is zero initialized,
+ // LLVM-based gcc will place the it in the __DATA.__common
+ // zero-filled section (our debug/macho doesn't support
+ // this)
+ fmt.Fprintf(&b, "\t1\n")
fmt.Fprintf(&b, "};\\n")
d, bo, debugData := p.gccDebug(b.Bytes())
コアとなるコードの解説
src/cmd/cgo/gcc.go
は、CgoがCコンパイラ(この場合はGCC)を呼び出してCコードをコンパイルし、GoとCの間のリンケージを処理する部分を担っています。
loadDWARF
関数は、Cgoが生成するデバッグ情報の一部であるDWARF(Debugging With Attributed Record Formats)情報を処理する際に、__cgodebug_data
というC言語の配列を生成するためのコードを書き出しています。この配列は、GoのランタイムがCgo呼び出しに関するデバッグ情報を取得するために使用されます。
変更前のコードでは、fmt.Fprintf(&b, "\t0\n")
という行で、__cgodebug_data
配列の最終エントリとして数値の 0
を書き込んでいました。これは、配列の終端を示すマーカーとして機能していました。
変更後のコードでは、この行が fmt.Fprintf(&b, "\t1\n")
に変更されています。これにより、最終エントリとして 1
が書き込まれるようになります。
追加されたコメントが変更の理由を明確に説明しています。
// for the last entry, we can not use 0, otherwise
// in case all __cgodebug_data is zero initialized,
// LLVM-based gcc will place the it in the __DATA.__common
// zero-filled section (our debug/macho doesn't support
// this)
このコメントは、最終エントリに0を使用できない理由を述べています。もし__cgodebug_data
全体がゼロで初期化される場合(つまり、他のエントリも全てゼロで、最終エントリもゼロの場合)、LLVMベースのGCCはこれを__DATA.__common
というゼロフィルセクションに配置します。しかし、Goのdebug/macho
パッケージはこのゼロフィルセクションの扱いをサポートしていないため、問題が発生するという背景が説明されています。最終エントリを1にすることで、この条件を回避し、セクションが通常のデータセクションに配置されるようにしています。
関連リンク
- Go Issue #3821: https://github.com/golang/go/issues/3821
- Go CL 6444049: https://golang.org/cl/6444049 (Gerrit Code Reviewへのリンク)
参考にした情報源リンク
- Mach-O File Format Reference: https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/MachORuntime/Reference/reference.html
- LLVM Project: https://llvm.org/
- GNU Compiler Collection (GCC): https://gcc.gnu.org/
- DWARF Debugging Format: https://dwarfstd.org/
- Go Cgo Documentation: https://pkg.go.dev/cmd/cgo
- Go
debug/macho
package: https://pkg.go.dev/debug/macho - Zero-initialized data in C/C++: https://en.wikipedia.org/wiki/Zero-initialized_data
- Common sections in Mach-O: https://www.mikeash.com/pyblog/friday-qa-2010-01-29-mach-o-executables.html (非公式ブログですが、Mach-Oのセクションに関する良い解説)
- Clang and zero-initialized data: https://clang.llvm.org/docs/InternalsManual.html#zero-initialized-data (Clangの内部マニュアル、ゼロ初期化データに関する記述)
- GoのソースコードとIssueトラッカーの関連情報。