バイナリストリームを対話環境でテストする

9月 26th, 2009

Java逆アセンブラを作っていたときの話。
バイナリファイルを読み込んで色々するプログラムを書くので、
「バイナリストリームからNバイト読み込んで…」
といった関数を沢山書いたんですが、これのテストがやりにくいです。
ファイルを実際に開く以外に、バイナリストリームを作る方法を知らないので、
ちょっとしたテストをする場合にも、実際にファイルを作らないといけない。
これでは非常に面倒です。
文字ストリームを扱う関数をテストする場合には、
with-input-from-stream, with-output-to-stream
を使えば、対話環境で楽に試せるので、
これのバイナリストリーム版を作ってみました。
Gray Streamを使っています。
SBCLでテスト済み。

(defclass binary-array-input-stream (fundamental-binary-input-stream)
  ((array :initarg :array :type (array t (*)))
   (index :initarg :start :type fixnum)
   (end :initarg :end :type fixnum)))
(defun make-binary-array-input-stream (array &optional (start 0) end)
  (make-instance 'binary-array-input-stream
                 :array array :start start :end (or end (length array))))
(defmethod stream-read-byte ((stream binary-array-input-stream))
  (with-slots (index end array) stream
    (if (>= index end)
	:eof
	(prog1 (aref array index)
          (incf index)))))
(defmacro with-input-from-binary-array((var array)
                                       &body body)
  `(let ((,var (make-binary-array-input-stream ,array)))
     (multiple-value-prog1
         (unwind-protect
              (progn ,@body)
           (close ,var)))))

配列の型は本当は (array (unsigned-byte 8) (*)) の方がいいんでしょうが、
そうすると、 #(1 2 3) の記法が使えなくなって面倒なので指定を緩くしました。
ここで、次のような関数を作ったとします。

(defun byte-list->number (byte-list &optional little-endian-p)
  (reduce #'(lambda (high low) (+ (ash high 8) low))
          (if little-endian-p
              (reverse byte-list)
              byte-list)))
(defun read-byte-list (n stream)
  (let (byte-list)
    (dotimes (_ n (nreverse byte-list))
      (push (read-byte stream) byte-list))))

これらの関数を対話環境でテストしたければ、次のようにします。

(with-input-from-binary-array (s #(1 44))
  (let ((byte-list (read-byte-list 2 s)))
    (format t "big endian: ~A~%" (byte-list->number byte-list))
    (format t "little endian: ~A~%" (byte-list->number byte-list t))))

big endianは300、little endianは11265となり、ちゃんと動きます。

Javaのアセンブラ/逆アセンブラをLispで作った

9月 23rd, 2009

Javaのアセンブラと逆アセンブラをCommon Lispで作りました。
一部対応してない命令がありますが、大体動作します。
アセンブリはもちろんS式で記述します。読み込むときはreadするだけ。
オペランドのない命令はアトム、オペランド付きの命令はリストとなっています。
とりあえずhello world。

;; ljTest.lja
(class "ljTest" "java/lang/Object" (public super)
 method
 ("<init>" "()V" (public)
   aload_0
   (invokespecial "java/lang/Object" "" "()V")
   return)
 method
 ("main" "([Ljava/lang/String;)V" (public static)
   (meta max-stack 2)
   (getstatic "java/lang/System" "out" "Ljava/io/PrintStream;")
   (ldc "hello world")
   (invokevirtual "java/io/PrintStream" "println" "(Ljava/lang/String;)V")
   return))

hello worldだけなのに長いです。さすがJava。
<init>というのはコンストラクタです。

% clisp
> (load "ljasm.lisp")
> (assemble-file "ljTest.java")
> (exit)
% java ljTest
hello world

こんな感じに動作します。
次は逆アセンブル。

// le.java
public class le{
  public static void main(String args[]) {
    try {
      for (int i=5; i>=0; i--) {
        System.out.println(100 / i);
      }
    } catch (ArithmeticException e) {
      System.out.println("Nice exception...");
    }
  }
}

ループと例外が使われています。

% javac le.java
% clisp
> (load "ljasm.lisp")
> (disassemble-file "le.class")
> (exit)

これで次のようなファイルが作られます。
(整形は私が手作業でやりました)

(CLASS "le" "java/lang/Object" (PUBLIC SUPER)
 INTERFACE NIL
 METHOD
 ("" "()V" (PUBLIC)
   (META MAX-STACK 1)
   (META MAX-LOCAL 1)
   ALOAD_0
   (INVOKESPECIAL "java/lang/Object" "" "()V")
   RETURN)
 METHOD
 ("main" "([Ljava/lang/String;)V" (PUBLIC STATIC)
   (META MAX-STACK 3)
   (META MAX-LOCAL 2)
   :L0
   ICONST_5 ISTORE_1
   :L2
   ILOAD_1 (IFLT :L22)
   (GETSTATIC "java/lang/System" "out" "Ljava/io/PrintStream;")
   (BIPUSH 100) ILOAD_1 IDIV
   (INVOKEVIRTUAL "java/io/PrintStream" "println" "(I)V")
   (IINC 1 255) (GOTO :L2)
   :L22
   (GOTO :L34)
   :L25
   ASTORE_1  ;store exception object                                                                                                                                               
   (GETSTATIC "java/lang/System" "out" "Ljava/io/PrintStream;")
   (LDC "Nice exception...")
   (INVOKEVIRTUAL "java/io/PrintStream" "println" "(Ljava/lang/String;)V")
   :L34
   RETURN
   (META EXCEPTION :L0 :L22 :L25 "java/lang/ArithmeticException"))

キーワードはラベルと見なされます。
逆アセンブリするときはラベル名は自動生成されます。
例外の指定は非常に地味です。 (META EXCEPTION …) の
最初の:L0と:L22は例外を捕まえる範囲、:L25は例外ハンドラです。
という訳で、それっぽく動いています。
めでたしめでたし。

マンガで分かるLisp [Section 2.3]

9月 22nd, 2009

CLISPのVMにはJUMPTAILなんて命令があったのか。

マンガで分かるLisp [その7]

9月 14th, 2009

先にその5を読み直した方がいいかも。

白クダーはミャーと鳴く。

マンガで分かるLisp [Section 2.2]

9月 7th, 2009

setqの正しい発音は「せっときゅー」とHyperSpecに書いてある。

それでも私は「せっとく」と発音したい。

マンガで分かるLisp [その6]

9月 1st, 2009

気がついたら9月になっていた。

気がつかなければ良かったのに…

マンガで分かるLisp [Section 2.1]

8月 27th, 2009

ワーオ!

実はこっそり目次ページを作ってました。

院試

8月 14th, 2009
            _ – –‐- 、
         r‐ ´ト、  ヽ   ‘ ,
         ヽ! /コュ      l
           |  ) l!`!/ ̄ ̄` ‐-ァ        ぜんぶ、した
           | く,. /       、>
           .!l iト、.!     /} ノトノ     過去問も、やりとげた
           i! l!./  ./  } ‐’ _ノ’,
            .l l //  !|ニ!  l、        もうじゅうぶんなぐらい…
             V从乂人ノ /ヽ  lヽ
             l      /   ‘,  l ‘ ,       この夏に一生ぶんの勉強がつまってた
             |     ヾ   l  :!: ヘ
             |, ‘     `-r、 ! :l:  ‘,    いろいろなことあったけど…
            /      ソ lニ>、l  !  l
            ./       /-‐、 | :!   !     わたし…がんばって、よかった
           /        ! _ -‘:::! !  :|!
        ./          |    ! } :|! l    つらかったり、苦しかったりしたけど…
        /           |    ヾシ/レ’
       (”T ‐r ァ-‐ ‐-、   :!    !レ’  でも…がんばって、よかった
         ソ  i !     フー、!    l
      /   !i     /ーr´    :l         ゴールは…幸せといっしょだったから
     /    l     / / |    !
     /     l     / / .!    l

という訳で院試受かった。

マンガで分かるLisp [その5]

8月 9th, 2009

明日は院試だ。

どうでもいいことだけど「マルチバンクキャッシュ」って銀行の用語っぽい。

マンガで分かるLisp [Section 1.4]

8月 5th, 2009

院試に落ちる夢を見た。

しかし、去年の春に単位が足りなくて4年生になれない夢を見たが、
普通に4年生になれたことを考えると、むしろ受かる兆候なのかもしれない。