アセンブラのインストール (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程大きくなっています。