H8マイコンで記号プログラミングをやってみた

__[]={(((('$'-' ')<<('$'-' '))|('!'-' '))<<('('-' '))|((('#'-'!')<<('$'-' '))|(' '-' ')),
(((('$'-' ')<<('$'-' '))|('('-' '))<<('('-' '))|((('&'-' ')<<('$'-' '))|('!'-' ')),
(((('('-'!')<<('$'-' '))|(' '-' '))<<('('-' '))|((('('-'!')<<('$'-' '))|(' '-' ')),
(((('('-'!')<<('$'-' '))|(')'-' '))<<('('-' '))|((('#'-'!')<<('$'-' '))|(' '-' ')),
(((('$'-' ')<<('$'-' '))|('.'-' '))<<('('-' '))|((('&'-' ')<<('$'-' '))|('%'-' ')),
(((('('-'!')<<('$'-' '))|('('-'!'))<<('('-' '))|((('#'-'!')<<('$'-' '))|(' '-' ')),
(((('%'-' ')<<('$'-' '))|(')'-' '))<<('('-' '))|((('&'-' ')<<('$'-' '))|('%'-' ')),
(((('&'-' ')<<('$'-' '))|('!'-' '))<<('('-' '))|((('('-'!')<<('$'-' '))|('#'-'!')),
(((('#'-'!')<<('$'-' '))|('!'-' '))<<('('-' '))|(((' '-' ')<<('$'-' '))|('*'-' ')),
((((' '-' ')<<('$'-' '))|(' '-' '))<<('('-' '))|(((' '-' ')<<('$'-' '))|(' '-' ')),};
_[]={((((' '-' ')<<('$'-' '))|('!'-' '))<<('('-' '))|(((' '-' ')<<('$'-' '))|(' '-' ')),
(((('&'-' ')<<('$'-' '))|('-'-' '))<<('('-' '))|((('/'-' ')<<('$'-' '))|('&'-' ')),
((((' '-' ')<<('$'-' '))|('/'-' '))<<('('-' '))|((('/'-' ')<<('$'-' '))|('&'-' ')),
(((('('-'!')<<('$'-' '))|('*'-' '))<<('('-' '))|(((' '-' ')<<('$'-' '))|(' '-' ')),
((((' '-' ')<<('$'-' '))|(' '-' '))<<('('-' '))|(((' '-' ')<<('$'-' '))|(' '-' ')),
((((' '-' ')<<('$'-' '))|('$'-' '))<<('('-' '))|((('*'-' ')<<('$'-' '))|('.'-' ')),
(((('%'-' ')<<('$'-' '))|('.'-' '))<<('('-' '))|(((' '-' ')<<('$'-' '))|(' '-' ')),
((((' '-' ')<<('$'-' '))|('#'-'!'))<<('('-' '))|((('('-' ')<<('$'-' '))|('#'-'!')),
(((('!'-' ')<<('$'-' '))|(')'-' '))<<('('-' '))|(((' '-' ')<<('$'-' '))|(' '-' ')),
((((' '-' ')<<('$'-' '))|('!'-' '))<<('('-' '))|(((' '-' ')<<('$'-' '))|(' '-' ')),
(((('&'-' ')<<('$'-' '))|('-'-' '))<<('('-' '))|((('('-'!')<<('$'-' '))|('&'-' ')),
(((('%'-' ')<<('$'-' '))|('$'-' '))<<('('-' '))|((('('-'!')<<('$'-' '))|(' '-' ')),};

これは何でしょう?知っている人はすぐに「ああ、アレか。」とわかると思いますが、コレを初めて見た人は何がなんだかわからないと思います。顔文字にも見えないことはないですね。
これはC言語で書かれたソースコードです(笑)違和感たっぷりですね。


記号プログラミングというものがありまして、まぁ、簡単に言ってしまえば、アルファベットと数字を使用せずに記号のみでプログラミングをしよう!というものですね。
今回はこれに初挑戦してみました!!!!そして、H8/3069F上で実際に動作させてみます!
挑戦する前は、ほんとにこんな訳分かんないのが動くのか?と疑っていたのですが、コンパイルできちゃうんだから面白いです。(警告は沢山でますw)
現在「12ステップで作る 組込みOS自作入門」という本を教材にして、H8/3069F上で動く組み込みOSのしくみを学習しているのですが、その途中のソースコードを改造して、記号プログラミングに挑戦しました。コンパイラはgcc4.2.2を使用しています。


新年を迎えたばかりということで、こんな感じのプログラムを題材にしてみたいと思います。
main.c

#include "lib.h"
int main(void)
{
  puts("A Happy New Year!\n");
  return 0;
}

完全にHello Worldのパクリですね!さて、これを記号だけにしたいと思います。
とりあえずこれを一旦コンパイルして、逆アセンブルしてみます。解析対象は最終的に出力されたelfファイルです。

$ make
〜〜
$ /usr/local/h8300-elf/bin/objdump -D prog.elf
〜〜
Disassembly of section .text:
〜〜
00000112 <_main>:
 112:	01 00 6d f6 	mov.l	er6,@-er7
 116:	0f f6       	mov.l	er7,er6
 118:	7a 00 00 00 	mov.l	#0x4b4,er0
 11c:	04 b4 
 11e:	5e 00 02 9a 	jsr	@0x29a:24
 122:	19 00       	sub.w	r0,r0
 124:	01 00 6d 76 	mov.l	@er7+,er6
 128:	54 70       	rts	
〜〜

こんな感じになりますね。main関数で行っている処理はこれだけのようです。
0x118で、er0レジスタにアドレスを格納しています。その後、0x11eで0x29aに飛んでいるようです。
これと最初のソースを比較すると、どうやら0x4b4が文字列が格納されているアドレスで、0x29aにあるputsにジャンプしているようです。ということは、文字列が格納されているアドレスと、その文字列を出力するputsが存在するアドレスがわかればいいみたいです。文字列が関数の引数の中に直接書かれていると面倒なので、ちょこっとソースを書き換えます。

#include "lib.h"
char c[]="A Happy New Year!\n";
int main(void)
{
  puts(c);
  return 0;
}

アセンブルすると、

$ make
〜〜
$ /usr/local/h8300-elf/bin/objdump -D prog.elf
〜〜
Disassembly of section .text:
〜〜
00000112 <_main>:
112:	01 00 6d f6 	mov.l	er6,@-er7
116:	0f f6       	mov.l	er7,er6
118:	7a 00 00 00 	mov.l	#0x4c5,er0
11c:	04 b4 
11e:	5e 00 02 9a 	jsr	@0x29a:24
122:	19 00       	sub.w	r0,r0
124:	01 00 6d 76 	mov.l	@er7+,er6
128:	54 70       	rts	
〜〜
Disassembly of section .data:

000004c5 <_c>:
4c5: 41 20         brn .+32 (0x4e7)
4c7: 48 61         .word H'48,H'61
4c9: 70 70         bset  #0x7,r0h
4cb: 79 20 4e 65   cmp.w #0x4e65,r0
4cf: 77 20         bld #0x2,r0h
4d1: 59 65         bra r6l.b
4d3: 61 72         bnot  r7h,r2h
4d5: 21 0a         mov.b @0xa:8,
〜〜

こんな感じになります。
先に文字列の方を処理します。C言語は最初の型宣言を省略すると、int型とみなされるのでその性質を利用して、

c[]={
  'A'<<8|' ','H'<<8|'a','p'<<8|'p','y'<<8|' ',
  'n'<<8|'e','w'<<8|' ',
  'y'<<8|'e','a'<<8|'r','!'<<8|'\n','\0'<<8|'\0',  
};

と置き換えます。
main関数も配列にしてしまいましょう!配列の要素には、先ほど逆アセンブルした物をいれます。

main[]={
  0x0100,0x6df6,
  0x0ff6,
  0x7a00,
  0x0000,
  0x0405,
  0x5e00,0x029a,
  0x1900,
  0x0100,0x6d76,
};

これをコンパイルして、逆アセンブルすると、

$ make
〜省略〜
$ /usr/local/h8300-elf/bin/objdump -D prog.elf
〜〜
Disassembly of section .text:
00000282 <_puts>:
282: 01 00 6d f6   mov.l er6,@-er7
286: 0f f6         mov.l er7,er6
288: 01 00 6d f4   mov.l er4,@-er7
28c: 0f 84         mov.l er0,er4
28e: 40 06         bra .+6 (0x296)
〜〜
Disassembly of section .data:

000004ae <_c>:
4ae: 41 20         brn .+32 (0x4d0)
4b0: 48 61         .word H'48,H'61
4b2: 70 70         bset  #0x7,r0h
4b4: 79 20 4e 65   cmp.w #0x4e65,r0
4b8: 77 20         bld #0x2,r0h
4ba: 59 65         bra r6l.b
4bc: 61 72         bnot  r7h,r2h
4be: 21 0a         mov.b @0xa:8,r1h
 ...
 
00004c2 <_main>:
4c2: 01 00 6d f6   mov.l er6,@-er7
4c6: 0f f6         mov.l er7,er6
4c8: 7a 00 00 00   mov.l #0x405,er0
4cc: 04 05
4ce: 5e 00 02 9a   jsr @0x29a:24
4d2: 19 00         sub.w r0,r0
4d4: 01 00 6d 76   mov.l @er7+,er6

〜〜

main関数の所を見てもらうとわかりますが、4c8の行に書いてあるアドレスが、#0x405となっていますが、ここには文字列を格納したcのアドレスを代入したいので、#0x4aeとなるべきです。
また、4ceの行のに書いてあるが、0x29aとなっていますが、putsのアドレスを代入したいので、0x282となるべきです。
ということで、ソースコードを編集します。

c[]={
  'A'<<8|' ','H'<<8|'a','p'<<8|'p','y'<<8|' ',
  'n'<<8|'e','w'<<8|' ',
  'y'<<8|'e','a'<<8|'r','!'<<8|'\n','\0'<<8|'\0', 
};
main[]={
  0x0100,0x6df6,
  0x0ff6,
  0x7a00,
  0x0000,
  0x04ae,
  0x5e00,0x0282,
  0x1900,
  0x0100,0x6d76,
};

これで下準備はできました。
機械語は数字ですから、記号で1〜Fまでが表せればよいわけです。というわけで、こうしました。

  • 0 → ' '-' '
  • 1 → '!'-' '
  • 2 → '#'-'!'
  • 3 → '#'-' '
  • 4 → '$'-' '
  • 5 → '%'-' '
  • 6 → '&'-' '
  • 7 → '('-'!'
  • 8 →'('-' '
  • 9 → ')'-' '
  • A → '*'-' '
  • B → '+'-' '
  • C → ','-' '
  • D → '-'-' '
  • E → '.'-' '
  • F → '/'-' '

あとはこれらとシフト演算子と論理演算子を組み合わせれば全ての値が作れます。
そして、さらに、変数cの名前を"__"に変えて、mainも"_"に変えてしまいましょう。#defineの行は必要ありません。消してしまいましょう。
次にスタートアップファイル(startup.s)を書き換えましょう。ここでは、メインとなる処理を始めるために、"_main"を呼んでいるのですが、これを"__"に変更すれば、"_"からプログラムが開始されます。
補足:Cのソースファイル上で書いた変数"abc"はアセンブルされると、先頭に"_"が付加され、"_abc"になります。よって、Cのソースファイル上で書いたシンボル"_"から開始させたければ、メインとなる処理を始める場所の名称を"__"とすればよい、ということになります。
これらを済ませると、こんな感じになります。

__[]={(((('$'-' ')<<('$'-' '))|('!'-' '))<<('('-' '))|((('#'-'!')<<('$'-' '))|(' '-' ')),
(((('$'-' ')<<('$'-' '))|('('-' '))<<('('-' '))|((('&'-' ')<<('$'-' '))|('!'-' ')),
(((('('-'!')<<('$'-' '))|(' '-' '))<<('('-' '))|((('('-'!')<<('$'-' '))|(' '-' ')),
(((('('-'!')<<('$'-' '))|(')'-' '))<<('('-' '))|((('#'-'!')<<('$'-' '))|(' '-' ')),
(((('$'-' ')<<('$'-' '))|('.'-' '))<<('('-' '))|((('&'-' ')<<('$'-' '))|('%'-' ')),
(((('('-'!')<<('$'-' '))|('('-'!'))<<('('-' '))|((('#'-'!')<<('$'-' '))|(' '-' ')),
(((('%'-' ')<<('$'-' '))|(')'-' '))<<('('-' '))|((('&'-' ')<<('$'-' '))|('%'-' ')),
(((('&'-' ')<<('$'-' '))|('!'-' '))<<('('-' '))|((('('-'!')<<('$'-' '))|('#'-'!')),
(((('#'-'!')<<('$'-' '))|('!'-' '))<<('('-' '))|(((' '-' ')<<('$'-' '))|('*'-' ')),
((((' '-' ')<<('$'-' '))|(' '-' '))<<('('-' '))|(((' '-' ')<<('$'-' '))|(' '-' ')),};
_[]={((((' '-' ')<<('$'-' '))|('!'-' '))<<('('-' '))|(((' '-' ')<<('$'-' '))|(' '-' ')),
(((('&'-' ')<<('$'-' '))|('-'-' '))<<('('-' '))|((('/'-' ')<<('$'-' '))|('&'-' ')),
((((' '-' ')<<('$'-' '))|('/'-' '))<<('('-' '))|((('/'-' ')<<('$'-' '))|('&'-' ')),
(((('('-'!')<<('$'-' '))|('*'-' '))<<('('-' '))|(((' '-' ')<<('$'-' '))|(' '-' ')),
((((' '-' ')<<('$'-' '))|(' '-' '))<<('('-' '))|(((' '-' ')<<('$'-' '))|(' '-' ')),
((((' '-' ')<<('$'-' '))|('$'-' '))<<('('-' '))|((('*'-' ')<<('$'-' '))|('.'-' ')),
(((('%'-' ')<<('$'-' '))|('.'-' '))<<('('-' '))|(((' '-' ')<<('$'-' '))|(' '-' ')),
((((' '-' ')<<('$'-' '))|('#'-'!'))<<('('-' '))|((('('-' ')<<('$'-' '))|('#'-'!')),
(((('!'-' ')<<('$'-' '))|(')'-' '))<<('('-' '))|(((' '-' ')<<('$'-' '))|(' '-' ')),
((((' '-' ')<<('$'-' '))|('!'-' '))<<('('-' '))|(((' '-' ')<<('$'-' '))|(' '-' ')),
(((('&'-' ')<<('$'-' '))|('-'-' '))<<('('-' '))|((('('-'!')<<('$'-' '))|('&'-' ')),
(((('%'-' ')<<('$'-' '))|('$'-' '))<<('('-' '))|((('('-'!')<<('$'-' '))|(' '-' ')),};

もう何がなんだかわかりませんね。訳がわからないままコンパイルしてみます。そしてマイコンに書きこんで…実行!!
結果は…

A Happy New Year!


ほんとにハッピーな気持ちになりました!!(笑)


こんなコードを書いてもコンパイルが通って、さらには実行できてしまうんですね。驚きです…


今回の記事は、途中の過程を多少省略し、おおまかな流れのみ書いています。なので、このとおりにやってもうまくいかないかもしれません。しかし、考え方は応用できると思うので、他の環境でもこのような感じでできるのではないかと思います。
アセンブラの勉強にもなるので、みなさんもぜひチャレンジしてみてください!!


追記: 本文中で、"putc"と書かれていたところは、正しくは"puts"でしたので、修正しました。ご指摘いただいたakimotokenichiさん、ありがとうございます。(2011/1/3)