LoginSignup
2
1

More than 5 years have passed since last update.

QEMUでARMの未定義命令例外

Last updated at Posted at 2018-05-21

QEMUでARMの未定義命令例外の処理について調べてみました。

普通にIRQの割り込みを使いたいだけなんですけど、うまくいかないのでIRQよりは簡単な未定義命令例外の動作を確認してみました。

未定義命令例外とは

ARMでは未定義命令を検出すると例外が発生します。この未定義命令例外をつかうと未対応の命令をソフトでエミュレートすることなどができます。

未定義命令例外の動作例

アセンブラで、0xff、0xff、0xff、0xffを配置する。この 0xff, 0xff, 0xff, 0xffは未定義命令なので未定義命令例外が発生する。

    // occur undefined exception 
.byte 0xFF, 0xFF, 0xFF, 0xFF

これを QEMUで動かしてトレースすると、0xff、0xff、0xff、0xffを検出した直後に、未定義例外のベクター 0x00000004に飛んで命令を実行する。

0x000080a4:  e1540009  cmp      r4, sb
0x000080a8:  3afffffc  blo      #0x80a0
0x000080ac:  ffffffff  .byte    0xff, 0xff, 0xff, 0xff
0x00000004:  e59ff018  ldr      pc, [pc, #0x18]

未定義命令例外を処理する。

未定義命令例外を処理するのに必要なこと

  • ベクターテーブルを配置
  • 未定義命令モードのスタックポインタを設定
  • 例外ハンドラを用意する。

ベクターテーブルを配置

ARMは歴史的経緯で、ベクタテーブルのデフォルトの配置先は0x0です。各テーブルは4バイトです。大抵はハンドラへのジャンプ命令を書きます。
ベクタテーブルには合計8つの例外をサポートします。

実際のコードです。

.globl _start
_start:
    ldr pc, reset_handler
    ldr pc, undefined_handler
    ldr pc, swi_handler
    ldr pc, prefetch_handler
    ldr pc, data_handler
    ldr pc, unused_handler
    ldr pc, irq_handler
    ldr pc, fiq_handler

reset_handler:      .word reset
undefined_handler:  .word undef
swi_handler:        .word io_halt
prefetch_handler:   .word io_halt
data_handler:       .word io_halt
unused_handler:     .word io_halt
irq_handler:        .word io_halt
fiq_handler:        .word io_halt

reset:
    // copy vector 8000 to 0.
    mov r0, #0x8000
    mov r1, #0x0000
    ldmia r0!,{r2,r3,r4,r5,r6,r7,r8,r9}
    stmia r1!,{r2,r3,r4,r5,r6,r7,r8,r9}
    ldmia r0!,{r2,r3,r4,r5,r6,r7,r8,r9}
    stmia r1!,{r2,r3,r4,r5,r6,r7,r8,r9}

この例では _startを0x8000にしています。 (Raspberry Piのブートローダの仕様のためです。)

8000番地から命令を実行し、まず、resetにジャンプし、0x8000-0x801Fにあるベクタテーブルを0x0000-0x001Fにコピーします。

未定義命令モードのスタックポインタを設定

ARMでは例外が発生すると、例外に応じたプロセッサモードに自動的に遷移します。そのときプロセッサモードに応じたスタックポインタ SPに自動的に切り替わります。

未定義命令例外が発生したら、プロセッサモードが未定義例外モードになります。

未定義命令例外発生時にスタックを使う必要があるなら、事前にスタックポインタSPを設定します。

コード例です。

    // save cpsr
    mrs r0, cpsr

    // setup sp in undef mode
    bic r1, r0, #0x1f
    orr r1, r1, #0x1b
    msr cpsr_c,r1
    mov sp,#0x4000

    // restore cpsr
    msr cpsr_c, r0

cpsrの下位5ビットにプロセッサモードを設定すると、そのプロセッサモードに遷移します。
なので、まず、r0にcpsrを退避します。
次に、下位5ビットを0クリアして、未定義命令モード 0x1bを設定し、cpsr_cにwriteします。
こすうると、未定義命令モードに入ったので、SPに0x4000を設定しています。
後片付けとして、r0をcpsr_cに戻して、元のプロセッサモードに戻しときます。

例外ハンドラの作成

例外ハンドラの例です。

undef:
    push {r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,lr}
    bl c_undef_handler
    pop  {r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,lr}
    movs pc, lr

r0からr12とlrをスタックに退避します。
C言語のハンドラを呼びます。
r0からr12とlrをスタックから戻します。
未定義命令例外の場合は、例外発生時にlrに次のPCが格納されているので、lrの値をpcに代入すると、次の命令に復帰します。

トレース

起動してから、未定義命令例外ハンドラ処理までのトレースです。

$ qemu-system-arm -M raspi2 -m 128 -serial mon:stdio -nographic -kernel kernel.elf -d in_asm
----------------
IN: 
0x00008000:  e59ff018  ldr      pc, [pc, #0x18]

----------------
IN: 
0x00008040:  ee101fb0  mrc      p15, #0, r1, c0, c0, #5
0x00008044:  e2011003  and      r1, r1, #3
0x00008048:  e3510002  cmp      r1, #2
0x0000804c:  1a00001d  bne      #0x80c8

----------------
IN: 
0x000080c8:  e320f002  wfe      
0x000080cc:  eafffffd  b        #0x80c8

----------------
IN: 
0x00008050:  e3a00902  mov      r0, #0x8000
0x00008054:  e3a01000  mov      r1, #0
0x00008058:  e8b003fc  ldm      r0!, {r2, r3, r4, r5, r6, r7, r8, sb}
0x0000805c:  e8a103fc  stm      r1!, {r2, r3, r4, r5, r6, r7, r8, sb}
0x00008060:  e8b003fc  ldm      r0!, {r2, r3, r4, r5, r6, r7, r8, sb}
0x00008064:  e8a103fc  stm      r1!, {r2, r3, r4, r5, r6, r7, r8, sb}
0x00008068:  e10f0000  mrs      r0, apsr
0x0000806c:  e3c0101f  bic      r1, r0, #0x1f
0x00008070:  e381101b  orr      r1, r1, #0x1b
0x00008074:  e121f001  msr      cpsr_c, r1

----------------
IN: 
0x00008078:  e3a0d901  mov      sp, #0x4000
0x0000807c:  e121f000  msr      cpsr_c, r0

----------------
IN: 
0x00008080:  e3a0d902  mov      sp, #0x8000
0x00008084:  e59f4044  ldr      r4, [pc, #0x44]
0x00008088:  e59f9044  ldr      sb, [pc, #0x44]
0x0000808c:  e3a05000  mov      r5, #0
0x00008090:  e3a06000  mov      r6, #0
0x00008094:  e3a07000  mov      r7, #0
0x00008098:  e3a08000  mov      r8, #0
0x0000809c:  ea000000  b        #0x80a4

----------------
IN: 
0x000080a4:  e1540009  cmp      r4, sb
0x000080a8:  3afffffc  blo      #0x80a0

----------------
IN: 
0x000080ac:  ffffffff  .byte    0xff, 0xff, 0xff, 0xff

----------------
IN: 
0x00000004:  e59ff018  ldr      pc, [pc, #0x18]

----------------
IN: 
0x000080b8:  e92d5fff  push     {r0, r1, r2, r3, r4, r5, r6, r7, r8, sb, sl, fp, ip, lr}
0x000080bc:  eb00001a  bl       #0x812c

----------------
IN: c_undef_handler
0x0000812c:  e12fff1e  bx       lr

----------------
IN: 
0x000080c0:  e8bd5fff  pop      {r0, r1, r2, r3, r4, r5, r6, r7, r8, sb, sl, fp, ip, lr}
0x000080c4:  e1b0f00e  movs     pc, lr

----------------
IN: 
0x000080b0:  e59f3020  ldr      r3, [pc, #0x20]
0x000080b4:  e12fff33  blx      r3

----------------
IN: kernel_main
0x00008120:  e92d4010  push     {r4, lr}
0x00008124:  ebffffe7  bl       #0x80c8

QEMU: Terminated

VBARを使って、ベクタテーブルの場所を指定

ARMv7-Aでは、VBAR (Vector Base Addess Register)を使ってみました。

VBARを使う場合、ベクターテーブルは32バイトアラインの場所に配置する仕様です。

VBARに設定してみました。


    // set VBAR
    ldr r0, =vector
    mcr P15, 0, r0, c12, c0, 0

中略

.align 5
vector:
    ldr pc, reset_handler
    ldr pc, undefined_handler
    ldr pc, swi_handler
    ldr pc, prefetch_handler
    ldr pc, data_handler
    ldr pc, unused_handler
    ldr pc, irq_handler
    ldr pc, fiq_handler

reset_handler:      .word reset
undefined_handler:  .word undef
swi_handler:        .word io_halt
prefetch_handler:   .word io_halt
data_handler:       .word io_halt
unused_handler:     .word io_halt
irq_handler:        .word io_halt
fiq_handler:        .word io_halt

.alignが5だと、2の5乗の32バイトにアラインする指定です。

r0に設定したいアドレスを書き込み。
VBARはコプロセッサCP15にあるので、ARMv7-Aのアーキテクチャリファレンスマニュアルに記載のパラメータでmcr命令を使います。
r0の内容をVBARに書き込みます。

動作を試してみましたので、そのトレースです。

IN: 
0x0000805c:  ffffffff  .byte    0xff, 0xff, 0xff, 0xff

----------------
IN: 
0x000080a4:  e59ff018  ldr      pc, [pc, #0x18]

未定義命令例外のベクタが、0x000080a4に変更できました。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1