Home > Software design >  Rust assembly: how do I indicate that I need the value of the SP?
Rust assembly: how do I indicate that I need the value of the SP?

Time:11-28

I'm messing around with the asm! macro on an embedded ARM (Thumb) target. I have an interrupt service routine that is designed to get the number that the svc instruction was called with:

#[cortex_m_rt::exception]
unsafe fn SVCall() {
    let mut svc_num: u8;

    asm!(
        "ldr {0}, [sp, #40]",   // read the PC that was saved before this interrupt happened
        "movs {1}, #2",         // store 2 in a reg
        "subs {0}, {1}",        // subtract 2 from that PC we recovered
        "ldrb {2}, [{0}]",      // read the byte at that position
        out (reg) _,
        out (reg) _,
        lateout (reg) svc_num
    );

    defmt::info!("svcall #{}", svc_num);
}

When I disassemble the resulting code compiled with opt-level = 2 (this is important, I get completely different results with opt-level = 0), I get the following:

08000502 <SVCall>:
 8000502:       b580            push    {r7, lr}
 8000504:       466f            mov     r7, sp
 8000506:       b082            sub     sp, #8
 8000508:       980a            ldr     r0, [sp, #40]   ; 0x28
 800050a:       2102            movs    r1, #2
 800050c:       1a40            subs    r0, r0, r1
 800050e:       7802            ldrb    r2, [r0, #0]
 8000510:       f807 2c05       strb.w  r2, [r7, #-5]
 8000514:       f000 fb04       bl      8000b20 <_defmt_acquire>
 8000518:       f240 000e       movw    r0, #14
 800051c:       f2c0 0000       movt    r0, #0
 8000520:       f000 fb70       bl      8000c04 <_ZN5defmt6export9make_istr17h6ffa41eb00995773E>
 8000524:       f8ad 0004       strh.w  r0, [sp, #4]
 8000528:       a801            add     r0, sp, #4
 800052a:       f000 fba4       bl      8000c76 <_ZN5defmt6export6header17h9dd906a13f87833fE>
 800052e:       f240 0002       movw    r0, #2
 8000532:       f2c0 0000       movt    r0, #0
 8000536:       f000 fb65       bl      8000c04 <_ZN5defmt6export9make_istr17h6ffa41eb00995773E>
 800053a:       f827 0c02       strh.w  r0, [r7, #-2]
 800053e:       1eb8            subs    r0, r7, #2
 8000540:       f000 fb61       bl      8000c06 <_ZN5defmt6export4istr17hddd45161235dee63E>
 8000544:       1f78            subs    r0, r7, #5
 8000546:       f000 fbb3       bl      8000cb0 <_ZN5defmt6export8integers2i817h6232ecd7ea5eb90dE>
 800054a:       f000 faeb       bl      8000b24 <_defmt_release>
 800054e:       b002            add     sp, #8
 8000550:       bd80            pop     {r7, pc}

My calculations indicate that I should only have to use an offset of 32 in my ldr instruction, but I am having to compensate for the sub sp, #8 instruction that is being inserted before my code.

My two questions are:

  • Is there a way to indicate to Rust that I do not want this instruction to be inserted (or at least, that it must come after my asm! instructions), because I need to read the value of the sp register?
  • Can I assume that sp will always be copied to r7 and use r7 as a stack frame base pointer?

CodePudding user response:

... that I do not want this instruction to be inserted ...

This would not help you at all:

The next compiler version might use push {r6, r7, lr} instead of push {r7, lr} and the information you are interested in is located at sp 44 instead of sp 40.

... Can I assume that sp will always be copied to r7 ...

Even if this was the case, the problem with the push {r6, r7, lr} could not be solved with this assumption.

// read the PC that was saved before this interrupt happened

To do such things, there is no other way but writing the whole function in assembly and calling the high-level language part (e.g. Rust, C, C ...) from the assembly code.

There is no way of doing this using inline assembly within a high-level language function. At least none that is guaranteed to be working with a newer compiler version.

CodePudding user response:

You can use naked functions:

#![no_std]
#![feature(asm, naked_functions)]

#[naked]
pub unsafe fn SVCall() {
    let mut svc_num: u8;

    asm!(
        "ldr {0}, [sp, #40]",   // read the PC that was saved before this interrupt happened
        "movs {1}, #2",         // store 2 in a reg
        "subs {0}, {1}",        // subtract 2 from that PC we recovered
        "ldrb {2}, [{0}]",      // read the byte at that position
        out (reg) _,
        out (reg) _,
        lateout (reg) svc_num
    );
    other_func(svc_num);
}

extern "Rust" {
    fn other_func(svc_num: u8);
}

compiles to:

example::SVCall:
        ldr     r1, [sp, #40]
        movs    r2, #2
        subs    r1, r1, r2
        ldrb    r0, [r1]
        b       other_func

Godbolt

  • Related