こんばんは、川合です。
Hidemi KAWAI は 2004/12/21 23:43:31 の「[osask 7134] bim2bin4u.
」で書きました:
> 実は僕とI.Tak.さんくらいしか興味を持たない(=リンカを使わずに
>直接naskやNASMのbinモードでアプリを作るような人しか関係ない)や
>やこしい話があるのですが、なんかもう書きつかれてきたので、それは
>またの機会にいたします。
この話をここに書きます。
いきなりですが、bballを186バイトにしたソースを書きます。
[FORMAT "BIN"]
[INSTRSET "i486p"]
[OPTIMIZE 1]
[OPTION 1]
[BITS 32]
[SECTION .text]
MOV ESP,CS:[EDX+0x20] ; (註1) 2E 8B 62 20
PUSH 0x21 ; (註1) 6A 21
JMP .skip ; (註1) EB 28
DD 'GUIGUI00'
DD 0x4000+0xc000,0,0,0
DD 0x4000,work1-work0,0,work0
; stack-size, .data-size, 0, img-ofs
..skip:
PUSH 0xfffffff1 ; (註1) 6A F1
PUSH 4 ; (註1) 6A 04
..cal:
MOV EBX,ESP ; (註1) 89 E3
CALL 0xc7:0 ; (註1) 9A 00 00 00 00 C7 00
ADD ESP,12 ; (註1) 83 C4 0C
; 以下はI.Tak.さんのbball4とほぼ同じ
; このアドレスがちょうど0x0040
PUSH EAX ; 終了ファンクション(0)
PUSH EAX ; (0)
PUSH EAX ; (0)
PUSH 1
PUSH 0x0018 ; waitsignal
MOV DL,13
..label0:
MOV CL,14
SUB ECX,EDX
..label1:
MOV AL,[EBX+37]
PUSH EAX
MOV AL,[EBX+36]
PUSH EAX
MOV AL,[EBX+ECX*2+37]
PUSH EAX
MOV AL,[EBX+ECX*2+36]
PUSH EAX
CMP CL,8
SBB AL,AL
XOR AL,CL
AND AL,15
INC EAX
PUSH EAX
PUSH 0x4400 ; gbox
PUSH 0x80 ; or
PUSH 0x0054 ; line
LOOP .label1
DEC EBX
DEC EBX
DEC EDX
JGE .label0
JMP .cal
ALIGNB 16
work0:
DD 0x4100 ; init (註2)
DD 0x0020,0x4200,0x0200,200,200
DD 0x0028,0x1000,0x4300,0,8,1,0,0,0x4200,0x00c0,0
DD 0x0040,0x1000,0,0,0x4300,0,0,0,8,'bballk'
DD 0x0030,0x0001,0x4400,1,0,200,200,0,0,0x4200
DD 0x0000
DB 0,0,0,0,0,0 ; pad
..table:
DB 196,100,187,61,164,29,129,9,90,5
DB 53,17,23,44,7,81,7,119,23,156
DB 53,183,90,195,129,191,164,171,187,139
work1:
ここでファンクション4-fffffff1について説明します。ここで使って
いる形式は、
0004-fffffff1-0021
というもので、この最後の21がポイントです。これはパラメータで、
bit0-1がオプションフラグ、bit2-31が、コードセグメントへのオフセ
ットということになっています。
まずオフセットですが、これは16バイトのパラメータがコードのどこ
にあるかを示したものです。16バイトのパラメータというのは、
スタック上限、staticデータサイズ、0、staticデータイメージ開始オフセット
をDDで書き並べたものです。
pioneer0.askは、まずスタック上限を認識し、オフセット0からこの
上限までのアドレス範囲をスタック専用アドレス空間と解釈します。そ
してもしメモリが不足してきたら、ESPがこのアドレス範囲に収まって
いるのを確認した後に、オフセット0からそのときのESPまでの値はゴミ
だと解釈し、捨ててしまいます(ページに未使用マークをつけシステム
に返してしまう)。
ESPがこの範囲に収まっていない場合は、たとえば擬似マルチスレッ
ドライブラリなどを使ってESPを操作している可能性があるので、ESPの
値から未使用スタック範囲を割り出すのを諦めます。
staticデータの展開先頭アドレスはこのスタック上限値であると解釈
します。
そしてstaticデータイメージ開始オフセットを読み込み、転送したよ
うにみえるようなマッピングをします。
このようなことを全部やるサブコマンドです。もしこの中の一つでも
不都合があるなら(たとえばDS/SSを"stack-.data-malloc-mmarea"の順
序ではなく、".data-stack-malloc-mmarea"などの順序にしているなど
)、0xfffffff1サブファンクションは使うべきではありません。ストリ
ング命令などで転送するべきでしょう。
以上が済むと、オプションフラグをチェックします。ここが0ですと
通常終了になり、次は-1サブファンクションが必要です(もちろん他の
サブファンクションでもいいですが)。
オプションフラグが1の場合は、もっと気の利いたことをします。こ
のbballk0の例ではまさにその0x21を使っています。このモードでは、
まずEBXをstaticデータの展開先頭アドレスにジャンプさせます。さら
にファンクション4を受け取った状態にします。そうした後に、解釈を
開始します。
ということで、この例ではlib_init(0x4100);が見事に実行され(註2
参照)、しかもその先のファンクションたちも順調に実行されていくの
です。これでcall 0xc7:0を一回減らすことができるわけです。
次の説明です。
bballk0をみると結構無駄に思えるコードがあります。たとえば
ESP+=12;とかです。ESPの初期値代入だってお得意のAH=0xc0;
XCHG(EAX,ESP);を使えば1バイト減るのに使っていません。これには理
由があります。
Twitchell6ではrjcも強化されました。ファイルの先頭が特定のパタ
ーンであれば、これを圧縮がかかりやすい特別なパターンに置換するの
で圧縮率が上がります。註1の部分のバイトの規定値はそこに示したよ
うになっているので、あえてそうしているのです。またスタックのサイ
ズは1KB単位、staticデータのサイズは16バイト単位、staticデータの
開始位置も16バイトアラインされている、の全ての条件を満たすとさら
に特別な変換がなされてさらに圧縮は有利になります。
ちなみにプログラムの最初の64バイトがどのように変換されるかです
が、bballk0の例ではこうなります。
0000 : F4 02 01 1D 19 17 09 00 47 55 49 47 55 49 30 30
0010 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0020 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0030 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0040 : 50 50 50 6A 01 6A 18 B2 0D B1 0E 29 D1 8A 43 25
F4はHLT();ですので、こんな風に始まるアプリは存在しません。それ
をもとに判定しています。それ以降の02や01や1Dはどういう意味がある
のかという突っ込みがあると思いますが、それはbim2binが面倒を見て
くれますので、僕たちは気にしなくていいのです。とにかく上記のよう
なプログラムを書けば、bim2binが内部で勝手に圧縮前に変換してくれ
ます。このバイナリ列を示したのは、ほほう、なるほどこれは小さくな
りそうだ、ということを納得してもらうためだけのものです。
ということで、この知識を生かしたプログラムを書くかもしれないの
は、たぶん僕とI.Tak.さんくらいしかいないかもしれませんが、参考ま
でに。
で、この説明を書いているうちにbim2bin4uにバグを見つけてしまい
ました。そんなわけで、不本意ながらまたバージョンアップです。どん
なバグを直したのかというと、この拡張rjc機能が上記プログラム例で
はうまく働かないというバグです。
http://k.hideyosi.com/bim2bi4v.lzh (206KB)
それでは。
--
川合 秀実(KAWAI Hidemi)
OSASK計画代表 / システム設計開発担当
E-mail:kawai !Atmark! osask.jp
Homepage http://osask.jp/