上に戻る
5.とりあえずディスアセ出力を眺めてみる
概要
 前回、ディスアセンブルしてみましたが、見方がわからないと意味がありません。ってことで書いていこうかと……
 もちろん、ファミコン限定な話です。
……と言っても……
 体系的?にやっていくほど私が詳しくないですので^^適当につまみながらやってみます。
 これは、一見して破れかぶれなやり方に見えますが、実は深い考えが……あったりせず、本当にあとの展開を考えていないだけです^^
ディスアセンブル結果をみる
 前回作られたoutput.txtを、『メモ帳』等のテキストエディタで開きます。
 『メモ帳』を出しましたが、それより、キーワードを着色して表示してくれるテキストエディタを利用したほうがいいです。
 コメント:
  『キーワードを着色して表示してくれるテキストエディタ』で、設定を以下のようにすると良いです;
  ・タブの幅:8
  ・『;』以後をコメント扱い
  ・『db』を、着色(汚らしい色で^^)
  ・『jsr,rts,rti,jmp』を別の色で着色
  ・『bmi,bpl,beq,bne,bcc,bcs,bvc,bvs』をまた別の色で着色
 ってことで、そういうテキストエディタで開いた例。(もちろんこれはμブロックの逆アセ結果^^)
ディスアセンブル結果
 ちょっと注を入れて…… ディスアセンブル結果+注
 (1)は、その機械語のあるアドレスです。
 何故か、8000から始まっています。とりあえずは気にしないでください。
 あと、もしC000から始まっていたら、ディスアセンブルをミスしているかもしれません。
 コメント:
 後述しますが、このアドレスは『ファミコン上のアドレス』です。とりあえず
 『ファミコンのアドレス』=『プログラムROMの先頭からの位置』+8000h=『ROMイメージのアドレス』+7FF0h
 となります。
 (2)は、機械語コードです。バイナリエディタでみるものと数字の順番は同じです。
 (3)が(2)に対応するアセンブリコードです。こいつが見たくてディスアセンブルしたのです……
 (4)は、ディスアセンブラが付けてくれたコメントなので、深い意味はありません。が、調査するときにとても役に立ちます。
コンピュータの仕事?(1)
 仕事とか書いてみたけど、自分は勉強したわけではないので、以下はあくまでも『自分なりのコンピュータ感』です。あんま参考にしないほうがいいかも^^
 コンピュータの仕事は、『メモリに並んでいるデータ』に『対応している命令』を順番に実行していくことなんじゃないかなぁと思っています。
 さっきの逆アセ結果をみてみると……
ディスアセンブル結果
 アドレス8000のメモリ(※ROM……リードオンリーなメモリなのでメモリの一種)の値は、78です。
 で、この値78hは、ファミコンのCPU『6502』(のカスタム?)において、seiという命令が割り当てられています。
 よって、8000hがCPUに『実行』されると、CPUはseiという命令を行います。(seiがなんなのかはおいておいて)
 それが終わると、続けて、CPUは、次のアドレスである8001hを『実行』します。
 8001hは、D8hです。これには、cldという命令が割り当てられています。
 cldが実行された後、続いて、CPUは8002hを『実行』します。
 8002hは、ldxという命令なのですが、この場合は、1バイトの属性を取ります。
(ldxでも何バイト属性を取るか数パターンあるんですが……)
 よって、8002hは、続く8003hと一体となって(?)『実行』されます。
 それが終わると、CPUは、8004hを実行します……以下繰り返し。
 こんな風に、順番に、メモリ上のデータに割り当てられた仕事を次々こなしていくのが、コンピュータの仕事の一つなんじゃないでしょうか……いや、全然知らんのだけどね^^ハードウェアは1分も勉強したことないので……

 んで、まぁ、この内容自体は、第2回でもやったことですが、CPUの命令が割り当てられた数字……今の例なら、cldという命令が割り当てられたD8hのことを『オペコード』というんじゃないかなぁと思ってます
 また、『オペコード』のとる属性……上の例では8003hのFFのことを命令ldxの『オペランド』というんだと思います
コンピュータの仕事?(2)
 じゃ、どんな『命令』があるんでしょ?あくまでも、私の『自分なりのコンピュータ感』での話ですよ^^
 例によってハードウェアは全然わからんのですが、コンピュータの重要な仕事に、『値のやり取り』が挙げられると思います。
 普通のコンピュータには、記憶領域があるはずです。(大昔の話になると……どうなんだろう?)
 いま、記憶領域Aと、記憶領域Bがあったとして……
 たとえば、Bの内容をAにコピーする……図示すると……
 A←B
 となるでしょうか……これは、『値のやり取り』です。
 あるいは、Aに値10を入れる……
 A←#$10
 も、『値のやり取り』になると思います……(突然現れた『#$』ってなんやねんってのはおいておいて……)
 AとBを足して、結果をAに入れる……
 A←A+B
 も、『値のやり取り』ってことにしておきます。
ファミコンの記憶領域
 とりあえず、
 『aという場所』
 『xという場所』
 『yという場所』
 それから、
 RAM上$0000〜$07FFの800h箇所
 に、それぞれ1バイトずつ読み書き可能な記憶領域があるみたいです。
 本当はそのほかにも、VRAMとか、カートリッジによっては拡張RAMとか、ありますが……
lda,ldx,ldy(1)
 『aという場所』に値を読み出す(ロードする)為に、ldaという命令を使います。たぶん、LoaD Aの略だと思います。
 aに、値10hを読み込むとします。図示すると……
 a←#$10
 でしょうか……(『#$』がなんなのかはおいておいて) で、アセンブリでは、これを、
 lda #$10   (機械語 A9 10)
 と書きます。機械語の、2バイト目の10が値10hに対応しています。
 同様に、x,yに値を読み出す為に、ldx,ldyを用います。LoaD Aと同様な略だと思います。
 ldx #$20   (機械語 A2 20)
 ldy #$30   (機械語 A0 30)
 と言った感じです。
sta,stx,sty
 上記のように、RAM上$0000〜$07FFに、データを格納可能です。
 データを書き込む(ストアする)ためには、sta命令を使います。STore Aの略だと思います。
 lda #$10   (機械語 A9 10)
 sta $0123   (機械語 8D 23 01)
 と書くと、1行目でaに10hがロードされ、2行目で、RAM$0123にその10hが書き込まれます。
 コメント:
 機械語を見ると、明らかに異様に見えます。2行目の、3バイトの命令の後ろ2つが『$0123』を表しているのですが、何故か、その順番が『23』→『01』と、なっています。
 これには、ハードウェア的な理由があったりするらしいです。このように、下から書いていく方法を『リトルエンディアン』といったりするらしいです。逆に、人間にとって自然に見える、上から順に書いていく方法を『ビッグエンディアン』というみたいです。
 計算をする上では、『リトルエンディアン』の方が分があるんじゃないかなぁと思ってます。人間も、大きな桁数の計算をする時って、下の桁から計算するでしょ?繰り上がりとかがラクに出来るんじゃないかと……
 ……ともあれ、このように、aを、オペランドに書き込む命令が、staです。
 同様に、x,yをオペランドに書き込む命令が、stx,styです。
lda,ldx,ldy(2)
 さっきは、『直な』値をロードしましたが、RAM$0000〜$07FFからロードしてみます。
 $0123には、10hが書き込まれているとして……
 lda $0123   (機械語 AD 23 01)
 と書くと、その10hが、aにロードされます。同様に、
 ldx $0123   (機械語 AE 23 01)
 ldy $0123   (機械語 AC 23 01)
 で、x,yに$0123の値をロードします。

 さっきのと、今のとを……
 lda #$10   (機械語 A9 10)
 lda $0123   (機械語 AD 23 01)
 と、比較してみるとわかるのですが、同じ命令ldaであっても……
・『直な』値と、RAMアドレスを区別するために『#』が必要だった
・対象によって、機械語の1バイト目からして違う
 です。ldaやldx,ldyの後ろには、他にもさまざまなものがついたりします。
 例えば、ldaの場合……
 lda #$10   (機械語 A9 10)
 lda $12    (機械語 A5 12)
 lda $0123   (機械語 AD 23 01)
 lda $12,x   (機械語 B5 12)
 lda $0123,x  (機械語 BD 23 01)
 lda $0123,y  (機械語 B9 23 01)
 lda ($12,x)  (機械語 A1 12)
 lda ($12),y  (機械語 B1 12)
 これだけパターンがあるそうです。
 後半の、xやyが現れるものは、xやyを用いて、指定されたアドレスから、いくらかずれたアドレスを扱ったりするものです。うまく使うと一定範囲のメモリをまとめて扱うことが出来ます。
 一個ずつ順番に説明すべきなのでしょうが、省略します^^
 ちなみに、ファミコンのCPU『6502』では、『RAM←RAM』の転送命令がありません……たぶん。
 したがって、それをやりたいときは……(以下の例は『$0300←$0200』)
 lda $0200
 sta $0300
 といったように、いったん、aやxやyを介入させる必要があります。

 あと、『値をやり取り』するには、足し算とかの演算命令がありますが、とりあえず置いておきます。
変な?ところから読み込む
 以上で、a,x,y,$0000〜$07FF間のデータやり取りを、コツコツと(1つずつ)はできるのですが、以下のようなコードはどうでしょう?
 lda $8000
 RAMは、$0000〜$07FFまでなので、なんかおかしなことが起きそうです。C言語的にはセグメンテーションフォルトしそうです。
 実際にはそんなことは起きません。
 ファミコンとか、少なくとも昔のコンシューマ機にはメモリマップというものがあります。
 (今のコンシューマとか、普通のコンピュータはどうなっているんだろう?)
 これは、ファミコンの扱えるアドレス$0000〜$FFFFまでに、イロイロ割り当てたものです。
(……滅茶苦茶な説明すみません……)
 例えば、RAMは、大きさ800hバイトですが、それが、$0000〜$07FFに割り当てられていたため、この辺からロード(ldaとか)すれば、それはRAMを読み込むことになるし、ストア(staとか)すれば、RAMに書き込むことになるわけです。
 んで、ものすごーく大雑把に言うと……
$0000〜$07FF RAM
$2000付近   グラフィック関連のもの
$4000付近   サウンド関連のものとか
$6000〜$7FFF (ちなみに、カートリッジ内に込められた拡張RAMはこの辺に当てられる)
$8000〜$FFFF プログラムROM
 と、マップされているそうです。
 さて、$8000〜は、プログラムROMが割り当てられているのですが、実は、冒頭での『なぜか8000から始まるアドレス』はココに起因しています。
 32Kバイト(8000hバイト)のプログラムROMなら、そのまま、$8000〜$FFFFに当てられます。
 16Kバイト(4000hバイト)のプログラムROMですと、$8000〜$BFFFと、$C000〜$FFFFに同じものが当てられるようです(カートリッジによって異なる?)
 じゃあ、32Kバイトより大きいプログラムだとどうなるの?って話ですが……まぁ、単純にはいかなそうなのがわかるかと思います。ディスアセンブルが容易でないのもこの辺に起因してます。
 先ほどの、
 lda $8000
 ですが、実はこれはプログラムROMデータ(先頭のデータ)を読み出しているのです。
 この場合は、いつも同じ値が読み出されるので面白みがなさそうですが……
 レジスタ?
 レジスタとは……何だろ(爆)
 定義は知らんのですが、CPUの内部(すぐ側?)にある、低容量だが高速な記憶領域の事と思います。
 ファミコンのCPUにもいくつかレジスタがあります。
 その中で、汎用的に……つまり自由に使ってよいものが、さっきから使っている、a,x,yの3つのレジスタです。
 そのほかにも、演算結果の一部やCPUの状態を格納するpレジスタとかありますが、まぁ省略します。
 ……んで、同じレジスタという名前がつくものに、『I/Oレジスタ』ってものがあるみたいです。
 上で……
$2000付近   グラフィック関連のもの
$4000付近   サウンド関連のものとか
 とか書きましたが、$2000とか$2001とかに、適当に値をストアすると、グラフィックを管理することが出来ます。
 $2000辺りは、ファミコンの『CPU』と『画像処理ユニット』とを『連絡するためのモノ』がマップされているようです。
 その、『連絡のためのモノ』のことを『I/Oレジスタ』って言うみたいです。IOはもちろん、Input/Outputのことですよね?
 $4000付近にしても同じ感じです。また、『I/Oレジスタ』の中には、読み込みをかける(ロードする)ことが出来るものもあります。I/Oですもんね。
 自分には、何故これにレジスタという名前がつくのか理解できないのですが……きっと私がレジスタの意味を取り違えているのだと思いますが。
 どうでもいいけど、昔のゲーム機でよく『*ビット機』とか言う言い方しましたが、これってCPUのレジスタのサイズの事を言っているんでしょうか……?
コンピュータの仕事?(3)
 コンピュータの最後の?仕事として、命令によっては『実行している位置』を変えることではないでしょうか……
 だって、毎回同じ風に上から下にプログラムを実行していてもしょうがないですし。
 『実行している位置』を変えることを、ジャンプと……たぶん広く……言われています。
 『強制的にジャンプ』のほかに、『条件によりジャンプ』があります。
jmp
 わかりやすいのが、jmp命令です。JuMPの略でしょう。これは、『強制的にジャンプ』します。
 意味のないコードになってしまいますが……
JUMP_LABEL:
lda #$00
jmp JUMP_LABEL
 と、アセンブリコードを書くと、永遠にlda #$00し続けます^^(実は、『割り込み』ってものがあるから永遠ではない)
問題はラベル
 上のコードの、『JUMP_LABEL:』ってなんでしょ?
 これは、プログラミングするときに必要なラベルです。『その場所のアドレス』に名前をつけます。
 この場合は、『(直後の)lda #$00の機械語コードのアドレス』に『JUMP_LABEL:』という名前が付けられました。
 そして、jmp命令のオペランドに『JUMP_LABEL:』となっています。『JUMP_LABEL:』という名前を付けられた場所のアドレスにジャンプしろ……ということですね。
 このコードが、アセンブルされるとき、『lda #$00』に相当する機械語コードが『どこか』に配置されます。そして、jmpのオペランドには、その、『どこか』となるように、自動的にアセンブラにより決めてくれます。
 さて、逆アセンブルの話になります。ラベルって言うのは、プログラミングするときに必要なだけですので、機械語コードになったら消滅します。
 よって、『さっきのコードをアセンブルしたもの』を逆アセンブルすると、一例として……
8100 : lda #$00   (機械語 A9 00)
8102 : jmp $8100   (機械語 4C 00 81)
(lda #$00が、偶然$8100に配置されたとして……)
 となります。
 lda #$00が、偶然$8100に振られたことにより、ジャンプするアドレスが$8100になっています。
 見方によっては、全ての命令に、数値のラベルが付けられたと見ることもできます。つーか、それが正しい?
条件分岐
 『条件によりジャンプ』する命令があります。『分岐』ですな。
 こーゆーことを、ブレンチ(branch)する……というんじゃないでしょうか。
 あまり深入りしませんが、以下のような8個の分岐命令があるそうです。
 bmi ⇔ bpl
 beq ⇔ bne
 bcc ⇔ bcs
 bvc ⇔ bvs
 最初のbはbranchのbだと思います。残りの2文字は、たぶん……
 マイナス            ⇔ プラス
 イコール            ⇔ ノットイコール
 キャリーフラグクリア      ⇔ キャリーフラグセット
 オーバーフローフラグクリア ⇔ オーバーフローフラグセット  なんでVがオーバーフローなんだろ?oVer flow?
 を表します。それぞれ、⇔を挟んで逆の条件になっていることが、なんとなく読み取れます。
 どの分岐も、上でちょこっとだけ出した、CPUの状態や演算結果の一部を格納する、pレジスタの特定のビットを見て分岐します。ま、細かいことは置いておきます。
ブレンチのオペランド
 アセンブリでは、以下のように書くと……
 lda $0123
 beq BRANCH_LABEL
(処理A)
BRANCH_LABEL:
(処理B)
 beq BRANCH_LABELの列で、『ある条件』(それが何なのかはおいておいて)により、処理Bに処理が移ります。
 その条件を満たさないときは、処理Aが実行されます。(その後、処理Bが実行される)
 このコードをアセンブルした一例が、以下です。
 AD 23 01(lda $0123に相当)
 F0 33  (beq BRANCH_LABELに相当)
 beq命令のオペランドの33hが謎です。一体どこから沸いてきたのでしょうか?
 実は、これは、処理Aの機械語コードのサイズです。つまり、処理Aのコードサイズ分だけジャンプすることで、処理Aを飛ばし、処理Bに処理が移るわけです。
 そんなわけで、処理A次第でこの値はまるっきり変わります。今回33にした理由は全くないです^^
 以下のようなコードだとして(**は適当な機械語コード)
 AD 23 01
 F0 33
 ** **
 **
 ** **
 .....
 もし、分岐条件が成立すると、『**の位置から、branchのオペランドのぶん進めた位置』に処理が移ります。
 ちなみに、オペランドは、負数を指定することが可能です。
 F0 FE
 という機械語コードですと、条件が一致したとき、永久にこの命令を実行し続けます^^(FEは-2です)
 ところで、−128バイトより少なくや、127バイト以上ジャンプするにはどうしたら良いでしょう?
 たぶん、逆条件とjmpを使って回避するのかと思われます。知らんけど^^
オチ?
 とりあえず……
・lda,ldx,ldy命令で、a,x,yレジスタにロードできる
・sta,stx,sty命令で、a,x,yレジスタからストアできる
・$0000〜$07FFにはRAMがマップされているので、そこにロードしたりストアしたりすると、RAMに読み書きできる
・$8000〜$FFFFに、ROMがマップされる。
・ジャンプしたり、ブレンチする命令がある
 と、言ったところでしょうか。
 次回は……どうなるんだろ(汗)
(16)2007年1月28日 プレさ兵衛
(288)2014年12月29日 改:誤植「分枝」→「分岐」の修正
inserted by FC2 system