「オープンソース」を使ってみよう (第33回 アセンブラ短歌)

No Comments
このエントリーをはてなブックマークに追加



アセンブラ短歌

アセンブラ短歌

KOZOSプロジェクト代表
坂井弘亮(さかい・ひろあき)

目次



1 アセンブラ短歌とは何か?

「アセンブラ短歌」は五・七・五・七・七の三十一バイト(みそひとバイト)から成る
機械語コードでプログラムを書いてみるという近未来の文化的趣味であり,近年,
国内のハッカー間で密かなブームが起きています.

http://kozos.jp/asm-tanka/



リスト1.1: アセンブラ短歌の作品「夏休み」

6a 00 58 50 40
68 79 61 6d 61 50 40
6a 08 5a 5b 40
68 57 61 6b 61 54 40
59 cd 80 58 58 58 c3

2 オープンソースです

作品の制作は,筆者は主にFreeBSD上でgccを用いて行っています.
他にもZ80や6502といった旧来のマイコンや,VAXなどでの実施例もあります.
これらのためにはオープンソースのアセンブラやシミュレータなどが多く利用
されています.

またアセンブラ短歌は機械語コードとアセンブラの作品を鑑賞するものなので,
当然ながら,作品自体がオープンソースです.

このようにアセンブラ短歌は,オープンソースとの親和性が非常に高い
芸術となっています.

3 セミナーやイベントが行われています

アセンブラ短歌はオープンソースカンファレンス2013Tokyo/Springの
ライトニングトークで初お披露目されました.
さらにOSC2013Nagoyaで初のセミナーが行われています.

セミナーはその後,OSC2013Hokkaido,OSC2013Tokyo/Fall,OSC2014Tokyo/Springでも引続き行われました.セミナー中ではアセンブラ短歌の紹介と説明から始まり,
短歌作品の解説や鑑賞などが行われています.

(OSCでのアセンブラ短歌の発表歴)

  • OSC2013Tokyo/Spring ライトニングトーク
  • OSC2013Nagoya セミナー・ライトニングトーク
  • OSC2013Hokkaido セミナー・ライトニングトーク (レポート)
  • OSC2013Tokyo/Fall セミナー・ライトニングトーク
  • OSC2014Tokyo/Spring セミナー

またアセンブラ短歌はOSCの他にも,
様々なイベントで積極的に紹介が行われています.


avtokyo

図3.1: AVTOKYO2013.5での発表は和装に扮した歌人により行われました

4 コンテストが行われています

SECCONというセキュリティ関連のコンテスト大会があります.

http://2013.seccon.jp/

2012年度は主にCTF(Capture The Flag)というセキュリティ競技が行われましたが,
2013年度はそれと並行してアセンブラかるた(図4.1),バイナリかるた,
Wiresharkパケット・コンテストなどの様々な競技やコンテストが行われました.


asm-karuta

図4.1: アセンブラかるた

3月には全国大会があり,カンファレンスではアセンブラ短歌のコンテストの
最優秀作品の発表が行われています.当日は受賞者の発表に加え,
和装に扮した歌人による発表が行われました.

アセンブラ短歌のプラットホームはPCで広く利用されているx86アーキテクチャに
限るわけではないのですが,コンテストでは初代ファミコンやApple IIで名を馳せた6502,往年の国産パソコンで利用され今でも現役のZ80,またPC-9801やPC-8001
などの懐かしのアーキテクチャの作品が応募され,掛軸になって展示されました.


seccon2013-zenkoku-tanka-works-1

図4.2: 応募された作品は,3月の全国大会で掛軸にして展示されました


表4.1: アセンブラ短歌コンテストの応募作品の一覧

歌人 作品名 アーキ
テクチャ
作品
山口文彦 黄金律は遠い x86
 31 c0 40 89 c3
 93 fe c9 7e 06 01 C3
 89 c0 eb f5 c3
 8b 44 24 08 8b 40 04
 8a 08 83 e9 30 eb e1
hirosk ミサカじゃないよ 6502
 d0 03 c8 98 60
 98 d0 04 c8 ca 50 f4
 88 c8 88 8a 48
 20 04 06 a8 68 aa 8a
 ca aa ca aa ca 50 e1
大坪雄平 x86
 BB 00 A2 8E C3
 93 F8 B1 04 BF 93 F8
 D3 EF 83 C7 17
 B0 07 FD 04 20 AA 4F
 79 FA B8 00 4C CD 21
テツの橘 ワビサビと
書こうと思い
頑張った
メモリ不足で
ワビで妥協す
Z80
 21 00 80 06 06
 11 39 F1 22 50 F3 AF
 21 00 F3 28 07
 13 79 F0 8A 03 03 00
 1A 77 13 23 10 FA C9
みむら うどんに思いを馳せて MSIL
 20 4A 46 69 93
 0A 06 15 1E 62 66 5F
 20 00 30 00 00
 58 28 01 00 00 0A 06
 1E 64 0A 06 2D E8 2A
安田豊 6502 の憂鬱 6502
 A9 00 48 A9 4D
 48 A9 4F 48 AA A9 54
 48 8A 48 A9 52
 68 68 A9 53 48 BA E8
 8A A0 01 20 1E AB 60

以下に,各作品について紹介させていただきます.

なおアセンブラ短歌コンテストの応募作品については,マイナビより発刊予定の書籍
「0と1のコンピュータ世界 -バイナリで遊ぼう-」にて,さらに詳しい解説つきで紹介させていただくつもりです.

様々なユニークな作品に溢れていますので,こちらもぜひご期待ください.

4.1 最優秀作品「虹」


seccon2013-zenkoku-tanka-best

図4.3: 最優秀作品「虹」

図4.3は最優秀賞に輝いた,大坪雄平さんによる作品「虹」です.

  • 歌人:大坪雄平
  • 作品のテーマ:
    実行するとテキストのアトリビュートエリアを書き換え画面いっぱいに7色で虹を表現します.画面いっぱいに虹を表現しつつ2句目の1〜2バイト目及び6〜7バイト目がSHIFT-JISで「虹」(93F8)となっており,主虹と副虹を表現しています.最後はシステムコールできれいに終了し,雨上がりに現れる虹の爽やかさを表現しています.

(作品の総評)

MS-DOS/PC-9801(x86)の作品です.テキストVRAMに色属性(上位3ビット)を変化させながらストリング命令で書き込むことで,カラーバー状の虹を生成しています.偶数位置に書き込むため,ストリング命令で減算されることに加えDEC命令が追加されています.

ループ回数を漢字コードから演算によって生成されており,「虹」という文字の埋め込みのため2重になった虹が表現されていることには,作者の強いこだわりが感じられます.またダミーのxchg命令とclc命令によりさらに「虹」の1文字を置くことで,主虹と副虹を表現している工夫も見られます.最後がコードゼロで終了することも,雨上がりの雲ひとつ無い青空が表現されているようで晴れやかです.


rainbow

図4.4: 「虹」の実行画面



リスト4.1: 「虹」のアセンブラ・リスト

BB 00 A2        mov     BX,0A200h       
8E C3           mov     ES,BX           
                                        
93              xchg    AX,BX           
F8              clc                     
B1 04           mov     cl,4            
BF 93 F8        mov     DI,0F893h       
                                        
D3 EF           shr     di,cl   ;F893 -> 0F89
83 C7 17        add     di,17h  ;0F89 -> 0FA0 (80*25*2)
                                        
B0 07           mov     AL,7            
FD              std                     
04 20   Loop1:  add     AL,020h         
AA              stosb                   
4F              dec     DI              
                                        
79 FA           jns     Loop1           
B8 00 4C        mov     AX,4C00h; AH=4Ch:終了, AL=DOSへの戻り値。
CD 21           int     21h     ; DOSへ戻る

4.2 優秀作品「6502の憂鬱」

  • 歌人:安田豊
  • 作品のテーマ:
    これは 6502 に自分のメーカー名を出力させるプログラムです.
    Commodore 64 上で実行すると SOM と出力されます.
    (左右逆順で MOS technology 社の MOS となる)

    6502 は余りにも多くの人から MOTOROLA 製だと勘違いされ続け,ついに最近では自分でも混乱しはじめました.

    今日,久々にメーカー名を尋ねられて,ついに自分でも「もとろー・・・」と口走ってしまいます.(ここまで上の句)

    しかしその途中,「あっ」と気がついて慌ててスタックから余計なTとOを回収して,めでたく S を入れて出力することに成功しました.(下の句)

    上の句と下の句できれいに処理が分かれ,上の句の最後に「あー」と R を途中まで読み込んだところでハッと我に返って慌てて Pull Stack を連発するところに6502の疲れが読み取れます.

    本来OUTSTRルーチンはスタックに逆順に文字列を積まなければならないところを頭から積んでしまったのは気の早い 6502 のご愛敬,ということで.

(作品の総評)

確かに誤解の多い6502とMOSテクノロジーですが,6502で動作しているCommodore 64自身が何も考えずに「MOTOR…」と言いかけて,慌てて言い直す点には洒落が利いており,全体的にコミカルな作品に仕上がっています.2回使われる「O」を保存までしている点も,自信満々ぶりと,自信満々に間違えている様子が見てとれて愛敬があります.

ついでに「MOTOROLA」でなく,これも間違いの多い「MOTOLORA」と言いかけるようなうっかりぶりもあるとなお作品が映えたかもしれませんね.(6502にとってはモトローラは他メーカーなので,間違えてしまうのはいたしかたないことかと思います)

スタックに積む順番の都合で文字列が逆順に出てしまう点も,おっちょこちょいの少年のような6502のイメージがうまく表現されているようで好感が持てます.



リスト4.2: 「6502の憂鬱」のアセンブラ・リスト

A9 00           LDA     #$00    ;  NULL 
48              PHA             ;  Push 
A9 4D           LDA     #$4D    ;  Load 'M'
                                        
48              PHA             ;  Push 
A9 4F           LDA     #$4F    ;  Load 'O'
48              PHA             ;  Push 
AA              TAX             ;  A -> X
A9 54           LDA     #$54    ;  Load 'T'
                                        
48              PHA             ;  Push 
8A              TXA             ;  X -> A (‘O’)
48              PHA             ;  Push 
A9 52           LDA     #$52    ;  Load 'R'
                                        
68              PLA             ;  Pull 
68              PLA             ;  Pull 
A9 53           LDA     #$53    ;  Load 'S'
48              PHA             ;  Push 
BA              TSX             ;  SP -> X
E8              INX             ;  INC X
                                        
8A              TXA             ;  X -> A
A0 01           LDY     #$01    ;  1 -> Y
20 1E AB        JSR     $AB1E   ;  OUSTR
60              RTS             ;  return

4.3 作品「黄金律は遠い」

  • 歌人:山口文彦
  • 作品のテーマ:
    上の句がフィボナッチ数を計算するサブルーチン、下の句が main です。

    このように上の句と下の句の役割を分けて詠めば、連歌もできたりして…

    上の句のうち、初句で初期化、二句と三句で計算をしています。

    main ではコマンドラインから一桁の数値を読んで、そのフィボナッチ数を終了コードに返します。とは言っても fib の返戻値が %eax に入って帰ってくるので、main からはただ帰るだけ、call – ret を jmp にしています。

    なお、上の句の末尾で韻を踏んでみました。

    Ubuntu での動作を確認していますが、他の UNIX でも動きそうです。

    コマンドラインの一文字で数値を表していますがアスキーコードから引き算しているだけなので、10 は : 11 は ; 12 は < を入れればよいことになります。
    しかし、終了コードは 255 までなので、13 以上の入力に対しては下位 8bit が出てきます…

    フィボナッチ数列の隣り合う二項は比を取ると黄金律に収束しますが、5桁くらいまでしか近づけませんでした。^_^;

(作品の総評)

Linux/x86の作品です.処理の内容を句分けしてきれいに57577に収めている点,main()関数を下の句にそのまま含めている点は見事です.さらに異なる命令による押韻が行われており,全体的に非常に完成度の高い作品です.

フィボナッチ数の計算にxchg命令を用いている点,関数末尾での関数コールをジャンプ命令で代用している点も,命令削減の効果を生んでいて流麗です.



リスト4.3: 「黄金律は遠い」のアセンブラ・リスト

31 c0 40 89 c3          fib:    clr %eax
                                inc %eax
                                mov %eax,%ebx
                                        
93 fe c9 7e 06 01 C3    loop:   xchg %eax,%ebx
                                decb %cl
                                jle end 
                                add %eax,%ebx
                                        
89 c0 eb f5 c3                  mov %eax,%eax
                                jmp loop
                        end:    ret     
                                        
8b 44 24 08 8b 40 04    main:   mov 8(%esp),%eax
                                mov 4(%eax),%eax
                                        
8a 08 83 e9 30 eb e1            movb 0(%eax),%cl
                                sub $48,%ecx
                                jmp fib 

4.4 作品「ミサカじゃないよ」

  • 歌人:hirosk
  • 作品のテーマ:
    アッカーマン関数A(m,n)を計算します.実行時間も比較的長く,末尾再帰および末尾再帰では除去できないものは普通の再帰呼び出しも行っており,複雑な動作をします.

    作品名は日本古来よりの萌え文化を大切にしたいので少し萌えた感じで付けました.3句,5句で韻も踏んでいます.

    mをXレジスタ,nをYレジスタにいれ,JSR命令でL0をコールするとAレジスタに答えが入ります.ただし,呼び出すときにはYレジスタ,Xレジスタの順に設定してください.

    http://skilldrick.github.io/easy6502/ でエミュレートが可能です.
    下記のコードを入力して「Assemble」「Run」とすれば,Aレジスタに結果が返されます.アッカーマン関数なのでAに結果が入るところにもこだわりました.A(3,3)までは結果が求まります.それ以上はスタックが足りません.

(作品の総評)

6502の1バイト命令が強力に活かされている作品です.
アッカーマン関数は1の加減算が多いためインクリメント/デクリメント命令を
効果的に利用可能で,アセンブラ短歌に非常に向いた処理であることを認識させ
られます.

様々な箇所でジャンプ命令を駆使した再帰呼出しが行われており工夫が感じられます.
アッカーマン関数の演算が各句に納められていることにもまとまりがあります.

また3句目のDEY/INY,4句目のTAX/TXA,5句目のDEX/TAXが単なるNOPでなく
韻を踏むために2命令を繰り返し呼び出している点には小気味よいリズムがあり,
軽快な作品に仕上がっています.



リスト4.4: 「ミサカじゃないよ」のアセンブラ・リスト

        L0:             ;; 5 // ack(x,y)=y+1 when x=0
d0 03           BNE L1  ;; x!=0 goto L1 
c8              INY     ;; y=y+1        
98              TYA     ;; result is in a
60              RTS                     
                                        
        L1:             ;; 7 // ack(x,y)=ack(x-1,1) when y=0
98              TYA     ;; a=y, y=0?    
d0 04           BNE L2  ;; y!=0 goto L2 
c8              INY     ;; y=1          
ca              DEX     ;; x=x-1        
50 f4           BVC L0  ;; get ack(x-1,1)
                                        
        L2:             ;; 5 // ack(x-1, ack(x,y-1)) otherwise
88              DEY                     
c8              INY                     
88              DEY     ;; y=y-1        
8a              TXA     ;; a=x          
48              PHA     ;; save x into stack
                                        
20 04 06        JSR L0  ;; 7 // get ack(x,y-1)
a8              TAY     ;; set y=ack(x,y-1)
68              PLA     ;; recover x into a
aa              TAX     ;;              
8a              TXA     ;;              
                                        
                        ;; 7 //         
ca              DEX     ;;              
aa              TAX     ;;              
ca              DEX     ;;              
aa              TAX     ;; x=a          
ca              DEX     ;; x=x-1        
50 e1           BVC L0  ;; get ack(x-1, ack(x,y-1))

4.5 作品「ワビサビと書こうと思い頑張ったメモリ不足でワビで妥協す」

  • 歌人:テツの橘
  • 作品のテーマ:
    プラットフォームのPC-8001およびZ80の特徴を最大限いかし、わびさびを
    表現しようと考えました。

    プログラムの動きは単純で、あらかじめ用意した”ワビ”のグラフィック
    データをVRAMに直接書き込むだけです。

    工夫した点としては、韻を踏むために、57577の先頭は、すべて2xと
    1xで統一し、かつ5の段は2x、7の段は1xで始まるようにしました。
    また、NOPでの調整を極力さけるため、データ領域を末尾にもってこずに、
    0xF139からはじまる中間に埋め込み、その直前で、相対ジャンプを使って、
    0xF140からのコピーループに飛ばしています。

    また同じくNOPをいれないために、JRではなく、JR Zを使い、0xF133で、
    Zフラグを立てるために、XOR Aをしています。
    また、このXOR Aは、Z80では多様されていたテクニック※で、
    今回、私がどうしても埋め込みたかったコードです。
    唯一0xF13Fに、0x00を埋め込みましたが、これは調整のためのNOPではなく、
    あくまでデータ列の最後を意味する0x00です。

    今回、唯一残念なのは、0xF12Dで、絶対アドレスを指定しているため、
    コードがリロケータブルでなくなってしまった点です。

    ※XOR Aは、次の二つの用途で利用されていた

    ・LD A,0x00(2バイト)と等価の動作を、XOR Aでは1バイトですむ
    エコなコーディング

    ・XOR Aは演算で必ず0になるため、Z-FLAGをセットするために利用される

(作品の総評)

PC-8001(Z80)のテキストベースグラフィック(低解像度グラフィック)を利用した作品です.4句目にきれいに納められた出力データを,0xf300から始まるテキストVRAM領域に直接書き込むことでグラフィック描画しています.各句の先頭で小さい値で韻が踏まれているため,軽めのリズムが感じられます.

Z80オリジナルで8080には存在しないDJNZ命令,またZ80で多用された「XOR A」が利用されていることには,熟練したプログラマによる十分に熟成した鮒ずしのような風味を感じさせ,タイトルにある「ワビ」の境地がうまく表現されています.



リスト4.5: 「ワビサビと書こうと思い頑張ったメモリ不足でワビで妥協す」のアセンブラ・リスト

F128  LD HL,0x8000 ; PC-8001 VRAM ATTRIBUTE(GRAPHICS)
F12B  LD B,0x06 ; SET LOOP COUNTER      
F12D  LD DE,0xF139 ; SET START ADDRESS OF DATA
F130  LD (0xF350),HL ; SET VRAM ATTRIBUTES
F133  XOR A ; SET Z-FLAG                
F134  LD HL,0xF300 ; SET START ADDRESS OF PC-8001 VRAM
F137  JR Z,+0x07 ; JUMP TO COPY ROUTINE WHEN Z-FLAG IS TRUE
F139  DATA 0x13 0x79 0xF0 0x8A 0x03 0x03 0x00 ; OUTPUT GRAPHIC DATA
F140  LD A,(DE) ; READ FROM DATA        
F141  LD (HL),A ; WRITE TO VRAM         
F142  INC DE ; INCREMENT POINTER FOR READ
F143  INC HL ; INCREMENT POINTER FOR WRITE
F144  DJNZ 0xFA ; LOOP                  
F146  RET ; RETURN TO N-BASIC           

4.6 作品「うどんに思いを馳せて」

  • 歌人:みむら
  • 作品のテーマ:
    香川にぶらりと立ち寄った際、ふと短歌を一句詠みたくなり、
    普段お世話になっている Microsoft .NET 環境を用いて作成しました。

    「おうどん」という文字列の文字一つ一つが、0x30?? で表現できること、
    そしてldc 命令を用いて値設定を行うと5バイトになることを使用して
    三十一バイトに納めました。

    定数設定が5バイト、それ以外が7バイトというところや、ループをして文字を出力した後、きれいにループを抜けて ret で美しく終わらせている点、出力される文字列がひらがなである点がアピールポイントではないかと考えています。

(作品の総評)

4文字の先頭バイトが共通していることを利用して,4バイトデータからシフト演算により1バイトデータを順次取り出すことで,出力データを圧縮している点に工夫が見られます.アーキテクチャがスタックベースであるためPUSH/POPが繰り返されており,長時間こねられた腰の強いうどんになりそうです.



リスト4.6: 「うどんに思いを馳せて」のアセンブラ・リスト

    ldc.i4 0x9369464A                   
    stloc.0                             
Write:                                  
    ldloc.0                             
    ldc.i4.m1                           
    ldc.i4.8                            
    shl                                 
    not                                 
    and                                 
    ldc.i4 0x3000                       
    add                                 
    call void [mscorlib]System.Console::Write(char)
    ldloc.0                             
    ldc.i4.8                            
    shr.un                              
    stloc.0                             
    ldloc.0                             
    brtrue.s Write                      
    ret                                 

5 作品を実行してみよう

リスト1.1で紹介した作品「夏休み」を動かすことで,
アセンブラ短歌に入門してみましょう.

まずなんらかのGNU/Linuxディストリビューション環境で,バイナリエディタによって
「夏休み」のバイトコードをファイル化します.
筆者はCentOS上で,hexeditというバイナリエディタを使いました.


hexedit

図5.1: hexeditでバイトコードをファイル化する

作成した natsuyasumi.bin を objdump で逆アセンブルすることで,
アセンブラを出力させます.



リスト5.1: objdumpで逆アセンブルする

$ objdump -b binary -m i386 -D natsuyasumi.bin

natsuyasumi.bin:     file format binary


Disassembly of section .data:

00000000 <.data>:
   0:   6a 00                   push   $0x0         ┐
   2:   58                      pop    %eax         │1句目
   3:   50                      push   %eax         │5バイト
   4:   40                      inc    %eax         ┘
   5:   68 79 61 6d 61          push   $0x616d6179  ┐
   a:   50                      push   %eax         │2句目
   b:   40                      inc    %eax         ┘7バイト
   c:   6a 08                   push   $0x8         ┐
   e:   5a                      pop    %edx         │3句目
   f:   5b                      pop    %ebx         │5バイト
  10:   40                      inc    %eax         ┘
  11:   68 57 61 6b 61          push   $0x616b6157  ┐
  16:   54                      push   %esp         │4句目
  17:   40                      inc    %eax         ┘7バイト
  18:   59                      pop    %ecx         ┐
  19:   cd 80                   int    $0x80        │
  1b:   58                      pop    %eax         │5句目
  1c:   58                      pop    %eax         │7バイト
  1d:   58                      pop    %eax         │
  1e:   c3                      ret                 ┘
$     └───────┘      └─────────┘
         機械語コード              ニーモニック

リスト5.1を見てみてください.
左側に出力されている機械語コードは,
5バイト,7バイト,5バイト,7バイト,7バイトの位置で区切ることができます.
つまり「5・7・5・7・7」のようになっているわけです.
このようなアセンブラ・プログラムを「アセンブラ短歌」と呼んでいます.

さらにニーモニック部分を切り出し,適当なヘッダをつけることで
アセンブルできる状態にします.


$ printf "\t.section .text\n" > natsuyasumi.S
$ printf "\t.global main\n" >> natsuyasumi.S
$ printf "\t.type main, @function\n" >> natsuyasumi.S
$ printf "main:\n" >> natsuyasumi.S
$ objdump -b binary -m i386 -D natsuyasumi.bin | tail -n 20 | cut -c 28- >> natsuyasumi.S

gccでアセンブルして見ましょう.


$ cat natsuyasumi.S
        .section .text
        .global main
        .type main, @function
main:
        push   $0x0
        pop    %eax
        push   %eax
        inc    %eax
        push   $0x616d6179
        push   %eax
        inc    %eax
        push   $0x8
        pop    %edx
        pop    %ebx
        inc    %eax
        push   $0x616b6157
        push   %esp
        inc    %eax
        pop    %ecx
        int    $0x80
        pop    %eax
        pop    %eax
        pop    %eax
        ret    
$ gcc natsuyasumi.S -o natsuyasumi
$ ./natsuyasumi
Wakayama$ 

bashのプロンプトの「$」が続いているためわかりにくいですが,
「Wakayama」と出力されています.

6 アセンブラの説明

アセンブラによるプログラミングには馴染みの無いかたも多いでしょうから,
ここで簡単に説明しておきましょう.

6.1 機械語コードとニーモニック

リスト5.1では中央の「6a 00」「58」「50」のような16進数コードの列が
「機械語コード」,右側の「push $0x0」「pop %eax」「push %eax」などと
なっている列が「ニーモニック」と呼ばれる部分です.

CPUは機械語コードを実行します.つまりメモリ上には
「6a 00 58 50…」のようなバイト列が置かれていて,
CPUはメモリ上からバイト列を読み込みながら実行します.
例えば「6a 00」ならば,スタック上にゼロという値を格納します.
「58」ならばスタックからレジスタに値を復旧する,という動作をします.
数値に対応する動作をするように,CPU内部の回路が設計してあるわけです.
これはCPUがネイティブに実行できる命令です.

しかしCPUへの命令を,「6a 00」「58」などのように数字として扱うのでは,
我々人間にはわかりにくくてしょうがありません.
そこで「6a 00」は便宜上「push $0x0」と表記することにします.
このような人間向きの表記を「ニーモニック」と呼びます.
さらに「push $0x0」というニーモニックがあったら「6a 00」という
バイトコードに変換するようなツールを作成すれば,我々人間はわかり
やすいニーモニックでプログラミングすることができます.
これが「アセンブラ」と呼ばれるツールです.

6.2 命令を3つだけ覚えてみよう

リスト5.1をもう一度見てみましょう.
ニーモニックの部分を見ると,「push」「pop」「inc」という3つの命令が
使われています.つまり,この3つの命令だけ覚えてしまえば,
リスト5.1の動作は読み解けます.

「push」はスタックへの「プッシュ」と呼ばれ,引数で与えられた値を
スタックに保存します.具体的には,スタックポインタの値を減算し,
さらにスタックポインタの指す先のメモリ上に引数の値を書き込みます.
「pop」は「ポップ」と呼ばれその逆の動作で,スタックから値を読み込みます.

「push」の引数には,「$0x0」「$0x616d6179」のような定数値が指定されています.
つまりこれらの定数値をスタック上に書き込むわけです.

また「%eax」「%ebx」のようなものが指定されている箇所もあります.
これらは「レジスタ」と呼ばれるもので,CPUが持っている固定の変数だと思って
いただいて構いません.「push %eax」ならばEAXレジスタに格納されている値を
スタックに書き込み,「pop %edx」ならばスタック上の値をEDXレジスタに読み込む,
ということになります.

そして「inc」はインクリメントと呼ばれる命令で,引数を1,増加させます.
例えば「inc %eax」ならば,EAXレジスタの値を1増加させることになります.

さて,リスト5.1のプログラムは,ここまでの説明でほとんどの部分を読み解くことが
できます.動きを追ってみましょう.

6.3 1句目

まず最初の1句目の5バイトは,push,pop,incから構成されています.


   0:   6a 00                   push   $0x0
   2:   58                      pop    %eax
   3:   50                      push   %eax
   4:   40                      inc    %eax

「0x」は16進数で,という意味です.「0x0」という値,つまりゼロをスタックに
保存します.が,直後のpopですぐに取り出してEAXレジスタに読み込みます.
ということはこれは,スタックを経由してEAXレジスタに「ゼロ」という値を
設定していることになります.

さらにその直後のpushでEAXレジスタの値をスタックに書き込みます.
これでスタックにはゼロが積まれることになります.さらにincでEAXレジスタを
インクリメントすることで,EAXは「1」という値になります.

6.4 2句目

次に2句目の7バイトの部分です.


   5:   68 79 61 6d 61          push   $0x616d6179
   a:   50                      push   %eax
   b:   40                      inc    %eax

push命令により「0x616d6179」という4バイトの値をスタックに積んでいます.
これはASCIIコードで「yama」という文字列になります.
つまりスタック上に,「yama」という文字列が積まれることになります.
リトルエンディアンなので,順番が逆になっていることに注意してください.

EAXにはいま「1」という値が格納されています.2つ目のpushによってEAXの値,
つまり「1」をスタックに積み,incによってEAXをインクリメントします.
これによりEAXの値は2になります.

6.5 3句目

さらに3句目の5バイトの部分に続きます.


   c:   6a 08                   push   $0x8
   e:   5a                      pop    %edx
   f:   5b                      pop    %ebx
  10:   40                      inc    %eax

まずスタックに「8」という値をpushしています.その後それらをEDXとEBXレジスタ
にpopします.これによりEDXには「8」,EBXには先ほどのEAXのpushにより保存された「1」という値がスタック経由で格納されることになります.
なおEAXはinc命令によってさらに増加し,「3」という値になります.

6.6 4句目

次に4句目の7バイトの部分です.


  11:   68 57 61 6b 61          push   $0x616b6157
  16:   54                      push   %esp
  17:   40                      inc    %eax

push命令により「0x616b6157」という4バイト値がスタックに積まれます.
これは「Waka」という文字列になりますので,「Waka」の4文字がスタックに
積まれることになります.スタック上には先ほど積まれた「yama」という
文字列がありますのでこれらは連結し,「Wakayama」という文字列が
置かれていることになります.

さらにESPというレジスタの値をpushしています.ESPはスタックポインタです.
またEAXレジスタはinc命令により増加し「4」という値になります.

6.7 5句目

スタックに保存したESPの値は,最後の5句目の先頭のpop命令によりECXに
戻されます.


  18:   59                      pop    %ecx

このためスタックポインタの値がスタックを経由してECXレジスタにコピーされる
ことになります.スタックポインタは実は先ほどの「Wakayama」という文字列を
指していますので,ECXは「Wakayama」という文字列を指すことになります.

続けて5句目を見ていきましょう.


  19:   cd 80                   int    $0x80

これはシステムコール命令というもので,システムコールを呼び出します.
これでLinuxカーネルに処理が渡り,あとはカーネルがシステムコールの処理を
実行します.

さてここで,レジスタの値は以下のように設定されています.

  • EAX … 4
  • EBX … 1
  • ECX … “Wakayama”
  • EDX … 8

そしてLinuxのシステムコールは,EAXレジスタでシステムコール番号,
EBX/ECX/EDXで第1/2/3引数を渡すことになっています.
システムコール番号が「4」のシステムコールは,writeシステムコールです.
よってこれは以下のようなシステムコールが呼ばれることになります.


write(1, "Wakayama", 8);

これで標準出力に「Wakayama」の8文字が出力されることになります.

最後は後始末です.


  1b:   58                      pop    %eax
  1c:   58                      pop    %eax
  1d:   58                      pop    %eax
  1e:   c3                      ret

スタック上には”Wakayama”の文字列が置かれていますが,これらをpopで回収し,
整合をとります.さらに3つ目のpopでは,最初のほうでスタックに積んでおいた
「ゼロ」という値がEAXレジスタに読み込まれます.これにより関数からの戻り値
として「ゼロ」が設定されます.

最後に「ret」命令は,関数から戻る命令です.これで関数から返ることになります.

7 作品を鑑賞してみよう

このアセンブラ・プログラムはLinuxのwriteシステムコールによって単に
「Wakayama」の8文字を出力するだけのものです.

が,実はプログラム中には,以下のような工夫が入れ込まれています.

  • writeシステムコール番号の「4」という値を生成するのに4つのinc命令を
    使っている.

  • さらにそれらを各句の末尾に配置することで,各句の末尾で韻をふんでいる.
    (最初にあげた「夏休み」のダンプで,各句の末尾が「40」になっていることに注目)

本来,EAXに「4」という値を格納するには直接代入してしまっても構わないのですが,韻を踏むためにあえて4つのinc命令を呼んでいるわけです.

もう一度,作品「夏休み」のバイトコードを見てみましょう.
各句の終端に注目してください.「40」で韻が踏まれていることがわかるでしょうか.


6a 00 58 50 40
68 79 61 6d 61 50 40
6a 08 5a 5b 40
68 57 61 6b 61 54 40
59 cd 80 58 58 58 c3

単なる文字列出力のプログラムではなく,このように趣向を凝らして味わい深さを
追求するものが「アセンブラ短歌」です.

そして作品「夏休み」には,以下のような総評をしています.
「夏休み」というタイトルの意味がおわかりいただけるでしょうか.

最初にゼロ設定したEAXがinc命令で1ずつ増加していく際に,その値を流用して
EBXや戻り値のEAXを設定していくさまは,流れる小川のせせらぎのようです.
またニーモニックを見ると「push-pop-push-inc, push-push-inc」
「push-pop-pop-inc, push-push-inc」というスキップするようなリズムがあり,
小川のほとりで遊ぶ子供たちの情景が浮かんできます.このため「夏休み」という
タイトルにしました.

最後に同じpopが連続していることはいつまでも続くかのような余韻が感じられ,
しかしいつかは(retで)終わってしまうというはかなさもあり,「夏休み」という
テーマがよく表現されています.

各句が40というバイトコードで終了することで韻を踏んでいることも小気味よく,
命令調整のための安易なnopが無いことも爽やかに感じられます.

8 おわりに

いかがだったでしょうか.

アセンブラ短歌は31バイトという短いサイズでプログラムを書くため,
アセンブラ・コーディングの練習用の手軽な題材として扱えます.
このため,アセンブラ学習の入門向けとして良い教材になるのではないかと
思っています.

初心者が学習するためには,教科書となる書籍が必要です.そのようなわけで,
「31バイトでつくるアセンブラプログラミング 〜アセンブラ短歌の世界〜」
という書籍がSECCON関係者によって執筆されています.


book

図8.1: 31バイトでつくるアセンブラプログラミング 〜アセンブラ短歌の世界〜

またアセンブラ短歌が気軽に試せるWebページ「Assembler Tanka on Javascript」
が著者のひとりである愛甲健二さんによって作成されています.

http://07c00.com/asmtanka_on_js/

「スマフォで気軽にアセンブラ短歌」がウリ文句ですので,
ぜひ遊び感覚で気軽に試してみてはいかがでしょうか?


Comments are closed.