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
指令。
我的两个问题是:
- 有没有办法向 Rust 表明我不想插入这条指令(或者至少,它必须在我的
asm!
指令之后),因为我需要读取的值sp
寄存器?
- 我可以假设
sp
将始终被复制到 r7
并使用 r7
作为堆栈帧基指针吗?
... 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 的函数。
我正在摆弄嵌入式 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
指令。
我的两个问题是:
- 有没有办法向 Rust 表明我不想插入这条指令(或者至少,它必须在我的
asm!
指令之后),因为我需要读取的值sp
寄存器? - 我可以假设
sp
将始终被复制到r7
并使用r7
作为堆栈帧基指针吗?
... 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 tor7
...
即使是这样,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 的函数。