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

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

このコミットは、Go言語のcmd/cgoツールにおける、packed struct(パックド構造体)の扱いに関するバグ修正です。具体的には、GCCの特定のバグ(PR52991)を回避するために、__gcc_struct__属性を__packed__属性と併用するように変更しています。これにより、GoとCの間のデータ構造のレイアウトの不一致によって引き起こされる問題が解決されます。

コミット

commit 949228a322e4ccac49abb620a9e33fe3dfde66e6
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Sun Jun 9 22:06:29 2013 +0800

    cmd/cgo: use gcc_struct attribute for packed structs to work around GCC PR52991.
    Fixes #5603.
    
    R=iant, dave
    CC=gobot, golang-dev
    https://golang.org/cl/9895043

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

https://github.com/golang/go/commit/949228a322e4ccac49abb620a9e33fe3dfde66e6

元コミット内容

このコミットは、cmd/cgoが生成するCコードにおいて、__packed__属性を持つ構造体に対して、GCCのバグ(PR52991)を回避するために__gcc_struct__属性も追加するように変更します。これにより、Goの構造体とCの構造体間でメモリレイアウトの不一致が発生する問題(Go issue #5603)を修正します。

変更の背景

Go言語のcgoツールは、GoプログラムからC言語のコードを呼び出すためのメカニズムを提供します。この際、Goの構造体とCの構造体の間でデータをやり取りする場合、両者のメモリレイアウトが一致している必要があります。しかし、コンパイラ(この場合はGCC)の最適化やアライメントのルールによって、GoとCで同じ定義の構造体でもメモリレイアウトが異なる場合があります。

特に問題となったのは、__packed__属性を持つ構造体でした。__packed__属性は、構造体のメンバ間にパディング(埋め草)を入れないようにコンパイラに指示するもので、メモリを節約したり、特定のハードウェアインターフェースとの互換性を保つために使用されます。

Go issue #5603は、cgoが生成するCコードにおいて、__packed__属性を持つ構造体がGCCによって正しく扱われないという問題が報告されました。これは、GCCのバグであるPR52991に起因していました。PR52991は、GCCが特定の条件下で__packed__属性を持つ構造体のアライメントを誤って計算するというバグです。このバグにより、Go側で期待される構造体レイアウトと、GCCがコンパイルしたCコード側での構造体レイアウトが食い違い、データ破損やクラッシュの原因となっていました。

このコミットは、この問題を解決するために、__packed__属性に加えて__gcc_struct__属性を併用することで、GCCに構造体のアライメントを正しく処理させるように強制します。

前提知識の解説

cgo

cgoはGo言語に組み込まれたツールで、GoプログラムからC言語の関数を呼び出したり、C言語のコード内でGoの関数を呼び出したりすることを可能にします。GoとCの相互運用性を実現するために、cgoはGoのソースコード内の特別なコメント(import "C"ブロック)を解析し、GoとCの間のブリッジコードを生成します。このブリッジコードは、Goの型とCの型を変換し、関数呼び出しを仲介します。

構造体のアライメントとパディング

コンピュータのメモリは、通常、バイト単位でアドレス指定されますが、CPUは特定のバイト境界(例えば、4バイトや8バイト)に配置されたデータに効率的にアクセスできます。この境界を「アライメント(alignment)」と呼びます。

構造体のメンバは、通常、その型のアライメント要件に従ってメモリに配置されます。例えば、4バイトの整数型は4バイト境界に、8バイトの長整数型は8バイト境界に配置されるのが一般的です。このアライメント要件を満たすために、コンパイラは構造体のメンバ間に「パディング(padding)」と呼ばれる未使用のバイトを挿入することがあります。

例:

struct MyStruct {
    char c;   // 1バイト
    int i;    // 4バイト (通常4バイト境界にアライメントされる)
};

この構造体では、char cの後に3バイトのパディングが挿入され、int iが4バイト境界に配置されることがあります。これにより、構造体全体のサイズは1 + 3 + 4 = 8バイトになる可能性があります。

__packed__属性

__packed__属性は、GCCなどのコンパイラ拡張機能で提供される属性で、構造体のメンバ間にパディングを挿入しないようにコンパイラに指示します。これにより、構造体は可能な限りコンパクトにメモリに配置されます。

例:

struct MyPackedStruct {
    char c;
    int i;
} __attribute__((__packed__));

この場合、char cの後にパディングは挿入されず、int ichar cの直後に配置されます。構造体全体のサイズは1 + 4 = 5バイトになります。

__packed__属性は、メモリ効率が重要な場合や、特定のデータフォーマット(例えば、ネットワークプロトコルのヘッダやハードウェアレジスタの構造)に厳密に合わせる必要がある場合によく使用されます。

__gcc_struct__属性

__gcc_struct__属性は、GCCの内部的な動作に関連する属性で、特定の構造体のアライメントとパディングの計算方法をGCCのデフォルトのルールに従わせるために使用されます。これは、特に他のコンパイラ拡張(例: __packed__)と組み合わせて使用される場合に、GCCが予期しない動作をしないようにするために役立ちます。

GCC PR52991

GCC PR52991は、GCCのバグトラッカーに登録された問題で、特定の条件下で__packed__属性を持つ構造体のアライメントが正しく計算されないというものです。このバグは、特にx86アーキテクチャ(386およびamd64)で顕著でした。このバグにより、__packed__属性が意図した通りに機能せず、構造体のメモリレイアウトが期待と異なる結果となり、GoとCの間のデータ交換で問題を引き起こしました。

Go issue #5603

Go issue #5603は、Goのバグトラッカーに登録された問題で、cgoを使用してCの関数を呼び出す際に、__packed__属性を持つ構造体の引数が正しく渡されないというものです。この問題は、GCC PR52991に起因しており、Go側で定義された構造体とC側でコンパイルされた構造体のメモリレイアウトが一致しないために発生しました。

技術的詳細

このコミットの核心は、cmd/cgo/out.goファイルにおける変更です。cgoは、GoのコードからCの関数を呼び出すためのラッパー関数を生成する際に、Goの構造体をCの構造体として表現します。この際、Goの構造体がpackedである場合、生成されるCの構造体にも__packed__属性を付与します。

しかし、GCC PR52991のバグにより、x86およびamd64アーキテクチャにおいて、__packed__属性だけではGCCが構造体のアライメントを正しく処理しないことが判明しました。このため、cgoは、これらのアーキテクチャ向けにCコードを生成する際に、__packed__属性に加えて__gcc_struct__属性も追加するように変更されました。

具体的には、out.gowriteOutputFunc関数内で、生成されるCの構造体ポインタの型定義に属性を追加する部分が変更されています。

変更前:

fmt.Fprintf(fgcc, "\t%s __attribute__((__packed__)) *a = v;\n", ctype)

変更後:

extraAttr := ""
if goarch == "amd64" || goarch == "386" {
	extraAttr = ", __gcc_struct__"
}
fmt.Fprintf(fgcc, "\t%s __attribute__((__packed__%v)) *a = v;\n", ctype, extraAttr)

この変更により、amd64または386アーキテクチャの場合にのみ、__gcc_struct__属性が追加されます。これにより、GCCは__packed__属性を持つ構造体のアライメントを正しく計算し、GoとCの間の構造体レイアウトの不一致が解消されます。

また、この修正を検証するために、misc/cgo/test/issue5603.goという新しいテストファイルが追加されました。このテストは、様々な引数の数のC関数を呼び出し、__packed__属性を持つ構造体が正しく扱われることを確認します。

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

misc/cgo/test/cgo_test.go

  • Test5603関数が追加され、新しいテストケースtest5603が実行されるようになりました。

misc/cgo/test/issue5603.go (新規ファイル)

  • Go issue #5603を再現し、修正を検証するための新しいテストファイルです。
  • Cのコードブロックで、issue5603expというlong long型の定数と、引数の数が異なる複数のissue5603fooN関数(void *型の引数を取る)が定義されています。これらの関数はすべてissue5603expを返します。
  • Goのtest5603関数では、これらのC関数を呼び出し、返された値が期待される値(issue5603exp)と一致するかどうかを検証します。これにより、cgoがCの関数呼び出しにおいて、特にポインタ引数を含む場合に、データが正しく渡され、返されることを確認します。

src/cmd/cgo/doc.go

  • __packed__属性を持つ構造体の例に、__gcc_struct__属性が追加された例が示されています。これは、cgoが生成するコードの変更を反映したドキュメントの更新です。

変更前:

		} __attribute__((__packed__)) *a = v;

変更後:

		} __attribute__((__packed__, __gcc_struct__)) *a = v;

src/cmd/cgo/out.go

  • writeOutputFunc関数内で、生成されるCの構造体ポインタの型定義に属性を追加するロジックが変更されました。
  • goarchamd64または386の場合にのみ、__gcc_struct__属性が__packed__属性に追加されるようになりました。

変更前:

	fmt.Fprintf(fgcc, "\t%s __attribute__((__packed__)) *a = v;\n", ctype)

変更後:

	// Use __gcc_struct__ to work around http://gcc.gnu.org/PR52991 on x86,
	// and http://golang.org/issue/5603.
	extraAttr := ""
	if goarch == "amd64" || goarch == "386" {
		extraAttr = ", __gcc_struct__"
	}
	fmt.Fprintf(fgcc, "\t%s __attribute__((__packed__%v)) *a = v;\n", ctype, extraAttr)

コアとなるコードの解説

このコミットの最も重要な変更は、src/cmd/cgo/out.goファイルにあります。

out.goは、cgoツールがGoのソースコードを解析し、Cのコードを生成する際の出力ロジックを扱います。特に、writeOutputFunc関数は、Goの関数がCの関数を呼び出すためのラッパー関数をC言語で生成する役割を担っています。

Goの構造体がCの関数に引数として渡される場合、cgoはGoの構造体をCの構造体として表現し、そのメモリレイアウトがCコンパイラによって正しく解釈されるようにする必要があります。Goの構造体が__packed__属性を持つべきであると判断された場合、cgoは生成するCの構造体にもこの属性を付与します。

しかし、前述のGCC PR52991のバグにより、x86およびamd64アーキテクチャでは、__packed__属性だけではGCCが構造体のアライメントを誤って計算してしまう問題がありました。このため、このコミットでは、goarch(Goのターゲットアーキテクチャ)がamd64または386である場合に限り、__packed__属性に加えて__gcc_struct__属性も追加するようにしました。

__gcc_struct__属性は、GCCに対して、その構造体のアライメントとパディングの計算を、GCCの標準的な構造体ルールに従って行うように強制します。これにより、__packed__属性と__gcc_struct__属性を併用することで、GCCはバグの影響を受けずに、意図した通りのコンパクトなメモリレイアウトを持つ構造体を生成できるようになります。結果として、GoとCの間の構造体レイアウトの不一致が解消され、cgoを介したデータ交換が正しく行われるようになります。

この修正は、特定のコンパイラのバグに対するワークアラウンドであり、Goのクロスコンパイルの性質上、ターゲットアーキテクチャに依存する条件分岐(if goarch == "amd64" || goarch == "386")が導入されています。これは、問題が特定のアーキテクチャのGCCに限定されていたためです。

関連リンク

参考にした情報源リンク