[インデックス 18037] ファイルの概要
コミット
commit 8ce584c2aaf3cb3afbb6bf4b8fde340dbe38532a
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Tue Dec 17 22:40:13 2013 -0500
liblink: rewrite '\' in paths to '/' on windows
At least three Go tests rely on that (log, runtime/{pprof,debug}).
Fixes #6972.
Fixes #6974.
Fixes #6975.
R=alex.brainman, mattn.jp, rsc
CC=golang-dev
https://golang.org/cl/43150043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/8ce584c2aaf3cb3afbb6bf4b8fde340dbe38532a
元コミット内容
このコミットは、Go言語のリンカであるliblink
において、Windows環境でのパス区切り文字の扱いを修正するものです。具体的には、Windowsでパスを表す際に使用されるバックスラッシュ\
を、スラッシュ/
に書き換える処理を導入しています。これにより、log
、runtime/pprof
、runtime/debug
といったGoのテストがWindows上で正しく動作するようになります。
この変更は、以下の3つのIssueを修正することを目的としています。
- Fixes #6972.
- Fixes #6974.
- Fixes #6975.
特にIssue #6975は「runtime/pprof: TestBlockProfile fails on windows」というタイトルであり、このコミットの目的と直接的に関連しています。
変更の背景
Windowsオペレーティングシステムでは、ファイルパスの区切り文字としてバックスラッシュ\
が慣習的に使用されます。しかし、Unix系システム(Linux, macOSなど)ではスラッシュ/
が使用され、Go言語の内部処理や多くのプログラミング言語、WebのURLなどではスラッシュが標準的なパス区切り文字として扱われます。
Go言語のリンカliblink
は、コンパイルされたオブジェクトファイルやライブラリを結合して実行可能ファイルを生成する役割を担っています。この過程で、ソースファイルのパス情報などが埋め込まれることがあります。Windows環境でGoのテストを実行する際、リンカが生成するパス情報にバックスラッシュが含まれていると、Goのランタイムやテストフレームワークが期待するパス形式(スラッシュ区切り)と一致せず、テストが失敗する問題が発生していました。
具体的には、log
パッケージ、runtime/pprof
(プロファイリングツール)、runtime/debug
(デバッグ情報)に関連するテストが、Windows上でのパス区切り文字の不一致により失敗していたことが報告されています。これらのテストは、内部的にファイルパスを処理し、そのパスが特定の形式であることを期待しているため、Windows特有のバックスラッシュが問題を引き起こしていました。
このコミットは、リンカがオブジェクトファイルにパス情報を書き込む際に、Windows環境であればバックスラッシュをスラッシュに変換することで、このパスの不一致問題を解決し、GoのテストスイートがWindows上でも安定して動作するようにすることを目的としています。
前提知識の解説
1. ファイルパスの区切り文字
- Windows: 主にバックスラッシュ
\
を使用します(例:C:\Users\User\Documents\file.txt
)。ただし、多くのアプリケーションやAPIではスラッシュ/
も許容されます。 - Unix系 (Linux, macOSなど): スラッシュ
/
を使用します(例:/home/user/documents/file.txt
)。
2. Go言語のリンカ (liblink
)
Go言語のビルドプロセスにおいて、リンカは非常に重要な役割を果たします。Goのソースコードはまずコンパイラによってオブジェクトファイルに変換され、その後、これらのオブジェクトファイルと必要なライブラリがリンカによって結合され、最終的な実行可能ファイルが生成されます。この過程で、デバッグ情報やプロファイリング情報など、実行時に参照される可能性のあるファイルパスが埋め込まれることがあります。
3. runtime/pprof
と runtime/debug
runtime/pprof
: Goプログラムのプロファイリング情報(CPU使用率、メモリ割り当て、ゴルーチンスタックトレースなど)を収集・分析するためのパッケージです。プロファイリングデータには、どのソースファイルのどの行で処理が行われたかといった情報が含まれるため、ファイルパスの正確な表現が重要になります。runtime/debug
: Goプログラムのデバッグ情報(スタックトレース、ビルド情報など)を提供するパッケージです。こちらも、エラー発生時のスタックトレース表示などでファイルパスが使用されるため、パスの正規化が求められます。
4. Biobuf
Biobuf
は、Goのリンカ内部で使用されるバッファリングされたI/O構造体です。ファイルへの書き込みを効率的に行うために使用されます。このコミットでは、Biobuf
を介してファイルパスが書き込まれる際に、パスの変換処理が適用されます。
技術的詳細
このコミットの核心は、Windows環境においてリンカがファイルパスをオブジェクトファイルに書き込む際に、パス区切り文字を正規化することです。具体的には、src/liblink/objfile.c
ファイルに以下の変更が加えられています。
-
wrpath
関数の追加:- この新しい関数
wrpath
は、文字列としてパスを受け取り、それをBiobuf
に書き込む役割を担います。 - Windows環境 (
ctxt->windows
が真) であり、かつパス文字列にバックスラッシュ\
が含まれている場合 (strchr(p, '\\') != nil
) にのみ、変換処理が実行されます。 - 変換処理では、パス文字列を1文字ずつ走査し、バックスラッシュ
\
が出現した場合はスラッシュ/
に置き換えてBiobuf
に書き込みます。 - それ以外の場合(Windows環境でない、またはバックスラッシュが含まれていない場合)は、元の文字列をそのまま
wrstring
関数を使って書き込みます。
- この新しい関数
-
wrpathsym
関数の追加とwritesym
からの呼び出し:wrpathsym
関数は、シンボル(LSym
)のパス名(s->name
)をwrpath
関数を使って書き込むためのヘルパー関数です。シンボルにはファイルパスを表すものも含まれます。writesym
関数は、シンボル情報をオブジェクトファイルに書き込む主要な関数です。この関数内で、関数のファイル情報(pc->file[i]
)を書き込む際に、従来のwrsym
関数ではなく、新しく追加されたwrpathsym
関数を呼び出すように変更されています。これにより、ファイルパスが書き込まれる際にwrpath
による変換が適用されるようになります。
この変更により、Goのリンカが生成するオブジェクトファイル内のパス情報は、Windows上であってもスラッシュ区切りで統一されるため、Goランタイムやテストが期待する形式と一致し、関連するテストの失敗が解消されます。
コアとなるコードの変更箇所
src/liblink/objfile.c
--- a/src/liblink/objfile.c
+++ b/src/liblink/objfile.c
@@ -104,8 +104,10 @@
static void writesym(Link*, Biobuf*, LSym*);
static void wrint(Biobuf*, int64);
static void wrstring(Biobuf*, char*);
+static void wrpath(Link *, Biobuf*, char*);
static void wrdata(Biobuf*, void*, int);
static void wrsym(Biobuf*, LSym*);
+static void wrpathsym(Link *ctxt, Biobuf *b, LSym *s);
static void readsym(Link*, Biobuf*, char*, char*);
static int64 rdint(Biobuf*);
@@ -165,7 +167,7 @@ linkwriteobj(Link *ctxt, Biobuf *b)\n \n if(p->as == ctxt->arch->AGLOBL) {\n s = p->from.sym;\
-if(s->size) print("duplicate %P\n", p);\
+\t\t\t\tif(s->size) print("duplicate %P\n", p);\
if(data == nil)\
data = s;\
else\
@@ -359,7 +361,7 @@ writesym(Link *ctxt, Biobuf *b, LSym *s)\
wrint(b, pc->funcdataoff[i]);\
wrint(b, pc->nfile);\
for(i=0; i<pc->nfile; i++)\
-\t\t\twrsym(b, pc->file[i]);\
+\t\t\twrpathsym(ctxt, b, pc->file[i]);\
}\
}\
\
@@ -385,6 +387,23 @@ wrstring(Biobuf *b, char *s)\
wrdata(b, s, strlen(s));\
}\
\
+// wrpath writes a path just like a string, but on windows, it\
+// translates '\\' to '/' in the process.\
+static void\
+wrpath(Link *ctxt, Biobuf *b, char *p)\
+{\
+\tint i, n;\
+\tif (!ctxt->windows || strchr(p, '\\\\') == nil) {\
+\t\twrstring(b, p);\
+\t\treturn;\
+\t} else {\
+\t\tn = strlen(p);\
+\t\twrint(b, n);\
+\t\tfor (i = 0; i < n; i++)\
+\t\t\tBputc(b, p[i] == '\\\\' ? '/' : p[i]);\
+\t}\
+}\
+\
static void\
wrdata(Biobuf *b, void *v, int n)\
{\
@@ -392,6 +411,18 @@ wrdata(Biobuf *b, void *v, int n)\
Bwrite(b, v, n);\
}\
\
+static void\
+wrpathsym(Link *ctxt, Biobuf *b, LSym *s)\
+{\\
+\tif(s == nil) {\
+\t\twrint(b, 0);\
+\t\twrint(b, 0);\
+\t\treturn;\
+\t}\
+\twrpath(ctxt, b, s->name);\
+\twrint(b, s->version);\
+}\
+\
static void\
wrsym(Biobuf *b, LSym *s)\
{\
コアとなるコードの解説
wrpath
関数の追加
// wrpath writes a path just like a string, but on windows, it
// translates '\\' to '/' in the process.
static void
wrpath(Link *ctxt, Biobuf *b, char *p)
{
int i, n;
if (!ctxt->windows || strchr(p, '\\\\') == nil) {
wrstring(b, p);
return;
} else {
n = strlen(p);
wrint(b, n);
for (i = 0; i < n; i++)
Bputc(b, p[i] == '\\\\' ? '/' : p[i]);
}
}
- この関数は、パス文字列
p
をBiobuf
b
に書き込むためのものです。 ctxt->windows
は、現在のビルド環境がWindowsであるかどうかを示すコンテキスト情報です。strchr(p, '\\') == nil
は、パス文字列p
の中にバックスラッシュ\
が含まれていないかをチェックします。if (!ctxt->windows || strchr(p, '\\\\') == nil)
:- もしWindows環境でない場合、またはパスにバックスラッシュが含まれていない場合は、変換の必要がないため、既存の
wrstring
関数を使ってそのまま文字列を書き込み、関数を終了します。
- もしWindows環境でない場合、またはパスにバックスラッシュが含まれていない場合は、変換の必要がないため、既存の
else
:- Windows環境であり、かつパスにバックスラッシュが含まれている場合、変換処理を行います。
n = strlen(p); wrint(b, n);
: まず、パスの長さを書き込みます。これは、Goのオブジェクトファイルフォーマットで文字列の長さを先に記述する慣習に従っています。for (i = 0; i < n; i++) Bputc(b, p[i] == '\\\\' ? '/' : p[i]);
: パス文字列の各文字をループで処理します。もし文字がバックスラッシュ\
であればスラッシュ/
に変換し、そうでなければそのままの文字をBiobuf
に書き込みます。Bputc
はBiobuf
に1文字を書き込む関数です。
wrpathsym
関数の追加
static void
wrpathsym(Link *ctxt, Biobuf *b, LSym *s)
{
if(s == nil) {
wrint(b, 0);
wrint(b, 0);
return;
}
wrpath(ctxt, b, s->name);
wrint(b, s->version);
}
- この関数は、
LSym
構造体(リンカシンボル)の情報を書き込むためのヘルパーです。 if(s == nil)
: シンボルがnil
の場合は、長さ0の文字列として処理します。wrpath(ctxt, b, s->name);
: シンボルの名前(s->name
)をwrpath
関数を使って書き込みます。これにより、シンボル名がファイルパスである場合に、Windowsでのパス区切り文字変換が適用されます。wrint(b, s->version);
: シンボルのバージョン情報を書き込みます。
writesym
関数の変更
@@ -359,7 +361,7 @@ writesym(Link *ctxt, Biobuf *b, LSym *s)\
wrint(b, pc->funcdataoff[i]);\
wrint(b, pc->nfile);\
for(i=0; i<pc->nfile; i++)\
-\t\t\twrsym(b, pc->file[i]);\
+\t\t\twrpathsym(ctxt, b, pc->file[i]);\
}\
}\
writesym
関数は、シンボル(特に関数シンボル)のデバッグ情報の一部として、関連するファイルパスを書き込みます。- 変更前は
wrsym(b, pc->file[i]);
を呼び出していましたが、変更後はwrpathsym(ctxt, b, pc->file[i]);
を呼び出すように修正されました。 pc->file[i]
は、関数に関連するソースファイルのシンボルを表します。このシンボル名が実際のファイルパスとなるため、wrpathsym
を介してwrpath
が呼び出されることで、Windows環境でのパス区切り文字の正規化が実現されます。
これらの変更により、GoのリンカはWindows環境で生成されるオブジェクトファイル内のパス情報を、Goランタイムが期待するスラッシュ区切り形式に統一し、テストの安定性を向上させます。
関連リンク
- Go言語のIssueトラッカー: https://github.com/golang/go/issues
- Go言語のコードレビューシステム (Gerrit): https://golang.org/cl/43150043
参考にした情報源リンク
- Issue 6975: runtime/pprof: TestBlockProfile fails on windows (Web検索で確認できた関連Issue)
- Go言語のソースコード (特に
src/liblink/objfile.c
の変更履歴) - Go言語のドキュメント (リンカ、
runtime/pprof
、runtime/debug
に関する情報) - WindowsおよびUnix系OSのファイルパスに関する一般的な知識