[インデックス 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など)
- シンボルテーブルとリンカに関する一般的な知識