↑この話の続き
ICM製Ethernet + SCSIちゃんぽんボードのIF-2771ETを使って、Ethernet接続する話の続きです。
とりあえず、それほど開発環境がそろっているわけでもないので、まずはそこからなのですが、さすがにこの文明開化の世の中に、MS-DOS(しかも似非)のプログラムをしようなどという酔狂な人はほとんどいないみたいで、とにかく情報がありません。
とりあえず、フリーのディスアセンブラをいくつかダウンロードしてみましたが、どれも割り込み駆動するプログラムはきちんと追えませんでした。
まあ、スタートアップから追っていっても割込みルーチンにはたどり着かないですから、仕方ないかもしれませんね。
そんなわけで、SYMDEBを使って、手作業でコードを追って、LSIC-C86に付属のR86アセンブラのソースコードに落としていきました。
プログラムサイズは4,836バイトです。
手作業でコードを追う気になるギリギリのラインですね。
1. 最適化コード
ところで、8086のインストラクションコードのなかで、以下のようなコードがあります。
AND BX,3
普通のコードっぽく見えますが、実はこのコード、R86は元通りにアセンブルしてくれませんでした。
元のコードは、
83 E3 03
となっていて、これをSYMDEBで見ると、
AND BX,03
と表示されるわけですが、これを信じて上記のコードをアセンブルすると、その出力結果は
81 E3 03 00
となってしまいます。
まあ、実行結果は同じなので、バグというわけではないのですが、1バイト大きくなります。
どういうことかというと、元のコードは、
AND reg16, imm8
で、R86が出力したコードは、
AND reg16, imm16
なのです。
R86はメモリや即値に型を持たないので、基本的にはレジスタの指定によって、メモリや即値の型を推定します。オペランドがメモリと即値だった場合は、メモリに型指定をします。
つまり、オペランド1とオペランド2のサイズが違うことを想定していません。
即値の値を見て、8ビット範囲内だったらimm8のコードを生成してほしいという考え方もあるでしょうが(OPTASMとかはそうなっています。)、このコードは、imm8で指定された即値を符号拡張するので、なかなか見つけられないバグの原因になりがちです。(例えば、AXレジスタのbit7だけ残してマスクする場合を考えてみてください。)
1バイトをケチるために、わざわざこんなことをしているあたり、当時のメモリの貴重さというものが伺えますよね。
で、結局、R86にはこのコードを生成するアセンブリコードが無いようなので、そのまま
DB 83h, E3h, 03h ;AND BX,3
と書いておきました。
2. COM形式実行ファイル
今回の解析対象となるパケットドライバIF27PD.COMは、COM形式実行ファイルです。
COM形式実行ファイルは、コード・データが、すべて1セグメント(64kバイト)内に収まるように作成されています。
実行時は、実行メモリ上に100hバイトのヘッダが付きますので、一般的にはセグメントの先頭に
ORG 100h
という疑似命令を入れて、コードやデータのアドレスが100hバイトずれて配置されることをアセンブラに知らせます。
しかし、困ったことに、R86にはこのORG疑似命令がありません。
リンカ(LLD)には、COM形式実行ファイルを作成するオプションがありますが、ORG 100hが指定されていないとエラーを出す仕様になっています。
その疑似命令、無いんですけど?
どういうことなんでしょうね?
基本的にR86は、LSI-C86のためのアセンブラで、ORG 100hは、C言語のスタートアップコード内に記述されているから不要という意味なのでしょうか?
R86だけでCOM形式の実行ファイルを記述することはできないの?
この答えは、まだわかっていません。
とりあえず、ORG 100hだけ記述したファイルをTASMでアセンブルして、そのオブジェクトをリンクしていますが、どなたか正解をご存じの方、教えてください。
3. セグメント配置
今回、解析しているのは、メモリに常駐して割込み処理で駆動するドライバです。
こういった常駐プログラムは、できるだけ常駐量を小さくするように工夫されています。
常駐する部分と、そうでない部分は明確に分けて、常駐完了時には非常駐部のメモリは解放します。
今回のIF27PD.COMの場合、メモリの配置は、
プロセスヘッダ(100hバイト)
jmp (非常駐部へ)
常駐部(データ、コード)
非常駐部(データ、コード)
のようになっています。
非常駐部の処理が終了すると非常駐部のデータやコードが配置されていたメモリは解放されます。
上記のように、常駐プログラムでは、必然的に、データ部とコード部が交互に現れる構造になります。
しかし、何も考えずにリンクすると、リンカのLLDは、コード部はコード部で、データ部はデータ部で、それぞれまとめてしまいます。
通常のプログラムでは特に問題ないのでしょうが、常駐プログラムの場合、勝手にそんなことをされては困ります。
最初は、
「だったらセグメントなんて分けずに、全部コードセグメント内に記述すればいいじゃん。」
と思って、セグメントを分けずに作っていたのですが、データをアクセスする全てのコードにCS:プリフィックスがついてしまうという残念な結果に…。
やっぱり、まじめにセグメントを配置をする必要があるようです。
R86では、セグメント定義時に、何も指定せずに
TEXT CSEG
とだけ記述すると、暗黙のクラス名'CODE'が指定されることになっています。
このクラス名が同じセグメントは、リンク時にまとめられてしまうということのようです。
まとめられないようにするためには、
TEXT1 CSEG 'CODE1'
のように、明示的にクラス名を指定すればいいということのようです。
でも、これだけではダメです。
先ほどのORG問題があります。書いてあるコードがメモリに配置されるときは、100hずれて配置されるので、ORG 100hが書かれたセグメントと連続していることを示さなければなりません。
これは、
IF27GROUP GROUP TEXT1,DATA1,TEXT2,DATA2
のようにすればいいようです。
書いてしまえばそれだけなんですけど、マニュアル読んでも、クラスとかグループっていうものの意味は最初から分かっている前提で、それをどのように記述するかだけが書いてあって、どうすれば自分が思ったように配置されるのか全然わかりません。
カットトライで色々コードを書いて調べていたので、これだけのことがわかるのに、けっこう時間がかかりました…。
(いや、これ、20数年前なら知っていた内容だったんでしょうけど、もう完全に忘れてしまって…)
まとめ
そんなこんなで、3日がかりぐらいでパケットドライバをすべてアセンブリコードに落とすことができました。
R86なんて使わないで、TASMを使えばよかったと、何度思ったことか…。
これで完了じゃなくて、スタート地点。
ドライバがハングアップする原因を調べるための前処理なんですよね。
(最初の目的を忘れてしまいそう…。)
もう、今日は疲れたので寝ます。
おやすみなさい。
↓続きはこちら
