現在地

Z180の不正命令TRAPを捕まえよう


Z8S180の前にやってみたいことがあります。

Z180には未定義な命令を実行しようとした時に発生するTRAP割り込みというものがあります。これを捕まえてRST 38Hによるブレークのようにアドレスやレジスタが表示できれば便利でしょう。

ということで調べ始めたのですが、思った以上にハードルが高く簡単ではありませんでした。

まず未定義な命令を実行しようとした時に何が起こるかです。

  1. ITC(Interrupt TRAP Control)レジスタのTRAPビットが1になる
  2. PCの値がスタックに積まれる
  3. 論理アドレス0000Hにジャンプする

えっ、0000Hってリセットと同じではないですか。MMUで論理アドレス0000Hに異なる物理アドレスを設定していれば問題ありませんが、ROM, RAMともに16kBしかないSBCZ80でこのためだけにMMUを有効にするのはかなり面倒です。

救済策も用意されていて、1.のTRAPビットはリセットで0になるので0000HからのルーチンでこれをチェックすればリセットなのかTRAPなのかは判別できるようになっています。

次に問題となったのはレジスタの保存です。TRAP時にレジスタを表示するためにはすべてのレジスタ内容を保存しておかなくてはなりません。リセットかTRAPかの判別の過程でFレジスタは壊れてしまうのでその前に保存することが必要ですが、そのためにはスタックを経由しなくてはなりません。しかし判別前ではリセットの可能性もありその場合はSPがまだ設定されていません。SPを再設定するならSP自体も保存しておかないとTRAPだった場合に元のスタックに積まれていたはずのPCが取り出せなくなってしまいます。

さらにUniversal Monitor固有の難しさもあります。Z80, HD64180, Z280を同一バイナリで対応しようとしているため、HD64180(Z180)系以外の場合はTRAPビットのチェックをスキップしなくてはなりません。通常CPU判別ルーチンの結果がメモリに書かれていてそれを参照すればよいのですが、リセットだった場合はこれが利用できません。Z180系かどうかだけでも独自に判別の必要があります。

ここまでなんとか解決のめどがたったので実際にコードを書いて試してみたのですが思ったように動作しません。実はもう一つ落とし穴がありました。

前回書いたように内蔵I/O空間のアドレスを初期値の00H~3FHから0C0H~0FFHに移動しています。リセットされていればこのアドレスは00H~3FHに戻っていますが、TRAPの場合はそのままなので0C0H~0FFHのままです。TRAPビットのあるITCレジスタもこの中なので、判別しないとアドレスがわからず、アドレスがわからないと判別できないという鶏と卵のような状態なのでした。

両方の空間で読んでみれば何とかならないか、さらにアドレス設定のICRレジスタも両方の空間で読んでみて矛盾のない方を採用しようかとか考えてみましたが、どうしても確実性に難があります。

ここでかなり悩んだのですが、意外に単純な形で解決しました。

核の部分のコードを見ていきます。0000H番地から割り込みを禁止しただけでここに飛んできます。

      78/     100 :                     CSTART:
      79/     100 :                     	;; Identify HD64180/Z180 TRAP
      80/     100 : =>TRUE              	IF USE_180TRAP
      81/     100 :                     	SAVE
      82/     100 :                     	CPU	Z180
      83/     100 : ED 73 00 BF         	LD	(INBUF),SP	; Save SP
      84/     104 : 31 10 BF            	LD	SP,INBUF+BUFLEN	; Temporary stack on INBUF area
      85/     107 : F5                  	PUSH	AF
      86/     108 : ED 5F               	LD	A,R
      87/     10A : 32 02 BF            	LD	(INBUF+2),A	; Save R
      88/     10D : C5                  	PUSH	BC
      89/     10E : 01 FF 00            	LD	BC,00FFH
      90/     111 : ED 4C               	MLT	BC
      91/     113 : 78                  	LD	A,B
      92/     114 : B1                  	OR	C
      93/     115 : 20 1A               	JR	NZ,CS0		; Not HD64180/Z180
      94/     117 :                     	;; HD64180/Z180
      95/     117 : 3E C0               	LD	A,IOBASE
      96/     119 : ED 39 3F            	OUT0	(3FH),A
      97/     11C : ED 38 F4            	IN0	A,(IOBASE+34H)	; ITC register
      98/     11F : 32 03 BF            	LD	(INBUF+3),A
      99/     122 : E6 80               	AND	80H
     100/     124 : 28 0B               	JR	Z,CS0		; Not TRAP
     101/     126 : 3A 03 BF            	LD	A,(INBUF+3)
     102/     129 : E6 7F               	AND	7FH
     103/     12B : ED 39 F4            	OUT0	(IOBASE+34H),A	; Reset TRAP bit
     104/     12E : C3 65 09            	JP	TRAPH
     105/     131 :                     CS0:
     106/     131 : ALL                 	RESTORE
     107/     131 : [80]                	ENDIF

83行は現在のSPをモニタの行入力バッファ(最低16バイトはある)の先頭に保存します。この命令はFレジスタを変化させません。

84行で仮のスタック領域を行入力バッファの後半に設定します。この命令もFレジスタを変化させません。

85行でAFレジスタペアを仮スタックに退避します。これでA,Fレジスタを壊せるようになったのでいろいろなことができるようになります。

86,87行でRレジスタを保存します。Rレジスタは命令実行のたびにインクリメントされるので早めに保存しておかないと後で補正する時に面倒なことになってしまいます。

88~93行ではHD64180/Z180系かどうかの判別を行ない、違っていれば通常のリセット処理に戻します。

95,96行で内蔵I/O空間を0C0H~0FFHに移動させます。どっちだったか判別するのは諦めて強制的に0C0H~0FFHにしてしまえば良かったのです。

97,98行でITCレジスタを読み保存しておきます。

99,100行ではビット7のTRAPビットをチェックし、0だった場合は通常のリセット処理に戻します。SPはこの後で再初期化されるので仮スタックに積んだAF,BCはそのままでOKです。

101~103行ではITCレジスタのTRAPビットをクリアしておきます。リセットでもTRAPでもなく0000H番地に飛んでしまった時にTRAPルーチンに入らないためです。

あとは仮保存したレジスタ・残りのレジスタをRST 38Hの場合と同じように保存するだけです。

    1561/     966 :                     ;;; HD64180/Z180 TRAP Handler
    1562/     966 :                     
    1563/     966 : =>TRUE              	IF USE_180TRAP
    1564/     966 :                     TRAPH:
    1565/     966 : =>TRUE              	IF USE_REGCMD
    1566/     966 :                     
    1567/     966 : 3A 02 BF            	LD	A,(INBUF+2)
    1568/     969 : 32 37 BF            	LD	(REGR),A
    1569/     96C : ED 57               	LD	A,I
    1570/     96E : 32 36 BF            	LD	(REGI),A
    1571/     971 : 22 24 BF            	LD	(REGHL),HL
    1572/     974 : ED 53 22 BF         	LD	(REGDE),DE
    1573/     978 : E1                  	POP	HL
    1574/     979 : 22 20 BF            	LD	(REGBC),HL
    1575/     97C : E1                  	POP	HL
    1576/     97D : 22 1E BF            	LD	(REGAF),HL
    1577/     980 : 08                  	EX	AF,AF'
    1578/     981 : F5                  	PUSH	AF
    1579/     982 : D9                  	EXX
    1580/     983 : 22 2C BF            	LD	(REGHLX),HL
    1581/     986 : ED 53 2A BF         	LD	(REGDEX),DE
    1582/     98A : ED 43 28 BF         	LD	(REGBCX),BC
    1583/     98E : E1                  	POP	HL
    1584/     98F : 22 26 BF            	LD	(REGAFX),HL
    1585/     992 : DD 22 2E BF         	LD	(REGIX),IX
    1586/     996 : FD 22 30 BF         	LD	(REGIY),IY
    1587/     99A :                     
    1588/     99A : ED 7B 00 BF         	LD	SP,(INBUF)	; Recover SP
    1589/     99E : E1                  	POP	HL
    1590/     99F : 06 0A               	LD	B,10		; for R register adjustment
    1591/     9A1 : 3A 03 BF            	LD	A,(INBUF+3)	; ITC
    1592/     9A4 : E6 40               	AND	40H
    1593/     9A6 : 28 02               	JR	Z,TH0
    1594/     9A8 : 2B                  	DEC	HL
    1595/     9A9 : 04                  	INC	B
    1596/     9AA :                     TH0:
    1597/     9AA : 2B                  	DEC	HL
    1598/     9AB : 22 34 BF            	LD	(REGPC),HL
    1599/     9AE : ED 73 32 BF         	LD	(REGSP),SP
    1600/     9B2 :                     
    1601/     9B2 :                     	;; R register adjustment
    1602/     9B2 : 3A 37 BF            	LD	A,(REGR)
    1603/     9B5 : 90                  	SUB	B
    1604/     9B6 : E6 7F               	AND	7FH
    1605/     9B8 : 47                  	LD	B,A
    1606/     9B9 : 3A 37 BF            	LD	A,(REGR)
    1607/     9BC : E6 80               	AND	80H
    1608/     9BE : B0                  	OR	B
    1609/     9BF : 32 37 BF            	LD	(REGR),A
    1610/     9C2 :                     
    1611/     9C2 : 21 65 0A            	LD	HL,TRAPMSG
    1612/     9C5 : CD EE 07            	CALL	STROUT
    1613/     9C8 : CD BE 07            	CALL	RDUMP
    1614/     9CB : C3 09 02            	JP	WSTART
    1615/     9CE :                     
    1616/     9CE : =>FALSE             	ELSE			; USE_REGCMD
    1617/     9CE :                     
    1618/     9CE :                     	LD	SP,(INBUF)	; Recover SP
    1619/     9CE :                     	POP	HL		; Drop return address
    1620/     9CE :                     	
    1621/     9CE :                     	LD	HL,TRAPMSG
    1622/     9CE :                     	CALL	STROUT
    1623/     9CE :                     	JP	WSTART
    1624/     9CE :                     	
    1625/     9CE : [1565]              	ENDIF			; USE_REGCMD
    1626/     9CE :                     
    1627/     9CE : [1563]              	ENDIF			; USE_180TRAP

大半はレジスタの値をメモリにコピーしているだけなので説明は省略します。RST 38Hのハンドラとよく似ているのでいずれ共通化できないか考えていきたいですね。

1588,1589でSPを復帰させ、TRAPによって保存されたPCを取り出します。このPCは不正命令を検出した時のPCで、命令の先頭アドレスから1あるいは2バイト進んでしまっているのでその補正を行ないます。

その補正が1590~1597行です。ITCレジスタのビット6を見れば進み量がわかるのでその分PCを戻します。同時にRレジスタの補正量も求めています。

Rレジスタも1あるいは2進んでしまっているので補正が必要です。こちらはさらにTRAP発生からRレジスタを退避するまでの間にも進んでしまっているので、その分も合わせて10~11という補正量を求めておきます。

1602~1609行で実際の補正を行ないます。Rレジスタはビット6からビット7への繰上りがないのでちょっと複雑になっていますね。

このRレジスタ、Z80ではリフレッシュしているアドレスですが、HD64180/Z180では互換性のための単なる命令カウンタで実際のリフレッシュアドレスは他にあります(ソフトウェアからアクセスできません)。

参考文献・関連図書: 
"Z8018x Family MPU User Manual", UM005003-0703, Zilog.

コメントを追加

Plain text

  • HTMLタグは利用できません。
  • ウェブページアドレスとメールアドレスは、自動的にハイパーリンクに変換されます。
  • 行と段落は自動的に折り返されます。
※ コメントは原則公開です。個別のご相談などは「ご意見・ご要望」からお願いします。