ARM にゅうもん

例のごとく、書かないと覚えられません。

参考文献

日本語命令セット: 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 が右になるけど。

ARM test environment on Debian

$ cat /etc/apt/sources.list.d/emdebian.list
deb http://www.emdebian.org/debian/ sid main

とか置いておいて、

$ sudo apt-get update
$ sudo apt-get install g++-4.7-arm-linux-gnueabihf qemu gdb-arm-none-eabi

とかで。 gdb-arm-none-eabi は gdb-multiarch はどうもおかしかったので。

実行は

$ arm-linux-gnueabihf-gcc-4.7 hello.c
$ qemu-arm -L /usr/arm-linux-gnueabihf ./a.out

デバッグ

$ qemu-arm -L /usr/arm-linux-gnueabihf -g 1234 ./a.out
$ arm-none-eabi-gdb ./a.out
(gdb) set sysroot /usr/arm-linux-gnueabihf
(gdb) target remote :1234
(gdb) b main
(gdb) cont

など。

Ubuntu はデフォルトでももうちょい色々入ってたと思います。

なにかあれば下記メールアドレスへ。
shinichiro.hamaji _at_ gmail.com
shinichiro.h