hotasm.connpass.com
おさらい
10.1.4 raレジスタはハードウェア的に特別なものになっている
00fe1598 <call_complex1>:
fe1598: 27bdffe8 addiu sp,sp,-24
fe159c: afbf0014 sw ra,20(sp)
fe15a0: 0c3f8518 jal fe1460 <return_arg1>
fe15a4: 240400fe li a0,254
fe15a8: 24420001 addiu v0,v0,1
fe15ac: 8fbf0014 lw ra,20(sp)
fe15b0: 00000000 nop
fe15b4: 03e00008 jr ra
fe15b8: 27bd0018 addiu sp,sp,24
10.2 PowerPC
- ABI的にr3が第一引数であり戻り値でもある
- stwuはアトミックな命令
mr r0, r1
addi r1,r1,-16
stw r0, (r1)
- mflrはリンクレジスタの値をコピーする命令(リンクレジスタはMIPSでいうところのreturn address)
- blで関数を呼び出すけど値が壊されるので退避と復帰が行われている
- リンクレジスタは特別なので、専用の命令が用意されている
- MIPSは特殊レジスタと汎用レジスタで特殊な線引きをやっている
00fe1580 <call_complex1>:
fe1580: 94 21 ff f0 stwu r1,-16(r1)
fe1584: 7c 08 02 a6 mflr r0
fe1588: 90 01 00 14 stw r0,20(r1)
fe158c: 38 60 00 fe li r3,254
fe1590: 4b ff fe cd bl fe145c <return_arg1>
fe1594: 38 63 00 01 addi r3,r3,1
fe1598: 80 01 00 14 lwz r0,20(r1)
fe159c: 7c 08 03 a6 mtlr r0
fe15a0: 38 21 00 10 addi r1,r1,16
fe15a4: 4e 80 00 20 blr
10.3 ARMの関数呼び出し
- なんかごちゃごちゃレジスタが登場している
- スタックは積み木で、下から積み上げていって(push)、上から取りに行く(pop)
- ビットマップというものが用意されている
00fe1578 <call_complex1>:
fe1578: e1a0c00d mov ip, sp ! ipとspが同じ所を指す
fe157c: e92dd800 push {fp, ip, lr, pc} ! pc lr ip fpの順にスタックに積まれる
fe1580: e24cb004 sub fp, ip, #4 ! 減算
fe1584: e3a000fe mov r0, #254 ; 0xfe ! r0に0xfeを入れる
fe1588: ebffffb5 bl fe1464 <return_arg1> ! return_arg1 関数呼び出し
fe158c: e2800001 add r0, r0, #1
fe1590: e89da800 ldm sp, {fp, sp, pc} ! fp sp pcの値を復帰させている(ipは復帰させていない)
ポインタ経由での関数呼び出し
- ポインタで関数を呼び出す
- 単純にint *fだと戻り値がint型のポインタになってしまう
void call_pointer(int (*f)(void))
{
f();
}
- プログラムカウンタを直接書き換えることでjal命令の代わりとしている
- ARMのプログラムカウンタは常に2つ先の命令を示している
- このアセンブラは結構トリッキー
- mov命令でジャンプしているので、もう狂っているとしか…
- プログラムカウンタはmovで弄るのは気持ち悪い
00fe15c0 <call_pointer>:
fe15c0: e1a0c00d mov ip, sp ! プロローグ
fe15c4: e92dd800 push {fp, ip, lr, pc}
fe15c8: e24cb004 sub fp, ip, #4
fe15cc: e1a0e00f mov lr, pc ! 実際の処理
fe15d0: e1a0f000 mov pc, r0
fe15d4: e89da800 ldm sp, {fp, sp, pc} ! エピローグ
ここから
10.3.5 ARM の命令セットは、あまりRISCっぽくないように思える
P274
- ARMの即値はあまりビット数を取れないのでPC相対を利用している
- RISCではメモリアクセスを極力しないという思想
- メモリへのアクセスはキャッシュミスによりパイプラインの動作を止めてしまう可能性
- 定数値を扱うためにメモリアクセスが発生→ARMは他のRISC系と比べてあまりRISCぽくない
- RISC系は命令が固定長なので、命令セットのビット数の割り振りが重要になる
- ARMは条件コードで4ビット使うので、自由に使えるビット数は32ビット
- ポインタ経由での関数呼び出しリターン命令を mov命令で実現することで、命令数を節約
10.3.6 フレームポインタ(fp)とはなにか
framepointerつきでコンパイルしたやつ
int return_zero()
{
return 0;
}
$ mips-elf-gcc -nostdlib -g -O -fno-omit-frame-pointer return_zero_mips.c -o test.out
00400018 <return_zero>:
400018: 27bdfff8 addiu sp,sp,-8
40001c: afbe0004 sw s8,4(sp)
400020: 03a0f021 move s8,sp
400024: 00001021 move v0,zero
400028: 03c0e821 move sp,s8
40002c: 8fbe0004 lw s8,4(sp)
400030: 03e00008 jr ra
400034: 27bd0008 addiu sp,sp,8
- もっとも return_zero()は他の関数を呼び出さない、いわゆる「リーフ関数」です。このため関数呼び出しによってフレームポインタの値が上書きされることは無く、実はフレームポインタの退避・復旧処理を行なわなくてもデバッガで追跡可能だったりします
10.4 SHの関数呼び出し
10.4.1 SHは「プリデクリメントレジスタ間接」というアドレッシングモードをもつ
00fe1508 <_call_complex1>:
fe1508: 4f 22 sts.l pr,@-r15
fe150a: 94 05 mov.w fe1518 <_call_complex1+0x10>,r4 ! fe
fe150c: d0 03 mov.l fe151c <_call_complex1+0x14>,r0 ! fe1440 <_return_arg1>
fe150e: 40 0b jsr @r0
fe1510: 00 09 nop
fe1512: 4f 26 lds.l @r15+,pr
fe1514: 00 0b rts
fe1516: 70 01 add #1,r0
fe1518: 00 fe mov.l @(r0,r15),r0
fe151a: 00 09 nop
fe151c: 00 fe mov.l @(r0,r15),r0
fe151e: 14 40 mov.l r4,@(0,r4)
gist.github.com
10.4.2 SH ではポインタ経由で関数呼び出しが行われる
- SHは即値を入れるところがものすごく少ない
- fe150aで return_arg1()のアドレスを rOレジスタに代入する
- fe150cで rOレジスタの指す先を関数として呼び出す
- jsrはちょっと謎
- movで別のアドレスの値(即値)を読み込んで、そこにジャンプする
10.4.3 関数呼び出しのアドレス指定方法
ここで突然のPowerPC
- なんらかのフラグ領域(つまりオペコードの一部)にして,実際のアドレス計算の際にはフラグ領域は無視してゼロとして計算するという思想、というか設計(作った人のこだわり)
- この本は機械語にこだわっていて、バイナリベースの本が多かったりする。
- PowerPCではジャンプ先のアドレスはその命令のアドレスを基点に計算する、という設計
- 差分計算
10.4.5 SHは多ビットのオペランドを取れない
- PowerPCや ARMでは相対アドレス指定でジャンプ先を指定
- SHは2バイト命令長のためオペランドとして指定できる即値のビット幅をそれほど大きくはとれませんから,相対アドレスで指定できる範囲が非常に狭くなってしまいます
- レジスタ経由で関数を呼び出す
P284