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