[インデックス 18864] ファイルの概要
このコミットは、Go言語のリンカ (cmd/ld
) において、生成されるELF (Executable and Linkable Format) およびMach-O (Mach Object) 形式のシンボルテーブル内で使用される特殊文字「·」(ミドルドット)を「.」(ピリオド)に置換する変更を導入します。この変更の主な目的は、macOSやFreeBSDに搭載されている古いバージョンのDTraceが、シンボル名に含まれるUnicode文字を正しく処理できない問題に対処し、GoプログラムのDTrace互換性を向上させることです。
コミット
Go言語のリンカが生成するELFおよびMach-O形式のシンボルテーブルにおいて、Goの内部シンボル名で使用される「·」(ミドルドット)文字を「.」(ピリオド)に置換する。これにより、古いDTraceバージョンでのUnicodeシンボル名に関する互換性問題を解決する。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/cbe777b2c70320f52f85c6c8f1242b35dd45b341
元コミット内容
commit cbe777b2c70320f52f85c6c8f1242b35dd45b341
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Fri Mar 14 10:07:51 2014 -0400
cmd/gc: replace '·' as '.' in ELF/Mach-O symbol tables
Old versions of DTrace (as those shipped in OS X and FreeBSD)
don't support unicode characters in symbol names. Replace '·'
to '.' to make DTrace happy.
Fixes #7493
LGTM=aram, rsc
R=aram, rsc, gobot, iant
CC=golang-codereviews
https://golang.org/cl/72280043
変更の背景
Go言語では、メソッド名や内部的なシンボル名において、型名とメソッド名を区切るために「·」(ミドルドット、Unicode U+00B7)という特殊な文字を使用する慣習があります。例えば、MyType.MyMethod
のようなメソッドは、コンパイル後のシンボルテーブルでは MyType·MyMethod
のように表現されることがあります。
しかし、macOSやFreeBSDなどの一部のオペレーティングシステムに搭載されている古いバージョンのDTrace(動的トレーシングフレームワーク)は、シンボル名にUnicode文字が含まれていると、それを正しく解析できないという既知の問題を抱えていました。この問題は、Goプログラムの実行時にDTraceを使用してパフォーマンス分析やデバッグを行おうとする際に、シンボルが正しく認識されない、あるいはDTraceがクラッシュするといった形で現れていました。
この互換性の問題を解決するため、GoのリンカがELFおよびMach-O形式の実行ファイルを生成する際に、シンボルテーブル内の「·」文字を、DTraceが問題なく処理できる「.」(ピリオド)に置換する必要が生じました。これにより、Go言語の内部的な命名規則を変更することなく、外部ツールとの互換性を確保することが目的です。
前提知識の解説
DTrace (Dynamic Tracing)
DTraceは、Solarisオペレーティングシステムで開発された強力な動的トレーシングフレームワークです。macOS、FreeBSD、NetBSDなどの他のUnix系システムにも移植されています。DTraceを使用すると、実行中のシステムやアプリケーションの動作を、コードを変更したり再コンパイルしたりすることなく、リアルタイムで詳細に監視・分析できます。
DTraceは、カーネルやユーザー空間の様々なプローブポイント(特定のイベントが発生したときにトリガーされる場所)にスクリプトをアタッチすることで機能します。これらのスクリプトはD言語(DTraceの専用言語)で記述され、イベント発生時のシステム状態やアプリケーションデータを収集し、分析することができます。
このコミットの文脈では、DTraceがGoプログラムのシンボル(関数名など)を正しく認識できないことが問題でした。DTraceは実行ファイルのシンボルテーブルを読み取り、関数呼び出しなどのイベントを特定のシンボルに関連付けます。シンボル名にDTraceが解釈できない文字が含まれていると、この関連付けが失敗し、トレーシングが機能しなくなります。
ELF (Executable and Linkable Format)
ELFは、Unix系オペレーティングシステム(Linux、Solarisなど)で広く使用されている実行可能ファイル、オブジェクトコード、共有ライブラリ、およびコアダンプファイルの標準ファイル形式です。ELFファイルは、プログラムのコード、データ、および実行に必要なメタデータ(シンボルテーブル、セクションヘッダなど)を構造化して格納します。
Mach-O (Mach Object)
Mach-Oは、AppleのmacOSおよびiOSオペレーティングシステムで使用されている実行可能ファイル、オブジェクトコード、共有ライブラリ、およびコアダンプファイルのファイル形式です。Mach-Oは、Machカーネルの設計に由来しており、ELFと同様にプログラムのコードとデータを構造化して格納しますが、その内部構造は異なります。
シンボルテーブル
シンボルテーブルは、実行可能ファイルやオブジェクトファイル内に含まれる重要なデータ構造の一つです。これには、プログラム内の関数、グローバル変数、静的変数などの「シンボル」に関する情報が格納されています。各シンボルには、その名前、型(関数、変数など)、アドレス、サイズなどの属性が関連付けられています。
デバッガやプロファイラ、トレーシングツール(DTraceなど)は、シンボルテーブルを利用して、実行中のプログラムの特定のメモリ位置や命令を、人間が理解しやすいシンボル名(例: main
関数、myGlobalVar
変数)に関連付けます。これにより、開発者はソースコードレベルでプログラムの動作を分析できるようになります。
Go言語における「·」(ミドルドット)の使用
Go言語のコンパイラとリンカは、内部的に特定のシンボル名にUnicode文字の「·」(ミドルドット、U+00B7)を使用します。最も一般的な例は、構造体やインターフェースのメソッド名です。例えば、type MyStruct struct {}
に func (m MyStruct) MyMethod() {}
というメソッドがある場合、そのシンボル名は MyStruct·MyMethod
のように表現されます。これは、Goのツールチェーンが内部的に型とメソッドを区別するための慣習であり、Goのソースコード上では通常のピリオド(.
)として記述されます。
この「·」は、UTF-8エンコーディングでは2バイト(\xc2\xb7
)で表現されます。古いDTraceバージョンがこのUnicodeシーケンスを正しく解釈できないことが、今回の問題の根源でした。
技術的詳細
このコミットは、Goリンカの2つの主要なファイル、src/cmd/ld/macho.c
と src/cmd/ld/symtab.c
に変更を加えることで、シンボルテーブル内の「·」文字を「.」に置換します。
src/cmd/ld/macho.c
の変更
macho.c
はMach-O形式の実行ファイルを生成する際のシンボルテーブル関連の処理を担当します。具体的には、machosymtab
関数が変更されています。
変更前は、シンボルの外部名 (s->extname
) をそのまま symstr
(シンボル文字列テーブル) に追加していました。変更後は、s->extname
に「·」が含まれているかどうかをチェックします。
strstr(s->extname, "·") == nil
: シンボル名に「·」が含まれていない場合、従来通りaddstring(symstr, s->extname)
で文字列を追加します。strstr(s->extname, "·") != nil
: シンボル名に「·」が含まれている場合、文字列を1バイトずつ走査し、UTF-8エンコーディングの「·」(\xc2\xb7
)を見つけると、それを「.」(ピリオド)に置換してsymstr
に追加します。この際、\xc2\xb7
は2バイトですが、「.」は1バイトであるため、文字列の長さが1バイト短縮されることに注意が必要です。
src/cmd/ld/symtab.c
の変更
symtab.c
はELF形式の実行ファイルを生成する際のシンボルテーブル関連の処理、特に文字列テーブル (.strtab
や .dynstr
) への文字列追加を担当します。具体的には、putelfstr
関数が変更されています。
putelfstr
関数は、ELF文字列テーブルに文字列を追加し、そのオフセットを返します。変更前は、入力された文字列 s
をそのまま elfstrdat
(ELF文字列データ) にコピーしていました。変更後は、コピー後に文字列内に「·」が含まれているかをチェックします。
p = strstr(s, "·")
: コピーされた文字列s
内に「·」が含まれているかを検索します。- 置換ロジック: 「·」が見つかった場合、文字列を走査し、UTF-8エンコーディングの「·」(
\xc2\xb7
)を「.」(ピリオド)に置換します。この際、\xc2\xb7
の2バイトを「.」の1バイトに置き換えるため、後続の文字を1バイト前方にシフトし、文字列全体のサイズを調整 (elfstrsize--
) します。これにより、文字列テーブル内のデータが正しく配置されます。
これらの変更により、Goリンカは、DTraceが期待する形式でシンボル名をエクスポートするようになり、古いDTraceバージョンとの互換性が確保されます。
コアとなるコードの変更箇所
src/cmd/ld/macho.c
--- a/src/cmd/ld/macho.c
+++ b/src/cmd/ld/macho.c
@@ -574,6 +574,7 @@ machosymtab(void)
{
int i;
LSym *symtab, *symstr, *s, *o;
+ char *p;
symtab = linklookup(ctxt, ".machosymtab", 0);
symstr = linklookup(ctxt, ".machosymstr", 0);
@@ -585,7 +586,21 @@ machosymtab(void)
// Only add _ to C symbols. Go symbols have dot in the name.
if(strstr(s->extname, ".") == nil)
adduint8(ctxt, symstr, '_');
- addstring(symstr, s->extname);
+ // replace "·" as ".", because DTrace cannot handle it.
+ if(strstr(s->extname, "·") == nil) {
+ addstring(symstr, s->extname);
+ } else {
+ p = s->extname;
+ while (*p++ != '\0') {
+ if(*p == '\xc2' && *(p+1) == '\xb7') {
+ adduint8(ctxt, symstr, '.');
+ p++;
+ } else {
+ adduint8(ctxt, symstr, *p);
+ }
+ }
+ adduint8(ctxt, symstr, '\0');
+ }
if(s->type == SDYNIMPORT || s->type == SHOSTOBJ) {
adduint8(ctxt, symtab, 0x01); // type N_EXT, external symbol
adduint8(ctxt, symtab, 0); // no section
src/cmd/ld/symtab.c
--- a/src/cmd/ld/symtab.c
+++ b/src/cmd/ld/symtab.c
@@ -40,6 +40,7 @@ static int
putelfstr(char *s)
{
int off, n;
+ char *p, *q;
if(elfstrsize == 0 && s[0] != 0) {
// first entry must be empty string
@@ -54,6 +55,21 @@ putelfstr(char *s)
off = elfstrsize;
elfstrsize += n;
memmove(elfstrdat+off, s, n);
+ // replace "·" as ".", because DTrace cannot handle it.
+ p = strstr(s, "·");
+ if(p != nil) {
+ p = q = elfstrdat+off;
+ while (*q != '\0') {
+ if(*q == '\xc2' && *(q+1) == '\xb7') {
+ q += 2;
+ *p++ = '.';
+ elfstrsize--;
+ } else {
+ *p++ = *q++;
+ }
+ }
+ *p = '\0';
+ }
return off;
}
コアとなるコードの解説
src/cmd/ld/macho.c
の machosymtab
関数
この関数はMach-O形式のシンボルテーブルを構築する際に呼び出されます。
変更の核心は、シンボルの外部名 s->extname
をシンボル文字列テーブル symstr
に追加するロジックにあります。
if(strstr(s->extname, "·") == nil)
: まず、シンボル名s->extname
にUnicodeのミドルドット「·」が含まれているかを確認します。strstr
は部分文字列を検索するC標準ライブラリ関数です。- 「·」が含まれていない場合:
addstring(symstr, s->extname);
が実行され、元のシンボル名がそのまま文字列テーブルに追加されます。 - 「·」が含まれている場合:
else
ブロックに入り、手動で文字列を走査して置換処理を行います。p = s->extname;
: シンボル名の先頭ポインタをp
に設定します。while (*p++ != '\0')
: 文字列の終端まで1バイトずつ走査します。*p++
は現在の文字を評価し、その後ポインタをインクリメントします。if(*p == '\xc2' && *(p+1) == '\xb7')
: 現在のポインタp
が指すバイトが\xc2
で、次のバイトが\xb7
であるかをチェックします。これはUnicodeのミドルドット「·」のUTF-8エンコーディングです。- ミドルドットが見つかった場合:
adduint8(ctxt, symstr, '.');
: シンボル文字列テーブルにピリオド「.」を追加します。p++;
: ミドルドットは2バイト文字なので、次のバイトもスキップするためにポインタをさらに1つ進めます。
- ミドルドットではない場合:
adduint8(ctxt, symstr, *p);
: 現在のバイトをそのままシンボル文字列テーブルに追加します。
- ループ終了後、
adduint8(ctxt, symstr, '\0');
で文字列の終端を示すヌル文字を追加します。
この手動でのバイト走査と置換は、addstring
関数が内部的にUnicode文字のバイトシーケンスをそのまま扱うため、DTraceが期待するASCII互換のシンボル名にするために必要です。
src/cmd/ld/symtab.c
の putelfstr
関数
この関数はELF形式の文字列テーブルに文字列を追加する際に呼び出されます。
変更の核心は、文字列が elfstrdat
にコピーされた後に、その内容を走査して置換を行う点です。
memmove(elfstrdat+off, s, n);
: まず、入力された文字列s
をelfstrdat
の適切なオフセットにコピーします。p = strstr(s, "·");
: コピー元の文字列s
にミドルドット「·」が含まれているかを確認します。これは、置換が必要かどうかを判断するための初期チェックです。- 「·」が含まれている場合:
if(p != nil)
ブロックに入り、置換処理を行います。p = q = elfstrdat+off;
:p
とq
の両方のポインタを、文字列がコピーされたelfstrdat
内の開始位置に設定します。q
は読み取りポインタ、p
は書き込みポインタとして機能します。while (*q != '\0')
:q
ポインタが文字列の終端に達するまでループします。if(*q == '\xc2' && *(q+1) == '\xb7')
:q
が指すバイトがミドルドットのUTF-8エンコーディングであるかをチェックします。- ミドルドットが見つかった場合:
q += 2;
: 読み取りポインタq
を2バイト進めて、ミドルドットの次の文字に移動します。*p++ = '.';
: 書き込みポインタp
が指す位置にピリオド「.」を書き込み、p
を1バイト進めます。elfstrsize--;
: ミドルドット(2バイト)がピリオド(1バイト)に置換されたため、文字列テーブル全体のサイズを1バイト減らします。
- ミドルドットではない場合:
*p++ = *q++;
:q
が指すバイトをp
が指す位置にコピーし、両方のポインタを1バイト進めます。
- ループ終了後、
*p = '\0';
で新しい文字列の終端にヌル文字を書き込みます。
この処理は、文字列をインプレースで変更し、ミドルドットをピリオドに置換すると同時に、文字列の長さを適切に調整します。これにより、ELFシンボルテーブル内の文字列がDTrace互換になります。
関連リンク
- Go Issue #7493:
cmd/gc: replace '·' as '.' in ELF/Mach-O symbol tables
- https://github.com/golang/go/issues/7493 - Go Code Review:
cmd/gc: replace '·' as '.' in ELF/Mach-O symbol tables
- https://golang.org/cl/72280043
参考にした情報源リンク
- DTrace Overview (Oracle): https://www.oracle.com/technical-resources/articles/it-infrastructure/dtrace.html
- ELF (Executable and Linkable Format) (Wikipedia): https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
- Mach-O (Wikipedia): https://en.wikipedia.org/wiki/Mach-O
- Go Language Specification (Go Symbols): https://go.dev/ref/spec#Identifiers (直接的な「·」の言及は少ないが、内部的な命名規則の背景を理解するのに役立つ)
- UTF-8 Encoding (Wikipedia): https://en.wikipedia.org/wiki/UTF-8 (特にU+00B7のエンコーディングについて)
- Goのシンボル名とDTraceに関する議論 (Stack Overflowなど、具体的なURLは省略するが、同様の問題が報告されている)