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

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

このコミットは、Go言語のツールチェインに含まれる addr2line および objdump コマンドのテストを改善するものです。具体的には、これらのコマンドがメモリアドレスを引数として受け取る際に、0x プレフィックスの有無にかかわらず正しく処理できることを検証するためのテストケースが追加・修正されています。これにより、ツールの堅牢性とユーザーエクスペリエンスが向上します。

コミット

435ba1295af24c0254707057a7d8dc6f17d6ad19

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

https://github.com/golang/go/commit/435ba1295af24c0254707057a7d8dc6f17d6ad19

元コミット内容

cmd/addr2line,cmd/objdump: test that commands accept addresses with 0x prefix and without

LGTM=iant
R=golang-codereviews, iant
CC=golang-codereviews
https://golang.org/cl/100440045

変更の背景

addr2lineobjdump といったデバッグツールは、プログラムの実行ファイル内の特定のアドレス(メモリ上の位置)を解析するために使用されます。これらのツールは、ユーザーがアドレスを様々な形式で入力する可能性があるため、入力の柔軟性が求められます。特に、16進数表記のアドレスは、0x プレフィックスを付けて表現されることが一般的ですが、一部の環境やツールではプレフィックスなしで入力されることもあります。

このコミット以前は、addr2line および objdump コマンドのテストが、0x プレフィックスの有無によるアドレス入力のバリエーションを十分にカバーしていなかった可能性があります。そのため、ユーザーがプレフィックスなしのアドレスを入力した場合や、逆にプレフィックス付きのアドレスを入力した場合に、ツールが期待通りに動作しない潜在的な問題が存在したかもしれません。

この変更の背景には、ツールの入力堅牢性を高め、異なる形式のアドレス入力にも対応できるようにするという目的があります。テストを強化することで、将来的なリグレッションを防ぎ、より安定したツールを提供することを目指しています。

前提知識の解説

  • addr2line コマンド: addr2line は、実行ファイル内のメモリアドレスを、対応するソースファイル名と行番号に変換するデバッグツールです。クラッシュレポートなどで得られたメモリアドレスから、問題が発生したソースコード上の正確な位置を特定する際に非常に役立ちます。例えば、./myprogram 0x401000 のように実行すると、0x401000 というアドレスが main.go:10 のような形式で出力されます。

  • objdump コマンド: objdump は、オブジェクトファイルや実行ファイルの内容を表示するツールです。これには、逆アセンブルされた機械語コード、シンボルテーブル、セクションヘッダなどが含まれます。デバッグやリバースエンジニアリングにおいて、プログラムの低レベルな動作を理解するために使用されます。objdump も特定のアドレス範囲を指定してその部分の情報を表示することができます。

  • メモリアドレスと16進数表記: コンピュータのメモリは、バイト単位でアドレスが割り振られています。これらのアドレスは通常、16進数(hexadecimal)で表現されます。16進数は0から9とAからFまでの16種類の記号を使って数を表現する方法で、コンピュータの内部表現と相性が良いため、プログラミングやデバッグで広く使われます。 16進数であることを明示するために、多くのプログラミング言語やツールでは 0x というプレフィックスを付けます。例えば、10進数の 255 は16進数で FF となり、0xFF と表記されます。このコミットの焦点は、この 0x プレフィックスの有無にかかわらず、ツールがアドレスを正しく解釈できるかどうかのテストです。

  • Go言語のテストフレームワーク: Go言語には、標準ライブラリとして testing パッケージが提供されており、これを使ってユニットテストやベンチマークテストを記述します。テスト関数は TestXxx という命名規則に従い、*testing.T 型の引数を受け取ります。テストの失敗は t.Errorft.Fatalf などで報告されます。

技術的詳細

このコミットの主要な変更は、addr2lineobjdump のテストコードに、アドレスを 0x プレフィックス付きとプレフィックスなしの両方で渡すテストケースを追加した点です。

src/cmd/addr2line/addr2line_test.go の変更

addr2line_test.go では、TestAddr2Line 関数が修正されています。 変更前は、シンボル名から取得したアドレス(syms[symName])を直接 runAddr2Line に渡していました。 変更後は、testAddr2Line というヘルパー関数が導入され、この関数内で runAddr2Line を2回呼び出しています。

  1. testAddr2Line(t, exepath, syms[symName]): プレフィックスなしのアドレスを渡します。
  2. testAddr2Line(t, exepath, "0x"+syms[symName]): 0x プレフィックスを付加したアドレスを渡します。

これにより、addr2line コマンドが両方の形式のアドレスを正しく処理できることが保証されます。また、テスト対象の行番号が変更されたため、期待される行番号も 70 から 94 に更新されています。これは、テストコード自体の変更によって、テストが参照するソースコードの行番号がずれたためです。

src/cmd/objdump/objdump_test.go の変更

objdump_test.go でも同様に、TestObjDump 関数が修正され、testObjDump というヘルパー関数が導入されています。 runObjDump 関数のシグネチャも変更され、startaddrendaddr を直接受け取るようになりました。以前は runObjDump 内部で startaddr をパースして endaddr を計算していましたが、テストの柔軟性を高めるために、呼び出し元で両方のアドレスを渡すように変更されました。

TestObjDump 関数内では、startaddrendaddr を計算した後、testObjDump を2回呼び出しています。

  1. testObjDump(t, exe, startaddr, endaddr): プレフィックスなしのアドレスを渡します。
  2. testObjDump(t, exe, "0x"+startaddr, "0x"+endaddr): 0x プレフィックスを付加したアドレスを渡します。

これにより、objdump コマンドが 0x プレフィックスの有無にかかわらず、開始アドレスと終了アドレスを正しく解釈できることが検証されます。こちらもテスト対象の行番号が 76 から 89 に更新されています。

これらの変更は、既存のテストロジックを再利用しつつ、新しいテストケースを効率的に追加するための一般的なパターンです。ヘルパー関数を導入することで、コードの重複を避け、テストの可読性と保守性を向上させています。

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

src/cmd/addr2line/addr2line_test.go

--- a/src/cmd/addr2line/addr2line_test.go
+++ b/src/cmd/addr2line/addr2line_test.go
@@ -67,6 +67,30 @@ func runAddr2Line(t *testing.T, exepath, addr string) (funcname, path, lineno st
 	return funcname, f[0], f[1]
 }
 
+const symName = "cmd/addr2line.TestAddr2Line"
+
+func testAddr2Line(t *testing.T, exepath, addr string) {
+	funcName, srcPath, srcLineNo := runAddr2Line(t, exepath, addr)
+	if symName != funcName {
+		t.Fatalf("expected function name %v; got %v", symName, funcName)
+	}
+	fi1, err := os.Stat("addr2line_test.go")
+	if err != nil {
+		t.Fatalf("Stat failed: %v", err)
+	}
+	fi2, err := os.Stat(srcPath)
+	if err != nil {
+		t.Fatalf("Stat failed: %v", err)
+	}
+	if !os.SameFile(fi1, fi2) {
+		t.Fatalf("addr2line_test.go and %s are not same file", srcPath)
+	}
+	if srcLineNo != "94" {
+		t.Fatalf("line number = %v; want 94", srcLineNo)
+	}
+}
+
+// This is line 93. The test depends on that.
 func TestAddr2Line(t *testing.T) {
 	if runtime.GOOS == "plan9" {
 		t.Skip("skipping test; see http://golang.org/issue/7947")
@@ -85,23 +109,6 @@ func TestAddr2Line(t *testing.T) {
 	\tt.Fatalf("go build -o %v cmd/addr2line: %v\\n%s", exepath, err, string(out))\n \t}\n \n-\tconst symName = "cmd/addr2line.TestAddr2Line"\n-\tfuncName, srcPath, srcLineNo := runAddr2Line(t, exepath, syms[symName])\n-\tif symName != funcName {\n-\t\tt.Fatalf("expected function name %v; got %v", symName, funcName)\n-\t}\n-\tfi1, err := os.Stat("addr2line_test.go")\n-\tif err != nil {\n-\t\tt.Fatalf("Stat failed: %v\", err)\n-\t}\n-\tfi2, err := os.Stat(srcPath)\n-\tif err != nil {\n-\t\tt.Fatalf("Stat failed: %v\", err)\n-\t}\n-\tif !os.SameFile(fi1, fi2) {\n-\t\tt.Fatalf("addr2line_test.go and %s are not same file\", srcPath)\n-\t}\n-\tif srcLineNo != "70" {\n-\t\tt.Fatalf("line number = %v; want 70\", srcLineNo)\n-\t}\n+\ttestAddr2Line(t, exepath, syms[symName])\n+\ttestAddr2Line(t, exepath, "0x"+syms[symName])\n }\n```

### `src/cmd/objdump/objdump_test.go`

```diff
--- a/src/cmd/objdump/objdump_test.go
+++ b/src/cmd/objdump/objdump_test.go
@@ -39,13 +39,8 @@ func loadSyms(t *testing.T) map[string]string {\n 	return syms\n }\n \n-func runObjDump(t *testing.T, exepath, startaddr string) (path, lineno string) {\n-\taddr, err := strconv.ParseUint(startaddr, 16, 64)\n-\tif err != nil {\n-\t\tt.Fatalf("invalid start address %v: %v", startaddr, err)\n-\t}\n-\tendaddr := fmt.Sprintf("%x", addr+10)\n-\tcmd := exec.Command(exepath, os.Args[0], "0x"+startaddr, "0x"+endaddr)\n+func runObjDump(t *testing.T, exe, startaddr, endaddr string) (path, lineno string) {\n+\tcmd := exec.Command(exe, os.Args[0], startaddr, endaddr)\n \tout, err := cmd.CombinedOutput()\n \tif err != nil {\n \t\tt.Fatalf("go tool objdump %v: %v\\n%s", os.Args[0], err, string(out))\n@@ -72,17 +67,8 @@ func runObjDump(t *testing.T, exepath, startaddr string) (path, lineno string) {\n \treturn f[0], f[1]\n }\n \n-// This is line 75.  The test depends on that.\n-func TestObjDump(t *testing.T) {\n-\tif runtime.GOOS == "plan9" {\n-\t\tt.Skip("skipping test; see http://golang.org/issue/7947")\n-\t}\n-\tsyms := loadSyms(t)\n-\n-\ttmp, exe := buildObjdump(t)\n-\tdefer os.RemoveAll(tmp)\n-\n-\tsrcPath, srcLineNo := runObjDump(t, exe, syms["cmd/objdump.TestObjDump"])\n+func testObjDump(t *testing.T, exe, startaddr, endaddr string) {\n+\tsrcPath, srcLineNo := runObjDump(t, exe, startaddr, endaddr)\n \tfi1, err := os.Stat("objdump_test.go")\n \tif err != nil {\n \t\tt.Fatalf("Stat failed: %v", err)\n@@ -94,9 +80,29 @@ func TestObjDump(t *testing.T) {\n \tif !os.SameFile(fi1, fi2) {\n \t\tt.Fatalf("objdump_test.go and %s are not same file", srcPath)\n \t}\n-\tif srcLineNo != "76" {\n-\t\tt.Fatalf("line number = %v; want 76", srcLineNo)\n+\tif srcLineNo != "89" {\n+\t\tt.Fatalf("line number = %v; want 89", srcLineNo)\n \t}\n+\n+}\n+\n+// This is line 88. The test depends on that.\n+func TestObjDump(t *testing.T) {\n+\tif runtime.GOOS == "plan9" {\n+\t\tt.Skip("skipping test; see http://golang.org/issue/7947")\n+\t}\n+\tsyms := loadSyms(t)\n+\n+\ttmp, exe := buildObjdump(t)\n+\tdefer os.RemoveAll(tmp)\n+\n+\tstartaddr := syms["cmd/objdump.TestObjDump"]\n+\taddr, err := strconv.ParseUint(startaddr, 16, 64)\n+\tif err != nil {\n+\t\tt.Fatalf("invalid start address %v: %v", startaddr, err)\n+\t}\n+\tendaddr := fmt.Sprintf("%x", addr+10)\n+\ttestObjDump(t, exe, startaddr, endaddr)\n+\ttestObjDump(t, exe, "0x"+startaddr, "0x"+endaddr)\n }\n```

## コアとなるコードの解説

### `addr2line_test.go` の変更点

*   **`testAddr2Line` ヘルパー関数の導入**:
    この新しい関数は、`addr2line` コマンドのテストロジックをカプセル化します。これにより、同じテストロジックを異なる入力(プレフィックスの有無)で再利用できるようになります。関数は `exepath`(実行ファイルのパス)と `addr`(テストするアドレス)を受け取ります。
*   **`TestAddr2Line` 内での `testAddr2Line` の呼び出し**:
    `TestAddr2Line` 関数内で、`syms[symName]`(プレフィックスなしのアドレス)と `"0x"+syms[symName]`(`0x` プレフィックス付きのアドレス)の両方を使って `testAddr2Line` を呼び出しています。これにより、`addr2line` が両方の形式のアドレスを正しく処理できることを確認します。
*   **期待される行番号の更新**:
    テストコード自体の変更により、`TestAddr2Line` 関数がソースファイル内で移動したため、期待される行番号が `70` から `94` に変更されています。これはテストの正確性を保つために必要な修正です。

### `objdump_test.go` の変更点

*   **`runObjDump` 関数のシグネチャ変更**:
    `runObjDump` 関数は、以前は `startaddr` のみを受け取り、`endaddr` を内部で計算していました。変更後は `startaddr` と `endaddr` の両方を直接引数として受け取るようになりました。これにより、テストケースでより柔軟なアドレス範囲を指定できるようになります。
*   **`testObjDump` ヘルパー関数の導入**:
    `addr2line_test.go` と同様に、`objdump` のテストロジックをカプセル化する `testObjDump` ヘルパー関数が導入されました。
*   **`TestObjDump` 内での `testObjDump` の呼び出し**:
    `TestObjDump` 関数内で、`startaddr` と `endaddr` を計算した後、プレフィックスなしと `0x` プレフィックス付きの両方のアドレスを使って `testObjDump` を呼び出しています。これにより、`objdump` が両方の形式のアドレス範囲を正しく処理できることを確認します。
*   **期待される行番号の更新**:
    こちらもテストコードの変更により、期待される行番号が `76` から `89` に変更されています。

これらの変更は、Go言語のデバッグツールが、ユーザーが入力するアドレスの形式に対してより堅牢になるように、テストカバレッジを向上させることを目的としています。これにより、ツールの信頼性が高まり、開発者やデバッガーがよりスムーズに作業できるようになります。

## 関連リンク

*   Go CL 100440045: [https://golang.org/cl/100440045](https://golang.org/cl/100440045)

## 参考にした情報源リンク

*   Go言語の `addr2line` コマンドに関するドキュメント (Go公式ドキュメント): [https://pkg.go.dev/cmd/addr2line](https://pkg.go.dev/cmd/addr2line)
*   Go言語の `objdump` コマンドに関するドキュメント (Go公式ドキュメント): [https://pkg.go.dev/cmd/objdump](https://pkg.go.dev/cmd/objdump)
*   Go言語の `testing` パッケージに関するドキュメント (Go公式ドキュメント): [https://pkg.go.dev/testing](https://pkg.go.dev/testing)
*   16進数 (Wikipedia): [https://ja.wikipedia.org/wiki/16%E9%80%B2%E6%95%B0](https://ja.wikipedia.org/wiki/16%E9%80%B2%E6%95%B0)
*   Go言語のソースコード (GitHub): [https://github.com/golang/go](https://github.com/golang/go)