目次
- ppcsimについて
- インストール
- 実行方法
- 実行例
- デバッグ例
- syscallの仕掛けなどについて
- 制限事項
- その他
- 履歴
ppcsimはPowerPCの命令コードをソフトウェア的に解釈して動作をシミュレートする
ソフトウェアシミュレータです。Power-Xプロジェクトの先行実験検証用(本当は単なる
お勉強用)に作成しました。本物PPCが使えれば良いのですが、Power-Xプロジェクト
自体が現実のものになるのかどうか不明であったので、労力はかかるけど設備投資が不要な
ソフトシミュレータで、GNUツールのレベル検証であるかとか、性能見積もりであるだとか
を行ってみようとTANEが勝手に行った実験の産物の一つです。
2000/5/13に、首謀者である
SYSTEMAXのKOJIさんの所で、
バーチャルPowerXが出ています。2002/5/13現在(あぁ.....きっかり二年前ですか)
一部未実装命令がある点やファイルIOが使えないなどの制限がありますが、
実行速度がppcsimより遥かに速いのが特徴です。こちらも合わせてみていただくと
良いでしょう。
Cygwin版はソースのみの配布となっていますので、コンパイルする必要があります。
アーカイブを展開して、ディレクトリを移動し、 make を実行すれば ppcsim.exeが
できあがります。パスを通してインストールする場合、ppcsim.exeと
ppcsim.hlp(ヘルプファイル)は同一ディレクトリに置いてください。
コマンドシェル型のユーザインターフェースとなっています。
> ppcsim
と実行する事でプロンプトが表示され、コマンド受け付け状態となります。
execコマンドでクロスコンパイル&アセンブル&リンクしたELFバイナリを実行する
事ができます
(PPCクロス gcc/binutilsの作り方を参照)
。命令単位実行のデバッガの様なイメージでしょうか。
Ver 0.05以前のイメージで、実行ファイルをシミュレータ内のメモリにロードし、
g(GO)コマンドで実行する事も可能です。こちらは、昔のPC-8801やPC-9801の
N8x-BASICで、MONコマンドを使用した事のある方なら、そのまんまのイメージを
思い浮かべられるのではないかと思います(^^;
本ドキュメントでは、前者のexecコマンドを使用したELFバイナリの実行をベースに
記述しています。
元々X68kで開発を開始したという経緯があり、ユーザインターフェースに凝るつもりは
無かったのですが、readlineライブラリを使用したおかげでインタラクティブ入力
性能が妙に良くなりました(^^; ただ、一連のテストや初期化を毎回入力するのも面倒
なので、一連の操作をバッチファイルの様に列記して実行する事が可能となってい
ます。
> ppcsim foo.ppc
の様に実行する事でファイル内(この場合はfoo.ppc)に書かれたコマンド列を順に実行
します。全てのコマンドを実行し終えるとppcsimの実行は終了します。
サンプルを実行してみます。
サンプルアーカイブ(test_08x.tar.gz)を展開し、test_08xディレクトリ
の下に移動します。そしてppcsimを起動します。グラフィックウインドが開くと
共に、起動したターミナルでは以下のようなプロンプト表示状態となります。
test_08x> ppcsim.exe
PPC>
この状態で、execコマンドを使って実行バイナリである「hello」を実行してみます。
PPC> exec hello
hello ppcsim world
called exit.
PPC>
「hello ppcsim world」はhello実行ファイルのソースhello.cを参照していただけ
れば一目瞭然でしょう。「called exit.」はexit()システムコールが実行されたことを
示すppcsimのメッセージです。
ppcsimの実行を終了するには exitコマンドを実行します。
PPC> exit
test_08x>
バグの入ったプログラムを意図して作るのは難しい(^^;ので、hello実行ファイルの
実行をトレースしてみます。
ppcsimを実行して、ブレークポイントを設定します。
PPC> bp 0x10000 +
Setting bp=0x00010000 , ID=0
'+'は現在のプロセスID+1のプロセス番号を示します。execコマンドで実行される実行
ファイルは、現在のID +1のプロセスというイメージで起動されます。そのため、実行前の状態
で新しく起動されるプロセスのブレークポイントを指定する為のものです。
また、Setting ..... , ID=0 のIDはブレークポイント自身のIDを示します。ブレークポイント
を削除する際はこのIDを指定します。
引き数無しでbpコマンドを実行すると、現在のブレークポイント一覧が表示されます。
PPC> bp
--- Break Point(s) for Process ID = 1 --
No.00 : 0x00010000
PPC>
実行ファイルhelloを実際に実行してみます。
ブレークポイントで停止した旨のメッセージが表示されて停止します。
PPC> exec hello
Exec stop by break point.
00010000 : 54210036 rlwinm %r1, %r1, 0, 0, 27
PPC>
実際に停止したアドレスと そのアドレスに置かれている命令の逆アセンブルリストが
表示されています。
この状態でレジスタの内容を表示するにはdumpコマンドを実行します。
PPC> dump reg
PC : 00010000 MSR : 00000000 SPRG0 : 00000000 DEC : 00000000
CR : 00000000 SRR0 : 00000000 SPRG1 : 00000000 TBU : 00000000
XER : 00000000 SRR1 : 00000000 SPRG2 : 00000000 TBL : 00000000
LR : 00000000 HID0 : 00000000 SPRG3 : 00000000
CTR : 00000000 HID1 : 00000000 SDR1 : 00000000
GPR :
00-07 : 00000000 007ff000 00000000 00000001 007ff000 00000000 00000000 00000000
08-15 : 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
16-23 : 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
24-31 : 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
PPC>
ステップ実行してみます。sコマンドを実行します。
PPC> s
PPC>
アドレス0x10000の命令が1つ実行されているのですが、表示上は見えません(^^;
そこで、traceコマンドを使用して実行した命令のリストを表示するようにし
5命令実行してみます。
PPC> trace pc
PPC> s 5
00010004 : 38000000 addi %r0, %r0, 0
00010008 : 9421fff0 stwu %r1, -16(%r1)
0001000c : 7c0803a6 mtspr %r0, %spr8
00010010 : 90010000 stw %r0, 0(%r1)
00010014 : 48000039 bl 56
更に続けてステップ実行してみます。
PPC> s
0001004c : 9421ffe0 stwu %r1, -32(%r1)
00010050 : 7c0802a6 mfspr %r0, %spr8
00010054 : 7d2b4b78 or %r11, %r9, %r9
00010058 : 90010024 stw %r0, 36(%r1)
0001005c : bf810010 stmw %r28, 16(%r1)
ppcsim起動時は一回のステップ実行では1命令だけ実行されます。実行命令
数を指定すると一回のステップ実行で実行されるデフォルト命令数は変更されます。
そのため、前述の例では s コマンドのみで5命令実行されています。
プロセスの一覧を表示してみます。psコマンドを実行します。
PPC> ps
ID size name
0 65536 init
1 8388608 hello
IDはプロセスIDを示し、sizeはプロセスのメモリサイズを示しています。ID=0
はルートプロセス(UNIX系OSに習ってinitと名付けています)になっており、
ppcsim起動時に必ず生成されます。また、
プロセスサイズはデフォルト8MB固定となっていますので、メモリサイズに不都合
がある場合は、ppcsimのソースを変更する必要があります(ここはコマンドで
変更できる様にしたいところですね)。
実行を再開するには、cコマンドを実行します。ここで注意点ですが、cコマンドで
続きを実行する際も、traceコマンドでの実行時の表示状態に従います。ここで
cコマンドを実行すると実行した命令がダンプされ続けますので、一度traceを
offにします。この点、前よりも面倒になっているのがイマイチですね(^^;。
PPC> trace off
PPC> c
hello ppcsim world
called exit.
ppcsim-0.8xでは命令毎の実行回数をカウントしています。実行が完了した時点で
sumコマンドを実行すると、それまでの命令の実行サマリが表示されます。
PPC> sum
subfic = 3 0.224215 %
cmpli = 12 0.896861 %
cmpi = 80 5.979073 %
addic. = 5 0.373692 %
: <中略>
or = 113 8.445441 %
mtspr = 37 2.765321 %
srawi = 3 0.224215 %
Total_count = 1338
helloの実行では1338命令を実行していることが判ります。また、各命令の実行
数が全体に占める割合がパーセント表示されています。
実際には関数の実行における実時間の方が重要になるかと思われますので、
チューニングなどの観点では参考程度にしかならないかも知れませんが、
コンパイラの吐くコードのチェックを行ったり、プログラム自身の特徴を
探る(例えばメモリアクセスの多いプログラムならロードストア命令が大量
に実行されるでしょうし、演算が多いプログラムであれば、演算命令が
大量に実行されるでしょう)事ができると思います。
わざと特徴づけたプログラムを作れば、ハードの性能を探る(もっと言うと
弱い個所を見つける)特殊プログラムを開発することができるかも知れません。
ppcsim実行中に簡単なコマンドヘルプを表示できます。Cygwinパッケージに
含まれるlessコマンドをインストールしてある必要があります。また、予め
(bashの場合) export LESSCHARSET=dos で環境変数を指定しておく必要があります。
ppcsimを実行した状態で、hコマンドを実行すると、ヘルプをlessを使って表示
する様になります。
ppcsim-0.8xでは命令列の逆アセンブルリストを表示できるようになりました。
PPC> dis 0x10000
00010000 : 54210036 rlwinm %r1, %r1, 0, 0, 27
00010004 : 38000000 addi %r0, %r0, 0
00010008 : 9421fff0 stwu %r1, -16(%r1)
0001000c : 7c0803a6 mtspr %r0, %spr8
00010010 : 90010000 stw %r0, 0(%r1)
00010014 : 48000039 bl 56
00010018 : 60000000 ori %r0, %r0, 0
0001001c : 60000000 ori %r0, %r0, 0
: <略>
ただ、バイナリを単純変換しているだけで、アセンブラ表記上見やすく
する為の省略表記を使った表示は行っていません。binutilsのobjdumpコマンドを
使用すれば、省略表記や-g付きコンパイルでのシンボルを含んだ逆アセンブルした
リストを生成することができます。
objdump --source hello > hello.dis
因みにhelloを逆アセンブルしてみると、printf()がputs()に置き換わっていると
いう少しやり過ぎな最適化の様が見られます(^^; printf()が自前関数だった場合の振舞い
は不明。
もっと詳しい使い方についてはobjdumpのマニュアルを参照してみて下さい。
システムコール実行(sc命令)時の振る舞いや、ppcsimで実行前に行う内部処理には
以下のようなものがあります。
- プロセス管理
- Ver0.05まではプロセスという概念はありませんでしたが、Ver0.50以降ではUNIXライクな
fork()システムコールのエミュレーションを行うため、プロセスという概念を取り
入れています。
fork()を実行すると、8MBのメモリサイズで子プロセスを割り当て、レジスタやメモリ
内容を親プロセスからコピーし、実行は子プロセスのfork()を抜けた所から実行を
続けます。興味のある方はニセshのソースを参照してみてください。
wait()はnewlibコンパイル時のシステムコールヘッダ内(include/powerx/syscall.h)で
0を返すスタブ関数として定義してあります。ppcsim内ではシングルプロセス動作を
行っているのと等価なため、親プロセスから見た場合、fork()を抜けた時点で
子プロセスは終了しているイメージになります。従って 0を返すスタブ関数でも特に
問題は無いと思われます。
- メモリ管理
- newlibではsbrk()までが存在している事が前提になっていますので、sbrk()は
システムコールとして動作する様にしました。その為、管理上ヒープの先頭アドレス
が必要になります。デフォルトのヒープの先頭は0x00200000に設定されています。
set hp コマンドでアドレスを変更することもできます。
newlibでは、実行ファイルの_endシンボルの後ろからをheapとするのが流儀の様です
が、基本的にはプログラム領域と重ならないアドレスを指定しておけばOKです。
- 標準入出力
- シミュ実行しているプログラムのstdioは、ppcsimでオープンしたstdin,stdoutと
共通のファイルハンドルを使用します。考えられる問題点として、ppcsimでインタ
ラクティブモードで動作すると、標準入力したコマンドがそのままシミュレータ上で
動作しているプログラムでも使われる事になりますので、予期せぬ結果になる可能性
があります。
- ファイルオープンモード
- 今まではテキストモード/バイナリモードを切り替える事ができましたが、バイナリ
モードのみとなっています。その為、ターミナルへの標準出力表示が期待したもの
と異なります。ファイルにリダイレクトし、lessなどで見るぶんには問題無い
様です。
- スタックポインタ,argc,argvの渡し方
- execコマンドでは、SYSTEMV-ABIに従いppcsim内でトークン分離をして、
ポインタをレジスタ渡しで行います。
引き数文字列の格納場所および、スタックポインタの先頭は以下のように
セグメント0のメモリ領域の一番最後尾部分を使用しています。
0x00000000┌───────────────┐
│ユーザテキスト/データ領域 │
-0x????????├───────────────┤
│スタック領域 │
-0x00001000├───────────────┤
│argvポインタリスト( 256byte) │
-0x00000f00├───────────────┤
│argv文字列群 (3840byte) │
0x0XXXXXXX└───────────────┘
SPはGPR1,argcはGPR3,argvはGPR4にそれぞれ格納されます。
引き数文字列が領域に収まらない場合はプロセスの起動に失敗します。
注意点として、スタックはアドレスの前方向に消費してゆきます。
そのため、スタックを大量に消費すると、データ領域を破壊する場合があります。
シミュレータではチェックできませんので、シミュレータ上で動作する
プログラムでスタックオーバーフローを起こさないように配慮する必要があります。
- exit()
- execで起動されたプロセスでexit()システムコールを実行すると、シミュレータは
exit()システムコールを実行したプロセスを破棄して、親プロセスに実行を移します。
goコマンド実行時にexit()システムコールを検出した場合は、プロセスを破棄しない
で実行を停止します。このときPCは変更しませんので、再びgoコマンドを実行すると
またexit()システムコールを検出して実行が停止されることになります。
完全にPowerPCの動作をシミュレートできている訳ではありません。
- 未実装だとまずいかも知れない未実装命令がある。
- 実装されていても結果が正しくない場合がある。
- キャッシュモデルのシミュレートができていない。
- メモリサイズはシミュレータを動作させるマシンのメモリサイズ(スワップ含む)に依存する。
- システムコールが埋め込みになっている(プログラム上のメンテナンス性の観点での課題点)。
このうち、2についてはプログラムの実行結果が、データ化けに見えたり、随分前に
実行された命令のシミュレートがおかしくて、後ろで実行される命令がおかしな
様に見えたりすることがあります。また、gcc,as,ldのバグが無いとも限りません
(実際 binutils-2.9.1のldはバグっていました(^^;)ので、もし期待通りに動作しない
場合は、ブレークポイントやステップ実行、コマンドファイルの自動実行などを
駆使して、その原因を突き止めてみてください。
できている「つもり」のものは、
- 例外モデルのシミュレート(ただし起動時は例外が発生した時点で停止するモードになっています)。
- MMUモデルのシミュレート。
- 一部のIO操作。
あくまで「つもり」なので、「実装されていても結果が正しくない場合がある。」は常に
つきまといます(^^;
実行する
ための最低限の事しか書いていないので、イマイチよく判らない所もあるかと思います。
判りにくいと思った点や、間違っている点などありましたら、ご指摘いただけると
幸いです。
プログラムとは全然関係無いのですが、emulationは競争するという意味がある様ですが、
最新のプロセッサに実行速度の点でかなうハズがありませんので、ppcsimはエミュレータ
ではなくシミュレータという事で。因みに、OSのシステムコール部分については
エミュレートという言い方を使用しています。
2000/05/04 : ppcsim-0.04+ppclibc2(based newlib)用に書き換えた。
2001/04/02 : ppcsim-0.50ベースに書き換えた。
2001/04/02 : 書き抜けていた部分やらをちょっこり修正。
2002/05/13 : ppcsim-0.84ベースに書き換えた。
2002/05/14 : wait()システムコールの説明を修正