Tweet

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:54:10 (3728d)