(シミュレータ編第1回)とりあえず動かしてみた

2010/09/06

あなたは 人目のお客様です.

ここんとこセプキャン関連とかOSCの準備とかセミナー準備とか勉強会の準備とか なにかと忙しくて,自宅でもメールの返事とか準備とかいろいろとやっているうちに 時間が過ぎてしまってあまりKOZOSの開発ができていない. TCP/IP実装とかROM化とかGDB対応とか,いろいろやりたいことはあるのだが.

実際に開発しようとすると,マイコンボードの実機が無いと動作させられないのが まずネックになっている. ということで,エミュレータとかシミュレータのような仮想環境で動作させることが できれば電車の中とか出先のちょっとした時間とかでも開発できるので, ちょっとエミュレーション環境での動作を考えてみることにした.

webで検索すると,H8用のエミュレータはいくつかあるようだ.しかしぼくは ふだんはGNU環境で開発しているのと,KOZOSのポリシーとしてGNU環境での開発を 主軸に置いているので,やっぱりGDBが使いたい.ということで,GDBにH8用の シミュレータ環境は付属していないかと思ってみてみたら,あった. ということで,GDB付属のH8シミュレータを利用するという方向でいくことに あっさり決定.やっぱりGDB使えると便利だし,なんせ無料だし,つぶしがきくしねえ.

参考までに説明しておくと,GDBはいくつかのCPU用のエミュレータも付属して 持っていて(ていうかいくつかのCPU用エミュレータがGDB対応した状態でいっしょに なって配布されている),そのCPU用にGDBをビルドするとエミュレータも自動で同時に ビルドされて,エミュレータ上でも(そのままというのはちょっと無理かもしれないが, ちょっと手を加えれば)動作させられるようになっている. エミュレータ用のコードはGDBを解凍したときに「sim」というディレクトリに まとめられている.ということでここでは「シミュレータ」と呼ぶことにする.

で,gdbを解凍してsimディレクトリを見てみると以下のようになっていて 「h8300」というディレクトリが存在する.

hiroaki@teapot:~/h8/gcc/orig/gdb-7.2>% cd sim
hiroaki@teapot:~/h8/gcc/orig/gdb-7.2/sim>% ls
ChangeLog       configure       frv             m68hc11         rx
MAINTAINERS     configure.ac    h8300           mcore           sh
Makefile.in     configure.tgt   igen            microblaze      sh64
README-HACKING  cr16            iq2000          mips            testsuite
arm             cris            lm32            mn10300         v850
avr             d10v            m32c            moxie
common          erc32           m32r            ppc
hiroaki@teapot:~/h8/gcc/orig/gdb-7.2/sim>% cd h8300/
hiroaki@teapot:~/h8/gcc/orig/gdb-7.2/sim/h8300>% ls
ChangeLog       compile.c       configure       inst.h          tconfig.in
Makefile.in     config.in       configure.ac    sim-main.h      writecode.c
hiroaki@teapot:~/h8/gcc/orig/gdb-7.2/sim/h8300>% 
ただこれだけではまだ安心できなくて, 「H8/300」には対応しているかもしれないけど,「H8/300H」には対応していないかも しれない(私がKOZOSの動作によく利用しているマイコンボードはH8/3069FというCPUが 搭載されていて,これはH8/300HというシリーズのCPUです).

で,h8300ディレクトリの中のファイルの中身を軽くみてみたのだけど, たとえば compile.c とかを見る限り,H8/300H対応もされているように思える. ということで,H8/3069F用のファームウエアを動作させることはできそうに思える.

ただそのまま何もせずに動作させることは無理だろうと思うがとりあえずGDBを ビルドして,動作させてみよう.

まずGDBなのだけど,GNUのサイトから以下をダウンロードした.

解凍して,以下でビルドできた.ちなみに環境はFreeBSD-6.2だけど,GNU/Linuxでも たぶん同じ感じでいけると思う.--disable-nls が無いとエラーで止まってしまうので 注意.
% ./configure --target=h8300-elf --disable-nls
% gmake
上で gdb-7.2/gdb/gdb という実行形式が作成される.これがGDBの実行ファイル.

次にKOZOSのソースコードの準備. KOZOSの書籍用のソースコードの第12回の ブートローダーのソースにとりあえず以下の変更を加えてビルドする. 今回はとりあえずブートローダーを動作させてみる(OSは動作させない). まあOSよりもブートローダーのほうがはるかに動作が簡単だからね.割り込みとか 考えなくていいし.

--- osbook_03/12/bootload/Makefile.orig   Sun Feb 28 23:23:47 2010
+++ osbook_03/12/bootload/Makefile        Mon Sep  6 23:51:33 2010
@@ -27,9 +27,10 @@
 CFLAGS = -Wall -mh -nostdinc -nostdlib -fno-builtin
 #CFLAGS += -mint32 # intを32ビットにすると掛算/割算ができなくなる
 CFLAGS += -I.
-#CFLAGS += -g
+CFLAGS += -g
 CFLAGS += -Os
 CFLAGS += -DKZLOAD
+CFLAGS += -DSIMULATOR
 
 LFLAGS = -static -T ld.scr -L.
 
まあMakefileをちょろっと修正しただけなのだが, まずデバッグ用シンボルを追加するために,-gオプションを追加している. これが無いとGDBにロードしたときにシンボルが無いと忠告される. 実はシンボル無くても実行はできるしデバッグもできるのだけど, 変数名とか関数名が使えなくなってしまうので,とりあえずつけておく. あと -DSIMULATOR を追加して,シミュレータ特有のコードを書くときに #ifdef SIMULATOR で切り分けられるようにしておく.

で,先ほどビルドしたgdbを以下のようにしてファームウエア指定で起動する.

% ./gdb-7.2/gdb/gdb ../sim/osbook_03/12/bootload/kzload.elf
注意として,実機にh8writeでブートローダーを書き込むときは「kzload」を ファイルに指定しているが,ここではkzload.elfをGDBに渡している. kzloadは実機書き込み用に余分な情報をそぎ落としたファイルだけど, 今回は実機でなくGDBに渡すので,そのようなことは考えなくていい,ていうか, せっかくデバッガが使えるのにデバッグ情報とか落としてしまってはもったいない. ということで strip でデバッグ情報を落とす前のELFファイルをGDBに渡している.

で,gdbが起動したら

(gdb) target sim
(gdb) load
(gdb) run
を実行すると,シミュレータ上でファームウエアを動作開始する.やってみよう.
hiroaki@teapot:~/h8/gcc>% ./gdb-7.2/gdb/gdb ../sim/osbook_03/12/bootload/kzload.elf
GNU gdb (GDB) 7.2
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later 
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=i386-unknown-freebsd6.2 --target=h8300-elf".
For bug reporting instructions, please see:
...
Reading symbols from /home/hiroaki/h8/sim/osbook_03/12/bootload/kzload.elf...done.
(gdb) target sim
Connected to the simulator.
(gdb) load
Loading section .vectors, size 0x100 vma 0x0
Loading section .text, size 0x900 vma 0x100
Loading section .rodata, size 0xc9 vma 0xa00
Loading section .data, size 0x10 vma 0xfffc20
Start address 0x100
Transfer rate: 22216 bits in <1 sec.
(gdb) run
Starting program: /home/hiroaki/h8/sim/osbook_03/12/bootload/kzload.elf 

うーん,予想どおりだが何も反応が無い.

この状態でCtrl-Cで動作中断し,gdbコマンドで動作状態を簡単に調べることができる. こういうのがシミュレータ動作の便利でいいところだ. gdbコマンドについてはweb上で探せばすぐ探せると思うので探してみて. 以下は where でスタックトレースをダンプした結果.関数の呼び出し階層が 出力されている.up/downで関数呼び出し元をたどることもできるし, emacsを使っていれば,ソースコードが同期して表示されるのでとっても便利. (emacsの利用に関しては(第10回)いよいよGDB対応だ! あたりからの説明を参照してください)

^C
Program received signal SIGINT, Interrupt.
serial_send_byte (index=1, c=107 'k') at serial.c:84
84        while (!serial_is_send_enable(index))
(gdb) where
#0  serial_send_byte (index=1, c=107 'k') at serial.c:84
#1  0x000005aa in putc (c=107 'k') at lib.c:80
#2  0x000005fa in puts (str=0xa01 "zload (kozos boot loader) started.\n")
    at lib.c:96
#3  0x00000266 in main () at main.c:72
(gdb) 
起動時メッセージの「kzload (kozos boot loader) started.\n」の先頭の「k」を 出力しようとして,止まっているようだ. どうやらシリアル送信待ちのビジーループで,SCIの送信完了フラグが立つことがなくて 無限ループになっているように思える.

まあSCI対応とかされていなければ当然だろうなあ.送信完了ビットが立つことは 無いわけだし.

で,ちょっと調べてみたら,以下のページでシリアル出力を標準出力に出す方法が 説明してあった.ありがたや,ありがたや.

上記ページはGDB上でH8用のファームをシミュレータ動作させる方法について いろいろ書かれていて,たいへん参考になります.

上記ページの「文字列出力(printf)をシミュレータと実機両方で使えるようにする」 あたりに詳しい説明があるのだが,このH8シミュレータは 「ER2に文字を設定して jsr 0xc4 すると,実際にジャンプはせずに設定された文字を 標準出力に出力する」という動作をするとのこと.

これをソースコード中では magic trap と呼んでいる.このようにしておけば, たとえば実機で動作させる場合には,0xc4の位置に出力用のハンドラを置いておけば 実際に出力が行われるわけなので,同一ファームで実機でもシミュレータでも 動作させられるというわけだ.うーん,うまい作りだなあ.

実際に各種のシミュレータでは,このように特殊な特定の処理 (ここでは0xc4へのジャンプ)を行うとシミュレータ特有の動作 (ここでは標準出力への1文字出力)を行うような作りにしてあって, 外部とのやりとりが簡便にできるようにしてある場合が多い.

実際にGDB付属のH8シミュレータのソースコードを見てみよう. 今回利用しているgdb-7.2では,sim/h8300/compile.c で以下のような部分がある.

		  /* And jsr's to these locations are turned into 
		     magic traps.  */

		  if (OP_KIND (dst->opcode) == O_JSR)
		    {
		      switch (dst->src.literal)
			{
			case 0xc5:
			  dst->opcode = O (O_SYS_OPEN, SB);
			  break;
			case 0xc6:
			  dst->opcode = O (O_SYS_READ, SB);
			  break;
			case 0xc7:
			  dst->opcode = O (O_SYS_WRITE, SB);
			  break;
			case 0xc8:
			  dst->opcode = O (O_SYS_LSEEK, SB);
			  break;
			case 0xc9:
			  dst->opcode = O (O_SYS_CLOSE, SB);
			  break;
			case 0xca:
			  dst->opcode = O (O_SYS_STAT, SB);
			  break;
			case 0xcb:
			  dst->opcode = O (O_SYS_FSTAT, SB);
			  break;
			case 0xcc:
			  dst->opcode = O (O_SYS_CMDLINE, SB);
			  break;
			}
		      /* End of Processing for system calls.  */
		    }
つまり jsr 0xc5 〜 jsr 0xcc の間に,いくつかのマジックトラップが割り当てられて いるようだ.先ほど紹介したホームページの説明では0xc4のみを利用している みたいで,gdb は gdb-4.17 を利用しているようなので,gdb-7.2になってだいぶ 拡張されているように思える.

ここで注目すべきなのは以下の部分だ.

			case 0xc6:
			  dst->opcode = O (O_SYS_READ, SB);
			  break;
			case 0xc7:
			  dst->opcode = O (O_SYS_WRITE, SB);
			  break;
つまり jsr 0xc6 がリード,jsr 0xc7 がライトに割り当てられている. これを利用すれば文字の入出力ができそうだ.

マジックトラップの処理部分は以下のようになっている.

	case O (O_SYS_READ, SB):
	  {
	    char *char_ptr;	/* Where characters read would be stored.  */
	    int fd;		/* File descriptor */
	    int buf_size;	/* BUF_SIZE parameter in read.  */
	    int i = 0;		/* Temporary Loop counter */
	    int read_return = 0;	/* Return value from callback to
					   read.  */

	    fd = (h8300hmode && !h8300_normal_mode) ? GET_L_REG (0) : GET_W_REG (0);
	    buf_size = (h8300hmode && !h8300_normal_mode) ? GET_L_REG (2) : GET_W_REG (2);

	    char_ptr = (char *) malloc (sizeof (char) * buf_size);

	    /* Callback to read and return the no. of characters read.  */
	    read_return =
	      sim_callback->read (sim_callback, fd, char_ptr, buf_size);

	    /* The characters read are stored in cpu memory.  */
	    for (i = 0; i < buf_size; i++)
	      {
		SET_MEMORY_B ((h8_get_reg (sd, 1) + (sizeof (char) * i)),
			      *(char_ptr + (sizeof (char) * i)));
	      }

	    /* Return value in Register 0.  */
	    h8_set_reg (sd, 0, read_return);

	    /* Freeing memory used as buffer.  */
	    free (char_ptr);
	  }
	  goto next;

	case O (O_SYS_WRITE, SB):
	  {
	    int fd;		/* File descriptor */
	    char temp_char;	/* Temporary character */
	    int len;		/* Length of write, Parameter II to write.  */
	    int char_ptr;	/* Character Pointer, Parameter I of write.  */
	    char *ptr;		/* Where characters to be written are stored. 
				 */
	    int write_return;	/* Return value from callback to write.  */
	    int i = 0;		/* Loop counter */

	    fd = (h8300hmode && !h8300_normal_mode) ? GET_L_REG (0) : GET_W_REG (0);
	    char_ptr = (h8300hmode && !h8300_normal_mode) ? GET_L_REG (1) : GET_W_REG (1);
	    len = (h8300hmode && !h8300_normal_mode) ? GET_L_REG (2) : GET_W_REG (2);

	    /* Allocating space for the characters to be written.  */
	    ptr = (char *) malloc (sizeof (char) * len);

	    /* Fetching the characters from cpu memory.  */
	    for (i = 0; i < len; i++)
	      {
		temp_char = GET_MEMORY_B (char_ptr + i);
		ptr[i] = temp_char;
	      }

	    /* Callback write and return the no. of characters written.  */
	    write_return = sim_callback->write (sim_callback, fd, ptr, len);

	    /* Return value in Register 0.  */
	    h8_set_reg (sd, 0, write_return);

	    /* Freeing memory used as buffer.  */
	    free (ptr);
	  }
	  goto next;
軽く見たところ,以下のようになっているようだ. で,シリアルの入出力で上記のように jsr のマジックトラップを利用するように ブートローダーの serial.c を書き換えてみた.
--- osbook_03/12/bootload/serial.c.orig	Tue Sep  7 00:57:23 2010
+++ osbook_03/12/bootload/serial.c	Tue Sep  7 01:31:09 2010
@@ -64,49 +64,80 @@
   sci->brr = 64; /* 20MHzのクロックから9600bpsを生成(25MHzの場合は80にする) */
   sci->scr = H8_3069F_SCI_SCR_RE | H8_3069F_SCI_SCR_TE; /* 送受信可能 */
   sci->ssr = 0;
 
   return 0;
 }
 
 /* 送信可能か? */
 int serial_is_send_enable(int index)
 {
+#ifndef SIMULATOR
   volatile struct h8_3069f_sci *sci = regs[index].sci;
   return (sci->ssr & H8_3069F_SCI_SSR_TDRE);
+#else
+  return 1;
+#endif
 }
 
 /* 1文字送信 */
 int serial_send_byte(int index, unsigned char c)
 {
+#ifndef SIMULATOR
   volatile struct h8_3069f_sci *sci = regs[index].sci;
 
   /* 送信可能になるまで待つ */
   while (!serial_is_send_enable(index))
     ;
   sci->tdr = c;
   sci->ssr &= ~H8_3069F_SCI_SSR_TDRE; /* 送信開始 */
 
   return 0;
+#else
+  char buf[1];
+  buf[0] = c;
+
+  asm volatile ("mov.l #1,er0");
+  asm volatile ("mov.l %0,er1" :: "r"(buf));
+  asm volatile ("mov.l #1,er2");
+  asm volatile ("jsr 0xc7");
+
+  return 0;
+#endif
 }
 
 /* 受信可能か? */
 int serial_is_recv_enable(int index)
 {
+#ifndef SIMULATOR
   volatile struct h8_3069f_sci *sci = regs[index].sci;
   return (sci->ssr & H8_3069F_SCI_SSR_RDRF);
+#else
+  return 1;
+#endif
 }
 
 /* 1文字受信 */
 unsigned char serial_recv_byte(int index)
 {
+#ifndef SIMULATOR
   volatile struct h8_3069f_sci *sci = regs[index].sci;
   unsigned char c;
 
   /* 受信文字が来るまで待つ */
   while (!serial_is_recv_enable(index))
     ;
   c = sci->rdr;
   sci->ssr &= ~H8_3069F_SCI_SSR_RDRF; /* 受信完了 */
 
   return c;
+#else
+  char buf[1];
+
+  asm volatile ("mov.l #0,er0");
+  asm volatile ("mov.l %0,er1" :: "r"(buf));
+  asm volatile ("mov.l #1,er2");
+  asm volatile ("jsr 0xc6");
+
+  return buf[0];
+#endif
 }
修正点はシミュレータ用なので,#ifdef SIMULATOR 〜 #endif でくくってある. あと送受信が可能かどうかの判断用の serial_is_send_enable(),serial_is_recv_enable() は, 常に1(可能)を返すように修正.

で,ブートローダーをビルドしなおす.serial.cのコンパイルの際に以下の ワーニングが出力されるのだが,jsrの飛び先が奇数(0xc7)になっているとの ことなので,マジックトラップのためにわざとやってることなので気にしない.

/usr/local/bin/h8300-elf-gcc -c -Wall -mh -nostdinc -nostdlib -fno-builtin -I. -g -Os -DKZLOAD -DSIMULATOR serial.c
/var/tmp//ccJQfIdQ.s: Assembler messages:
/var/tmp//ccJQfIdQ.s:83: Warning: branch operand has odd offset (c7)

gdb上で動作させてみよう.
hiroaki@teapot:~/h8/gcc>% ./gdb-7.2/gdb/gdb ../sim/osbook_03/12/bootload/kzload.elf
GNU gdb (GDB) 7.2
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later 
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=i386-unknown-freebsd6.2 --target=h8300-elf".
For bug reporting instructions, please see:
...
Reading symbols from /home/hiroaki/h8/sim/osbook_03/12/bootload/kzload.elf...done.
(gdb) target sim
Connected to the simulator.
(gdb) load
Loading section .vectors, size 0x100 vma 0x0
Loading section .text, size 0x88c vma 0x100
Loading section .rodata, size 0xc9 vma 0x98c
Loading section .data, size 0x10 vma 0xfffc20
Start address 0x100
Transfer rate: 21288 bits in <1 sec.
(gdb) run
Starting program: /home/hiroaki/h8/sim/osbook_03/12/bootload/kzload.elf 
kzload (kozos boot loader) started.
kzload> 
おー,起動メッセージが出力された.

文字入力をしてみよう.

kzload> echo test
echo test
unknown.
kzload> test
test
unknown.
kzload> 
そういやブートローダーでは echo コマンド無いんだったっけか.まあでも ちゃんと unknown が出力されている.文字入力もよさそうだ.

GDBのシミュレータ上で動作すると,デバッグが格段に楽になる. ブレークポイント設定やステップ実行,変数の値参照などが思いのままだからだ. うーん,これ,いいなあ.

まだまだ不備もいっぱいあるし,実用のためには考慮しなければならないことだらけ だが,とりあえずシミュレータ上での入出力はできた.よかったよかった.


メールは kozos(アットマーク)kozos.jp まで