5

我想知道在基于 ARM 的微控制器上编写 SVC 调用的正确方法。

到目前为止,我的理解是 ARM 有一个异常向量表,这意味着任何程序中的第一条指令都必须分支到适当的处理程序:

RESET          ;Handles reset
UNDEFINED      ;Undefined instructions
SVC             BL            SVC_Entry 
PRE_ABORT      ;Prefetch abort
DAT_ABORT      ;Data abort

然后,每次运行 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

所以,如果我们有这些说明:

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

程序必须切换到主管模式,将数字“1”存储在 R0 中,然后按照跳转表分支到 SVC_1,运行代码并切换回用户模式。

这个对吗?我做对了吗?

到目前为止,我遇到的问题是我的编译器对这一行说“操作员预期”:

SVC             BL            SVC_Entry 

在 Internet 上很难找到有关此主题的信息,我只想知道如何在 ARM 微控制器上正确使用 SVC 调用。

非常感谢。

编辑:底层处理器是时钟频率约为 240 MHz 的 ARM9。这存在于 AT91 微控制器中。它所在的实验室板已经过修改,以适应我大学的需要。

代码通过串行端口使用定制程序加载到板上。该程序还允许调试。

4

2 回答 2

7

如前所述,不要使用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          
于 2014-04-30T21:30:31.430 回答
4

只是补充上面 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

这是在皮质 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;
    }
于 2016-03-13T17:51:34.443 回答