久しぶりに pepsi & coke を触っているので復習がてらつらつら書きます。特にリビジョン 556 の jolt2 について書きます。まず最初に、基礎知識を簡単にまとめます。
- idst http://piumarta.com/svn2/idst/trunk/ には pepsi と jolt いう二つの言語が付属します。
- pepsi は Smalltalk に似たコンパイル言語で、C 言語へのトランスレータとして実装されています。実行には gcc が必要です。
- jolt とは Lisp に似た言語で、実行時に動的にソースから機械語をコンパイルして実行します。jolt の関数は C で書かれた関数と互換性があり、C ライブラリのコールバックにそのまま使えます。jolt は pepsi で実装されていて、 pepsi のライブラリオブジェクトを利用出来ます。
特に、jolt は実行時にプログラムを書き換え可能という動的な性質と、機械語に変換してから実行するという静的な性質を一つで持っている珍しい言語です。このなかで動的な性質を実現するために二つの基本機能があります。
- メソッドディスパッチ : jolt から pepsi のオブジェクトを使う事が出来ます。pepsi は Smalltalk や Javascript のようにレシーバでディスパッチするタイプのメソッド辞書 vtable を持っていて、vtable の内容を書き換える事でオブジェクトの性質を変える事が出来ます。また、vtable 自体の動作も変更可能です。
- 間接参照 : jolt の文法は scheme に似ていますが、意味はむしろ C に似ています。例えば (func arg1 arg2) という式はそのまま func(arg1, arg2) という C の関数呼び出しに対応し、クロージャはありません。ただし、関数 func の内容は実行時に変更する事が可能なので、直接 func のアドレスが機械語に埋込まれるのでは無く、アドレステーブル経由の参照になります。ここの所を詳しく書きます。
jolt2 による Hello world プログラムと、C ライブラリ puts のアドレスを表示するプログラムです。
;; indirect.k (define puts (dlsym "puts")) (printf "Address of puts == 0x%x\n" puts) (puts "Hello, world!")
$ ./jolt.sh boot.k indirect.k ; loading: boot.k Address of puts == 0x96c60f33 Hello, world!
ここで、オプションを変更し、(puts "Hello, world!") がどのようにコンパイルされるのかを見ます。
(define puts (dlsym "puts")) (printf "Address of puts == 0x%x\n" puts) [(import "CompilerOptions") verboseExec: '1] (puts "Hello, world!")
$ ./jolt.sh boot.k indirect.k ; loading: boot.k Address of puts == 0x96c60f33 => 3 free 0x301f20 code size 33 bytes code start 0x301fa0 call 0x301fa0 Hello, world! => 10 free 0x301fa0
と、見にくいですが、0x301fa0 から 33 バイトが (puts "Hello, world!") に対応する機械語です。gdb を使ってここを逆アセンブルしてみます。
$ gdb (gdb) file ~/src/idst.556/function/jolt2/main (gdb) b puts (gdb) run boot.k indirect.k Starting program: /Users/takashi/src/idst.556/function/jolt2/main boot.k indirect.k Reading symbols for shared libraries +++.. done Breakpoint 1 at 0x96c60f39 ; loading: boot.k Address of puts == 0x96c60f33 => 3 free 0x301f20 code size 33 bytes code start 0x301fa0 call 0x301fa0 Breakpoint 1, 0x96c60f39 in puts () (gdb) disas 0x301fa0 (0x301fa0 + 33) Dump of assembler code from 0x301fa0 to 0x301fc1: 0x00301fa0: push %ebp 0x00301fa1: mov %esp,%ebp 0x00301fa3: push %ebx 0x00301fa4: sub $0x4,%esp 0x00301fa7: mov 0x443640,%ebx // シンボル puts 0x00301fad: mov $0x301090,%eax // "Hello, world!" 0x00301fb2: mov %eax,(%esp) 0x00301fb5: call *%ebx // puts("Hello, world!") 0x00301fb7: mov %eax,%eax 0x00301fb9: mov %eax,%eax 0x00301fbb: add $0x4,%esp 0x00301fbe: pop %ebx 0x00301fbf: pop %ebp 0x00301fc0: ret End of assembler dump. (gdb) p (char *) 0x301090 // 引数の内容を確認する $1 = 0x301090 "Hello, world!" (gdb) p puts // 関数 puts のアドレスを確認する $2 = {<text variable, no debug info>} 0x96c60f33 <puts> (gdb) p (void *) *0x443640 // シンボル puts の内容を確認する。 $3 = (void *) 0x96c60f33
また、マクロを使ってコンパイラにアクセス出来るので、シンボル puts の位置を確認する事が出来ます。
(define puts (dlsym "puts")) (syntax address ;;;; (address name) (lambda (form compiler) (let ((var [compiler lookupVariable: [form second]]) (result [var translateLvalue: compiler])) result))) (printf "Address of puts == 0x%x\n" puts) (printf "Address of table position of puts == 0x%x\n" (address puts)) (printf "Dereference == 0x%x\n" (long@ (address puts)))
$ ./jolt.sh boot.k indirect.k ; loading: boot.k Address of puts == 0x96c60f33 Address of table position of puts == 0x443640 Dereference == 0x96c60f33
というわけで、シンボル puts のメモリ上の位置が 0x443640 で、その内容 0x96c60f33 が関数 puts を指している事が分かるわけです。味気ない日記ですが、今日はこんな所で。