Tweet


* ARM Thumb 命令セット Thumb Interwork [#f7214132]
gcc での Thumb 命令サポートでの Thumb Interwork とよばれてるものについて.

** 環境 [#ce52fdd0]

今回は old ABI の環境でいじりました.

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

** ソース [#k791ae8a]

例としてこんなソースを用意します.
- 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;
 }

** コンパイル [#v9aead66]
とりあえずコンパイルしてみます.-mthumb-interwork というオプションで Thumb Interwork を使うことを指示しています.

 $ gcc -mthumb-interwork main.c -c
 $ gcc -mthumb-interwork arm.c -c
 $ gcc -mthumb-interwork -mthumb thumb.c -c

** ELF ヘッダ [#udd04ca9]
生成されたオブジェクトファイルの 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 ファイルとは区別されるようになってます.

** オブジェクトファイルのディスアセンブル [#v44e8749]
ディスアセンブルしてみます.

 $ 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 と同様のことになってます.

** リンク [#gdbbbd62]
というわけで,おもむろにリンク.
 $ 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 は表示されなくなります.
ただし,「表示されない」というだけで問題が解決したわけではないので注意しましょう.

** 実行ファイルのディスアセンブル [#c66eb371]

生成された実行ファイルもディスアセンブルしてみましょう.

 $ 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 フラグ」のデータなのですが,ディスアセンブラは命令として解釈してしまってるようですね.
実行パスを解釈してディスアセンブルしているわけじゃないので,こんなこともよく起こります.

** 実行 [#s08d50c6]
プログラムを実行してみましょう.
 $ ./main 
 3 3
 $ echo $?
 0

ちゃんと動いてますね.

** まとめ [#pbd6184e]

Thumb Interwork は ARM Linux 上でもかろうじて使える,のかなぁ?


トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS