アセンブラのインストール (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 <mizutani.jun@nifty.ne.jp>
// 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 <mizutani.jun@nifty.ne.jp>
// 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程大きくなっています。