ELFファイル形式を理解する

Understanding Elf File Format



ソースコードからバイナリコードへ

プログラミングは、巧妙なアイデアを持ち、選択したプログラミング言語(Cなど)でソースコードを記述し、ソースコードをファイルに保存することから始まります。 GCCなどの適切なコンパイラを使用して、最初にソースコードをオブジェクトコードに変換します。最終的に、リンカはオブジェクトコードを、オブジェクトコードを参照ライブラリにリンクするバイナリファイルに変換します。このファイルには、CPUが理解できるマシンコードとしての単一の命令が含まれており、コンパイルされたプログラムが実行されるとすぐに実行されます。

上記のバイナリファイルは特定の構造に従い、最も一般的なものの1つはELFという名前で、Executable and LinkableFormatを省略します。実行可能ファイル、再配置可能オブジェクトファイル、共有ライブラリ、およびコアダンプに広く使用されています。







20年前の1999年に、86openプロジェクトは、x86プロセッサ上のUnixおよびUnixライクなシステムの標準バイナリファイル形式としてELFを選択しました。幸い、ELF形式は、SystemVアプリケーションバイナリインターフェイスとツールインターフェイス標準の両方で以前に文書化されていました[4]。この事実により、Unixベースのオペレーティングシステムのさまざまなベンダーと開発者の間の標準化に関する合意が大幅に簡素化されました。



その決定の背後にある理由は、ELFの設計でした。柔軟性、拡張性、およびさまざまなエンディアン形式とアドレスサイズに対するクロスプラットフォームのサポートです。 ELFの設計は、特定のプロセッサ、命令セット、またはハードウェアアーキテクチャに限定されません。実行可能ファイル形式の詳細な比較については、こちら[3]をご覧ください。



それ以来、ELF形式はいくつかの異なるオペレーティングシステムで使用されています。特に、これにはLinux、Solaris / Illumos、Free-、Net-、OpenBSD、QNX、BeOS / Haiku、およびFuchsiaOSが含まれます[2]。さらに、Android、Maemo、Meego OS / Sailfish OSを実行しているモバイルデバイスや、PlayStation Portable、Dreamcast、Wiiなどのゲーム機にも搭載されています。





この仕様では、ELFファイルのファイル名拡張子は明確にされていません。使用されているのは、.axf、.bin、.elf、.o、.prx、.puff、.ko、.so、.modなどのさまざまな文字の組み合わせです。

ELFファイルの構造

Linuxターミナルでは、コマンドman elfにより、ELFファイルの構造に関する便利な要約が表示されます。



リスト1:ELF構造のマンページ

$男11

ELF(5)LinuxプログラマーマニュアルELF(5)

名前
elf-Executable and Linking Format(ELF)ファイルの形式

概要
#含む

説明
ヘッダーファイルは、ELF実行可能バイナリの形式を定義します
ファイル。これらのファイルの中には、再配置可能な通常の実行可能ファイルがあります
オブジェクトファイル、コアファイル、共有ライブラリ。

ELFファイル形式を使用する実行可能ファイルは、ELFヘッダーで構成されます。
その後に、プログラムヘッダーテーブルまたはセクションヘッダーテーブル、あるいはその両方が続きます。
ELFヘッダーは、常にファイルのオフセットゼロにあります。プログラム
ファイル内のヘッダーテーブルとセクションヘッダーテーブルのオフセットは次のとおりです。
ELFヘッダーで定義されています。 2つの表は、残りの
ファイルの特殊性。

..。

上記の説明からわかるように、ELFファイルはELFヘッダーとファイルデータの2つのセクションで構成されています。ファイルデータセクションは、ゼロ個以上のセグメントを説明するプログラムヘッダーテーブル、ゼロ個以上のセクションを説明するセクションヘッダーテーブルと、それに続くプログラムヘッダーテーブルからのエントリによって参照されるデータ、およびセクションヘッダーテーブルで構成できます。各セグメントには、ファイルの実行時の実行に必要な情報が含まれ、セクションには、リンクと再配置のための重要なデータが含まれています。図1は、これを概略的に示しています。

ELFヘッダー

ELFヘッダーは32バイトの長さで、ファイルの形式を識別します。これは、0x7Fの4つの一意のバイトのシーケンスで始まり、0x45、0x4c、および0x46が続き、3文字のE、L、およびFに変換されます。他の値の中でも、ヘッダーは、32またはFのELFファイルであるかどうかも示します。 64ビット形式は、ほとんどまたは大きなエンディアンを使用せず、ELFバージョンと、適切なアプリケーションバイナリインターフェイス(ABI)およびcpu命令セットと相互運用するためにファイルがコンパイルされたオペレーティングシステムを示します。

バイナリファイルタッチの16進ダンプは次のようになります。

。リスト2:バイナリファイルの16進ダンプ

$ hd / usr / bin / touch |頭-5
00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 | .ELF ........... |
00000010 02 00 3e 00 01 00 00 00 e3 25 40 00 00 00 00 00 | ..> ......%@ ..... |
00000020 40 00 00 00 00 00 00 00 28 e4 00 00 00 00 00 00 | @ .......(....... |
00000030 00 00 00 00 40 00 38 00 09 00 40 00 1b 00 1a 00 | [メール保護] @ ..... |
00000040 06 00 00 00 05 00 00 00 40 00 00 00 00 00 00 00 | [メール保護] |

Debian GNU / Linuxは、GNUの「binutils」パッケージで提供されるreadelfコマンドを提供します。スイッチ-h(–file-headerの短縮版)を使用すると、ELFファイルのヘッダーが適切に表示されます。リスト3は、コマンドタッチの場合のこれを示しています。

。リスト3:ELFファイルのヘッダーを表示する

$ readelf -h / usr / bin / touch
ELFヘッダー:
マジック:7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
クラス:ELF64
データ:2の補数、リトルエンディアン
バージョン:1(現在)
OS / ABI:UNIX-System V
ABIバージョン:0
タイプ:EXEC(実行可能ファイル)
マシン:Advanced Micro Devices X86-64
バージョン:0x1
エントリポイントアドレス:0x4025e3
プログラムヘッダーの開始:64(ファイルへのバイト)
セクションヘッダーの開始:58408(ファイルへのバイト数)
フラグ:0x0
このヘッダーのサイズ:64(バイト)
プログラムヘッダーのサイズ:56(バイト)
プログラムヘッダーの数:9
セクションヘッダーのサイズ:64(バイト)
セクションヘッダーの数:27
セクションヘッダー文字列テーブルインデックス:26

プログラムヘッダー

プログラムヘッダーは、実行時に使用されるセグメントを示し、プロセスイメージの作成方法をシステムに指示します。リスト2のヘッダーは、ELFファイルがそれぞれ56バイトのサイズの9つのプログラム・ヘッダーで構成されており、最初のヘッダーがバイト64から始まることを示しています。

この場合も、readelfコマンドはELFファイルから情報を抽出するのに役立ちます。スイッチ-l(–program-headersまたは–segmentsの略)は、リスト4に示すように詳細を表示します。

。リスト4:プログラムヘッダーに関する情報を表示する

$ readelf -l / usr / bin / touch

Elfファイルの種類はEXEC(実行可能ファイル)です。
エントリポイント0x4025e3
オフセット64から始まる9つのプログラムヘッダーがあります

プログラムヘッダー:
タイプオフセットVirtAddrPhysAddr
FileSizMemSizフラグの整列
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
0x00000000000001f8 0x00000000000001f8 R E 8
INTERP 0x0000000000000238 0x0000000000400238 0x0000000000400238
0x000000000000001c 0x000000000000001c R 1
[プログラムインタープリターの要求:/lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x000000000000d494 0x000000000000d494 R E 200000
LOAD 0x000000000000de10 0x000000000060de10 0x000000000060de10
0x0000000000000524 0x0000000000000748 RW 200000
動的0x000000000000de280x000000000060de28 0x000000000060de28
0x00000000000001d0 0x00000000000001d0 RW 8
注0x00000000000002540x0000000000400254 0x0000000000400254
0x0000000000000044 0x0000000000000044 R 4
GNU_EH_FRAME 0x000000000000bc40 0x000000000040bc40 0x000000000040bc40
0x00000000000003a4 0x00000000000003a4 R 4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 10
GNU_RELRO 0x000000000000de10 0x000000000060de10 0x000000000060de10
0x00000000000001f0 0x00000000000001f0 R 1

セクションからセグメントへのマッピング:
セグメントセクション..
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text.fini。 rodata .eh_frame_hdr .eh_frame
03 .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06 .eh_frame_hdr
07
08 .init_array .fini_array .jcr .dynamic .got

セクションヘッダー

ELF構造の3番目の部分は、セクションヘッダーです。これは、バイナリの単一セクションをリストすることを目的としています。スイッチ-S(–section-headersまたは–sectionsの略)は、さまざまなヘッダーを一覧表示します。 touchコマンドに関しては、27のセクションヘッダーがあり、リスト5は、それらの最初の4つと最後の1つだけを示しています。各行は、セクションサイズ、セクションタイプ、およびそのアドレスとメモリオフセットをカバーしています。

。リスト5:readelfによって明らかにされたセクションの詳細

$ readelf -S / usr / bin / touch
オフセット0xe428から始まる27のセクションヘッダーがあります。

セクションヘッダー:
[Nr]名前タイプアドレスオフセット
サイズEntSizeフラグリンク情報整列
[0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[1] .interp PROGBITS 0000000000400238 00000238
000000000000001c 0000000000000000 A 0 0 1
[2] .note.ABI-tag NOTE 0000000000400254 00000254
0000000000000020 0000000000000000 A 0 0 4
[3] .note.gnu.build-i NOTE 0000000000400274 00000274
..。
..。
[26] .shstrtab STRTAB 0000000000000000 0000e334
00000000000000ef 0000000000000000 0 0 1
フラグの鍵:
W(書き込み)、A(割り当て)、X(実行)、M(マージ)、S(文字列)、l(大)
I(情報)、L(リンク順序)、G(グループ)、T(TLS)、E(除外)、x(不明)
O(追加のOS処理が必要)o(OS固有)、p(プロセッサー固有)

ELFファイルを分析するためのツール

上記の例からお気づきかもしれませんが、GNU / Linuxは、ELFファイルの分析に役立つ多くの便利なツールで具体化されています。最初に検討するのは、ファイルユーティリティです。

fileは、再配置可能、実行可能、または共有オブジェクトファイル内のコードが対象とする命令セットアーキテクチャを含む、ELFファイルに関する基本情報を表示します。リスト6では、/ bin / touchがLinuxStandard Base(LSB)に準拠し、動的にリンクされ、GNU / Linuxカーネルバージョン2.6.32用に構築された64ビットの実行可能ファイルであることを示しています。

。リスト6:ファイルを使用した基本情報

$ファイル/ bin / touch
/ bin / touch:ELF 64ビットLSB実行可能ファイル、x86-64、バージョン1(SYSV)、動的リンク、インタープリター/ lib64 / l、
GNU / Linux 2.6.32の場合、BuildID [sha1] = ec08d609e9e8e73d4be6134541a472ad0ea34502、削除
$

2番目の候補はreadelfです。 ELFファイルに関する詳細情報が表示されます。スイッチのリストは比較的長く、ELF形式のすべての側面をカバーしています。スイッチ-n(–notesの略)の使用リスト7は、ファイルtouchに存在するnoteセクション(ABIバージョンタグとビルドIDビット文字列)のみを示しています。

。リスト7:ELFファイルの選択したセクションを表示する

$ readelf -n / usr / bin / touch

ファイルオフセット0x00000254、長さ0x00000020で見つかったメモの表示:
所有者データサイズ説明
GNU 0x00000010 NT_GNU_ABI_TAG(ABIバージョンタグ)
OS:Linux、ABI:2.6.32

ファイルオフセット0x00000274、長さ0x00000024で見つかったメモの表示:
所有者データサイズ説明
GNU 0x00000014 NT_GNU_BUILD_ID(一意のビルドIDビット文字列)
ビルドID:ec08d609e9e8e73d4be6134541a472ad0ea34502

SolarisおよびFreeBSDでは、ユーティリティelfdump [7]はreadelfに対応していることに注意してください。 2019年の時点で、2003年以降の新しいリリースまたは更新はありません。

3番目は、Linuxで純粋に利用できるelfutils [6]という名前のパッケージです。 GNU Binutilsの代替ツールを提供し、ELFファイルの検証も可能にします。パッケージで提供されるユーティリティの名前はすべて、「elfutils」のeuで始まることに注意してください。

最後になりましたが、objdumpについて説明します。このツールはreadelfに似ていますが、オブジェクトファイルに焦点を当てています。 ELFファイルやその他のオブジェクト形式に関する同様の範囲の情報を提供します。

。リスト8:objdumpによって抽出されたファイル情報

$ objdump -f / bin / touch

/ bin / touch:ファイル形式elf64-x86-64
アーキテクチャ:i386:x86-64、フラグ0x00000112:
EXEC_P、HAS_SYMS、D_PAGED
開始アドレス0x00000000004025e3

$

ELFファイルの内容を読み取ったり操作したりするためのツールを含む「elfkickers」[9]と呼ばれるソフトウェアパッケージもあります。残念ながら、リリースの数はかなり少ないので、それについて言及し、それ以上の例は示しません。

開発者は、代わりに「pax-utils」[10,11]をご覧ください。この一連のユーティリティは、ELFファイルの検証に役立つ多数のツールを提供します。例として、dumpelfはELFファイルを分析し、詳細を含むCヘッダーファイルを返します。図2を参照してください。

結論

巧妙な設計と優れたドキュメントの組み合わせのおかげで、ELF形式は非常にうまく機能し、20年経ってもまだ使用されています。上記のユーティリティを使用すると、ELFファイルを洞察し、プログラムが何を実行しているかを把握できます。これらは、ソフトウェアを分析するための最初のステップです–ハッピーハッキング!

リンクとリファレンス
謝辞

執筆者は、この記事の準備に関して彼のサポートをしてくれたAxelBeckertに感謝します。