Per*_*age 5 hardware microcontroller assembly arm
我想知道在基于 ARM 的微控制器上编写 SVC 调用的正确方法。
到目前为止,我的理解是 ARM 有一个异常向量表,这意味着任何程序中的第一条指令都必须分支到适当的处理程序:
RESET ;Handles reset
UNDEFINED ;Undefined instructions
SVC BL SVC_Entry
PRE_ABORT ;Prefetch abort
DAT_ABORT ;Data abort
Run Code Online (Sandbox Code Playgroud)
然后,每次运行 SVC 指令时,模式都会切换到主管,SVC 提供的数字存储在 R0 中,程序将分支到适当的处理程序:
;== Handling SVC calls ========================================================
Max_SVC EQU 1
SVC_Entry CMP R0, #Max_SVC ;Check upper limit
BHI SVC_end ;Does nothing if unknown
ADD R0, PC, R0, LSL #2 ;Calculate table address
LDR PC, [R0, #0]
Jump_table DEFW SVC_0 ;Halt
DEFW SVC_1 ;Print string
;== SVC calls ================================================================
SVC_1 B SVC_end
SVC_end MOVS PC, LR ;Exiting
Run Code Online (Sandbox Code Playgroud)
所以,如果我们有这些说明:
ADR R1, string ;R1 points to the string
SVC 1 ;SVC_1 handles the printing
Run Code Online (Sandbox Code Playgroud)
程序必须切换到管理模式,将数字“1”存储在 R0 中,然后按照跳转表分支到 SVC_1,运行代码并切换回用户模式。
这样对吗?我做得对吗?
到目前为止,我遇到的问题是我的编译器对这一行说“操作员预期”:
SVC BL SVC_Entry
Run Code Online (Sandbox Code Playgroud)
在互联网上很难找到有关此主题的信息,我只想知道如何在 ARM 微控制器上正确使用 SVC 调用。
非常感谢。
编辑:底层处理器是时钟频率约为 240 MHz 的 ARM9。它存在于 AT91 微控制器中。它所在的实验室板已经过修改以满足我所在大学的需要。
使用定制程序通过串行端口将代码加载到板上。该程序还允许调试。
如上所述,不要使用BL跳转到SVC条目,使用B。首先有一个例程来确定SVC编号。(我的叫做 SVC_dispatcher)。你在哪所大学?我会尝试彻底解释这一点,不对你知道多少或不知道多少做任何假设。我输入了正确的术语,所以如果我的评论不清楚或者你想要更深入,你可以在谷歌上搜索更多信息。我不确定带有冒号的标记方法,我已经习惯了较旧的指令集。
祝你好运
SVC_dispatcher
PUSH {LR} ; always save your LR
LDR R14, [LR, #-4] ; its been stacked, so we can it (LR is R14)
; the link register is the line after the SVC instruction
; above, we load the instruction that is one before the
; link register (#-4 preindexed load) means instruction (SVC 1) into R14.
; Use bit clear to remove the mnemonic from the 32 bit instruction,
; leaving the data (1)
BIC R14, R14, #&FF000000
SVC_entry
CMP R14, #Max_SVC
BHI SVC_unknown
ADR R1, Jump_Table ; use this format, never add to the PC
LDR PC, [R1, R14, LSL #2]
; Beware: PC can be changed by IRQs **AT ANY TIME**
Jump_Table ; you know the drill here
DEFW SVC_0
DEFW SVC_1
SVC_0 B SVC_end
SVC_1 BL printString ; consider stacking registers that printString
B SVC_end ; will corrupt
SVC_end POP {LR} ; restore link register
MOV PC, LR
Run Code Online (Sandbox Code Playgroud)
只是补充 Yoker 上面的答案以进行澄清:
正如 Yoker 的答案示例所示,传统上,SVC 指令中的数字不存储在 R0 中(正如您在问题中所述),您应该从代码中检索它,直接从 svc 指令读取立即值。
为此,您需要获取指向返回地址之前指令的指针,例如,如果您有以下指令:
0x800010: ADR R1, string ;R1 points to the string
0x800014: SVC 1 ;SVC_1 handles the printing
0x800018: ADD R1, R2 ;The next instruction after the SVC where LR will point to
Run Code Online (Sandbox Code Playgroud)
返回地址为 LR=0x800018,然后我们可以检索 SVC 指令 LR-4=0x800014 的地址,读取该地址内容(即 SVC 1 指令)并只获取它的第一个字节(我们忽略 svc操作码并且仅获取立即值)。
因此,在 Yoker 的示例中,我们可以看到以下指令正是这样做的:
LDR R14, [LR, #-4]
BIC R14, R14, #&FF000000
Run Code Online (Sandbox Code Playgroud)
这是在皮质 M0 中使用 C 代码的另一个示例(拇指指令):
void SVC_Handler(void)
{
// Get stack pointer, assuming we the thread that generated
// the svc call was using the psp stack instead of msp
unsigned int *stack;
asm volatile ("MRS %0, psp\n\t" : "=rm" (stack) );
// Stack frame contains:
// r0, r1, r2, r3, r12, r14, the return address and xPSR
// - Stacked R0 = stack[0]
// - Stacked R1 = stack[1]
// - Stacked R2 = stack[2]
// - Stacked R3 = stack[3]
// - Stacked R12 = stack[4]
// - Stacked LR = stack[5]
// - Stacked PC = stack[6]
// - Stacked xPSR= stack[7]
// Thumb instructions have 2 bytes instead of 4, then get
// the first byte of the instruction right before the
// instruction pointed by the stacked PC
unsigned int svc_number = ((char *)svc_args[6])[-2];
switch(svc_number) {
case 0:
...
break;
case 1:
...
break;
}
Run Code Online (Sandbox Code Playgroud)