わらばんし仄聞記

南の国で引きこもってるWeb屋さん

pythonでJIT(64bit版Linux環境) part3

池袋バイナリ勉強会(5)にて、前回に引き続きpythonJITをいじってきました。
今回の目標は外部からbrainf*ck(以下bf)のコードを読ませ、それを実行するネイティブコードの生成とその実行を行う事としました。これが出来たなら、そのネイティブコードにELF形式のヘッダやらを付けて出力すれば、つまりはbfコンパイラとなるわけですね。

目標

bfのコードを外部から読ませ、それを実行するネイティブコードの生成、実行をする。
最適化等は得に考えないこととする。

コード

基本的にやってるのは前回と同じですね。

ポイント

  • jz命令の相対位置指定

抜粋すると

# jz    dword end_(loop_name)
r.extend([0x0f, 0x84, 0x00, 0x00, 0x00, 0x00])

の部分です。
前回の時点ではjz命令のコードは

0x74, 0x0e,

でした。これはHello World程度のループで囲む範囲が大きくないコードなら問題無いですが、移動先とのオフセットが-128~127を超えてしまうと表現できません。
そこで、jzのオフセット指定をdwordで表現できるようにしています。これならちょっとしたコードで足りなくなることはそうそう無いはず。
合わせて命令に相当するネイティブコードは0x74から0x0f 0x84へ変わっていますね。

  • bf用のメモリ領域取得

コード中では

bf_mem = (c_ubyte * 30000)()

となっている箇所ですね。これでc_ubyte型30000件分の領域を確保しています。
イメージとしては、c_ubyte30000件相当の領域を持つ型を定義し、そのインスタンスを作っているような感じ。

  • ファイル出力

コメントアウトしてますが

#with open('jit.out', "wb") as fp:
#    fp.write(p)
#f()

これによって実行部分のバイナリを出力しています。
作成されるjit.outをobjdumpやndisasmすることで、作成されたネイティブコードがどのような命令として解釈されていたかを閲覧することができ、デバッグしやすくなります。

結果

以下の二つのbfコードを実行させてみます

  • hello.bf

"Hello World!"と出力します

>+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++..+++.[-]>++++++++[<++++>-]<.>+++++++++++[<+++++>-]<.>++++++++[<+++>-]<.+++.------.--------.[-]>++++++++[<++++>-]<+.[-]++++++++++.
  • echo.bf

入力された文字を返します

>>++++++++[->++++++++<]>>>>+++++++++[->++++++++++<]>[<<,[->+<<+<<+>>>]<<<[->>>+<<<]>>>>>[->+>>+<<<]>[<<[->+>>+<<<]>>>[-<<<+>>>]<<[[-]<->]>-]>>[-<<<+>>>]<<<<<<<[-<+<<+>>>]<[>>[-<+<<+>>>]<<<[->>>+<<<]>>[[-]>-<]<-]<<[->>>+<<<]>>>>><[[-]>++++++++++++++++++++++++++++++++>[[-]<-------------------------------->]<<]>>[-]<.>>]
  • 実行結果
$ python jit-bf.py hello.bf
Hello World!

$ python jit-bf.py echo.bf
K
K