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

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

このコミットは、Goコンパイラのリンカ(cmd/ld)におけるDWARFデバッグ情報のエンコーディングに関する変更です。具体的には、配列の長さ(DW_AT_upper_bound属性)の表現方法を、固定長の1バイトから可変長整数(varint)エンコーディングに変更することで、より大きな配列長を正確に表現できるようにしています。

コミット

commit cddad8affe38c60bcec44d183eb6f8d6b43c18de
Author: Rob Pike <r@golang.org>
Date:   Tue Jul 15 21:18:18 2014 +0000

    cmd/ld: change DWARF encoding for array lengths
    They can be large, so use a varint encoding rather than only one byte.
    
    LGTM=iant, rsc
    R=rsc, iant
    CC=golang-codereviews
    https://golang.org/cl/113180043

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

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

元コミット内容

cmd/ld: change DWARF encoding for array lengths They can be large, so use a varint encoding rather than only one byte.

(日本語訳) cmd/ld: 配列長のDWARFエンコーディングを変更 配列長は大きくなる可能性があるため、1バイトではなく可変長整数エンコーディングを使用する。

変更の背景

Go言語を含む多くのプログラミング言語において、配列の長さは非常に大きくなる可能性があります。デバッグ情報フォーマットであるDWARFでは、プログラムの変数や型に関する情報が記述されます。配列の型情報には、その配列の要素数や上限値(DW_AT_upper_bound)が含まれることがあります。

このコミット以前は、Goコンパイラのリンカが生成するDWARF情報において、配列の長さ(DW_AT_upper_bound)を表現するためにDW_FORM_data1という形式が使用されていました。DW_FORM_data1は1バイトの固定長データ形式であり、表現できる値の範囲は0から255まで(符号なしの場合)に限定されます。

しかし、実際のプログラムでは255を超える要素を持つ配列が頻繁に利用されます。例えば、非常に大きなデータセットを扱う場合や、メモリ上に大量の要素を確保するようなケースです。このような大きな配列の長さが1バイトで表現できない場合、デバッガは正しい配列のサイズを認識できず、デバッグ時に誤った情報が表示されたり、配列の境界チェックが正しく機能しなかったりする問題が発生します。

この問題を解決するため、配列長をより大きな値まで表現できる可変長エンコーディングに変更する必要がありました。

前提知識の解説

DWARF (Debugging With Attributed Record Formats)

DWARFは、ソースコードレベルのデバッグを可能にするために、コンパイラやリンカによって生成されるデバッグ情報フォーマットです。実行可能ファイルに埋め込まれるか、別のファイルとして提供されます。デバッガはこの情報を使用して、ソースコードの行番号、変数名、型情報、関数呼び出しスタックなどを、実行中のバイナリコードと関連付けます。

DWARF情報は、主に以下の要素で構成されます。

  • Debugging Information Entry (DIE): プログラムの各エンティティ(関数、変数、型など)を表すレコード。
  • 属性 (Attribute): 各DIEに関連付けられた情報(例: 変数の名前、型、メモリ位置)。属性はDW_AT_プレフィックスで始まる定数で識別されます(例: DW_AT_name, DW_AT_type, DW_AT_upper_bound)。
  • フォーム (Form): 属性の値がどのようにエンコードされるかを指定する形式。DW_FORM_プレフィックスで始まる定数で識別されます(例: DW_FORM_data1, DW_FORM_udata, DW_FORM_ref_addr)。

DW_AT_upper_bound

DW_AT_upper_boundは、DWARFの属性の一つで、配列やサブレンジ型の最大インデックス値(または要素数-1)を示します。これにより、デバッガは配列のサイズや範囲を正確に把握できます。

DWARFフォームコード

  • DW_FORM_data1: 1バイトの固定長データ形式を表します。符号なし整数として0から255までの値を表現できます。
  • DW_FORM_udata: 可変長符号なし整数(Unsigned LEB128)形式を表します。LEB128(Little Endian Base 128)は、可変長のバイト数で任意のサイズの整数を効率的にエンコードできる形式です。これにより、小さな値は少ないバイト数で、大きな値はより多くのバイト数で表現され、データのサイズを最適化できます。

可変長整数エンコーディング (Varint / LEB128)

可変長整数エンコーディングは、数値の大きさに応じて必要なバイト数を動的に調整するエンコーディング方式です。LEB128はDWARFで広く使用されている可変長エンコーディングの一種です。

LEB128の基本的な仕組みは以下の通りです。

  1. 各バイトの最上位ビット(MSB)は、そのバイトが数値の最後のバイトであるかどうかを示すフラグとして使用されます。MSBが1の場合、後続のバイトが数値の一部であることを示し、MSBが0の場合、そのバイトが数値の最後のバイトであることを示します。
  2. 残りの7ビットが数値のデータとして使用されます。
  3. バイトはリトルエンディアン(最下位バイトが最初)の順序で格納されます。

例えば、256(16進数で0x100)をLEB128でエンコードする場合:

  • 256は7ビットで表現できないため、複数のバイトが必要になります。
  • 最初のバイトは、下位7ビットに256の最下位7ビット(0x00)を格納し、MSBを1に設定します(0x80)。
  • 残りの値(256 >> 7 = 2)を次のバイトに格納します。次のバイトは、下位7ビットに2を格納し、MSBを0に設定します(0x02)。
  • 結果として、256は0x80 0x02としてエンコードされます。

この方式により、小さな値は1バイトで、大きな値は2バイト以上で表現され、効率的なストレージが実現されます。

技術的詳細

このコミットの技術的な核心は、DWARFデバッグ情報における配列の長さの表現方法を、固定長のDW_FORM_data1から可変長のDW_FORM_udataに変更した点にあります。

Goコンパイラのリンカ(cmd/ld)は、コンパイルされたGoプログラムのバイナリを生成する際に、デバッグ情報も生成します。このデバッグ情報は、src/cmd/ld/dwarf.cファイルで定義されているDWARFの構造体や定数を使用して構築されます。

変更が行われたのは、DWAbbrev構造体内の定義です。DWAbbrevは、DWARFのAbbreviation Table(省略表)を定義するために使用されます。Abbreviation Tableは、DIE(Debugging Information Entry)の構造をテンプレートとして定義し、デバッグ情報のサイズを削減するために利用されます。

具体的には、DW_TAG_subrange_typeというタグを持つDIEの定義において、DW_AT_upper_bound属性のフォームが変更されました。

  • 変更前: DW_AT_upper_bound, DW_FORM_data1, これは、配列の上限値が1バイトの固定長データとしてエンコードされることを意味していました。これにより、配列の長さが255を超える場合に正確な値を表現できませんでした。
  • 変更後: DW_AT_upper_bound, DW_FORM_udata, これは、配列の上限値が可変長符号なし整数(Unsigned LEB128)としてエンコードされることを意味します。DW_FORM_udataを使用することで、配列の長さが255を超える場合でも、必要なバイト数だけを使用して正確な値を表現できるようになります。例えば、長さが1000の配列の場合、以前は表現できませんでしたが、DW_FORM_udataを使用すれば、LEB128エンコーディングによって正確な1000という値をデバッグ情報に含めることができます。

この変更により、Goプログラムが生成するデバッグ情報が、より大きな配列の長さを正確に反映できるようになり、デバッガがこれらの情報を正しく解釈できるようになります。これは、大規模なデータ構造を扱うGoアプリケーションのデバッグにおいて、非常に重要な改善となります。

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

変更はsrc/cmd/ld/dwarf.cファイルの一箇所のみです。

--- a/src/cmd/ld/dwarf.c
+++ b/src/cmd/ld/dwarf.c
@@ -266,7 +266,7 @@ static struct DWAbbrev {
 	\t\tDW_TAG_subrange_type, DW_CHILDREN_no,
 	\t\t// No name!
 	\t\tDW_AT_type,\t DW_FORM_ref_addr,
-	\t\tDW_AT_upper_bound, DW_FORM_data1,
+	\t\tDW_AT_upper_bound, DW_FORM_udata,
 	\t\t0, 0
 	\t},
 

コアとなるコードの解説

変更されたコードは、static struct DWAbbrevという構造体定義の一部です。この構造体は、DWARFのAbbreviation Tableのエントリを定義しています。

具体的に変更されたブロックは、DW_TAG_subrange_typeというDWARFタグに対応するAbbreviationエントリです。

  • DW_TAG_subrange_type: これは、配列のインデックス範囲や部分範囲を記述するために使用されるDWARFタグです。Go言語の配列型は、内部的にこのsubrange_typeとして表現されることがあります。
  • DW_CHILDREN_no: このDIEが子DIEを持たないことを示します。
  • DW_AT_type, DW_FORM_ref_addr: このsubrange_typeが参照する型(例えば、配列の要素型)を、アドレス参照形式で指定します。
  • DW_AT_upper_bound, DW_FORM_data1, から DW_AT_upper_bound, DW_FORM_udata, への変更:
    • DW_AT_upper_boundは、このサブレンジの最大値(配列の長さ-1)を定義する属性です。
    • 以前はDW_FORM_data1(1バイト固定長)でエンコードされていましたが、この変更によりDW_FORM_udata(可変長符号なし整数)でエンコードされるようになりました。

この変更は、Goコンパイラが生成するDWARFデバッグ情報において、配列の長さが255を超える場合でも正確に表現できるようにするためのものです。これにより、デバッガはGoプログラムの配列に関する情報をより正確に解釈し、デバッグ体験が向上します。

関連リンク

参考にした情報源リンク