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

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

このコミットは、Go言語のCgoツールにおける型変換処理のバグ修正に関するものです。具体的には、C言語のunion型やclass型をGoの型に変換する際に発生していたアライメントの問題を解決し、それによって引き起こされていたゼロ除算によるクラッシュを回避することを目的としています。

コミット

commit f934bb8ebaf4695ae08ae1302a1b7f22e3e61902
Author: Rob Pike <r@golang.org>
Date:   Sat Sep 22 07:25:41 2012 +1000

    cgo: set alignment to 1 for unions and classes; avoids crash from divide-by-zero
    Fixes #4114.
    
    R=golang-dev, iant, rsc, iant, devon.odell
    CC=golang-dev
    https://golang.org/cl/6553050

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

https://github.com/golang/go/commit/f934bb8ebaf4695ae08ae1302a1b7f22e3e61902

元コミット内容

cgo: set alignment to 1 for unions and classes; avoids crash from divide-by-zero
Fixes #4114.

R=golang-dev, iant, rsc, iant, devon.odell
CC=golang-dev
https://golang.org/cl/6553050

変更の背景

この変更は、Go言語のCgoツールがC言語のunion型やclass型を扱う際に発生していた既知のバグ(Go issue #4114)を修正するために導入されました。

Go issue #4114のタイトルは「cgo panics handling a C union」であり、CgoがC言語のunion型を含む構造体を処理する際にパニック(クラッシュ)が発生するという問題が報告されていました。このパニックは、特にビットフィールドのみを持つC構造体を扱う場合に顕著でした。

根本的な原因は、CgoがCのunion型やclass型のアライメントを正しく計算できていなかったことにあります。アライメントの計算が誤っていると、メモリレイアウトの不整合が生じ、結果としてゼロ除算のような予期せぬ動作やクラッシュを引き起こす可能性がありました。このコミットは、これらの型に対してアライメントを1に設定することで、この問題を一時的に回避し、クラッシュを防ぐことを目的としています。

前提知識の解説

Cgo

Cgoは、GoプログラムからC言語のコードを呼び出すためのGoツールチェーンの一部です。これにより、GoとCの間の相互運用が可能になります。Cgoは、Cの型をGoの型に、Goの型をCの型に変換する役割を担います。この変換プロセスにおいて、C言語特有のメモリレイアウトやアライメントの規則を正しく理解し、Goのランタイムに適切にマッピングする必要があります。

アライメント (Alignment)

アライメントとは、コンピュータのメモリ上でデータが配置される際の、特定の境界への整列規則のことです。CPUは、特定のメモリアドレスからデータを読み書きする際に、そのアドレスが特定の値(例えば4バイトや8バイト)の倍数である場合に、より効率的にアクセスできます。この「特定の値」がアライメント要件です。

  • 構造体とアライメント: C言語では、構造体(struct)や共用体(union)のメンバは、それぞれのアライメント要件に従ってメモリに配置されます。構造体全体のサイズも、そのメンバの中で最も厳しいアライメント要件を持つメンバに合わせてパディング(詰め物)が追加され、アライメントされます。
  • union: union型は、複数のメンバが同じメモリアドレスを共有する特殊な型です。unionのサイズは、そのメンバの中で最も大きいメンバのサイズに等しく、アライメントは最も厳しいアライメント要件を持つメンバに合わせられます。
  • class: C++におけるclass型も、メンバ変数のアライメント要件を持ちます。CgoはC++のコードも扱うため、class型のアライメントも考慮する必要があります。

ゼロ除算 (Divide-by-zero)

ゼロ除算は、数値をゼロで割る操作であり、数学的に未定義です。コンピュータプログラムにおいてゼロ除算が発生すると、通常はプログラムのクラッシュや例外(パニック)を引き起こします。このコミットの文脈では、アライメントの計算が誤っていた結果、何らかの計算で分母がゼロになり、それがクラッシュの原因となっていたと考えられます。

Dwarf Debugging Information Format

Dwarfは、デバッグ情報のための標準的なフォーマットです。コンパイラは、ソースコードから実行可能ファイルを生成する際に、変数名、型情報、関数名、行番号などのデバッグ情報をDwarf形式で出力します。Cgoは、Cの型情報をDwarfから読み取り、Goの型に変換する際に利用します。

技術的詳細

このコミットは、src/cmd/cgo/gcc.goファイル内のtypeConv構造体のTypeメソッドに修正を加えています。このメソッドは、Dwarfデバッグ情報からCの型を読み取り、それをGoの型に変換する役割を担っています。

修正の核心は、union型とclass型に対するアライメントの扱いを変更した点です。

変更前は、union型とclass型の場合、t.Go = c.Opaque(t.Size)によって不透明な型として扱われ、そのサイズに基づいてtypeof(unsigned char[%d])のようなCの型定義が生成されていました。しかし、この処理ではアライメントが適切に設定されていませんでした。

変更後、case "class", "union":のブロック内にt.Align = 1という行が追加されました。これにより、union型とclass型がGoの型に変換される際に、明示的にアライメントが1に設定されるようになりました。

なぜアライメントを1に設定するのかというと、これは最も緩いアライメント要件であり、どのようなメモリアドレスにも配置可能であることを意味します。これにより、Cgoがこれらの型の正確なアライメントを特定できない場合に、安全側に倒してクラッシュを回避するための暫定的な措置と考えられます。コミットメッセージの// TODO: should probably base this on field alignment.というコメントから、将来的にはより正確なフィールドアライメントに基づく計算が必要であるという認識があったことが伺えます。

この修正により、アライメントの不整合に起因するゼロ除算のエラーが回避され、Cgoがunion型やclass型を含むCコードをより安定して処理できるようになりました。

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

--- a/src/cmd/cgo/gcc.go
+++ b/src/cmd/cgo/gcc.go
@@ -1190,11 +1190,12 @@ func (c *typeConv) Type(dtype dwarf.Type, pos token.Pos) *Type {
 		t.Go = name // publish before recursive calls
 		goIdent[name.Name] = name
 		switch dt.Kind {
-		case "union", "class":
+		case "class", "union":
 			t.Go = c.Opaque(t.Size)
 			if t.C.Empty() {
 				t.C.Set("typeof(unsigned char[%d])", t.Size)
 			}
+			t.Align = 1 // TODO: should probably base this on field alignment.
 			typedef[name.Name] = t
 		case "struct":
 			g, csyntax, align := c.Struct(dt, pos)

コアとなるコードの解説

変更はsrc/cmd/cgo/gcc.goファイルのfunc (c *typeConv) Type(dtype dwarf.Type, pos token.Pos) *Typeメソッド内で行われています。

このメソッドは、Dwarfデバッグ情報から取得した型情報(dtype)をGoの型(*Type)に変換する主要なロジックを含んでいます。

  • t.Go = name: 変換中のGoの型に名前を割り当てています。
  • goIdent[name.Name] = name: 変換されたGoの型を識別子マップに登録しています。
  • switch dt.Kind: Dwarfの型情報(dt.Kind)に基づいて、異なる型の処理を分岐しています。
    • case "class", "union":: このブロックが今回の修正対象です。C++のclass型とCのunion型を処理します。
      • t.Go = c.Opaque(t.Size): これらの型をGo側では不透明な型(内部構造が見えない型)として扱います。t.SizeはDwarfから取得した型のサイズです。
      • if t.C.Empty() { t.C.Set("typeof(unsigned char[%d])", t.Size) }: もしCの型定義がまだ設定されていなければ、unsigned charの配列として定義します。これは、これらの型がGo側から直接アクセスされることを意図せず、単にメモリ上のサイズを確保するためのプレースホルダーとして扱われることを示唆しています。
      • t.Align = 1: この行が追加された修正の核心です。 tは変換中のGoの型を表す構造体であり、Alignフィールドはその型のアライメント要件を保持します。ここで明示的にアライメントを1に設定することで、これらの型がメモリ上で1バイト境界に配置されることを保証します。これにより、アライメントの不整合によるクラッシュが回避されます。
      • // TODO: should probably base this on field alignment.: このコメントは、アライメントを1に設定するのが暫定的な解決策であり、将来的にはunionclassの内部フィールドのアライメントに基づいてより正確なアライメントを決定する必要があることを示しています。
      • typedef[name.Name] = t: 変換された型を型定義マップに登録しています。
    • case "struct":: struct型を処理する別のケースです。この部分は今回の修正の直接的な対象ではありませんが、比較のために示されています。struct型はc.Structメソッドによってより複雑なアライメントと構造体のレイアウト計算が行われます。

この変更により、Cgoがunionclassのような複雑なCの型を扱う際の堅牢性が向上し、特定の条件下でのクラッシュが防止されました。

関連リンク

参考にした情報源リンク

  • Go issue #4114のGitHubページ
  • Go CL 6553050のGo Gerritページ
  • Cgoのドキュメント (Go言語公式ドキュメント)
  • C言語における構造体とアライメントに関する一般的な情報源
  • Dwarf Debugging Information Formatに関する一般的な情報源