[インデックス 19356] ファイルの概要
このコミットは、Go言語のcmd/objdump
ツールにx86アーキテクチャ用のディスアセンブラを導入するものです。これにより、objdump
ツールがx86バイナリの機械語命令を人間が読めるアセンブリコードに逆アセンブルする機能を持つようになります。
コミット
cmd/objdump: import x86 disassembler
The x86 disassembler lives in rsc.io/x86/x86asm for now.
We need to figure out what should live where in the long term,
but not before the 1.3 release.
The completed code reviews for the disassembler are at:
https://golang.org/cl/95350044
https://golang.org/cl/95300044
https://golang.org/cl/97100047
https://golang.org/cl/93110044
https://golang.org/cl/99000043
https://golang.org/cl/98990043
LGTM=crawshaw
R=crawshaw, jacek.masiulaniec
CC=golang-codereviews
https://golang.org/cl/92360043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e7ad1ebeac01ebfbc36b9848b05aa45712b3e2a2
元コミット内容
commit e7ad1ebeac01ebfbc36b9848b05aa45712b3e2a2
Author: Russ Cox <rsc@golang.org>
Date: Wed May 14 19:46:53 2014 -0400
cmd/objdump: import x86 disassembler
The x86 disassembler lives in rsc.io/x86/x86asm for now.
We need to figure out what should live where in the long term,
but not before the 1.3 release.
The completed code reviews for the disassembler are at:
https://golang.org/cl/95350044
https://golang.org/cl/95300044
https://golang.org/cl/97100047
https://golang.org/cl/93110044
https://golang.org/cl/99000043
https://golang.org/cl/98990043
LGTM=crawshaw
R=crawshaw, jacek.masiulaniec
CC=golang-codereviews
https://golang.org/cl/92360043
変更の背景
このコミットの主な背景は、Go言語の標準ツールであるobjdump
にx86アーキテクチャのディスアセンブル機能を追加することです。Go言語はクロスコンパイルをサポートしており、様々なアーキテクチャ向けのバイナリを生成できます。objdump
はこれらのバイナリを解析し、機械語命令をアセンブリコードに変換して表示するための重要なツールです。
以前は、Goのobjdump
は特定のアーキテクチャ(例えばGoが動作するホストアーキテクチャ)に限定されたディスアセンブル機能しか持っていなかったか、あるいはx86に対する十分なサポートがありませんでした。このコミットにより、objdump
はx86バイナリ(Goプログラムだけでなく、一般的なx86バイナリも含む)を正確にディスアセンブルできるようになります。
コミットメッセージには「The x86 disassembler lives in rsc.io/x86/x86asm for now. We need to figure out what should live where in the long term, but not before the 1.3 release.」とあります。これは、このディスアセンブラが一時的にrsc.io/x86/x86asm
という外部パッケージとして開発され、Go 1.3リリース前にGo本体のcmd/objdump
に取り込まれたことを示唆しています。長期的な配置については、Go 1.3リリース後に検討される予定でした。
この機能追加は、Go開発者が生成されたバイナリの低レベルな動作を理解したり、デバッグしたりする上で非常に役立ちます。特に、パフォーマンスの最適化や、コンパイラが生成するコードの検証において不可欠なツールとなります。
前提知識の解説
このコミットを理解するためには、以下の前提知識が必要です。
-
ディスアセンブラ (Disassembler): 機械語(バイナリコード)を人間が読めるアセンブリ言語に変換するツールです。CPUが直接実行する命令列を、対応するニーモニック(例:
MOV
,ADD
,JMP
)とオペランドに変換します。デバッグ、リバースエンジニアリング、セキュリティ分析などに用いられます。 -
x86アーキテクチャ: Intel 8086プロセッサから続く、広く普及している命令セットアーキテクチャ(ISA)です。デスクトップPCやサーバーで主に使用されます。x86命令セットは非常に複雑で、可変長の命令、多数のプレフィックス、多様なアドレッシングモード(ModR/M、SIBバイトなど)が特徴です。
-
x86命令の構造: x86命令は、一般的に以下の要素で構成されます。
- プレフィックス (Prefixes): 命令の動作を変更するオプションのバイト列です。
- REXプレフィックス (REX Prefix): 64ビットモードでのみ使用され、レジスタの拡張(R8-R15など)、オペランドサイズの指定(REX.W)、SIBバイトやModR/Mバイトの拡張などに使われます。
- セグメントオーバーライドプレフィックス (Segment Override Prefixes): メモリ参照の際に使用するセグメントレジスタ(CS, DS, ES, FS, GS, SS)を明示的に指定します。
- オペランドサイズオーバーライドプレフィックス (Operand Size Override Prefix -
0x66
): デフォルトのオペランドサイズ(例: 32ビットモードでの32ビット)を切り替えます(例: 16ビット)。 - アドレスサイズオーバーライドプレフィックス (Address Size Override Prefix -
0x67
): デフォルトのアドレスサイズ(例: 32ビットモードでの32ビット)を切り替えます(例: 16ビット)。 - 繰り返しプレフィックス (Repeat Prefixes -
0xF2
,0xF3
): 文字列操作命令などで繰り返し動作を指定します(例:REP
,REPE
,REPNE
)。 - ロックプレフィックス (LOCK Prefix -
0xF0
): メモリ操作をアトミックに行うことを保証します。
- オペコード (Opcode): 命令の種類を示すバイトです。1バイトから3バイト以上になることもあります。
- ModR/Mバイト (ModR/M Byte): オペランドがレジスタかメモリか、どのアドレッシングモードを使用するか、どのレジスタが関与するかなどを指定します。
Mod
(2ビット): オペランドがレジスタか、またはメモリ参照の場合のディスプレースメントの有無とサイズ。Reg/Opcode
(3ビット): レジスタオペランド、またはオペコードの拡張。R/M
(3ビット): レジスタオペランド、またはメモリ参照のベースレジスタ/SIBバイトの有無。
- SIBバイト (Scale-Index-Base Byte): ModR/Mバイトが特定のメモリ参照(ベースレジスタ + インデックスレジスタ * スケール + ディスプレースメント)を示す場合に存在します。
Scale
(2ビット): インデックスレジスタに乗算するスケールファクタ(1, 2, 4, 8)。Index
(3ビット): インデックスレジスタ。Base
(3ビット): ベースレジスタ。
- ディスプレースメント (Displacement): メモリ参照のオフセット値。1, 2, 4バイトのいずれか。
- イミディエイト (Immediate): 命令に直接埋め込まれる定数値。1, 2, 4, 8バイトのいずれか。
- プレフィックス (Prefixes): 命令の動作を変更するオプションのバイト列です。
-
Go言語の
cmd
パッケージ: Goの標準ツール群(コンパイラ、リンカ、アセンブラ、デバッガなど)が格納されているパッケージです。cmd/objdump
もその一つです。 -
Goのコード生成ツール (
bundle
): このコミットではbundle
というツールが使われています。これは、Goのソースコードを別のGoソースコードに埋め込む(バンドルする)ためのツールです。外部パッケージのコードをGo本体のリポジトリに直接取り込む際に利用されます。これにより、依存関係の管理が簡素化され、ビルドプロセスが安定します。
これらの知識は、x86.go
ファイル内のコードがどのようにx86命令を解析し、各バイトの意味を解釈しているかを理解する上で不可欠です。
技術的詳細
このコミットの技術的詳細の核心は、x86命令の複雑なデコードロジックをGo言語で実装している点にあります。特に注目すべきは、可変長命令、多数のプレフィックス、ModR/MおよびSIBバイトの解釈、そして異なるCPUモード(16/32/64ビット)への対応です。
src/cmd/objdump/x86.go
ファイルは、主に以下の要素で構成されています。
-
命令デコードのバイトコードプログラム:
x86_decodeOp
型とそれに対応する定数(x86_xFail
,x86_xMatch
,x86_xJump
,x86_xCondByte
など)は、x86命令のデコードプロセスを記述するためのカスタムバイトコード命令セットを定義しています。このバイトコードは、命令ストリームからバイトを読み込み、条件分岐を行い、オペランドを解釈するためのものです。x86_decoder
というグローバル変数がこのバイトコードプログラムを保持しており、x86_Decode
関数がこのプログラムを実行して命令をデコードします。このアプローチは、複雑な命令セットのデコードロジックを、より構造化された、テーブル駆動型またはバイトコード駆動型で表現するための一般的な手法です。 -
プレフィックスの処理: x86命令のデコードにおいて、プレフィックスの処理は最も複雑な部分の一つです。
x86_Decode
関数は、まず命令の先頭からプレフィックスを読み込みます。0xF0
(LOCK),0xF2
(REPN),0xF3
(REP),0x26
,0x2E
,0x36
,0x3E
,0x64
,0x65
(セグメントオーバーライド),0x66
(オペランドサイズオーバーライド),0x67
(アドレスサイズオーバーライド) などの各プレフィックスが識別され、inst.Prefix
配列に格納されます。- 64ビットモードでは、REXプレフィックス(
0x40
から0x4F
)も処理されます。REXプレフィックスは、レジスタの拡張や64ビットオペランドサイズ(REX.W)の指定に用いられます。 - 複数のプレフィックスが存在する場合の優先順位や、意味を持たないプレフィックスの扱い(無視されるか、例外を発生させるか)に関する複雑なロジックが実装されています。特に、
x86_xCondPrefix
のセクションでは、F2/F3プレフィックスと66hプレフィックスの競合に関する詳細なコメントがあり、Intel XEDとGNU libopcodesの動作の違いが考慮されています。
-
ModR/MおよびSIBバイトの解析:
x86_xCondSlashR
やx86_xReadSlashR
などのデコードオペレーションがModR/Mバイトの読み込みと解析をトリガーします。- ModR/Mバイトから
mod
,regop
,rm
フィールドが抽出されます。 mod
とrm
の値に基づいて、オペランドがレジスタ参照なのか、あるいはメモリ参照なのかが判断されます。- メモリ参照の場合、アドレスモード(16/32/64ビット)に応じて、ディスプレースメントの読み込みや、SIBバイトの有無がチェックされます。
- SIBバイトが存在する場合、
scale
,index
,base
フィールドが抽出され、メモリのアドレッシング計算に利用されます。
-
イミディエイト値の読み込み:
x86_xReadIb
,x86_xReadIw
,x86_xReadId
,x86_xReadIo
などのオペレーションにより、命令に埋め込まれたイミディエイト値(即値)がバイトストリームから読み込まれます。リトルエンディアン形式で読み込まれることが明示されています。 -
命令の正規化と特殊ケースの処理:
- デコードされた命令は
x86_Inst
構造体に格納されます。 XCHG EAX, EAX
がNOP
として扱われるなど、特定の命令の特殊な振る舞いが後処理で調整されます。- 文字列操作命令(
MOVSB
,LODSB
など)や入出力命令(INS
,OUTS
)のように、暗黙的なオペランドを持つ命令に対して、それらのオペランドが明示的に追加されます。 - 条件分岐命令に対するブランチ予測プレフィックス(
0x2E
,0x3E
)の解釈や、Intel TSX (Transactional Synchronization Extensions) 関連のXACQUIRE
/XRELEASE
プレフィックスの処理も含まれています。
- デコードされた命令は
-
GNUアセンブラ構文の生成 (
gnu.go
部分):x86_GNUSyntax
関数は、デコードされた命令をGNUアセンブラ(AT&T構文)形式の文字列に変換します。- オペコード名の調整(例:
FDIV
とFDIVR
の入れ替え)。 MONITOR
,MWAIT
などの命令に対する暗黙的な引数の追加。- プレフィックスの表示ルール調整。特に、
CRC32
やMOV
命令におけるプレフィックスの表示に関する複雑なロジックが含まれています。 - オペランドサイズを示すサフィックス(例:
b
,w
,l
,q
)の付与ロジック。レジスタオペランドからサイズが推測できる場合はサフィックスを省略するなど、GNUアセンブラの慣習に従っています。
- オペコード名の調整(例:
このディスアセンブラは、x86命令セットの膨大な複雑さを、Go言語の型システムとバイトコード駆動のアプローチで効率的に処理しようと試みていることがわかります。特に、プレフィックスの競合解決や、GNUアセンブラの出力形式に合わせるための調整は、x86ディスアセンブラ開発における一般的な課題であり、このコードがそれらに対応していることを示しています。
コアとなるコードの変更箇所
このコミットで追加された主要なファイルは以下の2つです。
-
src/cmd/objdump/Makefile
:--- /dev/null +++ b/src/cmd/objdump/Makefile @@ -0,0 +1,5 @@ +x86.go: bundle + ./bundle -p main -x x86_ rsc.io/x86/x86asm >x86.go + +bundle: + go build -o bundle code.google.com/p/rsc/cmd/bundle
このMakefileは、
x86.go
ファイルを生成するためのルールを定義しています。rsc.io/x86/x86asm
パッケージのコードをbundle
ツールを使ってx86.go
にバンドルしています。 -
src/cmd/objdump/x86.go
: このファイルは新規追加され、x86ディスアセンブラの主要なロジックを含んでいます。非常に大規模なファイルであり、その内容は以下の主要なセクションに分けられます。-
decode.go
セクション:x86_decodeOp
型と関連する定数(x86_xFail
,x86_xMatch
,x86_xJump
,x86_xCondByte
など)の定義。これらはディスアセンブラのバイトコード命令です。x86_Inst
構造体(デコードされた命令を表す)の定義。x86_Decode
関数: 実際の命令デコードロジック。プレフィックスの解析、ModR/MおよびSIBバイトの処理、イミディエイト値の読み込み、そしてバイトコードプログラムの実行が含まれます。x86_truncated
,x86_instPrefix
などのヘルパー関数。x86_ErrInvalidMode
,x86_ErrTruncated
,x86_ErrUnrecognized
などのエラー定義。x86_addr16
,x86_baseReg
,x86_prefixToSegment
,x86_fixedArg
,x86_memBytes
,x86_isCondJmp
,x86_isLoop
などの補助的なデータテーブルと関数。
-
gnu.go
セクション:x86_GNUSyntax
関数: デコードされた命令をGNUアセンブラ(AT&T)構文でフォーマットするロジック。- GNUアセンブラの出力における特殊なケース(例:
FDIV
とFDIVR
の入れ替え、MOVNTSS
の優先順位、暗黙的な引数の追加、プレフィックスの表示ルール、オペランドサイズサフィックスの付与)を処理します。
-
このコミットは、実質的にrsc.io/x86/x86asm
パッケージの大部分をcmd/objdump
に統合したものです。
コアとなるコードの解説
src/cmd/objdump/x86.go
のコアとなる部分は、x86_Decode
関数と、それを駆動するx86_decoder
バイトコードプログラムです。
x86_Decode
関数の動作フロー
-
初期化:
- 入力バイト列
src
とCPUモード(16, 32, 64ビット)を受け取ります。 - 最大命令長(15バイト)を超える場合は
src
を切り詰めます。 - プレフィックス、ModR/M、SIB、イミディエイト値などを格納するための変数を初期化します。
- 入力バイト列
-
プレフィックスの読み込み (
ReadPrefixes
ループ):- 命令の先頭からバイトを順に読み込み、それがプレフィックスであるかを判定します。
- LOCK (
0xF0
), REP/REPN (0xF2
,0xF3
), セグメントオーバーライド (0x26
,0x2E
,0x36
,0x3E
,0x64
,0x65
), オペランドサイズオーバーライド (0x66
), アドレスサイズオーバーライド (0x67
) などのプレフィックスを識別し、inst.Prefix
配列に格納します。 - 64ビットモードでは、REXプレフィックスもここで処理され、
dataMode
(オペランドサイズ)がREX.W
ビットによって64ビットに設定される場合があります。 - 複数の同じ種類のプレフィックスが存在する場合(例: 複数のREPプレフィックス)、後続のプレフィックスが以前のものを上書きする(または無視する)ロジックが実装されています。
-
デコードループ (
Decode
ループ):x86_decoder
というバイトコード配列をプログラムカウンタpc
で順に実行します。- 各バイトコード命令(
x86_decodeOp
)は、命令のデコードプロセスにおける特定のステップを表します。 x86_xCondByte
: 次のバイトが特定の値であるかによって分岐します。これにより、オペコードの最初のバイトに基づいて異なるデコードパスに進みます。x86_xCondSlashR
: ModR/Mバイトのregop
フィールドの値に基づいて分岐します。これは、同じオペコードでもreg
フィールドによって異なる命令になる場合(例:INC
,DEC
)に用いられます。x86_xCondPrefix
: どのプレフィックスが存在するかによって分岐します。これは、F2/F3/66hなどのプレフィックスが命令の意味を変化させる場合に重要です。特に、Intel XEDとGNU libopcodesの動作の違いを吸収するための複雑なロジックが含まれています。x86_xCondIs64
,x86_xCondDataSize
,x86_xCondAddrSize
,x86_xCondIsMem
: CPUモード、オペランドサイズ、アドレスサイズ、オペランドがメモリ参照かレジスタ参照かによって分岐します。x86_xReadSlashR
: ModR/Mバイトを読み込み、mod
,regop
,rm
を解析します。メモリ参照の場合、SIBバイトやディスプレースメントも読み込み、x86_Mem
構造体に格納します。x86_xReadIb
,x86_xReadIw
,x86_xReadId
,x86_xReadIo
,x86_xReadCb
,x86_xReadCw
,x86_xReadCd
,x86_xReadCp
,x86_xReadCm
: 各種サイズのイミディエイト値やオフセット値を命令ストリームから読み込みます。x86_xSetOp
: デコードされた命令のオペコード(inst.Op
)を設定します。x86_xArg...
: デコードされたオペランド(レジスタ、メモリ、イミディエイト、相対アドレスなど)をinst.Args
配列に追加します。
-
後処理と正規化:
- デコードループが終了した後、
inst.Op
が0
の場合は無効な命令としてエラーを返します。 XCHG EAX, EAX
のような特殊なNOP
命令の処理。INSB
,MOVSB
などの文字列操作命令やMONITOR
,MWAIT
などの命令に対する暗黙的なオペランドの追加。- アドレスサイズプレフィックスやセグメントプレフィックスが、命令のオペランドによって暗黙的に使用される場合に
x86_PrefixImplicit
フラグを設定し、ディスアセンブル出力時に表示されないようにします。 - 条件分岐命令に対するブランチ予測プレフィックス(
0x2E
,0x3E
)の変換。 - Intel MPX (Memory Protection Extensions) のBNDプレフィックスや、Intel TSX (Transactional Synchronization Extensions) のXACQUIRE/XRELEASEプレフィックスの処理。
- REPプレフィックスが適用できない命令に対しては、そのプレフィックスを無視するようマークします。
- REXプレフィックスのビットがすべて使用された場合、そのプレフィックスを暗黙的としてマークします。
- デコードループが終了した後、
x86_GNUSyntax
関数の動作
この関数は、デコードされたx86_Inst
構造体を受け取り、GNUアセンブラ(AT&T)形式の文字列を生成します。
-
オペコード名の調整: 一部の浮動小数点命令(
FDIV
,FSUB
など)やMOVNTSD
のように、GNUアセンブラがIntelマニュアルとは異なるニーモニックを使用する場合や、プレフィックスの順序によってニーモニックが変化する場合に対応します。 -
暗黙的な引数の追加:
MONITOR
,MWAIT
などの命令に対して、GNUアセンブラの慣習に従って暗黙的なレジスタ引数(例:EAX
,ECX
,EDX
)を追加します。 -
プレフィックスの表示調整:
CRC32
命令のように、特定のプレフィックス(例:0xF2
)が複数存在する場合の表示ルールを調整します。MOV
命令におけるセグメントレジスタの扱いなど、オペランドサイズプレフィックスの表示に関する特殊なケースを処理します。XACQUIRE
/XRELEASE
プレフィックスの表示優先順位を調整します。
-
オペコードサフィックスの決定:
- GNUアセンブラでは、オペランドのサイズがレジスタから推測できる場合、オペコードのサフィックス(例:
b
for byte,w
for word,l
for long,q
for quadword)を省略する慣習があります。このロジックは、needSuffix
フラグとSuffixLoop
によって実装されています。 CRC32
,LGDT
,LIDT
,MOVZX
,MOVSX
,LOOP
系命令、CALL
,JMP
,RET
,PUSH
,POP
など、特定の命令に対するサフィックスの付与ルールが詳細に定義されています。特に、MOVZX
やMOVSX
のように2つのサフィックスを持つ命令も処理されます。- 浮動小数点命令のサフィックス(
s
,l
,t
など)も、メモリオペランドのサイズに基づいて決定されます。
- GNUアセンブラでは、オペランドのサイズがレジスタから推測できる場合、オペコードのサフィックス(例:
これらのコードは、x86命令セットの複雑な仕様をGo言語で正確にモデル化し、さらにGNUアセンブラの出力慣習に合わせるための細かな調整を行っていることがわかります。これは、低レベルなバイナリ解析ツールを開発する上で非常に重要な側面です。
関連リンク
- Go言語の
objdump
ツールに関する公式ドキュメントや情報:- Go Command Documentation: objdump (このコミット時点ではまだ詳細なドキュメントは少ないかもしれません)
- x86命令セットアーキテクチャに関する情報:
- GNU Binutils (特に
objdump
): - Go言語のコードレビューシステム (Gerrit):
参考にした情報源リンク
-
コミットメッセージに記載されているGerrit Change-List (CL) リンク:
- https://golang.org/cl/95350044
- https://golang.org/cl/95300044
- https://golang.org/cl/97100047
- https://golang.org/cl/93110044
- https://golang.org/cl/99000043
- https://golang.org/cl/98990043
- https://golang.org/cl/92360043 これらのリンクは、このディスアセンブラがGo本体に取り込まれるまでの詳細な開発経緯と議論を示しています。
-
rsc.io/x86/x86asm
パッケージ: このコミットでインポートされた元のパッケージ。Goのobjdump
に統合される前のディスアセンブラのソースコードがここにありました。- https://pkg.go.dev/rsc.io/x86/x86asm (現在のGo Modulesのpkg.go.devリンク)
- https://code.google.com/p/rsc/cmd/bundle (
bundle
ツールの元の場所)
-
x86命令セットのデコードに関する一般的な情報:
- Intel Software Developer's Manuals (特にVolume 2A: Instruction Set Reference, A-L)
- AMD64 Architecture Programmer's Manuals
- GNU Binutilsの
opcodes
ライブラリのソースコード(特にx86関連の部分)は、x86ディスアセンブラの実装における一般的なアプローチや特殊なケースの処理を理解する上で参考になります。
-
Go言語の
objdump
の進化: このコミットは、Goのobjdump
がより強力なツールへと進化する過程の一部です。その後のGoのバージョンアップで、さらに多くのアーキテクチャや機能が追加されていきました。- Go 1.3 Release Notes (このコミットが関連するGoのバージョン)
- Go 1.4 Release Notes (その後の進化)
- Go 1.5 Release Notes (GoのツールチェインがGo自身でビルドされるようになった点など)
これらの情報源は、このコミットがGo言語のツールチェインにおいてどのような位置づけであり、x86ディスアセンブラがどのように機能するかを深く理解するために役立ちます。
[インデックス 19356] ファイルの概要
このコミットは、Go言語のcmd/objdump
ツールにx86アーキテクチャ用のディスアセンブラを導入するものです。これにより、objdump
ツールがx86バイナリの機械語命令を人間が読めるアセンブリコードに逆アセンブルする機能を持つようになります。
コミット
cmd/objdump: import x86 disassembler
The x86 disassembler lives in rsc.io/x86/x86asm for now.
We need to figure out what should live where in the long term,
but not before the 1.3 release.
The completed code reviews for the disassembler are at:
https://golang.org/cl/95350044
https://golang.org/cl/95300044
https://golang.org/cl/97100047
https://golang.org/cl/93110044
https://golang.org/cl/99000043
https://golang.org/cl/98990043
LGTM=crawshaw
R=crawshaw, jacek.masiulaniec
CC=golang-codereviews
https://golang.org/cl/92360043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e7ad1ebeac01ebfbc36b9848b05aa45712b3e2a2
元コミット内容
commit e7ad1ebeac01ebfbc36b9848b05aa45712b3e2a2
Author: Russ Cox <rsc@golang.org>
Date: Wed May 14 19:46:53 2014 -0400
cmd/objdump: import x86 disassembler
The x86 disassembler lives in rsc.io/x86/x86asm for now.
We need to figure out what should live where in the long term,
but not before the 1.3 release.
The completed code reviews for the disassembler are at:
https://golang.org/cl/95350044
https://golang.org/cl/95300044
https://golang.org/cl/97100047
https://golang.org/cl/93110044
https://golang.org/cl/99000043
https://golang.org/cl/98990043
LGTM=crawshaw
R=crawshaw, jacek.masiulaniec
CC=golang-codereviews
https://golang.org/cl/92360043
変更の背景
このコミットの主な背景は、Go言語の標準ツールであるobjdump
にx86アーキテクチャのディスアセンブル機能を追加することです。Go言語はクロスコンパイルをサポートしており、様々なアーキテクチャ向けのバイナリを生成できます。objdump
はこれらのバイナリを解析し、機械語命令をアセンブリコードに変換して表示するための重要なツールです。
以前は、Goのobjdump
は特定のアーキテクチャ(例えばGoが動作するホストアーキテクチャ)に限定されたディスアセンブル機能しか持っていなかったか、あるいはx86に対する十分なサポートがありませんでした。このコミットにより、objdump
はx86バイナリ(Goプログラムだけでなく、一般的なx86バイナリも含む)を正確にディスアセンブルできるようになります。
コミットメッセージには「The x86 disassembler lives in rsc.io/x86/x86asm for now. We need to figure out what should live where in the long term, but not before the 1.3 release.」とあります。これは、このディスアセンブラが一時的にrsc.io/x86/x86asm
という外部パッケージとして開発され、Go 1.3リリース前にGo本体のcmd/objdump
に取り込まれたことを示唆しています。長期的な配置については、Go 1.3リリース後に検討される予定でした。
この機能追加は、Go開発者が生成されたバイナリの低レベルな動作を理解したり、デバッグしたりする上で非常に役立ちます。特に、パフォーマンスの最適化や、コンパイラが生成するコードの検証において不可欠なツールとなります。
前提知識の解説
このコミットを理解するためには、以下の前提知識が必要です。
-
ディスアセンブラ (Disassembler): 機械語(バイナリコード)を人間が読めるアセンブリ言語に変換するツールです。CPUが直接実行する命令列を、対応するニーモニック(例:
MOV
,ADD
,JMP
)とオペランドに変換します。デバッグ、リバースエンジニアリング、セキュリティ分析などに用いられます。 -
x86アーキテクチャ: Intel 8086プロセッサから続く、広く普及している命令セットアーキテクチャ(ISA)です。デスクトップPCやサーバーで主に使用されます。x86命令セットは非常に複雑で、可変長の命令、多数のプレフィックス、多様なアドレッシングモード(ModR/M、SIBバイトなど)が特徴です。
-
x86命令の構造: x86命令は、一般的に以下の要素で構成されます。
- プレフィックス (Prefixes): 命令の動作を変更するオプションのバイト列です。
- REXプレフィックス (REX Prefix): 64ビットモードでのみ使用され、レジスタの拡張(R8-R15など)、オペランドサイズの指定(REX.W)、SIBバイトやModR/Mバイトの拡張などに使われます。
- セグメントオーバーライドプレフィックス (Segment Override Prefixes): メモリ参照の際に使用するセグメントレジスタ(CS, DS, ES, FS, GS, SS)を明示的に指定します。
- オペランドサイズオーバーライドプレフィックス (Operand Size Override Prefix -
0x66
): デフォルトのオペランドサイズ(例: 32ビットモードでの32ビット)を切り替えます(例: 16ビット)。 - アドレスサイズオーバーライドプレフィックス (Address Size Override Prefix -
0x67
): デフォルトのアドレスサイズ(例: 32ビットモードでの32ビット)を切り替えます(例: 16ビット)。 - 繰り返しプレフィックス (Repeat Prefixes -
0xF2
,0xF3
): 文字列操作命令などで繰り返し動作を指定します(例:REP
,REPE
,REPNE
)。 - ロックプレフィックス (LOCK Prefix -
0xF0
): メモリ操作をアトミックに行うことを保証します。
- オペコード (Opcode): 命令の種類を示すバイトです。1バイトから3バイト以上になることもあります。
- ModR/Mバイト (ModR/M Byte): オペランドがレジスタかメモリか、どのアドレッシングモードを使用するか、どのレジスタが関与するかなどを指定します。
Mod
(2ビット): オペランドがレジスタか、またはメモリ参照の場合のディスプレースメントの有無とサイズ。Reg/Opcode
(3ビット): レジスタオペランド、またはオペコードの拡張。R/M
(3ビット): レジスタオペランド、またはメモリ参照のベースレジスタ/SIBバイトの有無。
- SIBバイト (Scale-Index-Base Byte): ModR/Mバイトが特定のメモリ参照(ベースレジスタ + インデックスレジスタ * スケール + ディスプレースメント)を示す場合に存在します。
Scale
(2ビット): インデックスレジスタに乗算するスケールファクタ(1, 2, 4, 8)。Index
(3ビット): インデックスレジスタ。Base
(3ビット): ベースレジスタ。
- ディスプレースメント (Displacement): メモリ参照のオフセット値。1, 2, 4バイトのいずれか。
- イミディエイト (Immediate): 命令に直接埋め込まれる定数値。1, 2, 4, 8バイトのいずれか。
- プレフィックス (Prefixes): 命令の動作を変更するオプションのバイト列です。
-
Go言語の
cmd
パッケージ: Goの標準ツール群(コンパイラ、リンカ、アセンブラ、デバッガなど)が格納されているパッケージです。cmd/objdump
もその一つです。 -
Goのコード生成ツール (
bundle
): このコミットではbundle
というツールが使われています。これは、Goのソースコードを別のGoソースコードに埋め込む(バンドルする)ためのツールです。外部パッケージのコードをGo本体のリポジトリに直接取り込む際に利用されます。これにより、依存関係の管理が簡素化され、ビルドプロセスが安定します。
これらの知識は、x86.go
ファイル内のコードがどのようにx86命令を解析し、各バイトの意味を解釈しているかを理解する上で不可欠です。
技術的詳細
このコミットの技術的詳細の核心は、x86命令の複雑なデコードロジックをGo言語で実装している点にあります。特に注目すべきは、可変長命令、多数のプレフィックス、ModR/MおよびSIBバイトの解釈、そして異なるCPUモード(16/32/64ビット)への対応です。
src/cmd/objdump/x86.go
ファイルは、主に以下の要素で構成されています。
-
命令デコードのバイトコードプログラム:
x86_decodeOp
型とそれに対応する定数(x86_xFail
,x86_xMatch
,x86_xJump
,x86_xCondByte
など)は、x86命令のデコードプロセスを記述するためのカスタムバイトコード命令セットを定義しています。このバイトコードは、命令ストリームからバイトを読み込み、条件分岐を行い、オペランドを解釈するためのものです。x86_decoder
というグローバル変数がこのバイトコードプログラムを保持しており、x86_Decode
関数がこのプログラムを実行して命令をデコードします。このアプローチは、複雑な命令セットのデコードロジックを、より構造化された、テーブル駆動型またはバイトコード駆動型で表現するための一般的な手法です。 -
プレフィックスの処理: x86命令のデコードにおいて、プレフィックスの処理は最も複雑な部分の一つです。
x86_Decode
関数は、まず命令の先頭からプレフィックスを読み込みます。0xF0
(LOCK),0xF2
(REPN),0xF3
(REP),0x26
,0x2E
,0x36
,0x3E
,0x64
,0x65
(セグメントオーバーライド),0x66
(オペランドサイズオーバーライド),0x67
(アドレスサイズオーバーライド) などのプレフィックスを識別し、inst.Prefix
配列に格納します。- 64ビットモードでは、REXプレフィックス(
0x40
から0x4F
)も処理されます。REXプレフィックスは、レジスタの拡張や64ビットオペランドサイズ(REX.W)の指定に用いられます。 - 複数のプレフィックスが存在する場合の優先順位や、意味を持たないプレフィックスの扱い(無視されるか、例外を発生させるか)に関する複雑なロジックが実装されています。特に、
x86_xCondPrefix
のセクションでは、F2/F3プレフィックスと66hプレフィックスの競合に関する詳細なコメントがあり、Intel XEDとGNU libopcodesの動作の違いが考慮されています。
-
ModR/MおよびSIBバイトの解析:
x86_xCondSlashR
やx86_xReadSlashR
などのデコードオペレーションがModR/Mバイトの読み込みと解析をトリガーします。- ModR/Mバイトから
mod
,regop
,rm
フィールドが抽出されます。 mod
とrm
の値に基づいて、オペランドがレジスタか、あるいはメモリ参照なのかが判断されます。- メモリ参照の場合、アドレスモード(16/32/64ビット)に応じて、ディスプレースメントの読み込みや、SIBバイトの有無がチェックされます。
- SIBバイトが存在する場合、
scale
,index
,base
フィールドが抽出され、メモリのアドレッシング計算に利用されます。
-
イミディエイト値の読み込み:
x86_xReadIb
,x86_xReadIw
,x86_xReadId
,x86_xReadIo
などのオペレーションにより、命令に埋め込まれたイミディエイト値(即値)がバイトストリームから読み込まれます。リトルエンディアン形式で読み込まれることが明示されています。 -
命令の正規化と特殊ケースの処理:
- デコードされた命令は
x86_Inst
構造体に格納されます。 XCHG EAX, EAX
がNOP
として扱われるなど、特定の命令の特殊な振る舞いが後処理で調整されます。- 文字列操作命令(
MOVSB
,LODSB
など)や入出力命令(INS
,OUTS
)のように、暗黙的なオペランドを持つ命令に対して、それらのオペランドが明示的に追加されます。 - 条件分岐命令に対するブランチ予測プレフィックス(
0x2E
,0x3E
)の解釈や、Intel TSX (Transactional Synchronization Extensions) 関連のXACQUIRE
/XRELEASE
プレフィックスの処理も含まれています。
- デコードされた命令は
-
GNUアセンブラ構文の生成 (
gnu.go
部分):x86_GNUSyntax
関数は、デコードされた命令をGNUアセンブラ(AT&T構文)形式の文字列に変換します。- オペコード名の調整(例:
FDIV
とFDIVR
の入れ替え、MOVNTSD
の優先順位)。 MONITOR
,MWAIT
などの命令に対する暗黙的な引数の追加。- プレフィックスの表示ルール調整。特に、
CRC32
やMOV
命令におけるプレフィックスの表示に関する複雑なロジックが含まれています。 - オペランドサイズを示すサフィックス(例:
b
,w
,l
,q
)の付与ロジック。レジスタオペランドからサイズが推測できる場合はサフィックスを省略するなど、GNUアセンブラの慣習に従っています。
- オペコード名の調整(例:
このディスアセンブラは、x86命令セットの膨大な複雑さを、Go言語の型システムとバイトコード駆動のアプローチで効率的に処理しようと試みていることがわかります。特に、プレフィックスの競合解決や、GNUアセンブラの出力形式に合わせるための調整は、x86ディスアセンブラ開発における一般的な課題であり、このコードがそれらに対応していることを示しています。
コアとなるコードの変更箇所
このコミットで追加された主要なファイルは以下の2つです。
-
src/cmd/objdump/Makefile
:--- /dev/null +++ b/src/cmd/objdump/Makefile @@ -0,0 +1,5 @@ +x86.go: bundle + ./bundle -p main -x x86_ rsc.io/x86/x86asm >x86.go + +bundle: + go build -o bundle code.google.com/p/rsc/cmd/bundle
このMakefileは、
x86.go
ファイルを生成するためのルールを定義しています。rsc.io/x86/x86asm
パッケージのコードをbundle
ツールを使ってx86.go
にバンドルしています。 -
src/cmd/objdump/x86.go
: このファイルは新規追加され、x86ディスアセンブラの主要なロジックを含んでいます。非常に大規模なファイルであり、その内容は以下の主要なセクションに分けられます。-
decode.go
セクション:x86_decodeOp
型と関連する定数(x86_xFail
,x86_xMatch
,x86_xJump
,x86_xCondByte
など)の定義。これらはディスアセンブラのバイトコード命令です。x86_Inst
構造体(デコードされた命令を表す)の定義。x86_Decode
関数: 実際の命令デコードロジック。プレフィックスの解析、ModR/MおよびSIBバイトの処理、イミディエイト値の読み込み、そしてバイトコードプログラムの実行が含まれます。x86_truncated
,x86_instPrefix
などのヘルパー関数。x86_ErrInvalidMode
,x86_ErrTruncated
,x86_ErrUnrecognized
などのエラー定義。x86_addr16
,x86_baseReg
,x86_prefixToSegment
,x86_fixedArg
,x86_memBytes
,x86_isCondJmp
,x86_isLoop
などの補助的なデータテーブルと関数。
-
gnu.go
セクション:x86_GNUSyntax
関数: デコードされた命令をGNUアセンブラ(AT&T)構文でフォーマットするロジック。- GNUアセンブラの出力における特殊なケース(例:
FDIV
とFDIVR
の入れ替え、MOVNTSS
の優先順位、暗黙的な引数の追加、プレフィックスの表示ルール、オペランドサイズサフィックスの付与)を処理します。
-
このコミットは、実質的にrsc.io/x86/x86asm
パッケージの大部分をcmd/objdump
に統合したものです。
コアとなるコードの解説
src/cmd/objdump/x86.go
のコアとなる部分は、x86_Decode
関数と、それを駆動するx86_decoder
バイトコードプログラムです。
x86_Decode
関数の動作フロー
-
初期化:
- 入力バイト列
src
とCPUモード(16, 32, 64ビット)を受け取ります。 - 最大命令長(15バイト)を超える場合は
src
を切り詰めます。 - プレフィックス、ModR/M、SIB、イミディエイト値などを格納するための変数を初期化します。
- 入力バイト列
-
プレフィックスの読み込み (
ReadPrefixes
ループ):- 命令の先頭からバイトを順に読み込み、それがプレフィックスであるかを判定します。
- LOCK (
0xF0
), REP/REPN (0xF2
,0xF3
), セグメントオーバーライド (0x26
,0x2E
,0x36
,0x3E
,0x64
,0x65
), オペランドサイズオーバーライド (0x66
), アドレスサイズオーバーライド (0x67
) などのプレフィックスを識別し、inst.Prefix
配列に格納します。 - 64ビットモードでは、REXプレフィックスもここで処理され、
dataMode
(オペランドサイズ)がREX.W
ビットによって64ビットに設定される場合があります。 - 複数の同じ種類のプレフィックスが存在する場合(例: 複数のREPプレフィックス)、後続のプレフィックスが以前のものを上書きする(または無視する)ロジックが実装されています。
-
デコードループ (
Decode
ループ):x86_decoder
というバイトコード配列をプログラムカウンタpc
で順に実行します。- 各バイトコード命令(
x86_decodeOp
)は、命令のデコードプロセスにおける特定のステップを表します。 x86_xCondByte
: 次のバイトが特定の値であるかによって分岐します。これにより、オペコードの最初のバイトに基づいて異なるデコードパスに進みます。x86_xCondSlashR
: ModR/Mバイトのregop
フィールドの値に基づいて分岐します。これは、同じオペコードでもreg
フィールドによって異なる命令になる場合(例:INC
,DEC
)に用いられます。x86_xCondPrefix
: どのプレフィックスが存在するかによって分岐します。これは、F2/F3/66hなどのプレフィックスが命令の意味を変化させる場合に重要です。特に、Intel XEDとGNU libopcodesの動作の違いを吸収するための複雑なロジックが含まれています。x86_xCondIs64
,x86_xCondDataSize
,x86_xCondAddrSize
,x86_xCondIsMem
: CPUモード、オペランドサイズ、アドレスサイズ、オペランドがメモリ参照かレジスタ参照かによって分岐します。x86_xReadSlashR
: ModR/Mバイトを読み込み、mod
,regop
,rm
を解析します。メモリ参照の場合、SIBバイトやディスプレースメントも読み込み、x86_Mem
構造体に格納します。x86_xReadIb
,x86_xReadIw
,x86_xReadId
,x86_xReadIo
,x86_xReadCb
,x86_xReadCw
,x86_xReadCd
,x86_xReadCp
,x86_xReadCm
: 各種サイズのイミディエイト値やオフセット値を命令ストリームから読み込みます。x86_xSetOp
: デコードされた命令のオペコード(inst.Op
)を設定します。x86_xArg...
: デコードされたオペランド(レジスタ、メモリ、イミディエイト、相対アドレスなど)をinst.Args
配列に追加します。
-
後処理と正規化:
- デコードループが終了した後、
inst.Op
が0
の場合は無効な命令としてエラーを返します。 XCHG EAX, EAX
のような特殊なNOP
命令の処理。INSB
,MOVSB
などの文字列操作命令やMONITOR
,MWAIT
などの命令に対する暗黙的なオペランドの追加。- アドレスサイズプレフィックスやセグメントプレフィックスが、命令のオペランドによって暗黙的に使用される場合に
x86_PrefixImplicit
フラグを設定し、ディスアセンブル出力時に表示されないようにします。 - 条件分岐命令に対するブランチ予測プレフィックス(
0x2E
,0x3E
)の変換。 - Intel MPX (Memory Protection Extensions) のBNDプレフィックスや、Intel TSX (Transactional Synchronization Extensions) のXACQUIRE/XRELEASEプレフィックスの処理。
- REPプレフィックスが適用できない命令に対しては、そのプレフィックスを無視するようマークします。
- REXプレフィックスのビットがすべて使用された場合、そのプレフィックスを暗黙的としてマークします。
- デコードループが終了した後、
x86_GNUSyntax
関数の動作
この関数は、デコードされたx86_Inst
構造体を受け取り、GNUアセンブラ(AT&T)形式の文字列を生成します。
-
オペコード名の調整: 一部の浮動小数点命令(
FDIV
,FSUB
など)やMOVNTSD
のように、GNUアセンブラがIntelマニュアルとは異なるニーモニックを使用する場合や、プレフィックスの順序によってニーモニックが変化する場合に対応します。 -
暗黙的な引数の追加:
MONITOR
,MWAIT
などの命令に対して、GNUアセンブラの慣習に従って暗黙的なレジスタ引数(例:EAX
,ECX
,EDX
)を追加します。 -
プレフィックスの表示調整:
CRC32
命令のように、特定のプレフィックス(例:0xF2
)が複数存在する場合の表示ルールを調整します。MOV
命令におけるセグメントレジスタの扱いなど、オペランドサイズプレフィックスの表示に関する特殊なケースを処理します。XACQUIRE
/XRELEASE
プレフィックスの表示優先順位を調整します。
-
オペコードサフィックスの決定:
- GNUアセンブラでは、オペランドのサイズがレジスタから推測できる場合、オペコードのサフィックス(例:
b
for byte,w
for word,l
for long,q
for quadword)を省略する慣習があります。このロジックは、needSuffix
フラグとSuffixLoop
によって実装されています。 CRC32
,LGDT
,LIDT
,MOVZX
,MOVSX
,LOOP
系命令、CALL
,JMP
,RET
,PUSH
,POP
など、特定の命令に対するサフィックスの付与ルールが詳細に定義されています。特に、MOVZX
やMOVSX
のように2つのサフィックスを持つ命令も処理されます。- 浮動小数点命令のサフィックス(
s
,l
,t
など)も、メモリオペランドのサイズに基づいて決定されます。
- GNUアセンブラでは、オペランドのサイズがレジスタから推測できる場合、オペコードのサフィックス(例:
これらのコードは、x86命令セットの複雑な仕様をGo言語で正確にモデル化し、さらにGNUアセンブラの出力慣習に合わせるための細かな調整を行っていることがわかります。これは、低レベルなバイナリ解析ツールを開発する上で非常に重要な側面です。
関連リンク
- Go言語の
objdump
ツールに関する公式ドキュメントや情報:- Go Command Documentation: objdump (このコミット時点ではまだ詳細なドキュメントは少ないかもしれません)
- x86命令セットアーキテクチャに関する情報:
- GNU Binutils (特に
objdump
): - Go言語のコードレビューシステム (Gerrit):
参考にした情報源リンク
-
コミットメッセージに記載されているGerrit Change-List (CL) リンク:
- https://golang.org/cl/95350044
- https://golang.org/cl/95300044
- https://golang.org/cl/97100047
- https://golang.org/cl/93110044
- https://golang.org/cl/99000043
- https://golang.org/cl/98990043
- https://golang.org/cl/92360043 これらのリンクは、このディスアセンブラがGo本体に取り込まれるまでの詳細な開発経緯と議論を示しています。
-
rsc.io/x86/x86asm
パッケージ: このコミットでインポートされた元のパッケージ。Goのobjdump
に統合される前のディスアセンブラのソースコードがここにありました。- https://pkg.go.dev/rsc.io/x86/x86asm (現在のGo Modulesのpkg.go.devリンク)
- https://code.google.com/p/rsc/cmd/bundle (
bundle
ツールの元の場所)
-
x86命令セットのデコードに関する一般的な情報:
- Intel Software Developer's Manuals (特にVolume 2A: Instruction Set Reference, A-L)
- AMD64 Architecture Programmer's Manuals
- GNU Binutilsの
opcodes
ライブラリのソースコード(特にx86関連の部分)は、x86ディスアセンブラの実装における一般的なアプローチや特殊なケースの処理を理解する上で参考になります。
-
Go言語の
objdump
の進化: このコミットは、Goのobjdump
がより強力なツールへと進化する過程の一部です。その後のGoのバージョンアップで、さらに多くのアーキテクチャや機能が追加されていきました。- Go 1.3 Release Notes (このコミットが関連するGoのバージョン)
- Go 1.4 Release Notes (その後の進化)
- Go 1.5 Release Notes (GoのツールチェインがGo自身でビルドされるようになった点など)
これらの情報源は、このコミットがGo言語のツールチェインにおいてどのような位置づけであり、x86ディスアセンブラがどのように機能するかを深く理解するために役立ちます。