ES-BASIC ver.0.2b を「はりぼてOS」に移植する

  • (by K, 2020.05.18)

(0) はじめに

  • ES-BASICというのはKが中心となって開発している、プログラミング言語処理系です。
  • これをもし「はりぼてOS」の上で動かすことができたら、自作OSの上で自作言語が動くということで、なんか夢があると思いませんか?
    • ちなみに、OSASK計画では、OSASK上で自作言語を動かすのに成功したことはありました。だからこれが日本初だということにはなりません。
    • ・・・しかし初めてではないとしても、やはり夢があることに変わりはないです!
  • そもそも http://essen.osask.jp/?esbasic0016 に書いてある通り、ES-BASICを「はりぼてOS」に移植するというアイデアは結構以前からありました。
  • だから今回はそれを実際にやってみようという、ただそれだけの話です。

(1) 「はりぼてOS」の改造

  • まずは「はりぼてOS」を改造してJITコンパイル対応しなければいけません。
  • 今の「はりぼてOS」は、アプリがメモリ上に書いた機械語を実行する手段を提供していません。アプリはアプリ制作時に生成したバイナリ以外を一切実行できないようになっています。
  • それはセキュリティの観点では非常に堅牢なのですが、今回のように高速なスクリプト言語をアプリで実現しようとすると障害になってしまいます。
  • ということで、データ上のプログラムを実行可能にするためのAPIを「はりぼてOS」側に追加するところから開発を始めます。
  • と思ったけど、ES-BASICのソースを見て方針を少し変更。まずはadvance/FPUを入れることにします。というのはES-BASICは少しだけ実数演算を使っているところがあったからです。・・・まあこれは、やろうと思えばすぐにできるので簡単です。

  • そして以下のAPIを追加します。
    • api_semiFlat (EDX=32)
      • [EDX以外のパラメータ] なし
      • [説明] このAPIを呼び出すと、それ以降はデータセグメント内にあるコードもそのまま実行できるようになります。
      • より具体的に言うと、コードセグメントの内容をデータセグメントにコピーしたのちに、コードセグメントをデータセグメントのエイリアスに再設定しているだけです。

  • このための変更点は以下の通りです。
    • haribote/bootpack.h (/* dsctbl.c */の中です)
      struct SEGMENT_DESCRIPTOR {
          unsigned short limit_low, base_low; /* ココから(つけ忘れていたunsigned属性をつける) */
          unsigned char base_mid, access_right;
          unsigned char limit_high, base_high; /* ココまで */
      };
    • haribote/console.c (hrb_api()の中です)
          } else if (edx == 27) {
              reg[7] = task->langmode;
          } else if (edx == 32) {              /* ココから */
              struct SEGMENT_DESCRIPTOR *sd = task->ldt + 0;
              int cs_base = sd->base_low | sd->base_mid << 16 | sd->base_high << 24;
              int cs_size = ((sd->limit_low | sd->limit_high << 16) & 0xfffff) + 1;
              int ds_size = *((int *) (cs_base + 0x0000));
              memcpy((char *) ds_base, (char *) cs_base, cs_size);
              set_segmdesc(task->ldt + 0, ds_size - 1, ds_base, AR_CODE32_ER + 0x60); /* ココまで */
          }
    • apilib.h
      void api_semiFlat(void); /* これを末尾に追加 */
    • apilib/api032.nas (新規作成)
      [FORMAT "WCOFF"]
      [INSTRSET "i486p"]
      [BITS 32]
      [FILE "api032.nas"]
      
          GLOBAL  _api_semiFlat
      
      [SECTION .text]
      
      _api_semiFlat:  ; void api_semiFlat(void);
          MOV     EDX,32
          INT     0x40
          RET
    • apilib/Makefile
      • api032.objもライブラリに含めるように適当に修正してください。

  • テスト用アプリを作ってみました。
    #include "apilib.h"
    
    void HariMain(void)
    {
        static unsigned char p[] = {
            0xba, 0x01, 0x00, 0x00, 0x00, 0xb0, 0x41, 0xcd, 0x40, 0xc3
        };
        api_semiFlat();
        ((void (*)()) p)();
        api_end();
    }
  • これを実行すると、データセクション内にあるpが普通に実行されて、EDX=1,AL=0x41でINT 0x40が実行され、「A」が表示されます。
  • ということでうまくいったので、「はりぼてOS」側の改造はおしまいです。

  • と思ったけれど、esb02b.hrbが大きくなっていくにつれて、無改造のipl09.nasでは読み込みきれなくなってきて実行できなくなってきたので、advance/iplの改造を適用しました。
  • ついでにAthlon64X2advance/smaller1も適用しました。
  • さらにキーコードの問題があったのでadvance/keycodeも適用しました。

  • API番号表:
    EDX説明プロトタイプ宣言備考
    1コンソールに一文字表示void api_putchar(int c);
    2コンソールに文字列表示void api_putstr0(char *s);
    3コンソールに文字列表示void api_putstr1(char *s, int l);
    4アプリの終了void api_end(void);
    5ウィンドウのオープンint api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
    6ウィンドウに文字列描画void api_putstrwin(int win, int x, int y, int col, int len, char *str);
    7ウィンドウに矩形描画void api_boxfilwin(int win, int x0, int y0, int x1, int y1, int col);
    8アプリ内でapi_mallocが利用できるように内部初期化void api_initmalloc(void);
    9メモリの確保char *api_malloc(int size);
    10メモリの解放void api_free(char *addr, int size);
    11ウィンドウに点を打つvoid api_point(int win, int x, int y, int col);
    12ウィンドウの描画内容を手動で画面に反映void api_refreshwin(int win, int x0, int y0, int x1, int y1);
    13ウィンドウに線を引くvoid api_linewin(int win, int x0, int y0, int x1, int y1, int col);
    14ウィンドウを閉じるvoid api_closewin(int win);
    15入力バッファから入力int api_getkey(int mode);
    16タイマを取得int api_alloctimer(void);
    17タイマを初期化void api_inittimer(int timer, int data);
    18タイマを設定void api_settimer(int timer, int time);
    19タイマを解放void api_freetimer(int timer);
    20音を出すvoid api_beep(int tone);
    21ファイルの読み込み用オープンint api_fopen(char *fname);
    22ファイルのクローズvoid api_fclose(int fhandle);
    23ファイルのシークvoid api_fseek(int fhandle, int offset, int mode);
    24ファイルサイズ取得int api_fsize(int fhandle, int mode);
    25ファイルの読み込みint api_fread(char *buf, int maxsize, int fhandle);
    26アプリのコマンドライン引数の取得int api_cmdline(char *buf, int maxsize);
    27現在の言語モードを取得int api_getlang(void);
    27OSの識別番号を取得int api_getosid(void);詳しくはadvance/families参照 (しかし今回はこの改造を適用していません)
    27OSのバージョン番号を取得int api_getosver(void);詳しくはadvance/families参照 (しかし今回はこの改造を適用していません)
    28ファイルの書き込み用オープン詳しくはadvance/fwrite参照 (しかし今回はこの改造を適用していません)
    29ファイルの書き込み詳しくはadvance/fwrite参照 (しかし今回はこの改造を適用していません)
    30OSの切り替えvoid api_osselect(int i);詳しくはadvance/osselect参照 (しかし今回はこの改造を適用していません)
    31キー入力データを送り込むint api_sendkey(char *);詳しくはadvance/startup参照 (しかし今回はこの改造を適用していません)
    32JITコンパイル対応モードへ切り替えvoid api_semiFlat(void);詳しくはesb02b参照
    33(ECX=1)timerctl.countの取得int api_getTimeCount(void);詳しくはadvance/keycode参照
    33(ECX=2)キー入力の拡張int api_getkeyEx(int mode);詳しくはadvance/keycode参照

(2) ES-BASICの移植

  • [進捗&雑談#1]
    • 移植で一番面倒なのは、標準ライブラリが「はりぼてOS」では使えないこと。まずここをどうにかしないといけない。
    • 標準ライブラリくらいならもちろん自力でも書けるのだけど、そこでミスったら面倒だし、テストをいっぱいするのも面倒だし・・・。
    • そう思ってglibcから拝借しようかと思ったけど、glibcの実装は高速化と移植性のために冗長になっていて、うーん、そういうのがほしいわけじゃないんだ・・・になってしまった。他のライブラリをたくさん参照されると、それを用意する必要が出てくるので、労力が減らない・・・。
    • やっぱり自分でシンプルに実装するかー。
    • qsortとかを自前で作ることにちょっと熱中してしまった(笑)。
  • [進捗&雑談#2]
    • なるほどなー。「はりぼてOS」でグラフィックウィンドウを出すときは、ウィンドウの全領域(ユーザ領域以外も含む)のサイズのchar配列を用意するわけかー。ちょっと思い出してきたぞ・・・。
      char winbuf[336 * 261];
      win = api_openwin(winbuf, 336, 261, -1, "invader");
      api_boxfilwin(win, 6, 27, 329, 254, 0);
    • 「はりぼてOS」のシェルのコンソールは、ちょっとプログラムを書くには狭すぎるし、そもそも一行入力とかのAPIも用意していない記憶があるから、自前でコンソールを作るほうがよさそうだな・・・。
  • [進捗&雑談#3]
    • あと少しで初期化を終えられそうだ・・・。そしたらなんか画面が出そう。
  • [進捗&雑談#4]
    • ここまできて、移植方針を大転換。そのほうがうまくいく気がしたので。・・・ということで改造部分を全部ロールバックしてやり直した。
    • でも、そしたらすぐに1時間程度でいいところまで来た。今までの数日の苦労は一体・・・(笑)。
    • とりあえずあと15個の標準関数を適当に実装すれば、そこそこ動きそうなところまで来ました。
  • [進捗&雑談#5]
    • 面倒になってきて、標準関数をダミーで適当にごまかしたら、ついにES-BASICが起動して「Ok」が出るところまでは動くようになりました。しかしまだコンソールに対して入力ができないので、そこから先には進めませんが・・・。
  • [進捗&雑談#6]
    • コンソールが動くようになって、コンソールしか使わないプログラムはほとんど動くようになりました。自作OS上で自作言語処理系がサクサクと動くのは相当にしびれます。デバッグ機能もちゃんと動きます。笑いが止まりません(笑)。
    • 次はグラフィックを出せるようにしなければっ!
  • [進捗&雑談#7]
    • グラフィックも出るようになった。かっこいいぞ!
  • [進捗&雑談#8]
    • 完璧に動くようになったーー!・・・内容をまとめなければっ!

こめんと欄


コメントお名前NameLink

リロード   新規 編集 差分 添付   トップ 一覧 検索 最終更新 バックアップ   ヘルプ   最終更新のRSS
Last-modified: 2020-06-05 (金) 10:51:23 (137d)