机と椅子

机と椅子来たー!

酷いMacが玉に瑕・・・。

日記/2010/01/13/jonesforthのgasによるコンパイルと"--build-id"オプションなど - Glamenv-Septzen.net

docs.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.S

You 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を読む勉強を晒していくニコ生放送始めてみました。

【ニコニコミュニティ】Linuxプログラミング

ずーっと黒い画面なので興味無い方にはとてつもなく退屈だと思いますが、やってみたら強制的に集中して勉強しなくちゃいけなくなるのでとても楽しかったです。

毎日1〜2枠でも流せるようにやっていきたいと思います。

アクトインディ株式会社では自社Webサービスを作っています。

営業の谷くん「komagataさん、xxxをxxxする機能をサイトに追加することってできますかね?」

俺「(手のひらを上にし、人差し指と親指で輪を作り)コレ、さえ積めば思いのままだよ・・・。」


最低の大人や・・・。

JSAN - Joose.Manual

Joose 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を入れてみた。

gas.vim

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も出来るみたい。後で試してみたい。

関連:GAS - Hello, world - p0t

参照:

Rails検証報告書: プログラマの思索

Railsで特徴的なのは、CookieでHTTP セッションを管理できることだろう。

ここの仕組みが非常に分かりやすい。

Railsの後から付いた機能で一番素敵だと思うのがこの機能です。

「Cookieなんて仕様上は4KBしか保存出来ないんだから寧ろ弱体化してね?」

とか認識されることが多い気がしてならない。

コレ、導入時にも度肝を抜かれて、以降常に、

「ハンパねー、マジCookieセッションハンパねー!」

と脳内のアフロの人が言ってるんですが、大した利点に感じる人は少ないのか、他の言語やWAFで全面採用している例を見たことが無い。

Cookieセッション

そもそもセッションという言葉自体が複数の処理をまとめた単位という広義の意味とWebアプリケーションで複数リクエストにまたがってサーバー側に保存されるデータという狭義の意味が混在して使われているという事情があってWeb上でのテキストコミュニケーションを阻害している。

RailsのCookieセッションと言った場合は広義のセッションだが、狭義のセッションは存在しないと断言すると分り易い気がする。

RailsはHTTP(RESTアーキテクチャスタイル)の原則に従う形で何も共有しないという設計上のポリシーを持っている。

そもそもCookieと言う仕様はステートレスなHTTPプロトコルに広義のセッションを構築する上でNetscapeが考え出した上手い抜け穴だ。

CookieのデータはHTTPヘッダーに記載されて全データが送受信される訳だからHTTPリクエストの内容だけで全ての情報を表現するという原則からはみ出していない。

狭義のセッションに関しては言語仕様やアプリケーションサーバーの実装に委ねられていて、例えばPHP(言語仕様に狭義のセッションが含まれている)とJavaのTomcatとでは全然違う。

殆の実装でそうなっているんだけど、Cookie内の一意のIDをキーにしてサーバー上に(メモリであれ、ファイルであれ)狭義のセッションデータを持っている。

単体のHTTPリクエストやレスポンスで全ての情報を表現できなくてはHTTPの原則であるステートレスとは言えない。(Cookie内のIDがサーバー上のセッションデータを対応しているとしてもそれは単体のHTTPリクエストやレスポンスだけで判断出来ない、/tmp/sess_IDファイルの中にシリアライズされてるとかいった暗黙のルールが含まれている。)

RailsのCookieセッションは広義のセッションの維持はCookieによってのみ維持され、クライアントPCに保存され、それを元にしてステートレスなHTTPリクエストとして送出される。

認証

Rails2.x以降、Cookieセッションに移行すると同時に、認証は「restful_authenticationプラグインを使ってね!」という風にあっさりと変わった。

これは使う側やユーザーにとっては大した違いに見えないが、内部的には認証方式がBASIC認証に変わっている!

これまたファイルやDBに保存するステートフルな認証からステートレスな認証に思い切り変わっている。こんなもん土台をそっくりすり替えちゃう程の大変更だと思うんだけど、上位レイヤーに住んでる人には殆気付かれずに行った。

AtomPP策定のIETFの話し合い内でもあった、「APIと認証関係ねーだろう。認証はHTTPプロトコルの仕事」という身も蓋もない結論をあっさり採用する。そこに痺れる、あこがれる!

HTTPの原則を守る利点

ここまで過激にHTTPの原則を守る最大の利点は、Railsの設計方針としても上げられている「何も共有しない」からもわかるように非常にスケールアウトし易いところだ。そもそも、HTTP(の元のRESTアーキテクチャースタイル)はWebのような大きな分散環境を想定して作られたものである。

シンプルかつスケーラビリティの高いシステムを作るにはREST原則に従うのは理にかなっている。

何故ドラスティックな変更が可能なのか

Ruby界隈では標準仕様を決めてから実装するのではなく、「勝手に実装して、良いものだったら標準にしようか」というノリというか文化がある用に思う。

言語や周辺ツール・ライブラリの整備によって使い物になる実装が出来るまでの時間が恐ろしく短いため、整った仕様よりも、動くコードや自動テストが重視される傾向があるため、時間のかかる抽象的な議論が少なめになるせいではないだろうか。

例としては、最近各言語でxSGIと呼ばれてる仕様も通常、仕様とリファレンス実装を明確に分けるところをRubyではRackが実装を持って標準仕様を決めてしまった。そして実際にRack::RequestとRack::Responseはデファクトとしてあっという間に広まった。

不満

何が俺にこんな長文を書かせるかというと、

「Cookieセッション、BASIC認証を他の言語・環境でも使いたいんだよ!面倒臭くて仕方がない!」

というストレスが爆発したからである・・・。

最近、フィードリーダーで購読してるブログを全部読めている。

iPhoneのせいかと思ったけど、どうやらTwitterのお陰でいろんな人々のブログポスト数が減ってるからのようです。だからどんどん購読ブログは増やしていってるのに全部読める。そのせいか、フィードリーダーの1エントリーあたりの内容が面白まって来ている気がする。面白い。

Twitterのお陰で技術系じゃない人のブログもいつの間にか購読するようになって読むジャンルが増えたのも面白まってる要因かもしれない。

大山倍達のように(イメージ的に)山篭りしてるかのように外界と断絶した内容を書いてる人。どこから読んでも意味不明なのに精力的にポストを続ける人。無自覚に静かに狂っている人。高度な内容を平易な文章で書いてくれる人。

お宝ブログが多くて嬉しいです。