Rust 程序集:如何表明我需要 SP 的值?

Rust assembly: how do I indicate that I need the value of the SP?

我正在摆弄嵌入式 ARM (Thumb) 目标上的 asm! 宏。我有一个中断服务例程,旨在获取调用 svc 指令的编号:

#[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);
}

当我反汇编用 opt-level = 2 编译的结果代码时(这很重要,我用 opt-level = 0 得到完全不同的结果),我得到以下内容:

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}

我的计算表明我只需要在我的 ldr 指令中使用偏移量 32,但我必须补偿在我的代码之前插入的 sub sp, #8 指令。

我的两个问题是:

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

这对你一点帮助都没有:

下一个编译器版本可能会使用 push {r6, r7, lr} 而不是 push {r7, lr},您感兴趣的信息位于 sp+44 而不是 sp+40

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

即使是这样,push {r6, r7, lr} 的问题也无法用这个假设来解决。

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

要做到这样,除了把整个函数写成汇编,再从汇编代码中调用高级语言部分(如Rust、C、C++...),别无他法

在高级语言函数中使用内联汇编无法做到这一点。至少 none 保证可以使用较新的编译器版本。

您可以使用 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
}

编辑:感谢 Peter Cordes 指出我之前的回答有很多不奏效的地方。本质上,裸函数应该包含一个内联汇编块,并且您只能在汇编中调用具有定义的 ABI 的函数。