IA32(x86)汎用命令対応のアセンブラ実装方法(2)

  • 5
    いいね
  • 0
    コメント

自作OS Advent Calendar 2016 12日目の @Hiroyuki-Nagata です.
前の人は 自分でした,次の人は Yuma Ohgamiさんです.

前回までで、基本的な仕様は書けたので以下の項目について述べて終わりにします。

  • 自分のアセンブラ実装例
  • YaccPegを用いた実装の可能性
  • 実装の難所 ModR/M と インテルの仕様書の記号の意味

アセンブラ実装例

拙作 opennask は、アセンブラの構文解析にはほとんどParasolを使用しています。なかなかこれがアセンブラの構文解析には使いやすかったです。というのは、アセンブラは昨今の普通のプログラム構文とは違って、基本的に1行単位で意味や処理が区切れるからです。

Parasolの使用例

まずは、TParaTokenizerを宣言しておいて、それに対してstd::istringstreamを渡します

     // オペコード処理用のクラスインスタンス
     nask_utility::Instructions inst;
     // 出力先のコンテナを作っておく
     std::vector<uint8_t> test;

     // テスト用の文字列をstd::arrayで用意
     std::array<std::string, 2> naskfunc_src = {
      "CYLS EQU 0x0ff0               \r\n", //  0
      "MOV  CL,BYTE [CYLS]           \r\n"  //  1
     };

     // std::array<std::string, x>
     // 1行1行をインスタンスに食わせます(実際はfor文で書く)
     std::istringstream input_stream1(naskfunc_src.at(0));
     std::istringstream input_stream2(naskfunc_src.at(1));

     // TParaTokenizerを宣言して渡す(これがParasolの機能の一部)
     TParaTokenizer tokenizer1(input_stream1, &inst.token_table);
     TParaTokenizer tokenizer2(input_stream2, &inst.token_table);
     // 1行目を処理
     inst.process_token_EQU(tokenizer1, test);
     // 2行目を処理
     inst.process_token_MOV(tokenizer2, test);

次に、トーカナイザーを作った後の処理

  • 何も設定せずに "MOV CL,BYTE [CYLS] \r\n" を食わせると

token.AsString() == "MOV"
tokenizer.LookAhead(1).AsString() == "CL"
tokenizer.LookAhead(2).AsString() == ","
tokenizer.LookAhead(3).AsString() == "BYTE"
tokenizer.LookAhead(4).AsString() == "["
tokenizer.LookAhead(5).AsString() == "CYLS"
tokenizer.LookAhead(6).AsString() == "]"

で入っているはずです、適当なところで区切られていますね? Parasol側の設定をいじると、もうちょいトークンの区切り方を賢くできそうなんですがそこまでやってません。

あとはそれを以下のようなループで回せば言語処理よくわからんプログラマーでも何か作れるわけです。

  • ループ処理の例
     // inst.process_token_MOV(tokenizer2, test);
     for (TParaToken token = tokenizer.Next(); ; token = tokenizer.Next()) {

     }

あとは、トークン自体に生えてるメソッドが結構便利

  • 文字列から数値への変換とか、トークンの時点で型を判定したり
// tokenに"MOV"が入っている時
token.AsString() == "MOV"

token.Is("MOV")    == true;  // virtual bool Is(const std::string& String) const;
token.IsNot("LIE") == true;  // virtual bool IsNot(const std::string& String) const;
token.AsString()   == "MOV"; // virtual std::string AsString(void) const;

// tokenに"0x0ff0" が入っている時
token.AsLong("0x0ff0") == 0x0ff0;  // virtual long AsLong(void) const throw(TScriptException);

それ以外にもまだ使ってない機能があります

YaccPegを用いた実装の可能性

とは言え、言語作ってる方からすると上記の方法は全然エレガントな方法では無いんじゃないかと思ってます。
アセンブラを1行1行処理してて思ったんですが、「これってもっと共通処理に括りだせね?」 って感じなんです。

なので、YaccPegについて触りだけ紹介したい

Wikipedia - Yacc

Yacc(英: yet another compiler compiler、ヤック)はパーサジェネレータの一つである。
1970年代にAT&TでUNIX用にステファン(スティーブ)・ジョンソン(英語版)が開発した。

こんな昔からあるなら使わんと損ですだよ。日本語のサイトだと徳丸さんのyacc入門が分かりやすかった。

そこの文章によると最低限必要な手数は以下:

yaccを利用する上では、最低限以下を記述する必要があります。

1. YACC文法ファイル:拡張子が .yであるファイル
2. 字句解析関数:yylex()
3. エラー表示関数:yyerror()
4. パーサの呼び出し:yyparse()関数の呼び出し

これを使えば、少なくともアセンブラの処理の分岐判定を .y ファイルに任せられると思いました。
ただし、Yacc には構文解析器の機能しかない、字句解析器にはLexなどを使うようだ。

Wikipedia - Peg

Parsing Expression Grammar (PEG, Parsing Expression Grammar) は、
分析的形式文法の一種であり、形式言語をその言語に含まれる文字列を認識するための一連の規則を使って表したものである。
  • Pegでは、従来必要だった字句解析器と構文解析器の2つをまとめて記述できる、らしい。楽しそうですね。 1

実装の難所 ModR/M と インテルの仕様書の記号の意味

最後に ModR/M の意味とか、
Qiitaに書こうと思ったのですが前にブログに書いた内容とかぶるのでリンクだけ張っておこうと思います。

ModR/M

x86 汎用命令 - ModR/M の解説

その他仕様書の記号の意味

オペコードについてくるいろいろな記号、これは以下のサイトが詳しいです
x86-64 命令の概要