例のごとく、書かないと覚えられません。
参考文献
日本語命令セット: http://www.ced.is.utsunomiya-u.ac.jp/lecture/2013/aca/ARMjp-vH.pdf
コンパイラのマニュアルらしいけど概要的に便利なやつ: http://infocenter.arm.com/help/topic/com.arm.doc.dui0056d/DUI0056.pdf
registers
r0 から r3 は caller save なレジスタで引数に使う。 r4-r11 はたぶん普通は callee save 。
r12 は別名 ip 。 r12 は ARM Thumb の切り替え時に必要なら使うらしい…が、ぱっと見短い R_ARM_CALL なら使わない気もする。まぁいずれにせよ caller save
r13 は sp 。 r14 は lr で bl か blx 時に戻りアドレスが勝手に入る。 r15 は pc 。いじると jmp になる。
d0-d31 倍精度浮動小数点レジスタ。 s0-s31 までは単精度浮動小数点レジスタで、 d0 は s0:s1 。 VFP とか言う。 SIMD 拡張が NEON で、 q0 が d0:d1 みたいな感じで 128 bit の SIMD レジスタになる。
あと NaCl は r9 が thread pointer で触れない
calling convention
r0 から r3 が引数と戻り値。レジスタ一つに入らない struct とかは stack 使う。戻り値は double と long long で r0 r1 使って、 128bit の値は r0-r3 使う。 64bit struct とかは stack 、ぽい。入り切らなくなったら stack 使うけど、これで long long が分割されたりもする。
浮動小数まわりの ABI は GCC だと三種類あって、 -mfloat-abi=soft, -mfloat-abi=softfp (android), -mfloat-abi=hard (chrome) 。 soft だと VFP レジスタをいっさい使わなくて、 softfp は使うけど引数渡しは汎用レジスタ使う、 hard は引数渡しにも VFP レジスタ (NaCl だと d0-d7) 使う。
レジスタ渡しする calling convention は可変長引数が面白いことになりがちですが、 ARM も面白くて、浮動小数点レジスタを絶対使わなくなる。つまり正しい宣言が無いと呼び出しが混乱して、
int main() { va_func(42.0); extern void va_func(float f, ...); va_func(42.0); } void va_func(float f, ...) { printf("va_func: %f\n", f); }
とかだと一個目の結果がおかしくなる。浮動小数の ABI は混ぜるな危険もいいところなんで、 .o の .ARM.attributes って section に ABI 情報が入ってる。
ARM Thumb interwork
Thumb モードは命令エンコードが ARM モードより短い変わりに、少し命令がリッチじゃないモード、らしい。一般的には ARM モードが速いとされてる、らしい。基本的に decoder の state を変えるだけなので変更は一瞬らしい(つっても pipeline は乱れる?)。
Thumb モードの関数ポインタは奇数。 ARM モードは偶数。レジスタ指定のブランチ命令 (bx と blx) はレジスタに入ってる値が偶数なら ARM に、奇数なら Thumb モードに切り変える。 24bit 相対のブランチ命令 (b, bx, bl と blx) は x がついてるやつは ARM Thumb 逆転。 relocation が R_ARM_CALL になってると、飛び先のモードに応じて、 link time に bl と blx を変えたりするっぽい。
今 ARM Thumb モードどっちか、ってのは cpsr ってレジスタの 0x20 の bit に入ってるぽい。 gdb とかはたぶんこれ見て状態を確認してるんだと思われる。ちなみに pc は Thumb でも常に偶数ぽい。
リンカとか objdump とかが適切に ARM Thumb 出せるのは、たぶんシンボルの値が奇数なら Thumb 、とかやってるのかなぁと思います…たぶん。 objdump -b binary とかだとシンボル情報無いんで、 Thumb のコードを見たい、って時は -Mforce-thumb とかつければ良いぽい。
instructions
よく言われる特徴的なのは ARM mode ではたいていの命令は condition bit を見て条件実行する 4bit が先頭についてること。常時実行が 0b1110 なので、 ARM mode の命令はだいたい 0xe で始まってる。ただ、スペースが明らかに足りてない、 24bit 即値ブランチとかは条件無し。
あとは RISC なんで即値代入が2命令にわかれてるのが特徴的か。やたらと movw movt ってのを見る感じ。 0x23456789 とかを代入すると、
10: e3062789 movw r2, #26505 ; 0x6789 14: e3422345 movt r2, #9029 ; 0x2345
とかになる。即値が二箇所に飛び散るからエンコードされたものが読みにくい…
あとは push/pop で任意個の複数レジスタを好き勝手 push/pop できるのが面白いですかね。関数の入口出口でまとめて退避できて便利。レジスタ 16 本なんで 16bit でどれを退避…みたいな感じになる。
objdump とか gas 読む時は基本 destination が左で、まぁ読みやすい。 str だけ destination が右になるけど。