わらばんし仄聞記

南の国で引きこもってるWeb屋さん

簡易brainf*ckコンパイラを作る part2

part1に引き続き、もう一点必要なものがある。
brainf*ckはそれぞれの命令以外に、少なくともサイズ30000のバイトの配列を持つ必要があり、これは今までgasの記法を主に使っている所で見られた

.comm mem, 30000

に相当する。
この領域をELFファイルに直接持たせ、使用する必要がある。

目標

ELFファイルにbss領域を持たせ、その領域で値の読み書きをする。

コード

今までにあったELFとの違いは

  • プログラムヘッダが2つある
  • 実行コード部分でbss領域に書き込む処理を追加
  • sectionヘッダーにbss領域についての記述を追加

といったところになる。

処理本体のコードではまず、bss領域のアドレスをrbxレジスタへ格納し(46行目)、その先頭アドレスへAsciiコードで"H"を表す0x48を格納している。
その後のwriteシステムコールを呼ぶ箇所で、出力対象文字列へのアドレス指定へbss領域を指定。最後に後始末としてexitシステムコールをよんで終了させている。
ここでrbxレジスタへ指定しているbss領域のアドレスは0x08248000で、この値はbss領域用のsectionヘッダーにて、sh_addrの値を$$ + 200000としている事により、orgディレクティブで指定している0x8048000から200000だけ進んだ位置がbss領域として確保されている為にこの値となる。

前述したように、bss領域についてsectionヘッダーを追加しており、また、bss領域はセグメントなのでprogramヘッダーも追加している。
ここでbss領域についてprogramヘッダーを定義する際にそれぞれの値をどう指定するかは、bss領域を使う単純なプログラムを書いて、そこから得られる実行ファイルのヘッダー情報を読み取って真似た。
例えば

$ cat bss.c
int foo[200];

void _start(){
}

というようなbss.cを記述し

$ gcc -nostdlib bss.c
$ readelf -all a.out

とすることで

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x00000000000001e8 0x00000000000001e8  R E    200000
  LOAD           0x0000000000001000 0x0000000000601000 0x0000000000601000
                 0x0000000000000000 0x0000000000000320  RW     200000
  NOTE           0x0000000000000158 0x0000000000400158 0x0000000000400158
                 0x0000000000000024 0x0000000000000024  R      4
  GNU_EH_FRAME   0x0000000000000198 0x0000000000400198 0x0000000000400198
                 0x0000000000000014 0x0000000000000014  R      4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     8

といった結果を得られる。同時に得られるsectionヘッダーの情報

  [ 5] .bss              NOBITS           0000000000601000  00001000
       0000000000000320  0000000000000000  WA       0     0     32

より、bss領域のプログラムヘッダーは

  LOAD           0x0000000000001000 0x0000000000601000 0x0000000000601000
                 0x0000000000000000 0x0000000000000320  RW     200000

であることが読み取れる。アドレスの開始位置とサイズが同じなので。
これらを参考に、ELFヘッダーの調整、programヘッダーの追加、sectionヘッダーの追加をすることでbss領域を確保することができる。

実行

$ nasm -f bin -o nasm.out elf64-bss.asm
$ ./nasm.out
H

期待する結果を得られ、また

$ readelf -all nasm.out

...

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .shstrtbl         STRTAB           0000000000000000  000000e6
       0000000000000016  0000000000000000           0     0     1
  [ 2] .text             PROGBITS         00000000080480b0  000000b0
       0000000000000036  0000000000000000  AX       0     0     4
  [ 3] .bss              NOBITS           0000000008078d40  00001000
       0000000000007530  0000000000000000  WA       0     0     16

...

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000008048000 0x0000000008048000
                 0x00000000000001fc 0x00000000000001fc  R E    200000
  LOAD           0x0000000000001000 0x0000000008248000 0x0000000008248000
                 0x0000000000000000 0x0000000000007530  RW     200000
...

期待されるセグメントがbss領域として動作していると思われます