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

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

このコミットは、Goランタイムにおけるプログラムカウンタ(PC)からファイル名と行番号を特定するための pcln ウォーカーの重複した実装を削除し、そのロジックをC言語で書かれた単一の実装に統合することを目的としています。これにより、コードの複雑性を減らし、メンテナンス性と正確性を向上させています。

コミット

commit 610757b1552d35d3e960b053ad2a5aedea85b8da
Author: Russ Cox <rsc@golang.org>
Date:   Wed Jan 11 18:45:32 2012 -0800

    runtime: delete duplicate implementation of pcln walker
    
    It's hard enough to get right once.
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/5533073
---
 src/pkg/runtime/extern.go | 47 ++++-------------------------------------------
 src/pkg/runtime/symtab.c  |  9 +++++++++
 2 files changed, 13 insertions(+), 43 deletions(-)

diff --git a/src/pkg/runtime/extern.go b/src/pkg/runtime/extern.go
index e86da01732..1860c5b896 100644
--- a/src/pkg/runtime/extern.go
+++ b/src/pkg/runtime/extern.go
@@ -59,51 +59,12 @@ func (f *Func) Entry() uintptr { return f.entry }
 // The result will not be accurate if pc is not a program
 // counter within f.
 func (f *Func) FileLine(pc uintptr) (file string, line int) {
 -	// NOTE(rsc): If you edit this function, also edit
 -	// symtab.c:/^funcline.  That function also has the
 -	// comments explaining the logic.
 -	targetpc := pc
 -
 -	var pcQuant uintptr = 1
 -	if GOARCH == "arm" {
 -		pcQuant = 4
 -	}
 -
 -	p := f.pcln
 -	pc = f.pc0
 -	line = int(f.ln0)
 -	i := 0
 -	//print("FileLine start pc=", pc, " targetpc=", targetpc, " line=", line,
 -	//	" tab=", p, " ", p[0], " quant=", pcQuant, " GOARCH=", GOARCH, "\n")
 -	for {
 -		for i < len(p) && p[i] > 128 {
 -			pc += pcQuant * uintptr(p[i]-128)
 -			i++
 -		}
 -		//print("pc<", pc, " targetpc=", targetpc, " line=", line, "\n")
 -		if pc > targetpc || i >= len(p) {
 -			break
 -		}
 -		if p[i] == 0 {
 -			if i+5 > len(p) {
 -				break
 -			}
 -			line += int(p[i+1]<<24) | int(p[i+2]<<16) | int(p[i+3]<<8) | int(p[i+4])
 -			i += 5
 -		} else if p[i] <= 64 {
 -			line += int(p[i])
 -			i++
 -		} else {
 -			line -= int(p[i] - 64)
 -			i++
 -		}
 -		//print("pc=", pc, " targetpc=", targetpc, " line=", line, "\n")
 -		pc += pcQuant
 -	}
 -	file = f.src
 -	return
 +	return funcline_go(f, pc)
  }
  
 +// implemented in symtab.c
 +func funcline_go(*Func, uintptr) (string, int)
 +
  // mid returns the current os thread (m) id.
  func mid() uint32
  
 diff --git a/src/pkg/runtime/symtab.c b/src/pkg/runtime/symtab.c
 index 6cd59136f4..0346a420b5 100644
 --- a/src/pkg/runtime/symtab.c
 +++ b/src/pkg/runtime/symtab.c
 @@ -381,6 +381,15 @@ runtime·funcline(Func *f, uintptr targetpc)
  	return line;
  }
  
 +void
 +runtime·funcline_go(Func *f, uintptr targetpc, String retfile, int32 retline)
 +{
 +\tretfile = f->src;
 +\tretline = runtime·funcline(f, targetpc);
 +\tFLUSH(&retfile);
 +\tFLUSH(&retline);
 +}
 +\n static void
  buildfuncs(void)
  {

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

https://github.com/golang/go/commit/610757b1552d35d3e960b053ad2a5aedea85b8da

元コミット内容

commit 610757b1552d35d3e960b053ad2a5aedea85b8da
Author: Russ Cox <rsc@golang.org>
Date:   Wed Jan 11 18:45:32 2012 -0800

    runtime: delete duplicate implementation of pcln walker
    
    It's hard enough to get right once.
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/5533073
---
 src/pkg/runtime/extern.go | 47 ++++-------------------------------------------
 src/pkg/runtime/symtab.c  |  9 +++++++++
 2 files changed, 13 insertions(+), 43 deletions(-)

diff --git a/src/pkg/runtime/extern.go b/src/pkg/runtime/extern.go
index e86da01732..1860c5b896 100644
--- a/src/pkg/runtime/extern.go
+++ b/src/pkg/runtime/extern.go
@@ -59,51 +59,12 @@ func (f *Func) Entry() uintptr { return f.entry }
 // The result will not be accurate if pc is not a program
 // counter within f.
 func (f *Func) FileLine(pc uintptr) (file string, line int) {
 -	// NOTE(rsc): If you edit this function, also edit
 -	// symtab.c:/^funcline.  That function also has the
 -	// comments explaining the logic.
 -	targetpc := pc
 -
 -	var pcQuant uintptr = 1
 -	if GOARCH == "arm" {
 -		pcQuant = 4
 -	}
 -
 -	p := f.pcln
 -	pc = f.pc0
 -	line = int(f.ln0)
 -	i := 0
 -	//print("FileLine start pc=", pc, " targetpc=", targetpc, " line=", line,
 -	//	" tab=", p, " ", p[0], " quant=", pcQuant, " GOARCH=", GOARCH, "\n")
 -	for {
 -		for i < len(p) && p[i] > 128 {
 -			pc += pcQuant * uintptr(p[i]-128)
 -			i++
 -		}
 -		//print("pc<", pc, " targetpc=", targetpc, " line=", line, "\n")
 -		if pc > targetpc || i >= len(p) {
 -			break
 -		}
 -		if p[i] == 0 {
 -			if i+5 > len(p) {
 -				break
 -			}
 -			line += int(p[i+1]<<24) | int(p[i+2]<<16) | int(p[i+3]<<8) | int(p[i+4])
 -			i += 5
 -		} else if p[i] <= 64 {
 -			line += int(p[i])
 -			i++
 -		} else {
 -			line -= int(p[i] - 64)
 -			i++
 -		}
 -		//print("pc=", pc, " targetpc=", targetpc, " line=", line, "\n")
 -		pc += pcQuant
 -	}
 -	file = f.src
 -	return
 +	return funcline_go(f, pc)
  }
  
 +// implemented in symtab.c
 +func funcline_go(*Func, uintptr) (string, int)
 +
  // mid returns the current os thread (m) id.
  func mid() uint32
  
 diff --git a/src/pkg/runtime/symtab.c b/src/pkg/runtime/symtab.c
 index 6cd59136f4..0346a420b5 100644
 --- a/src/pkg/runtime/symtab.c
 +++ b/src/pkg/runtime/symtab.c
 @@ -381,6 +381,15 @@ runtime·funcline(Func *f, uintptr targetpc)
  	return line;
  }
  
 +void
 +runtime·funcline_go(Func *f, uintptr targetpc, String retfile, int32 retline)
 +{
 +\tretfile = f->src;
 +\tretline = runtime·funcline(f, targetpc);
 +\tFLUSH(&retfile);
 +\tFLUSH(&retline);
 +}
 +\n static void
  buildfuncs(void)
  {

変更の背景

このコミットの背景には、Goランタイム内でプログラムカウンタ(PC)からソースコードのファイル名と行番号を特定するロジックが、Go言語とC言語の両方で重複して実装されていたという問題があります。このような重複は、以下のような課題を引き起こします。

  • 一貫性の欠如: 同じ目的のロジックが複数存在すると、それぞれが異なる動作をする可能性があり、バグの原因となります。
  • メンテナンスの複雑性: 一方の実装に変更を加えた場合、もう一方の実装も同様に変更する必要があり、手間がかかるだけでなく、変更漏れのリスクも高まります。
  • バグの温床: pcln テーブルの解析は非常に複雑な処理であり、コミットメッセージにある「一度正しく実装するだけでも大変だ」という言葉が示すように、複数の実装を正確に維持することは困難です。

このコミットは、この複雑で重要なロジックをC言語で書かれた単一の実装に集約することで、これらの課題を解決しようとしています。これにより、コードベースの健全性を高め、将来的な機能追加やバグ修正を容易にすることを目指しています。

前提知識の解説

このコミットを理解するためには、以下のGoランタイムに関する基本的な概念を理解しておく必要があります。

  • Goランタイム (Go Runtime): Goプログラムの実行を管理する低レベルな部分です。ガベージコレクション、スケジューリング、システムコール、スタック管理など、Goプログラムが動作するために必要な多くの機能を提供します。Goランタイムの一部はGo言語で書かれていますが、パフォーマンスやシステムとの密接な連携が必要な部分はC言語やアセンブリ言語で書かれています。

  • PCLNテーブル (Program Counter Line Table): Goバイナリ内に埋め込まれている重要なデータ構造です。これは、実行中のプログラムカウンタ(PC)アドレスと、対応するソースコードのファイル名および行番号をマッピングするために使用されます。デバッグツールがスタックトレースを表示したり、プロファイラがコードのどの部分が実行されているかを特定したりする際に不可欠な情報源となります。pcln テーブルの形式はGoのバージョンによって進化しており、その解析は複雑なロジックを必要とします。

  • funcline: pcln テーブルを利用して、特定のプログラムカウンタアドレスに対応する関数、ファイル名、行番号を取得する機能です。Goのデバッグ情報やスタックトレースの生成において中心的な役割を果たします。

  • シンボルテーブル (Symbol Table): プログラム内のシンボル(関数名、変数名など)とそのメモリアドレスを関連付けるデータ構造です。デバッグや動的リンクの際に使用されます。Goランタイムは独自のシンボル情報を管理しており、実行時にシンボル解決を行うために利用されます。

  • GoとCの相互運用 (Cgo): Go言語はC言語のコードを呼び出すためのメカニズム(Cgo)を提供しています。Goランタイムの低レベルな部分は、歴史的な経緯や特定のシステム機能へのアクセス、パフォーマンス上の理由からC言語で実装されていることがあります。このコミットのように、GoコードからC言語で実装された関数を呼び出すことで、GoとCの間の連携が実現されます。

技術的詳細

このコミットの技術的な核心は、Goランタイムにおける Func.FileLine メソッドの内部実装の変更にあります。以前は、このメソッド内に pcln テーブルをウォークしてファイル名と行番号を特定するGo言語によるロジックが直接記述されていました。しかし、これとほぼ同じ機能がC言語のコード(symtab.c 内の runtime·funcline)にも存在していました。

このコミットでは、src/pkg/runtime/extern.go 内の Func.FileLine メソッドから、Go言語で書かれた pcln ウォーカーのロジックが完全に削除されました。代わりに、このメソッドは funcline_go という新しい関数を呼び出すように変更されています。

funcline_go 関数は、src/pkg/runtime/symtab.c で実装されていると宣言されており、実際には既存のC言語関数 runtime·funcline をラップしてGoから呼び出せるようにするブリッジ関数として機能します。これにより、pcln テーブルの解析とファイル・行番号の特定に関するすべての複雑なロジックが、C言語の runtime·funcline に一元化されることになります。

この変更の主な利点は以下の通りです。

  1. 単一責任の原則: pcln テーブルの解析という複雑なタスクが、C言語の単一の実装に集約されます。これにより、ロジックの重複が解消され、コードの理解とメンテナンスが容易になります。
  2. 正確性の向上: pcln テーブルの構造は非常に複雑であり、その解析ロジックを複数箇所で維持することは、バグを導入するリスクを高めます。単一の実装にすることで、その正確性を確保しやすくなります。
  3. 将来の変更への対応: pcln テーブルの形式が将来的に変更された場合でも、修正が必要な箇所がC言語の単一の実装に限定されるため、変更の影響範囲が小さくなり、対応が迅速になります。

このコミットは、Goランタイムの内部構造をより堅牢で保守しやすいものにするための重要なリファクタリングの一環と言えます。

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

このコミットによる主要なコード変更は、以下の2つのファイルに集中しています。

  1. src/pkg/runtime/extern.go:

    • Func 型の FileLine メソッドから、pcln テーブルを直接解析していたGo言語のコードブロック(約40行)が削除されました。
    • 削除されたコードの代わりに、return funcline_go(f, pc) という1行が追加されました。これは、C言語で実装された funcline_go 関数を呼び出すことを意味します。
    • また、funcline_go 関数のGo言語側の宣言(func funcline_go(*Func, uintptr) (string, int))が追加され、この関数が symtab.c で実装されていることがコメントで示されています。
  2. src/pkg/runtime/symtab.c:

    • runtime·funcline_go という新しいC言語関数が追加されました。
    • この関数は、Goから渡された Func ポインタとプログラムカウンタ targetpc を受け取り、既存のC言語関数 runtime·funcline を呼び出してファイル名と行番号を取得します。
    • 取得したファイル名と行番号は、Goに返すための引数 retfileretline に格納されます。

コアとなるコードの解説

  • src/pkg/runtime/extern.go の変更: Func.FileLine メソッドは、Goの関数オブジェクト (Func) とプログラムカウンタ (pc) を受け取り、そのPCが属するソースファイルのパスと行番号を返す役割を担っています。この変更前は、このメソッド内に pcln テーブルの複雑なウォークロジックがGo言語で直接記述されていました。これは、pcln テーブルのエンコーディングを解釈し、PCアドレスに基づいて適切なファイルと行番号を計算するものでした。

    今回の変更では、このGo言語による pcln ウォークロジックが完全に削除され、代わりに funcline_go(f, pc) というC言語関数への呼び出しに置き換えられました。これは、Go側での pcln 解析の責任を完全に放棄し、その処理をC言語側に委譲したことを意味します。これにより、Goコードはよりシンプルになり、pcln 解析の複雑性から解放されました。

  • src/pkg/runtime/symtab.c の変更: symtab.c は、Goランタイムのシンボルテーブル関連の処理をC言語で実装しているファイルです。このファイルには、以前から runtime·funcline というC言語関数が存在しており、これが pcln テーブルを解析してファイル名と行番号を特定する「真の」ロジックを保持していました。

    今回の変更で追加された runtime·funcline_go 関数は、Go言語から runtime·funcline を呼び出すためのブリッジ(ラッパー)として機能します。Goの Func.FileLine メソッドが funcline_go を呼び出すと、このC言語の runtime·funcline_go が実行され、内部で runtime·funcline を呼び出して実際の pcln 解析を行います。結果として得られたファイル名と行番号は、Go側に返されます。

    この一連の変更により、pcln ウォーカーのロジックはC言語の runtime·funcline に一本化され、Go言語側からはそのC言語実装を透過的に利用できるようになりました。これは、Goランタイムの内部におけるGoとCの役割分担を明確にし、複雑な低レベルロジックの管理を効率化するものです。

関連リンク

  • Go Change List (CL): https://golang.org/cl/5533073 (※このCL番号は古い形式のため、現在のGoのCLシステムでは直接参照できない可能性があります。)

参考にした情報源リンク