ローファイ日記

出てくるコード片、ぼくが書いたものは断りがない場合 MIT License としています http://udzura.mit-license.org/

mruby 3.2 で実行できるmrbバイナリを自作した

手作りの温かみのあるバイナリです。

作りかた

そろそろRustで書くか、と思ってRustで書いた。

bytes というcrateが今回の用途にめちゃくちゃマッチしていた。数値をBig Endianで書き込めればいいので、こういう感じで。

    pub fn write(&self, mut out: impl io::Write) -> Result<(), io::Error> {
        let b = &self.binary_header;
        let mut buf = bytes::BytesMut::new();
        buf.put_slice(&b.ident);
        buf.put_slice(&b.major_version);
        buf.put_slice(&b.minor_version);
        let size_pos = buf.len();
        buf.put_i32(-1);
        buf.put_slice(&b.compiler_name);
        buf.put_slice(&b.compiler_version);

        for section in self.sections.iter() {
            match section.header.ident {
                rite::markers::irep => {
                    let h = &section.header;
                    let i = section.body_irep.as_ref().unwrap();

                    buf.put_slice(&h.ident); //....

                    buf.put_u16(i.pool.len() as u16);
                    for pi in i.pool.iter() {
                        match pi {
                            rite::PoolItem::Str(s) => {
                                buf.put_u8(0); // IREP_TT_STR
                                // ...
                            }
                            _ => {
                                todo!("unsupported")
                            }
                        }
                    }

                    buf.put_u16(i.syms.len() as u16);
                    for s in i.syms.iter() {
                        buf.put_u16(s.name.len() as u16);
                        buf.put_slice(&s.name);
                        buf.put_u8(0);
                    }

                    for _ in i.children.iter() {
                        // ...
                    }
                }
                rite::markers::end => {
                    let h = &section.header;
                    buf.put_slice(&h.ident);
                    buf.put_i32(h.size);
                }
                _ => {
                    unreachable!("unknown section ident")
                }
            }
        }

        // finally write back total binsize
        let size = buf.len();
        buf[size_pos] = ((size >> 24) & 0xff) as u8;
        buf[size_pos + 1] = ((size >> 16) & 0xff) as u8;
        buf[size_pos + 2] = ((size >> 8) & 0xff) as u8;
        buf[size_pos + 3] = (size & 0xff) as u8;

        let freeze = buf.freeze();
        out.write(&freeze[..])?;
        out.flush()?;

        Ok(())
    }

あとはサイズの検知の仕方とかをこなれた感じにするかなー。

できたバイナリ、compiler name が MATZ と違っていてもちゃんと実行できる!

$ xxd /opt/ghq/github.com/udzura/mrbasm/sample.mrb        
00000000: 5249 5445 3033 3030 0000 0056 4d41 534d  RITE0300...VMASM
00000010: 3030 3030 4952 4550 0000 003a 3033 3030  0000IREP...:0300
00000020: 0000 002e 0001 0004 0000 0000 0000 000a  ................
00000030: 5102 002d 0100 0138 0169 0001 0000 0548  Q..-...8.i.....H
00000040: 656c 6c6f 0000 0100 0470 7574 7300 454e  ello.....puts.EN
00000050: 4400 0000 0008                           D.....
$ ./bin/mruby /opt/ghq/github.com/udzura/mrbasm/sample.mrb
Hello

コード

github.com

色々ラフですが。一旦上記に置いた。

リポジトリ名の通り、最終的にはmrubyの命令を直接記述できるアセンブリ言語を作ろうとしている。まだ何も設計できていないが、多分こういう感じ。

!binformat mruby 3.2
!begin irep main:
!rootirep
    LOADI_2       R1      (2)
    MOVE          R2      R1
    LOADI_1       R3      (1)
    GT            R2      R3
    JMPNOT        R2      else1
    STRING        R3      "big"
    SSEND         R2      :puts   1
    JMP           end1
else1:
    STRING        R3      "small"
    SSEND         R2      :puts   1
end1:
    RETURN        R2
    STOP
!end

!begin irep sub:
    LOADI_2       R1      (2)
    MOVE          R2      R1
    ...
!end

ただまあ、このasmは正直やろうとしていることに必要だから作っていて、ゴールでもなんでもない。作りたいのはもうちょっと別のもの。

ということでRubyKaigi お疲れ様です。みなさんと沖縄で会えるといいっすね。