ORANGE pico で使用されているマイコンの PIC32MX170F256B には、RAMが64KiB搭載されている。
前回の記事
ORANGE pico の配列の負の添字の先に何があるのか #配列 - Qiita
では、配列の負の添字へのアクセスを利用し、RAMの先頭から配列のデータがある部分までの領域のダンプに成功した。
今回は、別の方法を使い、RAM全体のダンプを行う。
配列を用いてリセットせずにRAMをダンプする
前回の記事では「リセットがかかるまでメモリの内容を取得する処理を続ける」というアプローチを行ったが、今回は処理を繋げるため、リセットがかからない形でメモリの内容を取得したい。
そこで、前回の調査結果に基づいてメモリー配列の先頭アドレスを 0x03c8
(KSEG1 上では 0xa00003c8
) と決め打ちし、メモリからメモリー配列の先頭を探すことで、取得中のアドレスを特定し、リセットがかからない範囲のみを取得できるようにすることにした。
具体的には、以下のプログラムを作成した。
10 memarrayaddr = &H03C8
20 dim array(3)
30 for i=0 to 3
40 v=rnd(&H10000):array(i)=v
50 mpoke i*2+0,v & &HFF:mpoke i*2+1,(v>>8) & &HFF
60 next
70 i=-1
80 if array(i)<>array(0) then i=i-1:goto 80
90 if array(i+1)<>array(1) then i=i-1:goto 80
100 if array(i+2)<>array(2) || array(i+3)<>array(3) then i=i-1:goto 80
110 print i
120 for i=i-memarrayaddr/2 to 3
130 uartput 1,format$("%02x%02x",array(i) & &HFF,(array(i)>>8) & &HFF)
140 next
まず、4要素の配列を用意し、合計8バイトの乱数を書き込む。
そして、その乱数の内容をメモリー配列の先頭に書き込み、添字を遡って乱数の内容が書き込まれている部分を探す。
該当部分が見つかったら、そこがメモリー配列の先頭であると仮定し、ダンプを実行する。
今回は、データを反転せずに From Hex だけで取り出せるような形で出力を行う。
さらに、画面に出力する print
のかわりに、UARTに直接出力する uartput
を用いるようにした。
実行すると、5秒ほどでメモリー配列の先頭を特定することができ、そこから10秒ほどでダンプを終えることができた。
gput を用いたメモリ内容の取得
手法
gput
コマンドは、メモリー配列の内容をグラフィック画面に描画するコマンドである。
以前書いた記事
【リ】ORANGE pico をリセットするコード|みけCAT
では、gput
コマンドを幅712で実行するとリセットはかからず、幅720で実行するとリセットがかかった、という報告をした。
このときのデータ量は、幅712のとき $712 \times 712 \div 8 = 63368$ バイト、幅720のとき $720 \times 720 \div 8 = 64800$ バイトである。
このデータ量にメモリー配列の先頭アドレス 0x03c8
を加えると、幅712のときは 0xfb3c
、幅720のときは 0x100e8
となり、RAMの最終アドレス 0xffff
を超えるか否かの境界があることがわかる。
ということは、gput
コマンドをうまく用いることで、RAM上の指定した位置のデータをグラフィック画面に出力させ、取得することが可能そうである。
これを、以下のプログラムで実装した。
10 memarrayaddr = &H03C8:endaddr=&H10000
20 dim array(3)
30 for i=0 to 3
40 v=rnd(&H10000):array(i)=v
50 mpoke i*2+0,v & &HFF:mpoke i*2+1,(v>>8) & &HFF
60 next
70 s=-1
80 if array(s)<>array(0) then s=s-1:goto 80
90 if array(s+1)<>array(1) then s=s-1:goto 80
100 if array(s+2)<>array(2) || array(s+3)<>array(3) then s=s-1:goto 80
110 print s
120 for i=s-memarrayaddr/2 to s+3
130 uartput 1,format$("%02x%02x",array(i) & &HFF,(array(i)>>8) & &HFF)
140 next
150 addr=memarrayaddr+8:width=8
160 datasize=width*width/8
170 readidx=addr-(datasize-1)-memarrayaddr
180 if readidx>=8000 then width=width+8:goto 160
190 line 0,0,7,0,0:gput -width+8,-width+1,width,readidx,&HFFFF
200 gget 0,0,8,0
210 uartput 1,format$("%02x",mpeek(0))
220 addr=addr+1
230 if addr<endaddr then goto 170
最初は、前のプログラムと同様に、配列を用いてメモリー配列の先頭の位置を特定し、RAMの先頭からメモリー配列の先頭までのダンプを行う。
その後、gput
で用いるデータ量に基づいて参照される最後のバイトが取得したいバイトになるように gput
で指定するアドレスを調整する。
このアドレスが8000以上になった時は、gput
で取得する幅を増やし、アドレスを再計算する。
幅720以下において、幅を8増やしたときに増えるデータ量は8000を下回るので、この方法でうまくいくはずである。
そして、VRAMの使用が最小限になるよう、画面の左上8ピクセルを黒く塗り、そこに gput
で描画する右下の隅 (すなわち、最後のバイト) が来るように座標を調整して描画を行う。
さらに、ここで描画したデータを gget
コマンドで取得し、UARTで送信する。
実行結果 (VRAMの位置の特定)
VRAMの位置を調べるため、DE AD BE EF
のパターンをグラフィック画面に描画した後、このプログラムをEEPROMから読み込んで実行した。
実行には約1時間かかった。
以下は、得られたメモリ内容の最初の部分である。
メモリー配列の最初の部分である。
最後に取得して書き込んだ乱数の2バイトが、メモリ内容の先頭2バイトと一致している。
これは偶然なのか、それとも何か関係があるのだろうか?
0x4014
から、テキスト画面に描画されている内容が出ている。
したがって、ここがテキスト画面用のVRAMであると考えられる。
ここは前回の記事では 0x33
の連続が観測されていたが、これは 0x33
はASCII文字の 3
であるため、データ「0x33」を表現する文字列「33」を画面に出力するとデータは「0x33」のままとなり、収束するためである可能性がある。
一定間隔おきに DE AD BE EF
が格納されたエリアが現れた。
ここがグラフィック画面用のVRAMであると推測できる。
パターンを書き込んだのはy座標1からであるので、DE AD BE EF
が格納されている間隔を考えると先頭アドレスは 0x44c4
であると推測できる。
文字列がまばらに格納されている。
gget
コマンドを幅448で実行するとリセットがかかったと報告しているが、このときこのあたりまで書き込みが行われると考えられる。
よく見ると、RAMのアドレスと思われるデータも格納されており、このアドレスの付近に次のアドレスと思われるデータが格納されている場所もみられた。
ということは、この領域はスタックとして使われており、このアドレスと思われるデータは呼び出し元のベースポインタである可能性が考えられる。
前回の記事では 00
の連続だった 0x99b0
付近に、「pico」や「ramdump」というデータが見られる。
これは、プログラムをEEPROMから読み込んだため、ヘッダが格納されていると考えられる。
配列の各要素のデータが連続して格納されている。
その後、配列の変数名 array
が4個格納されている。
よく見ると、それぞれの変数名の20バイト前に、配列のデータへのポインタや要素数の情報が格納されているようである。
変数が格納された領域を過ぎると、謎のデータがあった。
CyberChef で Entropy を計算すると、このあたりのエントロピーは7を超えており、乱数または暗号に近いデータであると考えられる。
一旦 00
が多い部分があり、また乱数のようなデータが少しあったあと、再び 00
が多くなった。
意味はよくわからない。
RAMの最後の部分である。
意味はよくわからない。
gput を用いたメモリ内容の取得 (改良)
手法
前の章では、配列を用いて取得する部分を最小限にし、RAMの大部分を gput
を用いて取得した。
しかし、この手法は長時間かかり、特に gput
で指定する幅が大きくなるにつれてかかる時間も伸びていくようであった。
ただ、これで gput
を用いても配列を用いるのと同様のデータが取得できることが確認できたので、配列のデータの直前までを配列を用いて取得し、配列では取得できない残りを gput
を用いて取得するようにした。
配列と gput
の担当範囲を変えただけであり、プログラムの変更はほとんど無い。
10 memarrayaddr = &H03C8:endaddr=&H10000
20 dim array(3)
30 for i=0 to 3
40 v=rnd(&H10000):array(i)=v
50 mpoke i*2+0,v & &HFF:mpoke i*2+1,(v>>8) & &HFF
60 next
70 s=-1
80 if array(s)<>array(0) then s=s-1:goto 80
90 if array(s+1)<>array(1) then s=s-1:goto 80
100 if array(s+2)<>array(2) || array(s+3)<>array(3) then s=s-1:goto 80
110 print s
120 for i=s-memarrayaddr/2 to -1
130 uartput 1,format$("%02x%02x",array(i) & &HFF,(array(i)>>8) & &HFF)
140 next
150 addr=memarrayaddr+2*(-s):width=8
160 datasize=width*width/8
170 readidx=addr-(datasize-1)-memarrayaddr
180 if readidx>=8000 then width=width+8:goto 160
190 line 0,0,7,0,0:gput -width+8,-width+1,width,readidx,&HFFFF
200 gget 0,0,8,0
210 uartput 1,format$("%02x",mpeek(0))
220 addr=addr+1
230 if addr<endaddr then goto 170
実行結果 (文字列変数の格納場所の特定)
今回は、文字列変数のデータがどこに格納されるのかを調べるため、プログラムをEEPROMから読み込んだ後、文字列変数にデータを格納する処理を追加し、実行を行った。
実行には約20分かかった。
今回取得したRAMの内容の先頭である。0x0020
付近には前回の取得との違いが見られるが、最初の32バイトは前回取得したデータと同じである。
メモリー配列の先頭部分である。
書き込まれた乱数が、前回と全く同じである。
最初の文字列の内容と思われるデータが、0x2AF8
から見つかった。
その後も、256バイトごとに文字列のデータが並んでるようである。
0x34f8
から、別のデータが格納されているようである。
文字列を格納する場所は10個分しか無い…?
前の観察で、グラフィック画面のVRAMは 0x44c4
から始まるらしいことがわかった。
これに320×200ピクセル分のデータサイズ $320 \div 8 \times 200 = 8000$ バイトを足すと、0x6404
となる。
このアドレスを観察すると、再び文字列のデータが格納されていた。
同じデータが複数格納されており、作業領域の可能性がある。
先ほどスタックであると推測した領域にも、文字列のデータが格納されていた。
スタックにしては、VRAMに隣接しすぎており、余裕が無い。
それでも、作業用のデータが格納され、下手な書き込みを行うとリセットがかかるほど重要な領域であるようである。
となると、ヒープの可能性が考えられる。
ヒープであれば、前から使われる可能性が高いのでVRAMに隣接していてもよく、空き領域の管理用に隣接する領域のアドレスを書き込む可能性もある。
変数が格納された領域である。
変数 memarrayaddr
を見ると、変数名の20バイト前に変数の値 0x03c8
がリトルエンディアンで格納されているのがわかる。
同様に文字列変数の変数名の20バイト前を見ると、先ほどの領域の何番目のデータを使用するかの値が格納されているようである。
変数が格納された領域を過ぎた部分で 00
が多い部分をよくみると、4で割って3余るアドレスに 0xa0
や 0x9d
がよくみられる。
PIC32MX系のマイコンにおいて、0xa0
で始まるアドレスは KSEG1 の RAM、0x9d
で始まるアドレスは KSEG0 のProgram Flash である。
したがって、これらが呼び出し元のベースポインタやリターンアドレスを表している可能性があり、このあたりがスタックとして用いられている可能性が考えられる。
追加実験 (文字列変数の最大数)
文字列変数のデータが格納された領域を観察した結果、格納する場所が10個しかないようであったので、「文字列変数は10個までしか管理できない」という仮説を立てた。
検証のため、先ほどのプログラムの実行後、文字列変数を追加していった。
すると、プログラム内の変数と合わせて11個目を追加しようとした際、「Out of string space」が出た。
実際に、文字列変数は10個までしか管理できないようである。
まとめ
gput
コマンドを用いてメモリの内容を一旦VRAMに読み出すことにより、配列のデータの場所を超える部分のRAMのデータを取得することができた。
前回と今回の実験結果から、ORANGE pico (Ver 1.06) のRAMには、以下の位置に以下のデータが格納されていると推測できた。
なお、「位置」はRAMの先頭からのオフセットを表す。
位置 | 内容 |
---|---|
0x0000 |
乱数の状態? |
0x03c8 |
メモリー配列 |
0x2af8 |
文字列変数のデータ |
0x3780 |
キャラクターパターン |
0x4014 |
テキスト画面 VRAM |
0x44c4 |
グラフィック画面 VRAM |
0x6404 |
ヒープ? |
0x8968 |
UART受信バッファ |
0x8d68 |
UART受信バッファ上のポインタ |
0x99b0 |
プログラム (ヘッダを含む) |
0xb9b0 |
構文解析用の情報? |
0xd990 |
変数・スタック? |
文字列変数のデータは1個が256バイトの固定長で、10個分の領域が用意されている。
文字列変数は10個までしか同時に管理することができない。
キャラクターパターンは、まずそれぞれの文字の1行目が1バイトずつ、合計256バイト並んだ後、それぞれの文字の2行目が1バイトずつ…というように、文字ごとではなく行ごとにまとまって格納されている。
変数が格納された領域では、変数名の20バイト前にその変数の値が4バイトリトルエンディアンで格納されている。
整数変数では、値がそのまま格納されている。
浮動小数点数変数では、値がfloat32型で格納されている。
文字列変数では、データ領域の何番目を用いるかの番号が格納されている。
配列変数では、同じ変数名が4個格納され、最初の変数名で配列のデータへのポインタが、残りの変数名で配列の要素数が格納されるようである。
(dim a(1,2,3)
は実行できたが、dim a(1,2,3,4)
は Syntax error となり、配列は3次元までしか使用できないようである)