ARM Thumb 命令セット gcc サポート †gcc 上での Thumb 命令サポートについて. コンパイルオプション †gcc のドキュメントから Thumb 命令に関するオプションを拾ってみると
というあたりかな. ざっくり分類すると,こんなところかな.
Thumb Interwork †-mthumb-interwork の説明によると Generate code which supports calling between the ARM and Thumb instruction sets. Without this option the two instruction sets cannot be reliably used inside one program. The default is `-mno-thumb-interwork', since slightly larger code is generated when `-mthumb-interwork' is specified. ARM 命令セットと Thumb 命令セットをチャンポンに使ってプログラムをリンクできる枠組みのようです. Super Interworking †-mcallee-super-interworking の説明によると Gives all externally visible functions in the file being compiled an ARM instruction set header which switches to Thumb mode before executing the rest of the function. This allows these functions to be called from non-interworking code. 上記の Thumb Interwork の枠組みを使わずに呼び出し元 or 呼び出し先でモード切り替えを行うことで,非 Thumb Interwork コードとインターフェースしよう,ということのようですね. ARM Thumb 命令セット Thumb Interwork †gcc での Thumb 命令サポートでの Thumb Interwork とよばれてるものについて. 環境 †今回は old ABI の環境でいじりました.
ソース †例としてこんなソースを用意します.
コンパイルすることを考えてます. 関数の呼び出しはこんなところ. ARM → Thumb と Thumb → ARM の両パターンが入るようにしています. main -+-> arm_caller -> thumb_callee |-> thumb_caller -> arm_callee arm.c extern int thumb_callee ( int ); int arm_caller ( int a ) { return a + thumb_callee ( a ); } int arm_callee ( int a ) { return 1 + a; } thumb.c extern int arm_callee ( int ); int thumb_caller ( int a ) { return a + arm_callee ( a ); } int thumb_callee ( int a ) { return 1 + a; } main.c #include <stdio.h> extern int arm_caller ( int ); extern int thumb_caller ( int ); int main ( void ) { printf ( "%d %d\n", arm_caller(1), thumb_caller(1) ); return 0; } コンパイル †とりあえずコンパイルしてみます.-mthumb-interwork というオプションで Thumb Interwork を使うことを指示しています. $ gcc -mthumb-interwork main.c -c $ gcc -mthumb-interwork arm.c -c $ gcc -mthumb-interwork -mthumb thumb.c -c ELF ヘッダ †生成されたオブジェクトファイルの ELF ヘッダを見てみます. $ readelf -h arm.o ELF ヘッダ: マジック: 7f 45 4c 46 01 01 01 61 00 00 00 00 00 00 00 00 クラス: ELF32 データ: 2 の補数、リトルエンディアン バージョン: 1 (current) OS/ABI: ARM ABI バージョン: 0 タイプ: REL (再配置可能ファイル) マシン: ARM バージョン: 0x1 エントリポイントアドレス: 0x0 プログラムの開始ヘッダ: 0 (バイト) セクションヘッダ始点: 268 (バイト) フラグ: 0x4, GNU EABI, interworking enabled このヘッダのサイズ: 52 (バイト) プログラムヘッダサイズ: 0 (バイト) プログラムヘッダ数: 0 セクションヘッダ: 40 (バイト) Number of section headers: 9 Section header string table index: 6 「フラグ」のところに interworking enabled というフラグが立っており,通常の ARM 命令の ELF ファイルとは区別されるようになってます. オブジェクトファイルのディスアセンブル †ディスアセンブルしてみます. $ objdump -d arm.o arm.o: ファイル形式 elf32-littlearm セクション .text の逆アセンブル: 00000000 <arm_caller>: 0: e1a0c00d mov ip, sp 4: e92dd800 stmdb sp!, {fp, ip, lr, pc} 8: e24cb004 sub fp, ip, #4 ; 0x4 c: e24dd004 sub sp, sp, #4 ; 0x4 10: e50b0010 str r0, [fp, #-16] 14: e51b0010 ldr r0, [fp, #-16] 18: ebfffffe bl 0 <thumb_callee> 1c: e1a02000 mov r2, r0 20: e51b3010 ldr r3, [fp, #-16] 24: e0823003 add r3, r2, r3 28: e1a00003 mov r0, r3 2c: e24bd00c sub sp, fp, #12 ; 0xc 30: e89d6800 ldmia sp, {fp, sp, lr} 34: e12fff1e bx lr … 関数からの戻りは bx 命令でモード切り替えをするようになってますが,thumb_callee の呼び出しは bl 命令を使っており,このままでは Thumb モードにはスイッチできませんね. もっとも,単体でコンパイルしているのでこの段階ではコンパイラは「thumb_callee が Thumb 命令の関数である」ということを知らないわけで,無理もありません. thumb.o も同様にディスアセンブルしてみます. $ objdump -d thumb.o thumb.o: ファイル形式 elf32-littlearm セクション .text の逆アセンブル: 00000000 <thumb_caller>: 0: b580 push {r7, lr} 2: b081 sub sp, #4 4: af00 add r7, sp, #0 6: 1c3b adds r3, r7, #0 8: 6018 str r0, [r3, #0] a: 1c3b adds r3, r7, #0 c: 681b ldr r3, [r3, #0] e: 1c18 adds r0, r3, #0 10: f7ff fffe bl 0 <arm_callee> 14: 1c02 adds r2, r0, #0 16: 1c3b adds r3, r7, #0 18: 681b ldr r3, [r3, #0] 1a: 18d3 adds r3, r2, r3 1c: 1c18 adds r0, r3, #0 1e: 46bd mov sp, r7 20: b001 add sp, #4 22: bc80 pop {r7} 24: bc02 pop {r1} 26: 4708 bx r1 … 1命令が 16bit になっており,ちゃんと Thumb 命令が使われてますね. 関数呼び出しと関数からのリターンは arm.o と同様のことになってます. リンク †というわけで,おもむろにリンク. $ gcc -o main main.o arm.o thumb.o /usr/bin/ld: Warning: /usr/lib/gcc/arm-linux-gnu/4.1.2/libgcc_s.so does not support interworking, whereas main does /usr/bin/ld: Warning: /lib/libc.so.6 does not support interworking, whereas main does /usr/bin/ld: Warning: /usr/lib/libc_nonshared.a(elf-init.oS) does not support interworking, whereas main does /usr/bin/ld: Warning: /lib/ld-linux.so.2 does not support interworking, whereas main does /usr/bin/ld: Warning: /usr/lib/gcc/arm-linux-gnu/4.1.2/libgcc_s.so does not support interworking, whereas main does /usr/bin/ld: Warning: /usr/lib/gcc/arm-linux-gnu/4.1.2/crtend.o does not support interworking, whereas main does /usr/bin/ld: Warning: /usr/lib/gcc/arm-linux-gnu/4.1.2/../../../crtn.o does not support interworking, whereas main does ELF ヘッダの interworking フラグを見ているようで,warning の嵐. main.o arm.o thumb.o は Thumb Interwork の ELF ファイルですが,libc や crt.o (スタートアップルーチン.main の前に実行される部分)などは 非 Thumb Interwork ファイルなので「形式が違うぞ」と警告しているわけです. とりあえずこのままでも実行ファイルはできているのですが,warning が嫌いという場合には $ gcc -Wl,--no-warn-mismatch -o main main.o arm.o thumb.o これで warning は表示されなくなります. ただし,「表示されない」というだけで問題が解決したわけではないので注意しましょう. 実行ファイルのディスアセンブル †生成された実行ファイルもディスアセンブルしてみましょう. $ objdump -d main …(略)… 0000840c <arm_caller>: 840c: e1a0c00d mov ip, sp 8410: e92dd800 stmdb sp!, {fp, ip, lr, pc} 8414: e24cb004 sub fp, ip, #4 ; 0x4 8418: e24dd004 sub sp, sp, #4 ; 0x4 841c: e50b0010 str r0, [fp, #-16] 8420: e51b0010 ldr r0, [fp, #-16] 8424: eb000060 bl 85ac <__thumb_callee_from_arm> 8428: e1a02000 mov r2, r0 842c: e51b3010 ldr r3, [fp, #-16] 8430: e0823003 add r3, r2, r3 8434: e1a00003 mov r0, r3 8438: e24bd00c sub sp, fp, #12 ; 0xc 843c: e89d6800 ldmia sp, {fp, sp, lr} 8440: e12fff1e bx lr …(略)… 00008498 <thumb_callee>: 8498: b580 push {r7, lr} 849a: b081 sub sp, #4 849c: af00 add r7, sp, #0 849e: 1c3b adds r3, r7, #0 84a0: 6018 str r0, [r3, #0] 84a2: 1c3b adds r3, r7, #0 84a4: 681b ldr r3, [r3, #0] …(略)… 000085ac <__thumb_callee_from_arm>: 85ac: e59fc000 ldr ip, [pc, #0] ; 85b4 <__thumb_callee_from_arm+0x8> 85b0: e12fff1c bx ip 85b4: 00008499 muleq r0, r9, r4 …(略)… 関数呼び出しの間に __thumb_callee_from_arm というルーチンが挟まっていて,arm_caller → __thum_callee_from_arm → arm_callee という経路で呼び出されています. で,__thumb_callee_from_arm の中で ARM → Thumb のモード切り替えが行われています. ARM 社のツールキットの説明では,このように間に挟まってモード切り替えを行うルーチンを「ベニア (veneer)」と呼んでいます. 余談ですが,アドレス 0x85b4 に入っているのはホントは命令ではなくて「thumb_callee の先頭アドレス + thumb フラグ」のデータなのですが,ディスアセンブラは命令として解釈してしまってるようですね. 実行パスを解釈してディスアセンブルしているわけじゃないので,こんなこともよく起こります. 実行 †プログラムを実行してみましょう. $ ./main 3 3 $ echo $? 0 ちゃんと動いてますね. まとめ †Thumb Interwork は ARM Linux 上でもかろうじて使える,のかなぁ? |