ARMの説明

ARMの開発環境
(1) ARM 社のもの
ARM Software Development Toolkit(アカデミック版)
http://cis.k.hosei.ac.jp/~nakata/lectureMachine/202u_w32_v1.zip
ARM Developer Suite(評価版 45日間)
S. Furber著/アーム(株)監訳「改訂ARMプロセッサ」CQ出版社
(2) DevKitAdv
http://devkitadv.sourceforge.net/index.html
(3) GCC
Linux/GBA用
西田亙著「Linuxから目覚める僕らのゲームボーイ」
ソフトバンク パブリッシング
Linux/Zaurus用
http://www.nk.rim.or.jp/~jun/slasm/arm00.html
Linux Zaurusでアセンブリプログラミング
ARM Software Development Toolkit
のインストール
(1) 202u_w32_v1.zipを解凍し、その中のARM202U を
C:\ARM202U
に置く。 C:\ARMProjectというディレクトリを作る。
(2) C:\ARM202U\BIN\APM.EXEをダブルクリックして立ち上げる。
Project -> New...
で、たとえば
C:\ARMProject\test1.apj
を指定すると、TEST1.APJというプロジェクトがC:\ARMProject\ にできる。
(「マイドキュメント」には入れないこと)
Options -> Directories...
で
C:\ARM200
となっているところ(3箇所ある)を
C:\ARM202U
に変更する(たとえば、 C:\ARM200\BIN\ を C:\ARM202U\BIN\ に変更)。
インストールした
場所の指定
ARM Software Development Toolkit
を使う(プログラム作成)
APM(ARM Project Manager)で
File -> new
でUntitled1という名前のファイルが出来てその画面(最初は空)がでるから、
そこにアセンブリ言語のプログラムを書いていく。
X
Y
Z
AREA
ENTRY
LDR
LDR
ADD
STR
DCD
DCD
DCD
END
test,CODE
R0, X
R1, Y
R0, R0, R1
R0, Z
15
21
0
; “タブAREAタブtest,CODE”と叩く
; “タブENTRY”と叩く。タブはスペースでも可
; “タブLDRタブR0, X”と叩く。 R0 = X;
; R1 = Y; LDRはLoad Register
; R0 = R0 + R1;
; STRはStore Register, Z = R0
; X番地に定数(DCD: Define Constant)15
; Y番地に定数21
; Z番地に定数0
X,Y,Zはラベルと呼ばれ
; end of program
行の先頭に書くことで
その値が定義される。
これを test1.s という名前でARMProjectに保存する。
ARM Software Development Toolkit
を使う(プロジェクト作成)
APM(ARM Project Manager)で
Project -> edit...
でtest1.sをプロジェクトTEST1.APJに付け加える。
(プロジェクトについて何か行うときは、そのプロジェクトのウィンドウを選んでおく。
質問のウィンドウが出たら、大体は「はい」と答えればよい。)
次に
Project -> Build TEST1.APJ
を行う。ビルドでは、そのプロジェクトのアセンブラ言語のプログラムから実行形式の
プログラムを作る。
Building...
Linking (C:\ARMP`ROJ\TEST1) ...
Build Complete.
と表示されたら成功である。
プロジェクト作成に成功した時
ARM Software Development Toolkit
を使う(実行1)
APM(ARM Project Manager)で
Project -> Debug TEST1.APJ
を選ぶ。そうすると、ARM Debuggerの画面が作られる。
Execution Window (8008番地に色が付いている。すべて16進表示)
Console Window
などがある。次に
View -> Registers -> User Mode
を選んで、User Registersを表示する。その下から2番目のPCはレジスタ15番であ
り、プログラムカウンタである。次に実行される命令は8008番地の命令である。
Execute -> Step
を選ぶ、あるいは、Stepボタンをクリックすると、 800c番地で止まる。そこには
bl test
という命令がある(testはプログラムの中に書いておいた名前)。
今度は、
Execute -> Step In
を選ぶ、あるいは、Step Intoボタンをクリックする。すると、testに飛ぶ(ブランチす
る)。Execution Windowの内容はtest1.sに変わる。 User RegistersのPCを見ると、
その値は8080であるから、このプログラムの先頭番地は8080である
Project -> Debug TEST1.APJ
を選んだ結果
View -> Registers -> User Mode
Execute -> Step
Execute -> Step In を選んだ結果
ARM Software Development Toolkit
を使う(実行2)
View -> Memory...
を選んで、8080番地を指定すると、その番地以降の内容がMemory Windowに表
示される。それを見ると、
LDR R0, X は機械語では 0xe59f0008
8090番地に10進の15(16進ではf)
8094番地に10進の21(16進では15)。
ここで、
Execute -> Step In
を選ぶ(Execute -> Stepを選んでも同じ)と8080番地の命令が実行される。
User Registersで見るとX番地(8090番地)の値がr0レジスタにロードされている。
さらにステップ実行を続けていくと、
r1に0x15がロードされ、
それとfとを加えた0x24がr0に入り、
それがZ番地(8098番地)にストア(格納)される
。
X、Y、Zが実際に何番地になっているかは、
View -> Low Level Symbols
のウィンドウで調べることもできる。
View -> Memory... で8080を指定
Execute -> Step In
で8080番地の命令を実行
STR命令まで実行
View -> Low Level Symbols
で名前と番地の対応がわかる
ARM Software Development Toolkit
を使う(実行3)
そこでさらに、ステップ実行をしたらどうなるか。
計算機は次の番地にある
0x0000000f
を命令であると思って実行しようとする。
それがどんな命令であるかを調べるためには
View -> Disassembly... ( Disassemblyはアセンブラの逆のことをする)
を選んで、8080または8090番地を指定すればよい。
それで見ると、
0x0000000f
は命令語としては
andeq r0, r0, pc
であることが分かる。それを実行するとr0の値が0になる。
コンピュータは「プログラム内蔵方式」
プログラム内蔵方式ではプログラムはメモリに内蔵されている。メモリに内蔵されている
ものはデータとして扱うことも出来るし、命令語として扱うことも出来る。コンピュータは
PCの指すところにあるものを命令語と見なし、その命令語の実行によって取り出された
り格納されたりするものをデータと見なすだけである。
View -> Disassembly...
例題2
AREA
ENTRY
LDR
LDR
LDR
LDR
test2, CODE
add
str
ADD
CMP
BNE
SWI
DCD
DCD
DCD
DCD
AREA
%
END
R0, R0, R1
R0, [R3]
; Out[0] ], Out[1],…,にストア。
R3, R3, #4
; R3 = Out[i]番地。i=1,2,3,...
R0, R2
; compare R0 and R2
L1
; if (R0 != R2) go to L1;
0x11
; Software Interrupt: 0x11 はHalt
0
; 定数0
1
; 定数1
10
; 定数10
Out
; A_Out番地には Outの値(番地)
block, DATA, READWRITE
40
; "%": 値0のバイト、それを40個
; Out[0], Out[1],…, Out[9],
L1
M0
M1
M10
A_Out
Out
R0 = 0;
R1 = 1;
R2 = 10;
i = 0;
do {
R0 = R0 + R1;
Out[i] = R0;
i = i + 1;
} while (R0 != R2)
R0, M0
R1, M1
R2, M10
R3, A_Out
; Out[0]の番地
str命令を最初に実行した直後
CMP命令を10回目に実行した直後
大文字と小文字の使い分け
AREA
ENTRY
LDR
LDR
LDR
LDR
test2, CODE
R0, M0
R1, M1
R2, M10
R3, A_Out
L1
M0
M1
M10
A_Out
Out
add
str
ADD
CMP
BNE
SWI
DCD
DCD
DCD
DCD
AREA
%
END
R0, R0, R1
R0, [R3]
R3, R3, #4
R0, R2
L1
0x11
0
1
10
Out
block, DATA, READWRITE
40
AREA,CODE,ENTRY,END
などの疑似命令は大文字
ADD, STR などの命令は
add, strのように小文字でもよい。
R0,R1などのレジスタ名は
r0, r1のように小文字でもよい。
L1,M0,A_Outなどのラベルは
大文字と小文字が混在してもよい。
ただし、定義と使用は合っていなけ
ればならない
例題3
1から9までの数をコンソールに出力する
プログラムは次のようにすればよい。
小さな定数は命令の中に
入れておくことができる。
WriteC EQU 0x0
AREA test3, CODE
ENTRY
MOV R1, #0
L1
ADD R1, R1, #1
ADD R0, R1, #0x30
SWI
WriteC
CMP R1,#10
BNE
L1
SWI
0x11
END
R1 = 0;
do {
R1 = R1 + 1;
R0 = R1 + 0x30;
WriteC(R0);
} while (R1 != 10)
; WriteCは定数0(0x0)を表す
; R1 = 0; 命令の中の定数nは'#n'の形
; R1 = R1 + 1;
; 数値1=00000001から文字コード00110001へ
; WriteC(R0);
; compute R1 - 10 and set Condition Code
; if (Z==0 すなわちR1 != 10) go to L1;
JISの文字コード
上位4ビット
下位4 ビット
ASCIIの場合は「\」が「\」
SWI 0を最初に実行した直後
ARMアーキテクチャ
ARM: Advanced RISC Machine
RISC: Reduced Instruction Set Computer
(CISC:Complex Instruction Set Computer)
RISCの特徴
・ロード/ストア アーキテクチャ
メモリにアクセスする命令はロード/ストア だけで
ADD R0, M1
のような命令はない
・命令は固定長
命令語の長さは一定。 ARMでは固定長の32ビット命令
データ型
・バイト:8ビット
符号付き(signed): -128 ~ 127
符号なし(unsigned) : 0 ~ 255
・ハーフワード:16ビット
符号付き: -32768 ~ 32767
符号なし: 0 ~ 65535
2バイト境界でアライン(偶数番地から)
・ワード:32ビット
符号付き: -2147483648 ~ 2147483647
符号なし: 0 ~ 4294967295
4バイト境界でアライン(4の倍数番地から)
そのほかに、浮動小数点型(実数型)のデータがあるが、
ここではとりあげない。
レジスタ
32ビットのレジスタが16個ある(ユーザモードで使える)。
r0~r15
r15: pc (program counter)
r14: lr(link register)
r13: sp(stack pointer)
CPSR: Current Program Status Register)
条件コードフラッグを変更する命令を実行した結果により
N: 負(Negative)。 負のとき(最上位ビットが'1'のとき)'1'、
その他の時'0'にリセット
Z: ゼロ(Zero)。ゼロのとき'1'をセット、その他の時'0'にリセット
C: 桁上げ(Carry)。加減算で、最上位ビットからの桁上げが
あったときにセット、またはシフトによるキャリアウトをセット
V: あふれ(oVerflow)。加減算で、符号付きオーバフロー
が生じたときにセット、その他の時リセット
メモリシステム
メモリは0から231-1までのバイトの線形配列と考えられる。
ワード、ハーフワードとバイトとの位置関係は little endian
bi t 31
bi t 0
bi t 31
bi t 0
23
22
21
20
20
21
22
23
19
18
17
16
16
17
18
19
word16
15
14
13
word16
12
12
half-word14 half-word12
11
10
9
6
8
2
15
8
9
10
11
word8
5
4
byte6 half-word4
3
14
half-word12 half-word14
word8
7
13
1
byte
address
0
byte3 byte2 byte1 byte0
(a) Little-endian memory
organization
4
5
6
7
byte5 half-word6
0
1
2
byte
address
3
byte0 byte1 byte2 byte3
(b) Big-endian memory
organization
ARMアセンブリ言語
以下はARM社のアセンブリ言語で、gccのARM用のものとは違う。
アセンブラの疑似命令あるいはディレクティブ
AREA:
ENTRY:
END:
ORG:
IMPORT:
EXPORT:
ALIGN:
データやコードのひとまとまりの始まり
実行される最初の命令
プログラムの終わりを示す
以下のプログラムの先頭番地を指定する
他のファイルで定義されている名前を使う
名前を他のファイルで使えるようにする
次の番地をワード境界にあわせる
命令語
命令語の形
[ラベル] WS 命令語 [WS][ ; コメント]
[ ]で囲まれたものはなくてもよい
WS(white space)ではスペースまたはタブ
ラベルは行の先頭から始まり、命令語の前にはWSが必要である
例:
L1
ADD R1,R1,#1
; R1 = R1 + 1;
ADD R0,R1,#0x30 ; 数値1から文字コードへ
定数名
定数名の定義
ラベル WS EQU 式
EQUはequate(同等視する)を意味する疑似命令
この形で、「式」の値を「ラベル」の値とする。
例:
Abc
EQU 100
以後100と書く代わりにAbcと書くことが出来る
データ
データは以下の形で書く
[ラベル] WS (DCB|DCW|DCD|%) WS データ
DCB(バイト)、DCW(ハーフワード)、DCD(ワード)、%
いずれも疑似命令
DCはDefine Constantの意味
%の後にはゼロバイト(1バイトでその値が0)の数を書く
例:
DCB4 DCB 'a', 0xff, 5, 0
DCW6 DCW 'a', 0xff, 5, 0, 12345, 0xffff
DCD5 DCD 'a', 0xff, 12345, 0xffff, 0xffffff
DCB "C_string",0
最後は
DCB 'C', '_', 's', 't', 'r', 'i', 'n', 'g', 0
と同じ
JISの文字コード
上位4ビット
下位4 ビット
ASCIIの場合は「\」が「\」
データ処理命令(1)
算術演算:
ADD、SUB、ADC(キャリ付き加算)、SBC(キャリ付き減算)、
RSB(逆減算)、RSC(キャリ付き逆減算)
add r0, r1, r2
; r0 = r1 + r2
sub r0, r1, r2
; r0 = r1 - r2
rsb r0, r1, r2
; r0 = r2 - r1
論理演算:
AND 両方1なら1。マスク演算。共通集合
01010101 AND 00001111 = 00000101 (下4ビットを取り出す)
ORR どちらかが1なら1。和集合
01010101 ORR 00001111 = 01011111
EOR(Exclusive OR): 両者が異なれば1。ビットごとの加算。
01010101 EOR 00001111 = 01011010
BIC 第2オペランドが1なら0。 ANDの逆のマスク演算
BIC r0,r1,r2
; r0 = r1 and not r2
01010101 BIC 00001111 = 01010000 (下4ビットを0)
データ処理命令(2)
レジスタ転送
MOV(転送)、MVN(否定して転送)。
MOV r0, r2
; r0 = r2
MVN r0, r2
; r0 = not r2
比較演算(結果でcc(条件コード)をセット)
CMP(比較)、CMN(符号反転して比較)、TST、TEQ
CMP r1, r2
; compare: set cc on r1 - r2
CMN r1, r2
; compare negative: set cc on r1 + r2。
TST r1, r2
; bit test: set cc on r1 and r2
TEQ r1, r2
; bit test equal: set cc on r1 xor r2
即値オペランド(immediate operand)
演算対象の2番目のレジスタの代わりに定数を使う。
8ビットで表現できるデータに限る。たとえば#0xff*4、#0xff*64
ADD r3, r3, #1 ; r3 = r3 + 1
MOV r4, #9
; r4 = 9
AND r8,r7,#0xff ; r8 = r7の下位8ビット
データ処理命令の形式
31
28 27 26 25 24
cond
00
#
opcode
21 20 19
S
16 15
Rn
12 11
0
operand 2
Rd
destination register
first operand register
set condition codes
arithmetic/logic function
25
11
8
7
#rot
1
0
8-bit immediate
immediate alignment
11
7
0
5
Sh
#shift
25
6
4
3
0
Rm
0
immediate shift length
shift type
second operand register
11
8
Rs
register shift length
7
0
6
Sh
5
4
1
3
0
Rm
シフトしたオペランド
データ処理命令(シフト)
LSL: Logical Shift Left
論理左シフト。
空いたビットには0
LSR: Logical Shift Right
論理右シフト。
空いたビットには0
ASR: Arithmetic Shift Right
算術右シフト。空いたビットには
符号ビットと同じもの
ROR: Rotate Right
右回転。右端から左端
RRX: Rotate Right Extended
(Cビットを先頭につけ
1ビット右回転)
データ処理命令(シフト例)
シフト操作を指定するのは、命令語の形式のoperand2
ADD r3, r2, r1, LSL #3 ; r3 = r2 + r1*23
ADD r5, r5, r3, LSL r2 ; r5 = r5 + r3*2r2
logical shift(論理シフト)の例:
1110 0000 1001 0000 0011 0000 0101 0010
を「LSR #4」で右に4ビットシフトすると
0000 1110 0000 1001 0000 0011 0000 0101
さらに「LSL #4」で左に4ビットシフトすると
1110 0000 1001 0000 0011 0000 0101 0000
arithmetic shift(算術シフト)の例:
右にmビットシフトしたら、ほぼ2mで割った数に。符号は不変
1110 0000 1001 0000 0011 0000 0000 0010
を右に4ビット算術シフトすると
1111 1110 0000 1001 0000 0011 0000 0000
乗算、命令の実行時間(サイクル)
乗算
MUL r4,r3,r2; r4 = r3 * r2の下位32ビット
即値オペランドは使えず、結果レジスタが第2ソースレジスタと同じにはできない
乗算には時間がかかるから別の命令で
MOV r0, r1, LSL #16
; r0 = r1 * 65536
ADD r0, r1, r1, LSL #2
; r0 = r1 * 5
RSB r0, r1, r1, LSL #3
; r0 = r1 * 7
View -> Debugger Internals
のウィンドウの中のclockの項を見ればよい。
Options -> Debugger Configuration -> ARMulator
でclock speedを1MHzとすると、 クロック値=サイクル数
各命令の実行時間(サイクル数)
ALU 1
演算命令:ADD, SUB, MOV;
B, BL 3
分岐命令
LDR 3
ロード命令: +2 if Rd is pc.
STR 2
ストア命令
MUL 1+M
乗算命令: M = 1~4(オペランドによる)
データ転送命令
LDR/STRの命令形式
31
28 27 26 25 24 23 22 21 20 19
cond
01 # P U BW L
16 15
Rn
12 11
0
offset
Rd
sou rce/destination regis ter
bas e reg iste r
loa d/store
write-b ack (au to-in dex)
uns igne d byte /word
up/down
pre-/post-inde x
25
11
0
12-bit immediate
0
25
11
7 6 5 4 3
#shift
1
imm edia te s hift len gth
shi ft typ e
offse t reg iste r
Sh 0
0
Rm
データ転送命令のアドレッシング(1)
レジスタ間接アドレッシング(Rnでレジスタ指定、offset=0)
LDR r0, [r1] ; r0 = mem[r1] = r1の値をnとして、n番地のワード
STR r0, [r1] ; mem[r1] = r0
PC相対アドレッシング(Rn=pc、offset=その命令からの距離(12ビット以内))
LDR R2, M10
LDR R3, A_Out
; M10はアセンブラがPC相対にする
; A_OutはPC相対。Out番地は近くないかも
...
M10
DCD 10
A_Out DCD Out
AREA block, DATA
Out
% 40
; address of Out(32ビット)
このように書くのはわずらわしいから
LDR R3, =Out
と書けば、アセンブラがA_Outに相当するものを作り出してくれる
データ転送命令のアドレッシング(2)
ベース+オフセット(Rnでベース指定、offsetでベースからの距離)
pre-indexedアドレッシング
LDR r0, [r1,#4] ; r0 = mem [r1 + 4]
自動アドレッシング
LDR r0, [r1,#4]! ; r0 = mem[r1 + 4]
; r1 = r1 + 4
post-indexedアドレッシング
LDR r0, [r1], #4 ; r0 = mem[r1]
; r1 = r1 + 4
loop
LDR
LDR
LDR
STR
CMP
BNE
r1, =TABLE1
r2, =TABLE2
r0, [r1], #4
r0, [r2], #4
r0, #0
loop
i = 0; j = 0;
do {
r0 = TABLE2[j++];
TABLE1[i++] = r0;
} while (r0 != 0)
データ転送命令のアドレッシング(3)
ベース+レジスタオフセット(offsetにもレジスタ指定、シフトも)
LDR r1, =TABLE1
LDR r2, =TABLE2
MOV r3, #0
loop LDR r0, [r1, r3, LSL #2]
STR r0, [r2, r3, LSL #2]
ADD r3, r3, #1
CMP r0, #0
BNE loop
i = 0;
do {
r0 = TABLE2[i];
TABLE1[i] = r0;
i++;
} while (r0 != 0)
バイト・データの転送
データ転送命令の命令コードに"B"をつければ、
バイト・データが転送される
LDRB r0, [r1]
; r0 = mem8[r1]
r1の示す番地の1バイトのデータをr0レジスタの下位8ビット
に入れ、その他のビットはすべて0になる。
AREA Copy, CODE
ENTRY
LDR r1, =srcstr
LDR r0, =dststr
strcopy
LDRB r2, [r1], #1
STRB r2, [r0], #1
CMP r2, #0
BNE strcopy
SWI 0x11
文字列データを
1バイトずつコピーする
AREA Strings, DATA
srcstr DCB "First string",0
dststr DCB "Second string",0
END
分岐命令
分岐命令の形式
31
28 27
cond
25 24 23
101
0
24-bit signed word offs et
L
(24-bit signed word offset)*4 + この命令のアドレス+8
が飛び先(分岐先)のアドレス(PC相対アドレッシング)
4倍する:飛び先の命令のアドレスは4の倍数に決まっているから
8を足す:この命令の実行時にはPCはそれだけ進んでいるから
1
2
3
instruc tion
fetc h
dec ode
execute
fetc h
dec ode
execute
fetc h
dec ode
execute
time
条件分岐命令
Branch
B
BAL
BEQ
BNE
BPL
BMI
BCC
BLO
BCS
BHS
BVC
BVS
BGT
BGE
BLT
BLE
BHI
BLS
Interpretation
Unconditional
Always
Equal
Not equal
Plus
Minus
Carry clear
Lower
Carry set
Higher or same
Overflow clear
Overflow set
Greater than
Greater or equal
Less than
Less or equal
Higher
Lower or same
Normal uses
Always take this branch
Always take this branch
Comparison equal or zero result
Comparison not equal or non-zero result
Result positive or zero
Result minus or negative
Arithmetic operation did not give carry-out
Unsigned comparison gave lower
Arithmetic operation gave carry-out
Unsigned comparison gave higher or same
Signed integer operation; no overflow occurred
Signed integer operation; overflow occurred
Signed integer comparison gave greater than
Signed integer comparison gave greater or equal
Signed integer comparison gave less than
Signed integer comparison gave less than or equal
Unsigned comparison gave higher
Unsigned comparison gave lower or same
条件実行
分岐命令はハードウェアのパイプラインを乱す
次の命令のデコードや次の次の命令のフェッチの時間が無駄
この無駄を解消するための工夫
遅延分岐:分岐命令の次の命令を実行してから分岐
分岐予測:予測したほうの命令をフェッチ
条件実行:条件が成り立つときだけ実行。分岐しないですむ
CMP
BEQ
ADD
SUB
BYPASS …
r0, #5
BYPASS
r1, r1, r0
r1, r1, r2
=
if ((r0==r1) && (r2==r3)) r4++;
CMP
r0, #5
ADDNE r1, r1, r0
SUBNE r1, r1, r2
BYPASS …
=
CMP
r0, r1
CMPEQ r2, r3
ADDEQ r4, r4, #1
条件実行のコード
Opcode
[31:28]
0000
0001
0010
0011
0100
0101
0110
0111
1000
1001
1010
1011
1100
1101
1110
1111
Mnemonic
extension
EQ
NE
CS/HS
CC/LO
MI
PL
VS
VC
HI
LS
GE
LT
GT
LE
AL
NV
Interpretation
Equal / equals zero
Not equal
Carry set / unsigned higher or same
Carry clear / unsigned lower
Minus / negative
Plus / positive or zero
Overflow
No overflow
Unsigned higher
Unsigned lower or same
Signed greater than or equal
Signed less than
Signed greater than
Signed less than or equal
Always
Never (do not use!)
Status flag state for
execution
Z set
set = 1
Z clear
clear = 0
C set
C clear
N set
N clear
V set
V clear
C set and Z clear
C clear or Z set
N equals V
N is not equal to V
Z clear and N equals V
Z set or N is not equal to V
any
none
SWI(Software Interrupt)命令
SWI_WriteC (SWI 0) r0の1バイトを出力
SWI_Write0 (SWI 2) r0が指す文字列(0が終わりを示す)を出力
SWI_ReadC (SWI 4) キーボードから1バイト読んでr0に入れる
SWI_Exit (SWI 0x11) ユーザプログラムを終了しデバッガに戻る
SWI_Clock (SWI 0x61) 実行時間を1/100秒単位でr0に返す
SWI_Open (SWI 0x66) r0が指すファイル名のファイルをオープン
r1がモードを示す整数:0 (read), 4 (write), 8 (apend)
そのファイルのハンドルがr0に返される。
オープンに失敗すると0がr0に返される
SWI_Close (SWI 0x68) ファイルをクローズする。
ファイルのハンドルはr0に与えておく。成功すれば0がr0に返る
SWI_Write (SWI 0x69) バッファからファイルに書き出す
r0=ファイルのハンドル、r1=バッファの先頭番地、r2=書くバイト数
SWI_Read (SWI 0x6a) ファイルからバッファに読み込む
r0=ファイルのハンドル、r1=バッファの先頭番地、r2=読むバイト数
入出力プログラム(1)
AREA hello1,CODE
SWI_WriteC
EQU 0x0
SWI_Exit
EQU 0x11
ENTRY
START LDR
r1, =TEXT
LOOP LDRB r0, [r1], #1
CMP r0, #0
SWINE SWI_WriteC
BNE
LOOP
SWI
SWI_Exit
TEXT DCB "Hello World"
DCB
0x0d, 0x0a, 0
END
r0の1バイトが0と等しくなければそれを出力
r0の1バイトが0と等しくなければループ
"Hello World"と改行を出力
0x0aがLine Feed(改行)、
0x0dがCarriage Return(復帰)
(unixのファイルでは0x0aだけ、
マックのファイルでは0x0dだけが使われる)
入出力プログラム(2)
write_only EQU 4
AREA hello2,CODE
ENTRY
start
LDR
r0, =filename
MOV r1, #write_only
SWI
SWI_Open
MOV r5, r0
LDR
r1, =String
MOV r2, #14
SWI
SWI_Write
MOV r0, r5
SWI
SWI_Close
SWI
SWI_Exit
filename
DCB "hello.txt",0
String DCB "Hello World!"
DCB
0x0d, 0x0a, 0
END
r0にファイル名hello.txtのアドレス
r1にファイルの属性「書き込みのみ」
ファイルをオープン、ハンドルをr0へ
ファイルhello.txtのハンドルをr5へ
出力する文字列のアドレスをr1へ
出力する文字列の長さをr2へ
出力する(r0のハンドルのファイルへ)
r0は壊れるから入れなおす
最後にファイルをクローズ
Game Boy Advance(GBA)-1
CPUはARM7
アドレス
外部RAM
内部RAM
I/Oレジスタ
VRAM
容量 アクセス(サイクル)
8/16/32ビット
0200 0000 - 0203 ffff 256KB 3/3/6
0300 0000 - 0300 7fff
32KB 1/1/1
0400 0000 - 0400 03ff
1KB 1/1/1
0600 0000 - 0601 7fff
96KB 1/1/2
・外部RAMと内部RAMにプログラムとデータ
・I/Oレジスタに書き込むことによって入出力が行われる
・VRAMの内容が画面に表示される。画面は縦160ドット×横240ドット
画面上の1つのドットは2バイトで表現され、RGB形式の色情報は各5ビットで
0BBBBBGGGGGRRRRR 0111110000000000 = 7C00 = 1F/0/0 ブルー
0000001111100000 = 03E0 = 0/1F/0 グリーン
0000000000011111 = 03E0 = 0/0/1F レッド
0111111111111111 = 7FFF = 1F/1F/1F 白
0000000000000000 = 0000 = 0/0/0
黒
Game Boy Advance(GBA)-2
画面の左上に、黒点、白点、黒点、白点、黒点、白点、黒点、白点、
を表示するプログラム
mov r1, #0x04000000
ldr r2, =0xF03
str r2, [ r1 ]
mov r3, #0x06000000
ldr r4, =0x7FFF
str r4, [ r3 ]
str r4, [ r3, #4 ]
str r4, [ r3, #8 ]
str r4, [ r3, #12 ]
loop b loop
; I/O base address
; video mode 3
; display control
; VRAM address
; 0x00007FFF 黒点と白点
; 最初の黒点と白点
; 2番目の黒点と白点
; 3番目の黒点と白点
; 4番目の黒点と白点
; 無限ループ
ウィンドウズ上のGBAのエミュレータVisualBoyAdvanceで試す、
GBAの画面を赤、緑、青の三色で塗りつぶす、
のは「ARMの説明.doc」を見よ
サブルーチン
プログラムの1単位はルーチン(routine)と呼ばれる
サブルーチン(subroutine)
他のルーチンから呼び出される。Javaのメソッドに相当する。
メイン・ルーチンまたは主ルーチン
最初に実行されるルーチン(Javaのmainメソッドに相当)。
サブルーチンを実現するためには、どこからそれが呼び出されても、
呼び出した元の場所へ戻る仕掛けが必要。
サブルーチンに飛ぶ(ブランチする)と同時に戻り番地を渡せばよい。
その命令がBL(Branch with Link)命令である。
BL m
を実行すると、そのときのPCの値(次の命令の番地)を
レジスタr14に入れてm番地へ飛ぶ。サブルーチンの最後で
MOV PC, r14
; MOV r15, lr
を実行すると「BL m」命令の次の命令に戻る。
"r14"の代わりに"lr"または"LR"(Link Register)と書いてもよい。
サブルーチンの例
start
MOV
MOV
BL
MOV
MOV
BL
SWI
r0, #15
r1, #20
addsubr
r0, #30
r1, #54
addsubr
0x11
addsubr
ADD r0, r0, r1
MOV pc, lr
; Set up parameters
; Call subroutine
; Set up parameters
; Call subroutine
; terminate
; Subroutine addsubr
; r0 = r0 + r1
; Return from subroutine
; with result in r0
サブルーチンgcd(最大公約数)
start
MOV r0, #10
MOV r1, #4
BL
gcd
; r0 = gcd(10, 4)
STR r0, result
SWI 0x11
result DCD 0
int gcd(int r0, int r1){
gcd
do {
CMP r0, r1
if (r0 > r1) r0 -= r1;
SUBGT r0, r0, r1
if (r0 < r1) r1 -= r0;
SUBLT r1, r1, r0
} while(r0 != r1)
BNE
gcd
return r0;
MOV pc,lr
}
サブルーチン:文字列コピー
main LDR
LDR
BL
SWI
r0, =srcstr
r1, =dststr
strcopy
0x11
; pointer to first string
; pointer to second string
; copy the first into second
; and exit
srcstr DCB "This is my first (source) string",0
dststr DCB "This is my second (destination) string",0
ALIGN
strcopy
LDRB r2, [r0], #1
STRB r2, [r1], #1
CMP r2, #0
BNE strcopy
MOV pc, lr
; realign address to word boundary
; load byte, then update address
; store byte, then update address
; check for zero terminator
; keep going if not
; return
サブルーチン呼び出しのネストの問題
start
RA0
MOV
MOV
BL
SWI
r0, #15
r1, #20
firstfunc
0x11
; Set up parameters
; Call firstfunc. lr = RA0
; terminate
firstfunc
ADD r0, r0, r1
BL
secondfunc
RA1 MOV pc, lr
; Subroutine firstfunc
; r0 = r0 + r1
; Call secondfunc. lr = RA1
; Return to RA1 !! (not RA0)
secondfunc
ADD r0, r0, #45
MOV pc, lr
; Subroutine secondfunc
; r0 = r0 + 45
; Return to RA1
firstfucから戻れない
戻り番地をスタックに積む
main(){
f(){
g(){
(1)
f();
(2)
g();
f1:
m1:
(6)
}
スタック
h(){
(3)
h();
g1:
(5)
}
(4)
}
}
g1
f1
f1
m1
m1
m1
(1)で積む
(2) で積む
(3) で積む
(6)で降ろす (5) で降ろす (4) で降ろす
戻るときはスタックのトップを降ろし、そこへ飛べばよい。
サブルーチン呼び出しのネストの解決
start
RA0
MOV
MOV
BL
SWI
firstfunc
STR
ADD
BL
RA1 LDR
r0, #15
r1, #20
firstfunc
0x11
; Set up parameters
; Call firstfunc. lr = RA0
; terminate
; Subroutine firstfunc
lr, [sp], #4
;スタックに戻り番地(RA0)を積む
r0, r0, r1
; r0 = r0 + r1
secondfunc ; Call secondfunc. lr = RA1
pc, [sp, #-4]! ;スタックから RA0を降ろしてそこへ戻る
secondfunc
STR lr, [sp], #4
ADD r0, r0, #45
LDR pc, [sp, #-4]!
; Subroutine secondfunc
;スタックのRA0の上にRA1を積む
; r0 = r0 + 45
; スタックから RA1を降ろしてそこへ戻る
再帰的サブルーチンfactorial
static int fact(int n){
if (n == 1) return 1;
return n * fact(n-1);
}
factからfactを呼ぶときは引数
が変わっている。戻ったときに
以前の引数が必要。
引数もスタックに積めばよい。
STR lr, [sp], #4
; 戻り番地をスタックへ積む
STR R0, [sp], #4 ; R0にある引数nをスタックへ積む
SUBS R0, R0, #1 ; 引数n - 1をR0へ
BEQ fact1
; if (n == 1) goto fact1
BL
fact
; else fact(n - 1)
factn LDR R1, [sp, #-4]! ; 引数nを降ろしてR1へ
MUL R0, R1, R0 ; R0 = n * fact(n - 1)
LDR pc, [sp, #-4]! ; 戻り番地を降ろして戻る
fact1 LDR R0, [sp, #-4]! ; 引数1=fact(1)を降ろしてR0へ
LDR pc, [sp, #-4]! ; 戻り番地を降ろして戻る
fact
fact
STR lr, [sp], #4
STR R0, [sp], #4
SUBS R0, R0, #1
BEQ fact1
r0
3
BL
fact
factn LDR R1, [sp, #-4]!
MUL R0, R1, R0
r13(sp)
LDR pc, [sp, #-4]!
r14(lr) main1
fact1 LDR R0, [sp, #-4]!
r15(pc) fact
LDR pc, [sp, #-4]!
main LDR sp, =stackBegin
MOV R0, #3
BL
fact
main1 STR R0, result
; result = fact(3)
factorialの呼び出し(1)
AREA stack,DATA,READWRITE
stackBegin
%
1000
スタック
mainのBL fact命令実行直後
fact
STR lr, [sp], #4
STR R0, [sp], #4
SUBS R0, R0, #1
BEQ fact1
r0
2
BL
fact
factn LDR R1, [sp, #-4]!
MUL R0, R1, R0
r13(sp)
LDR pc, [sp, #-4]!
r14(lr) factn
fact1 LDR R0, [sp, #-4]!
r15(pc) fact
LDR pc, [sp, #-4]!
main LDR sp, =stackBegin
MOV R0, #3
BL
fact
main1 STR R0, result
; result = fact(3)
factorialの呼び出し(2)
AREA stack,DATA,READWRITE
stackBegin
%
1000
3
main1
スタック
factのBL fact命令の最初の実行直後
fact
STR lr, [sp], #4
STR R0, [sp], #4
SUBS R0, R0, #1
BEQ fact1
r0
1
BL
fact
factn LDR R1, [sp, #-4]!
MUL R0, R1, R0
r13(sp)
LDR pc, [sp, #-4]!
r14(lr) factn
fact1 LDR R0, [sp, #-4]!
r15(pc) fact
LDR pc, [sp, #-4]!
main LDR sp, =stackBegin
MOV R0, #3
BL
fact
main1 STR R0, result
; result = fact(3)
factorialの呼び出し(3)
AREA stack,DATA,READWRITE
stackBegin
%
1000
2
factn
3
main1
スタック
factのBL fact命令の2回目の実行直後
fact
STR lr, [sp], #4
STR R0, [sp], #4
SUBS R0, R0, #1
BEQ fact1
r0
0
BL
fact
factn LDR R1, [sp, #-4]!
MUL R0, R1, R0
r13(sp)
LDR pc, [sp, #-4]!
r14(lr) factn
fact1 LDR R0, [sp, #-4]!
r15(pc) fact1
LDR pc, [sp, #-4]!
main LDR sp, =stackBegin
MOV R0, #3
BL
fact
main1 STR R0, result
; result = fact(3)
factorialの呼び出し(4)
AREA stack,DATA,READWRITE
stackBegin
%
1000
1
factn
2
factn
3
main1
スタック
factのBEQ fact1命令の実行直後
fact
STR lr, [sp], #4
STR R0, [sp], #4
SUBS R0, R0, #1
BEQ fact1
r0
1
BL
fact
factn LDR R1, [sp, #-4]!
MUL R0, R1, R0
r13(sp)
LDR pc, [sp, #-4]!
r14(lr) factn
fact1 LDR R0, [sp, #-4]!
r15(pc) factn
LDR pc, [sp, #-4]!
main LDR sp, =stackBegin
MOV R0, #3
BL
fact
main1 STR R0, result
; result = fact(3)
factorialの呼び出し(5)
AREA stack,DATA,READWRITE
stackBegin
%
1000
1
factn
2
factn
3
main1
スタック
fact
STR lr, [sp], #4
STR R0, [sp], #4
SUBS R0, R0, #1
BEQ fact1
r0
2
BL
fact
r1
2
factn LDR R1, [sp, #-4]!
MUL R0, R1, R0
r13(sp)
LDR pc, [sp, #-4]!
r14(lr) factn
fact1 LDR R0, [sp, #-4]!
r15(pc) factn
LDR pc, [sp, #-4]!
main LDR sp, =stackBegin
MOV R0, #3
BL
fact
main1 STR R0, result
; result = fact(3)
factorialの呼び出し(6)
AREA stack,DATA,READWRITE
stackBegin
%
1000
1
factn
2
factn
3
main1
スタック
factのLDR pc命令の実行直後
r0 = 1
fact
STR lr, [sp], #4
STR R0, [sp], #4
SUBS R0, R0, #1
BEQ fact1
r0
6
BL
fact
r1
3
factn LDR R1, [sp, #-4]!
MUL R0, R1, R0
r13(sp)
LDR pc, [sp, #-4]!
r14(lr) factn
fact1 LDR R0, [sp, #-4]!
r15(pc) main1
LDR pc, [sp, #-4]!
main LDR sp, =stackBegin
MOV R0, #3
BL
fact
main1 STR R0, result
; result = fact(3)
factorialの呼び出し(7)
AREA stack,DATA,READWRITE
stackBegin
%
1000
1
factn
2
factn
3
main1
スタック
factのLDR pc命令の最後の実行直後
r0 = 2
レジスタの使い方のルール
独立に開発したサブルーチンがお互いに呼び合ってもうまく機能する
ためには、サブルーチン間でのレジスタの使い方の約束が必要
一般に、calling conventionといわれる。
ARMの場合は、ARM Procedure Call Standard といわれ
r0: 第1引数、整数の結果、スクラッチ
r1: 第2引数、スクラッチ
r2: 第3引数、スクラッチ
r3: 第4引数、スクラッチ
r4~r8: グローバル(r9~r13は特別な役割)
スクラッチ: 使い捨て。サブルーチンは自由に使える。
caller save register: callerが、呼び出す前にsaveする
グローバル: サブルーチンは勝手には使えない。
callee save register:
呼ばれたcalleeがsaveする
サブルーチンの入口・出口で退避・回復
サブルーチンfactorial
ルールに従ってレジスタの退避・回復を行う
lr(戻り番地: callee save)、r4(callee save)を使うから
fact
STMIA sp!,{r4,lr}
MOV r4, r0
SUBS r0, r0, #1
BEQ fact1
BL
fact
factn MUL r0, r4, r0
LDMDB sp!, {r4, pc}
fact1 MOV r0, #1
LDMDB sp!, {r4, pc}
; 戻り番地とr4をスタックへ退避
; r4 = n
; 引数n - 1をr0へ
; if (n == 1) goto fact1
; else fact(n - 1)
; r0 = n * fact(n - 1)
; スタックから回復してreturn
; r0 = 1 ( = fact(1) )
; スタックから回復してreturn
複数ファイルで構成するプロジェクト
ファイルfactsub.s
ファイルfactmain.s
IMPORT fact
AREA factmain,CODE
ENTRY
main LDR sp, =stackBegin
MOV r0, #3
BL
fact
main1 STR r0, result
SWI 0x11
result DCD 0
AREA stack,DATA
stackBegin
%
1000
END
EXPORT fact
AREA factsub, CODE
fact STMIA
sp!,{r4,lr}
MOV r4, r0
SUBS r0, r0, #1
BEQ fact1
BL
fact
factn MUL r0, r4, r0
LDMDB
sp!, {r4, pc}
fact1 MOV r0, #1
LDMDB
sp!, {r4, pc}
END
別のファイルでの定義名を使うときは、
定義側でEXPORT、使う側でIMPORT
ARMの命令(1)
算術演算
ADD{c}{S} Rd, Rn, op2
ADC{c}{S} Rd, Rn, op2
SUB{c}{S} Rd, Rn, op2
SBC{c}{S} Rd, Rn, op2
RSB{c}{S} Rd, Rn, op2
RSC{c}{S} Rd, Rn, op2
MUL{c}{S} Rd, Rm, Rs
論理演算
AND{c}{S} Rd, Rn, op2
ORR{c}{S} Rd, Rn, op2
EOR{c}{S} Rd, Rn, op2
BIC{c}{S} Rd, Rn, op2
op2
Rd = Rn + op2
Rd = Rn + op2 + Carry
Rd = Rn – op2
Rd = Rn + op2 – Not(Carry)
Rd = op2 – Rn
Rd = op2 – Rn – Not(Carry)
Rd = Rm * Rs (Rd != Rm)
Rd = Rn AND op2
Rd = Rn OR op2
Rd = Rn ROR op2
Rd = Rn AND NOT op2
Rm
#32bit_imm(5bit_non_0)
Rm, LSL #5bit_imm
Rm, LSR #5bit_imm
Rm, ASR #5bit_imm
Rm, ROR #5bit_imm
Rm, LSL Rs
Rm, LSR Rs
Rm, ASR Rs
Rm, ROR Rs
Rm, RRX
命令コードにSをつけると、実行結果によって条件コード(フラグ:NZCV)をセット
命令コードにcをつけると、その条件コードがセットされているときだけ実行
c: EQ (Equal), NE (Not Equal), MI (Minus), PL (Plus, Positive or zero)
GE (Greater or Equal), LT (Less Than), GT (Greater Than), LE (Less than or Equal)
ARMの命令(2)
レジスタ転送
MOV{c}{S} Rd, op2 Rd = op2
MVN{c}{S} Rd, op2 Rd = 0xFFFFFFFF EOR op2
比較演算
CMP{c}{S} Rn, op2 cc = Rn – op2
CMN{c}{S} Rn, op2 cc = Rn + op2
TST{c}{S} Rn, op2 cc = Rn AND op2
TEQ{c}{S} Rn, op2 cc = Rn EOR op2
分岐命令
B{c} label
pc = label address
BL{c} label lr = pc, pc = label address
命令コードにSをつけると、実行結果によって条件コードをセットする
命令コードにcをつけると、その条件コードがセットされているときだけ実行
c: EQ (Equal), NE (Not Equal), MI (Minus), PL (Plus, Positive or zero)
GE (Greater or Equal), LT (Less Than), GT (Greater Than), LE (Less than or Equal)
ARMの命令(3)
データ転送
LDR{c} Rd, addr
LDR{c}B Rd, addr
LDM{c}DB Rd{!}, regs
STR{c} Rd, addr
STR{c}B Rd, addr
STM{c}IA Rd{!}, regs
ソフトウェア・インタラプト
SWI 24bit_value
Rd = [addr]
Rd0-7 = [byte from addr], Rd8-31=0
[Rd]からregsへロード。regsは{ }で囲む
[addr] = Rd
[addr] = Rd0-7
regsを[Rd]へストア。regsは{ }で囲む
ARMの命令(4) アドレッシング
addr
label
=value
[Rn]
labelのアドレス( PC相対)
valueがあるアドレス(PC相対)
Rnの値がアドレス(レジスタ間接)
[Rn, #+/-12bit_offset]
[Rn, +/-Rm]
[Rn, +/-Rm, LSL #5bit]
ベース[Rn] +オフセット(定数)がアドレス
[Rn] +オフセット(レジスタ)がアドレス
[Rn] +オフセット(レジスタをシフト)
[Rn, #+/-12bit_offset]!
[Rn, +/-Rm]!
[Rn, +/-Rm, LSL #5bit]!
ベース+オフセットと同じだが
実行後ベースオフセットの
値を加える
[Rn], #+/-12bit_offset
[Rn], +/-Rm
[Rn], +/-Rm, LSL #5bit
アドレスは[Rn]だが
実行後Rnに加える