# Linux Kernel Module Cheat

#### 33.10.2. ARM SVC instruction

This is the most basic example of exception handling we have.

We a handler for SVC, do an SVC, and observe that the handler got called and returned from C and assembly:

    ./run --arch aarch64 --baremetal baremetal/arch/aarch64/svc.c
    ./run --arch aarch64 --baremetal baremetal/arch/aarch64/svc_asm.S

Sources:

- baremetal/arch/aarch64/svc.c

- baremetal/arch/aarch64/svc_asm.S

Sample output for the C one:

    DAIF 0x3C0
    SPSEL 0x1
    VBAR_EL1 0x40000800
    after_svc 0x4000209c
    lkmc_vector_trap_handler
    exc_type 0x11
    exc_type is LKMC_VECTOR_SYNC_SPX
    ESR 0x5600ABCD
    ESR.EC 0x15
    ESR.EC.ISS.imm16 0xABCD
    SP 0x4200C510
    ELR 0x4000209C
    SPSR 0x600003C5
    x0 0x0
    x1 0x1
    x2 0x15
    x3 0x15
    x4 0x4000A178
    x5 0xFFFFFFF6
    x6 0x4200C390
    x7 0x78
    x8 0x1
    x9 0x14
    x10 0x0
    x11 0x0
    x12 0x0
    x13 0x0
    x14 0x0
    x15 0x0
    x16 0x0
    x17 0x0
    x18 0x0
    x19 0x0
    x20 0x0
    x21 0x0
    x22 0x0
    x23 0x0
    x24 0x0
    x25 0x0
    x26 0x0
    x27 0x0
    x28 0x0
    x29 0x4200C510
    x30 0x40002064

The C code does an:

    svc 0xABCD

and the value 0xABCD appears at the bottom of ARM ESR register:

    ESR 0x5600ABCD
    ESR.EC 0x15
    ESR.EC.ISS.imm16 0xABCD

The other important register is the ARM ELR register, which contains the return address after the exception.

From the output, we can see that it matches the value as obtained by taking the address of a label placed just after the SVC:

    after_svc 0x4000209c
    ELR 0x4000209C

Both QEMU and gem5 are able to trace interrupts in addition to instructions, and it is instructive to enable both and have a look at the traces.

With QEMU -d tracing:

    ./run \
      --arch aarch64 \
      --baremetal baremetal/arch/aarch64/svc.c \
      -- -d in_asm,int \
    ;

the output at 8f73910dd1fc1fa6dc6904ae406b7598cdcd96d7 contains:

    ----------------
    IN: main
    0x40002098:  d41579a1  svc      #0xabcd

    Taking exception 2 [SVC]
    ...from EL1 to EL1
    ...with ESR 0x15/0x5600abcd
    ...with ELR 0x4000209c
    ...to EL1 PC 0x40000a00 PSTATE 0x3c5
    ----------------
    IN:
    0x40000a00:  14000225  b        #0x40001294

    ----------------
    IN:
    0x40001294:  a9bf7bfd  stp      x29, x30, [sp, #-0x10]!
    0x40001298:  a9bf73fb  stp      x27, x28, [sp, #-0x10]!
    0x4000129c:  a9bf6bf9  stp      x25, x26, [sp, #-0x10]!
    0x400012a0:  a9bf63f7  stp      x23, x24, [sp, #-0x10]!
    0x400012a4:  a9bf5bf5  stp      x21, x22, [sp, #-0x10]!
    0x400012a8:  a9bf53f3  stp      x19, x20, [sp, #-0x10]!
    0x400012ac:  a9bf4bf1  stp      x17, x18, [sp, #-0x10]!
    0x400012b0:  a9bf43ef  stp      x15, x16, [sp, #-0x10]!
    0x400012b4:  a9bf3bed  stp      x13, x14, [sp, #-0x10]!
    0x400012b8:  a9bf33eb  stp      x11, x12, [sp, #-0x10]!
    0x400012bc:  a9bf2be9  stp      x9, x10, [sp, #-0x10]!
    0x400012c0:  a9bf23e7  stp      x7, x8, [sp, #-0x10]!
    0x400012c4:  a9bf1be5  stp      x5, x6, [sp, #-0x10]!
    0x400012c8:  a9bf13e3  stp      x3, x4, [sp, #-0x10]!
    0x400012cc:  a9bf0be1  stp      x1, x2, [sp, #-0x10]!
    0x400012d0:  d5384015  mrs      x21, spsr_el1
    0x400012d4:  a9bf03f5  stp      x21, x0, [sp, #-0x10]!
    0x400012d8:  d5384035  mrs      x21, elr_el1
    0x400012dc:  a9bf57ff  stp      xzr, x21, [sp, #-0x10]!
    0x400012e0:  d2800235  movz     x21, #0x11
    0x400012e4:  d5385216  mrs      x22, esr_el1
    0x400012e8:  a9bf5bf5  stp      x21, x22, [sp, #-0x10]!
    0x400012ec:  910003f5  mov      x21, sp
    0x400012f0:  910482b5  add      x21, x21, #0x120
    0x400012f4:  f9000bf5  str      x21, [sp, #0x10]
    0x400012f8:  910003e0  mov      x0, sp
    0x400012fc:  9400023f  bl       #0x40001bf8

    ----------------
    IN: lkmc_vector_trap_handler
    0x40001bf8:  a9bd7bfd  stp      x29, x30, [sp, #-0x30]!

And with gem5 tracing:

    ./run \
      --arch aarch64 \
      --baremetal baremetal/arch/aarch64/svc_asm.S \
      --trace ExecAll,Faults \
      --trace-stdout \
    ;

the output contains:

       4000: system.cpu A0 T0 : @main+8    :   svc   #0x0               : IntAlu :   flags=(IsSerializeAfter|IsNonSpeculative|IsSyscall)
       4000: Supervisor Call: Invoking Fault (AArch64 target EL):Supervisor Call cpsr:0x3c5 PC:0x80000808 elr:0x8000080c newVec: 0x80001200
       4500: system.cpu A0 T0 : @vector_table+512    :   b   <_curr_el_spx_sync>  : IntAlu :   flags=(IsControl|IsDirectControl|IsUncondControl)

So we see in both cases that the:

- SVC is done

- an exception happens, and the PC jumps to address 0x40000a00. From our custom terminal prints further on, we see that this equals `VBAR_EL1 + 0x200`.

  According to the format of the ARMv8 exception vector table format, we see that the `+ 0x200` means that we are jumping in the Current EL with SPx.

  This can also be deduced from the message `exc_type is LKMC_VECTOR_SYNC_SPX`: we just manually store a different integer for every exception vector type in our handler code to be able to tell what happened.

  This is the one used because we are jumping from EL1 to EL1.

  We set VBAR_EL1 to that address ourselves in the bootloader.

- at 0x40000a00 a `b #0x40001294` is done and then at 0x40001294 boilerplate preparation is done for lkmc_vector_trap_handler starting with several STP instructions.

  We have coded both of those in our vector table macro madness. As of LKMC 8f73910dd1fc1fa6dc6904ae406b7598cdcd96d7, both come from lkmc/aarch64.h:

  - `b #0x40001294` comes from: `LKMC_VECTOR_ENTRY`

  - the STP come from: `LKMC_VECTOR_BUILD_TRAPFRAME`

    We jump immediately from inside `LKMC_VECTOR_ENTRY` to `LKMC_VECTOR_BUILD_TRAPFRAME` because we can only use 0x80 bytes of instructions for each one before reaching the next handler, so we might as well get it over with by jumping into a memory region without those constraints.

    TODO: why doesn’t QEMU show our nice symbol names? gem5 shows them fine, and `nm` says they are there!

        0000000040000800 T lkmc_vector_table
        0000000040001294 T lkmc_vector_build_trapframe_curr_el_spx_sync

The exception return happens at the end of `lkmc_vector_trap_handler`:

    ----------------
    IN: lkmc_vector_trap_handler
    0x40002000:  d503201f  nop
    0x40002004:  a8c37bfd  ldp      x29, x30, [sp], #0x30
    0x40002008:  d65f03c0  ret

    ----------------
    IN:
    0x40001300:  910043ff  add      sp, sp, #0x10
    0x40001304:  a8c15bf5  ldp      x21, x22, [sp], #0x10
    0x40001308:  d5184036  msr      elr_el1, x22

    ----------------
    IN:
    0x4000130c:  a8c103f5  ldp      x21, x0, [sp], #0x10
    0x40001310:  d5184015  msr      spsr_el1, x21

    ----------------
    IN:
    0x40001314:  a8c10be1  ldp      x1, x2, [sp], #0x10
    0x40001318:  a8c113e3  ldp      x3, x4, [sp], #0x10
    0x4000131c:  a8c11be5  ldp      x5, x6, [sp], #0x10
    0x40001320:  a8c123e7  ldp      x7, x8, [sp], #0x10
    0x40001324:  a8c12be9  ldp      x9, x10, [sp], #0x10
    0x40001328:  a8c133eb  ldp      x11, x12, [sp], #0x10
    0x4000132c:  a8c13bed  ldp      x13, x14, [sp], #0x10
    0x40001330:  a8c143ef  ldp      x15, x16, [sp], #0x10
    0x40001334:  a8c14bf1  ldp      x17, x18, [sp], #0x10
    0x40001338:  a8c153f3  ldp      x19, x20, [sp], #0x10
    0x4000133c:  a8c15bf5  ldp      x21, x22, [sp], #0x10
    0x40001340:  a8c163f7  ldp      x23, x24, [sp], #0x10
    0x40001344:  a8c16bf9  ldp      x25, x26, [sp], #0x10
    0x40001348:  a8c173fb  ldp      x27, x28, [sp], #0x10
    0x4000134c:  a8c17bfd  ldp      x29, x30, [sp], #0x10
    0x40001350:  d69f03e0  eret

    Exception return from AArch64 EL1 to AArch64 EL1 PC 0x4000209c
    ----------------
    IN: main
    0x4000209c:  d0000040  adrp     x0, #0x4000c000

which does an `eret` and jumps back to 0x4000209c, which is 4 bytes and therefore one instruction after where SVC was taken at 0x40002098.

On the terminal output, we observe the initial values of:

- DAIF: 0x3c0, i.e. 4 bits (6 to 9) set to 1, which means that exceptions are masked for each exception type: Synchronous, System error, IRQ and FIQ.

  This reset value is defined by ARMv8 architecture reference manual C5.2.2 "DAIF, Interrupt Mask Bits".

- SPSel: 0x1, which means: use SPx instead of SP0.

  This reset value is defined by ARMv8 architecture reference manual C5.2.16 "SPSel, Stack Pointer Select".

- VBAR_EL1: 0x0 holds the base address of the vector table

  This reset value is defined UNKNOWN by ARMv8 architecture reference manual D10.2.116 "VBAR_EL1, Vector Base Address Register (EL1)", so we must set it to something ourselves to have greater portability.

Bibliography:

- https://github.com/torvalds/linux/blob/v4.20/arch/arm64/kernel/entry.S#L430 this is where the kernel defines the vector table

- https://github.com/dwelch67/qemu_arm_samples/tree/07162ba087111e0df3f44fd857d1b4e82458a56d/swi01

- https://github.com/NienfengYao/armv8-bare-metal/blob/572c6f95880e70aa92fe9fed4b8ad7697082a764/vector.S#L168

- https://stackoverflow.com/questions/24162109/arm-assembly-code-and-svc-numbering/57064062#57064062

- https://stackoverflow.com/questions/44991264/armv8-exception-vectors-and-handling

- ARMv8 exception vector table format

- ARM ESR register

- ARM ELR register