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

前回は、オブジェクトファイルと言いながら、絶対番地のHEXファイルなど、ついついFlashライタでもなければ使わないようなものにフォーカスしてしまいました。今回は、心を入れ替えて(どう?)、本道のリンク可能なオブジェクトファイルを調べてみたいと思います。コンパイル、アセンブルした後のオブジェクトファイルとリンクした後の実行可能なオブジェクトファイル、例によってRaspbian上のArmの32ビットコードで見てみます。

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

オブジェクトファイルなど下々のところに慣れていらっしゃらない方向けに、今回はバッサリやりたいと思います。ダンプなどすると大量の情報が出てきますけんど、以下の4つのセクションだけ最低押さえておけば、

なんとかなる

と言ってしまいましょう。

  1. .text
  2. .data
  3. .bss
  4. .rodata

この4つだけ、その意味を押さえてしまいたいと思います。

まず、.textセクション。ザックリ言ってしまえば、「低レベルプログラミング」の世界では、textというのは、実行可能な機械語の命令コードが入っているプログラムの本体を収める場所です。「テキストファイル」を連想してはいけません。通常「テキストファイル」であるソースコードをコンパイラ様がお読みになり、直接か、アセンブラを通じて間接かは処理系にもよりますが、生成された機械語命令コードがここに置かれる、というわけです。

その次の、.dataセクション、.bssセクション、.rodataセクションは、データを置く場所なのですが、なぜ3つもあるかと言えば、

  • .dataセクションは、初期値が与えられた変数などを置く場所
  • .bssセクションは、初期値の無い(普通は0だが)変数などを置く場所
  • .rodataセクションは、定数(初期値ともいえる)を置く場所

という使い分けが必要になるからです。Flash-ROMとRAMを持つようなマイコンを想像すると分かり易いと思います。.dataは変数なので読み書きするのですからRAM上に置く必要があります。しかし、電源切ればRAMは消えてしまうので、毎回起動時にどこからか初期値を持ってくる必要があります。大抵のFlashマイコンでは、.dataセクションの変数に対応する初期値は.rodataセクション(roはRead Onlyの略だと思います)に並べられていて、ターゲットアプリケーションプログラムが走る前に実行されるスータトアップのためのプログラムが.rodataから.dataへとコピーしているのであります。それ以外にも定数と見なせるデータ類は.rodataに格納されます。

ただし、WindowsやLinuxのように、普通のプログラムは全てDRAM上に展開されるような場合は別です。.dataといい、.rodataといい、実体は、読み書き可能なDRAMでしかないので、.dataセクションには最初から初期値がずらずらと並べてあって、それをメモリに展開すれば即変数となり、.rodataも同じです。ただ、.rodata領域はリードオンリで書き換えないというお約束(OSによってはハードウエアで書き換え保護することも可能かもしれませんがそれはまた別な話)でしかありません。

これに対して、.bssは何も初期化する必要がありません。実データそのものの記録は不要で、どれだけの読み書き可能なメモリを確保して、そこに置く変数の番地はどこ、という情報のみが記録されていれば良いわけです。

ここまでのデータ関係のセクションと対比するならば、.textセクションは、Flashマイコンであれば、FlashROMの実行可能領域に書き込まれるべきもの(実行時には通常書き換えできない)であり、LinuxやWindowsでは、.rodataのように結局書き換え可能なDRAM上に展開されるけれども通常は書き換えしないもの、です。

まず、前回も使ったサンプルプログラムをコンパイル、アセンブルした時点でのオブジェクトファイルの中のセクションの状態を調べてみます。

$ gcc -g -O0 -c サンプルプログラム名.c

とやって出来た サンプルプログラム名.o なるファイルを objdump -x でダンプしたものの一部です。

セクション:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         0000022c  00000000  00000000  00000034  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000018  00000000  00000000  00000260  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  00000000  00000000  00000278  2**0
                  ALLOC
  3 .rodata       00000010  00000000  00000000  00000278  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
~以降略~

ここだけ抑えて置けば、と書かせていただいた4種のセクションが最初に並んでいます。表の欄のうちVMAは、Virtual Memory Addressです。CPUが実行するときに「見える」番地です。LMAは、Load Memory Addressです。オブジェクトファイルを実際にメモリ上に展開するときに使用されるアドレスだと思います。アプリケーションプログラムでは通常 VMA=LMAの関係の筈ですが、論理、物理のアドレスのマッピングによっては異なる場合があるかと思われます。

ここで分かるのは、ファイル単位でコンパイル(からアセンブル)された時点では、メモリアドレスは決まっていない、ということです。それぞれのセクション毎、仮に0番地スタートということでオブジェクトが生成されています。それだけであると、例えば、テキストセクションから参照しているデータセクション上の変数のアドレスとか、後でメモリアドレスを決定して入れ込まなければいけない場所が分からなくなってしまいます。そのため、後でメモリアドレスを決定するときに書きこまなければいけない場所を示すためのリロケーション情報というものが含まれています。.textセクションのリロケーション情報を以下に引用しておきます。頭文字gで始まっているのが、サンプルプログラム内で定義しているグローバル変数です。

RELOCATION RECORDS FOR [.text]:
OFFSET   TYPE              VALUE 
000000b8 R_ARM_CALL        atoi
000000f4 R_ARM_CALL        sub
000001e8 R_ARM_CALL        printf
00000218 R_ARM_ABS32       g_s1
0000021c R_ARM_ABS32       g_v1
00000220 R_ARM_ABS32       ga
00000224 R_ARM_ABS32       g_v2
00000228 R_ARM_ABS32       .rodata

他にもデバッグ情報など、プログラムの実行に直接かかわらないようなセクションも含まれるので、この時点でオブジェクトファイルには12ものセクションが含まれていました。

さて、上の1ファイルをコンパイルして作った .o なるオブジェクトファイルをリンクして出来た実行ファイル(これもELFオブジェクトファイル)をダンプしてみます。通常は、複数のオブジェクトファイルをリンクして、相互の参照関係を解決するわけですが、今回は、1個だけ。であれば何もやることが無いような気もしますが、そうではないです。リンク後、実行ファイル上のセクション数は、なんと30個に増えていました。内部を見ていくと分かりますが、暗黙でリンクしなければいけないものが結構あるのでした。例えばmain()関数の前に走る初期化ルーチンのようなも。また、静的にはリンクされないけれど、動的に(実行する時に)リンクして使われるライブラリファイルと接続するための情報なども必要です。cの場合、libcと言われるライブラリファイルの中に多くの標準的な関数群などが入っています。多くのプログラムが使用するのでメモリ内にほぼ常に存在している筈。それを呼び出すための仕組みです。そういったもろもろのものがファイルの先頭の方からいろいろ配置されているので、大事な4個のセクションは、大分後ろの方に現れます。こんな感じ。

セクション:
Idx Name          Size      VMA       LMA       File off  Algn
~途中略~
 12 .text         00000380  00010344  00010344  00000344  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
~途中略~
 14 .rodata       00000014  000106cc  000106cc  000006cc  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
~途中略~
 21 .data         00000020  00021024  00021024  00001024  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 22 .bss          00000014  00021044  00021044  00001044  2**2
                  ALLOC

順番も変わっていますが、サイズも変わっており、何より大事なのは、メモリ上の番地が割り振られていることです。ここでどんなメモリ番地(論理アドレス)を割り振るのかはOSのメモリの使い方で決まってきます。OSの無い、ベアメタルのFlashマイコンなどでは、どの番地にどのセクションを置くべきなのか、リンカに明示的に指示してやることが必要な場合もあります。

当然、先ほどの個別 .o ファイル内にあった、リロケーション情報は「消費」されて消えています。その代わり、各シンボルには値(メモリ番地であることがある)が割り振られています。SYMBOL TABLE:という項目を見ると、たとえば、g_v2という大域変数は、以下のように.dataセクション内の 0x0002102c番地に置かれている、ということが判明します。

0002102c g O .data 00000004 g_v2

今回も具体的に変数にアクセスするアセンブリ言語コードまで行きつけませんでした。コードを見るのは今度こそ次回ですかね。

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

ぐだぐだ低レベルプログラミング(13) 変数アクセスのコードを眺めてみれば