日記/2010/01/13/jonesforthのgasによるコンパイルと"--build-id"オプションなど - Glamenv-Septzen.netdocs.komagata.orgの「ガチ鬱プログラマー日記」のRSSフィードを読んでいると、暫く前からjonesforthというアセンブラで書かれたForth処理系をLinuxのgasでコンパイルしようとして上手く行かなかったり、謎のオプションでエラーになったりトラブルに突き当たっている。
(...)
同じくアセンブラに手を出した人間として、捨てておくわけにはいかない。
というわけで自分もCentOS5.2(古っ)上でjonesforthのコンパイルに挑戦してみた。
ELFのbuild ID noteって何? - p0tというエントリにmsakamoto-sfさんからコメント頂きました。
タイトルの疑問にズバリ回答を示してくださって、衝撃を受けました。
ナレーション「この時、komagataに電流走る・・・っっっ!!」
この、「これは何なんだろう?」という疑問と「これはこういう仕組みになっている」というところがつながった時の快感は何物にも代え難いモノがあります。中毒、というか病気です。仕方ないね・・・。
ELFのbuild ID noteとは何か
最近のldから入った機能で、ビルド毎のバイナリを識別するためのIDのようです。msakamoto-sfさんが書かれているように、デバッグの時に重宝しそうな機能です。僕の環境はDebian lennyのGNU ld 2.18.0で、--build-idオプションに対応しているため、入れても入れなくても動きました。
% ld -v
GNU ld (GNU Binutils for Debian) 2.18.0.20080103
helloworld.sの-e mainオプションについて
helloworld.sをasでコンパイルするときに-e mainというエントリポイントを指定するオプションを使っていましたが、エントリポイントを_start(cで書かれたソースはこのエントリポイントにコンパイルされる)にしたら必要無かったです。多分デフォルトのエントリポイント名なんだと思います。
.code32
.text
.globl _start
_start:
(...)
helloworldの結果が1になってる件
ちょっと気になってたんですが、以前のhelloworldは実行した返り値が1なんですよね。エラーです。exit 1になっていたようなのでexit 0になるように変えました。
変更前:
movl $1, %eax
int $0x80
変更後:
movl $1, %eax
xor %ebx, %ebx # exit 0
int $0x80
ldの-Ttext 0オプションについて
これは僕も未だに謎です。
jonesforthのコメントにはjonesforthが学習に向いている点として素のPCから直接FORTHを立ち上げられることを上げています。(僕のショボイ英語力で間違ってなければ・・・)
jonesforth.SYou could boot such a FORTH on a bare PC and it would
come up with a prompt where you could start doing useful work.
その時にtextセクションが0から始まっている必要があったりするのかもしれません。
asでjonesforthのコンパイルが失敗する理由
僕もコメントが怪しいと踏んでjonesforthに使われている複数のコメントスタイルをhelloworld.sに入れてasでコンパイルしてみました。
.code32
.text
.globl _start
_start:
movl $4, %eax
movl $1, %ebx
movl $msg, %ecx
movl $len, %edx
int $0x80
# foo
/* bar */
// baz
movl $1, %eax # foo
xor %ebx, %ebx /* bar */
int $0x80 // buz
.data
msg: .string "Hello, world!\n"
msglen: len = msglen - msg
asでコンパイル。
% as -o helloworld.o helloworld.s
helloworld.s: Assembler messages:
helloworld.s:15: Error: junk `//buz' after register
どうやら命令の後に//スタイルのコメントを書いた場合だけasでは“余計なオペランドがある”みたいな感じでエラーになってしまうようです。そこだけ削除したらコンパイルできました。
gccを使わずにjonesforthをコンパイルする
上記を踏まえてcプリプロセッサ、as、ldを直接使ってコンパイルできるようにやっとなりました。
cppで余計なコメントを削除。恐らくcで使う#includeとか#defineも使えるんだと思います。でも.macro ... .endmも十分便利ですよね。
% cpp -v -o jonesforth.s jonesforth.S
Using built-in specs.
Target: i486-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Debian 4.3.2-1.1' --with-bugurl=file:///usr/share/doc/gcc-4.3/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --enable-shared --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --enable-nls --with-gxx-include-dir=/usr/include/c++/4.3 --program-suffix=-4.3 --enable-clocale=gnu --enable-libstdcxx-debug --enable-objc-gc --enable-mpfr --enable-targets=all --enable-cld --enable-checking=release --build=i486-linux-gnu --host=i486-linux-gnu --target=i486-linux-gnu
Thread model: posix
gcc version 4.3.2 (Debian 4.3.2-1.1)
COLLECT_GCC_OPTIONS='-v' '-E' '-o' 'jonesforth.s' '-mtune=generic'
/usr/lib/gcc/i486-linux-gnu/4.3.2/cc1 -E -lang-asm -quiet -v jonesforth.S -o jonesforth.s -mtune=generic -fno-directives-only
ignoring nonexistent directory "/usr/local/include/i486-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/i486-linux-gnu/4.3.2/../../../../i486-linux-gnu/include"
ignoring nonexistent directory "/usr/include/i486-linux-gnu"
#include "..." search starts here:
#include <...> search starts here:
/usr/local/include
/usr/lib/gcc/i486-linux-gnu/4.3.2/include
/usr/lib/gcc/i486-linux-gnu/4.3.2/include-fixed
/usr/include
End of search list.
COMPILER_PATH=/usr/lib/gcc/i486-linux-gnu/4.3.2/:/usr/lib/gcc/i486-linux-gnu/4.3.2/:/usr/lib/gcc/i486-linux-gnu/:/usr/lib/gcc/i486-linux-gnu/4.3.2/:/usr/lib/gcc/i486-linux-gnu/:/usr/lib/gcc/i486-linux-gnu/4.3.2/:/usr/lib/gcc/i486-linux-gnu/
LIBRARY_PATH=/usr/lib/gcc/i486-linux-gnu/4.3.2/:/usr/lib/gcc/i486-linux-gnu/4.3.2/:/usr/lib/gcc/i486-linux-gnu/4.3.2/../../../../lib/:/lib/../lib/:/usr/lib/../lib/:/usr/lib/gcc/i486-linux-gnu/4.3.2/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-v' '-E' '-o' 'jonesforth.s' '-mtune=generic'
プリプロセッサを通したので普通にasでコンパイル出来る。
% as -o jonesforth.o jonesforth.s
普通にlinkする。-m elf_i386は無くても大丈夫でした。-nostdlibもそもそも使ってないからサイズも変わりませんでした。-staticも動的ライブラリを使ってないので関係無いみたいです。
% ld -o jonesforth jonesforth.o
strip -s jonesforthで3.6KBぐらいになりました。
% wc -c jonesforth
12983 jonesforth
% strip -s jonesforth
% wc -c jonesforth
3672 jonesforth
ちゃんと動くのでshell上から使う分には問題無さそうです。
% cat jonesforth.f - | ./jonesforth
JONESFORTH VERSION 47
14499 CELLS REMAINING
OK 1 2 + .
3
今の困り事
msakamoto-sfさんが示してくださったように、build ID noteもldのinfoにバッチリ載っていました。しかし、info全体の検索方法が分からない・・・。これは我ながら情けない。
Debianのinfoの罠
gccやcppやgdbのドキュメントはnon-freeなのでmainだけでやってたときハマりました。商用UNIX関係の記述がその辺から来ているのが原因なんでしょうか。インストールしてるとvrmsにも怒られてしまいます。
PSの古いゲームだけど、このゲーム内に出てくる仮想のOS、データスワローのUIと企業ロゴやアイコンがスゴい好きでした。今見ると少々野暮ったいけど、当時は衝撃を受けたもんでした。
jonesforthを読む勉強を晒していくニコ生放送始めてみました。
ずーっと黒い画面なので興味無い方にはとてつもなく退屈だと思いますが、やってみたら強制的に集中して勉強しなくちゃいけなくなるのでとても楽しかったです。
毎日1〜2枠でも流せるようにやっていきたいと思います。
アクトインディ株式会社では自社Webサービスを作っています。
営業の谷くん「komagataさん、xxxをxxxする機能をサイトに追加することってできますかね?」
俺「(手のひらを上にし、人差し指と親指で輪を作り)コレ、さえ積めば思いのままだよ・・・。」
最低の大人や・・・。
JSAN - Joose.ManualJoose is based in large part on the Moose system, which in turn borrows a lot of from Perl 6 object system, as well as drawing on the best ideas from CLOS, Smalltalk, and many other languages.
ジュース・・・だと・・・?
JSAN自体もそうだけどあまりにPerlフレンドリー過ぎると他の人には取っ付き辛くなってしまうかも。
JAUSEでのライブラリアップロードやパッケージングも最初は
「おー、CPANと同じだw」
と楽しんだけど、糞面倒臭くてそれ以降やってないもんなぁ・・・。
jonesforthを見てますが、分からないことが沢山あるのでまだ具体的に進んで無いです。
64bit版はquekさんが既に作られてるということで、JavaのJVMかFlashのAVM2への移植を取り敢えずの目標にしてやっていこうと思います。quekさんのchocoforthはnasmで書かれていて、実装方法がjonesforthはIndirect Threaded Codeなのに対してDirect Threaded Codeというので実装されてるところが違うみたいです。さっぱり分かりませんが、gasとnasmとがあると比べて見れるのでとても参考になります。
まず真っ先に疑問に思ったのが、helloworldはコンパイルできるのに、jonesforthは何故asでコンパイルするとエラーが出まくるのかということ。
$ as -o jonesforth.o jonesforth.S
大量に出るので先頭だけ見てみる。
$ as -o jonesforth.o jonesforth.S 2> error.log
$ head -n 3 error.log
jonesforth.S: Assembler messages:
jonesforth.S:502: Error: too many positional arguments
jonesforth.S:503: Error: junk `//%eax points to codeword' after register
502行目付近はこんな感じ。
498 /* DOCOL - the interpreter! */
499 .text
500 .align 4
501 DOCOL:
502 PUSHRSP %esi // push %esi on to the return stack
503 addl $4,%eax // %eax points to codeword, so make
504 movl %eax,%esi // %esi point to first data word
505 NEXT
.macroや//のコメントが怪しい気がしたけど、helloworldに入れても動くしgasのちゃんとした機能だそうです。何がいけないのかな?
一旦それは諦めてgccでのコンパイル時のオプションの意味を調べてみた。
$ gcc -m32 -nostdlib -static -Wl,-Ttext,0 -Wl,--build-id=none -o jonesforth jonesforth.S
gccは-v --help
とやるとサブプロセスのオプションも全部だしてくれるそうなので一つずつ調べる。
$ gcc -v --help | grep m32
(...)
-m32 Generate 32bit i386 code
(...)
$ gcc -v --help | grep nostdlib
(...)
-nostdlib Only use library directories specified on
the command line
(...)
$ gcc -v --help | grep static
(...)
-Bstatic, -dn, -non_shared, -static
Do not link against shared libraries
(...)
$ gcc -v --help | grep Wl
(...)
-Wl, Pass comma-separated on to the linker
(...)
$ gcc -v --help | grep build-id
(...)
elf_i386:
--build-id[=STYLE] Generate build ID note
(...)
他は分かるけど最後のbuild ID noteって何だろう?
ldのオプションでelf_i386と書いてあるのでELFのフォーマットの中にそういう部分があるんだろうか?BINARY HACKSのELF入門やreadelf -aの出力を見てみたけどbuildなんたらというのは見つからなかった。
jonesforthインタプリタは-Wl,--build-id=none
というオプション無しでも普通に動いてしまう。
$ gcc -m32 -nostdlib -static -Wl,-Ttext,0 -o jonesforth jonesforth.S
$ cat jonesforth.f - | ./jonesforth
JONESFORTH VERSION 47
14499 CELLS REMAINING
OK
1 2 + .
3
謎だ・・・。
参照:
儲からない勉強は業務時間外の2時間までと決めてたんですが、Macのバッテリーが1時間50分ぐらいしか持たないのが切ないkomagataです。
jonesforthを読んでるんですが、まずはGASのhello worldとのコンパイル方法の違いが分からないとしょうがないので勉強。
$ gcc -m32 -nostdlib -static -Wl,-Ttext,0 -Wl,--build-id=none -o jonesforth jonesforth.S
UNIXの部屋 コマンド検索:info (*BSD/Linux)どうでもいいが、当ページ管理人が一番嫌いな UNIX コマンドが info である。有益な情報が info 内にあるのに、見づらいし、操作しづらいから。info2html の吐く HTML も嫌い。世の人々は info に満足しているのだろうか?
GCCのマニュアルにはinfo見ろと書いてある。でも上記にもとても共感してる。
jonesforthをasでコンパイルしようとすると沢山エラーが出る。違いを知るために、hello worldにjonesforthで使われてるものを少しずつ足して行くことに。まずは毎回as, ldが面倒なのでMakefileを書く。
PROGRAM = helloworld
SRCS = helloworld.s
OBJS = $(subst .s,.o,$(SRCS))
ASFLAGS = -a=$(PROGRAM).lst
LDFLAGS = -Ttext 0 -e main
$(PROGRAM): $(OBJS)
$(LD) $(LDFLAGS) -o $@ $<
.PHONY: clean
clean:
$(RM) $(OBJS) $(PROGRAM)
こんな感じで良いのかな?substとかmakeの関数なんて知らなかった。viからは:makeでmakeできるんですね。
それとちょっと気になったのでvimのgasのsyntaxを入れてみた。
apt-cacheで適当に探してたらintel2gasという面白そうなものを見つけた。
Package: intel2gas
Priority: optional
Section: devel
Installed-Size: 340
Maintainer: Alexander Zangerl
Architecture: i386
Version: 1.3.3-12
Depends: libc6 (>= 2.3.6-6), libgcc1 (>= 1:4.1.1-12), libstdc++6 (>= 4.1.1-12)
Filename: pool/main/i/intel2gas/intel2gas_1.3.3-12_i386.deb
Size: 30456
MD5sum: a7b336eb691b3391211cc20b8a29d2e5
SHA1: 6afaec8b0505b3fc1d2b862fdf38de3d63ab1abd
SHA256: 0d8f32134c862b032f7bcf0c4a0bfe0d32a480edfcd7ae30fe25d5839bc91bc5
Description-ja: NASM アセンブラ言語から GAS への変換プログラム
Intel2GAS は、i386 プラットホーム上で、NASM 用に書かれたアセンブラソー
スファイルを GNU Assembler (GAS) を使ってアセンブル可能なファイルに変換
するプログラムです。基本的な MMX 命令への対応も提供します。
.
ホームページ: http://www.niksula.cs.hut.fi/~mtiihone/intel2gas/
Tag: devel::machinecode, interface::commandline, role::program, scope::utility, use::converting
ホームページを見るとgas2intelも出来るみたい。後で試してみたい。
参照: