-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path02-chapter2.re
515 lines (416 loc) · 28.3 KB
/
02-chapter2.re
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
= Plan 9 アセンブラのお作法
この章では, Plan 9のアセンブラのお作法について見ていきます.
基本的には gas や NASM と同じ立ち位置のツールなのでそれらと
役割やできることに大きな差があるわけではありません.
なのでアセンブラとは, といったお話はしません.
また細かい文法や命令セットに踏み込むと切りがないため,
ここでは Plan 9 アセンブラに特有と思われる部分や面白そうな部分に絞って
見ていきます.
それぞれのアーキテクチャで使えるレジスタ, 定義されているオペランド,
およびそれらの使い方はもちろんそれぞれに異なります.
Plan 9のアセンブラではこれらのアーキテクチャの差異を部分的に抽象化,
ある程度共通化しおよそ同じような記述で利用できるようにしています.
このために記法の共通化や, 疑似命令, 疑似レジスタなどを定義しています.
#@# これらのアセンブラのベースになっているのは 2a つまり Motorola MC68020 です.
#@# その他のアセンブラはもちろんこのアーキテクチャをサポートしていませんが,
#@# 2a でのお作法を輸入する形で一部レジスタや命令が擬似的に提供されています.
== 共通のお作法
命令やレジスタ, アドレッシングについては後述しますが以下に
列挙するような共通のお作法が存在します.
1. 予約済みのシンボルはすべて大文字
2. ラベルやユーザ定義のシンボルは基本的に小文字
3. 即値は @<strong>{$} (ドルマーク)を先頭に付ける
4. コメントは C 言語での記法を用いる
5. @<strong>{;} (セミコロン)は区切り文字として利用
6. データの転送方向は基本的に左から右
レジスタや各アーキテクチャでサポートされるニーモニックなどの予約語は全て
大文字で定義されています. たとえば疑似命令の MOV や レジスタ R0, AX など
は小文字での表記はできません.
一方でラベルやサブルーチンの名前, 外部参照や局所参照に用いられるシンボルは
小文字で命名します.
即値は先頭に @<strong>{$} (ドルマーク) を付与し '$100' といった形で用います.
この場合は 10 進数ですが '$0x56' のように 16 進数による記述も可能です.
また即値内では四則演算や AND, OR などによる計算を行うこともできます
(@<list>{pc64_expression}). まためったに使われませんが浮動小数点も
サポートしています.
//list[pc64_expression][演算子の利用例 (src/9/pc64/l.s)]{
/* 第1オペランド: 即値として即値同士の計算式を記述 */
ADDL $(16*1024), CX /* qemu puts multiboot data after the kernel */
/* 第1オペランド: もちろんマクロ同士も可 */
MOVQ $(CPU0MACH+MACHSIZE), SP
/* 第1オペランド: もちろんマクロ同士も可. OR や シフト演算も利用可 */
LONG $(SEGL|SEGG|SEGP|(0xF<<16)|SEGPL(0)|SEGEXEC|SEGR)
/* 第1オペランド: NOT の結果を即値として計算可 */
ANDL $~(BY2PG-1), DI
//}
この例にもありますが, コメントは gas のそれと同じくC言語の @<strong>{"/* ... */"}
または @<strong>{"// ..."} の両方を使うことができます.
一方で gas の "#" (ハッシュマーク), NASM の ";" (セミコロン) といった記法は
サポートしていません. 後者のセミコロンは Plan 9 アセンブラではC言語のそれと
同じく文あるいは式の区切り文字として用います.
gas は AT&T 記法, NASM は インテル記法をベースにしています. Plan 9 では
AT&T 記法つまり「ニーモニック 転送元 転送先」の並びを基本としています.
前章で述べたとおり, 様々なアーキテクチャ向けのアセンブラが存在しますが
一部の命令を除きこれを保つようアセンブラで命令の抽象化などが行われています.
== 命令
個々のアーキテクチャのニーモニックを取り上げると切りがないため, ここでは
Plan9 アセンブラでの疑似命令およびアセンブラにてサポートされない命令の実装方法
に絞って記述します.
=== 疑似命令
Plan 9 のアセンブラコード中に良く出てくる疑似命令として, ここでは MOV,
BYTE (およびその仲間), DATA, GLOBL の4種を取り上げます.
: MOV
MOV はレジスタ, アドレスを対象とした値の転送を行うための疑似命令です.
アーキテクチャにより即値, レジスタ, アドレスを転送元, 転送先とした場合の
オペレーションは異なります. Plan 9 アセンブラでは MOV 疑似命令により
これらを抽象化し @<strong>{MOVx src, dst} との記法で共通に行えるようにしています.
先にも述べたとおり AT&T 記法をベースとしているため「転送元 to 転送先」の順序
で記述します. MOVの末尾にはサフィクスとして転送するサイズを表す1文字を付記
します. 1バイトは @<strong>{B} (Byte), 2バイトは @<strong>{W} (Word),
4バイトは @<strong>{L} (Long), 8バイトは @<strong>{Q} (Quad) を用います.
なおレジスタのビット幅とMOV疑似命令で指定される幅が一致しない場合は,
より狭い方のサイズの転送と見なしてMOVの幅が自動的に変わります.
例として @<list>{pc_ls_mov} における MOVW の利用例では, 32bit 幅のレジスタ
であるAX に対して 2 バイト (WORD) の転送を行おうとしています.
この場合, AX の下2バイト (AL分) のみが転送されます. 転送先には下2バイトが
反映される一方で上2バイトは 0 クリアされます.
//list[pc_ls_mov][MOVの利用例 (src/9/pc/l.s)]{
# MOVW の利用例 : AX から DS に下2バイト転送
MOVW AX, DS
# MOVL の利用例 : DX から CR0 に 4バイト転送
MOVL DX, CR0 /* turn on paging */
//}
: BYTE, WORD, LONG, QUAD
これらの命令はそれぞれ1, 2, 4, 8 バイトのデータを命令列の中に配置する
疑似命令です. 単純にあるメモリの位置に値を格納したい場合は前述の MOV,
データセクション中であれば後述の DATA を使います. この命令はプログラム中の
この場所に値を配置することを目的としています. 主な用途としては各種テーブル,
たとえば割り込みベクタやGDT, マルチブートヘッダなどの構造を定義するために
用いられます(@<list>{pc_ls_multibootheader}).
また別の使い方として後述するように, ニーモニックが未実装の命令を呼び出すために
用いることがあります.
//list[pc_ls_multibootheader][LONGによるマルチブートヘッダの実装 (src/9/pc/l.s)]{
/*
* Must be 4-byte aligned.
*/
TEXT _multibootheader(SB), $0
LONG $0x1BADB002 /* magic */
LONG $0x00010003 /* flags */
LONG $-(0x1BADB002 + 0x00010003) /* checksum */
LONG $_multibootheader-KZERO(SB) /* header_addr */
LONG $_startKADDR-KZERO(SB) /* load_addr */
LONG $edata-KZERO(SB) /* load_end_addr */
LONG $end-KZERO(SB) /* bss_end_addr */
LONG $_multibootentry-KZERO(SB) /* entry_addr */
LONG $0 /* mode_type */
LONG $0 /* width */
LONG $0 /* height */
LONG $0 /* depth */
//}
: DATA
DATAはデータセクションに値を置く疑似命令です. 「
@<strong>{DATA 名前/バイト数, 値}」といった記法で利用することができます.
「@<strong>{DATA array+0(SB)/4, $"abc\z"}」 と書いた場合は, "main" という
文字列データの配置された領域をデータセクションに確保し, array というシンボルを
付与して参照, アクセスできるようにしています.
スラッシュ (/) の後ろに指定できるバイト数の上限は 8 までとなっています.
実際の利用例を@<list>{libc_386_main9}に引用します.
このコードは libc が 各プログラムを main 関数経由で起動する部分および
終了後の処理を記述しています.
プログラムが終了すると最終的に exits 関数を呼び終了処理を行いますが,
この時に引数としてシンボル $_exits により示される "main\z" の格納された領域
のポインタを渡しています.
//footnote[null_term][ @<strong>{"\z"} は NULL 終端 ("\0") を意味します]
: GLOBL
GLOBL は シンボルをグローバルにする疑似命令です. 「
@<strong>{GLOBL 名前, バイト数}」 という記法で利用されます. 基本的には前述の
DATA疑似命令の直後に配置され, シンボルが指す領域を外部から参照できるものとして
宣言します. この宣言には第2引数にてサイズを付与することができます. 通常は
シンボルにて参照できる領域がDATA疑似命令により事前に初期化されていることを
期待しますが, そうでない場合は指定されたサイズに従って 0 クリアします.
@<list>{libc_386_main9}の例では, DATA では "main" と 4 バイト分の初期値を
埋めています. ここにかぶせる形で GLOBL では 5 バイトとしています.
これにより, DATA 疑似命令側で "\z" を追加しなくても NULL 終端するように
しています.
//list[libc_386_main9][DATA および GLOBL の利用例 (src/libc/386/main9.s)]{
#define NPRIVATES 16
TEXT _main(SB), 1, $(8+NPRIVATES*4)
MOVL AX, _tos(SB)
LEAL 8(SP), AX
MOVL AX, _privates(SB)
MOVL $NPRIVATES, _nprivates(SB)
MOVL inargc-4(FP), AX
MOVL AX, 0(SP)
LEAL inargv+0(FP), AX
MOVL AX, 4(SP)
CALL main(SB)
loop:
MOVL $_exits<>(SB), AX /* "main" の書かれた領域のアドレスを AX に */
MOVL AX, 0(SP) /* アドレスをスタックに push */
CALL exits(SB) /* exits 関数の呼び出し */
JMP loop
DATA _exits<>+0(SB)/4, $"main" /* 局所シンボル _exits<> を定義, 初期値設定 */
GLOBL _exits<>+0(SB), $5 /* _exits<> の領域を外部からアクセス可能に */
//}
これらの他にTEXTなる疑似命令も存在しますが, これについてはサブルーチンの節にて
後述します.
=== サポートされていないニーモニックの利用
アセンブラでサポートされているアーキテクチャ依存の命令は,
その一覧がそれぞれのコンパイラのソースディレクトリ直下に置かれている
@<strong>{O.out.h} なるファイルに列挙されています.
i386 の場合は @<strong>{src/cmd/8c/8.out.h} になります.
実際のバイナリとしての命令との対応表は Optab 構造体として各ローダにて定義
されています. i386 の場合は @<strong>{src/cmd/8l/optab.c} をご参照ください.
この一覧は, アーキテクチャリファレンスに完全には追従はできておらず
ニーモニックとして定義されていない命令が存在します.
このような場合に Plan 9 では先に述べた BYTE 疑似命令を使ってオペコードの
バイナリを手で打って未サポートの命令を呼び出すといったテクニックを多用しています.
@<list>{pc64_ls_unimplemented}にこの例を記載します.
//list[pc64_ls_unimplemented][touser での 未実装命令 SYSRETQ の実装 (src/9/pc64/l.s) ]{
# touser での SYSRETQ の実装
TEXT touser(SB), 1, $-4
CLI
SWAPGS
MOVL $0, RMACH
MOVL $0, RUSER
MOVQ $(UTZERO+0x28), CX /* ip */
MOVL $0x200, R11 /* flags */
MOVQ RARG, SP /* sp */
BYTE $0x48; SYSRET /* SYSRETQ */
//}
この例では最終行の @<strong>{BYTE $0x48; SYSRET} という表記で
ニーモニックが定義されていない SYSRETQ を実装しています.
SYSRET は 6l でサポートされているニーモニックでオペコードとしては
@<strong>{0x0f 0x07} なるバイナリ列です. 一方 SYSRETQ は
@<strong>{0x48 0x0f 0x07} というバイト列としてアーキテクチャマニュアルに
記載されていますが 6a のニーモニックとしては定義されておらず変換表にも
載っていません. ここで BYTE 疑似命令を用います.
先に述べたとおり BYTE などの疑似命令は命令列の中に値を置くための命令です.
最終的な成果物のバイナリでは, CPU がそれと解釈できるバイナリになっていれば
未実装命令であれ実行させることができます. この例では SYSRETQ のバイナリ列だと
思わせるにに必要な @<strong>{0x48} を BYTE を用いて SYSRET 命令の前に挿入し
実質的に SYSRETQ を呼び出すようにしています.
== レジスタ(疑似レジスタ)
レジスタの構成および名前はアーキテクチャごとに固有のため, 詳細については
各アーキテクチャのマニュアルに説明を譲ります.
ここでは Plan 9 にて用意されている疑似レジスタについて触れます.
疑似レジスタは, どのアーキテクチャでも同じ名前で同じ役割をもち同じ使い方
ができるようにアセンブラおよびローダが提供する擬似的なレジスタです.
種類としては @<strong>{SP, FP, PC, SB} の4つが利用可能となっています.
アーキテクチャによっては, 一部は別名で存在していたりまた一部はそもそも存在
しなかったりと様々です. この部分をローダとアセンブラが隠蔽して擬似的に等価な
機能を提供するようになっています. たとえば x86 には FP がありませんが ebp が
存在しています.
それぞれの機能を以下に解説します.
: SP (Stack Pointer)
現在のスタックの先頭位置のポインタを格納するレジスタです.
i386 では ESP に相当し, PUSH/POP 操作時に増減します.
一時領域として, ポインタを引き渡して値を取得するタイプの命令向けに
テンポラリな値の格納場所として用いられる場合もあります.
@<list>{8a_libc_getfcr} の例では x87 FPU から制御レジスタとステータスレジスタ
の値を読み出してくるためにスタックの先頭を初期化して引き渡しています.
また サブルーチンを呼び出す際に引数を格納するためにも用いられます.
//list[8a_libc_getfcr][getfcr での SP の利用例 (src/libc/386/getfcr.c)]{
TEXT getfcr(SB), $4
MOVW AX, 0(SP)
WAIT
FSTCW 0(SP) /* 一時領域としてスタックの先頭を利用 */
MOVW 0(SP), AX
XORB $0x3f,AX
RET
//}
: FP (Frame Pointer)
現在のフレームの先頭位置のアドレスを格納します. 後述するアドレッシングの節
でも取り上げるように @<strong>{0(FP)}, @<strong>{4(FP)} といった記法を用いて,
現在のフレームにスタック経由で引き渡される引数を参照することができます.
@<list>{8a_ls_outsl} は outsl 関数にて, フレームポインタから引き渡されるはずの
3つの引数 (ポート番号, アドレス, カウント数) を取り出している例です.
コード中に @<strong>{port, address, count} といった変数のような名前が登場
しますが, こちらについては後述する「アドレッシング」をご参照ください.
//list[8a_ls_outsl][outsl での FP の利用例 (src/9/pc/l.s)]{
TEXT outsl(SB), $0
MOVL port+0(FP), DX /* 第1引数: ポート番号 */
MOVL address+4(FP), SI /* 第2引数: アドレス */
MOVL count+8(FP), CX /* 第3引数: カウント数 */
CLD
REP; OUTSL
RET
//}
: PC (Program Counter)
PC は現在実行中の命令のアドレスを格納するレジスタです. i386 では EIP が
これに対応します. 2a などでは BRA (BRanch Always) 命令にて特殊な使い方が
できますが, i386/amd64 ではあまり活用されていないため詳細は省略します.
: SB (Static Base)
SB は現在のプログラムのアドレス空間の先頭アドレスを格納するレジスタです.
これまでに挙げたアセンブラのコードの例にもちらほらとでてきました.
あるプログラムのメモリ空間を考えると, 定義したサブルーチンや
テキストセクションあるいはデータセクション中の値の場所(アドレス)は
そのプログラムのアドレス空間の先頭から数えてあるオフセット離れた位置に
存在するメモリ上の一定サイズの領域と考えることができます.
アセンブラでは便宜のために, このそれぞれに名前を付けることができます.
実体として「先頭 + ある名前に対応するオフセットの位置にあるサブルーチン」,
「先頭 + ある名前に対応するオフセットの位置にある値」という考え方をするため,
先頭を意味する @<strong>{SB} との表記が付いて回ります.
//list[8a_ls_SB][SB の利用例 (src/9/pc/l.s)]{
/*
* サブルーチンのシンボルを定義する場合:
* SB から数えてここの位置に bios32call なるシンボルを作り,
* サブルーチンとして呼び出せるようにする
*/
TEXT bios32call(SB), $0
....
//}
== アドレッシング
Plan 9 のアセンブラでは MC68020 (2a) のアドレッシング記法をベースに, これを
全アーキテクチャでも利用するようにしています. ここでは i386, amd64 のコードに
登場する主要なアドレッシング記法に絞って記載します.
: レジスタ: @<strong>{REG} (例: AX, BX, PC)
物理, 疑似問わずレジスタに格納されている値を参照する場合はひらで書きます
: 即値: @<strong>{$con} (例: $100, $(100-80), $0xFFFFFFFF )
即値を表現する場合は, 値の先頭に @<strong>{$} (ドルマーク) を付与します.
なお先にも述べたとおり10進数以外にも16進数や括弧で囲んだ計算式を
書くことも可能です. また浮動小数点も記述できます.
: オフセット付き間接参照: @<strong>{o(REG)} (例: 0(AX), 4(AX) )
レジスタに格納されている値をアドレスとして参照し, そこにオフセット o を足した
場所を参照する記法です. MOV 疑似命令の対象として例の様に書くことで,
転送元として AX に格納されたアドレスから 0, 4 バイト目の領域に存在する
値を取得したり, 転送先としてその位置に値を転送するといったことができます.
: 引数参照: @<strong>{o(FP)} (例: 0(FP), 4(FP) )
これは前述のオフセット付き間接参照の一部ですが, 特にサブルーチン内で
引数を取得するために多用されます. この具体例は @<list>{8a_ls_outsl} にて
挙げたとおりです.
: 名前付き引数参照: @<strong>{name+o(FP)} (例: address+4(FP) )
フレームポインタから引っ張った引数に対して名前を付ける際に用いられる記法です.
この例は同じく @<list>{8a_ls_outsl} でも紹介しました.
実体は @<strong>{'+'} より後ろの @<strong>{4(FP)} と記述している部分,
つまり "第2引数である" と記述している部分です. name の部分には人間が読む際に
ヒント情報となるように名前が付けられるようになっており, これを用いて port, address
などと引数としての意味を提示しています. 次の自動シンボルと異なり, この名前は
当該行以外からは参照できません.
: 自動シンボル: @<strong>{name+o(SB), name(SB)} (例: address+4(FP), touser(SB), array+1(SB) )
前述の引数の例と酷似していますが, こちらは SB からのオフセットを取る
自動シンボル(automatic symbol)とよばれる記法です. SB 疑似レジスタの項でも
述べた通り, TEXT や GLOBL で定義されたシンボルの位置から指定された
オフセット離れた領域にある値やサブルーチンを参照するために用いられます.
前述の引数参照とは異なり, この名前はグローバルに参照することができます.
例としてTEXT での定義およびその呼び出しの記法を
@<list>{a_automatic_text} に, GLOBL との組合せを @<list>{a_automatic_globl}
に記載します. 例にもあるようにオフセットが 0 の場合は @<strong>{+0} との表記を
省略可能です. また滅多登場しませんがにありませんが, マイナス方向にオフセットを
取る場合 "-1" とすることも可能です.
//list[a_automatic_text][サブルーチン rmode16 を定義した場合 (src/boot/pc/l.s)]{
/* サブルーチン rmode16 を定義 */
TEXT rmode16(SB), $0
...
/* rmode16 を呼び出し: オフセット0のため "+0" は省略可能 */
CALL rmode16(SB)
//}
//list[a_automatic_globl][自動シンボルとして array を定義した場合]{
/* データセクションの任意の位置に array として 4バイトのデータをセット */
DATA array+0(SB)/4, $"abc\z"
/* array として確保された領域を外部から参照できる様にする (4バイト分) */
GLOBL array(SB), $4
/* array の先頭0バイト目から1バイトを AX に格納する ('a' だけが入る) */
MOVB array(SB), AX
/* array の先頭2バイト目から1バイトを AX に格納する ('b' だけが入る) */
MOVB array+1(SB), AX
//}
: 局所シンボル: name<>+o(SB) (例: _exit<>+0(SB) )
局所シンボル (local symbol) はほぼ自動シンボルと同じような用途で用いられます.
記法上はシンボルの末尾に @<strong>{"<>"} が付与されているという違いがあります.
機能上は「局所」の名前が示すとおりこのシンボルのスコープはこれが定義されている
ファイルの中のみに限定されるという違いがあります.
実装上はローダがこの "<>" の位置に, 重複しない任意の整数を勝手に割り当てる
といったことを行い, これによりさもファイルごとに固有であるかのように
見せています (たとえば _exit<>+0(SB) は _exit99+0(SB) のように
変換される). GLOBL を用いた例については前述の @<list>{libc_386_main9} に
おける _exits<>+0(SB) の定義・利用が参考となります. TEXT による
局所シンボルとしてのサブルーチン定義の例を
@<list>{pc64_ls_protected} に記載します.
//list[pc64_ls_protected][局所シンボル _protected<> の定義 (src/9/pc64/l.s)]{
/* 同一ファイル内からしか参照できない _protected<>(SB) の定義 */
TEXT _protected<>(SB), 1, $-4
....
//}
: アドレス参照: @<strong>{$name(SB), $name+o(SB), $name<>+o(SB)} (例: $apmjumpstruct+0(SB) )
アドレス参照は指定されたシンボルのアドレスを取得する記法です.
前述の自動シンボルではシンボルの位置に格納されている値を取得できました.
この記法ではシンボルの指すアドレスを取得します. 局所シンボルのアドレス参照の
例は @<list>{libc_386_main9} にて登場していますが,
該当部分だけを @<list>{libc_386_main9_address} に抜粋します.
この例では @<strong>{_exits<>+0(SB)} なるデータセクションに確保された領域の
局所シンボルを定義していました.「アドレス参照」の部分でこれを参照しています.
この例では, @<strong>{"main"} の文字列が格納されている領域の先頭アドレスを
取得, これを AX に転送しています.
//list[libc_386_main9_address][局所シンボルのアドレス参照 _exits<>(SB)]{
loop:
MOVL $_exits<>(SB), AX /* アドレス参照 */
MOVL AX, 0(SP)
CALL exits(SB)
JMP loop
DATA _exits<>+0(SB)/4, $"main"
GLOBL _exits<>+0(SB), $5
//}
== プロシージャの定義
=== サブルーチンの定義
サブルーチンやテーブルのエントリーポイントを宣言するためには @<strong>{TEXT} 疑似
命令を用います. これはシンボルを定義するものであり JMP 命令で対象となるラベル
とは区別されます.
TEXT は通常2つ引数を取り, 第1引数に名前, 第2引数にサイズを指定できます.
第1引数の名前には CALL 命令などの対象として利用するシンボルを指定します.
第2引数のサイズはこのサブルーチンを呼ぶ際に, スタック中に自動的に確保して
ほしいバイト数を指定します.
//list[a_TEXT][TEXTを用いたエントリポイントの例: cas64 (src/libc/i386/atom.s)]{
/*
* int cas64(u64int *p, u64int ov, u64int nv);
*/
/*
* CMPXCHG64 (DI): 0000 1111 1100 0111 0000 1110,
*/
#define CMPXCHG64 BYTE $0x0F; BYTE $0xC7; BYTE $0x0F
TEXT cas64+0(SB),0,$0
MOVL p+0(FP), DI
MOVL ov+0x4(FP), AX
MOVL ov+0x8(FP), DX
MOVL nv+0xc(FP), BX
MOVL nv+0x10(FP), CX
LOCK
CMPXCHG64
JNE fail
MOVL $1,AX
RET
//}
TEXT 疑似オペレーションは引数を3つ取る場合があります. このときは上記で述べた
第2引数は第3引数に引き下がり, 代わりにビットフィールドを指定する
引数が追加されます.
このビットフィールドでは各ビットを立てることによりローダがこれを処理する際の
挙動を変えることができます. 例として1bit目が立っていればプロファイリングの禁止,
2bit目が立っていれば同一プログラム中に複数の同一TEXTを定義できるといった
塩梅です.
=== サブルーチンの呼び出し
前節にて定義したサブルーチンは TEXT 疑似命令の第1引数として指定したシンボルを
もとに呼び出すことが可能です. アセンブラ内から呼び出す場合は CALL 疑似命令を
用います. C 言語のコードからであれば関数として呼び出すことができます.
呼び出し規約はアーキテクチャにより様々ですが, 基本的には
呼び出し側は SP に示されるスタックに引数を積んでサブルーチンを呼び出します.
サブルーチン側は FP として示されるスタックにそれが渡されているものとして
処理を行い, データレジスタまたは汎用レジスタのうち第1のものにアドレスなり
即値なりを返値として転送して戻る, という良くある流れを取ります.
なおサブルーチン側はレジスタは呼び出し側が保存しているものとして, 一部例外を除き
好きに使って良いとされています ("caller saves").
== その他
Plan 9アセンブラでは @<strong>{#define} や @<strong>{#include} が使えます.
たとえば amd64 のマシン依存部のコードでは @<list>{pc64_ls}の例にあるように
先頭で各種defineによるマクロの定義されたヘッダファイルの取り込みや,
前述したアセンブラでサポートされていない命令の定義のために,
BYTE 疑似命令によって構成したバイト列 (エンコードされた機械語命令) を
マクロとして名前を付けて利用といったことを行っています.
//list[pc64_ls][#include と #define の利用 (src/9/pc64/l.s)]{
/* マクロとして DELAY を定義 */
#define DELAY BYTE $0xEB; BYTE $0x00 /* JMP .+2 */
/* 以下の様にニーモニックであるかのように記述 */
TEXT _lme<>(SB), 1, $-4
MOVL SI, CR3 /* load the mmu */
DELAY
....
//}