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

[インデックス 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でパスを表す際に使用されるバックスラッシュ\を、スラッシュ/に書き換える処理を導入しています。これにより、logruntime/pprofruntime/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)。

Go言語のビルドプロセスにおいて、リンカは非常に重要な役割を果たします。Goのソースコードはまずコンパイラによってオブジェクトファイルに変換され、その後、これらのオブジェクトファイルと必要なライブラリがリンカによって結合され、最終的な実行可能ファイルが生成されます。この過程で、デバッグ情報やプロファイリング情報など、実行時に参照される可能性のあるファイルパスが埋め込まれることがあります。

3. runtime/pprofruntime/debug

  • runtime/pprof: Goプログラムのプロファイリング情報(CPU使用率、メモリ割り当て、ゴルーチンスタックトレースなど)を収集・分析するためのパッケージです。プロファイリングデータには、どのソースファイルのどの行で処理が行われたかといった情報が含まれるため、ファイルパスの正確な表現が重要になります。
  • runtime/debug: Goプログラムのデバッグ情報(スタックトレース、ビルド情報など)を提供するパッケージです。こちらも、エラー発生時のスタックトレース表示などでファイルパスが使用されるため、パスの正規化が求められます。

4. Biobuf

Biobufは、Goのリンカ内部で使用されるバッファリングされたI/O構造体です。ファイルへの書き込みを効率的に行うために使用されます。このコミットでは、Biobufを介してファイルパスが書き込まれる際に、パスの変換処理が適用されます。

技術的詳細

このコミットの核心は、Windows環境においてリンカがファイルパスをオブジェクトファイルに書き込む際に、パス区切り文字を正規化することです。具体的には、src/liblink/objfile.cファイルに以下の変更が加えられています。

  1. wrpath関数の追加:

    • この新しい関数wrpathは、文字列としてパスを受け取り、それをBiobufに書き込む役割を担います。
    • Windows環境 (ctxt->windowsが真) であり、かつパス文字列にバックスラッシュ\が含まれている場合 (strchr(p, '\\') != nil) にのみ、変換処理が実行されます。
    • 変換処理では、パス文字列を1文字ずつ走査し、バックスラッシュ\が出現した場合はスラッシュ/に置き換えてBiobufに書き込みます。
    • それ以外の場合(Windows環境でない、またはバックスラッシュが含まれていない場合)は、元の文字列をそのままwrstring関数を使って書き込みます。
  2. 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]);
	}
}
  • この関数は、パス文字列pBiobuf bに書き込むためのものです。
  • ctxt->windowsは、現在のビルド環境がWindowsであるかどうかを示すコンテキスト情報です。
  • strchr(p, '\\') == nilは、パス文字列pの中にバックスラッシュ\が含まれていないかをチェックします。
  • if (!ctxt->windows || strchr(p, '\\\\') == nil):
    • もしWindows環境でない場合、またはパスにバックスラッシュが含まれていない場合は、変換の必要がないため、既存のwrstring関数を使ってそのまま文字列を書き込み、関数を終了します。
  • else:
    • Windows環境であり、かつパスにバックスラッシュが含まれている場合、変換処理を行います。
    • n = strlen(p); wrint(b, n);: まず、パスの長さを書き込みます。これは、Goのオブジェクトファイルフォーマットで文字列の長さを先に記述する慣習に従っています。
    • for (i = 0; i < n; i++) Bputc(b, p[i] == '\\\\' ? '/' : p[i]);: パス文字列の各文字をループで処理します。もし文字がバックスラッシュ\であればスラッシュ/に変換し、そうでなければそのままの文字をBiobufに書き込みます。BputcBiobufに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ランタイムが期待するスラッシュ区切り形式に統一し、テストの安定性を向上させます。

関連リンク

参考にした情報源リンク

  • Issue 6975: runtime/pprof: TestBlockProfile fails on windows (Web検索で確認できた関連Issue)
  • Go言語のソースコード (特にsrc/liblink/objfile.cの変更履歴)
  • Go言語のドキュメント (リンカ、runtime/pprofruntime/debugに関する情報)
  • WindowsおよびUnix系OSのファイルパスに関する一般的な知識