アセンブラのインストール (2015-12-11)

「Linux で Arm64 アセンブリプログラミング」シリーズでは、サンプルプログラムを Linux 上で作成します。 Linux 用のアセンブラは GNU Binutils というパッケージにリンカとともに含まれています。 GNU Binutils にはアセンブラ、リンカ、逆アセンブラのほかにも色々なツールが含まれています。使いそうなものを簡単に紹介します。 また、拙作の ARM64 アセンブリプログラミング用の簡単な標準入出力用のコードも紹介します。

GNU Binutils のインストール

Debian や Ubuntu の場合、次のコマンドを実行するだけです。gcc (C コンパイラ) が動作している環境ならば、必ずインストール済みです。

  $ sudo apt-get install binutils

アセンブラは「as」というコマンド、アセンブラが生成したオブジェクトファイル (拡張子が「.o」のファイル) から実行ファイルを作成するリンカは「ld」という コマンドです。 実行ファイルからアセンブリコードを作成(逆アセンブル)する コマンドの「objdump」もインストールされます。

Arm64仮想マシンの作成と同じサンプルですが、 Arm64 のアセンブラで書いた「hello、world」です。 hello.s というファイル名 で保存します。

.text
        .global _start
_start:
        mov     x2,  #13    // x2  length
        adr     x1,  msg    // x1  string address
        mov     x0,  #1     // x0  stdout
        mov     x8,  #64
        svc     #0          // sys_write
        mov     x0,  xzr
        mov     x8,  #93
        svc     #0          // exit
msg:
        .asciz  "hello, world\n"

コードの意味は今後の課題として、コマンドの使い方を見ておきましょう。 次の例では、ソースファイルの「hello.s」を、アセンブラの「as」でアセンブル してオブジェクトファイルの「hello.o」を作ります。 このままでは 実行できないので、実行ファイルを作成するリンカの「ld」を使って 「hello」という実行ファイルを作成して、実行しています。

アセンブルとリンク

ソースファイルをアセンブルしてオブジェクトファイルを作成する コマンドのアセンブラは「as」、オブジェクトファイルを実行ファイルに 変換するリンカは「ld」です。 どちらのコマンドも -o の次に生成するファイル名を指定しています。

$ as -o hello.o hello.s
$ ld -o hello hello.o

簡単ですね。「hello」を入れ替えれば、色々なソースファイルを アセンブル/リンク/実行できます。 この例ではオブジェクトファイルが1つ(hello.o) ですが、複数のファイル を分割してアセンブルし、リンカで1つの実行ファイルにまとめることも できます。「リンクする」ためのリンカです。

実行します。実行ファイルを実行するには、今いるディレクトリを 指定するために、実行ファイルの前に「./」を付けるのを忘れないように しましょう

$ ./hello
hello, world

ファイルサイズを見ておきます。hello は 952 バイトです。

$ ls -l hello*
-rwxrwxr-x 1 jun jun 952 Dec 10 03:10 hello
-rw-rw-r-- 1 jun jun 824 Dec 10 03:01 hello.o
-rw-rw-r-- 1 jun jun 361 Dec 10 03:00 hello.s

nm

アセンブル、リンクした実行ファイルにはシンボル情報が残っています。 実行ファイルのシンボル情報を表示するコマンドとして「nm」があります。 単純に「nm ファイル名」で実行できます。

$ nm hello
00000000004100a6 T __bss_end__
00000000004100a6 T _bss_end__
00000000004100a6 T __bss_start
00000000004100a6 T __bss_start__
00000000004100a6 T _edata
00000000004100a8 T _end
00000000004100a8 T __end__
0000000000400098 t msg
0000000000400078 T _start

strip

上記のシンボル情報は実行には不要なので削除することができます。 そのためのコマンドが「strip」です。 実行してファイルサイズの変化を 確認してみます

$ strip hello
$ ls -l hello
-rwxrwxr-x 1 jun jun 376 Dec 10 03:12 hello

952バイトから376バイトに576バイト小さくなりました。もう一度「nm」を 実行してもシンボル情報は削除されているため何も表示されません。

$ nm hello
nm: hello: no symbols

objdump

実行ファイルを「objdump」コマンドで逆アセンブルして、元の アセンブリコードを確認できます。

$ objdump -d hello

hello:     file format elf64-littleaarch64


Disassembly of section .text:

0000000000400078 <.text>:
  400078:       d28001a2        mov     x2, #0xd                        // #13
  40007c:       100000e1        adr     x1, 0x400098
  400080:       d2800020        mov     x0, #0x1                        // #1
  400084:       d2800808        mov     x8, #0x40                       // #64
  400088:       d4000001        svc     #0x0
  40008c:       aa1f03e0        mov     x0, xzr
  400090:       d2800ba8        mov     x8, #0x5d                       // #93
  400094:       d4000001        svc     #0x0
  400098:       6c6c6568        ldnp    d8, d25, [x11,#-320]
  40009c:       77202c6f        .inst   0x77202c6f ; undefined
  4000a0:       646c726f        .inst   0x646c726f ; undefined
  4000a4:       Address 0x00000000004000a4 is out of bounds.

od

ファイルのダンプリストを色々なフォーマットで出力するコマンドです。 次の例は hello をバイト単位で16進表示のリストを表示しています。

$ od -t x1 hello
0000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
0000020 02 00 b7 00 01 00 00 00 78 00 40 00 00 00 00 00
0000040 40 00 00 00 00 00 00 00 b8 00 00 00 00 00 00 00
0000060 00 00 00 00 40 00 38 00 01 00 40 00 03 00 02 00
0000100 01 00 00 00 05 00 00 00 00 00 00 00 00 00 00 00
0000120 00 00 40 00 00 00 00 00 00 00 40 00 00 00 00 00
0000140 a6 00 00 00 00 00 00 00 a6 00 00 00 00 00 00 00
0000160 00 00 01 00 00 00 00 00 a2 01 80 d2 e1 00 00 10
0000200 20 00 80 d2 08 08 80 d2 01 00 00 d4 e0 03 1f aa
0000220 a8 0b 80 d2 01 00 00 d4 68 65 6c 6c 6f 2c 20 77
0000240 6f 72 6c 64 0a 00 00 2e 73 68 73 74 72 74 61 62
0000260 00 2e 74 65 78 74 00 00 00 00 00 00 00 00 00 00
0000300 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
0000360 00 00 00 00 00 00 00 00 0b 00 00 00 01 00 00 00
0000400 06 00 00 00 00 00 00 00 78 00 40 00 00 00 00 00
0000420 78 00 00 00 00 00 00 00 2e 00 00 00 00 00 00 00
0000440 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00
0000460 00 00 00 00 00 00 00 00 01 00 00 00 03 00 00 00
0000500 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000520 a6 00 00 00 00 00 00 00 11 00 00 00 00 00 00 00
0000540 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00
0000560 00 00 00 00 00 00 00 00

次に32ビットの数値としてダンプしています。 リトルエンディアンのため、 バイト単位の出力と比べて、4バイト単位で逆転して見えます。

$ od -t x4 hello
0000000 464c457f 00010102 00000000 00000000
0000020 00b70002 00000001 00400078 00000000
0000040 00000040 00000000 000000b8 00000000
0000060 00000000 00380040 00400001 00020003
..

同じファイルを PowerPC 上でダンプしてみました。ビッグエンディアンの マシンでは、バイト単位の出力と同じ順序でリストされます。

jun@KURO-BOX:~$ od -t x4 hello
0000000 7f454c46 02010100 00000000 00000000
0000020 0200b700 01000000 78004000 00000000
0000040 40000000 00000000 b8000000 00000000
0000060 00000000 40003800 01004000 03000200
..

strings

ファイルに含まれている印刷可能文字を抽出します。

$ strings hello
hello, world
.shstrtab
.text

size

実行ファイルのセクションのサイズを表示します。次の readelf の簡易版という感じです。

$ size hello
   text    data     bss     dec     hex filename
     46       0       0      46      2e hello

readelf

Linux の実行ファイルは ELF という形式です。 このELF形式のヘッダー情報、セクション情報の詳細を出力します。 リンカを作る場合ぐらいしか使い道を思いつきません(笑)。

$ readelf -a hello
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           AArch64
  Version:                           0x1
  Entry point address:               0x400078
  Start of program headers:          64 (bytes into file)
  Start of section headers:          184 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         1
  Size of section headers:           64 (bytes)
  Number of section headers:         3
  Section header string table index: 2

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .text             PROGBITS         0000000000400078  00000078
       000000000000002e  0000000000000000  AX       0     0     1
  [ 2] .shstrtab         STRTAB           0000000000000000  000000a6
       0000000000000011  0000000000000000           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)

There are no section groups in this file.

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x00000000000000a6 0x00000000000000a6  R E    10000

 Section to Segment mapping:
  Segment Sections...
   00     .text

There is no dynamic section in this file.

There are no relocations in this file.

The decoding of unwind sections for machine type AArch64 is not currently supported.

No version information found in this file.

ヘルパーライブラリ

標準入出力ルーチン

アセンブリプログラミング用のヘルパーライブラリとして、簡単な標準入出力ルーチンを用意しました。 stdio_arm64.tar.gz (6KB) です。 次のファイルが入っています。

-rw-r--r-- 1 jun jun 10228 Dec 10 16:13 syscalls.s
-rw-r--r-- 1 jun jun 14869 Dec 10 16:12 stdio.s
-rw-r--r-- 1 jun jun  3676 Dec 10 16:12 debug.s
-rw-r--r-- 1 jun jun   362 Dec 10 16:12 hello.s

下の表にしたがって x0、 x1 に引数を設定して「bl ルーチン名」で呼び出せます。 (破壊)となっているレジスタ以外は保存されます。

機能 ルーチン名 引数1 (x0) 引数2 (x1)
プログラム終了 Exit -
プログラム終了 ExitN 終了コード
文字列出力 OutString 文字列先頭アドレス文字列長
文字列出力(終端0) OutAsciiZ 文字列先頭アドレス
Pascal文字列出力 OutPString 文字列先頭アドレス
1文字出力 OutChar 文字コード
4文字出力 OutChar4 下位4文字
8文字出力 OutChar8 8文字
改行出力 NewLine -
バックスペース出力 BackSpace -
2進数出力 PrintBinary 数値 桁数
8進数出力 PrintOctal 数値 桁数
16進数2桁出力 PrintHex2 数値 (破壊)
16進数4桁出力 PrintHex4 数値 (破壊)
16進数8桁出力 PrintHex8 数値 (破壊)
16進数16桁出力 PrintHex16 数値 (破壊)
16進数出力 PrintHex 数値 桁数
無符号整数出力 PrintLeftU 数値
符号付整数出力 PrintLeft 数値
右0詰符号付整数出力 PrintRight0 数値 桁数
右詰無符号整数出力 PrintRightU 数値 桁数
右詰符号付整数出力 PrintRight 数値 桁数
1文字入力 InChar 文字が返る
1行入力 InputLine0 バッファサイズバッファアドレス

ちょっと長いですが、ARM64 のアセンブリコードの例として、 リストも掲載します。

// ------------------------------------------------------------------------
// Standard I/O Subroutine for ARM64
//   2015/07/29 arm64 system call
// Copyright (C) 2015  Jun Mizutani <[email protected]>
// stdio.s may be copied under the terms of the GNU General Public License.
// ------------------------------------------------------------------------

// SP must be quad-word aligned.

.ifndef __STDIO
__STDIO = 1

.ifndef __SYSCALL
  .equ sys_exit,  93
  .equ sys_read,  63
  .equ sys_write, 64
.endif

.text

//------------------------------------
// exit with 0
Exit:
        mov     x0, xzr
        mov     x8, #sys_exit
        svc     #0
        ret                         // ret x30

//------------------------------------
// exit with x0
ExitN:
        mov     x8, #sys_exit
        svc     #0
        ret


//------------------------------------
// print string to stdout
// x0 : address, x1 : length
OutString:
        stp     x8, x30, [sp, #-16]!
        stp     x0,  x1, [sp, #-16]!
        stp     x2,  x3, [sp, #-16]!    // x3 : filler
        mov     x2,  x1                 // a2  length
        mov     x1,  x0                 // a1  string address
        mov     x0,  #1                 // a0  stdout
        mov     x8,  #sys_write
        svc     #0
        ldp     x2,  x3, [sp], #16
        ldp     x0,  x1, [sp], #16
        ldp     x8, x30, [sp], #16
        ret

//------------------------------------
// input  x0 : address
// output x1 : return length of strings
StrLen:
        stp     x2, x30, [sp, #-16]!
        stp     x0, x3,  [sp, #-16]!    // x3 : filler
        mov     x1, xzr                 // x1 : counter
1:      ldrb    w2, [x0], #1            // x2 = *pointer++ (1byte)
        cmp     x2, #0
        add     x1, x1, #1              // counter++
        bne     1b
        sub     x1, x1, #1              // counter++
        ldp     x0, x3, [sp], #16
        ldp     x2, x30, [sp], #16
        ret

//------------------------------------
// print asciiz string
// x0 : pointer to string
OutAsciiZ:
        stp     x1, x30, [sp, #-16]!
        bl      StrLen
        bl      OutString
        ldp     x1, x30, [sp], #16
        ret

//------------------------------------
// print pascal string to stdout
// x0 : top address
OutPString:
        stp     x0, x30, [sp, #-16]!
        stp     x1, x2, [sp, #-16]!
        ldrb    w1, [x0]
        add     x0, x0, #1
        bl      OutString
        ldp     x1, x2, [sp], #16
        ldp     x0, x30, [sp], #16
        ret

//------------------------------------
// print 1 character to stdout
// x0 : put char
OutChar:
        stp     x8, x30, [sp, #-16]!
        stp     x1, x2,  [sp, #-16]!
        stp     x0, x1, [sp, #-16]!
        mov     x1, sp                  // x1  address
        mov     x0, #1                  // x0  stdout
        mov     x2, x0                  // x2  length
        mov     x8, #sys_write
        svc     #0
        ldp     x0, x1, [sp], #16
        ldp     x1, x2,  [sp], #16
        ldp     x8, x30, [sp], #16
        ret

//------------------------------------
// print 4 printable characters in x0 to stdout
OutChar4:
        stp     x2, x30, [sp, #-16]!
        stp     x0, x1,  [sp, #-16]!
        mov     x2, #4
        b       OutCharN

//------------------------------------
// print 8 printable characters in x0 to stdout
OutChar8:
        stp     x2, x30, [sp, #-16]!
        stp     x0, x1,  [sp, #-16]!
        mov     x2, #8

OutCharN:
        mov     x1, x0
1:
        and     x0, x1, #0x7F
        cmp     x0, #0x20
        b.ge    2f
        mov     x0, #'.'
2:
        bl      OutChar
        lsr     x1, x1, #8
        subs    x2, x2, #1
        b.ne    1b
        ldp     x0, x1,  [sp], #16
        ldp     x2, x30, [sp], #16
        ret


//------------------------------------
// new line
NewLine:
        stp     x0, x30, [sp, #-16]!
        mov     x0, #10
        bl      OutChar
        ldp     x0, x30, [sp], #16
        ret


//------------------------------------
// Backspace
BackSpace:
        stp     x0, x30, [sp, #-16]!
        mov     x0, #8
        bl      OutChar
        mov     x0, #' '
        bl      OutChar
        mov     x0, #8
        bl      OutChar
        ldp     x0, x30, [sp], #16
        ret

//------------------------------------
// print binary number
//   x0 : number
//   x1 : bit
PrintBinary:
        stp     x0, x30, [sp, #-16]!
        stp     x1, x2,  [sp, #-16]!
        cmp     x1, #0                  // x1 > 0 ?
        b.eq    4f                      // if x1=0 exit
        mov     x2, #64
        cmp     x1, x2
        b.le    1f
        mov     x1, x2                  // if x1>64 then x1=64
    1:  subs    x2, x2, x1
        lsl     x2, x0, x2              // discard upper 64-x1 bit
    2:  mov     x0, #'0'
        adds    x2, xzr, x2
        b.pl    3f
        add     x0, x0, #1
    3:  bl      OutChar
        lsl     x2, x2, #1
        subs    x1, x1, #1
        b.ne    2b
    4:
        ldp     x1, x2,  [sp], #16
        ldp     x0, x30, [sp], #16
        ret

//------------------------------------
// print ecx digit octal number
//   x0 : number
//   x1 : columns
PrintOctal:
        stp     x0, x30, [sp, #-16]!
        stp     x1, x2,  [sp, #-16]!
        stp     x3, x6,  [sp, #-16]!
        mov     x6, sp
        sub     sp, sp, #32             // allocate buffer
        cmp     x1, #0                  // x1 > 0 ?
        beq     3f                      // if x1=0 exit
        mov     x3, x1                  // column
    1:  and     x2, x0, #7
        lsr     x0, x0, #3
        strb    w2, [x6, #-1]!
        subs    x3, x3, #1
        bne     1b
    2:  ldrb    w0, [x6], #1            // 上位桁から POP
        add     x0, x0, #'0'            // 文字コードに変更
        bl      OutChar                 // 出力
        subs    x1, x1, #1              // column--
        bne     2b
    3:
        add     sp, sp, #32
        ldp     x3, x6,  [sp], #16
        ldp     x1, x2,  [sp], #16
        ldp     x0, x30, [sp], #16
        ret

//------------------------------------
// print 2 digit hex number (lower 8 bit of x0)
//   x0 : number
PrintHex2:
        mov     x1, #2
        b       PrintHex

//------------------------------------
// print 4 digit hex number (lower 16 bit of x0)
//   x0 : number
PrintHex4:
        mov     x1, #4
        b       PrintHex

//------------------------------------
// print 8 digit hex number (x0)
//   x0 : number
PrintHex8:
        mov     x1, #8
        b       PrintHex

PrintHex16:
        mov     x1, #16

//------------------------------------
// print hex number
//   x0 : number     x1 : digit
PrintHex:
        stp     x0, x30, [sp, #-16]!
        stp     x1, x2,  [sp, #-16]!
        stp     x3, x6,  [sp, #-16]!    // x6 : pointer for  buffer
        mov     x6, sp
        sub     sp, sp, #16             // allocate buffer
        mov     x3, x1                  // column
1:      and     x2, x0, #0x0F           //
        lsr     x0, x0, #4              //
        orr     x2, x2, #0x30
        cmp     x2, #0x39
        b.le    3f
        add     x2, x2, #0x41-0x3A      // if (x2>'9') x2+='A'-'9'
3:
        strb    w2,  [x6, #-1]!         // first in/last out
        subs    x3, x3, #1              // column--
        b.ne    1b
        mov     x3, x1                  // column
2:
        ldrb    w0, [x6], #1
        bl      OutChar
        subs    x3, x3, #1              // column--
        b.ne    2b
        add     sp, sp, #16
        ldp     x3, x6,  [sp], #16
        ldp     x1, x2,  [sp], #16
        ldp     x0, x30, [sp], #16
        ret

//------------------------------------
// Output Unsigned Number to stdout
// x0 : number
PrintLeftU:
        stp     x0, x30, [sp, #-16]!
        stp     x1, x2,  [sp, #-16]!
        stp     x3, x4,  [sp, #-16]!
        stp     x5, x6,  [sp, #-16]!    // x6 : fp
        mov     x6, sp
        sub     sp, sp, #32             // allocate buffer
        mov     x2, #0                  // counter
        mov     x3, #0                  // positive flag
        b       1f

//------------------------------------
// Output Number to stdout
// x0 : number
PrintLeft:
        stp     x0, x30, [sp, #-16]!
        stp     x1, x2,  [sp, #-16]!
        stp     x3, x4,  [sp, #-16]!
        stp     x5, x6,  [sp, #-16]!    // x6 : fp
        mov     x6, sp
        sub     sp, sp, #32             // allocate buffer
        mov     x2, #0                  // counter
        mov     x3, #0                  // positive flag
        cmp     x0, #0
        b.ge    1f
        mov     x3, #1                  // set negative
        sub     x0, x2, x0              // x0 = 0-x0
    1:  mov     x1, #10                 // x3 = 10
        udiv    x5, x0, x1              // division by 10
        msub    x1, x5, x1, x0
        mov     x0, x5
        add     x2, x2, #1              // counter++
        strb    w1, [x6, #-1]!          // least digit (reminder)
        cmp     x0, #0
        bne     1b                      // done ?
        cmp     x3, #0
        b.eq    2f
        mov     x0, #'-'                // if (x0<0) putchar("-")
        bl      OutChar                 // output '-'
    2:  ldrb    w0, [x6], #1            // most digit
        add     x0, x0, #'0'            // ASCII
        bl      OutChar                 // output a digit
        subs    x2, x2, #1              // counter--
        bne     2b
        add     sp, sp, #32
        ldp     x5, x6,  [sp], #16
        ldp     x3, x4,  [sp], #16
        ldp     x1, x2,  [sp], #16
        ldp     x0, x30, [sp], #16
        ret

//------------------------------------
// Output Number to stdout
// x1:column
// x0:number
PrintRight0:
        stp     x0, x30, [sp, #-16]!
        stp     x1, x2,  [sp, #-16]!
        stp     x3, x4,  [sp, #-16]!
        stp     x5, x6,  [sp, #-16]!
        stp     x7, x8,  [sp, #-16]!    // x8 : fp
        mov     x8, sp
        sub     sp, sp, #32             // allocate buffer
        mov     x4, #'0'
        b       0f

//------------------------------------
// Output Unsigned Number to stdout
// x1:column
// x0:number
PrintRightU:
        stp     x0, x30, [sp, #-16]!
        stp     x1, x2,  [sp, #-16]!
        stp     x3, x4,  [sp, #-16]!
        stp     x5, x6,  [sp, #-16]!
        stp     x7, x8,  [sp, #-16]!    // x8 : fp
        mov     x8, sp
        sub     sp, sp, #32             // allocate buffer
        mov     x4, #' '
    0:  mov     x5, x1
        mov     x2, #0                  // counter
        mov     x3, #0                  // positive flag
        b       1f                      // PrintRight.1

//------------------------------------
// Output Number to stdout
// x1:column
// x0:number
PrintRight:
        stp     x0, x30, [sp, #-16]!
        stp     x1, x2,  [sp, #-16]!
        stp     x3, x4,  [sp, #-16]!
        stp     x5, x6,  [sp, #-16]!
        stp     x7, x8,  [sp, #-16]!    // x8 : fp
        mov     x8, sp
        sub     sp, sp, #32             // allocate buffer
        mov     x4, #' '
        mov     x5, x1
        mov     x2, xzr                 // counter=0
        mov     x3, xzr                 // positive flag
        cmp     x0, xzr
        b.ge    1f
        mov     x3, #1                  // set negative
        sub     x0, xzr, x0             // x0 = 0-x0
    1:  mov     x1, #10                 // x3 = 10
        udiv    x7, x0, x1              // division by 10
        mul     x6, x7, x1
        sub     x1, x0, x6              // x1:remainder
        mov     x0, x7                  // x0 : quotient7
        add     x2, x2, #1              // counter++
        strb    w1, [x8, #-1]!          // least digit
        cmp     x0, #0
        bne     1b                      // done ?

        subs    x5, x5, x2              // x5 = no. of space
        ble     3f                      // dont write space
        cmp     x3, #0
        b.eq    2f
        sub     x5, x5, #1              // reserve spase for -
    2:  mov     x0, x4                  // output space or '0'
        bl      OutChar
        subs    x5, x5, #1              // nspace--
        bgt     2b

    3:  cmp     x3, #0
        b.eq    4f
        mov     x0, #'-'                // if (x0<0) putchar("-")

    3:  cmp     x3, #0
        b.eq    4f
        mov     x0, #'-'                // if (x0<0) putchar("-")
        bl      OutChar                 // output '-'
    4:  ldrb    w0, [x8], #1            // most digit
        add     x0, x0, #'0'            // ASCII
        bl      OutChar                 // output a digit
        subs    x2, x2, #1              // counter--
        b.ne    4b
        add     sp, sp, #32
        ldp     x7, x8,  [sp], #16      // x8 : filler
        ldp     x5, x6,  [sp], #16
        ldp     x3, x4,  [sp], #16
        ldp     x1, x2,  [sp], #16
        ldp     x0, x30, [sp], #16
        ret

//------------------------------------
// input 1 character from stdin
// x0 : get char
InChar:
        mov     x0, xzr                 // clear upper bits
        stp     x0, x30, [sp, #-16]!
        stp     x1, x2,  [sp, #-16]!
        stp     x8, x3,  [sp, #-16]!    // x3 : filler
        add     x1, sp, #32             // x1(stack) address
        mov     x0, #0                  // x0  stdin
        mov     x2, #1                  // x2  length
        mov     x8, #sys_read
        svc     #0
        ldp     x8, x3,  [sp], #16
        ldp     x1, x2,  [sp], #16
        ldp     x0, x30, [sp], #16
        ret

//------------------------------------
// Input Line
// x0 : BufferSize
// x1 : Buffer Address
// return       x0 : no. of char
InputLine0:
        stp     x1, x30, [sp, #-16]!
        stp     x2, x3,  [sp, #-16]!
        stp     x4, x5,  [sp, #-16]!
        mov     x4, x0                  // BufferSize
        mov     x5, x1                  // Input Buffer
        mov     x3, xzr                 // counter
    1:
        bl      InChar
        cmp     x0, #0x08               // BS ?
        bne     2f
        cmp     x3, #0
        beq     2f
        bl      BackSpace               // backspace
        sub     x3, x3, #1
        b       1b
    2:
        cmp     x0, #0x0A               // enter ?
        beq     4f                      // exit

        bl      OutChar                 // printable:
        strb    w0, [x5, x3]            // store a char into buffer
        add     x3, x3, #1
        cmp     x3, x4
        bge     3f
        b       1b
    3:
        sub     x3, x3, #1
        bl      BackSpace
        b       1b

    4:  mov     x0, #0
        strb    w0, [x5, x3]
        add     x3, x3, #1
        bl      NewLine
        mov     x0, x3
        ldp     x4, x5,  [sp], #16
        ldp     x2, x3,  [sp], #16
        ldp     x1, x30, [sp], #16
        ret

.endif

debug.s

アセンブラでプログラムする上でデバッグが大変です。そこで、プログラムの 他の部分に影響しないで、レジスタの値の出力や、フラグの状態の表示、 文字列の表示などを行う debug.s です。 ARM64 のアセンブリ言語でマクロを 使う例として示します。

例えば、PRINTREG x15 という行を、調べたいソースに挿入すると、 そこを実行した時点の x15 レジスタの値が、符号付き10進数、無符号10進数、 16進数、文字コードで表示されます。すべてのレジスタの値にもフラグの値にも 影響を与えません(そのはずです)。PRINTFLAGS は実行時点でステータスレジスタの 値(NZCV)を、1 なら大文字、0 なら小文字で 「NzCv」のように表示します。

//-------------------------------------------------------------------------
//  Debugging Macros for ARM64 assembly
//  file : debug.s
//  2015/08/06
//  Copyright (C) 2015 Jun Mizutani <[email protected]>
//  This file may be copied under the terms of the GNU General Public License.
//-------------------------------------------------------------------------

.ifndef __STDIO
.include "stdio.s"
.endif


//          +------+
//          | x30  |
//          +------+
//    sp->  | x0   | +16
//          +------+
//          | x1   | +8
//          +------+
//    sp->  | nzcv | +0
//          +======+
.macro  ENTER
        stp     x0, x30, [sp, #-16]!
        mrs     x0, nzcv
        stp     x0, x1,  [sp, #-16]!
        ldr     x0, [sp, #+16]
.endm

.macro  LEAVE
        ldp     x0,  x1, [sp], #16
        msr     nzcv, x0
        ldp     x0, x30, [sp], #16
.endm

// Print a register value (x0 - x30)
// x0 - x30, nzcv registers are unchanged.
// ex. PRINTREG x0
// x0 -4520909795638496974 13925834278071054642:C142807E61623132 21ba~.BA
.macro  PRINTREG   reg
        ENTER
        adr     x0, 998f
        bl      OutAsciiZ
        ldr     x0, [sp, #+16]
        ldr     x1, [sp, #+8]
        mov     x0, \reg
        mov     x1, #21
        bl      PrintRight
        bl      PrintRightU
        mov     x0, #':'
        bl      OutChar
        ldr     x0, [sp, #+16]
        ldr     x1, [sp, #+8]
        mov     x0, \reg
        bl      PrintHex16
        mov     x0, #' '
        bl      OutChar
        ldr     x0, [sp, #+16]
        bl      OutChar8
        bl      NewLine
        LEAVE
        b       999f
        .align  2
998:    .asciz "\reg"
        .align  2
999:
.endm

// Print ASCIIZ string from the address value in the register.
//   ex. PRINTSTR x11
.macro  PRINTSTR   reg
        ENTER
        mov     x0, \reg
        bl      OutAsciiZ
        bl      NewLine
        LEAVE
.endm

// Print ASCIIZ string from the operand.
//   ex. PRINT  string
.macro  PRINT   reg
        ENTER
        adr     x0, 998f
        bl      OutAsciiZ
        bl      NewLine
        LEAVE
        b       999f
998:    .asciz "\reg"
        .align  2
999:
.endm

// Print ASCIIZ string from the address value in the memory
// pointed by the register.
//   ex. PRINTSTRI x11
.macro  PRINTSTRI  reg
        ENTER
        ldr     x0, [\reg]
        bl      OutAsciiZ
        bl      NewLine
        LEAVE
.endm

// Print a number.
//   ex. CHECK 99
.macro  CHECK   number
        ENTER
        mov     x0, #\number
        bl      PrintLeft
        bl      NewLine
        LEAVE
.endm

// Print NZCV flags in Hex format(4bit).
.macro  PRINTFLAGSHEX
        ENTER
        ldr     x0, [sp]
        lsr     x0, x0, #28
        mov     X1, #1
        bl      PrintHex
        bl      NewLine
        LEAVE
.endm

// Print NZCV flags such as "nzcv, NzCv".
.macro  PRINTFLAGS
        ENTER
        ldr     x1, [sp]
        lsl     x1, x1, #32
        mov     x0, #'n'
        adds    x1, x1, xzr
        b.pl    1f
        sub     x0, x0, #0x20
    1:  bl      OutChar
        lsl     x1, x1, #1
        mov     x0, #'z'
        adds    x1, x1, xzr
        b.pl    2f
        sub     x0, x0, #0x20
    2:  bl      OutChar
        lsl     x1, x1, #1
        mov     x0, #'c'
        adds    x1, x1, xzr
        b.pl    3f
        sub     x0, x0, #0x20
    3:  bl      OutChar
        lsl     x1, x1, #1
        mov     x0, #'v'
        adds    x1, x1, xzr
        b.pl    4f
        sub     x0, x0, #0x20
    4:  bl      OutChar
        bl      NewLine
        LEAVE
.endm

// Wait until key press.
.macro  PAUSE
        ENTER
        bl      InChar
        LEAVE
.endm

sample1.s

stdio.s を使って「hello, world」を表示してみましょう。

.include        "stdio.s"
.text
.global _start
_start:
        adr     x0, msg
        bl      OutAsciiZ
        bl      Exit
msg:
        .asciz  "hello, world\n"

実行例

$ as -o sample.o sample.s
$ ld -o sample sample.o
$ ./sample
hello, world

最初のサンプルと同じように動作することが確認できました。

$ ls -l sample*
-rwxrwxr-x 1 jun jun 3256 Dec 10 06:57 sample
-rw-rw-r-- 1 jun jun 3112 Dec 10 06:57 sample.o
-rw-rw-r-- 1 jun jun  165 Dec 10 06:57 sample.s
$ strip sample
$ ls -l sample
-rwxrwxr-x 1 jun jun 1672 Dec 10 06:58 sample

stdio.s のせいで1.5KB程大きくなっています。


続く...