mruby 透過編譯器(Compiler,通常是 mrbc
)編譯後,會產生 mrb
格式的二進位檔案,這個檔案的格式被稱作 RITE 如果要運行編譯後的 mruby 程式碼,就需要能夠解析並且讀取。
結構
RITE 的結構大致上分為兩個部分,rite_binary_header 以及數個 Section(區段)所組成,rite_binary_header
會紀錄這個二進位檔案的格式、mruby 版本、編譯器資訊等等。
區段基本上分為四種,分別是 irep
、debug
、lv
(Local Variable)、footer
(無意義)四個類型,如果沒有特別指定要包含 debug
區段,實際上只會有另外三個類型。
每個區段都會有自己的 Section Header 用來識別,除了 irep
還會有額外的 rite_version
的版本資訊外,其他區段都由 char[4]
的識別(Identity,ident
)和 uint32
的區段大小所組成。
1// example irep section
2struct rite_section_irep_header {
3 uint8_t section_ident[4];
4 uint8_t section_size[4];
5 uint8_t rite_version[4];
6};
了解這些後,我們就能夠用 Golang 讀取這些資訊。
BinaryHeader
讀取 rite_binary_header
的方式並不困難,我們只需要定義一個 BinaryHeader
的結構,並且使用 Sized Bytes(固定大小的 byte
陣列)就可以讓 binary
套件的 Read 方法正確的將數值填入。
1type BinaryHeader struct {
2 Identifier [4]byte
3 Version struct {
4 Major [4]byte
5 Minor [4]byte
6 }
7 Size uint32
8 Compiler struct {
9 Name [4]byte
10 Version uint32
11 }
12}
13
14func ReadHeader(r io.Reader) (*BinaryHeader, error) {
15 header := &BinaryHeader{}
16 err := binary.Read(r, binary.BigEndian, header)
17 if err != nil {
18 return nil, err
19 }
20 return header, nil
21}
Sections
讀取區段的方式比較複雜,會在後續的文章依序介紹。如果想要區分出一個區段,我們可以加入 SectionHeader
結構,將共用的區段資訊提取出來。
1type SectionHeader struct {
2 Identity [4]byte
3 Size uint32
4}
5
6func ReadeSection(r io.Reader, remain uint32) (*Section, error) {
7 header := &SectionHeader{}
8 err := binary.Read(r, binary.BigEndian, header)
9 if err != nil {
10 return nil, err
11 }
12
13 isOverSize := header.Size > remain
14 if isOverSize {
15 return nil, errors.New("section size is larger than binary")
16 }
17
18 // ...
19
20 sectionHeaderSize = uint32(unsafe.SizeOf(SectionHeader{}))
21 noopBuffer := make([]byte, header.Size - sectionHeaderSize)
22 _, err := r.Read(noopBuffer)
23 if err != nil {
24 return ni, err
25 }
26
27 return section, nil
28}
在虛擬機器運作中,不論是 BinaryHeader
或者 SectionHeader
都是非必要的資訊,因此在實際的實作中會拋棄這些資料。
在 RITE 的設計中,Header 所包含的大小資訊會包含 Header 的大小,因此在計算的時候需要扣掉 Header 的大小,在上述的範例中我們會用 header.Size - sectionHeaderSize
來當做要製作的 noopBuffer
陣列的大小,確保 io.Reader
讀取時可以正確地停在下一個區段的起始點。
在 C 語言中的陣列可以透過指標(Pointer)的特性切換到任意的位置處理,然而在 Golang 中被封裝成
io.Reader
後我們只能依序讀取,那麼讀取的游標(Cursor)停止的位置就變得非常重要。
mruby-go 是完全使用 Golang 來實現 mruby 的計畫,預期讓 Golang 可以運行 Ruby 來實現像是 DSL 或是 Hook 這類處理,讓開發有更多的彈性。
如果對這篇文章有興趣,可以透過以下連結繼續閱讀這系列的其他文章。
- RITE 的結構 - mruby-go