[インデックス 19694] ファイルの概要
このコミットは、Go言語のデバッグツールであるcmd/addr2line
と、Plan 9 a.out形式のオブジェクトファイルを扱うdebug/plan9obj
パッケージにおける修正です。具体的には、Plan 9環境において実行ファイルのテキストセグメントの開始アドレスを正確に特定するためのロジックが改善されました。これまではリンカが追加するtext
シンボルに依存していましたが、これがamd64
アーキテクチャで誤った値を示す問題があったため、a.outヘッダのサイズとロードアドレスを直接利用するように変更されました。
コミット
commit 0a2083edd7a31f2248da1cdaca6e39466a9fb05b
Author: Aram Hăvărneanu <aram@mgk.ro>
Date: Wed Jul 9 12:33:13 2014 +0200
debug/plan9obj, cmd/addr2line: on Plan 9 use a.out header
size instead of abusing text symbol
cmd/addr2line needs to know the virtual address of the start
of the text segment (load address plus header size). For
this, it used the text symbol added by the linker. This is
wrong on amd64. Header size is 40 bytes, not 32 like on 386
and arm. Function alignment is 16 bytes causing text to be
at 0x200030.
debug/plan9obj now exports both the load address and the
header size; cmd/addr2line uses this new information and
doesn't rely on text anymore.
LGTM=0intro
R=0intro, gobot, ality
CC=ality, golang-codereviews, jas, mischief
https://golang.org/cl/106460044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0a2083edd7a31f2248da1cdaca6e39466a9fb05b
元コミット内容
debug/plan9obj, cmd/addr2line: on Plan 9 use a.out header
size instead of abusing text symbol
cmd/addr2line needs to know the virtual address of the start
of the text segment (load address plus header size). For
this, it used the text symbol added by the linker. This is
wrong on amd64. Header size is 40 bytes, not 32 like on 386
and arm. Function alignment is 16 bytes causing text to be
at 0x200030.
debug/plan9obj now exports both the load address and the
header size; cmd/addr2line uses this new information and
doesn't rely on text anymore.
LGTM=0intro
R=0intro, gobot, ality
CC=ality, golang-codereviews, jas, mischief
https://golang.org/cl/106460044
変更の背景
cmd/addr2line
は、実行ファイル内のアドレスから対応するソースコードのファイル名と行番号を特定するためのツールです。このツールが正しく機能するためには、実行ファイルのテキストセグメント(実行可能コードが格納されている領域)の仮想アドレス上の開始位置を正確に知る必要があります。
従来のcmd/addr2line
は、このテキストセグメントの開始位置を特定するために、リンカによって追加されるtext
という特殊なシンボルの値に依存していました。しかし、このアプローチには以下の問題がありました。
amd64
アーキテクチャでの不正確さ:amd64
アーキテクチャにおけるPlan 9 a.out形式のヘッダサイズは40バイトですが、386
やarm
アーキテクチャでは32バイトでした。text
シンボルは、このヘッダサイズの差異を適切に考慮していなかったため、amd64
環境ではテキストセグメントの実際の開始位置とtext
シンボルの値がずれてしまうことがありました。- 関数アライメントの影響:
amd64
では関数のアライメントが16バイトであるため、テキストセグメントが0x200030
のような特定のアドレスから始まることがあり、これもtext
シンボルが示す値との乖離を生じさせていました。
これらの問題により、cmd/addr2line
がamd64
環境のPlan 9 a.outファイルに対して正確な情報を提供できない可能性がありました。このコミットは、この不正確さを解消し、より堅牢な方法でテキストセグメントの開始位置を特定することを目的としています。
前提知識の解説
このコミットを理解するためには、以下の概念について理解しておく必要があります。
- a.out形式: Unix系システムで古くから使われている実行ファイル形式の一つです。Plan 9オペレーティングシステムでも独自のa.out形式が採用されており、Go言語のツールチェインもPlan 9環境をサポートしています。a.outファイルは通常、ヘッダ、テキストセグメント(コード)、データセグメント(初期化済みデータ)、BSSセグメント(初期化されていないデータ)、シンボルテーブルなどから構成されます。
- テキストセグメント (Text Segment): 実行可能コードが格納されるメモリ領域です。通常、読み取り専用としてマークされ、複数のプロセスで共有されることがあります。
- ロードアドレス (Load Address): プログラムがメモリにロードされる際の仮想アドレス上の開始位置です。オペレーティングシステムや実行ファイル形式によって、デフォルトのロードアドレスが定められています。
- ヘッダサイズ (Header Size): 実行ファイルのヘッダ部分のサイズです。ヘッダには、ファイルの種類、セグメントのサイズ、エントリポイントなどのメタデータが含まれます。テキストセグメントは通常、ヘッダの直後から始まります。
- シンボル (Symbol): プログラム内の関数、変数、セクションなどの名前と、それに対応するアドレスやサイズなどの情報です。リンカによって生成され、デバッグや動的リンクに利用されます。
text
シンボルは、伝統的にテキストセグメントの開始位置を示すために使われることがあります。 cmd/addr2line
: Go言語の標準ツールの一つで、実行ファイル内のアドレス(プログラムカウンタの値など)から、そのアドレスに対応するソースコードのファイル名と行番号を逆引きするデバッグツールです。クラッシュレポートの解析などで利用されます。debug/plan9obj
パッケージ: Go言語の標準ライブラリの一部で、Plan 9 a.out形式のオブジェクトファイルを解析するための機能を提供します。ファイルヘッダやセクション情報、シンボルテーブルなどを読み取るためのAPIが含まれています。- 仮想アドレス (Virtual Address): プロセスが利用するメモリ空間のアドレスです。物理メモリのアドレスとは異なり、メモリ管理ユニット (MMU) によって物理アドレスに変換されます。
- アライメント (Alignment): データやコードがメモリ上で特定のバイト境界に配置されることを保証する要件です。例えば、16バイトアライメントの場合、データのアドレスは16の倍数でなければなりません。これは、プロセッサが効率的にメモリにアクセスするために重要です。
技術的詳細
このコミットの技術的な核心は、cmd/addr2line
がテキストセグメントの開始アドレスを計算する方法を、不正確なtext
シンボルへの依存から、より信頼性の高いdebug/plan9obj
パッケージが提供する情報へと変更した点にあります。
具体的には、以下の変更が行われました。
-
debug/plan9obj/file.go
の変更:FileHeader
構造体にLoadAddress
(uint64) とHdrSize
(uint64) フィールドが追加されました。これにより、Plan 9 a.outファイルのロードアドレスとヘッダサイズが明示的に構造体の一部として保持されるようになります。NewFile
関数(a.outファイルを解析してFile
構造体を生成する関数)内で、これらの新しいフィールドが適切に初期化されるようになりました。- デフォルトの
LoadAddress
は0x1000
、HdrSize
は4 * 8 = 32
バイト(386/armの場合)と設定されます。 Magic64
フラグ(64ビットa.outを示す)がセットされている場合、PtrSize
が8バイトに更新されるとともに、LoadAddress
が0x200000
に、HdrSize
が8
バイト追加されて40
バイト(amd64の場合)に更新されます。これにより、amd64
アーキテクチャにおけるヘッダサイズの差異が正確に反映されるようになりました。
- デフォルトの
- セクションのオフセット計算において、これまではローカル変数
hdrSize
を使用していた箇所が、f.HdrSize
を使用するように変更されました。これにより、ファイルヘッダから直接取得した正確なヘッダサイズが利用されるようになります。
-
src/cmd/addr2line/main.go
の変更:loadPlan9Table
関数内で、これまでfindPlan9Symbol(f, "text")
を呼び出してtext
シンボルの値を取得していた部分が削除されました。- 代わりに、
f.LoadAddress
とf.HdrSize
という、debug/plan9obj
パッケージが新しく提供するフィールドを利用して、テキストセグメントの開始アドレスを計算するようになりました。具体的には、f.LoadAddress + f.HdrSize
がテキストセグメントの仮想アドレス上の開始位置として使用されます。 - これにより、
data[ssym.Value-text.Value : esym.Value-text.Value]
という計算が、data[ssym.Value-(f.LoadAddress+f.HdrSize) : esym.Value-(f.LoadAddress+f.HdrSize)]
に変更されました。この計算は、シンボルの値からテキストセグメントの開始アドレスを引くことで、データセグメント内での相対オフセットを正確に求めるためのものです。
この変更により、cmd/addr2line
は、リンカが生成するtext
シンボルの潜在的な不正確さに依存することなく、Plan 9 a.outファイルの構造から直接、テキストセグメントの正確な開始アドレスを導き出せるようになりました。特にamd64
アーキテクチャにおけるヘッダサイズの差異や関数アライメントによるオフセットの問題が解消され、より信頼性の高いアドレス解決が可能になります。
コアとなるコードの変更箇所
src/cmd/addr2line/main.go
--- a/src/cmd/addr2line/main.go
+++ b/src/cmd/addr2line/main.go
@@ -237,10 +237,6 @@ func loadPlan9Table(f *plan9obj.File, sname, ename string) ([]byte, error) {
if err != nil {
return nil, err
}
- text, err := findPlan9Symbol(f, "text")
- if err != nil {
- return nil, err
- }
sect := f.Section("text")
if sect == nil {
return nil, err
@@ -249,5 +245,5 @@ func loadPlan9Table(f *plan9obj.File, sname, ename string) ([]byte, error) {
if err != nil {
return nil, err
}
- return data[ssym.Value-text.Value : esym.Value-text.Value], nil
+ return data[ssym.Value-(f.LoadAddress+f.HdrSize) : esym.Value-(f.LoadAddress+f.HdrSize)], nil
}
src/pkg/debug/plan9obj/file.go
--- a/src/pkg/debug/plan9obj/file.go
+++ b/src/pkg/debug/plan9obj/file.go
@@ -15,10 +15,12 @@ import (
// A FileHeader represents a Plan 9 a.out file header.
type FileHeader struct {
- Magic uint32
- Bss uint32
- Entry uint64
- PtrSize int
+ Magic uint32
+ Bss uint32
+ Entry uint64
+ PtrSize int
+ LoadAddress uint64
+ HdrSize uint64
}
// A File represents an open Plan 9 a.out file.
@@ -148,20 +150,21 @@ func NewFile(r io.ReaderAt) (*File, error) {
}
f := &File{FileHeader: FileHeader{
- Magic: ph.Magic,
- Bss: ph.Bss,
- Entry: uint64(ph.Entry),
- PtrSize: 4,
+ Magic: ph.Magic,
+ Bss: ph.Bss,
+ Entry: uint64(ph.Entry),
+ PtrSize: 4,
+ LoadAddress: 0x1000,
+ HdrSize: 4 * 8,
}}
- hdrSize := 4 * 8
-
if ph.Magic&Magic64 != 0 {
if err := binary.Read(sr, binary.BigEndian, &f.Entry); err != nil {
return nil, err
}
f.PtrSize = 8
- hdrSize += 8
+ f.LoadAddress = 0x200000
+ f.HdrSize += 8
}
var sects = []struct {
@@ -177,7 +180,7 @@ func NewFile(r io.ReaderAt) (*File, error) {
f.Sections = make([]*Section, 5)
- off := uint32(hdrSize)
+ off := uint32(f.HdrSize)
for i, sect := range sects {
s := new(Section)
コアとなるコードの解説
src/pkg/debug/plan9obj/file.go
の変更点
FileHeader
構造体の拡張:LoadAddress uint64
: 実行ファイルがメモリにロードされる際の仮想アドレス上の開始位置を保持します。HdrSize uint64
: 実行ファイルのヘッダのサイズを保持します。 これらのフィールドが追加されたことで、debug/plan9obj
パッケージはa.outファイルの基本的なレイアウト情報をより正確に表現できるようになりました。
NewFile
関数での初期化ロジックの変更:f.LoadAddress
とf.HdrSize
が初期化されます。デフォルト値は32ビット環境(386/arm)を想定した0x1000
と32
バイト(4 * 8
)です。Magic64
フラグが検出された場合(64ビット環境、amd64
など)、f.LoadAddress
は0x200000
に、f.HdrSize
は40
バイト(32 + 8
)に更新されます。これは、amd64
におけるPlan 9 a.outのヘッダサイズとロードアドレスの慣習を反映しています。- セクションのオフセット計算に使用されていたローカル変数
hdrSize
が削除され、代わりにf.HdrSize
が直接使用されるようになりました。これにより、ファイルヘッダから読み取られた正確なヘッダサイズが、セクションの配置計算に反映されます。
src/cmd/addr2line/main.go
の変更点
text
シンボルへの依存の排除:loadPlan9Table
関数内で、これまでfindPlan9Symbol(f, "text")
を呼び出してtext
シンボルの値を取得していた行が削除されました。これは、text
シンボルがamd64
環境で不正確な値を示す可能性があったためです。
LoadAddress
とHdrSize
の利用:- テキストセグメント内のデータオフセットを計算する際に、
ssym.Value - text.Value
(シンボルの値からtext
シンボルの値を引く)という計算から、ssym.Value - (f.LoadAddress + f.HdrSize)
(シンボルの値からロードアドレスとヘッダサイズの合計を引く)という計算に変更されました。 f.LoadAddress + f.HdrSize
は、Plan 9 a.out形式において、ヘッダの直後に続くテキストセグメントの仮想アドレス上の開始位置を正確に表します。これにより、cmd/addr2line
は、実行ファイルの物理的な構造に基づいた正確なオフセット計算を行うことができるようになり、amd64
環境でのアドレス解決の精度が向上しました。
- テキストセグメント内のデータオフセットを計算する際に、
これらの変更により、debug/plan9obj
パッケージがPlan 9 a.outファイルの構造に関するより詳細で正確な情報を提供するようになり、cmd/addr2line
はその情報を利用して、より堅牢で正確なアドレス解決を実現しています。
関連リンク
- Go言語の
debug/plan9obj
パッケージのドキュメント: https://pkg.go.dev/debug/plan9obj - Go言語の
cmd/addr2line
ツールのドキュメント: https://pkg.go.dev/cmd/addr2line - Plan 9 a.out形式に関する情報 (一般的な情報源): https://9p.io/sys/doc/a.out.html (これは一般的なPlan 9のドキュメントであり、Goの特定の実装とは異なる場合がありますが、概念理解に役立ちます)
参考にした情報源リンク
- Go言語のコミット履歴: https://github.com/golang/go/commit/0a2083edd7a31f2248da1cdaca6e39466a9fb05b
- Go言語のコードレビューシステム (Gerrit): https://golang.org/cl/106460044
- Plan 9 a.out format (Wikipediaなど、一般的な情報源)
- Go言語の
cmd/addr2line
とdebug/plan9obj
のソースコード - Go言語の
amd64
アーキテクチャに関する情報 (Goのドキュメントやソースコード) - 関数アライメントに関する一般的なコンピュータアーキテクチャの知識
- 仮想アドレスと物理アドレスに関する一般的なオペレーティングシステムの知識
- 実行ファイル形式に関する一般的な知識 (a.out, ELF, PEなど)
- シンボルテーブルとリンカに関する一般的な知識