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

[インデックス 13762] ファイルの概要

このコミットは、Go言語のcgoツールにおけるデバッグ情報の取り扱いに関する修正です。具体的には、src/cmd/cgo/gcc.goファイルが変更され、ELFファイル内のデバッグデータセクション(__cgodebug_data)をDWARFデバッグ情報よりも優先して使用するように改善されています。これにより、OpenBSD/386およびNetBSD/386環境でのmkerrors.shスクリプトの動作が修正されました。

コミット

commit 4cfcb4a04bc140da0ad196b1b9cab32bde8f2a62
Author: Joel Sing <jsing@google.com>
Date:   Fri Sep 7 13:32:40 2012 +1000

    cgo: use debug data section for ELF

    When generating enums use the debug data section instead of the
    DWARF debug info, if it is available in the ELF file. This allows
    mkerrors.sh to work correctly on OpenBSD/386 and NetBSD/386.

    Fixes #2470.

    R=golang-dev, minux.ma
    CC=golang-dev
    https://golang.org/cl/6495090
---
 src/cmd/cgo/gcc.go | 33 ++++++++++++++++++++++++++-------\n 1 file changed, 26 insertions(+), 7 deletions(-)

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/4cfcb4a04bc140da0ad196b1b9cab32bde8f2a62

元コミット内容

このコミットは、cgoがELF形式の実行ファイルを生成する際に、列挙型(enums)のデバッグ情報を処理する方法を変更します。既存のDWARFデバッグ情報に加えて、ELFファイル内に存在する可能性のある専用のデバッグデータセクション(__cgodebug_data)を優先的に利用するように修正されました。この変更は、OpenBSD/386およびNetBSD/386環境でmkerrors.shスクリプトが正しく機能しない問題(Issue #2470)を解決することを目的としています。

変更の背景

Go言語のcgoツールは、GoコードとC/C++コードを連携させるための重要なコンポーネントです。この連携において、C言語で定義された列挙型などの情報をGo側で利用できるようにするためには、コンパイルされたCオブジェクトファイルからデバッグ情報を正確に抽出する必要があります。

Issue #2470は、「cmd/cgo: -godefs makes negative values from unsigned constants」というタイトルで、cgoが符号なし定数から負の値を生成してしまう問題が報告されていました。これは、特にOpenBSD/386およびNetBSD/386のような特定のアーキテクチャとOSの組み合わせにおいて、mkerrors.shというスクリプト(おそらくシステムコールやエラーコードに関連する定数を生成するもの)が期待通りに動作しない原因となっていました。

この問題の根本原因は、cgoがデバッグ情報を解析する際に、DWARF情報のみに依存していたことにありました。しかし、特定の環境やコンパイラの挙動によっては、DWARF情報だけでは不十分であったり、あるいはより正確な情報が別の専用デバッグデータセクションに格納されている場合がありました。このコミットは、そのような状況に対応し、より信頼性の高いデバッグ情報源を利用することで、クロスプラットフォームでの互換性と正確性を向上させることを目指しています。

前提知識の解説

cgo

cgoは、GoプログラムからC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりするためのGoツールチェーンの一部です。GoとCの間のデータ型の変換や、関数呼び出しのメカニズムを提供します。cgoを使用すると、Goの強力な並行処理機能と、既存のCライブラリの豊富なエコシステムを組み合わせることができます。

ELF (Executable and Linkable Format)

ELFは、Unix系オペレーティングシステム(Linux, BSD, Solarisなど)で広く使用されている実行可能ファイル、オブジェクトファイル、共有ライブラリの標準ファイルフォーマットです。ELFファイルは、プログラムコード、データ、シンボルテーブル、デバッグ情報など、実行に必要な様々なセクションで構成されています。

DWARF (Debugging With Attributed Record Formats)

DWARFは、ソースレベルデバッガがプログラムの実行を解析するために必要なデバッグ情報を格納するための標準フォーマットです。変数名、型情報、ソースコードの行番号と実行可能コードのアドレスのマッピング、関数呼び出しスタックの情報などが含まれます。DWARF情報は通常、ELFファイル内の.debug_*セクションに格納されます。

デバッグデータセクション (__cgodebug_data)

このコミットで言及されている__cgodebug_dataは、cgoが生成するELFファイル内に、DWARFとは別に特定のデバッグ情報を格納するために使用されるカスタムセクションであると推測されます。これは、DWARFが提供する汎用的なデバッグ情報とは異なり、cgoがGoとCの連携のために特に必要とする、より特化した情報(例えば、列挙型の値など)を効率的に格納するために導入された可能性があります。

enum (列挙型)

列挙型は、プログラミングにおいて、一連の名前付き定数を定義するためのデータ型です。C言語では、enumキーワードを使用して定義され、各列挙子には整数値が関連付けられます。cgoがCの列挙型をGoに変換する際、これらの整数値を正確にGoの定数として表現する必要があります。

mkerrors.sh

コミットメッセージに登場するmkerrors.shは、Goの標準ライブラリやシステム関連のコードベースで、エラーコードや定数を自動生成するために使用されるシェルスクリプトであると推測されます。このようなスクリプトは、OS固有の定数やシステムコール番号などをCヘッダファイルから抽出し、Goコードとして利用可能な形式に変換する役割を担うことがあります。このスクリプトが正しく動作しないということは、cgoがCヘッダから必要な定数情報を正確に取得できていないことを意味します。

技術的詳細

このコミットの核心は、cgoがELFファイルからデバッグ情報を取得する際のロジックの変更にあります。

従来のcgoは、Cオブジェクトファイルからデバッグ情報を抽出する際に、主にDWARF情報に依存していました。しかし、特定の環境(OpenBSD/386, NetBSD/386)では、DWARF情報だけでは列挙型の値が正しく取得できない、あるいは誤った値が解釈される問題が発生していました。

この修正では、ELFファイル内に__cgodebug_dataという名前の特定のデバッグデータセクションが存在する場合、そのセクションから情報を取得することを優先するように変更されました。このセクションは、cgoがCの列挙型などの情報をより直接的かつ正確に格納するために設計されたものと考えられます。

変更されたgcc.go内のloadDWARF関数では、列挙型の値を処理する際に、まずn.Kind == "const"かつi < len(enumVal)という条件で、enumVal配列(おそらく__cgodebug_dataから読み込まれた値)から定数値を設定しようとします。これが利用できない場合、または列挙型がDWARF情報から抽出されたものである場合にのみ、従来のn.Type.EnumValues(DWARF由来)から値を設定するフォールバックロジックが適用されます。

また、gccDebug関数では、ELFファイルをオープンした後、シンボルテーブルを走査し、__cgodebug_dataという名前のシンボルを探します。このシンボルが見つかった場合、そのシンボルが指すセクションから実際のデバッグデータ(dataスライス)を抽出します。これにより、DWARF情報とは別に、cgoが特別に用意したデバッグデータを取得できるようになります。

このアプローチにより、cgoは、より信頼性の高いデバッグデータセクションが存在する場合はそれを優先し、そうでない場合はDWARF情報にフォールバックするという、より堅牢なデバッグ情報取得メカニズムを実現しています。これにより、特定のプラットフォームでのデバッグ情報の解析問題が解決され、mkerrors.shのようなツールが正しく機能するようになりました。

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

変更はsrc/cmd/cgo/gcc.goファイルに集中しています。

--- a/src/cmd/cgo/gcc.go
+++ b/src/cmd/cgo/gcc.go
@@ -616,15 +616,16 @@ func (p *Package) loadDWARF(f *File, names []*Name) {
 		tn.FuncType = conv.FuncType(f, pos)
 	} else {
 		tn.Type = conv.Type(types[i], pos)
-		if enums[i] != 0 && n.Type.EnumValues != nil {
+		// Prefer debug data over DWARF debug output, if we have it.
+		if n.Kind == "const" && i < len(enumVal) {
+			n.Const = fmt.Sprintf("%#x", enumVal[i])
+		} else if enums[i] != 0 && n.Type.EnumValues != nil {
 			k := fmt.Sprintf("__cgo_enum__%d", i)
 			tn.Kind = "const"
 			tn.Const = fmt.Sprintf("%#x", tn.Type.EnumValues[k])
 			// Remove injected enum to ensure the value will deep-compare
 			// equally in future loads of the same constant.
 			delete(tn.Type.EnumValues, k)
-		} else if n.Kind == "const" && i < len(enumVal) {
-			tn.Const = fmt.Sprintf("%#x", enumVal[i])
 		}
 	}
 }
@@ -802,17 +803,35 @@ func (p *Package) gccDebug(stdin []byte) (*dwarf.Data, binary.ByteOrder, []byte) {
 		return d, f.ByteOrder, data
 	}
 
-	// Can skip debug data block in ELF and PE for now.
-	// The DWARF information is complete.
-
 	if f, err := elf.Open(gccTmp()); err == nil {
 		d, err := f.DWARF()
 		if err != nil {
 			fatalf("cannot load DWARF output from %s: %v", gccTmp(), err)
 		}
-		return d, f.ByteOrder, nil
+		var data []byte
+		symtab, err := f.Symbols()
+		if err == nil {
+			for i := range symtab {
+				s := &symtab[i]
+				if s.Name == "__cgodebug_data" {
+					// Found it.  Now find data section.
+					if i := int(s.Section); 0 <= i && i < len(f.Sections) {
+						sect := f.Sections[i]
+						if sect.Addr <= s.Value && s.Value < sect.Addr+sect.Size {
+							if sdat, err := sect.Data(); err == nil {
+								data = sdat[s.Value-sect.Addr:]
+							}
+						}
+					}
+				}
+			}
+		}
+		return d, f.ByteOrder, data
 	}
 
+	// Can skip debug data block in PE for now.
+	// The DWARF information is complete.
+
 	if f, err := pe.Open(gccTmp()); err == nil {
 		d, err := f.DWARF()
 		if err != nil {

コアとなるコードの解説

func (p *Package) loadDWARF(f *File, names []*Name) の変更

この関数は、DWARFデバッグ情報から型や定数をロードする役割を担っています。変更点は以下の通りです。

  • 優先順位の変更:
    • 変更前は、enums[i] != 0 && n.Type.EnumValues != nil の条件でDWARF由来の列挙型を処理し、その後に n.Kind == "const" && i < len(enumVal) の条件でenumVal(おそらく__cgodebug_data由来)を処理していました。
    • 変更後は、// Prefer debug data over DWARF debug output, if we have it. というコメントが追加され、n.Kind == "const" && i < len(enumVal) の条件が先に評価されるようになりました。これは、__cgodebug_dataセクションから取得した情報が利用可能であれば、それを優先的に使用するという意図を明確に示しています。
  • ロジックの整理:
    • n.Kind == "const" && i < len(enumVal) のブロックが、DWARF由来の処理ブロックの前に移動しました。これにより、enumValに値があれば、まずその値が定数として設定されます。
    • もしenumValに値がない場合、またはn.Kind"const"でない場合は、引き続きenums[i] != 0 && n.Type.EnumValues != nil の条件でDWARF情報から列挙型を処理します。

この変更により、cgoは列挙型の値を決定する際に、より直接的で信頼性の高い__cgodebug_dataセクションの情報を優先的に利用するようになり、特定の環境での値の誤解釈を防ぐことができます。

func (p *Package) gccDebug(stdin []byte) (*dwarf.Data, binary.ByteOrder, []byte) の変更

この関数は、GCCが生成した一時ファイルからデバッグ情報を読み込む役割を担っています。主な変更点は、ELFファイルの処理部分です。

  • ELFファイルのデバッグデータ抽出:
    • 変更前は、ELFファイルをオープンし、DWARF情報を読み込んだ後、return d, f.ByteOrder, nil として、デバッグデータ(dataスライス)をnilで返していました。これは、DWARF情報が完全であると仮定し、__cgodebug_dataのような追加のデバッグデータブロックをスキップしていたことを意味します。
    • 変更後は、DWARF情報を読み込んだ後、さらにELFファイルのシンボルテーブル(f.Symbols())を走査するロジックが追加されました。
    • この走査の中で、シンボル名が"__cgodebug_data"であるシンボルを探します。
    • "__cgodebug_data"シンボルが見つかった場合、そのシンボルが指すセクション(f.Sections[i])から実際のデータ(sdat)を読み込み、シンボルの値(s.Value)に基づいて適切なオフセットからデータを抽出します。
    • 抽出されたデータはdataスライスに格納され、最終的にreturn d, f.ByteOrder, dataとして返されます。

この変更により、gccDebug関数は、DWARF情報だけでなく、ELFファイル内に埋め込まれた__cgodebug_dataセクションのデータも取得できるようになりました。このデータはloadDWARF関数で列挙型の値を設定する際に利用され、OpenBSD/386やNetBSD/386のような環境でのmkerrors.shの動作不良を解決するのに貢献します。

関連リンク

参考にした情報源リンク