通常は binutils と呼ばれる GNU Binary Utilities は、アセンブリ ファイル、オブジェクト ファイルを処理する開発ツールのコレクションです。 、およびライブラリ。
ここ数年で登場した新世代のプログラミング言語は、これらのユーティリティの機能をバックグラウンドで実際に覆い隠しています。そのため、多くの開発者はこれらのツールに触れていません。
ただし、Linux / UNIX プラットフォームで作業している開発者であれば、GNU 開発ツールの一部として利用できるさまざまなコマンドを理解することが不可欠です。
以下は、このチュートリアルで取り上げる 12 の異なる binutils コマンドです。
これらのツールは、バイナリ、オブジェクト、およびライブラリ ファイルを効果的に操作するのに役立ちます。
これらの 12 のユーティリティのうち、ld と ld が最も重要であり、GNU Compiler Collection (gcc) のデフォルトのバックエンドです。 GCC は、C/C++ からアセンブリ言語にコンパイルするジョブと、実行可能なバイナリを出力する as および ld のジョブのみを実行します。
サンプル コードを準備する
これらすべてのコマンドがどのように機能するかを理解するために、まず、gcc -S を使用して C コードからサンプル アセンブリ コードをいくつか準備しましょう。ここに示されているすべての実験は、x86 64 ビット Linux ボックスで行われています。
以下は、外部関数の戻り値を戻りコードとして使用する C コードです。入出力はありませんので、プログラムが期待どおりに実行されたかどうかを確認したい場合は、リターンステータス (echo $?) を確認してください。 main、func1、func2 の 3 つの関数と、関数ごとに 1 つのファイルがあります。
// func1.c file: int func1() { return func2(); } // func2.c file: int func2() { return 1; } // main.c file: int main() { return func1(); }
GCC は C ランタイム ライブラリをサポートしているため、メイン関数は通常の関数として扱われます。デモを単純化するために、これらの .s ファイルをコンパイルおよびリンクするときに C ライブラリを関与させたくありません。そのため、main.s に対して 2 つの変更が行われます:
最初の変更は、リンク ステージにラベル _start が追加されたことです。
_start ラベルはアプリのエントリ ポイントです。定義されていない場合、ld の実行時に以下のような警告が報告されます。
ld: warning: cannot find entry symbol _start; defaulting to 0000000000400078
2 番目の変更は、ret がシステム終了呼び出しに置き換えられることです。
システム終了割り込みを手動で発生させる必要があります。 %eax は関数の戻り値を保持するために使用されますが、システム終了呼び出しはそれを %ebx に保持します。したがって、それを %eax から %ebx にコピーします
以下は、gcc アセンブリ コードの再編集バージョンです。
func1.s ファイル:
.file "func1.c" .text .globl func1 .type func1, @function func1: pushq %rbp movq %rsp, %rbp movl $0, %eax call func2 leave
func2.s ファイル:
.file "func2.c" .text .globl func2 .type func2, @function func2: pushq %rbp movq %rsp, %rbp movl $1, %eax leave ret
main.s ファイル:
.file "main.c" .text .globl main .globl _start .type main, @function _start: main: pushq %rbp movq %rsp, %rbp movl $0, %eax call func1 movl %eax, %ebx movl $1, %eax int $0x80 leave
1. as – GNU アセンブラー コマンド
as は、アセンブリ ファイルを入力として受け取り、オブジェクト ファイルを出力します。オブジェクト ファイルは、最終的な実行可能ファイルを生成するための ld の入力として使用される内部形式のみです。
以下に示すように、main.s ファイルに対して as コマンドを実行して、main.o オブジェクト ファイルを取得します。
as main.s -o main.o
ファイル main.o (「as main.s -o main.o」で作成) から、以下の情報を取得できます。
main.o: ELF 64-bit LSB relocatable, AMD x86-64, version 1 (SYSV), not stripped
オブジェクト ファイルは、Linux ディストリビューションで最も広く使用されているファイル形式である ELF 形式です。
「as」コマンドには、前処理、シンボル、制約、式、疑似操作/ディレクティブ、およびコメントの構文サポートもあることに注意してください。
GNU Assembler は膨大な数のマシンをサポートできますが、通常、コンパイルまたはクロスコンパイル時に選択されるマシン/アーキテクチャ ファミリは 1 つだけです。
2. ld – GNU リンカ コマンド
通常、オブジェクト ファイルには異なるライブラリ/オブジェクトの外部関数への参照が含まれており、リンカー (ld) の仕事は、最終的なバイナリに必要なすべてのオブジェクト/ライブラリ ファイルを結合し、セクションを再配置し、参照を解決することです。
ld の実際の動作は、実行可能ファイルのメモリ レイアウトを記述するリンカー スクリプトで定義されます。
main.o のみをリンクすると (ld main.o -o main)、未定義の参照エラーが発生します:
main.o: In function `_start': main.c:(.text+0xa): undefined reference to `func1'
3 つの異議ファイル (ld main.o func1.o func2.o -o main) をすべてリンクしないと、実行可能ファイルを取得できません。
# file main main: ELF 64-bit LSB executable, AMD x86-64, version 1 (SYSV), statically linked, not stripped
オブジェクト ファイルとは異なります。ここでは、静的にリンクされた実行可能ファイルを取得します。
as および ld は、特定のターゲット/アーキテクチャで機能します。しかし、binutils で定義された BFD オブジェクトで動作するツールがいくつかあります。
objcopy -h の出力の最後の数行から、サポート対象を取得できます。
objcopy: supported targets: elf64-x86-64 elf32-i386 a.out-i386-linux pei-i386 pei-x86-64 elf64-l1om elf64-little elf64-big elf32-little elf32-big plugin srec symbolsrec verilog tekhex binary ihex
verilog、ihex は実際の OS ではサポートされていませんが、オブジェクトの内容をテキスト形式で処理するのに非常に役立ちます。これらは、メモリ/ROM の初期化のためにチップ シミュレーション環境で広く使用されています。
3. ar/ranlib – GNU アーカイブ コマンド
ar を使用して、多くのオブジェクトで構成されたアーカイブ ファイルである静的ライブラリを生成および操作できます。
ar の動作は、コマンド ライン引数 (UNIX スタイル) またはスクリプト ファイルから制御できます。 ranlib は、シンボルのインデックスをアーカイブに追加できます。これにより、リンク速度が向上し、ルーチンの呼び出しも容易になります。 ar -s は ranlib と同じことを行います。
私のテストでは、-s の有無にかかわらず、ar は常にアーカイブ インデックスを出力します。
Test1、-s なしの ar。
# ar -r extern.a func1.o func2.o && nm -s extern.a ar: creating extern.a Archive index: func1 in func1.o func2 in func2.o func1.o: 0000000000000000 T func1 U func2 func2.o: 0000000000000000 T func2
ar コマンドの詳細については、こちらをお読みください:Linux ar コマンドの例:C アーカイブ ファイル (*.a) を作成、表示、抽出、変更する方法
テスト 2、-s 付きの ar。
# ar -r -s externS.a func1.o func2.o && nm -s externS.a ar: creating externS.a Archive index: func1 in func1.o func2 in func2.o func1.o: 0000000000000000 T func1 U func2 func2.o: 0000000000000000 T func2
テスト 3、ranlib を再度実行します。
# cp extern.a externR.a && ranlib externR.a && nm -s externR.a Archive index: func1 in func1.o func2 in func2.o func1.o: 0000000000000000 T func1 U func2 func2.o: 0000000000000000 T func2
各テストが同じ結果を出力することを示すことができます。
4. nm – オブジェクト ファイル シンボルの一覧表示
nm は、オブジェクト ファイルからシンボルを一覧表示できます。上記のセクションで使用方法を示しました。
nm コマンドは、オブジェクト ファイルまたは実行可能ファイルで使用されているシンボルに関する情報を提供します。
nm コマンドが提供するデフォルトの情報は次のとおりです:
- シンボルの仮想アドレス
- 記号の種類を表す文字。文字が小文字の場合、シンボルはローカルですが、文字が大文字の場合、シンボルは外部です
- シンボルの名前
$ nm -A ./*.o | grep func ./hello2.o:0000000000000000 T func_1 ./hello3.o:0000000000000000 T func_2 ./hello4.o:0000000000000000 T func_3 ./main.o: U func ./reloc.o: U func ./reloc.o:0000000000000000 T func1 ./test1.o:0000000000000000 T func ./test.o: U func
続きを読む:10 の実用的な Linux nm コマンドの例
5. objcopy – オブジェクト ファイルのコピーと翻訳
objcopy は、あるオブジェクト ファイルの内容を別のオブジェクト ファイルにコピーでき、入出力オブジェクトは別の形式にすることができます。
ある種類のプラットフォーム (ARM や x86 など) で使用可能なオブジェクト ファイルを別の種類のプラットフォームに移植する必要がある場合があります。
ソース コードが利用可能であれば、ターゲット プラットフォームで再コンパイルできるため、作業は比較的簡単です。
しかし、ソース コードが入手できず、オブジェクト ファイルを別のプラットフォームに移植する必要がある場合はどうすればよいでしょうか。 Linux を使用している場合、コマンド objcopy は必要な処理を正確に実行します
このコマンドの構文は次のとおりです:
objcopy [options] infile [outfile]...
詳細:オブジェクト ファイルをコピーおよび変換する Linux Objcopy コマンドの例
6. objdump – オブジェクト ファイル情報の表示
objdump は、オブジェクト ファイルから選択した情報を表示できます。逆アセンブルを main に適用するには、objdump -d を使用できます。
# objdump -d main main: file format elf64-x86-64 Disassembly of section .text: 0000000000400078 <main>: 400078: 55 push %rbp 400079: 48 89 e5 mov %rsp,%rbp 40007c: b8 00 00 00 00 mov $0x0,%eax 400081: e8 0a 00 00 00 callq 400090 <func1> 400086: c9 leaveq 400087: 89 c3 mov %eax,%ebx 400089: b8 01 00 00 00 mov $0x1,%eax 40008e: cd 80 int $0x80 0000000000400090 <func1>: 400090: 55 push %rbp 400091: 48 89 e5 mov %rsp,%rbp 400094: b8 00 00 00 00 mov $0x0,%eax 400099: e8 02 00 00 00 callq 4000a0 <func2> 40009e: c9 leaveq 40009f: c3 retq 00000000004000a0 <func2>: 4000a0: 55 push %rbp 4000a1: 48 89 e5 mov %rsp,%rbp 4000a4: b8 01 00 00 00 mov $0x1,%eax 4000a9: c9 leaveq 4000aa: c3 retq
詳細:Linux Objdump コマンドの例 (バイナリ ファイルの逆アセンブル)
7. size – リスト セクションのサイズと総サイズ
size は、オブジェクト ファイル内のセクションのサイズ情報を表示できます。
# size main text data bss dec hex filename 51 0 0 51 33 main
8.文字列 – ファイルから印刷可能な文字を表示
string は、オブジェクト ファイルからの印刷可能な char シーケンスを表示できます。デフォルトでは、.data セクションのみを検索します。 -a スイッチを使用すると、すべてのセクションを検索できます。
# strings -a main .symtab .strtab .shstrtab .text main.c func1.c func2.c func1 _start __bss_start main func2 _edata _end
続きを読む:Linux 文字列コマンドの例 (UNIX バイナリ ファイル内のテキストを検索)
9. strip – オブジェクト ファイルからシンボルを破棄
strip は、オブジェクト ファイルからシンボルを削除できます。これにより、ファイル サイズが縮小され、実行が高速化されます。
objdump でシンボル テーブルを表示できます。シンボル テーブルには、各関数/ラベルのエントリ/オフセットが表示されます。
# objdump -t main main: file format elf64-x86-64 SYMBOL TABLE: 0000000000400078 l d .text 0000000000000000 .text 0000000000000000 l df *ABS* 0000000000000000 main.c 0000000000000000 l df *ABS* 0000000000000000 func1.c 0000000000000000 l df *ABS* 0000000000000000 func2.c 0000000000400090 g F .text 0000000000000000 func1 0000000000400078 g .text 0000000000000000 _start 00000000006000ab g *ABS* 0000000000000000 __bss_start 0000000000400078 g F .text 0000000000000000 main 00000000004000a0 g F .text 0000000000000000 func2 00000000006000ab g *ABS* 0000000000000000 _edata 00000000006000b0 g *ABS* 0000000000000000 _end
ストリップ (#strip main) の後、シンボル テーブルは削除されます。
#objdump -t main main: file format elf64-x86-64 SYMBOL TABLE: no symbols
続きを読む:10 の Linux ストリップ コマンドの例 (実行ファイル/バイナリ ファイルのサイズを減らす)
10. c++filt – Demangle コマンド
C++ は、同じ関数名が異なる種類/数の引数を取ることができるオーバーロードをサポートします。
これは、関数名をマングリングと呼ばれる低レベルのアセンブラー名に変更することによって行われます。 c++filt は、C++ および Java のデマングリングを実行できます。
ここでは、マングリングを説明するための新しいサンプル コードを作成します。
異なる種類の入力引数、void と int を取る 2 種類の func3 があるとします。
==> mangling.cpp <== int func3(int a) { return a; } int func3() { return 0; } int main() { return func3(1); }
アセンブリ形式では、_Z5func3v と _Z5func3i という異なる名前が付けられています。そして、これらの 1 つが、mangling.cpp で func3 に渡した引数のタイプに従って呼び出されます。この例では、_Z5func3i が呼び出されます。
==> mangling.s <== .file "mangling.cpp" .text .globl _Z5func3i .type _Z5func3i, @function _Z5func3i: pushq %rbp movq %rsp, %rbp movl %edi, -4(%rbp) movl -4(%rbp), %eax leave ret .globl _Z5func3v .type _Z5func3v, @function _Z5func3v: pushq %rbp movq %rsp, %rbp movl $0, %eax leave ret .globl main .type main, @function main: pushq %rbp movq %rsp, %rbp movl $1, %edi call _Z5func3i leave ret #grep func3.*: mangling.s _Z5func3i: _Z5func3v:
これらのアセンブリ関数名を c++filt に渡すと、元の関数定義ステートメントが復元されます。
#grep func3.*: mangling.s | c++filt func3(int): func3():
objdump は、さまざまなスタイルでデマングルを実行することもできます:
-C, --demangle[=STYLE] Decode mangled/processed symbol names The STYLE, if specified, can be 'auto', 'gnu', 'lucid', 'arm', 'hp', 'edg', 'gnu-v3', 'java' or 'gnat'
11. addr2line – アドレスをファイル名と数値に変換
addr2line は、デバッグ情報を渡すことにより、再割り当てされたセクション内の指定されたアドレスまたはオフセットのファイルと行番号を取得できます。
まず、デバッグ情報がオブジェクトに追加されるように、アセンブリ ファイルを -g フラグでコンパイルする必要があります。現在、いくつかのデバッグ セクションがあることが下から示されています。
objdump -h mainD mainD: file format elf64-x86-64 Sections: Idx Name Size VMA LMA File off Algn 0 .text 00000033 0000000000400078 0000000000400078 00000078 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE 1 .debug_aranges 00000090 0000000000000000 0000000000000000 000000b0 2**4 CONTENTS, READONLY, DEBUGGING 2 .debug_info 000000dd 0000000000000000 0000000000000000 00000140 2**0 CONTENTS, READONLY, DEBUGGING 3 .debug_abbrev 0000003c 0000000000000000 0000000000000000 0000021d 2**0 CONTENTS, READONLY, DEBUGGING 4 .debug_line 000000ba 0000000000000000 0000000000000000 00000259 2**0 CONTENTS, READONLY, DEBUGGING
セクション 2.d objdump に示されている逆アセンブル結果から、0x400090 が func1 のエントリであることがわかります。これは、addr2line によって与えられた結果と同じです。
addr2line -e mainD 0x400090 /media/shared/TGS/func1.s:6
12. readelf – ELF ファイル情報を表示
readelf と elfedit は、elf ファイルでのみ操作できます。
readelfはelfファイルから情報を表示できます.
ELFヘッダーの詳細情報を表示できます.
#readelf -h main_full ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: Advanced Micro Devices X86-64 Version: 0x1 Entry point address: 0x400078 Start of program headers: 64 (bytes into file) Start of section headers: 208 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 1 Size of section headers: 64 (bytes) Number of section headers: 5 Section header string table index: 2
readelf と同様に、elf ヘッダーでマシン、ファイル タイプ、OS ABI を更新できる elfedit を使用することもできます。デフォルトでは elfedit がディストリビューションに含まれていない可能性があることに注意してください。
詳細:Linux ELF オブジェクト ファイル形式 (および ELF ヘッダー構造) の基本