実装されたマイコンがオプション非対応品だった装置を復活させた話

# はじめに
ある装置メーカーから、「いつも通り製品を組み立てたのに、ウンともスーともいわない。はんだ不良もパターン不良もないと思う。見てほしい」と相談を受けました。
最終的に、CPU命令を偽装するFPGA基板を製作することになったので、共有します。
## 装置の概要
この装置はプレス機の制御に使われ、設定した角度になるとONしたりOFFしたりする装置です。
マイコンはH8S/2638が使われ、I<sup>2</sup>CでEEPROMにパラメータを書き込むため、I<sup>2</sup>C機能（オプション）つき品番を必要としています。
## 何が起きたか？
電源を投入しても、まったく動作しません。
装置としては相当な製造実績があるため設計不良は考えにくく、また製造した5台すべてが同じ現象でした。
# 現象の調査
なにがどこで動かないのかを調査しました。
幸いなことに、この装置はプログラムを外部EPROMに格納しており、アドレスピンをあたれば、動いているのか、またはどのアドレスで止まっているのかがわかりそうです。
## 必要な機器
- オシロスコープ
信号のレベルを確認するため、オシロスコープを用意しました。
一番高い周波数がマイコンのクロック・20MHzのため、秋月電子の以下のものを用意しました。
https://akizukidenshi.com/catalog/g/gM-01985/
- ロジックアナライザ
外部PROMのアドレス、データを確認するため、ロジックアナライザを用意しました。
同様に高い周波数は必要なく、秋月電子の以下のものを用意しました。
https://akizukidenshi.com/catalog/g/gM-04426/
## クロックが発振していることを確認
マイコンのクロックが動作していなければ動くはずがないので、クロック信号をオシロスコープで確認します。
波形の高さが動作電圧近辺で、周期が部品の周波数と合致していれば問題ありません。
## ROMアドレスの確認
マイコンが動作していることが確認できたので、つぎにアドレスをトレスします。PROMのアドレスピンにロジックアナライザを接続し、変化を観測します。
その結果、アドレス0x0000から始まり、特定のアドレスでループしていることがわかりました。
# 解析の準備
アドレスがループしている箇所で何が起きているかを解析します。
## アセンブラ環境を準備する
以下を参考に、H8の開発環境を準備しました。
1. https://monoist.itmedia.co.jp/mn/articles/0910/23/news102.html
1. http://www.kikaiken.org/lib/junk/h8dev-doc-linux/
1. https://qiita.com/meister68k/items/5168eca10afe2489da6f
1. http://exception.blog.shinobi.jp/iot/gnu%20toolchain%20for%20hitachi

今回の手順では、4で公開されているビルド済み実行ファイルを使用しました。
## ROMイメージを用意する
ROMライタを使って、EPROMの内容を吸い出します。HEX形式で保存します。
## 逆アセンブルする
以下のコマンドで、HEXファイルをアセンブリコードに変換します。
```
> h8300-hitachi-elf-objdump -D -b elf32-h8300 -m h8300 入力hexファイル.hex > dump出力.txt
```
# 解析方法
逆アセンブリしたファイルは以下のようになっています。
```
      f4:	7a 07 00 ff 	mov.l	#0xffc256,er7
      f8:	c2 56 
      fa:	5e 00 52 ee 	jsr	@0x52ee:24
      fe:	40 fe       	bra	.-2 (0xfe)
     100:	6a a8 00 ff 	mov.b	r0l,@0xffb000:32
     104:	b0 00 
     106:	01 00 6d f6 	mov.l	er6,@-r7
     10a:	0f f6       	mov.l	er7,er6
     10c:	1b 87       	subs	#2,r7
     10e:	65 00       	xor.w	r0,r0
```
これを見やすくするために、以下のような観点で区切っていきます。
## 関数単位に分ける
アセンブリコードの中で、Call、Return関係を探します。
機械語に変換（ROMに書き込んだ）時点でラベルは消えてしまうので、関数からのリターンを示す「rts」命令を探し、区切っていきます。
rts命令があれば、何かしらの関数から呼ばれた終わりであることがわかります。
```
    5cf2:	01 20 6d 72 	ldm.l	@sp+,er0-er2
    5cf6:	54 70       	rts	
    5cf8:	01 10 6d f2 	stm.l	er2-er3,@-sp
    5cfc:	79 03 00 20 	mov.w	#0x20,r3
    5d00:	01 f0 65 22 	xor.l	er2,er2
    5d04:	10 30       	shll.l	er0
    5d06:	12 32       	rotxl.l	er2
    5d08:	1f 92       	cmp.l	er1,er2
    5d0a:	45 04       	bcs	.+4 (0x5d10)
    5d0c:	1a 92       	sub.l	er1,er2
    5d0e:	0b 70       	inc.l	#1,er0
    5d10:	1b 53       	dec.w	#1,r3
    5d12:	64 33       	or.w	r3,r3
    5d14:	46 ee       	bne	.-18 (0x5d04)
    5d16:	0f a1       	mov.l	er2,er1
    5d18:	01 10 6d 73 	ldm.l	@sp+,er2-er3
    5d1c:	54 70       	rts	
    5d1e:	00 01       	.word	H'0,H'1
```
2行目と17行目にrts命令があります。この場合、3行目から17行目が1つの関数であることが推測できます。
そして、アドレス0x5CF8目掛けてCallしている場所を探すと、
```
    5ce0:	17 b1       	neg.l	er1
    5ce2:	5c 00 00 12 	bsr	.+18 (0x5cf8)
    5ce6:	aa 00       	cmp.b	#0x0,r2l
```
が見つかりました。これで関数Call関係にあることがわかります。
## 割込みベクタを分ける
コードを読むと、急に命令が始まるところがありますが、それは割り込みベクタなので先に把握しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330042/231e4696-6d81-8a85-c665-8503c914bf39.png)
0x0000～0x01FFは、4バイトごとに割り込み要因が割り当てられていることがわかります。
```
      80:	00 00       	nop	
      82:	26 b2       	mov.b	@0xb2:8,r6h
```
実際のコードではアドレス0x0080に記載があり、これはTGI0A割り込みの遷移先が記載してあります。
なお、遷移先0x26b2は以下のようなコードで、やはりちょうど、関数になっていることがわかります。
```
    26b0:	54 70       	rts	
    26b2:	01 30 6d f0 	stm.l	er0-er3,@-sp
    26b6:	01 20 6d f4 	stm.l	er4-er6,@-sp
    26ba:	0f f6       	mov.l	er7,er6
    26bc:	1b 87       	subs	#2,r7
    26be:	28 15       	mov.b	@0x15:8,r0l
```
## ループを見つける
bne命令、bra命令等をある程度機械的に解析し、ループ箇所を把握しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330042/3c6b1df9-a3c1-eabe-68cd-96df3207bb0c.png)
Excelに貼り付けておけば、命令で並べ替えて一括でハイライトすることができます。
# コードを追いかける手順
## スタートアップルーチンから追いかける
さきほどの割り込みベクタテーブルの記述にあるように、パワーオンリセットでは0x0000を読み出し、そこに記載されているアドレスから実行します。
その実行順にプログラムを追跡していきます。
```
00000000 <.sec1>:
       0:	00 00       	nop	
       2:	00 f4       	.word	H'0,H'f4
       4:	ff ff       	mov.b	#0xff,r7l
       6:	ff ff       	mov.b	#0xff,r7l
```
```
      f4:	7a 07 00 ff 	mov.l	#0xffc256,er7
      f8:	c2 56 
      fa:	5e 00 52 ee 	jsr	@0x52ee:24
      fe:	40 fe       	bra	.-2 (0xfe)
     100:	6a a8 00 ff 	mov.b	r0l,@0xffb000:32
```
jsr命令で分岐指定されている先は以下の通り。初期化ルーチンの塊だと思われます。
```
    52ec:	54 70       	rts	
    52ee:	01 00 6d f6 	mov.l	er6,@-r7
    52f2:	0f f6       	mov.l	er7,er6
    52f4:	7a 37 00 00 	sub.l	#0xc,er7
    52f8:	00 0c 
    52fa:	15 88       	xor.b	r0l,r0l
    52fc:	6e e8 ff f5 	mov.b	r0l,@(0xfff5:16,r6)
    5300:	5e 00 01 36 	jsr	@0x136:24
    5304:	5e 00 04 0e 	jsr	@0x40e:24
```
## I/Oレジスタへのアクセスを追いかける
特定の機能に注目して追いかける場合は、I/Oレジスタアドレスを追いかけます。
例えばI/Oポートであれば、以下のアドレス構成なので、そのアドレスを探します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330042/a3b6b99b-a9db-9b45-f1b6-b5f41bb01aa5.png)
0xFE30は以下の箇所で操作がありました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330042/e4419a40-c9dc-47d8-0229-d9b93349487a.png)
# 現象を追求する
特定の箇所でアドレスがループしているので、その処理が何なのかを確認します。
## ループ箇所を確認する
ループ箇所では、以下の処理が行われていました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330042/aa5d0ba0-7da9-9e6e-7695-f16d6cf69d86.png)
0x0A83から0x0A74を繰り返していました。
これは0xFF78を読み出し、2ビット目が0のとき次に進むという処理でした。
このアドレスはシリアルモードレジスタ0またはI<sup>2</sup>Cバスコントロールレジスタです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330042/4bb3f99d-5759-05c5-5fcb-6feadde50305.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330042/7fef61e9-164b-c428-ba97-610b33746654.png)
この装置では、パラメータのバックアップにEEPROMを使用しており、その接続はI<sup>2</sup>Cです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330042/2d07cf7c-1ff8-73c7-186b-cb2babcbe2fc.png)
当然従来から使い続けているのですが、お客様に確認すると、この型番はかねてより製造中止の連絡があったが、特段対応せず、その都度仕入れ先に声をかけて探してもらっていたとのことでした。
そして今回も流通在庫品なのですが「1週間以内に検品しておかしなところがあれば言ってください」とくぎを刺されていた品物だったそうです。
しかし、解析するすべがなく、はんだ付けを疑ったり、いろいろしているうちにどうにもならなくなり、今回の対応となりました。
## 引っかかっている場所を特定する
0xFF78（ICCR0）の2ビット目はBBSYで、初期値は0です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330042/0a6178fd-2aab-ca19-dad7-c0347521caa7.png)
つまり、現象としては、BBSYのビットが1のままのため、次に進まないという内容でした。
# 対応と試行
## 引っかかっている命令をバイパスしてみる
以下のアドレス0x0a80の命令がループしている箇所です。
```
     a80:	40 f2       	bra	.-14 (0xa74)
     a82:	28 78       	mov.b	@0x78:8,r0l
```
そもそもソースプログラムがないため、アドレスを1ミリたりとも動かしたくない状況でした。
たとえば、0x0a80にある命令がなければ次に進むのですが、削除してしまうとそれ以降のアドレスがすべてずれてしまうため、ここではnop命令（0x0000）に差し替えることで、アドレスを維持したまま0x0a80の命令を無効にしました。
その状態で装置を起動したところ、パラメータの設定ができないものの、動作することができました。
## 特定の機能／レジスタへのアクセスを偽装する
パラメータの記録（I<sup>2</sup>C）以外は動作したので、この部分だけを切り出す方法を検討しました。
1. マイコン内にI<sup>2</sup>C信号をGPIOで生成するプログラムを追加する
1. I<sup>2</sup>C機能をFPGAで置き換える

1.の方法ではアセンブリコードで1から機能を作る必要があります。貼り付ければすぐ動くモジュールがあるわけでなく、デバッグ環境もないため、2.の方法をとりました。
この方法では、偽装アドレスへのアクセス以降はFPGA内の動作に閉じるため、デバッグが可能になります。

## 代替機能を用意する
FPGAを中心とした、以下の基板を製作しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330042/c890e9da-ad52-68ad-a482-661fdcb12351.png)
右側にEPROMを載せて、左側に下向きのピンを取り付けます。
つまり、元のEPROMを取り外し、代わりにこの基板を亀の子で取り付けます。

EPROM内の既存プログラムは、以下の箇所を変更します。
- I<sup>2</sup>Cバスコントロールレジスタ、I<sup>2</sup>Cバスデータレジスタ等のレジスタアドレスをFPGA内に構築する偽装レジスタに変更する
例）0xFF78→0x79B0
- 不要な設定処理（マスタ／スレーブ設定等）をnopで置き換えまたはジャンプ

FPGA側のI<sup>2</sup>C機能はLattice MachXO2のI<sup>2</sup>Cハードマクロを使用しました。

## 動作検証／デバッグ
書き込み操作、読み出し操作、電源ON／OFF、連続した電源ON／OFF等を試行し、問題がないことを確認しました。
今回該当箇所以外明らかに触っていないので、これ以外のテストを省略しました。

# さいごに
機械語レベルで変更した／していないを把握しているので、テストする箇所を限定することができました。
古い製品で設計書どころかソースコードすらなく、ただEPROMだけがある状態で始めたので手探りでしたが、レジスタアドレスを偽装（置き換え）することで、影響範囲を限定することができました。

今回問題となったマイコンが、結局品違いなのか、リマークだったのか偽装だったのかは不明です。