编写自己的SVC调用ARM汇编

5
我希望您能够在基于ARM的微控制器上正确编写SVC调用。根据我目前的理解,ARM具有异常向量表,这意味着任何程序的第一条指令必须是分支到适当处理程序的指令:
RESET          ;Handles reset
UNDEFINED      ;Undefined instructions
SVC             BL            SVC_Entry 
PRE_ABORT      ;Prefetch abort
DAT_ABORT      ;Data abort

每次运行SVC指令时,模式会切换为supervisor,提供的数字将存储在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

如果我们有以下指令:

ADR R1, string       ;R1 points to the string
SVC 1                ;SVC_1 handles the printing

该程序需要切换到监管模式,将数字“1”存储在R0中,然后根据跳转表跳转到SVC_1,运行代码并切回用户模式。

这样正确吗?我操作得对吗?

到目前为止,我遇到的问题是编译器对这行代码报错:“预期操作符”。

SVC             BL            SVC_Entry 

这个话题在互联网上很难找到相关信息,我只是想知道如何在ARM微控制器上正确使用SVC调用。

非常感谢。

编辑:底层处理器是一个时钟频率大约为240 MHz的ARM9。它位于AT91微控制器中。实验板已经被修改以适应我的大学需要。

通过串口,使用自制程序将代码加载到板子上。该程序也允许调试。


你需要像这样的东西,SVC: B SVC_Entry。注意,冒号 : 使其成为标签。最好将其命名为 svc_vec,因此 svc_vec: b SVC_Entry。另一个问题是,如果使用 BL,将会破坏用户返回地址。您可以使用 R0 或任何您希望建立的约定;检查指令通常很慢。不要忘记设置监管者堆栈和模式。在更改它们之前,您可能希望保存一些用户寄存器;除非您在调用约定中定义了它。 - artless noise
传统上,SVC调用号被编码在操作码中(这是Acorn在RISCOS中的做法)。然而,ARM Linux和iOS都将其传递到寄存器中 - 从性能角度来看,在所有具有哈佛架构的ARM设备上都是有意义的,因为它避免了在只有4个字节可能被读取的情况下污染D-Cache的行以及相应的缓存行填充。 - marko
@artlessnoise,我已经按照你建议的命名方式尝试了,编译器显示:未找到助记符。 - Percentage
“Mnemonic not found” 意味着汇编器仍然认为您的标签是一条指令。您应该使用您现在拥有的确切代码更新您的问题。大多数您所勾画的内容似乎是正确的。实际上,在cortex-m上,像 dwelch 所示的Thumb中,向量表中并不需要标签。越多的真实代码越好。 - artless noise
我已经编辑了问题(在我看来,当这种情况发生时,用户似乎不会收到通知),并添加了一些有关硬件的细节。正如我告诉dwelch的那样,这是微控制器课程的一部分,我们需要学习上下文切换和监管呼叫。 - Percentage
显示剩余2条评论
2个回答

8
如上所述,不要使用BL跳转到SVC入口,请使用B。首先编写一个程序来确定SVC号码(我的称为SVC_dispatcher)。你在哪个大学?我会尽力解释得透彻些,不假设你知道多少或不知道多少。我已经放置了正确的术语,这样你就可以通过Google获得更多的信息,如果我的注释不清楚或你想要更深入了解。我对带有冒号的标记方法不确定,我习惯于旧的指令集。
祝好运。
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          

5

补充一下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

返回地址将为LR=0x800018,然后我们可以检索SVC指令LR-4=0x800014的地址,读取该地址内容(即SVC 1指令),并仅获取其第一个字节(我们忽略SVC操作码,只获取立即数值)。
因此,在Yoker的示例中,我们可以看到以下指令正好做到了这一点:
LDR     R14, [LR, #-4]
BIC     R14, R14, #&FF000000

下面是一个示例,使用C代码在Cortex M0中实现(使用Thumb指令):

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;
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接