---
title: "RITE 的結構 - mruby-go"
date: 2023-07-12T00:00:00+08:00
publishDate: 2023-07-12T00:00:00+08:00
lastmod: 2023-09-03T17:21:26+08:00
tags: ["mruby","mruby-go","Ruby","Golang","筆記"]
series: "mruby-go"
toc: true
permalink: "https://blog.aotoki.me/posts/2023/07/12/mruby-go-the-rite-structure/"
language: "zh-tw"
---


[mruby](https://mruby.org) 透過編譯器（Compiler，通常是 `mrbc`）編譯後，會產生 `mrb` 格式的二進位檔案，這個檔案的格式被稱作 RITE 如果要運行編譯後的 mruby 程式碼，就需要能夠解析並且讀取。

<!--more-->

## 結構{#structure}

RITE 的結構大致上分為兩個部分，[rite_binary_header](https://github.com/mruby/mruby/blob/3.2.0/include/mruby/dump.h#L81-L88) 以及數個 Section（區段）所組成，`rite_binary_header` 會紀錄這個二進位檔案的格式、mruby 版本、編譯器資訊等等。

區段基本上分為四種，分別是 `irep`、`debug`、`lv`（Local Variable）、`footer`（無意義）四個類型，如果沒有特別指定要包含 `debug` 區段，實際上只會有另外三個類型。

每個區段都會有自己的 Section Header 用來識別，除了 `irep` 還會有額外的 `rite_version` 的版本資訊外，其他區段都由 `char[4]` 的識別（Identity，`ident`）和 `uint32` 的區段大小所組成。

```c
// example irep section
struct rite_section_irep_header {
  uint8_t section_ident[4];
  uint8_t section_size[4];
  uint8_t rite_version[4];
};
```

了解這些後，我們就能夠用 Golang 讀取這些資訊。

## BinaryHeader

讀取 `rite_binary_header` 的方式並不困難，我們只需要定義一個 `BinaryHeader` 的結構，並且使用 Sized Bytes（固定大小的 `byte` 陣列）就可以讓 `binary` 套件的 [Read](https://pkg.go.dev/encoding/binary#Read) 方法正確的將數值填入。

```go
type BinaryHeader struct {
    Identifier [4]byte
	Version    struct {
		Major [4]byte
		Minor [4]byte
	}
	Size       uint32
	Compiler   struct {
		Name    [4]byte
		Version uint32
	}
}

func ReadHeader(r io.Reader) (*BinaryHeader, error) {
	header := &BinaryHeader{}
	err := binary.Read(r, binary.BigEndian, header)
	if err != nil {
		return nil, err
	}
	return header, nil
}
```

## Sections

讀取區段的方式比較複雜，會在後續的文章依序介紹。如果想要區分出一個區段，我們可以加入 `SectionHeader` 結構，將共用的區段資訊提取出來。

```go
type SectionHeader struct {
	Identity [4]byte
	Size     uint32
}

func ReadeSection(r io.Reader, remain uint32) (*Section, error) {
	header := &SectionHeader{}
	err := binary.Read(r, binary.BigEndian, header)
	if err != nil {
		return nil, err
	}

	isOverSize := header.Size > remain
	if isOverSize {
		return nil, errors.New("section size is larger than binary")
	}

	// ...

    sectionHeaderSize = uint32(unsafe.SizeOf(SectionHeader{}))
    noopBuffer := make([]byte, header.Size - sectionHeaderSize)
    _, err := r.Read(noopBuffer)
    if err != nil {
      return ni, err
    }

    return section, nil
}
```

在虛擬機器運作中，不論是 `BinaryHeader` 或者 `SectionHeader` 都是非必要的資訊，因此在實際的實作中會拋棄這些資料。

在 RITE 的設計中，Header 所包含的大小資訊會包含 Header 的大小，因此在計算的時候需要扣掉 Header 的大小，在上述的範例中我們會用 `header.Size - sectionHeaderSize` 來當做要製作的 `noopBuffer` 陣列的大小，確保 `io.Reader` 讀取時可以正確地停在下一個區段的起始點。

> 在 C 語言中的陣列可以透過指標（Pointer）的特性切換到任意的位置處理，然而在 Golang 中被封裝成 `io.Reader` 後我們只能依序讀取，那麼讀取的游標（Cursor）停止的位置就變得非常重要。

---

[mruby-go](https://github.com/elct9620/mruby-go) 是完全使用 Golang 來實現 mruby 的計畫，預期讓 Golang 可以運行 Ruby 來實現像是 DSL 或是 Hook 這類處理，讓開發有更多的彈性。

