ES-BASIC ver.0.2b を「はりぼてOS」に移植する
(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」側の改造はおしまいです。
- 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); | | 27 | OSの識別番号を取得 | int api_getosid(void); | 詳しくはadvance/families参照 (しかし今回はこの改造を適用していません) | 27 | OSのバージョン番号を取得 | int api_getosver(void); | 詳しくはadvance/families参照 (しかし今回はこの改造を適用していません) | 28 | ファイルの書き込み用オープン | | 詳しくはadvance/fwrite参照 (しかし今回はこの改造を適用していません) | 29 | ファイルの書き込み | | 詳しくはadvance/fwrite参照 (しかし今回はこの改造を適用していません) | 30 | OSの切り替え | void api_osselect(int i); | 詳しくはadvance/osselect参照 (しかし今回はこの改造を適用していません) | 31 | キー入力データを送り込む | int api_sendkey(char *); | 詳しくはadvance/startup参照 (しかし今回はこの改造を適用していません) | 32 | JITコンパイル対応モードへ切り替え | 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]
- 完璧に動くようになったーー!・・・内容をまとめなければっ!
こめんと欄
|