投稿日 : 2019/07/15 22:26:15
PC-G850エミュレータにデバッグ機能を付けてみた
これまでの改造版g800エミュレータに、さらにMSVC(Microsoft Visual C++)のようなデバッグ機能を付けてみました。


ちなみにデバッグ機能とは、例えばプログラム実行中に特定の位置に来たらプログラムを止め、その時のレジスタやメモリ状態を確認したり、止めた場所からアセンブラコードを1つずつ実行することで、思った通りの遷移が行われているかを確認したりすることが出来る機能です。

昔はICEデバッガと呼ばれる専用のハードウェアと組み合わせることで、これらのデバッグ機能を実現することが出来ましたが、デバッグ用に専用のハードウェアを設計したりする必要があるため、製品を作るには時間もコストも掛かりました。

またその頃のPCは非力だったため、ソフトウェアによるエミュレートでデバッグを行うなんてことは考えられなかったわけですが、今ならこれらのCPUをエミュレータで実行することも出来るので、であればエミュレータにデバッガも入れてしまえばクロス開発がもっと楽になるのではと思い、何かいい方法が無いものかと考えていたところ、なんとSDCCのビルド時に書き出されるファイルにデバッグに必要な情報がすべて出力されていることに気づきました。

SDCCはコンパイル時に中間ファイルとしてMAPファイルとLSTファイルを書き出しています。

MAPファイルには各ソースや関数の絶対アドレスの情報(メモリマップ)が含まれており、またLSTファイルにはC言語やアセンブラを最終的なアセンブラコードに変換した際の詳細な情報が含まれていて、実はこれらを総合的に解析することでプログラム全体のメモリマップが構築出来ます。

ちなみに、最初は逆アセンブラを使ってアセンブラコードを逆算することも考えましたが、これだとあるアドレスの値がデータなのかコードなのか分からなかったり、もしコードの位置がずれてしまっていると逆アセンブルの結果もおかしくなってしまうため、出来るとしたら中断した位置のコードしか正確に分かりません。また、逆アセンブラはアセンブラコードをデバッグするものであり、C言語のデバッグは一切出来ないわけですが、SDCCが書き出したファイルにはC言語のコードがそのままコメントとしてアセンブラコード上に残っていたり、さらに便利なのがアセンブラコードが実行単位で行ごとに分かれているので、これを利用すればソースコードの位置を正確に把握しながらデバッグ出来るのではと思ったわけです。

なお、VCではメモリ上にある構造体の中身を確認することは出来ますが、今回のエミュレータではそれには対応していません。このため、そのアドレスをダンプして自分で値を確認する必要がありますが、例えばレジスタにその構造体のアドレスが代入されているならば、そのレジスタのアドレス先に飛んで直接ダンプすることが出来るので、これを利用すれば間接的に値の確認などが可能です。

1つ問題点として、このエミュレータが使用しているSDL2(Simple DirectMedia Layer2ライブラリ)のせいか、起動直後にウィンドウメッセージの処理が滞るようで、デバッグウィンドウが表示されるまで少し時間がかかります。そもそもSDLはWindows以外でも使える汎用のライブラリであり、ウィンドウメッセージの部分は内部でブラックボックス化されているため、外部からいじることが出来ません。
(SDLのソースは公開されているので中身をいじることは出来ますが、ライブラリを修正するとなると本末転倒なのでここではいじりません)

ということでこれは仕様としてひとまず気にしないことにして、今回はあらためてWindows10でのSDCCのインストールの仕方と、デバッグ機能付きg800エミュレータを同梱したビルドツールv2.01を使ったデバッグ方法について説明します。

旧バージョンからのソース移植について

以前のビルドツールv2.00で作成したプログラムは、g800ライブラリ(g800.h、g800.c、crt0.s)以外のゲームソース(game.hやgame.c、自分で作成した各データヘッダなど)をv2.01にコピーすることでそのまま利用可能です。v2.01の関数は基本的にv2.00と変わりはないので、各APIの説明は以前の記事を参考にしてください。

なお、v1.00のものはライブラリ自体が古いため、v2.01への移植作業が必要となります。
※v1.00はもう更新はされないので、新規で作るプログラムは新しいバージョンを使用してください

SDCCのインストール(改)

SDCCにはバージョンがあり、ビルドツールv1.00の時は当時最新だった3.6.0を使用していましたが、現在は3.9.0となっています。
※実は最新バージョンを使ってビルドツールv2.xxに同梱のPSG版のサンプルをビルドしようとすると、
 MMLPlayer.cのコンパイルがフリーズしたかのようにすごく長くなる現象を確認しました。
 ビルド自体は通るので特に最新バージョンを使用しても問題はありませんが、
 ビルド時間が長く時間がもったいないため、特に最新が必要でなければ古い3.6.0を使用することをお勧めします。

ダウンロードは以下から行います。
https://sourceforge.net/projects/sdcc/files/sdcc-win32/
※こちらは32bit版のSDCCですが、64bitOSでも問題なく動きます(64bit版のSDCCも別途あります)

この中から使用したいバージョンをクリックします。(3.6.0を使う場合は下のそのバージョン番号をクリックしてください)


その中にある「sdcc-X.X.X-setup.exe」というファイルをクリックしてください。


画面が切り替わり数秒待つとダウンロードが開始されます。

※DL先はデスクトップなど適当な場所を選んでください

DLしたらあとはその実行ファイルをダブルクリックして実行します。


Windows10では以下のような警告が出ることがあります。


この場合は「詳細情報」ボタンを押すと実行が押せるようになります。


インストールはほぼ次へを押すだけで完了します。







インストールするファイルを選べますが、ここではすべてインストールしています。






PATHの設定が表示されたらチェックを外さずに次へを押してください。
※PATHとはコマンドラインで実行ファイルを実行する際に探すフォルダのことです


Finishを押したらセットアップ完了です。


次に正しくインストールされたかを確認してみます。
スタートを右クリックして「コマンドプロンプト」もしくは「Windows Power Shell」をクリックします。
 

※新しいWindows10ではコマンドプロンプトがPowerShellに置き換わっている場合があります。
 SDCCの確認はどちらでも良いですが、開発系なら通常はコマンドプロンプトを使用するので、
 もしPowerShellをコマンドプロンプトに変えたい場合は、「スタート」→「設定」→
 「個人用設定」→「タスクバー」を開き、右側の「[スタート]ボタンを右クリックするか…」
 の部分のスイッチをオフにしてください。
 

コマンド入力画面で「sdcc」と入力してエンターを押します。


ここでsdccのヘルプが表示されたなら、インストールは正しく行われています。


これでビルドツールのコンパイルが可能となりました。

ビルドツールのダウンロード


ビルドツールは以下からダウンロードしてください。
ポケコンビルドツール v2.01 ダウンロード
※デバッガ対応のg800エミュレータv1.01が同梱されています

ビルドツールには2種類のテンプレートプロジェクトフォルダがあります。
それぞれ「開発用テンプレート(PSG音源対応)」と「開発用テンプレート(シンプル)」ですが、このテンプレートフォルダが実質1アプリ分のプロジェクトフォルダということになるので、あとあと作成するゲームの名前などに自由に変更してください。
※各プロジェクトの詳細や同梱のg800ライブラリの詳細はこちらの記事を参考にしてください

このビルドツールには以下のサポートツールが同梱されています。
※プロジェクトフォルダごとにtoolフォルダがあり、各フォルダには同じものが入っています
ソフト名 Platform 内容
PCGTool Win32 ポケコン転送ツール。
転送の前に必ず一度起動し、転送用の設定(COMポートやボーレート)などを設定する必要があります。
※最新版は本家サイトのSOFTWARE→ポケコンにありますが、一応このビルドツールにも入っています(ヘルプはありません)
AsciiConv2 Dos g800ライブラリの文字表示関数(DrawString、PPrintf)で表示されるフォント画像のヘッダファイル(ascii.h)を作成します。
「ascii.bmp」を編集することで自前のフォントを作成することが出来ます。
v1.00では固定サイズ(5x6)で、強制的に全てのASCII文字が登録されてしまっていましたが、
v2.00からは文字サイズをパラメータで指定出来たり、ゲーム中に使わない文字タイプを除外することで、
メモリの節約が出来るようになっています。
※詳しい使い方は同梱のAsciiConv2.htmlを確認してください
※裏ワザとして使わないフォントを♥♠♦などの記号にして、ゲーム中にそのコードを指定することで
 特殊な文字として表示させることが可能です
ConvMidi Dos MIDIファイルからg800ライブラリのSライブラリで使用する曲データのヘッダファイルを出力します。
これは指定した1トラック内にある全ノートを出力するため、和音も表現することが出来ます。
※SライブラリはPSG音源ではなく一般的なスピーカー(3pinと7pinに繋げたもの)を使うため、
 簡易的なBGMを再生するのに向いています
 (Sライブラリを使うには割り込み制御が必要です>ポケコンの割込みついてを参考)
※仕様上ドラムトラック(tr10)は変換出来ません
IHXConv Dos IHXファイルとバイナリファイルを相互に変換します。
変換の際にオフセットを指定することで全体をズラすことも出来ます。
※コマンドプロンプト上で実行するとヘルプが見れます
ImgConv Dos BMPやTGAを変換してg800ライブラリの描画関数で直接使用可能なヘッダファイルを生成します。
BMPの場合はマスク無し(アルファチャンネルは無視)、
TGAの場合はマスクあり(32bitアルファチャンネルを使用)で変換します。
もし画像がカラーだった場合、一般的なモノクロ変換を行ってから灰色中心で2値化します。
ディザ合成は行われないため必要ならばあらかじめ編集段階で白黒化(2値化)しておいてください。

透明画像(TGA)
TGAファイルでは透明部分を表現することが出来ます。
通常の合成とはORで各ビットを合成するため、背景に何らかの画像がすでに存在すると、
そのドットはそのまま残ってしまいます。

そこでここではマスクという元の画像と同じサイズの専用画像を用意します。
マスクとは消したくない個所を黒、消したい箇所を白で塗った画像で、
最初に背景とANDすることで、今回描画したい画像の部分のみを消しておきます。

そしてそのあとにこの位置に通常の画像をORすることで、
最終的に白で抜かれた透明部分のある画像を合成することが出来るわけです。
元画像


原理
AND OR
背景 マスク AND後 画像 OR後

画像データフォーマット
画像データは先頭にIMGDATA構造体があり、その直後に画像データが続きます。
マスクが存在する場合は、画像データ(OR用)のあとにマスクデータ(AND用)が続きます。
なお、マスクの存在する画像では通常の画像とマスク画像の2つ分のデータが存在することになるため、
メモリ使用量が単純に2倍になります。
このため基本的にはマスク無しの画像を使用し、必要な個所だけマスクありで作成することをお勧めします。
MargeRam Dos ポケコンから吸い出したRAM領域(0000h-7FFFh)のバイナリイメージに、
SDCCから出力されたコードを結合して最終的なRAMイメージを生成します。
これはビルドバッチ内で使用されるもので、基本的に直接使用することはありません。
MMLPlayer Win32 PSG音源ライブラリ用のMMLを作成し、これをヘッダファイルとして出力出来ます。
また入力したMMLを即座に再生出来るため、MMLのデバッグにも使用出来ます。
さらにMIDIからのインポートに対応しているため、既存のMIDIからMMLを手軽に作ることもできます。

詳しい使い方やMMLの書式は同根のMMLPlayer.htmlを確認してください。
基本的にはTSSに準拠しており、ここから余計なコマンドを削除したり、音色定義がポケコン専用になっているだけなので、
音色定義をTSSに合わせることでTSS側で再生も可能です。

※このソフトは音声出力にWASAPIを使用しているため、対応した音源とVistaSP1以上が必要です


ポケコンの開発用のみであれば上記のビルドツールだけダウンロードすれば問題ありません。
以下はエミュレータのソースコードと、11pinミニI/Oのエミュレーション用のDLLのソースコードです。
g800(改) VC++ Build v1.01のソース ダウンロード
※SDL2.0.7同梱
g800(改)用ミニI/O音源エミュv2.00のソース ダウンロード
※実機と比べて1オクターブ高く再生されます
※全てVC2010用のプロジェクトとなります

修正内容は以下の通りです。
・ミニI/O用外部DLLのMIOInput()関数の引数定義が間違っていた(MIO_VERSIONを0x0200に修正)
・MIOInput()でFALSEを返すと現在の入力値をそのまま使用するようにした
・in62_g850()関数のミニI/Oの入力ビットのマスクが出力ビットのマスクになっていた(元から)
・デバッガ機能追加(map、lstファイルと連動してブレークが可能)
 ※g800configのdebuggerを'y'にすること

g800エミュレータのライセンス表記
---------------------------------------------------------------------------------------
Copyright (c) 2005 ~ 2014 maruhiro
All rights reserved. 

Redistribution and use in source and binary forms, 
with or without modification, are permitted provided that 
the following conditions are met: 

 1. Redistributions of source code must retain the above copyright notice, 
    this list of conditions and the following disclaimer. 

 2. Redistributions in binary form must reproduce the above copyright notice, 
    this list of conditions and the following disclaimer in the documentation 
    and/or other materials provided with the distribution. 

THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 
THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------------------------------------

PSG音源をWindows上で再現するために、以下のライブラリを使用しています。
-------------------------------------------------
  FM Sound Generator with OPN/OPM interface
  Copyright (C) by cisc 1998, 2003.
  http://retropc.net/cisc/m88/
-------------------------------------------------
※この中のPSG音源部のソースのみ利用しています

デバッガの使い方

ここではビルドツールに含まれているサンプルプログラム「開発用テンプレート(シンプル)」を例に、プログラムのデバッグ方法について説明します。

このサンプルは1枚のフルスクリーン画像を描画するだけのプログラムで、ON(BREAK)キーを押すことでプログラムを終了することが出来ます。


これらの処理はgame.cに書かれていますが、ここでは参考として以下に全コードを示します。
/////////////////////////////////////////////////////////////////////////////////
// Game : ゲームメインプログラム
/////////////////////////////////////////////////////////////////////////////////
#include "Game.h"

#include "data/title.h"



/////////////////////////////////////////////////////////////////////////////////
// グローバル変数
/////////////////////////////////////////////////////////////////////////////////
KEYDATA         mKeyData;                           // キー入力



/////////////////////////////////////////////////////////////////////////////////
// 初回起動時に呼び出される
/////////////////////////////////////////////////////////////////////////////////
void Init( void )
{
    __memset( &mKeyData,0x00,sizeof(mKeyData) );    // ゼロクリア
}




/////////////////////////////////////////////////////////////////////////////////
// 毎フレーム呼び出される
/////////////////////////////////////////////////////////////////////////////////
BOOL EnterFrame( void )
{
    // キー入力
    GetAllKey( &mKeyData );

    // ONキーが押されたら抜ける
    if( IsKey(mKeyData,VK_BREAK) ) {
$       return FALSE;                   // 先頭に'$'を書くとブレークポイントとなる
    }

    // 描画
    DrawImage( DMODE_COPY, 0, 0, (LPIMGDATA)title_img );

    return TRUE;
}

このサンプルではプログラムのコメントに書かれてあるように、EnterFrame()内にあらかじめブレークポイントが設定されています。
ブレークポイントを定義するには行の先頭に「$」と書きますが、実行中この処理が行われる直前にデバッガにより動作を停止します。
ちなみに「$」は「g800.h」にてC言語上では何もしない空の定数として定義されています。
※ブレークポイントはいくつも設定することが出来ます

この状態で「!!!build.bat」を実行してビルドを行い、成功後に何かキーを押すことでエミュレータを起動させますが、この時はまだブレークポイントのコードが実行されないため、プログラムはそのまま動き続けます。逆に言えば、ブレークポイントが設定されていなければ普通にプログラムを実行出来るため、デバッグ機能が有効になっていたとしても、通常通りプログラムのテストを行うことが出来ます。


さて、この状態でONキーを押すとif文が評価され、その中のコードに移行するわけですが、この時ブレークが発動することでデバッガがプログラムを停止させます。
すると、このブレークが置かれているソース(ここではgame.c)に対するアセンブラコードの位置にカーソルがジャンプします。

※ブレークポイントは赤丸で表現され、矢印とピンクの行は現在停止中の位置を表しています
 (ブレーク直後はこの2つが重なっている状態となります)

このコードを簡単に解説すると、このアセンブラコードはC言語で「return FALSE;」を実行するコードになっています。
SDCCでは、関数の戻り値として8bitの値を返す場合はLレジスタ、16bitの値を返す場合はHLレジスタを使用しますが、EnterFrame()の戻り値は「BOOL型」であり、BOOLは「platform.h」にて符号無し8bitと定義しているため、ここではLレジスタに0を設定したあとでリターンするというコードになっています。

ブレークがかかると現在のレジスタの内容が上部に表示されます。
表示形式は左上のラジオボタンにより10進数の符号ありや符号なしにも変えることも可能です。


8bitレジスタは10進数や16進数の他に2進数によるビット値も表示されます。


また、フラグレジスタは各フラグの状態を1つずつ分かりやすいよう、チェックボックスで分けて表示されるようになっています。


さらに、16bitレジスタは主にメモリのアドレスの参照に使われるため、横の「→」ボタンを押すことでそのアドレスにジャンプすることが出来るようになっています。



メモリダンプを行うには「@RAM」タブを選択します。
ただし、ポケコンのRAMは32KBしかないため、このタブで表示できるのは32KBまでとなります。


例えばPCレジスタのアドレスにジャンプしたい場合、PCレジスタの横の矢印ボタンを押しますが、この時該当するメモリアドレスの値が赤字で表示されるので、その位置をパッと見で把握できるようになっています。


※このダンプリストとブレーク時のgameのコードを見ると、
 以下のようにメモリ内のコードとアセンブラコードが一致しているのが確認できると思います
 
 


次にこのブレークした位置からステップ実行を行ってみます。
ステップ実行とはアセンブラコードを1個分だけ進めることで、これは上のステップボタンを押すことで行います。


ステップ実行をすると現在のコードが実行され、次のコードで再び停止します。
この時変化したレジスタの色が変わるので、実行したコードで何が行われたのかをすぐに把握することが出来ます

※ここではLレジスタに0x00を入れたので、Lレジスタとそれに付随するHLペアレジスタが、
 またPCレジスタもコードに合わせて進んだため色が付いています
 (値を代入した結果が同じ値だった場合は、変化無しと判定されるため色は付きません)

このステップ実行を繰り返すことで、1つ1つのコードを確認しながら実行することが出来るようになっています。

なお、ステップ実行をやめて再び普通の処理に戻したい場合は、上の「再開」ボタンを押します。
これを押すと次のブレークポイントが見つかるまで処理を止めずに実行することが出来ます。


ブレークポイントは実行中やブレーク中にON/OFFすることが出来ます。

赤丸や行番号のあるエリアはブレークポイントを設定することが出来る場所で、クリックすることでブレークポイントを切り替えることが出来ます。また、実行中にブレークポイントを設定した場合、その位置に到達するとブレークが発動するため、一時的に内容を確認したいといった場合にも使用することが可能です。

ちなみにCのソースコード上で指定するブレークポイントは、単にブレーク位置をあらかじめ設定出来るというだけであり、実行中にブレークポイントを解除することも可能です。
※ブレークポイントはブレークが可能な行にしか設定出来ません

ブレークを利用した裏技

このデバッガはC言語用というよりはアセンブラ用に近いものとなっています。

ステップ実行はあくまでもアセンブラコード単位のため、アセンブラを知らないと何をやっているのか全く分からないことになりますが、もっと大まかにCの関数単位で動作を確認する方法があります。

その方法とはCのソース側でデバッグしたい行全てにブレークポイントを設定し、ステップ実行ではなく再開を使用します。
こうすることで関数単位でブレークが発動するため、どこに処理が通っているのかといった大まかな動作を確認することが可能です。
※例えばプログラムが暴走したときにどの関数で死んだかなどが分かると思います
/////////////////////////////////////////////////////////////////////////////////
// 毎フレーム呼び出される
/////////////////////////////////////////////////////////////////////////////////
BOOL EnterFrame( void )
{
    // キー入力
$   GetAllKey( &mKeyData );

    // ONキーが押されたら抜ける
$   if( IsKey(mKeyData,VK_BREAK) ) {
$       return FALSE;                   // 先頭に'$'を書くとブレークポイントとなる
    }

    // 描画
$   DrawImage( DMODE_COPY, 0, 0, (LPIMGDATA)title_img );

$   return TRUE;
}
ブレークしたら再開ボタンを押すことで次のブレークポイントまで進むので、この方法でCソース上での遷移を把握することが出来ます。

※エミュレータのソースコードを見れば分かるかもしれませんが、実はCの関数ごとにブレークするための解析も既に行っており、あとは処理さえ実装するだけで上記のようなブレークポイントを設定せずともCの関数ごとにブレークすることも出来るのですが、今回はとりあえずアセンブラのデバッグがやりたかっただけなのでこの機能は見送りました(時間があったらそのうち実装するかも)


同じくアセンブラ上でCALL呼び出しがある場合、ステップ実行ではCALLの呼び出し先のコードにジャンプしてしまいますが、特にCALLの中身を確認する必要が無く即座に次のコードを確認したい場合は、CALL呼び出し直後のコードにブレークを設定してから再開ボタンを押します。


こうすることでCALLはそのまま実行され、CALLから戻ってきた際に次のコードでブレークが入るので、結果的にCALLをスキップしたことになります。

g800エミュレータのデバッガが使用するファイル構成について

g800エミュレータのデバッグに使用するファイルはMAPファイルとLSTファイルですが、これはビルドバッチを実行すると自動的にエミュレータのデバッグフォルダ(debugger)にコピーされます。

エミュレータはこの中にあるファイルをすべて読み取り、メモリマップを構築してデバッグ情報を生成していますが、もしこのフォルダの中に何も無かった場合は、デバッグ機能をONにしていたとしてもメモリマップが作れないためデバッグ処理は行われません。
※デバッグウィンドウが表示されません

ちなみにデバッグ機能を使用するかどうかは、エミュレータのあるフォルダの直下にある「g800config」をメモ帳で開き、debuggerの定義を「y」か「n」で指定します。

自作アセンブラのデバッグ

このデバッガでは自分で作ったアセンブラコードもデバッグ出来ます。

実はポケコンのブートアップコード「crt0.s」はアセンブラファイルであり、アセンブルするとCのソースと同じくlstファイルが生成されるので、これもデバッガに自動的に読み込まれるようになっているため、デバッガ上でcrt0タブを開くことで同じくブレークポイントを設定することが出来ます。

ただし、アセンブラのソースはCのソースのようにコード上であらかじめブレークポイントを指定することは出来ないため、この場合はCのソース側でアセンブラコードを呼び出す処理を書いておき、そこにブレークポイントを設定してステップ実行するか、または実行中でもブレークポイントは設定できるので、実行中にアセンブラコード内にブレークを置くことで強制的に停止させることが出来ます。

もし自分でアセンブラファイルを用意した場合は、ビルドバッチにそのファイルのアセンブルとリンク指定を行う必要があります。
※面倒なら自分のアセンブラもcrt0.sに書いてしまうのも手です

デバッグウィンドウの操作

デバッグウィンドウに表示される各ソースコードは、ドラッグすることで自由にスクロールすることが出来ます。
※「@RAM」タブはドラッグによるスクロールには対応していません

スクロールバーによる移動ではウィンドウメッセージによりメイン処理が停止してしまいますが、ドラッグでのスクロールでは停止しないので、実行したままソースコードを確認することが出来ます。
コメントはまだ登録されていません。
コメントする
名前
コメント
※タグは使用出来ません
この記事に関連するタグ
ポケコン(PC-G850)