[MS技術]
COFF形式ファイル
以前にOS自作入門をやったとき、COFF形式ファイルを生成する処理を作ったのだが、今度はCOFF形式ファイルを読み取る必要が出てきたので海外ブログを翻訳しつつ注釈を入れつつまとめたいと思う。本当にやりたいことはELF形式でそれをやることなので、ELF形式に関しても同程度にまとめる必要がでてくるかもしれない。大本となるホームページは消えているが、Internet Archiveにあるのでそれを参照する。
汝のプログラムを知れ:PE/COFFの紹介
私は、エントリーレベルのプログラマーの興味をわずかに超えているが、前進するために不可欠なトピックを扱う一連の記事を書くことにしました。 これは、OSの観点からプログラムが実際にどのように機能するかについての一般的な考え方を示す概要です。 そのため、このシリーズを「Know your program ...」と名付けました。
今日のテーマは、Windows実行可能ファイルの形式です。 デスクトップOSを支配するWindowsには、私を含む膨大な数のソフトウェア開発者がいます。 しかし、多くのプログラマーにとってのコンパイルとリンクの成果はブラックボックスです。 私はそれが少しややこしいことを認めますが、その分解の楽しみから私たちを斥けるほどではありません。 まず、基本を考えてみましょう。
基本
実行可能ファイルのロード方法とその形式そのものが、OSについて多くのことを教えてくれるかもしれません。 たとえば、1つの64Kメモリセグメントにコードとデータを含むMS-DOS .COM形式は、使用可能なメモリがほとんどない状態で動作するシステムに適していました。 ヘッダーは含まれず、固定アドレスにロードされました。 時間が経過し、ソフトウェアで使用できるメモリのサイズが増えると、他の形式が導入され、コードとデータをさまざまなメモリ領域に配置し、実行可能ファイルをさまざまな場所にロードできるようになりました。
より高度な形式では、ファイルがセクションに分割されるため、ファイルがロードされると、セクションは分割されたメモリ領域にロードされます。 実行ファイルを分析すると、OSがさまざまなメモリ領域に特定のアクセス許可を設定できるため、その部分の管理と安全性の問題の軽減に役立ちました。 たとえば、コードセクションからの書き込みアクセスを取り消したり、データセクションにある命令の実行を禁止したりすることは非常に合理的です。
最も重要な高レベルのセクションは、コードセクションとデータセクションです。今はMicrosoftの標準について話しているので、それらの命名規則にはこだわりましょう。
・コードセクション .text
・データセクション .data
・読み取り専用データセクション .rdata
コードセクションの指定は非常に明確ですが、データセクションに立ち寄って、そこに含まれるデータの種類を確認することはおそらく価値があります。
セクション.dataは、グローバル変数と静的変数を含む読み取り/書き込み領域ですが、自動変数は実行時にスタックに割り当てられます。 ご存知のように、初期化されていないグローバル変数は自動的にゼロで初期化されます。 初期化されていないグローバル変数を持つこのような領域は、歴史的に.bssセクションと呼ばれ、割り当てられると完全にゼロになりました。 ただし、最適化が無効になっているにもかかわらず、Microsoftリンカーは.bssセクションを.dataとマージする場合があります。 したがって、初期化されたグローバル変数と初期化されていないグローバル変数の両方が.dataセクションに移動する可能性があります。 それは私がデモに使用したプログラムの場合でした、これの唯一の理由は画像サイズの節約であるかもしれません。
セクション.rdataは読み取り専用のメモリ領域であり、たとえば、プログラムで使用する文字列リテラルやグローバル定数配列を保持します。 他のグローバル定数変数もこのセクションに移動すると思われる場合は、間違っている可能性があります。 組み込み型の定数変数は直接置換の対象となる可能性が高く、グローバルで定数にするユーザー定義のクラスまたは構造は、初期化されていないグローバル変数と一緒になります。 そして、それらの不変性の違反は、コンパイル時にコンパイラによって保護されますが、.rdataの文字列リテラルを上書きしようとすると、メモリページ保護メカニズムによって生成される実行時エラーになります。 言われたことの実例として、グローバル変数とそれらが見つかるコメントを含む次のスニペットを見てください。
// これはグローバルスコープ int a=1; // .data に配置される int b; // .data に配置される (本当は .bss に配置されるべき) const int c=0x00ff00ff; // これは配置されない、値が代入されているから const int constArray[] = {1,2,3,4,5,6,7,8,9}; // .rdata に配置される int mutableArray[] = {0xa, 0xb, 0xc, 0xd, 0xe, 0xf}; //.data に配置される int uninitArray[10]; //.data に配置される (本当は .bss に配置されるべき) char str1[]= "Hello read-write world!"; // 文字列リテラルは配置されない、配列は.data に配置される char *str2 = "Hello read-only world!"; // 文字列リテラルは.rdata に配置される、ポインタは.data に配置される SomeClass t; // .data に配置される (本当は .bss に配置されるべき) const SomeClass ct; // .data に配置される (本当は .bss に配置されるべき)
いくつかのグローバル変数を宣言し、それらをコードで使用しない場合、おそらくデータセクションでそれらを見つけることができません。これは、リンカーの最適化設定によって異なります。
ユーザーと起動コードによって定義された読み取り専用データに加えて、.rdataセクションには、インポートおよびエクスポートデータなど、Microsoftによって定義されたいくつかの特別なセクションとデータ構造が含まれています。 これらのデータ構造のいくつかについては、後で説明します。
セクションの横にあるもう1つの重要な概念は、再配置です。 一般に、再配置とは、コード内の一部のアドレスを別の値で上書きすることを意味します。これにより、参照オブジェクトの「再配置」が実現します。 たとえば、関数に変数を渡したい場合は、その変数のアドレスが使用されます(値を渡す場合は、値を読み取ってコピーするためにアドレスが必要です。参照を渡す場合は、アドレス自体が渡されます)。 私が述べたように、現代のOSは、リンカーによって設定されたものとは異なる可能性のあるアドレスに実行ファイルをロードする可能性があります。 その場合、コード内のすべての絶対アドレス参照は、デフォルトのロードアドレスと実際のロードアドレスの違いを考慮した固定値で書き直す必要があります。 これは「ベースリロケーション」と呼ばれ、少し後で紹介します。
簡単な歴史
セクションと再配置をサポートした最初の実行可能形式の1つは、「a.out」でした。 これは、PDPマシンの時代とその後しばらくしてUnixシステムで積極的に使用されました。 リンカによって生成されるファイルのデフォルト名にちなんでその名前が付けられました。 しばらくすると、a.outはCOFF形式に置き換えられ、可変数のセクションをサポートして名前を付けることができるようになりました。 これは大きな一歩であり、共有ライブラリの使用が可能になりました。 しかし、それでも、さまざまなシステムベンダーがCOFF標準を「拡張」する原因となる制限がありました。 Windowsの世界では、このような拡張機能は、MicrosoftがWindows NT3.1で導入したPE / COFF形式になりました。ここで、PEは「PortableExecutable」の略です。
マイクロソフトにとって、これは重要なマイルストーンであり、変化する世界の要件を満たす安価で拡張可能なソリューションでした。 DOS .COM、MZ実行可能ファイル、およびNE形式の後、古いUNIX環境でテストされたCOFFがWindowsに採用されました。 その後、64ビットプラットフォームをサポートするように拡張され、この変更はPE +またはPE32 +と名付けられました。 このフォーマットを15年以上使用しているので、悪い選択ではなかったと言えます。
いくつかの詳細
まず、実行ファイルのレイアウトを見てみましょう。 COFF形式はそれぞれ単純です。最初に、OSローダーにファイルのどこに何があるかを伝えるヘッダー領域があります。 このヘッダー領域には、COFFヘッダー、オプションヘッダー、セクションテーブルの3つのヘッダーが含まれています。 ヘッダー領域の後にセクションデータが続きます。
Microsoftはレイアウトを少し変更し、「このプログラムはDOSモードでは実行できません」というメッセージを表示する最初の小さなDOSプログラムに配置しました(もちろん、DOSで実行する場合)。 大幅に拡張されたもう1つの場所は、オプションヘッダーで、特定のウィンドウフィールドとデータディレクトリテーブルが追加されました。
- PE / COFFファイルの一般的なレイアウト
次のセクションからは簡単なヘッダーについての概要で、オフセットやサイズなどの気が散るような詳細は意図的に見ないようにしています。 この情報の一部はデモセクションで提供されており、脚注で完全な仕様とより詳細な説明を見つけることもできます。
リンク先は一番下に記載する。
COFFヘッダー
このヘッダーは最も一般的です。プログラム情報にはあまり役立ちませんが、私たちにとって重要な2つの値が含まれています。
・次に続くヘッダーのサイズ
・セクションテーブルのサイズ(より正確なセクション数)
オプショナルヘッダー
それは3つの領域で構成されています。