ARMのアセンブラをいじってみました。生のバイナリを確認するにはアセンブルしてから逆アセンブルすれば簡単です。x86のときはnasmとndisasm -uでやっていましたが、binutilsではasとobjdumpを使います。
以下の内容をa.sとして保存します。
mov r1, #0x12000000 add r1, r1, #0x340000 add r1, r1, #0x5600 add r1, r1, #0x78
x86に慣れているとaddの引数が3つもあるのは不思議な感じです。これは r1 = r1 + 0x340000 を意味しています。
アセンブルします。
% arm-wince-pe-as a.s
出力ファイルを指定していないため出力はデフォルトのa.outになります。それを逆アセンブルします。
% arm-wince-pe-objdump -d a.out a.out: file format pe-arm-wince-little Disassembly of section .text: 00000000 <.text>: 0: e3a01412 mov r1, #301989888 ; 0x12000000 4: e281170d add r1, r1, #3407872 ; 0x340000 8: e2811c56 add r1, r1, #22016 ; 0x5600 c: e2811078 add r1, r1, #120 ; 0x78
ARMでは引数込みで4バイト固定長になっているのが分かります。
x86ならmov eax, 0x12345678とすれば済みますが、ARMでは4バイト制約があるため、即値の代入でもこのように演算を交える必要があります。即値はシフトと組み合わせることが出来るため、下位ビットがゼロであれば巨大な数(0x12000000など)も表現することができます。
これでは効率が悪いため、プロシージャの直後に定数用のエリアを確保して、そこから値を読み込むようです。たとえば以下のC言語のソース(test.c)を用意します。
void test() { int a = 0x12345678; }
% arm-wince-pe-gcc -c test.c % arm-wince-pe-objdump -d test.o test.o: file format pe-arm-wince-little Disassembly of section .text: 00000000 <_test>: 0: e1a0c00d mov ip, sp 4: e92dd800 push {fp, ip, lr, pc} 8: e24cb004 sub fp, ip, #4 ; 0x4 c: e24dd004 sub sp, sp, #4 ; 0x4 10: e59f3004 ldr r3, [pc, #4] ; 1c <L3> 14: e50b3010 str r3, [fp, #-16] 18: e89da808 ldm sp, {r3, fp, sp, pc} 0000001c <L3>: 1c: 12345678 eorsne r5, r4, #125829120 ; 0x7800000
1cは定数エリアのため、逆アセンブル結果(eorsne)に意味はありません。
gccに-Sオプションを渡せば逆アセンブルする必要はないのですが、アセンブリだけでなくバイナリが見たかったため、今回は-Sオプションを使わずにコンパイル結果を逆アセンブルしています。-Sオプションの出力はx86と違ってAT&T形式ではありません。
ちなみにx86やx64では-masm=intelと指定すればnasmと同じ形式で出力できます。
余談
今までx86以外はどこから手を付ければ良いのか分からなかったのですが、こうやってARMのバイナリに触れてみると、急に身近になったような気がしました。
ハードも開発用の評価ボードがないと手が出ないような先入観があったのですが、いつも持ち歩いているWILLCOM 03でも実験できるので、これまた身近です。
むしろなぜ今まで気付かなかったのだろうという印象です。