How to use the ARM ADR instruction in Rust?

Viewed 207

I'm writing an interrupt handler in Rust, and I want to use the ADR instruction to get the address of an array containing functions:

#[naked]
#[export_name = "SVCall"]
unsafe extern "C" fn svcall_isr() -> ! {
    asm!(
        "ldr  r0, [sp, #24]",   // read the PC that was saved before this interrupt happened
        "movs r1, #2",          // store 2 in a reg
        "subs r0, r1",          // subtract 2 from that PC we recovered
        "ldrb r0, [r0]",        // read the byte at that position

        "adr  r1, {0}",         // put address of svcall_table into r1
        "lsls r0, #2",          // multiply svc number by 4 to get byte offset into svcall_table
        "ldr  r2, [r1, r0]",    // load the pointer at offset in svcall_table
        "mov  pc, r2",          // branch into specified svcall routine

        sym SVCALL_TABLE,
        options(noreturn)
    );
}

pub static SVCALL_TABLE: [unsafe extern "C" fn() -> !; 3] = [svcall0_isr, svcall1_isr, svcall2_isr];

So that this interrupt handler can call svcall0_isr, svcall1_isr, or svcall2_isr (which are all unsafe extern "C" fns defined elsewhere in the file). However, this ADR instruction is not compiling correctly; this is the output of an objdump:

08000306 <SVCall>:
 8000306:       9806            ldr     r0, [sp, #24]
 8000308:       2102            movs    r1, #2
 800030a:       1a40            subs    r0, r0, r1
 800030c:       7800            ldrb    r0, [r0, #0]
 800030e:       f2af 010c       subw    r1, pc, #12
 8000312:       0080            lsls    r0, r0, #2
 8000314:       580a            ldr     r2, [r1, r0]
 8000316:       4697            mov     pc, r2
 8000318:       defe            udf     #254    ; 0xfe

The ADR instruction is being transformed into a SUBW, and the resulting address is not correct. I also can't seem to find SVCALL_TABLE using objdump, even if I add a #[no_mangle]. Why is this happening, and how do I get the correct address?

Edit: I notice in pure-assembly examples like the one in this question, the table is defined using lines that say .word. How do I do this in Rust?

1 Answers

I was able to fix this by using a LDR instruction instead:

#[naked]
#[export_name = "SVCall"]
unsafe extern "C" fn svcall_isr() -> ! {
    asm!(
        "ldr  r0, [sp, #24]",   // read the PC that was saved before this interrupt happened
        "movs r1, #2",          // store 2 in a reg
        "subs r0, r1",          // subtract 2 from that PC we recovered
        "ldrb r0, [r0]",        // read the byte at that position

        "ldr  r1, ={0}",         // put address of svcall_table into r1
        "lsls r0, #2",          // multiply svc number by 4 to get byte offset into svcall_table
        "ldr  r2, [r1, r0]",    // load the pointer at offset in svcall_table
        "mov  pc, r2",          // branch into specified svcall routine

        sym SVCALL_TABLE,
        options(noreturn)
    );
}

Which was resolved correctly in the generated assembly:

0800034c <SVCall>:
 800034c:   9806        ldr r0, [sp, #24]
 800034e:   2102        movs    r1, #2
 8000350:   1a40        subs    r0, r0, r1
 8000352:   7800        ldrb    r0, [r0, #0]
 8000354:   4902        ldr r1, [pc, #8]    ; (8000360 <SVCall+0x14>)
 8000356:   0080        lsls    r0, r0, #2
 8000358:   580a        ldr r2, [r1, r0]
 800035a:   4697        mov pc, r2
 800035c:   defe        udf #254    ; 0xfe
 800035e:   bf00        nop
 8000360:   08003aa0    .word   0x08003aa0

Which I verified by checking the symbol table, where I can see the SVCALL_TABLE located at 0x08003aa0:

08003aa0 l O .rodata 0000000c _ZN5tasks12SVCALL_TABLE17h255202af4e3eea15E

Related