最初に白状してしまうと、この逆アセンブラはキレイに作れた感がまるでない(^^;;
むしろ力技で作りきったというか。
困ったことにもう一度作ったとしてもエレガントに作れる気がしない(-_-;
それを踏まえた上で、読み物として流してくれると助かります!!!←力説
最初に余談!
私は若い頃、ハンド逆アセンブルをしていた期間がかなり長い。さんざん逆アセンブラをしてから「これってパソコンで自動で出来ないのかな?」と考えた事があった。
BASICで逆アセンブル出来るプログラムを書いてみた。アルゴリズムも何もない、このコードが来たらこう表示するってだけのプログラムだった。ほんの数命令しか逆アセンブル出来なかったが、相対アドレスを計算しなくても済むように出来たのが嬉しかった!
ちょっとしたプログラムだったけど、すごい達成感があって「もしかして…このままマシン語をマスターしちゃう?!」とか自惚れたのを覚えている(^-^)
あと、逆アセンブラ=ディスアセンブラという事を知らず、世にたくさんある事も理解出来てなかった(^^;;
なんだ、それだったらI/Oにたくさん載ってるじゃん…と後になってから萎えまくったのは、若い時代のほろ苦い思い出w
センシティブな内容だぜ!
先日から私のツイートに「センシティブな内容が含まれている可能性があるため、このツイートに警告を表示しています」と出てしまっていて、Twitterアプリを使ってる方からはツイートが見えなくなってるみたい。
公序良俗に反する事なんぞ書いてないと思っているのですが、どうやら「エミュレータ」という単語に反応しているのではないか…と見解もいただきました。なるほど…それだとしたら引っかかるけど、解除してくれるのかな…これ(T-T)
いきなりアカウント削除されたらすみません(ありうるので怖い)。
逆アセンブラ
当初、「逆アセンブラとは」みたいな項目を書いていたけど、すっきり説明ができそうになかったので、ネット上にある優秀な文献に任せる事にした!←
ここでは逆アセンブラとはなんぞやが理解してる人に向けて話を進める!
進めるったら進める!(^-^)/
↑こんな感じのバイナリのプログラムをもう少し人間が分かりやすいように変換してやると…
↑こんな感じになる!
アセンブラを見たことがない人には「これでも全然わからんのですけど…」となると思うが、そこはホレ、入門書とか読んでみて理解を深めてもらいたい(^^;;
または「ふーん、そういうもんね」と流してもらえたら(^^)
エミュレータを作ろうとした場合、アセンブラ表記の解読は避けて通れない。
表示をどうするか!?
まず、最初にこだわりたいのは「どんなふうに表示するか」だ!
エミュレータ開発中はずっとずっと見る逆アセンブラだ、ここにこだわらなくてどーする!
場合によっては数万行ある逆アセンブラリストと格闘する事だってあるんだぞ(^^)/
というわけで、私はこだわりなかったのでX68000のSCD表記を真似してみる事にした(をぃ
うん、こだわりとか気にせず考えずに作っちゃってるね(-_-;;
ニーモニックに付くサイズ指定もあったりなかったり…未完成感がすごい。
これはこの先の開発を進めならが修正していく(moveaには.wと.lを付けたい、など)。
最初は、参考にしたもの、または使い慣れた表記に合わせてみるのが良いと思う。
私はX68000のSCDで慣れてしまっていたので、この表記が見やすい。
人によって違いはありそうなので、製作者のこだわりでどうぞ!(^^)
上の逆アセンブラ表示で、バイト列とニーモニックの間がどどーんと空いてる。
これは68000の命令長が最大で5ワード(10バイト)あるから、その分を空けてあるのだ。
最大の長い命令が来てもキレイに表示されるよ!
もうひとつ、レジスタの表示をどうするか。
私はこんな感じで表示させてみた。そこはかとなくSCDに似てるのは気のせい…かも?
ニーモニックの下に表示されるレジスタが、実行後のレジスタを表示している。
なので実行前のレジスタはニーモニックの上に表示されてるモノを見る必要がある。
これは過去にどんな経緯でマシン語人生を歩んできたかで表示する順番が変わりそう(^^;
[SUPER]というのは、X68000 SUPERを意識してるワケではなくて、MC68000の動作モード(スーパーバイザー | ユーザー)の表示だ(^o^)
逆アセンブラを呼び出す関数をどうするか!?
逆アセンブラを使う側としては「アドレスを渡したら、そこにある命令コードを何も考えずに逆アセンブルして表示してくれる」のが楽ちんで良い。
逆アセンブラ単体でテストする場合には、アドレスが更新していかないと都合が悪いけど「その命令の長さ」が分からないので、次の命令を指す事が出来ない。
というわけで、逆アセンブラ関数からの戻り値としては「今逆アセンブルした命令の長さ(バイト長)を返す」ようにしたら良さそうだ。
……意味通じた?(^^;;;
uint32_t pc = 0x00FF0000;
while(1){
pc +=逆アセンブラ関数(pc);
}
こんな感じにすれば永遠と逆アセンブルする事が可能だ!
命令デコード
逆アセンブラを作る上で、おそらく一番肝心な部分であるデコード。
ここがキレイに作れるかどうかで、その後の難易度がどどーんと変わってくる(T-T)
まずはありがちな単純デコード。
単純明快に命令の数分だけ関数を並べてしまうやり方だ。
void (*func[256])(unit32_t);
8080、Z80、SC61860、65816などはこんな感じでテーブルを用意した。
1980年代ならサイズ的に躊躇する方法だけど、現代のコンピュータであればメモリもCPUパワーも問題にはならないので、解決策のひとつだろう(^^)
ちなみにswitch〜caseを256個書いても、gcc(コンパイラ)がジャンプテーブルに直してくれる場合がある。それを見越した上で書いていくのもアリだ!
しかしMC68000でこの方法は難しい。
命令コードが2〜10バイトで最短長が2バイト。2バイトということは最低でも65536の組み合わせがある。テーブルジャンプなんぞ作ったら65536 * 8バイト(最近の64bitマシンだとアドレスは8バイトある)で256KB!テーブルだけでこのサイズいただけない(^^;
かといってswitch〜case文で65536を分岐する方法もさすがに避けたい(T-T)
もうちょっと違う方法を考えてみよう。
例えば……MOVE命令とそれ以外を見てみると、MOVEはbit15/14が必ず00になるらしい。しかし、15/14が00になる命令は他にもたくさんあるため、MOVE命令を先に判断してしまうと失敗しそうだ。
ORI to CCRなどは16bitの値が固定($003C)になっているため、単純な比較で調べる事ができる。
ということは、比較するビットとマスクを調べ、マスクのビット数が多い順に並べてやったら上手くいくのでは…(^-^)
こんなデータを用意してみた。
メモリから拾ってきた命令コードと1つめのデータをマスク、その結果と2つめのデータを比較して、同じならば関数へ飛ぶ…とすれば、目的が達成できそうだ。
問題は、命令を調べるごとにテーブルの最初から最後まで検索する可能性があるため、処理負荷が安定しない事だろうか…。
コード的にはこんな感じ?(実行は別の場所でやってる)
分岐先では各ビット情報を元に解析をしまくっている。
おそらくこの処理も重要なポイントなのだが、残念ながらこの部分は力技で作ってしまった。もうちょっと工夫すればよかった…(^^;;
表示をより簡単に!
表示が確定した内容を、確定したタイミングでどんどん表示していく方法もあるが、私は最後の最後にまとめて処理するようにしてる。
・アドレス情報
・バイナリ情報(2〜10バイト可変)
・オペランド1
・オペランド2
分岐先の各関数では上記の文字列を埋めるようにしておいて、最終的に良い感じに整形しながら表示するようにしている。
表示処理がたくさんあると、あとでフォーマットを変更しようとするとホントに大変!この辺りは何度も何度も作ってるうちに学習した(^^;;
開発したMC68000の逆アセンブラのプログラム行数を数えてみたら1105行だった。
力技で解決しなかったら、半分くらいで作れるかもしれない??(^^;;
エミュレータがある程度動くようになってきたら検討してみようとは思う(やる?)
-----
なんか…今回はめちゃくちゃ概念的な話になっていて、こんなんで逆アセンブラの理解が深まるだろうか…心配だ(T-T) ソースコードを合わせて出して行こうかと思うが、そうするとめっちゃプログラム語になっていきそうで躊躇してる。
そういう回があって良いのかもね!(^-^)
現在の進捗は、X68000のリセット時から3559ステップ実行、命令グループは17まで実装した。頑張ってる割に進まないな…ぐぬぬ(-_-;;
ではまた次回!(^-^)ノ