docs.komagata.orgの「ガチ鬱プログラマー日記」のRSSフィードを読んでいると、暫く前からjonesforthというアセンブラで書かれたForth処理系をLinuxのgasでコンパイルしようとして上手く行かなかったり、謎のオプションでエラーになったりトラブルに突き当たっている。
同じくアセンブラに手を出した人間として、捨てておくわけにはいかない。
というわけで自分もCentOS5.2(古っ)上でjonesforthのコンパイルに挑戦してみた。
ldとgasのバージョンは以下の通り:
$ ld --version GNU ld version 2.17.50.0.6-6.el5 20061020 ... $ as --version GNU assembler 2.17.50.0.6-6.el5 20061020 ...
jonesforthのサイトURLは以下:
"Download"の"jonesforth.s.txt"をローカルにダウンロードし、"jonesforth.S"にリネームする。
ソースコードを見てみると、FORTH処理系の概要や実装方法、gasアセンブラの簡単な説明からコンパイルオプションまで至れりつくせりなドキュメントが埋め込まれている。
・・・これでコンパイルできないって、かなり涙目。
ということで、ソースコード中で指示されている通りにgccを動かしてみる。
$ gcc -m32 -nostdlib -static -Wl,-Ttext,0 -Wl,--build-id=none -o jonesforth jonesforth.S /usr/bin/ld: unrecognized option '--build-id=none' /usr/bin/ld: use the --help option for usage information collect2: ld はステータス 1 で終了しました
・・・涙目。
binutilsのldコマンドが "--build-id" オプションを認識出来なくて失敗している。p0tの記事でも調査されたようだが、うまく解決できていないようだ。
Google先生の助けを借りて、ldや"--build-id"について検索していく内に、やはりldコマンドのバージョンが若干古いらしいことと、"--build-id"オプションは指定しなくても問題ない事が分かってきた。
まずGNU binutils本家のサイトを調べてみると、バージョン2.18で"--build-id"オプションが追加された旨がldのNEWSファイルに記されていた。
Changes in 2.18: * Linker sources now released under version 3 of the GNU General Public License. * ELF: New --build-id option to generate a unique per-binary identifier embedded in a note section. ...
GNU binutils本家から辿れる、最新2.20のldのinfoファイルを見てみる。
--build-id --build-id=style Request creation of .note.gnu.build-id ELF note section. ...
ファイルを識別する為のユニークなビットフィールドを ".note.gnu.build-id" というELFのnoteセクションに埋め込む。
styleには以下の値を指定出来る。
"uuid" | 128 random bits |
"sha1" | 160-bit SHA1 hash |
"md5" | 128-bit MD5 hash |
"0x" + hexdigits | 任意の16進数 |
styleが省略された場合は "sha1" を指定したものと見なされる。
特別なstyleとして "none" を指定すると、"--build-id" の機能を無効化する。
"--build-id"を調べていると、最近のLinuxディストリビューションで"--build-id"の有効化についてのドキュメントが見つかった。
".note.gnu.build-id"セクションは実行時にプロセスイメージにロードされる。もしkernelがサポートしていればコアダンプファイルに含まれるようになる。
OpenSUSEやFedoraProjectでこの機能を使うようになった背景として、FedoraProjectのページの"1.1 Summary"に以下の2点が挙げられている。(OpenSUSEの方も似たような事情のようだ)
前掲のFedoraProjectのページを見てみると
The Linux binutils-2.17.50.0.17 release includes this, in f8test1. binutils-2.17.50.0.17-3 and later in Rawhide have the feature.
というような記述があるが、バックポートでもされたのだろうか。
いずれにせよ、自分が使っているバージョンのld(2.17.50.0.6)ではサポートされていないのは確実である。
jonesforthに戻ると、以上の情報から "-Wl,--build-id=none" オプションはそもそも"--build-id"機能を無効化している。よって、"--build-id"オプションをサポートしていないバージョンのldでは指定する必要が無いと予想される。
実際にこのオプションを削った状態でコンパイルに挑戦してみる。
$ gcc -m32 -nostdlib -static -Wl,-Ttext,0 -o jonesforth jonesforth.S $ echo $? 0 $ file jonesforth jonesforth: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, not stripped
コンパイルに成功した。
続けてjonesforthのサイトからサンプルスクリプトファイルをダウンロードし、実行してみる。以下のコマンドサンプルがjonesforth.Sファイル中のドキュメントにあったので、試してみた。
$ cat jonesforth.f - | ./jonesforth 強制終了
強制終了してしまった。試しにjonesforth単体で動かしてみる。
$ ./jonesforth 強制終了 $ echo $? 137
ぱっと見、次に怪しいのはこのオプションである。
-Wl,-Ttext,0
ldのヘルプを見てみると
-Ttext ADDRESS Set address of .text section
とあるし、infoファイルにも
`-Ttext ORG' Same as -section-start, with `.bss', `.data' or `.text' as the SECTIONNAME.
とある。0が指定されているのだから、".text"セクションの開始アドレスを"0"にする意味になる。
"--save-temps", "-v"オプションをつけて途中生成物の保存&gccドライバの詳細メッセージを表示させる。
$ gcc -v --save-temps -m32 -nostdlib -static -Wl,-Ttext,0 -o jonesforth jonesforth.S Using built-in specs. Target: i386-redhat-linux ... as -V -Qy -o jonesforth.o jonesforth.s GNU assembler version 2.17.50.0.6-6.el5 (i386-redhat-linux) using BFD version 2.17.50.0.6-6.el5 20061020 /usr/libexec/gcc/i386-redhat-linux/4.1.2/collect2 \ -m elf_i386 --hash-style=gnu -static -o jonesforth -L/usr/lib/gcc/i386-redhat-linux/4.1.2 -L/usr/lib/gcc/i386-redhat-linux/4.1.2 -L/usr/lib/gcc/i386-redhat-linux/4.1.2/../../.. -Ttext 0 jonesforth.o $ ls jonesforth* jonesforth.S jonesforth.f jonesforth.o jonesforth.s $ readelf -S jonesforth There are 8 section headers, starting at offset 0x1d04: Section Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .text PROGBITS 00000000 001000 0005ba 00 AX 0 0 4 [ 2] .rodata PROGBITS 000005bc 0015bc 0006d0 00 A 0 0 4 [ 3] .data PROGBITS 00001c8c 001c8c 000044 00 WA 0 0 4 [ 4] .bss NOBITS 00002000 001cd0 003000 00 WA 0 0 4096 [ 5] .shstrtab STRTAB 00000000 001cd0 000034 00 0 0 1 [ 6] .symtab SYMTAB 00000000 001e44 001650 10 7 40 4 [ 7] .strtab STRTAB 00000000 003494 000dcc 00 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings) I (info), L (link order), G (group), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific) $ readelf -h jonesforth ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: Intel 80386 Version: 0x1 Entry point address: 0xe Start of program headers: 52 (bytes into file) Start of section headers: 7428 (bytes into file) Flags: 0x0 Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 2 Size of section headers: 40 (bytes) Number of section headers: 8 Section header string table index: 5
".text"セクションがアドレス0から開始され、エントリポイントのアドレスが0xeになっている。".text"セクションを逆アセンブルしてみる。
$ objdump -d -S -j .text jonesforth | less jonesforth: file format elf32-i386 Disassembly of section .text: 00000000 <DOCOL>: 0: 8d 6d fc lea 0xfffffffc(%ebp),%ebp 3: 89 75 00 mov %esi,0x0(%ebp) 6: 83 c0 04 add $0x4,%eax 9: 89 c6 mov %eax,%esi b: ad lods %ds:(%esi),%eax c: ff 20 jmp *(%eax) 0000000e <_start>: e: fc cld f: 89 25 98 1c 00 00 mov %esp,0x1c98 15: bd 00 40 00 00 mov $0x4000,%ebp 1a: e8 7e 05 00 00 call 59d <set_up_data_segment> 1f: be bc 05 00 00 mov $0x5bc,%esi 24: ad lods %ds:(%esi),%eax 25: ff 20 jmp *(%eax) ...
gdbでデバッグしてみる。
$ gdb jonesforth GNU gdb Red Hat Linux (6.5-37.el5_2.2rh) ... (gdb) b _start Breakpoint 1 at 0xe (gdb) run Starting program: /home/msakamoto/in_vitro/lang.asm/jonesforth/jonesforth Couldn't get registers: そのようなプロセスはありません.
・・・なーんか、そろそろ深みに嵌りそうな気がしてきた・・・。
ちょっと "-Wl,-Ttext,0" による影響の詳細追跡は置いておき、とにかく怪しいオプションなのだから削ってみる。
$ gcc -v -m32 -nostdlib -static -o jonesforth jonesforth.S Using built-in specs. Target: i386-redhat-linux ... as -V -Qy -o /tmp/ccgxvdKK.o /tmp/ccEEORGU.s GNU assembler version 2.17.50.0.6-6.el5 (i386-redhat-linux) using BFD version 2.17.50.0.6-6.el5 20061020 /usr/libexec/gcc/i386-redhat-linux/4.1.2/collect2 -m elf_i386 --hash-style=gnu -static -o jonesforth -L/usr/lib/gcc/i386-redhat-linux/4.1.2 -L/usr/lib/gcc/i386-redhat-linux/4.1.2 -L/usr/lib/gcc/i386-redhat-linux/4.1.2/../../.. /tmp/ccgxvdKK.o $ readelf -S jonesforth There are 8 section headers, starting at offset 0xd78: Section Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .text PROGBITS 08048074 000074 0005ba 00 AX 0 0 4 [ 2] .rodata PROGBITS 08048630 000630 0006d0 00 A 0 0 4 [ 3] .data PROGBITS 08049d00 000d00 000044 00 WA 0 0 4 [ 4] .bss NOBITS 0804a000 000d44 003000 00 WA 0 0 4096 [ 5] .shstrtab STRTAB 00000000 000d44 000034 00 0 0 1 [ 6] .symtab SYMTAB 00000000 000eb8 001650 10 7 40 4 [ 7] .strtab STRTAB 00000000 002508 000dcc 00 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings) I (info), L (link order), G (group), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific) $ readelf -h jonesforth ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: Intel 80386 Version: 0x1 Entry point address: 0x8048082 Start of program headers: 52 (bytes into file) Start of section headers: 3448 (bytes into file) Flags: 0x0 Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 2 Size of section headers: 40 (bytes) Number of section headers: 8 Section header string table index: 5 $ objdump -d -S -j .text jonesforth | less jonesforth: file format elf32-i386 Disassembly of section .text: 08048074 <DOCOL>: 8048074: 8d 6d fc lea 0xfffffffc(%ebp),%ebp 8048077: 89 75 00 mov %esi,0x0(%ebp) 804807a: 83 c0 04 add $0x4,%eax 804807d: 89 c6 mov %eax,%esi 804807f: ad lods %ds:(%esi),%eax 8048080: ff 20 jmp *(%eax) 08048082 <_start>: 8048082: fc cld 8048083: 89 25 0c 9d 04 08 mov %esp,0x8049d0c 8048089: bd 00 c0 04 08 mov $0x804c000,%ebp 804808e: e8 7e 05 00 00 call 8048611 <set_up_data_segment> 8048093: be 30 86 04 08 mov $0x8048630,%esi 8048098: ad lods %ds:(%esi),%eax 8048099: ff 20 jmp *(%eax) ...
いつも見慣れた"0x80480.."近辺のアドレスに"_start"シンボルが移動した。雰囲気的にイケそうな感じがするので、試してみる。
$ ./jonesforth (Ctrl-D) $ echo $? 0
イケそうなので、サンプルスクリプトに再挑戦してみる。
$ cat jonesforth.f - | ./jonesforth JONESFORTH VERSION 47 14499 CELLS REMAINING OK $ echo $? 0
・・・ようやく動いた・・・。
ところが話はここで終わっていない。以下の記事によると、そもそもgasの時点でエラーが沢山表示されてしまうとのこと。
$ as -o jonesforth.o jonesforth.S
実際に試してみたところ、同様に大量のエラーメッセージが表示された。
しかし・・・".S"ファイルを直接asに食べさせているのが気になる。もう一度、上でコンパイル&実行に成功したオプションでのgccの詳細を見てみる。
$ gcc -v -m32 -nostdlib -static -o jonesforth jonesforth.S Using built-in specs. Target: i386-redhat-linux ... /usr/libexec/gcc/i386-redhat-linux/4.1.2/cc1 -E -lang-asm -quiet -v jonesforth.S -m32 -mtune=generic -o /tmp/ccFU9F94.s 存在しないディレクトリ "/usr/lib/gcc/i386-redhat-linux/4.1.2/../../../../i386-redhat-linux/include" を無視します #include "..." の探索はここから始まります: #include <...> の探索はここから始まります: /usr/local/include /usr/lib/gcc/i386-redhat-linux/4.1.2/include /usr/include 探索リストの終わり as -V -Qy -o /tmp/ccC32TCa.o /tmp/ccFU9F94.s GNU assembler version 2.17.50.0.6-6.el5 (i386-redhat-linux) using BFD version 2.17.50.0.6-6.el5 20061020 /usr/libexec/gcc/i386-redhat-linux/4.1.2/collect2 (省略)
一旦"cc1", プリプロセッサを通してその出力をasに食わせている。
試しに手動で同じコマンドを実行してみる。まずプリプロセッサの結果を"jonesforth.asm"に保存してみる。
$ cpp -E -lang-asm -quiet -v jonesforth.S -m32 -mtune=generic -o jonesforth.asm Using built-in specs. cpp: unrecognized option '-quiet' Target: i386-redhat-linux ... /usr/libexec/gcc/i386-redhat-linux/4.1.2/cc1 -E -lang-asm -quiet -v jonesforth.S -o jonesforth.asm -m32 -mtune=generic 存在しないディレクトリ "/usr/lib/gcc/i386-redhat-linux/4.1.2/../../../../i386-redhat-linux/include" を無視します #include "..." の探索はここから始まります: #include <...> の探索はここから始まります: /usr/local/include /usr/lib/gcc/i386-redhat-linux/4.1.2/include /usr/include 探索リストの終わり cpp: -lang-asm: リンクが完了しなかったのでリンカの入力ファイルは使われませんでした $ cat jonesforth.asm # 1 "jonesforth.S" # 1 "<built-in>" # 1 "<コマンドライン>" # 1 "jonesforth.S" (...) .set JONES_VERSION,47 # 306 "jonesforth.S" .macro NEXT lodsl jmp *(%eax) .endm (...)
うまく動いたようだ。続いてこれを、gasに入力してみる。
$ as -V -Qy -o jonesforth_asm.o jonesforth.asm GNU assembler version 2.17.50.0.6-6.el5 (i386-redhat-linux) using BFD version 2.17.50.0.6-6.el5 20061020 $ echo $? 0 $ file jonesforth_asm.o jonesforth_asm.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
ここまでも無事動いているようだ。最後に、リンクしてみる。
$ ld \ -m elf_i386 \ --hash-style=gnu \ -static \ -o jonesforth_byhand \ ./jonesforth_asm.o $ cat jonesforth.f - | ./jonesforth_byhand JONESFORTH VERSION 47 14499 CELLS REMAINING OK $ echo $? 0
問題ないようだ。
jonesforthのソースコードでは、
/* ... */
形式のコメントが大量に使われている。一方、msakamoto-sf自身がこれまで経験してきたアセンブラのコメント形式は
#...
形式だった。
恐らく
/* ... */
形式のコメントを除去する為にプリプロセッサを通す必要があり、そのため、直接asに入力してもエラーとなってしまう。それが、"asでコンパイル出来ない"問題の原因(の、少なくとも一つ)であることが予想される。