ARM Thumb 命令セット gcc サポート

gcc 上での Thumb 命令サポートについて.

コンパイルオプション

gcc のドキュメントから Thumb 命令に関するオプションを拾ってみると

  • -mthumb-interwork / -mno-thumb-interwork
  • -mthumb
  • -mtpcs-frame / -mno-tpcs-frame / -mtpcs-leaf-frame
  • -mcallee-super-interworking / -mcaller-super-interworking

というあたりかな.

ざっくり分類すると,こんなところかな.

Thumb 命令自体の有効/無効
これは当然.
関数呼び出し・リンク時の問題
ARM 命令モードの状態で 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 の環境でいじりました.

CPU
88F5182 (I-O DATA HDL-GXR)
kernel
linux 2.6.12.6
userland
debian etch
gcc
gcc 4.1.2

ソース

例としてこんなソースを用意します.

  • arm.c, main.c は ARM 命令セットで
  • thumb.c は Thumb命令セットで

コンパイルすることを考えてます.

関数の呼び出しはこんなところ. 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 上でもかろうじて使える,のかなぁ?


トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2009-11-08 (日) 18:49:29 (4000d)