SBCLでマシンコードを直接実行

どこかで、sbclでメモリ領域に直接マシンコードを書き込んで実行できる、というような話を読んだような気がするので試してみた*1


まずは、(マシンコードを取得するために)実行したい関数をCで定義する。
簡単なものがいいので、二つの変数を足すだけの関数にする。

/* ファイル名: plus.c */
int plus(int a, int b) {
  return a+b;
}

次に、この関数からマシンコードを生成。

> gcc -c -Wa,-al,-L plus.c
   1              		.file	"plus.c"
   2              		.text
   3              	.globl plus
   4              		.type	plus, @function
   5              	plus:
   6 0000 55       		pushl	%ebp
   7 0001 89E5     		movl	%esp, %ebp
   8 0003 8B450C   		movl	12(%ebp), %eax
   9 0006 034508   		addl	8(%ebp), %eax
  10 0009 5D       		popl	%ebp
  11 000a C3       		ret

出力を見ると、関数plusは、マシンコードでは5589E58B450C0345085DC3であることが分かる。


これをlisp(sbcl)で実行したい。
諸々の途中経過は省いて最終的な関数定義(と使用例)だけを提示する。

;; ※ sb-alien:castは、コンパイル時に変換する型を指定する必要があるようなので、マクロで定義する
(defmacro string-to-alien-function (str function-type-spec)
   ;; 1] 必要なサイズのメモリ(unsigned-charの配列)を確保   ※ 後で明示的に解放する必要有り 
  `(let ((codes (sb-alien:make-alien unsigned-char (/ (length ,str) 2))))
     ;; 2] マシンコードの16進数表現を読み取り、1で確保したメモリにセットする
     (loop for i from 0 to (1- (length ,str)) by 2 do
           (setf (sb-alien:deref codes (/ i 2)) (parse-integer ,str :radix 16 :start i :end (+ i 2))))
     
     ;; 3] (* unsigned-char) から任意の型に変換
     (sb-alien:cast codes ,function-type-spec)))

実行

> (defvar plus-fn (string-to-alien-function "5589E58B450C0345085DC3" (function int int int)))
--> PLUS-FN

> (sb-alien:alien-funcall plus-fn 12 15)
--> 27  ; 正しい結果が返ってくる

案外簡単だった*2


アセンブラlispで実装すれば、アセンブリ言語の勉強がlispで簡単に出きるようになったりするかもしれない。

*1:使ったsbclのバイナリは、sbcl-1.0.28-x86-linux-binary.tar.gz

*2:ただ残念なことに、lispの関数のdisassemble出力で得られるマシンコードを上のマクロに渡し、alien-funcallで呼び出しても、正常な結果は得られない。もしそれが可能なら、disassembleの結果を見て、自分でマシンコードをいじって最適化するという-やらない方が良さそうな-こともできるのに...。