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:
asm!
instructions), because I need to read the value of the sp
register?sp
will always be copied to r7
and use r7
as a stack frame base pointer?You can use naked functions:
#![no_std]
#![feature(asm, naked_functions)]
#[naked]
#[export_name = "SVCall"]
pub unsafe extern "C" fn SVCall() {
asm!(
"ldr r0, [sp, #40]", // read the PC that was saved before this interrupt happened
"ldrb r0, [r0, #-2]", // read the byte at PC - 2
"b other_func", // call other_func with that byte as first argument
options(noreturn)
);
}
pub extern "C" fn other_func(svc_num: u8) {
// do something
}
Edit: Thanks to Peter Cordes for pointing out the many ways my previous answer did not work. Essentially naked functions should contain one inline-assembly block and you can only call functions with a defined ABI from it, in assembly.