[インデックス 19122] ファイルの概要
このコミットは、Go言語のツールチェインに含まれるobjdump
コマンドを、C言語で書かれた既存の実装からGo言語による新しい実装へと書き換えるものです。これにより、objdump
はGoツールチェインとの統合が強化され、クロスプラットフォームでの利用が容易になります。特に、pprof
ツールが提供するlist
およびweblist
コマンドの基本的な機能サポートを目的としています。
コミット
commit 0d441a088d2cb23af32dae473aea989830d11055
Author: Russ Cox <rsc@golang.org>
Date: Mon Apr 14 10:58:49 2014 -0400
cmd/objdump: rewrite in Go
Update cmd/dist not to build the C version.
Update cmd/go to install the Go version to the tool directory.
Update #7452
This is the basic logic needed for objdump, and it works well enough
to support the pprof list and weblist commands. A real disassembler
needs to be added in order to support the pprof disasm command
and the per-line assembly displays in weblist. That's still to come.
Probably objdump will move to go.tools when the disassembler
is added, but it can stay here for now.
LGTM=minux.ma
R=golang-codereviews, minux.ma
CC=golang-codereviews, iant, r
https://golang.org/cl/87580043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0d441a088d2cb23af32dae473aea989830d11055
元コミット内容
cmd/objdump: rewrite in Go
Update cmd/dist not to build the C version.
Update cmd/go to install the Go version to the tool directory.
Update #7452
This is the basic logic needed for objdump, and it works well enough
to support the pprof list and weblist commands. A real disassembler
needs to be added in order to support the pprof disasm command
and the per-line assembly displays in weblist. That's still to come.
Probably objdump will move to go.tools when the disassembler
is added, but it can stay here for now.
LGTM=minux.ma
R=golang-codereviews, minux.ma
CC=golang-codereviews, iant, r
https://golang.org/cl/87580043
変更の背景
この変更の主な背景には、Go言語のツールチェインにおけるobjdump
コマンドの統合と、クロスプラットフォーム対応の改善があります。
元々、objdump
はC言語で実装されており、特にmacOSのような特定のプラットフォームでは、pprof
ツールが正しく機能するために必要な情報(シンボル情報や行番号情報など)を抽出する上で課題がありました。C言語のコードはプラットフォーム固有の依存関係を持つことが多く、Goのクロスプラットフォーム開発の哲学とは相容れない部分がありました。
Go言語でobjdump
を再実装することで、以下の利点が得られます。
- クロスプラットフォーム互換性: Go言語は強力なクロスコンパイル機能を持ち、単一のコードベースから様々なOSやアーキテクチャ向けのバイナリを生成できます。これにより、
objdump
もGoツールチェインの一部として、より広範な環境で一貫して動作するようになります。 - Goツールチェインとの統合:
objdump
をGoで書くことで、Goの標準ライブラリ(特にdebug/elf
,debug/macho
,debug/pe
,debug/gosym
など)を直接利用できるようになり、Goのバイナリ形式やデバッグ情報との連携がより密になります。 - メンテナンス性の向上: Go言語はC言語に比べてメモリ安全性や並行処理の扱いが容易であり、コードの可読性も高い傾向にあります。これにより、将来的な機能追加やバグ修正がより効率的に行えるようになります。
pprof
の機能強化:pprof
はGoプログラムのプロファイリングを行うための重要なツールです。objdump
はpprof
がバイナリのシンボル情報やアセンブリコードを解析するために利用されます。このコミットでは、特にpprof list
(ソースコードとアセンブリの対応リスト表示)とpprof weblist
(Webベースのソース/アセンブリ表示)の基本的なサポートを目的としています。将来的には、完全な逆アセンブル機能を追加することで、pprof disasm
(完全な逆アセンブル表示)もサポートする計画です。
この変更は、Go Issue #7452に関連しており、Goツールチェイン全体の堅牢性と使いやすさを向上させる一環として行われました。
前提知識の解説
objdump
とは
objdump
は、オブジェクトファイルや実行可能ファイルの内容を解析し、人間が読める形式で表示するためのコマンドラインツールです。一般的な機能としては以下のようなものがあります。
- 逆アセンブル (Disassembly): 機械語コードをアセンブリ言語に変換して表示します。これにより、プログラムが実際にどのように動作しているかを低レベルで理解できます。
- セクション情報の表示: 実行可能ファイル内の様々なセクション(例:
.text
(コード),.data
(初期化済みデータ),.bss
(初期化なしデータ),.symtab
(シンボルテーブル)など)の情報を表示します。 - シンボルテーブルの表示: プログラム内の関数名、変数名などのシンボルとそのアドレス情報を表示します。デバッグやプロファイリングにおいて、特定のコードがどこにあるかを特定するのに役立ちます。
- ヘッダ情報の表示: 実行可能ファイルの形式(ELF, Mach-O, PEなど)やアーキテクチャに関する情報を表示します。
このコミットにおけるobjdump
は、特にGoプログラムのバイナリを解析し、pprof
ツールが利用するシンボル情報や行番号情報を抽出する役割を担います。
pprof
とは
pprof
は、Go言語の標準的なプロファイリングツールです。CPU使用率、メモリ割り当て、ゴルーチンスタックトレースなど、様々な種類のプロファイルデータを収集し、視覚化することができます。pprof
は、Goプログラムのパフォーマンスボトルネックを特定し、最適化を行う上で不可欠なツールです。
pprof
の主な機能には以下のようなものがあります。
- プロファイルデータの収集:
runtime/pprof
パッケージやHTTPインターフェースを通じて、実行中のGoプログラムからプロファイルデータを収集します。 - プロファイルデータの解析と視覚化: 収集したプロファイルデータを解析し、テキスト形式、グラフ(Call Graph, Flame Graphなど)、Webインターフェースなど、様々な形式で表示します。
list
コマンド: ソースコードと対応するアセンブリコードを並べて表示し、どの行がどれだけCPU時間を使っているかなどを視覚的に確認できます。weblist
コマンド:list
コマンドのWebインターフェース版で、ブラウザでソースコードとアセンブリコードの対応を確認できます。disasm
コマンド: 特定の関数のアセンブリコードを完全に逆アセンブルして表示します。
このコミットでobjdump
がGoで書き直されたのは、pprof
のlist
およびweblist
コマンドがGoバイナリのシンボル情報や行番号情報を正確に取得できるようにするためです。
Goツールチェインの構成要素
cmd/dist
: Goのビルドシステムの一部であり、Goツールチェインの様々なコンポーネント(コンパイラ、リンカ、その他のツール)をビルドする役割を担います。このコミットでは、objdump
のC言語版をビルド対象から除外する変更が行われました。cmd/go
: Goコマンドラインツールの中核であり、Goプログラムのビルド、テスト、実行、パッケージ管理など、開発者が日常的に使用するほとんどの操作を制御します。このコミットでは、Goで書き直されたobjdump
をGoツールチェインのtool
ディレクトリにインストールするよう設定が変更されました。
C言語とGo言語の比較(この文脈において)
- C言語: システムプログラミングに適しており、ハードウェアに密接にアクセスできます。しかし、メモリ管理(ポインタ、手動でのメモリ解放)が複雑で、メモリリークやセグメンテーション違反などのバグが発生しやすいという側面があります。また、クロスプラットフォーム開発においては、プラットフォーム固有のAPIやコンパイラ、ライブラリの依存関係を管理する必要があります。
- Go言語: Googleによって開発された静的型付けのコンパイル言語で、シンプルさ、効率性、並行処理のサポートを重視しています。ガベージコレクションによる自動メモリ管理、強力な標準ライブラリ、優れたクロスコンパイル機能が特徴です。システムプログラミングにも利用されますが、C言語よりも抽象度が高く、安全性が高いとされています。
objdump
のようなバイナリ解析ツールをGoで書き直すことは、C言語の持つ低レベルな制御能力の一部を犠牲にするかもしれませんが、Goの持つクロスプラットフォーム性、メモリ安全性、そしてGoツールチェインとのシームレスな統合という大きなメリットを享受できます。
Goの標準ライブラリパッケージ
このコミットで新しくGo言語で実装されたobjdump
は、Goの標準ライブラリの以下のパッケージを多用しています。
debug/elf
: ELF (Executable and Linkable Format) 形式の実行可能ファイルやオブジェクトファイルを解析するためのパッケージです。LinuxやUnix系システムで広く使われています。debug/macho
: Mach-O (Mach Object) 形式の実行可能ファイルやオブジェクトファイルを解析するためのパッケージです。macOSやiOSで使われています。debug/pe
: PE (Portable Executable) 形式の実行可能ファイルやオブジェクトファイルを解析するためのパッケージです。Windowsで使われています。debug/gosym
: Goバイナリに埋め込まれたGo固有のシンボルテーブル(gosymtab
)とPC-Lineテーブル(gopclntab
)を解析するためのパッケージです。これにより、実行アドレス(PC)からファイル名、行番号、関数名へのマッピングが可能になります。
これらのパッケージを利用することで、Goで書かれたobjdump
は、異なるOSで生成されたGoバイナリを統一的な方法で解析できるようになります。
技術的詳細
新しいGo言語版のobjdump
(src/cmd/objdump/main.go
) は、主に以下の機能を提供します。
-
バイナリ形式の自動判別と解析:
loadTables
関数は、入力されたバイナリファイルがELF、Mach-O、PEのいずれの形式であるかを自動的に判別し、それぞれの形式に対応するGoのdebug/elf
,debug/macho
,debug/pe
パッケージを使用して解析します。 この関数は、バイナリから以下の情報を抽出します。textStart
: コードセクション(.text
)の開始アドレス。textData
: コードセクションの生データ。symtab
: Go固有のシンボルテーブル(.gosymtab
)の生データ。pclntab
: Go固有のPC-Lineテーブル(.gopclntab
)の生データ。
-
Goシンボル情報の利用: 抽出された
symtab
とpclntab
は、debug/gosym
パッケージのgosym.NewLineTable
とgosym.NewTable
を使用して、Goのシンボル情報とPC-Lineマッピングを構築するために利用されます。これにより、特定のアドレスがどのファイル、どの行、どの関数に属するかを正確に特定できます。 -
基本的な逆アセンブル機能(暫定): 現在の実装では、完全な命令の逆アセンブルは行いません。代わりに、
pprof list
やweblist
が要求するPC(プログラムカウンタ)範囲に対して、ファイル名、行番号、関数名を基に「ダミーの命令」を出力します。具体的には、同じファイル、行、関数に属するPCの範囲をspan
として扱い、その範囲内の各PCに対して、対応するバイトコード(textData
から取得可能であれば)または?
を表示します。 コミットメッセージにもあるように、これはpprof list
とweblist
の基本的な動作をサポートするためのものであり、pprof disasm
のような完全な逆アセンブル機能や、より詳細なアセンブリ表示には、将来的に「真の逆アセンブラ」の実装が必要とされています。 -
Goツールチェインとの連携:
src/cmd/dist/build.c
からC言語版objdump
のビルドとクリーンアップに関する記述が削除されました。これは、GoツールチェインがC言語版objdump
をビルドしなくなることを意味します。src/cmd/go/pkg.go
のgoTools
マップに"cmd/objdump": toTool
が追加されました。これにより、go install cmd/objdump
コマンドがGo言語版のobjdump
をGoのツールディレクトリ(通常は$GOPATH/bin
または$GOBIN
)にインストールするようになります。
この変更により、Goのobjdump
はGoツールチェインのネイティブな一部となり、Goプログラムのデバッグとプロファイリングのワークフローが改善されます。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は以下の4つのファイルに集中しています。
-
src/cmd/dist/build.c
:buildorder
配列とcleantab
配列から"cmd/objdump"
のエントリが削除されました。- これは、GoツールチェインのビルドプロセスからC言語版の
objdump
が除外されることを意味します。
--- a/src/cmd/dist/build.c +++ b/src/cmd/dist/build.c @@ -1332,7 +1332,6 @@ static char *buildorder[] = { "misc/pprof", - "cmd/objdump", "cmd/prof", "cmd/cc", // must be before c @@ -1409,7 +1408,6 @@ static char *cleantab[] = { "cmd/cc", "cmd/gc", "cmd/go", - "cmd/objdump", "cmd/prof", "lib9", "libbio",
-
src/cmd/go/pkg.go
:goTools
マップに"cmd/objdump": toTool
が追加されました。- これにより、Go言語版の
objdump
がGoツールチェインの標準ツールとして認識され、go install
コマンドでインストールできるようになります。
--- a/src/cmd/go/pkg.go +++ b/src/cmd/go/pkg.go @@ -313,6 +313,7 @@ var goTools = map[string]targetDir{ "cmd/fix": toTool, "cmd/link": toTool, "cmd/nm": toTool, +\t"cmd/objdump": toTool, "cmd/pack": toTool, "cmd/yacc": toTool, "code.google.com/p/go.tools/cmd/cover": toTool,
-
src/cmd/objdump/main.c
:- C言語で書かれた既存の
objdump
の実装ファイルが完全に削除されました。
--- a/src/cmd/objdump/main.c +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -/* - * objdump simulation - only enough to make pprof work on Macs - */ - -#include <u.h> -#include <libc.h> -#include <bio.h> -#include <mach.h> - -void -usage(void) -{ - fprint(2, "usage: objdump binary start stop\n"); - fprint(2, "Disassembles binary from PC start up to stop.\n"); - exits("usage"); -} - -void -main(int argc, char **argv) -{ - int fd, n; - uvlong pc, start, stop; - Fhdr fhdr; - Biobuf bout; - char buf[1024]; - Map *text; - - ARGBEGIN{ - default: - usage(); - }ARGEND - - if(argc != 3) - usage(); - start = strtoull(argv[1], 0, 16); - stop = strtoull(argv[2], 0, 16); - - fd = open(argv[0], OREAD); - if(fd < 0) - sysfatal("open %s: %r", argv[0]); - if(crackhdr(fd, &fhdr) <= 0) - sysfatal("crackhdr: %r"); - machbytype(fhdr.type); - if(syminit(fd, &fhdr) <= 0) - sysfatal("syminit: %r"); - text = loadmap(nil, fd, &fhdr); - if(text == nil) - sysfatal("loadmap: %r"); - - Binit(&bout, 1, OWRITE); - for(pc=start; pc<stop; ) { - if(fileline(buf, sizeof buf, pc)) - Bprint(&bout, "%s\n", buf); - buf[0] = '\0'; - machdata->das(text, pc, 0, buf, sizeof buf); - Bprint(&bout, " %llx: %s\n", pc, buf); - n = machdata->instsize(text, pc); - if(n <= 0) - break; - pc += n; - } - Bflush(&bout); - exits(0); -}
- C言語で書かれた既存の
-
src/cmd/objdump/main.go
:- Go言語による新しい
objdump
の実装ファイルが追加されました。 - このファイルが、バイナリの解析、シンボル情報の抽出、そして基本的な逆アセンブル(PC-Lineマッピングに基づく)のロジックを含んでいます。
--- /dev/null +++ b/src/cmd/objdump/main.go @@ -0,0 +1,162 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// objdump simulation - only enough to make pprof work on Macs + +package main + +import ( + "bufio" + "debug/elf" + "debug/gosym" + "debug/macho" + "debug/pe" + "flag" + "fmt" + "log" + "os" + "strconv" +) + +func printUsage(w *os.File) { + fmt.Fprintf(w, "usage: objdump binary start end\n") + fmt.Fprintf(w, "disassembles binary from start PC to end PC.\n") +} + +func usage() { + printUsage(os.Stderr) + os.Exit(2) +} + +func main() { + log.SetFlags(0) + log.SetPrefix("objdump: ") + + flag.Usage = usage + flag.Parse() + if flag.NArg() != 3 { + usage() + } + + f, err := os.Open(flag.Arg(0)) + if err != nil { + log.Fatal(err) + } + + textStart, textData, symtab, pclntab, err := loadTables(f) + if err != nil { + log.Fatalf("reading %s: %v", flag.Arg(0), err) + } + + pcln := gosym.NewLineTable(pclntab, textStart) + tab, err := gosym.NewTable(symtab, pcln) + if err != nil { + log.Fatalf("reading %s: %v", flag.Arg(0), err) + } + + start, err := strconv.ParseUint(flag.Arg(1), 0, 64) + if err != nil { + log.Fatalf("invalid start PC: %v", err) + } + end, err := strconv.ParseUint(flag.Arg(2), 0, 64) + if err != nil { + log.Fatalf("invalid end PC: %v", err) + } + + stdout := bufio.NewWriter(os.Stdout) + + // For now, find spans of same PC/line/fn and + // emit them as having dummy instructions. + var ( + spanPC uint64 + spanFile string + spanLine int + spanFn *gosym.Func + ) + + flush := func(endPC uint64) { + if spanPC == 0 { + return + } + fmt.Fprintf(stdout, "%s:%d\n", spanFile, spanLine) + for pc := spanPC; pc < endPC; pc++ { + // TODO(rsc): Disassemble instructions here. + if textStart <= pc && pc-textStart < uint64(len(textData)) { + fmt.Fprintf(stdout, " %x: byte %#x\n", pc, textData[pc-textStart]) + } else { + fmt.Fprintf(stdout, " %x: ?\n", pc) + } + } + spanPC = 0 + } + + for pc := start; pc < end; pc++ { + file, line, fn := tab.PCToLine(pc) + if file != spanFile || line != spanLine || fn != spanFn { + flush(pc) + spanPC, spanFile, spanLine, spanFn = pc, file, line, fn + } + } + flush(end) + + stdout.Flush() +} + +func loadTables(f *os.File) (textStart uint64, textData, symtab, pclntab []byte, err error) { + if obj, err := elf.NewFile(f); err == nil { + if sect := obj.Section(".text"); sect != nil { + textStart = sect.Addr + textData, _ = sect.Data() + } + if sect := obj.Section(".gosymtab"); sect != nil { + if symtab, err = sect.Data(); err != nil { + return 0, nil, nil, nil, err + } + } + if sect := obj.Section(".gopclntab"); sect != nil { + if pclntab, err = sect.Data(); err != nil { + return 0, nil, nil, nil, err + } + } + return textStart, textData, symtab, pclntab, nil + } + + if obj, err := macho.NewFile(f); err == nil { + if sect := obj.Section("__text"); sect != nil { + textStart = sect.Addr + textData, _ = sect.Data() + } + if sect := obj.Section("__gosymtab"); sect != nil { + if symtab, err = sect.Data(); err != nil { + return 0, nil, nil, nil, err + } + } + if sect := obj.Section("__gopclntab"); sect != nil { + if pclntab, err = sect.Data(); err != nil { + return 0, nil, nil, nil, err + } + } + return textStart, textData, symtab, pclntab, nil + } + + if obj, err := pe.NewFile(f); err == nil { + if sect := obj.Section(".text"); sect != nil { + textStart = uint64(sect.VirtualAddress) + textData, _ = sect.Data() + } + if sect := obj.Section(".gosymtab"); sect != nil { + if symtab, err = sect.Data(); err != nil { + return 0, nil, nil, nil, err + } + } + if sect := obj.Section(".gopclntab"); sect != nil { + if pclntab, err = sect.Data(); err != nil { + return 0, nil, nil, nil, err + } + } + return textStart, textData, symtab, pclntab, nil + } + + return 0, nil, nil, nil, fmt.Errorf("unrecognized binary format") +}
- Go言語による新しい
コアとなるコードの解説
src/cmd/objdump/main.go
の主要な部分を解説します。
main
関数
main
関数はobjdump
コマンドのエントリポイントです。
- 引数解析:
flag
パッケージを使用してコマンドライン引数を解析します。objdump
はbinary start end
の3つの引数を期待します。binary
は解析対象の実行可能ファイル、start
とend
は逆アセンブルするPC(プログラムカウンタ)の範囲を指定します。 - ファイルオープン: 指定されたバイナリファイルを開きます。
- テーブルのロード:
loadTables
関数を呼び出し、バイナリファイルからコードセクション、Goシンボルテーブル、PC-Lineテーブルの生データを抽出します。 - Goシンボルテーブルの構築: 抽出した生データと
debug/gosym
パッケージを使用して、gosym.NewLineTable
とgosym.NewTable
を構築します。これにより、PCからファイル名、行番号、関数名へのマッピングが可能になります。 - PC範囲の解析: コマンドライン引数で指定された
start
とend
のPC値をuint64
に変換します。 - 出力処理:
bufio.NewWriter(os.Stdout)
を使用して標準出力への書き込みをバッファリングします。for pc := start; pc < end; pc++
ループで、指定されたPC範囲を1バイトずつ(または命令サイズずつ、将来的には)走査します。tab.PCToLine(pc)
を呼び出して、現在のPCに対応するファイル名、行番号、関数名を取得します。spanPC
,spanFile
,spanLine
,spanFn
変数を使って、同じファイル/行/関数に属するPCの連続した範囲(span
)を追跡します。flush
関数は、span
が変更されたとき(つまり、ファイル、行、または関数が変わったとき)に呼び出されます。flush
関数は、現在のspan
のファイル名と行番号を出力し、そのspan
内の各PCに対して「ダミーの命令」(バイトコードまたは?
)を出力します。- ループの最後に
flush(end)
を呼び出すことで、最後のspan
も出力されます。 stdout.Flush()
でバッファリングされた内容を実際に出力します。
loadTables
関数
loadTables
関数は、Go言語版objdump
の核心部分であり、様々な実行可能ファイル形式から必要な情報を抽出する役割を担います。
-
ELF形式の解析:
elf.NewFile(f)
を試みます。成功した場合、ELFファイルとして解析を進めます。.text
セクションからtextStart
(開始アドレス)とtextData
(生データ)を取得します。.gosymtab
セクションからsymtab
(Goシンボルテーブル)を取得します。.gopclntab
セクションからpclntab
(Go PC-Lineテーブル)を取得します。- これらの情報が取得できれば、それを返します。
-
Mach-O形式の解析: ELFとしての解析が失敗した場合、
macho.NewFile(f)
を試みます。成功した場合、Mach-Oファイルとして解析を進めます。__text
セクションからtextStart
とtextData
を取得します。__gosymtab
セクションからsymtab
を取得します。__gopclntab
セクションからpclntab
を取得します。- これらの情報が取得できれば、それを返します。
-
PE形式の解析: Mach-Oとしての解析も失敗した場合、
pe.NewFile(f)
を試みます。成功した場合、PEファイルとして解析を進めます。.text
セクションからtextStart
(VirtualAddress
を使用)とtextData
を取得します。.gosymtab
セクションからsymtab
を取得します。.gopclntab
セクションからpclntab
を取得します。- これらの情報が取得できれば、それを返します。
-
未認識の形式: いずれの形式としても認識できなかった場合、
"unrecognized binary format"
というエラーを返します。
このloadTables
関数のおかげで、Go言語版objdump
は、Linux (ELF), macOS (Mach-O), Windows (PE) など、異なるプラットフォームでビルドされたGoバイナリを透過的に処理できるようになっています。
関連リンク
- Go Issue #7452:
cmd/objdump: rewrite in Go
- このコミットが解決または関連するGoのIssueトラッカーのエントリです。詳細な議論や背景情報が含まれている可能性があります。
- Go CL 87580043:
https://golang.org/cl/87580043
- このコミットに対応するGerrit Code Reviewのチェンジリストです。コードレビューのコメントや、より詳細な変更履歴を確認できます。
参考にした情報源リンク
- Go言語の公式ドキュメント
debug/elf
パッケージのドキュメント: https://pkg.go.dev/debug/elfdebug/macho
パッケージのドキュメント: https://pkg.go.dev/debug/machodebug/pe
パッケージのドキュメント: https://pkg.go.dev/debug/pedebug/gosym
パッケージのドキュメント: https://pkg.go.dev/debug/gosympprof
ツールのドキュメント: https://pkg.go.dev/runtime/pprofobjdump
コマンドに関する一般的な情報(GNU Binutilsなど)- ELF, Mach-O, PEファイル形式に関する一般的な情報```markdown
[インデックス 19122] ファイルの概要
このコミットは、Go言語のツールチェインに含まれるobjdump
コマンドを、C言語で書かれた既存の実装からGo言語による新しい実装へと書き換えるものです。これにより、objdump
はGoツールチェインとの統合が強化され、クロスプラットフォームでの利用が容易になります。特に、pprof
ツールが提供するlist
およびweblist
コマンドの基本的な機能サポートを目的としています。
コミット
commit 0d441a088d2cb23af32dae473aea989830d11055
Author: Russ Cox <rsc@golang.org>
Date: Mon Apr 14 10:58:49 2014 -0400
cmd/objdump: rewrite in Go
Update cmd/dist not to build the C version.
Update cmd/go to install the Go version to the tool directory.
Update #7452
This is the basic logic needed for objdump, and it works well enough
to support the pprof list and weblist commands. A real disassembler
needs to be added in order to support the pprof disasm command
and the per-line assembly displays in weblist. That's still to come.
Probably objdump will move to go.tools when the disassembler
is added, but it can stay here for now.
LGTM=minux.ma
R=golang-codereviews, minux.ma
CC=golang-codereviews, iant, r
https://golang.org/cl/87580043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0d441a088d2cb23af32dae473aea989830d11055
元コミット内容
cmd/objdump: rewrite in Go
Update cmd/dist not to build the C version.
Update cmd/go to install the Go version to the tool directory.
Update #7452
This is the basic logic needed for objdump, and it works well enough
to support the pprof list and weblist commands. A real disassembler
needs to be added in order to support the pprof disasm command
and the per-line assembly displays in weblist. That's still to come.
Probably objdump will move to go.tools when the disassembler
is added, but it can stay here for now.
LGTM=minux.ma
R=golang-codereviews, minux.ma
CC=golang-codereviews, iant, r
https://golang.org/cl/87580043
変更の背景
この変更の主な背景には、Go言語のツールチェインにおけるobjdump
コマンドの統合と、クロスプラットフォーム対応の改善があります。
元々、objdump
はC言語で実装されており、特にmacOSのような特定のプラットフォームでは、pprof
ツールが正しく機能するために必要な情報(シンボル情報や行番号情報など)を抽出する上で課題がありました。C言語のコードはプラットフォーム固有の依存関係を持つことが多く、Goのクロスプラットフォーム開発の哲学とは相容れない部分がありました。
Go言語でobjdump
を再実装することで、以下の利点が得られます。
- クロスプラットフォーム互換性: Go言語は強力なクロスコンパイル機能を持ち、単一のコードベースから様々なOSやアーキテクチャ向けのバイナリを生成できます。これにより、
objdump
もGoツールチェインの一部として、より広範な環境で一貫して動作するようになります。 - Goツールチェインとの統合:
objdump
をGoで書くことで、Goの標準ライブラリ(特にdebug/elf
,debug/macho
,debug/pe
,debug/gosym
など)を直接利用できるようになり、Goのバイナリ形式やデバッグ情報との連携がより密になります。 - メンテナンス性の向上: Go言語はC言語に比べてメモリ安全性や並行処理の扱いが容易であり、コードの可読性も高い傾向にあります。これにより、将来的な機能追加やバグ修正がより効率的に行えるようになります。
pprof
の機能強化:pprof
はGoプログラムのプロファイリングを行うための重要なツールです。objdump
はpprof
がバイナリのシンボル情報やアセンブリコードを解析するために利用されます。このコミットでは、特にpprof list
(ソースコードとアセンブリの対応リスト表示)とpprof weblist
(Webベースのソース/アセンブリ表示)の基本的なサポートを目的としています。将来的には、完全な逆アセンブル機能を追加することで、pprof disasm
(完全な逆アセンブル表示)もサポートする計画です。
この変更は、Go Issue #7452に関連しており、Goツールチェイン全体の堅牢性と使いやすさを向上させる一環として行われました。
前提知識の解説
objdump
とは
objdump
は、オブジェクトファイルや実行可能ファイルの内容を解析し、人間が読める形式で表示するためのコマンドラインツールです。一般的な機能としては以下のようなものがあります。
- 逆アセンブル (Disassembly): 機械語コードをアセンブリ言語に変換して表示します。これにより、プログラムが実際にどのように動作しているかを低レベルで理解できます。
- セクション情報の表示: 実行可能ファイル内の様々なセクション(例:
.text
(コード),.data
(初期化済みデータ),.bss
(初期化なしデータ),.symtab
(シンボルテーブル)など)の情報を表示します。 - シンボルテーブルの表示: プログラム内の関数名、変数名などのシンボルとそのアドレス情報を表示します。デバッグやプロファイリングにおいて、特定のコードがどこにあるかを特定するのに役立ちます。
- ヘッダ情報の表示: 実行可能ファイルの形式(ELF, Mach-O, PEなど)やアーキテクチャに関する情報を表示します。
このコミットにおけるobjdump
は、特にGoプログラムのバイナリを解析し、pprof
ツールが利用するシンボル情報や行番号情報を抽出する役割を担います。
pprof
とは
pprof
は、Go言語の標準的なプロファイリングツールです。CPU使用率、メモリ割り当て、ゴルーチンスタックトレースなど、様々な種類のプロファイルデータを収集し、視覚化することができます。pprof
は、Goプログラムのパフォーマンスボトルネックを特定し、最適化を行う上で不可欠なツールです。
pprof
の主な機能には以下のようなものがあります。
- プロファイルデータの収集:
runtime/pprof
パッケージやHTTPインターフェースを通じて、実行中のGoプログラムからプロファイルデータを収集します。 - プロファイルデータの解析と視覚化: 収集したプロファイルデータを解析し、テキスト形式、グラフ(Call Graph, Flame Graphなど)、Webインターフェースなど、様々な形式で表示します。
list
コマンド: ソースコードと対応するアセンブリコードを並べて表示し、どの行がどれだけCPU時間を使っているかなどを視覚的に確認できます。weblist
コマンド:list
コマンドのWebインターフェース版で、ブラウザでソースコードとアセンブリコードの対応を確認できます。disasm
コマンド: 特定の関数のアセンブリコードを完全に逆アセンブルして表示します。
このコミットでobjdump
がGoで書き直されたのは、pprof
のlist
およびweblist
コマンドがGoバイナリのシンボル情報や行番号情報を正確に取得できるようにするためです。
Goツールチェインの構成要素
cmd/dist
: Goのビルドシステムの一部であり、Goツールチェインの様々なコンポーネント(コンパイラ、リンカ、その他のツール)をビルドする役割を担います。このコミットでは、objdump
のC言語版をビルド対象から除外する変更が行われました。cmd/go
: Goコマンドラインツールの中核であり、Goプログラムのビルド、テスト、実行、パッケージ管理など、開発者が日常的に使用するほとんどの操作を制御します。このコミットでは、Goで書き直されたobjdump
をGoツールチェインのtool
ディレクトリにインストールするよう設定が変更されました。
C言語とGo言語の比較(この文脈において)
- C言語: システムプログラミングに適しており、ハードウェアに密接にアクセスできます。しかし、メモリ管理(ポインタ、手動でのメモリ解放)が複雑で、メモリリークやセグメンテーション違反などのバグが発生しやすいという側面があります。また、クロスプラットフォーム開発においては、プラットフォーム固有のAPIやコンパイラ、ライブラリの依存関係を管理する必要があります。
- Go言語: Googleによって開発された静的型付けのコンパイル言語で、シンプルさ、効率性、並行処理のサポートを重視しています。ガベージコレクションによる自動メモリ管理、強力な標準ライブラリ、優れたクロスコンパイル機能が特徴です。システムプログラミングにも利用されますが、C言語よりも抽象度が高く、安全性が高いとされています。
objdump
のようなバイナリ解析ツールをGoで書き直すことは、C言語の持つ低レベルな制御能力の一部を犠牲にするかもしれませんが、Goの持つクロスプラットフォーム性、メモリ安全性、そしてGoツールチェインとのシームレスな統合という大きなメリットを享受できます。
Goの標準ライブラリパッケージ
このコミットで新しくGo言語で実装されたobjdump
は、Goの標準ライブラリの以下のパッケージを多用しています。
debug/elf
: ELF (Executable and Linkable Format) 形式の実行可能ファイルやオブジェクトファイルを解析するためのパッケージです。LinuxやUnix系システムで広く使われています。debug/macho
: Mach-O (Mach Object) 形式の実行可能ファイルやオブジェクトファイルを解析するためのパッケージです。macOSやiOSで使われています。debug/pe
: PE (Portable Executable) 形式の実行可能ファイルやオブジェクトファイルを解析するためのパッケージです。Windowsで使われています。debug/gosym
: Goバイナリに埋め込まれたGo固有のシンボルテーブル(gosymtab
)とPC-Lineテーブル(gopclntab
)を解析するためのパッケージです。これにより、実行アドレス(PC)からファイル名、行番号、関数名へのマッピングが可能になります。
これらのパッケージを利用することで、Goで書かれたobjdump
は、異なるOSで生成されたGoバイナリを統一的な方法で解析できるようになります。
技術的詳細
新しいGo言語版のobjdump
(src/cmd/objdump/main.go
) は、主に以下の機能を提供します。
-
バイナリ形式の自動判別と解析:
loadTables
関数は、入力されたバイナリファイルがELF、Mach-O、PEのいずれの形式であるかを自動的に判別し、それぞれの形式に対応するGoのdebug/elf
,debug/macho
,debug/pe
パッケージを使用して解析します。 この関数は、バイナリから以下の情報を抽出します。textStart
: コードセクション(.text
)の開始アドレス。textData
: コードセクションの生データ。symtab
: Go固有のシンボルテーブル(.gosymtab
)の生データ。pclntab
: Go固有のPC-Lineテーブル(.gopclntab
)の生データ。
-
Goシンボル情報の利用: 抽出された
symtab
とpclntab
は、debug/gosym
パッケージのgosym.NewLineTable
とgosym.NewTable
を使用して、Goのシンボル情報とPC-Lineマッピングを構築するために利用されます。これにより、特定のアドレスがどのファイル、どの行、どの関数に属するかを正確に特定できます。 -
基本的な逆アセンブル機能(暫定): 現在の実装では、完全な命令の逆アセンブルは行いません。代わりに、
pprof list
やweblist
が要求するPC(プログラムカウンタ)範囲に対して、ファイル名、行番号、関数名を基に「ダミーの命令」を出力します。具体的には、同じファイル、行、関数に属するPCの連続した範囲をspan
として扱い、その範囲内の各PCに対して、対応するバイトコード(textData
から取得可能であれば)または?
を表示します。 コミットメッセージにもあるように、これはpprof list
とweblist
の基本的な動作をサポートするためのものであり、pprof disasm
のような完全な逆アセンブル機能や、より詳細なアセンブリ表示には、将来的に「真の逆アセンブラ」の実装が必要とされています。 -
Goツールチェインとの連携:
src/cmd/dist/build.c
からC言語版objdump
のビルドとクリーンアップに関する記述が削除されました。これは、GoツールチェインがC言語版objdump
をビルドしなくなることを意味します。src/cmd/go/pkg.go
のgoTools
マップに"cmd/objdump": toTool
が追加されました。これにより、go install cmd/objdump
コマンドがGo言語版のobjdump
をGoのツールディレクトリ(通常は$GOPATH/bin
または$GOBIN
)にインストールするようになります。
この変更により、Goのobjdump
はGoツールチェインのネイティブな一部となり、Goプログラムのデバッグとプロファイリングのワークフローが改善されます。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は以下の4つのファイルに集中しています。
-
src/cmd/dist/build.c
:buildorder
配列とcleantab
配列から"cmd/objdump"
のエントリが削除されました。- これは、GoツールチェインのビルドプロセスからC言語版の
objdump
が除外されることを意味します。
--- a/src/cmd/dist/build.c +++ b/src/cmd/dist/build.c @@ -1332,7 +1332,6 @@ static char *buildorder[] = { "misc/pprof", - "cmd/objdump", "cmd/prof", "cmd/cc", // must be before c @@ -1409,7 +1408,6 @@ static char *cleantab[] = { "cmd/cc", "cmd/gc", "cmd/go", - "cmd/objdump", "cmd/prof", "lib9", "libbio",
-
src/cmd/go/pkg.go
:goTools
マップに"cmd/objdump": toTool
が追加されました。- これにより、Go言語版の
objdump
がGoツールチェインの標準ツールとして認識され、go install
コマンドでインストールできるようになります。
--- a/src/cmd/go/pkg.go +++ b/src/cmd/go/pkg.go @@ -313,6 +313,7 @@ var goTools = map[string]targetDir{ "cmd/fix": toTool, "cmd/link": toTool, "cmd/nm": toTool, +\t"cmd/objdump": toTool, "cmd/pack": toTool, "cmd/yacc": toTool, "code.google.com/p/go.tools/cmd/cover": toTool,
-
src/cmd/objdump/main.c
:- C言語で書かれた既存の
objdump
の実装ファイルが完全に削除されました。
--- a/src/cmd/objdump/main.c +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -/* - * objdump simulation - only enough to make pprof work on Macs - */ - -#include <u.h> -#include <libc.h> -#include <bio.h> -#include <mach.h> - -void -usage(void) -{ - fprint(2, "usage: objdump binary start stop\n"); - fprint(2, "Disassembles binary from PC start up to stop.\n"); - exits("usage"); -} - -void -main(int argc, char **argv) -{ - int fd, n; - uvlong pc, start, stop; - Fhdr fhdr; - Biobuf bout; - char buf[1024]; - Map *text; - - ARGBEGIN{ - default: - usage(); - }ARGEND - - if(argc != 3) - usage(); - start = strtoull(argv[1], 0, 16); - stop = strtoull(argv[2], 0, 16); - - fd = open(argv[0], OREAD); - if(fd < 0) - sysfatal("open %s: %r", argv[0]); - if(crackhdr(fd, &fhdr) <= 0) - sysfatal("crackhdr: %r"); - machbytype(fhdr.type); - if(syminit(fd, &fhdr) <= 0) - sysfatal("syminit: %r"); - text = loadmap(nil, fd, &fhdr); - if(text == nil) - sysfatal("loadmap: %r"); - - Binit(&bout, 1, OWRITE); - for(pc=start; pc<stop; ) { - if(fileline(buf, sizeof buf, pc)) - Bprint(&bout, "%s\n", buf); - buf[0] = '\0'; - machdata->das(text, pc, 0, buf, sizeof buf); - Bprint(&bout, " %llx: %s\n", pc, buf); - n = machdata->instsize(text, pc); - if(n <= 0) - break; - pc += n; - } - Bflush(&bout); - exits(0); -}
- C言語で書かれた既存の
-
src/cmd/objdump/main.go
:- Go言語による新しい
objdump
の実装ファイルが追加されました。 - このファイルが、バイナリの解析、シンボル情報の抽出、そして基本的な逆アセンブル(PC-Lineマッピングに基づく)のロジックを含んでいます。
--- /dev/null +++ b/src/cmd/objdump/main.go @@ -0,0 +1,162 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// objdump simulation - only enough to make pprof work on Macs + +package main + +import ( + "bufio" + "debug/elf" + "debug/gosym" + "debug/macho" + "debug/pe" + "flag" + "fmt" + "log" + "os" + "strconv" +) + +func printUsage(w *os.File) { + fmt.Fprintf(w, "usage: objdump binary start end\n") + fmt.Fprintf(w, "disassembles binary from start PC to end PC.\n") +} + +func usage() { + printUsage(os.Stderr) + os.Exit(2) +} + +func main() { + log.SetFlags(0) + log.SetPrefix("objdump: ") + + flag.Usage = usage + flag.Parse() + if flag.NArg() != 3 { + usage() + } + + f, err := os.Open(flag.Arg(0)) + if err != nil { + log.Fatal(err) + } + textStart, textData, symtab, pclntab, err := loadTables(f) if err != nil { log.Fatalf("reading %s: %v", flag.Arg(0), err) } pcln := gosym.NewLineTable(pclntab, textStart) tab, err := gosym.NewTable(symtab, pcln) if err != nil { log.Fatalf("reading %s: %v", flag.Arg(0), err) } start, err := strconv.ParseUint(flag.Arg(1), 0, 64) if err != nil { log.Fatalf("invalid start PC: %v", err) } end, err := strconv.ParseUint(flag.Arg(2), 0, 64) if err != nil { log.Fatalf("invalid end PC: %v", err) } stdout := bufio.NewWriter(os.Stdout) // For now, find spans of same PC/line/fn and // emit them as having dummy instructions. var ( spanPC uint64 spanFile string spanLine int spanFn *gosym.Func ) flush := func(endPC uint64) { if spanPC == 0 { return } fmt.Fprintf(stdout, "%s:%d\n", spanFile, spanLine) for pc := spanPC; pc < endPC; pc++ { // TODO(rsc): Disassemble instructions here. if textStart <= pc && pc-textStart < uint64(len(textData)) { fmt.Fprintf(stdout, " %x: byte %#x\n", pc, textData[pc-textStart]) } else { fmt.Fprintf(stdout, " %x: ?\n", pc) } } spanPC = 0 } for pc := start; pc < end; pc++ { file, line, fn := tab.PCToLine(pc) if file != spanFile || line != spanLine || fn != spanFn { flush(pc) spanPC, spanFile, spanLine, spanFn = pc, file, line, fn } } flush(end) stdout.Flush() } func loadTables(f *os.File) (textStart uint64, textData, symtab, pclntab []byte, err error) { if obj, err := elf.NewFile(f); err == nil { if sect := obj.Section(".text"); sect != nil { textStart = sect.Addr textData, _ = sect.Data() } if sect := obj.Section(".gosymtab"); sect != nil { if symtab, err = sect.Data(); err != nil { return 0, nil, nil, nil, err } } if sect := obj.Section(".gopclntab"); sect != nil { if pclntab, err = sect.Data(); err != nil { return 0, nil, nil, nil, err } } return textStart, textData, symtab, pclntab, nil } if obj, err := macho.NewFile(f); err == nil { if sect := obj.Section("__text"); sect != nil { textStart = sect.Addr textData, _ = sect.Data() } if sect := obj.Section("__gosymtab"); sect != nil { if symtab, err = sect.Data(); err != nil { return 0, nil, nil, nil, err } } if sect := obj.Section("__gopclntab"); sect != nil { if pclntab, err = sect.Data(); err != nil { return 0, nil, nil, nil, err } } return textStart, textData, symtab, pclntab, nil } if obj, err := pe.NewFile(f); err == nil { if sect := obj.Section(".text"); sect != nil { textStart = uint64(sect.VirtualAddress) textData, _ = sect.Data() } if sect := obj.Section(".gosymtab"); sect != nil { if symtab, err = sect.Data(); err != nil { return 0, nil, nil, nil, err } } if sect := obj.Section(".gopclntab"); sect != nil { if pclntab, err = sect.Data(); err != nil { return 0, nil, nil, nil, err } } return textStart, textData, symtab, pclntab, nil } return 0, nil, nil, nil, fmt.Errorf("unrecognized binary format") }
- Go言語による新しい
コアとなるコードの解説
src/cmd/objdump/main.go
の主要な部分を解説します。
main
関数
main
関数はobjdump
コマンドのエントリポイントです。
- 引数解析:
flag
パッケージを使用してコマンドライン引数を解析します。objdump
はbinary start end
の3つの引数を期待します。binary
は解析対象の実行可能ファイル、start
とend
は逆アセンブルするPC(プログラムカウンタ)の範囲を指定します。 - ファイルオープン: 指定されたバイナリファイルを開きます。
- テーブルのロード:
loadTables
関数を呼び出し、バイナリファイルからコードセクション、Goシンボルテーブル、PC-Lineテーブルの生データを抽出します。 - Goシンボルテーブルの構築: 抽出した生データと
debug/gosym
パッケージを使用して、gosym.NewLineTable
とgosym.NewTable
を構築します。これにより、PCからファイル名、行番号、関数名へのマッピングが可能になります。 - PC範囲の解析: コマンドライン引数で指定された
start
とend
のPC値をuint64
に変換します。 - 出力処理:
bufio.NewWriter(os.Stdout)
を使用して標準出力への書き込みをバッファリングします。for pc := start; pc < end; pc++
ループで、指定されたPC範囲を1バイトずつ(または命令サイズずつ、将来的には)走査します。tab.PCToLine(pc)
を呼び出して、現在のPCに対応するファイル名、行番号、関数名を取得します。spanPC
,spanFile
,spanLine
,spanFn
変数を使って、同じファイル/行/関数に属するPCの連続した範囲(span
)を追跡します。flush
関数は、span
が変更されたとき(つまり、ファイル、行、または関数が変わったとき)に呼び出されます。flush
関数は、現在のspan
のファイル名と行番号を出力し、そのspan
内の各PCに対して「ダミーの命令」(バイトコードまたは?
)を出力します。- ループの最後に
flush(end)
を呼び出すことで、最後のspan
も出力されます。 stdout.Flush()
でバッファリングされた内容を実際に出力します。
loadTables
関数
loadTables
関数は、Go言語版objdump
の核心部分であり、様々な実行可能ファイル形式から必要な情報を抽出する役割を担います。
-
ELF形式の解析:
elf.NewFile(f)
を試みます。成功した場合、ELFファイルとして解析を進めます。.text
セクションからtextStart
(開始アドレス)とtextData
(生データ)を取得します。.gosymtab
セクションからsymtab
(Goシンボルテーブル)を取得します。.gopclntab
セクションからpclntab
(Go PC-Lineテーブル)を取得します。- これらの情報が取得できれば、それを返します。
-
Mach-O形式の解析: ELFとしての解析が失敗した場合、
macho.NewFile(f)
を試みます。成功した場合、Mach-Oファイルとして解析を進めます。__text
セクションからtextStart
とtextData
を取得します。__gosymtab
セクションからsymtab
を取得します。__gopclntab
セクションからpclntab
を取得します。- これらの情報が取得できれば、それを返します。
-
PE形式の解析: Mach-Oとしての解析も失敗した場合、
pe.NewFile(f)
を試みます。成功した場合、PEファイルとして解析を進めます。.text
セクションからtextStart
(VirtualAddress
を使用)とtextData
を取得します。.gosymtab
セクションからsymtab
を取得します。.gopclntab
セクションからpclntab
を取得します。- これらの情報が取得できれば、それを返します。
-
未認識の形式: いずれの形式としても認識できなかった場合、
"unrecognized binary format"
というエラーを返します。
このloadTables
関数のおかげで、Go言語版objdump
は、Linux (ELF), macOS (Mach-O), Windows (PE) など、異なるプラットフォームでビルドされたGoバイナリを透過的に処理できるようになっています。
関連リンク
- Go Issue #7452:
cmd/objdump: rewrite in Go
- このコミットが解決または関連するGoのIssueトラッカーのエントリです。詳細な議論や背景情報が含まれている可能性があります。
- Go CL 87580043:
https://golang.org/cl/87580043
- このコミットに対応するGerrit Code Reviewのチェンジリストです。コードレビューのコメントや、より詳細な変更履歴を確認できます。
参考にした情報源リンク
- Go言語の公式ドキュメント
debug/elf
パッケージのドキュメント: https://pkg.go.dev/debug/elfdebug/macho
パッケージのドキュメント: https://pkg.go.dev/debug/machodebug/pe
パッケージのドキュメント: https://pkg.go.dev/debug/pedebug/gosym
パッケージのドキュメント: https://pkg.go.dev/debug/gosympprof
ツールのドキュメント: https://pkg.go.dev/runtime/pprofobjdump
コマンドに関する一般的な情報(GNU Binutilsなど)- ELF, Mach-O, PEファイル形式に関する一般的な情報