ppcsim Ver0.96の使い方

目次

  1. ppcsimについて
  2. インストール
  3. 実行方法
  4. 実行例
  5. デバッグ例
  6. syscallの仕掛けなどについて
  7. 制限事項
  8. その他
  9. 履歴

ppcsimについて

ppcsimはPowerPCの命令コードをソフトウェア的に解釈して動作をシミュレートする ソフトウェアシミュレータです。Power-Xプロジェクトの先行実験検証用(本当は単なる お勉強用)に作成しました。本物PPCが使えれば良いのですが、Power-Xプロジェクト 自体が現実のものになるのかどうか不明であったので、労力はかかるけど設備投資が不要な ソフトシミュレータで、GNUツールのレベル検証であるかとか、性能見積もりであるだとか を行ってみようとTANEが勝手に行った実験の産物の一つです。

インストール

Cygwin版はソースのみの配布となっていますので、コンパイルする必要があります。 アーカイブを展開して、ディレクトリを移動し、 make を実行すれば ppcsim.exeが できあがります。

実行方法

コマンドシェル型のユーザインターフェースとなっています。

    > ppcsim


と実行する事でプロンプトが表示され、コマンド受け付け状態となります。 execコマンドでクロスコンパイル&アセンブル&リンクしたELFバイナリを実行する 事ができます (PPCクロス gcc/binutilsの作り方 もしくは PPC-Linuxバイナリ生成の為のクロスbinutils/gcc/glibcのビルド方法 を参照) 。命令単位実行のデバッガの様なイメージでしょうか。
Ver 0.05以前のイメージで、実行ファイルをシミュレータ内のメモリにロードし、 g(GO)コマンドで実行する事も可能です。こちらは、昔のPC-8801やPC-9801の N8x-BASICで、MONコマンドを使用した事のある方なら、そのまんまのイメージを 思い浮かべられるのではないかと思います(^^;
本ドキュメントでは、前者のexecコマンドを使用したELFバイナリの実行をベースに 記述しています。

元々X68kで開発を開始したという経緯があり、ユーザインターフェースに凝るつもりは 無かったのですが、readlineライブラリを使用したおかげでインタラクティブ入力 性能が妙に良くなりました(^^; ただ、一連のテストや初期化を毎回入力するのも面倒 なので、一連の操作をバッチファイルの様に列記して実行する事が可能となってい ます。

    > ppcsim -f foo.ppc


の様に実行する事でファイル内(この場合はfoo.ppc)に書かれたコマンド列を順に実行 します。全てのコマンドを実行し終えるとppcsimの実行は終了します。

-hもしくは--helpをコマンドラインオプションに指定する事で、簡単なコマンド ラインヘルプを表示します。

> ppcsim.exe --help
PPC Simulator for Cygwin Ver 0.96 1999-2004 TANE
Usage: ppcsim [-Options] [cmd arg1 arg2 ....]
 Options
  -M num or --text-memsize=num :
       Change default memory size for exec command & fork emulate
  -H num or --heaptop-adrs=num :
       Change default heap top for sbrk emulate
  -P num or --heap-memsize=num :
       Change default heap size for sbrk emulate
  -E num or --exec-start-adrs=num :
       Change default execute start address for exec command & fork emulate
  -S num or --stack-top-offset=num :
       Change default stack offset
  -L     or --linux-sc-emu :
       Change to Linux Syscall Emulation mode
  -o log or --outlog=log :
       Simulation log message output to file(default=stderr)
  -f ... or --file :
       Exec script file mode
  -h     or --help :
       Print this help

 if cmd or -f option is not specified, it start with interactive shell mode.


PPC-Linuxバイナリ生成の為のクロスbinutils/gcc/glibcのビルド方法 で作成したツールを使用して実行バイナリを作成した場合、-Lもしくは--linux-sc-emuを指定 する必要があります。

実行例

サンプルを実行してみます。 サンプルアーカイブ(test_08x.tar.gz)を展開し、test_08xディレクトリ の下に移動します。そしてppcsimを起動します。グラフィックウインドが開くと 共に、起動したターミナルでは以下のようなプロンプト表示状態となります。

test_08x> ppcsim.exe
PPC>


この状態で、execコマンドを使って実行バイナリである「hello」を実行してみます。
PPC> exec hello
hello ppcsim world
PPC>


「hello ppcsim world」はhello実行ファイルのソースhello.cを参照していただけ れば一目瞭然でしょう。
ppcsimの実行を終了するには exitコマンドを実行します。

PPC> exit
test_08x>


もっと簡単に、コマンドライン指定で実行を指示することもできます。

test_08x> ppcsim.exe hello
hello ppcsim world


ただし、デバッグに関するメッセージを表示したり設定する事はできませんので、 詳細に動作を確認するには、次のデバッグ例の様な使い方を行なう必要がある でしょう。

デバッグ例

バグの入ったプログラムを意図して作るのは難しい(^^;ので、hello実行ファイルの 実行をトレースしてみます。

hello実行ファイルのスタートアドレスを調べます。テストで使用するhello実行ファイル は以下の様なバイナリ配置になっています。

PPC> eload -i hello
[No] Name                 Adrs     Type     Size     Offset  
[ 0]                      00000000 00000000 00000000 00000000
[ 1] .text                00010000 00000001 000030c8 00010000
[ 2] .rodata              000130c8 00000001 0000001c 000130c8
[ 3] .sdata2              000130e4 00000001 00000000 000130e4
[ 4] .data                000230f0 00000001 000007a8 000130f0
[ 5] .sdata               00023898 00000001 0000000c 00013898
[ 6] .sbss                000238a4 00000008 00000010 000138a4
[ 7] .bss                 000238b4 00000008 00000028 000138a4
[ 8] .comment             00000000 00000001 00000500 000138a4
[ 9] .shstrtab            00000000 00000003 00000052 00013da4
[10] .symtab              00000000 00000002 00000a20 00013fd8
[11] .strtab              00000000 00000003 0000055e 000149f8

「.text」セクションがプログラム本体となります。この場合、.textの Adrsの欄 0x00010000がプログラムのスタートアドレスとなります。

ppcsimを実行して、ブレークポイントを設定します。

PPC> bp 0x10000 + start_adrs
Setting bp=0x00010000 , BPID=1 , label=start_adrs , PID=1


'+'は現在のプロセスID+1のプロセス番号を示します。execコマンドで実行される実行 ファイルは、現在のID +1のプロセスというイメージで起動されます。そのため、実行前の状態 で新しく起動されるプロセスのブレークポイントを指定する為のものです。
また、Setting ..... , BPIDはブレークポイント自身のIDを示します。ブレークポイント を削除する際はこのBPIDを指定します。 「start_adrs」はブレークポイントに付けた任意の名前です。空白を含まない形で自由に 指定して構いません。複数のブレークポイントを設定する場合、名前を指定しておくと、 どの部分のブレークポイントであるかが判りやすくなると思います。
引き数無しでbpコマンドを実行すると、現在のブレークポイント一覧が表示されます。

PPC> bp
--- Break Point(s) for Process ID =  1 --
No.00 : 0x00010000 start_adrs
PPC>


実行ファイルhelloを実際に実行してみます。
ブレークポイントで停止した旨のメッセージが表示されて停止します。

PPC> exec hello
Exec stop by break point.
  1 : 00010000 : 54210036    rlwinm     %r1, %r1, 0, 0, 27
PPC>


実際に停止したアドレスと そのアドレスに置かれている命令の逆アセンブルリストが 表示されています。

この状態でレジスタの内容を表示するにはdumpコマンド(もしくはdコマンド)を実行します。

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  FPSCR : 00000000
CTR   : 00000000  HID1  : 00000000  SDR1  : 00000000
GPR   : 
00-07 : 00000000 007fef00 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


ステップ実行してみます。stepコマンド(もしくはsコマンド)を実行します。

PPC> s
PPC> 


アドレス0x10000の命令が1つ実行されているのですが、表示上は見えません(^^; そこで、traceコマンドを使用して実行した命令のリストを表示するようにし 5命令実行してみます。

PPC> trace pc
PPC> s 5
  1 : 00010004 : 38000000    addi       %r0, %r0, 0
  1 : 00010008 : 9421fff0    stwu       %r1, -16(%r1)
  1 : 0001000c : 7c0803a6    mtspr      %r0, %spr8
  1 : 00010010 : 90010000    stw        %r0, 0(%r1)
  1 : 00010014 : 48000039    bl 56


更に続けてステップ実行してみます。

PPC> s
  1 : 0001004c : 9421ffe0    stwu       %r1, -32(%r1)
  1 : 00010050 : 7c0802a6    mfspr      %r0, %spr8
  1 : 00010054 : 7d2b4b78    or %r11, %r9, %r9
  1 : 00010058 : 90010024    stw        %r0, 36(%r1)
  1 : 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起動時に必ず生成されます。また、execコマンドで生成されるプロセスの プロセスサイズはデフォルト8MB固定となっていますので、メモリサイズに不都合 がある場合は、ppcsimのソースを変更する必要があります(ここはコマンドで 変更できる様にしたいところですね)。

実行を再開するには、cコマンドを実行します。ここで注意点ですが、cコマンドで 続きを実行する際も、traceコマンドでの実行時の表示状態に従います。ここで cコマンドを実行すると実行した命令がダンプされ続けますので、一度traceを offにします。この点、前よりも面倒になっているのがイマイチですね(^^;。

PPC> trace off
PPC> c
hello ppcsim world


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実行中に簡単なコマンドヘルプを表示できます。helpコマンドで ビルトインコマンドの一覧を表示する事ができます。

PPC> help
List of builtin commands
memal   : Allocate memory          , load    : Load file to memory      
save    : Save file from memory    , set     : Set Register/Memory value
dump    : Dump Register/Memory     , d       : Same "dump" command      
go      : Start simulation         , g       : Same "go" command        
step    : Step execution           , s       : Same "step" command      
td      : Print trace history      , bp      : Set break point          
pp      : Set pass point           , truss   : Set truss mode           
exec    : Load ELF-binary & exec   , c       : Continue execution       
kill    : Kill current process     , ps      : Print process list       
trace   : Set trace mode           , ppmsg   : Set pass point message   
eload   : Load ELF-binary          , sim     : Set exception simlate status
mount   : Mount DISK device        , umount  : Unmount DISK device      
dload   : Load from DISK device    , dsave   : Save to DISK device      
sum     : Print execution summary  , dis     : Disassemble              
run     : Run ppcsim-script        , prob    : Probe memory             
dbp     : Delete break point       , dpp     : Delete pass point        
help    : Listing builtin commands , 


各コマンドは-hもしくは--helpを引数に与える事で、コマンド毎の詳細な 使い方を表示する事ができます。

PPC> exec --help
Loading ELF file and execute
Usage: exec [Options] file [arg1 arg2 ...]
 Options
  -b or --break : 実行ファイルロード後、プロセスを停止します
  -h or --help  : Print this help.

     ELF形式の実行ファイルをロードし実行します。


SJIS表示のヘルプになっている為、ターミナルがSJIS表示に対応している 必要があります。

ppcsim-0.8xでは命令列の逆アセンブルリストを表示できるようになりました。

PPC> dis 0x10000
  1 : 00010000 : 54210036    rlwinm     %r1, %r1, 0, 0, 27
  1 : 00010004 : 38000000    addi       %r0, %r0, 0
  1 : 00010008 : 9421fff0    stwu       %r1, -16(%r1)
  1 : 0001000c : 7c0803a6    mtspr      %r0, %spr8
  1 : 00010010 : 90010000    stw        %r0, 0(%r1)
  1 : 00010014 : 48000039    bl 56
  1 : 00010018 : 60000000    ori        %r0, %r0, 0
  1 : 0001001c : 60000000    ori        %r0, %r0, 0
    :    <略>


フォーマットは「PID : アドレス : 命令コード 命令 オペランド」に なっています。 バイナリを単純変換しているだけなので、あまり読みやすくありません。 アセンブラ表記上見やすくする為の省略表記を使った表示は、binutilsの objdumpコマンドで出力する事ができます。省略表記以外にも、-g付き コンパイルでのシンボルを含んだ逆アセンブルしたリストを生成することも できます。

powerpc-linux-objdump --source hello > hello.dis


因みにhelloを逆アセンブルしてみると、printf()がputs()に置き換わっていると いう少しやり過ぎな最適化の様が見られます(^^; printf()が自前関数だった場合の振舞い は不明。

もっと詳しい使い方についてはobjdumpのマニュアルを参照してみて下さい。

syscallの仕掛けなどについて

システムコール実行(sc命令)時の振る舞いや、ppcsimで実行前に行う内部処理には 以下のようなものがあります。

プロセス管理

Ver0.05まではプロセスという概念はありませんでしたが、Ver0.50以降ではUNIXライクな fork()システムコールのエミュレーションを行うため、プロセスという概念を取り 入れています。
fork()を実行すると、子プロセスを割り当て、レジスタやメモリ内容を親プロセスから コピーし、実行は子プロセスのfork()を抜けた所から実行を続けます。興味のある方は ニセshのソースを参照してみてください。
wait()はnewlibコンパイル時のシステムコールヘッダ内(include/powerx/syscall.h)で 0を返すスタブ関数として定義してあります。ppcsim内ではシングルプロセス動作を 行っているのと等価なため、親プロセスから見た場合、fork()を抜けた時点で 子プロセスは終了しているイメージになります。従って 0を返すスタブ関数でも特に 問題は無いと思われます。

メモリ管理

newlibではsbrk()までが存在している事が前提になっていますので、sbrk()は システムコールとして動作する様にしました。その為、管理上ヒープの先頭アドレス が必要になります。デフォルトのヒープの先頭は0x01000000に設定されています。 set hp コマンドでアドレスを変更することもできます。
newlibでは、実行ファイルの_endシンボルの後ろからをheapとするのが流儀の様です が、基本的にはプログラム領域と重ならないアドレスを指定しておけばOKです。

標準入出力

シミュ実行しているプログラムのstdioは、ppcsimでオープンしたstdin,stdoutと 共通のファイルハンドルを使用します。考えられる問題点として、ppcsimでインタ ラクティブモードで動作すると、標準入力したコマンドがそのままシミュレータ上で 動作しているプログラムでも使われる事になりますので、予期せぬ結果になる可能性 があります。

ファイルオープンモード

今まではテキストモード/バイナリモードを切り替える事ができましたが、バイナリ モードのみとなっています。その為、ターミナルへの標準出力表示が期待したもの と異なります。ファイルにリダイレクトし、lessなどで見るぶんには問題無い 様です。

スタックポインタ,argc,argvの渡し方

execコマンドでは、SYSTEMV-ABIに従いppcsim内でトークン分離をして、 ポインタをレジスタ渡しで行います。 引き数文字列の格納場所および、スタックポインタの先頭は以下のように テキストセグメントのメモリ領域の一番最後尾部分を使用しています。

      0x00000000┌───────────────┐
                │ユーザテキスト/データ領域     │
     -0x????????├───────────────┤
                │スタック領域                  │
     -0x00001000├───────────────┤
                │argvポインタリスト( 256byte)  │
     -0x00000f00├───────────────┤
                │argv文字列群      (3840byte)  │
      0x00800000└───────────────┘
      


SPはGPR1,argcはGPR3,argvはGPR4にそれぞれ格納されます。
引き数文字列が領域に収まらない場合はプロセスの起動に失敗します。 注意点として、スタックはアドレスの前方向に消費してゆきます。 そのため、スタックを大量に消費すると、データ領域を破壊する場合があります。 シミュレータではチェックできませんので、シミュレータ上で動作する プログラムでスタックオーバーフローを起こさないように配慮する必要があります。

exit()

execで起動されたプロセスでexit()システムコールを実行すると、シミュレータは exit()システムコールを実行したプロセスを破棄して、親プロセスに実行を移します。 goコマンド実行時にexit()システムコールを検出した場合は、プロセスを破棄しない で実行を停止します。このときPCは変更しませんので、再びgoコマンドを実行すると またexit()システムコールを検出して実行が停止されることになります。

制限事項

完全にPowerPCの動作をシミュレートできている訳ではありません。

  1. 未実装だとまずいかも知れない未実装命令がある。
  2. 実装されていても結果が正しくない場合がある。
  3. キャッシュモデルのシミュレートができていない。
  4. メモリサイズはシミュレータを動作させるマシンのメモリサイズ(スワップ含む)に依存する。
  5. システムコールが埋め込みになっている(プログラム上のメンテナンス性の観点での課題点)。

このうち、2についてはプログラムの実行結果が、データ化けに見えたり、随分前に 実行された命令のシミュレートがおかしくて、後ろで実行される命令がおかしな 様に見えたりすることがあります。また、gcc,as,ldのバグが無いとも限りません (実際 binutils-2.9.1のldはバグっていました(^^;)ので、もし期待通りに動作しない 場合は、ブレークポイントやステップ実行、コマンドファイルの自動実行などを 駆使して、その原因を突き止めてみてください。
できている「つもり」のものは、
  1. 例外モデルのシミュレート(ただし起動時は例外が発生した時点で停止するモードになっています)。
  2. MMUモデルのシミュレート。
  3. 一部のIO操作。

あくまで「つもり」なので、「実装されていても結果が正しくない場合がある。」は常に つきまといます(^^;

その他

実行する ための最低限の事しか書いていないので、イマイチよく判らない所もあるかと思います。 判りにくいと思った点や、間違っている点などありましたら、ご指摘いただけると 幸いです。

全然関係無い話ですが、PPCSIMは「シミュレータ」という位置付けに しています。シミュレータとエミュレータの呼び方の違いについて、正確な定義が あるのか判りませんが、この場では以下のような定義で区別しています。

●シミュレータ
作成したプログラムは実機で動作させる事を最終目的としている。 この時、実機が存在していない場合の先行開発用テストベッドとして使用する ソフトウェアによるプログラム実行環境をシミュレータと定義する。
●エミュレータ
最終的に動作させるプログラムは実機ではなく、ソフトウェアによるプログラム 実行環境のみでしか実行しない場合、このプログラム実行環境をエミュレータと 定義する。

使う用途によって、シミュレータと呼んだり、エミュレータと呼んだり、呼び方が変わる という定義です。PPCSIM上で動作するプログラムが、最終的に実CPU上で動作させるのが 目的であれば、PPCSIMはシミュレータの役割を果たしている事になり、単に PPCSIM上でしか動作させないのであれば、PPCSIMはエミュレータという事になります。 一応、実CPU上で動作させるプログラムを実機無しの状態でも開発/デバッグする為のツール というポリシーを持っていますので、シミュレータを名乗らせていただきたいと 思います。
因みに、sc命令で発生するシステムコール例外を、OS上でのシステムコール動作の様に 模する事には、「エミュレート」という言い方を使用しています。 OSシステムコールを使用する実行バイナリを、そのままネイティブのOS上で 動作させる事は殆ど無いと思われます。その為、OSシステムコールを使用した プログラムを開発する場合は、エミュレータとしての役割を果たす要素の方が 大きいと思われますので、OSシステムコールの実装をエミュレートと呼んでいます (ここ、論理展開が少し強引な感じはしますが(^^;)。

OSの開発だとかに役立って欲しいというのがPPCSIMの本意なのですが、そこまでの用途には まだまだ耐えられないというのが、実際のところです。 クロス開発というマイナーな行為自体に興味を引くために、通常OS上で動作する プログラムを書くノリ(例えば、OS上でexec()システムコールにより読み込まれた プログラムの真の実行開始位置はmain()関数では無く、 _startラベルだったりするのですが、それを意識しているプログラマはそんなに 多くは無いと思います)から導入しようと考えたのですが、そちらに力を入れすぎて 目的が少しすり替わっているような気がしなくもない今日この頃です。

履歴

  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()システムコールの説明を修正
  2004/09/18 : ppcsim-0.96ベースに書き換えた。
  2004/10/04 : 誤記の修正やら文章の追加などを行なった。


TOP PREV