アセンブラ GNU as の基礎知識
PowerPC用のGNUアセンブラを使って、簡単なソースのアセンブル、リンク、実行から始めて、 GNUアセンブラの文法の重要な部分を見ていきます。
Windowsマシンで使用しているインテルのi386系のCPUでは、GNU as と MASM, TASM, NASM等のアセンブラと文法が異なるため、GNU asを使ったプログラミングでは特にレジスタの指定で混乱します。PowerPC用の GNU as は PowerPC用の他のアセンブラとほぼ同じなので安心して GNU as を使いましょう。
アセンブリソースの例
PowerPCのアセンブリプログラムのソースの例です。5行の「hello, world」を表示するだけの簡単なプログラムなので、もっと短くすることもできますが、大きなプログラムでも使えるようにPowerPC用のGNUアセンブラの重要事項を詰め込んでみました。
#-------------------------------------
# プログラム例 hello5.s
#-------------------------------------
#-------------------------------------
# text セクション
#-------------------------------------
.text
.align 2 # 4バイト境界で整列
.global _start # 実行開始位置を指定(必須)
_start:
lis r3, repeat@ha # address of repeat
addi r3, r3, repeat@l
lwz r0, 0(r3)
mtctr r0
lis r4, msg@ha # address of msg
addi r4, r4, msg@l
1: li r3, 1 # stdout
li r5, msg_sz # length
li r0, 4 # sys_write
sc
bdnz 1b
li r3, 0
li r0, 1 # sys_exit
sc
/* 書き換え不要な定数は text セクションに
* 置くことができる */
repeat: .long 5
#-------------------------------------
# data セクション
#-------------------------------------
.data
.align 2
/* ここは初期化が必要なデータを置く *
* プログラムサイズに影響する */
msg: .asciz "hello, world\n"
.align 2 # 4バイト境界に設定
msg_sz = . - msg # 文字列の長さを計算
#-------------------------------------
# bss セクション
#-------------------------------------
.section .bss
.align 2
/* ここは初期化が不要なデータを置く *
* プログラムサイズには影響しない */
data: .long 0 # この例では使っていない
#-------------------------------------
ソースファイルの最終には改行が必要です。ファイルの最後が改行で終わっていない場合はエラーとなります。アセンブリのソースファイルの拡張子には「s」を使います。大文字の「S」の場合は C のプリプロセッサ(cpp)で前処理をする場合に使われます。最近の GNU as はマクロも使えるので C のプリプロセッサを使わなくても困ることはありません。
アセンブルとリンクと実行
「アセンブリ言語のソースをアセンブラでアセンブルする」には as コマンドを使います。レジスタ名を r0 の形式で使用できるようにオプションに「-mregnames」を指定しています。
$ as -mregnames -o hello5.o hello5.s
オブジェクトファイルはリンカで実行可能な形式に変換(リンク)しなければなりません。リンクには ld コマンドを使います。「-o」 の後ろに指定したファイル名が実行可能なコマンドの名前になります。
$ ld -o hello5 hello5.o
実行してみます。アセンブル、リンクして生成された実行可能ファイルの場所はコマンド検索パス(echo $PATH で確認)に入っていない場合が普通なので、前に「./」を付けて実行します。
$ ./hello5 hello, world hello, world hello, world hello, world hello, world $
ソースファイルをアセンブルしてリンクする場合のコマンドが長くて面倒です。次の内容のシェルを作成しておくと便利です。ファイル名を asm として保存してください。
#!/bin/sh as -mregnames -o $1.o $1.s ld -o $1 $1.o
「chmod +x asm」を実行して実行可能にします。検索パスの通ったディレクトリにおきます。「su」を実行した後に、例えば「cp asm /usr/bin」とします。次のように簡単にアセンブルとリンクできるようになります。
$ asm hello5 $ ./hello5 hello, world hello, world hello, world hello, world hello, world $
hello5のサイズを見てみましょう。
$ ls -lt -rwxr-xr-x 1 jun users 1058 Mar 19 23:06 hello5 -rw-r--r-- 1 jun users 724 Mar 19 23:06 hello5.o -rw-r--r-- 1 jun users 1540 Mar 19 23:06 hello5.s
1058バイトでも小さいプログラムですが、strip コマンドで実行に不要な情報を除くことで、さらに小さくすることが可能です。
$ strip -p hello5 $ ls -lt -rwxr-xr-x 1 jun users 564 Mar 19 23:06 hello5
564バイトのコマンドが作成できました。
コメント
プログラム中に埋め込むコメント(注釈)の形式には2種類あります。
- C言語と同じ 「/*」と「*/」に囲まれたコメントで複数行に渡ってコメントを記述できます。
- 「#」から行末までがコメントとなります。
コメントはアセンブル時に1つの空白に置換されます。
セクション
セクションとは、プログラム本体やプログラム中で使用する定数、文字列、変数に必要な性質に応じて区別して管理するためにあります。書き換え不可能な領域(text)、書き換え可能な領域(data)、実行前に値を設定する必要のない領域(bss)があります。複数のソースファイルを別々にアセンブルした後に、まとめてリンカ(ld)でリンクすると同じセクションごとにまとめた形で実行ファイルが生成されます。
text セクション (.text) にはプログラム本体や初期化されて変更する必要のないデータを置きます。
.text
data セクション (.data) は初期化が必要なデータを置きます。data セクションのデータは実行時に書き換えることができます。
.data
bss セクションは実行時にメモリを割り当てる領域です。実行開始時に定義済みの値を持つ必要のないメモリ領域に使用します。bss セクションでは通常 .byte, .long などのシンボル定義や、配列用などのまとまった領域を確保する .skip だけを使います。実行開始時にはすべて 0 に初期化されています。
.section ".bss"
ラベルとシンボル
シンボル名は「.」「_」または英文字から始まる文字列で32ビットの値を持っています。コロン「:」が後ろに付いたシンボルはラベルで、宣言された場所のメモリアドレスを値として持ちます。英文字の大文字と小文字は区別され、異なるシンボル名とみなされます。同じシンボル名は一度だけ宣言できます。コロンを付けて宣言した場所のメモリアドレスを値に持つシンボルが「ラベル」ですが、シンボルとラベルを厳密に区別して考えなくてもいいと思います。
数字のみのシンボル名はローカルシンボル名として特別な機能を持ちます。ローカルシンボル名は何度でも同じシンボル名を使うことができます。参照する場合は前方なら「f」、後方なら「b」をローカルシンボル名の後ろに付けて区別します。例えばソースファイル中で「55:」というローカルラベルが何度も現われても「55b」として参照された場合はソースファイルの後方(上方) で一番近くにある「55:」を示します。同様に「55f」の場合は前方(下方)で一番近くにある「55:」を示します。同じローカルシンボル名でもアセンブラは内部で異なる値として管理しています。ローカルシンボル名は同じファイル内で有効(スコープはファイル)です。
ピリオドだけのシンボル名「.」は、その行のアドレスを示します。「=」を使ってシンボルに任意の値を割り当てることができます。 list01.s では次の部分でシンボルへの値の割り当てと「.」シンボルを使っています
msg0: .asciz "hello, world\n"
.align 2 # 4バイト境界に設定
msg_sz0 = . - msg0 # 文字列の長さを計算
データ形式
整数は2進数、8進数、10進数、16進数で表記することができます。以下の表の書式で記述します。
| 整数 | 表記 | 例 | |
|---|---|---|---|
| 2進数 | 「0b」または「0B」から始まる数値 | 0b0010 | 0B111101 |
| 8進数 | 「0」から始まる数値 | 0123 | 0765 |
| 10進数 | 「1」から「9」で始まる数値 | 234 | 90 |
| 16進数 | 「0x」 「0X」から始まる数値 | 0x9FEB | 0X12ffab |
命令のオペランドの即値とする場合は「#」を前につけます。「.byte, .word, .long」はそのメモリ位置に値を書き込む擬似命令です。
# 例
li r0, 10
addi r1, r2, 0x7F
.byte 100, 0144, 0x64, 0X7f
.word 12356
.long 1234567890, 0xffffffff
文字列はダブルクォート「"」で囲みます。文字列中に特殊な文字を含める場合はエスケープ文字「\」に続けます。
| 表記 | 意味 | 値 |
|---|---|---|
| \b | バックスペース | 0x08 |
| \f | フォームフィード | 0x0C |
| \n | 改行(LF) | 0x0A |
| \r | キャリッジリターン(CR) | 0x0D |
| \t | タブ | 0x09 |
| \3桁の8進数 | 対応する文字コードの文字 | |
| \x16進数 | 対応する文字コードの文字 | |
| \\ | 文字「\」 | |
| \" | 文字「"」 | |
文字列は text セクションや data セクションでラベルに続けて使用します。文字列に続けて命令や整数を置く場合は、文字列の直後に「.align 2」を置いて4バイト境界に配置するようにします。「.asciz」は文字列の直後に 0 が挿入されます。
# 例
str1: .ascii "mov\tr1, r2\n"
.align 2
num1: .long 1234567890
str2: .asciz "New Line\n"
.align 2
num2: .long 0x12345678
文字定数はシングルクォート「'」を前においてあらわします。特殊文字は文字列と同様にエスケープ文字「\」に続けた形式を利用できます。文字定数は 1バイトの値(文字コード)となります。オペランドで使う定数(即値)は前に「#」をつける必要があります。
# 例
li r1, 'A
addi r0, r1, '0
.byte 'J, '\n
式
整数定数やシンボルを記述する位置に整数定数やシンボルを使った式を使うことができます。使用できる演算子には単項演算子と二項演算子があります。単項演算子には2種類あり、定数の前につけて 2 の補数 (-1を乗算した値) を求める「-」と 1 の補数(ビット否定)を求める「~」が利用できます。二項演算子はCと同様な以下の演算子があります。
| 優先順位 | 演算子 | 意味 |
|---|---|---|
| 1 | * | 乗算 |
| 1 | / | 除算 |
| 1 | % | 剰余 |
| 1 | < << | 左シフト |
| 1 | > >> | 右シフト |
| 2 | | | ビット論理和 OR |
| 2 | & | ビット論理積 AND |
| 2 | ^ | ビット排他的論理和 XOR |
| 2 | ! | ビット否定 NOT |
| 3 | + | 加算 |
| 3 | - | 減算 |
| 3 | == | 等しい (真:-1 偽:0) |
| 3 | <> | 等しくない (真:-1 偽:0) |
| 3 | < | より小さい (真:-1 偽:0) |
| 3 | > | より大きい (真:-1 偽:0) |
| 3 | >= | 大きいか等しい (真:-1 偽:0) |
| 3 | <= | 小さいか等しい (真:-1 偽:0) |
| 4 | && | 論理積 (真:1 偽:0) |
| 4 | || | 論理和 (真:1 偽:0) |
通常の計算と同じく括弧を使って優先順位を変更することもできます。
擬似命令
アセンブラには直接命令に変換されない擬似命令が多く用意されています。これまでにセクションの指定「.text, .data, .bss」、メモリの境界に配置する「.align」、定数をメモリに書き込む「.byte, .word, .asciz」などを使ってきました。
条件に従ってアセンブルを制御する条件アセンブリ擬似命令もあります。デバッグ用のコードを挿入したり、1本のソースから複数のプログラムを作成する場合などに利用できます。
.ifdef SYMBOL
/* シンボル SYMBOL が定義済みのときにアセンブルされるコード */
.else
/* シンボル SYMBOL が未定義のときにアセンブルされるコード */
.endif
「.ifdef」以外にも「.ifndef」、「.ifeq」、「.ifne」などのバリエーションがあります。
拡張ニーモニック
PowerPCには標準フォーマットのニーモニックではオペランドの記述が複雑になるため、拡張ニーモニック(Extended Mnemonics, Simplified Mnemonics)と呼ばれる命令があります。拡張ニーモニックは、別名としてよく使われる命令とオペランドを組み合わせて分かりやすい命令をつけたものです。例えば 「ori r0,r0,0」に対して「nop」という拡張ニーモニックを使うことができます。
マクロ
最近のGNU asはマクロが使えるようになっていて cpp (Cのプリプロセッサ)を使う必要がなくなっています。マクロは次の形式で宣言して、マクロ名を命令のように使うことができます。
.macro マクロ名 引数
定義
.endm
例えば、次のマクロは任意のレジスタに32ビットのアドレスまたは定数を設定します。マクロ定義の中で、引数名の前に「\」を付けて引数を参照することができます。
#---------------------------------------------------------------
# シンボル(32bit)をレジスタに設定
#---------------------------------------------------------------
.macro LWI reg, symbol
lis \reg, \symbol@ha
addi \reg, \reg, \symbol@l
.endm