言語ゲーム

とあるエンジニアが嘘ばかり書く日記

Twitter: @propella

jolt2 が生成する機械語について

久しぶりに pepsi & coke を触っているので復習がてらつらつら書きます。特にリビジョン 556 の jolt2 について書きます。まず最初に、基礎知識を簡単にまとめます。

特に、jolt は実行時にプログラムを書き換え可能という動的な性質と、機械語に変換してから実行するという静的な性質を一つで持っている珍しい言語です。このなかで動的な性質を実現するために二つの基本機能があります。

  • メソッドディスパッチ : jolt から pepsi のオブジェクトを使う事が出来ます。pepsiSmalltalkJavascript のようにレシーバでディスパッチするタイプのメソッド辞書 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 を指している事が分かるわけです。味気ない日記ですが、今日はこんな所で。