[インデックス 19407] ファイルの概要
このコミットは、Go言語のリンカ(cmd/ld
)におけるMach-O形式の出力ファイル(特に6.out
というGo特有の命名規則によるバイナリ)に関する修正です。具体的には、macOS上でGoバイナリをLLDBデバッガでデバッグする際に発生していた問題に対処しています。LLDBがDWARFデバッグ情報を正しく解釈できるよう、__DWARF
セクションに仮想アドレスを割り当てる変更が加えられました。
コミット
commit a4a1fadfa2bdb4f6cfb1ed1dcf6b4bb0cd58060f
Author: Russ Cox <rsc@golang.org>
Date: Tue May 20 11:35:20 2014 -0400
cmd/ld: make lldb happy with Mach-O 6.out files
Apparently all the __DWARF sections need addresses
even though they are marked as "do not load from disk".
Continue the address numbering from the data segment.
With this change:
g% lldb helloworld
Current executable set to 'helloworld' (x86_64).
(lldb) b main.main
Breakpoint 1: where = helloworld`main.main + 25 at helloworld.go:12, address = 0x0000000000002019
(lldb) r
Process 68509 launched: '/Users/rsc/g/go/src/cmd/6l/helloworld' (x86_64)
1 location added to breakpoint 1
(lldb)
Process 68509 stopped
* thread #1: tid = 0x8b7a27, 0x0000000000002019 helloworld`main.main + 25 at helloworld.go:12, stop reason = breakpoint 1.2
frame #0: 0x0000000000002019 helloworld`main.main + 25 at helloworld.go:12
9 package main
10
11 func main() {
-> 12 print("hello, world\\n")
13 }
(lldb) bt
* thread #1: tid = 0x8b7a27, 0x0000000000002019 helloworld`main.main + 25 at helloworld.go:12, stop reason = breakpoint 1.2
* frame #0: 0x0000000000002019 helloworld`main.main + 25 at helloworld.go:12
(lldb) disas
helloworld`main.main at helloworld.go:11:
0x2000: movq %gs:0x8a0, %rcx
0x2009: cmpq (%rcx), %rsp
0x200c: ja 0x2015 ; main.main + 21 at helloworld.go:11
0x200e: callq 0x20da0 ; runtime.morestack00_noctxt at atomic_amd64x.c:28
0x2013: jmp 0x2000 ; main.main at helloworld.go:11
0x2015: subq $0x10, %rsp
-> 0x2019: leaq 0x2c2e0, %rbx
0x2021: leaq (%rsp), %rbp
0x2025: movq %rbp, %rdi
0x2028: movq %rbx, %rsi
0x202b: movsq
0x202d: movsq
0x202f: callq 0x10300 ; runtime.printstring at compiler.go:1
0x2034: addq $0x10, %rsp
0x2038: ret
0x2039: addb %al, (%rax)
0x203b: addb %al, (%rax)
0x203d: addb %al, (%rax)
(lldb) quit
Quitting LLDB will kill one or more processes. Do you really want to proceed: [Y/n] y
g%
Fixes #7070.
LGTM=iant
R=golang-codereviews, iant
CC=golang-codereviews
https://golang.org/cl/93510043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a4a1fadfa2bdb4f6cfb1ed1dcf6b4bb0cd58060f
元コミット内容
cmd/ld: make lldb happy with Mach-O 6.out files
Apparently all the __DWARF sections need addresses
even though they are marked as "do not load from disk".
Continue the address numbering from the data segment.
With this change:
g% lldb helloworld
Current executable set to 'helloworld' (x86_64).
(lldb) b main.main
Breakpoint 1: where = helloworld`main.main + 25 at helloworld.go:12, address = 0x0000000000002019
(lldb) r
Process 68509 launched: '/Users/rsc/g/go/src/cmd/6l/helloworld' (x86_64)
1 location added to breakpoint 1
(lldb)
Process 68509 stopped
* thread #1: tid = 0x8b7a27, 0x0000000000002019 helloworld`main.main + 25 at helloworld.go:12, stop reason = breakpoint 1.2
frame #0: 0x0000000000002019 helloworld`main.main + 25 at helloworld.go:12
9 package main
10
11 func main() {
-> 12 print("hello, world\n")
13 }
(lldb) bt
* thread #1: tid = 0x8b7a27, 0x0000000000002019 helloworld`main.main + 25 at helloworld.go:12, stop reason = breakpoint 1.2
* frame #0: 0x0000000000002019 helloworld`main.main + 25 at helloworld.go:12
(lldb) disas
helloworld`main.main at helloworld.go:11:
0x2000: movq %gs:0x8a0, %rcx
0x2009: cmpq (%rcx), %rsp
0x200c: ja 0x2015 ; main.main + 21 at helloworld.go:11
0x200e: callq 0x20da0 ; runtime.morestack00_noctxt at atomic_amd64x.c:28
0x2013: jmp 0x2000 ; main.main at helloworld.go:11
0x2015: subq $0x10, %rsp
-> 0x2019: leaq 0x2c2e0, %rbx
0x2021: leaq (%rsp), %rbp
0x2025: movq %rbp, %rdi
0x2028: movq %rbx, %rsi
0x202b: movsq
0x202d: movsq
0x202f: callq 0x10300 ; runtime.printstring at compiler.go:1
0x2034: addq $0x10, %rsp
0x2038: ret
0x2039: addb %al, (%rax)
0x203b: addb %al, (%rax)
0x203d: addb %al, (%rax)
(lldb) quit
Quitting LLDB will kill one or more processes. Do you really want to proceed: [Y/n] y
g%
Fixes #7070.
LGTM=iant
R=golang-codereviews, iant
CC=golang-codereviews
https://golang.org/cl/93510043
変更の背景
このコミットは、Go言語でビルドされた実行可能ファイル(特にmacOS向けのMach-O形式のバイナリ)をLLDBデバッガでデバッグする際に発生していた問題を解決するために導入されました。
Goのリンカ(cmd/ld
)は、デバッグ情報(DWARF)を含むセクションを生成しますが、これらのセクションは実行時にはメモリにロードされないようにマークされていました。これは、デバッグ情報がプログラムの実行に直接必要ないため、バイナリサイズを最適化し、ロード時間を短縮するための一般的なプラクティスです。
しかし、LLDBデバッガは、たとえディスクからロードされないとマークされていても、__DWARF
セクション内のすべてのデバッグ情報に有効な仮想アドレスが割り当てられていることを期待していました。Goのリンカがこれらのセクションにアドレスを割り当てていなかったため、LLDBはデバッグ情報を正しく解釈できず、ブレークポイントの設定やスタックトレースの表示など、基本的なデバッグ機能が動作しないという問題が発生していました。
この問題は、GoのIssue #7070として報告されており、このコミットはその修正を目的としています。
前提知識の解説
Mach-O (Mach object)
Mach-Oは、macOS、iOS、watchOS、tvOSなどのApple製オペレーティングシステムで使用される実行可能ファイル、オブジェクトコード、共有ライブラリ、動的にロードされるコード、およびコアダンプのファイル形式です。Mach-Oファイルは、以下の主要なコンポーネントで構成されます。
- ヘッダ (Header): ファイルの基本的な情報(CPUタイプ、ファイルタイプ、ロードコマンドの数とサイズなど)を識別します。
- ロードコマンド (Load Commands): ファイルのレイアウトとリンケージ特性を定義する可変サイズのコマンド群です。これには、仮想メモリ内の初期レイアウト、シンボルテーブルの位置、共有ライブラリの名前などが含まれます。
- セグメント (Segments): 1つ以上のセクションを含む論理的なブロックです。セグメントは通常、ページ境界で開始され、
__TEXT
(読み取り専用のコードと定数データ)や__DATA
(読み書き可能なデータ)などの一般的なセグメントがあります。
コミットメッセージにある「6.out」は、Goのリンカが64ビットアーキテクチャ(amd64)向けに生成するバイナリの慣習的な命名であり、Mach-Oの標準的な拡張子ではありません。
DWARF (Debugging With Attributed Record Formats)
DWARFは、ソースレベルデバッガがプログラムの実行状態をソースコードのコンテキストで解釈するために使用する標準的なデバッグ情報形式です。コンパイルされたバイナリに埋め込まれ、以下の情報を提供します。
- ソースファイルと行番号のマッピング: 実行中のマシンコードがどのソースファイルのどの行に対応するか。
- 変数情報: 変数の名前、型、スコープ、メモリ上の位置。
- 関数情報: 関数の名前、引数、戻り値、スタックフレームのレイアウト。
- 型情報: 構造体、配列、ポインタなどの複雑なデータ型の定義。
DWARF情報は通常、実行可能ファイル内の特定のセクション(例: __debug_abbrev
, __debug_line
, __debug_info
, __debug_frame
など)に格納されます。これらのセクションは、プログラムの実行には直接必要ないため、「ディスクからロードしない (do not load from disk)」というフラグが付けられることがあります。
LLDB
LLDBは、LLVMプロジェクトの一部として開発されたオープンソースのデバッガです。GDB(GNU Debugger)の代替として設計されており、モダンな言語やアーキテクチャに対応しています。LLDBは、Mach-O形式のバイナリのデバッグに広く使用されており、macOSのXcodeにバンドルされています。
デバッガは、実行可能ファイル内のデバッグ情報を読み取り、それを使用してソースコードレベルでのデバッグ機能(ブレークポイント、ステップ実行、変数検査、スタックトレースなど)を提供します。LLDBがDWARF情報を正しく利用するためには、デバッグ情報が格納されているセクションが、たとえメモリにロードされなくても、有効な仮想アドレスを持っていることを期待します。
技術的詳細
このコミットの核心は、GoのリンカがMach-O形式のバイナリを生成する際に、__DWARF
セクションに仮想アドレス(vaddr
)を割り当てるように変更した点です。
以前のGoリンカでは、__DWARF
セクションは「ディスクからロードしない」とマークされていたため、これらのセクションには仮想アドレスが割り当てられていませんでした。これは、実行時にこれらのセクションがメモリにマッピングされる必要がないという論理に基づいています。しかし、LLDBのようなデバッガは、デバッグ情報を参照する際に、これらのセクションがメモリ上のどこかに存在するという仮定(たとえそれが仮想的なアドレス空間であっても)に基づいて動作します。有効なアドレスがないと、LLDBはデバッグ情報を正しく参照できず、デバッグセッションが失敗したり、期待通りに動作しなくなったりします。
この修正では、dwarfaddmachoheaders
関数内で、各__DWARF
関連セクション(__debug_abbrev
, __debug_line
, __debug_frame
, __debug_info
, __debug_pubnames
, __debug_pubtypes
, __debug_aranges
, __debug_gdb_scripts
)に対して、msect->addr
フィールドを設定する行が追加されました。
具体的には、msect->addr = msect->off + segdata.vaddr - segdata.fileoff;
という計算式が用いられています。
msect->off
: セクションのファイルオフセット(ファイル内の物理的な位置)。segdata.vaddr
: データセグメントの仮想アドレス(メモリ上の開始アドレス)。segdata.fileoff
: データセグメントのファイルオフセット。
この計算は、__DWARF
セクションの仮想アドレスを、データセグメントの仮想アドレス空間の続きとして割り当てることを意味します。これにより、__DWARF
セクションは、実際にメモリにロードされなくても、LLDBが期待する有効な仮想アドレスを持つことになります。この変更によって、LLDBはGoバイナリのDWARF情報を正しく解析し、デバッグセッションを円滑に進めることができるようになりました。
コアとなるコードの変更箇所
変更は src/cmd/ld/dwarf.c
ファイルの dwarfaddmachoheaders
関数内で行われています。
--- a/src/cmd/ld/dwarf.c
+++ b/src/cmd/ld/dwarf.c
@@ -2353,31 +2353,37 @@ dwarfaddmachoheaders(void)
ms = newMachoSeg("__DWARF", nsect);
ms->fileoffset = fakestart;
ms->filesize = abbrevo-fakestart;
+ ms->vaddr = ms->fileoffset + segdata.vaddr - segdata.fileoff;
msect = newMachoSect(ms, "__debug_abbrev", "__DWARF");
msect->off = abbrevo;
msect->size = abbrevsize;
+ msect->addr = msect->off + segdata.vaddr - segdata.fileoff;
ms->filesize += msect->size;
msect = newMachoSect(ms, "__debug_line", "__DWARF");
msect->off = lineo;
msect->size = linesize;
+ msect->addr = msect->off + segdata.vaddr - segdata.fileoff;
ms->filesize += msect->size;
msect = newMachoSect(ms, "__debug_frame", "__DWARF");
msect->off = frameo;
msect->size = framesize;
+ msect->addr = msect->off + segdata.vaddr - segdata.fileoff;
ms->filesize += msect->size;
msect = newMachoSect(ms, "__debug_info", "__DWARF");
msect->off = infoo;
msect->size = infosize;
+ msect->addr = msect->off + segdata.vaddr - segdata.fileoff;
ms->filesize += msect->size;
if (pubnamessize > 0) {
msect = newMachoSect(ms, "__debug_pubnames", "__DWARF");
msect->off = pubnameso;
msect->size = pubnamessize;
+ msect->addr = msect->off + segdata.vaddr - segdata.fileoff;
ms->filesize += msect->size;
}
@@ -2385,6 +2391,7 @@ dwarfaddmachoheaders(void)
msect = newMachoSect(ms, "__debug_pubtypes", "__DWARF");
msect->off = pubtypeso;
msect->size = pubtypessize;
+ msect->addr = msect->off + segdata.vaddr - segdata.fileoff;
ms->filesize += msect->size;
}
@@ -2392,6 +2399,7 @@ dwarfaddmachoheaders(void)
msect = newMachoSect(ms, "__debug_aranges", "__DWARF");
msect->off = arangeso;
msect->size = arangessize;
+ msect->addr = msect->off + segdata.vaddr - segdata.fileoff;
ms->filesize += msect->size;
}
@@ -2400,6 +2408,7 @@ dwarfaddmachoheaders(void)
msect = newMachoSect(ms, "__debug_gdb_scripts", "__DWARF");
msect->off = gdbscripto;
msect->size = gdbscriptsize;
+ msect->addr = msect->off + segdata.vaddr - segdata.fileoff;
ms->filesize += msect->size;
}
}
コアとなるコードの解説
dwarfaddmachoheaders
関数は、Mach-OバイナリにDWARFデバッグ情報セクションを追加する役割を担っています。この関数内で、各DWARFセクションを表すmachoSect
構造体(msect
)が作成されます。
変更前は、msect->addr
フィールド(セクションの仮想アドレス)は設定されていませんでした。このコミットでは、以下の行が各DWARFセクションの初期化に追加されました。
msect->addr = msect->off + segdata.vaddr - segdata.fileoff;
この行は、各DWARFセクションの仮想アドレスを計算し、設定しています。
msect->off
: ファイル内でのセクションの開始オフセット(物理的な位置)。segdata.vaddr
: データセグメントの仮想アドレス(メモリ上の開始アドレス)。segdata.fileoff
: データセグメントのファイルオフセット。
この計算により、DWARFセクションの仮想アドレスは、データセグメントの仮想アドレス空間の続きとして割り当てられます。これにより、LLDBはこれらのセクションを、あたかもメモリにロードされているかのように参照できるようになり、デバッグ情報の解析が可能になります。
また、__DWARF
セグメント自体にも仮想アドレスが割り当てられています。
ms->vaddr = ms->fileoffset + segdata.vaddr - segdata.fileoff;
これは、__DWARF
セグメント全体の仮想アドレスを、そのファイルオフセットとデータセグメントの仮想アドレス/ファイルオフセットの差分に基づいて設定するものです。これにより、__DWARF
セグメントもLLDBが期待するアドレス空間内に配置され、デバッグ情報の整合性が保たれます。
この修正により、Goのリンカは、LLDBがMach-OバイナリのDWARFデバッグ情報を正しく解釈するために必要な仮想アドレスを、たとえセクションが「ディスクからロードしない」とマークされていても、適切に割り当てるようになりました。
関連リンク
- Go Issue #7070: https://github.com/golang/go/issues/7070
- Go CL 93510043: https://golang.org/cl/93510043
参考にした情報源リンク
- Mach-O File Format: https://en.wikipedia.org/wiki/Mach-O
- DWARF Debugging Format: https://dwarfstd.org/
- LLDB Debugger: https://lldb.llvm.org/
- Debugging Go programs with LLDB on macOS: https://github.com/go-delve/delve/blob/master/Documentation/macOS-debugging.md (これはDelveのドキュメントですが、LLDBでのGoデバッグに関する一般的な情報が含まれています)
- Goのリンカに関するドキュメント(Goのソースコード内)
- Mach-Oのセクションとセグメントに関するAppleのドキュメント(開発者向けドキュメント)