CPU Collection

この間、見る気は無く、何でも鑑定団 なんてのを見てたら(再再放送ぐらいのやつですかね) 昔々のコンピュータキット、TLCS-12が出品されてた。この 番組を見てた人も懐かしさに駆られてかブログを 書いてましたよ。

骨董品値段で、100万円ですって! 世界初のマイクロコンピュータ、 i4004が出品されたら、幾らの値段を付けるんだろう? 日本でも、大事に大事に持っている人が、そこかしこに居そうだな。

実物を持っているか分かりませんが探してみると、 CPU worldなんてのが出てきました。一般に手に入るのは、 インテル系なんですかね。日本国内だと、 私の知っているCPUコレクターさんは、ちょっと幅広く 集めておられるようです。

実物が無くても、持ってる気分にさせてくれる魔法が有ります。火を入れればちゃんと動く所 が凄い、CPUワールドが有るんです。

魔法な所

MIPSの資料を求めてうろうろしてた時に見つけましたよ。魔法な所を。 フィーリングで読むアセンブラ入門がそれです。

アセンブラ出力だけで、39種。HelloWorldの出力だけで、18種を誇るコレクターぶり。 CPUのバリエーションも、インテル系だけなんて隔たっておらず、なんでもみさかいなく (これ、褒め言葉ですよ)収拾してるのがいいですね。

特に嬉しいのは、gdb付属の実行環境を使って、あれこれ出来る事でしょう。

# native architectures.
TARGETS_NATIVE += i386-elf.d

# major architectures.
TARGETS_SIM += arm-elf.d h8300-elf.d mips-elf.d powerpc-elf.d sh-elf.d

# other architectures.
TARGETS_SIM += avr-elf.d cris-elf.d frv-elf.d m32r-elf.d m6811-elf.d
TARGETS_SIM += mcore-elf.d mn10300-elf.d sh64-elf.d sparc-elf.d v850-elf.d

# use compiler for main architecture.
TARGETS_SIM += arm16-elf.d mips16-elf.d

折角なので、おいらも何か動かしてみよっと。いろいろ有りすぎて目移りしちゃうんで、 メジャーな所から取りあえず、一つだけ選んでみます。

フィーリングで選び(無視し)ます。shって、shellみたいな名前だから却下。powerpcって 3社連合(アプル、モト、IBM)の怨念を引きずっていそうなのでpass。mipsって前回、 作ってあるからpass。h8300って、H8の事だよね。はるか昔にやったからpass。sparcなんてのも 目につくな。これって、 TOPS 500の2番手、京コンピュータ に使われている石じゃないですか。64Bitで8coreを真似してくれたら、入れてもいいんだけど、 今回はpass。

そうなると残ったのは、ARMか。自分の携帯に入っていそうだな。今回は、こやつで やってみるか。

ARM環境

ArchLinuxの上に作ってみた。早速、試運転。

[sakae@archbang exec]$ make run
---------------- arm-elf ----------------
---- create source file (sample.c => arm-elf.c)
cp sample.c arm-elf.c
---- compile (arm-elf.c => arm-elf.s)
/usr/local/cross/bin/arm-elf-gcc \
                -fno-builtin -nostdinc -nostdlib -static -O -Wall -g -D___`uname -s | sed 's/[\_\-].*//'`___  \
                -fverbose-asm -fomit-frame-pointer -DARCH=\"arm-elf\"  -o arm-elf.s -S arm-elf.c
---- assemble (arm-elf.s => arm-elf.o)
/usr/local/cross/bin/arm-elf-gcc \
                -fno-builtin -nostdinc -nostdlib -static -O -Wall -g -D___`uname -s | sed 's/[\_\-].*//'`___  \
                  -o arm-elf.o -c arm-elf.s
---- link (arm-elf.o => arm-elf.x)
/usr/local/cross/bin/arm-elf-gcc \
                -fno-builtin -nostdinc -nostdlib -static -O -Wall -g -D___`uname -s | sed 's/[\_\-].*//'`___  \
                -Wl,-Tld.scr lib-arm-elf.S  -o arm-elf.x arm-elf.o
---- simulate (arm-elf.x => arm-elf.sot)
/usr/local/cross/bin/arm-elf-run \
                  arm-elf.x > arm-elf.sot
Hello World! abadface This architecture is arm-elf

長い時間をかけて、造ったかいがありましたよ。あれ? 良く見ると、逆アセンブラのコードが 出てきていないな。万能すぎるMakefileを改変しすぎたか。それにしても鬼のMakefileだなあ。

[sakae@archbang ~]$ du -sh /usr/local/cross/
105M    /usr/local/cross/

ターゲットが1CPUだと、こんなもので済むんですね。入れてはみたものの、どういうCPUか良く知らんな。 資料を探してみる。

玄箱PRO、Linux ZaurusでARMアセンブリプログラミング

こういう楽しいページが出てきた。 玄箱ってPowerPCも有るのね。知らなかったよ。 こちらも読んで、知ったかぶりしましょ。

移植

上でインストールしたARMはさておき、このLinuxにはmips版のクロスコンパイル環境(組み込み用 ライブラリー付き)が入っているので、こちらでもminiライブラリーを使って、Helloを やってみる。

まずは、cross/exec内にある必要なものを頂いてくる。

[sakae@archbang y]$ ls
ld.scr  lib-mips-elf.S  Makefile  sample.c  syscall.h

続いて、要になるMakefileをちょっと改変(改悪?)。このあたりが移植なのかなあ。 そして実行

[sakae@archbang y]$ make run
---------------- mips-elf ----------------
---- create source file (sample.c => mips-elf.c)
cp sample.c mips-elf.c
---- compile (mips-elf.c => mips-elf.s)
/usr/local/bin/mipsel-linux-elf-gcc \
    -fno-builtin -nostdinc -nostdlib -static -O -Wall -g -D___`uname -s | sed 's/[\_\-].*//'`___  \
    -fverbose-asm -fomit-frame-pointer -DARCH=\"mips-elf\"  -o mips-elf.s -S mips-elf.c
---- assemble (mips-elf.s => mips-elf.o)
/usr/local/bin/mipsel-linux-elf-gcc \
    -fno-builtin -nostdinc -nostdlib -static -O -Wall -g -D___`uname -s | sed 's/[\_\-].*//'`___  \
    -o mips-elf.o -c mips-elf.s
---- link (mips-elf.o => mips-elf.x)
/usr/local/bin/mipsel-linux-elf-gcc \
    -fno-builtin -nostdinc -nostdlib -static -O -Wall -g -D___`uname -s | sed 's/[\_\-].*//'`___  \
    -Wl,-Tld.scr lib-mips-elf.S -Wl,-Ttext -Wl,0x80000000 -o mips-elf.x mips-elf.o
---- simulate (mips-elf.x => mips-elf.sot)
/usr/local/bin/mipsel-linux-elf-run \
     mips-elf.x > mips-elf.sot
Hello World! abadface This architecture is mips-elf

無事に実行出来ましたよ。でも、引数の中に潜り込んでいる、unameのパイプは何やってる?

[sakae@archbang y]$ uname -s | sed 's/[\_\-].*//'
Linux
[sakae@archbang y]$ uname -s
Linux
[sakae@archbang y]$ uname -a
Linux archbang 3.4.4-2-ARCH #1 SMP PREEMPT Sun Jun 24 17:28:37 UTC 2012 i686 GNU/Linux

リンカーとリンク

折角分かりやすい、上から下まで無色透明の例があるので、こってりと見て行く。 まずは、

[sakae@archbang y]$ readelf -e mips-elf.x
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           MIPS R3000
  Version:                           0x1
  Entry point address:               0x80000000
  Start of program headers:          52 (bytes into file)
  Start of section headers:          8108 (bytes into file)
  Flags:                             0x1001, noreorder, o32, mips1
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         1
  Size of section headers:           40 (bytes)
  Number of section headers:         20
  Section header string table index: 17

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        80000000 001000 0001f0 00  AX  0   0  4
  [ 2] .rodata           PROGBITS        800001f0 0011f0 000044 01 AMS  0   0  4
  [ 3] .data             PROGBITS        80000400 001400 000004 00  WA  0   0  4
  [ 4] .bss              NOBITS          80000404 001404 000004 00  WA  0   0  4
  [ 5] .stack            NOBITS          80000410 001404 000400 00  WA  0   0  1
  [ 6] .reginfo          MIPS_REGINFO    00000000 001404 000018 01      0   0  4
  [ 7] .pdr              PROGBITS        00000000 00141c 0000c0 00      0   0  4
  [ 8] .comment          PROGBITS        00000000 0014dc 000011 01  MS  0   0  1
  [ 9] .gnu.attributes   LOOS+ffffff5    00000000 0014ed 000010 00      0   0  1
  [10] .debug_line       MIPS_DWARF      00000000 0014fd 0000eb 00      0   0  1
  [11] .debug_info       MIPS_DWARF      00000000 0015e8 000334 00      0   0  1
  [12] .debug_abbrev     MIPS_DWARF      00000000 00191c 000168 00      0   0  1
  [13] .debug_aranges    MIPS_DWARF      00000000 001a88 000040 00      0   0  8
  [14] .debug_loc        MIPS_DWARF      00000000 001ac8 0002c0 00      0   0  1
  [15] .debug_str        MIPS_DWARF      00000000 001d88 0000aa 01  MS  0   0  1
  [16] .debug_frame      MIPS_DWARF      00000000 001e34 0000bc 00   o  0   0  4
  [17] .shstrtab         STRTAB          00000000 001ef0 0000ba 00      0   0  1
  [18] .symtab           SYMTAB          00000000 0022cc 0002f0 10     19  18  4
  [19] .strtab           STRTAB          00000000 0025bc 0000cc 00      0   0  1

Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x001000 0x80000000 0x80000000 0x00404 0x00810 RWE 0x1000

 Section to Segment mapping:
  Segment Sections...
   00     .text .rodata .data .bss .stack

readelf(i386用)とmipsel-linux-elf-readelfは同じ挙動をするの? これってひょっとして endianが同一だから?まあ、中身には触れないで、管理情報だけを扱うOSっぽい事をしてるだけ だから、兼用出来ちゃうんだな。それを証拠に、中身をもろに扱うアプリは、

[sakae@archbang y]$ objdump -d mips-elf.x

mips-elf.x:     file format elf32-little

objdump: can't disassemble for architecture UNKNOWN!

そんなもの知るかいって、文句たれますな。

次は、readelfした時に出て来る、Entry addressの0x80000000。これリンクの時に、-Wl,0x80000000で 指定してるのね。疑い深いおいらは、-Wl,0x80000300にずらしてa.outを作ってみた。

[sakae@archbang y]$ mipsel-linux-elf-nm a.out
00000010 A _.d1
00000014 A _.d2
00000018 A _.d3
0000001c A _.d4
0000000c A _.frame
00000040 A _.tmp
00000004 A _.xy
00000008 A _.z
8000035c T __close
8000031c T __exit
8000034c T __open
8000032c T __read
8000033c T __write
80000808 B _ebss
80000804 D _edata
80000c10 A _end
80000534 R _erodata
80000c10 B _estack
800004f0 T _etext
80000800 A _gp
80000300 T _start     ;; <=======
80000804 B bss_value
80000800 D data_value
8000036c T exit
800004b0 T main
800003a4 T putchar
800003d0 T puts
80000428 T putxval
8000037c T write1

うん、なる程。で、armの逆アセンブルリストを見てたら、1400番地からコードが詰まっていた。arm版は、特に 配置アドレスを指定していないけど、この1400と言う値は何処から出てくるの?

その答えは、ld.scrを嫁って事でした。

        .text 0x1400 : {
                *(.text .text.*)
                _etext = .;
        }

ここに、デフォの値がセットしてあったのね。ついでに、このスクリプトと、nmした時に出て来る 値を比べてみると、なーる程ってなって、今までの疑問が氷解しましたよ。こういう事は、例の リンカー本に懇切丁寧に解説されているんだろうね。真面目に読んでみよっと。

面白いのは、lib-mips-elf.Sの冒頭付近に書いてある、ここ見ろっていう案内。

/*
 * Use reserved instruction.
 * See gdb/sim/mips/interp.c:signal_exception(),sim_monitor()
 * (ReservedInstruction)
 * Memory address is defined by K0BASE. (See gdb/sim/mips/interp.c)
 */
     :
        /*
         * To avoid exit message.
         * (See gdb/sim/mips/mips.igen:break,HALT_INSTRUCTION)
         */

まずは簡単な所から

/* Note that the monitor code essentially assumes this layout of memory.
   If you change these, change the monitor code, too.  */
/* FIXME Currently addresses are truncated to 32-bits, see
   mips/sim-main.c:address_translation(). If that changes, then these
   values will need to be extended, and tested for more carefully. */
#define K0BASE  (0x80000000)
#define K0SIZE  (0x20000000)
#define K1BASE  (0xA0000000)
#define K1SIZE  (0x20000000)

0x80000000以下は、ユーザーのための領域。それ以上は、カーネル領域となり、3つの区画(K0-K2)に 分かれてると言うのがMIPSでのお約束。K0もK1もアドレス変換を行わないエリア。そして、K1は キャッシュしないエリアとなっている。(きっと、I/O関係のレジスタでも置くんだろうな) と言う事は、何でも出来るカーネルモードなのね。だから、節度ある使い方をしてねって事か。 このあたりの事は、例の本では、291ページから例外って事で説明してたよ。 ちらちら本を見てたら、SPIMでは、I/OレジスタをK2エリアに割り付けていたぞ。

次は、sim_monitorあたりかな。

    case 6: /* int open(char *path,int flags) */
      {
        char *path = fetch_str (sd, A0);
        V0 = sim_io_open (sd, path, (int)A1);
        free (path);
        break;
      }

    case 7: /* int read(int file,char *ptr,int len) */
      :
    case 8: /* int write(int file,char *ptr,int len) */
      :
    case 10: /* int close(int file) */
      :
    case 17: /* void _exit() */
      :

この case XXって、理由って名前を評価したものなんだけど、これって割り込みの理由って 事なのね。そう思って、lib-mips-elf.Sを覗くと、有りましたねぇ。俗に言うシステムコール 番号ってやつ。

#define SYS_exit  17
#define SYS_open   6
#define SYS_read   7
#define SYS_write  8
#define SYS_close 10

lib-arm-elf.Sの該当部分がどうなっているかと調べてみると、

#define SWI_Exit  0x11
#define SWI_Open  0x66
#define SWI_Close 0x68
#define SWI_Write 0x69
#define SWI_Read  0x6a

この違いは、gdb-simの作者様の違いによるものだろう。そんな各作者の意図をソースから 読み取って、lib-xxx-elf.Sを造っちゃう人って尊敬しちゃうぞ。これが一番の逸品だと 思いますね。

一方、割り込みの上位概念に相当するのが例外だ。(なんて書くと、厳格な人からは 注意されそう)signal_exceptionが担当する事になってる。 gdb-simの都合で、上記の割り込みは、予約命令として扱ってるのかな。

  switch (exception) {
    case DebugBreakPoint:
      :
    case ReservedInstruction:
      :
    default:
      :
     switch ((CAUSE >> 2) & 0x1F) { 
       case Interrupt:
       case NMIReset:
       case TLBModification:
       case TLBLoad:
       case TLBStore:
       case AddressLoad:
       case AddressStore:
       case InstructionFetch:
       case DataReference:
       case ReservedInstruction:
       case CoProcessorUnusable:
       case IntegerOverflow:
       case FPE:
       case BreakPoint:
       case SystemCall:
       case Trap:
       case Watch:
         :

2番目のswitch以下の分岐では、ほとんどの場合、sim-haltするように実装されてた。と言う事は gdb-simは、軽くユーザーアプリを動かす時に使ってねって事です。夢々、カーネルっぽいアプリを 動かしてはいけません。

最後は、HALTを見てからHALTします。

000000,20.CODE,001101:SPECIAL:32::BREAK
"break %#lx<CODE>"
*mipsI:
*mipsII:
  :
{
  /* Check for some break instruction which are reserved for use by the simulator.  */
  unsigned int break_code = instruction_0 & HALT_INSTRUCTION_MASK;
  if (break_code == (HALT_INSTRUCTION  & HALT_INSTRUCTION_MASK) ||
      break_code == (HALT_INSTRUCTION2 & HALT_INSTRUCTION_MASK))
    {
      sim_engine_halt (SD, CPU, NULL, cia,
                       sim_exited, (unsigned int)(A0 & 0xFFFFFFFF));
    }
  :

HALTに出会ったら、終了って風に読めるんだけど、冒頭の方に余計なものが付いてて、Cのソース じゃない事は分かるな。ファイル名からして、mips.igenってなってるし。こりゃ、mipsの命令の 定義書だな。きっとこれを元に、Cのソースを自動生成するのだろうな。 行頭のやつは、難だ? ファイルの冒頭にあるのが、説明書かな。

//    <insn> ::=
//        <insn-word> { "+" <insn-word> }
//        ":" <format-name>
//        ":" <filter-flags>
//        ":" <options>
//        ":" <name>
//        <nl>
//        { <insn-model> }
//        { <insn-mnemonic> }
//        <code-block>

もしかしてBFD(big fucking deal)に関係してる??

hexedit

フィーリング... のページでおまけとして紹介されたhexedit。 パッチを当ててより便利に使いましょうって案内。

早速、FreeBSDにもArchLinuxにも入れておきました。昔、おいらがMac信者だった頃、一番 使ってたのがResEditって言うツール。hexeditも必需品になりそうな予感。

ここでもMIPS

PIC32MX220 (200円のMIPS CPU)を使う とか、retroBSD on PIC32マイコンとか、 感覚の鋭いお方は、遊び方が素晴らしいですね。

上記の石は、日本の趣味の電子工作を支えてきた、秋月ちゃんが売ってるそうで、こちらも鋭い。 折角なので、この石に魅せられた方を探してみた。

PIC32MX220 Tutorials の回路図を見ると、石は28PinのDIPなのね。これだと、おいらにも作れそうだな。200円の石にも A/Dコンバーターが付いてるなんて、飽食な時代だねぇ。そんな事より、MIPSが200円ってのに 驚けよ!!

仕様書は、MICROCHIP のサイトに有るのか。落としてきて読んでみるかな。