ぐだぐだ低レベルプログラミング(11) オブジェクトファイルその1

前回、コンパイラの出力を元とするオブジェクトファイルのアセンブリリストをながめておりました。そこで現れてくるメモリアクセスを読み解くときには、セクションなる存在を無視することはできません。ただ、セクションなるもの、多面的な顔を持っていますな。全てを一度に知ろうとしても膨大すぎるかも。多分、列挙する前に速攻で忘れそうです。裏では活躍されているのでしょうが、実際、とりあえず知らんでも済むセクションの方々も多数。しかし、だいたいセクションとは何に含まれておるのかや?その在りかを知らずしてセクションの理解には至らぬであろう(本当か)。今回はその手前のオブジェクトファイルについてぐだぐだ書いてみました。

※「ぐだぐだ低レベルプログラミング」投稿順indexはこちら

コンピュータの神々の時代、某社の偉大な設計者は試作機の制御盤のスイッチをパチパチやってブートローダを入力し、ボタンを押すと「見事に」動いたという話を聞いたことがあります。その時、彼の頭脳の中にはセクションだの、オブジェクトファイルだのはなかったでしょう。たた、メモリ番地とそこに書き込まれてあるべきデータ(バイナリ)だけがあった(多分?)。それは今も昔も変わらぬコンピュータの原理であります。だいたい昔は、アドレスとバイナリコードを見ただけで動作を理解できる勇者が多くいたような気がしますが、そんな時代はとっくに過ぎさったようです。

コンパイラからバイナリツール群の助けを得て(それすら意識しないことも多いですが)プログラムを作る場合、中間表現としてのオブジェクトファイルと、オブジェクトファイル内の構造を管理するためのセクションと呼ばれる「区画」にお世話にならざるを得ません。

さて、その、オブジェクトファイルといっても、いくつもフォーマットがあることはご存知かと思います。linux系であれば 、普通 ELF と呼ばれるフォーマットを使っていますし、Windowsであれば PE と呼ばれるフォーマットです。もし fileコマンドが使えるならば(Linuxのコマンドですが、Windowsでも、WSLやMSYS, Cygwinが入っていれば使えるでしょう)

$ file 実行ファイル

とすれば、その実行ファイルがどのようなオブジェクトフォーマットであるか教えてくれます。同じELFであっても

  • x86-64のLinuxであれば、ELF 64-bit LSB executable
  • 32bit ArmのRasbianであれば、ELF 32-bit LSB executable, ARM, EABI5

など、違いがあることも分かります。ここから分かることは、.exe などの拡張子がついているオブジェクト・ファイルといえども、CPUが即実行可能なメモリイメージそのものとは「ちょっと違う」かもしれないということです。オブジェクト・ファイルといっても、以下の4段階くらいに分類できるのではないかと考えます。

  1. 特定のアドレスとはくっついていない(再配置可能)で、実行可能なプログラムの断片のコード、データ、と付加的な情報を格納しているオブジェクトコード
  2. 部分的なコード、データ間の相互参照関係を解決し、一本の実行可能なプログラムにまとめ上げているもの。特定のアドレスとはくっついておらず(再配置可能)、付加的な情報も含むオブジェクトコード
  3. 部分的なコード、データ間の相互参照関係を解決し、一本の実行可能なプログラムにまとめ上げた上で、実行時のアドレスと対応づけられた(再配置不能)なオブジェクトコード、ただし、デバッグなどの付加的な情報も含む
  4. 実行時のアドレスと対応づけられた(再配置不能)なオブジェクトコードであって、メモリに配置すべき情報以外の付加的な情報を含まない純粋なコードとデータを格納したもの

真に実行可能なメモリイメージへの近さから、一番下の4から逆に見ていきます。4の場合、ELFやPEなどのオブジェクト・フォーマットの出番はなく、またそれを必要とするのは、多くの場合、マイコン組み込みのフラッシュ・プログラマなどのハードウエアに近いツールのみです。フラッシュ・プログラマは、物理的なフラッシュメモリの何番地に何というデータを書き込むのか分かれば、その仕事を達成できるので、必要なのは、番地とデータの羅列でしかありません。そのデータが機械語命令コードなのか、データなのかなど知る必要もなし。このような用途に活躍するのが、

  • HEX型式
  • BIN型式

です。ここでBIN型式は、特定番地に始まるバイナリデータの羅列であって、まさにメモリイメージそのものであることが普通です。肝心の番地については、ファイルの外側で「握っておく」必要があります。これに対してHEX型式は、バイナリではなく、テキストファイルです。主に16進数の文字列をずらずらと並べてあります。HEXフォーマットの場合、レコード毎にメモリ番地が指定されているので、一本のファイル内に飛び飛びのメモリブロックを置いておくことが可能です。HEXフォーマットには昔会社毎にいろいろ流儀があったという歴史的経緯あり、いくつも種類があります。有名どころでは、

  • モトローラSフォーマット
  • インテルHEXフォーマット

でしょうか。実用上では、いろいろなCPUに適用しやすいモトローラSフォーマットを使うことが多いのではないかと思います。インテルHEXは名前こそ有名ですが、仕様的に8086にひっついてしまった部分が多いので、実際にはあまり使われないように思います。またモトローラSにしても、アドレスの幅で複数のフォーマットがあります。一般的なマイコン用フラッシュプログラマではアドレス幅32ビットのフォーマットを受け入れるのではないかと思います。この第4のカテゴリのフォーマットのファイルをデスクトップで使うようなOSが受け入れて実行するケースは今ではほぼありませんが、遥か以前はありました。CP/MやMS-DOSなどの

.COM型式実行ファイル

です。このCOMは、Common Object Modelではありません。勿論インターネットの.comドメインでもありません。

現在のWindowsの実行ファイルは、.exe という拡張子をもっていますが、16ビットのMS-DOSの時代は .com という拡張子と .exe という拡張子の実行ファイルが混在していました。初期のWindowsでは、comや古い16ビット型式の.exeも使えた時期がありました(Windowsが64ビットしてからはほぼ絶滅)。このうち、.com型式は、絶対番地で100hスタートのバイナリイメージほぼそのものの実行ファイル形式でした。この起源は、MS-DOS(PC-DOC)以前のパーソナルコンピュータ用OSのデファクトであったデジタルリサーチ社の

CP/M

(CPUは8080)に遡ります。ただし、MS-DOSの.com型式は、CP/Mの.comが真に絶対番地100Hであったのと比べると、8086のセグメントの中の100H固定であって、プログラムの外側でセグメントアドレスを操作できるので真の絶対番地とは言えません。しかしファイル内部は限りなくCP/MのCOM型式に近かったです。

なお、GnuのBinutilでも、モトローラSフォーマットの絶対番地HEXファイルを生成することは出来ます。手順としては、

  • リンカ(ld)で、個別のオブジェクトファイルをリンクして一本のファイルにまとめるときに、各セグメントに対して絶対番地を指定する
  • リンカの出力した絶対番地指定済のELFオブジェクトに対して、出力フォーマットをモトローラS(32ビットモードであればオプション -O srec –srec-forceS3)指定で、Binutilのプログラム objcopy で処理する

です。

さて、上記のモトローラS生成の途中に先ほどのカテゴリわけの第3のものが出てきてしまいました。ELFのくせに絶対番地な。ただ、この手のファイルが出現するのは、ほぼ実用上は、

フラッシュマイコンのフラッシュROMの書き換え

過程くらいでしょう。マイコンツールをセットアップするときなどに「誰か」が設定してくれているのか、ツールメーカのスクリプトで自動設定しているのかは分かりませんが、まず、意識することはないかも。

ぐだぐだ書いている間に絶対番地で終わってしまい、肝心の1、2のカテゴリまでたどり着けませんでした。予定数終了。1、2はまた次回ですかね。

ぐだぐだ低レベルプログラミング(10) アセンブラリスティング へ戻る

ぐだぐだ低レベルプログラミング(12) オブジェクトファイルその2 へ進む