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 thesp
register? - Can I assume that
sp
will always be copied tor7
and user7
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 tor7
...
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