1: 2008-12-23 (火) 21:40:20 |
2: 2008-12-23 (火) 23:01:36 |
| -アセンブラレベルでこそabcdw007の仕様のよさがはっきりするので、それを書きたいと思います。 | | -アセンブラレベルでこそabcdw007の仕様のよさがはっきりするので、それを書きたいと思います。 |
| -abcdw007以降の仕様では、APIを呼び出すには、ESIをアプリ起動時の値に戻して(変更していないのならそのままで) | | -abcdw007以降の仕様では、APIを呼び出すには、ESIをアプリ起動時の値に戻して(変更していないのならそのままで) |
- | EDI = 0; CALL([ESI+20]); DB ... | + | EDI = 0; CALL([ESI + 20]); DB ... |
| -とするのが一般的です。EDIをゼロにするとCALLの直後がパケット開始アドレスだと見なされて、パケットの実行が終わるとDBのあとのところへちゃんと戻ってきてくれます。EDIに適当なアドレスを入れた場合は、そこがパケット開始アドレスになります。旧OSASKのEBXみたいなものだと思えば分かりやすいでしょう(旧OSASKとは違い、EDIはパケット終端の次のアドレスまで進んでくれます・・・まだ未実装ですが)。 | | -とするのが一般的です。EDIをゼロにするとCALLの直後がパケット開始アドレスだと見なされて、パケットの実行が終わるとDBのあとのところへちゃんと戻ってきてくれます。EDIに適当なアドレスを入れた場合は、そこがパケット開始アドレスになります。旧OSASKのEBXみたいなものだと思えば分かりやすいでしょう(旧OSASKとは違い、EDIはパケット終端の次のアドレスまで進んでくれます・・・まだ未実装ですが)。 |
| --なお上記の EDI = 0; はASKAでは XOR(EDI, EDI); という意味になります。もちろんMOVで0にしたければそれでもいいですが。 | | --なお上記の EDI = 0; はASKAでは XOR(EDI, EDI); という意味になります。もちろんMOVで0にしたければそれでもいいですが。 |
| --なおアプリ起動時はEDI=0, EBP=[ESI+20]になっていますので、何も設定せずにCALL(EBP); DB ...だけでいけます。 | | --なおアプリ起動時はEDI=0, EBP=[ESI+20]になっていますので、何も設定せずにCALL(EBP); DB ...だけでいけます。 |
| -まあなにはともあれ、一つ例を出しましょう。 | | -まあなにはともあれ、一つ例を出しましょう。 |
- | EDI = 0; CALL([ESI+20]); DB(0x40); | + | EDI = 0; CALL([ESI + 20]); DB(0x40); |
| -これは正常終了です。パケットの中身は、gh4でエンコードされているので[4 0]です。 | | -これは正常終了です。パケットの中身は、gh4でエンコードされているので[4 0]です。 |
- | --gh4については[[GUIGU01/man004]]を参照。 | + | --gh4については[[GUIGUI01/man0004]]を参照のこと。 |
| + | -4が正常終了の機能番号で、0がオプションパラメータです。0は正常終了を意味します。 |
| + | -では異常終了して、数値1を返してみます。いわゆるexit(1);です。 |
| + | EDI = 0; CALL([ESI + 20]); DB(0x43, 0x10); |
| + | -パケットの中身は[4 3 1]です。末尾の0はただのパディングなのでなんでもいいです。ここでは無視することにします。オプション3は異常終了かつ一つの整数値を終了コードとしてシステムに返すという意味です。で、その数値が1だというわけです。 |
| + | -返したい数字はもっと大きいかもしれません。たとえばexit(100);がやりたければ、 |
| + | EDI = 0; CALL([ESI + 20]); DB(0x43, 0xc6, 0x40); |
| + | -とします。パケットは[4 3 100]です。 |
| + | -さてこれで定数を終了コードとして返すことは簡単にできました。しかしEAXに入っている値を返したいなんていう場合はどうすればいいでしょうか。旧OSASKなら |
| + | PUSH(EAX); PUSH(3); PUSH(4); EBX = ESP; CALL(0xc7,0); |
| + | -などとやっていました。これはどういうことかというと、パケットに一つでも定数ではない部分があると、もはや.textセクション上にパケットをおいておくことができないということです(もっとも旧OSASKでは.textセクション上にパケットを置くことそのものが稀でしたが)。abcdw007ではどうかというと、うまい方法があります。そうでないと、せっかくCALL直後にパケットを並べてスタック操作しなくていいぶんだけ得なのに、そのメリットが生かせないからです。結論から言うとこうします。 |
| + | EDI = 0; CALL([ESI + 20]); DB(0x43, 0x60, 0x00); |
| + | -このパケットは[4 3 6 0 0]なのですが、この6が4bit形式の6であり、しかも特別な意味を持つことを強調するために、[4 3 !6 0 0]と書くことにします。 |
| + | -この!6は普通の数字の6とは解釈されません。つまりexit(6);にはならないのです。もし単にexit(6);がやりたいときは、4bit形式の6を避けます。たとえば、 |
| + | EDI = 0; CALL([ESI + 20]); DB(0x43, 0x86); |
| + | -とするわけです。ここでは8bit形式の6を使いました。・・・で、!6の効果ですが、!6_0_の形式は32bitレジスタを表します。!6_0_0はレジスタ番号0、つまりEAXです。だから先ほどのパケットは、[4 3 EAX]なのです。PUSHとかはもういらないわけです。 |
| + | -レジスタ番号1はECX、レジタ番号2はEDX、以下、ECX、EBX、ESP、EBP、ESI、EDIとなります。またレジスタ番号8は今のところ使用禁止ですが、レジスタ番号9以降は使用可能で、CALL直前の[ESP]、[ESP+4]、[ESP+8]、...を意味します。 |
| + | -パラメータとして使えるのは32bitレジスタだけではありません。8bitレジスタも使えます。たとえば、 |
| + | EDI = 0; CALL([ESI + 20]); DB(0x43, 0x61, 0x40); |
| + | -とすると、パケットの中身は[4 3 AH]になります。 |
| + | -今までの例では変数が使えるのは、パラメータの部分だけでしたが、これは別にどこだって使えます。機能番号やオプションの部分もできます。また現在は未実装ですが、単にレジスタやメモリの値を参照するだけではなく、簡単な式を書き込むこともできるようになる予定です。ですからEAXだけではなく、EAX+2とかを使わせることすらできるわけです。 |
| + | -どうでしょう?なんだかわくわくしませんか?アセンブラ使いにはたまらないと思うのですが・・・。 |
| + | ---- |
| + | -さてここから第2部です。先ほどまで一番簡単な終了ファンクションを例に取りましたが、もっと複雑な例を見せたいと思います。正直、ここが一番設計で苦労しました。 |
| + | -文字列を扱うファンクションがいい例だと思うので、文字表示ファンクション5を例にとりたいと思います。 |
| + | -たとえば、画面に"246"と表示させてみることにします。書き方はいろいろあるのですが、まずは一番説明しやすい方法で。 |
| + | EDI = 0; CALL([ESI + 20]); DB(0x50, 0x53, 0xb2, 0xb4, 0xb6, 0x30); |
| + | -このパケットは普通に見れば[5 0 5 3 0x32 0x34 0x36 3]となります。しかし0x32は'2'のキャラクターコードなので、ここではあえて[5 0 5 3 "246" 3]と書くことにします。またこの3番目の5はこの文脈では!5という意味で、結果として何の意味も持たないので、これを消すことにします(詳細は後で説明します)。そうすると、結局[5 0 3 "246"][3]となります。パケットはコマンドの切れ目で切ると見やすいのでこう書いています。 |
| + | -これは旧OSASKのパケットととてもよく似ていて(数値はいろいろ違いますが)、slot0で文字数3で文字コードが並んでいるだけなのです。[3]はパケット終端です。これを実行するとアプリに戻ります。 |
| + | -これを応用すれば、まずEAX一文字を表示するputcが簡単に作れます。 |
| + | EDI = 0; CALL([ESI + 20]); DB(0x50, 0x51, 0x60, 0x03); |
| + | -こんなものです。パケットは[5 0 1 EAX]になります。同じように応用すればhelloももちろん作れます。 |
| + | -しかしこのままでは終了ファンクションと同じで大して面白くありません。面白いのはここからです。!5の後に、!6_504_0_!5_1_3_0...と続けることができます。つまりこういうことです。 |
| + | EDI = 0; CALL([ESI + 20]); DB(0x50, 0x56, 0xdf, 0x80, 0x51, 0x30, 0x32, 0x34, 0x36, 0x30); |
| + | -これは[5 0 !6 504 0 1 3 0 "246"][3]という意味になります(余計な!5は消しました)。!6_504_0_1_3というのが特別なもので、!6_504_0は「モード変更直接」次の1が8bit配列モード、次の3が「文字数兼モード長」です。8bit配列モードになれば、数値をいちいちgh4でエンコードしなくていいのです。普通にcharで書けます。ちなみに文字数3の後の0は、ただのパディングですが、これは必ず0でなければいけません。適当な数値を入れると誤動作します。 |
| + | -しかし実は、機能番号5のフォーマットは、[5 slot (!6 504) mod (1) len str]となっています(mod=0の場合)。この()で囲まれた部分は、!5で打ち消さなければ自動で挿入される部分です。つまり自動挿入をあてにするなら、 |
| + | EDI = 0; CALL([ESI + 20]); DB(0x50, 0x03, 0x32, 0x34, 0x36, 0x30); |
| + | -これで十分なのです。 |
| + | -また文字列といえば、C言語で見られるような、'\0'で終わるタイプもあります。つまり事前に文字数が分からないけど、ターミネータがあるからたどっていけば終端は分かる、というやつです。そういうのは、mod=1を使うと簡単に扱えます。 |
| + | -mod=1のときの機能番号5のフォーマットは、[5 slot (!6 504) mod (1) (term) str]になります。ここでtermはデフォルトでは自動挿入ですが、自動挿入任せにすると0が入ります。まさに'\0'がターミネータですね。 |
| + | EDI = 0; CALL([ESI + 20]); DB(0x50, 0x10, 0x32, 0x34, 0x36, 0x00, 0x30); |
| + | -まだあります。mod=2/3にすると、strの部分が (!6 0) r に化けます(それ以外はmod=0/1と同じ)。これはどういうことかというと、パケットの中に文字列をおかずに、レジスタで指定したところに文字列を置けるのです。これが「モード変更間接」です。 |
| + | -なんかもう疲れてきたので説明は適当になってきていますが(すみません)、このような(!6 504)の自動挿入は文字列を指定するような場所にはすべて設定されていて、たとえばfopenでファイル名指定するときなんかも、パケットの中にファイル名を埋め込むもよし、ポインタで指定するもよしの、何でもござれです。 |
| | | |
| * こめんと欄 | | * こめんと欄 |
| + | - 書いてて思ったけど、最後まで興味を持って読める人はまずいないだろうな・・・しくしく。まあCで使う分には気にしなくていい話だし・・・。 -- [[K]] &new{2008-12-23 (火) 23:01:36}; |
| + | |
| #comment | | #comment |