【问题标题】:How to implement SVC handler on ARM926EJ-S?如何在 ARM926EJ-S 上实现 SVC 处理程序?
【发布时间】:2020-01-03 17:13:11
【问题描述】:

我正在为基于 ARM 的设备编写一个业余操作系统,目前正试图使其在 QEMU 的 versatilepb (ARM926EJ-S) 中运行。

当我尝试在我的内核中实现syscalls 时,问题就出现了。这个想法很简单:通过SVC (SWI) 指令实现系统调用。所以应用程序在用户模式下工作,为了调用内核函数,它们执行SVC <code> 指令,因此 ARM 处理器切换到超级用户模式并调用适当的SVC 处理程序。

但问题是当我调用__asm__("SVC #0x08");时,设备只是重置并调用RESET_HANDLER,所以看起来模拟器刚刚重新启动。

我已经花了几个小时来找出问题所在,但仍然不知道。

这是ivt.s的代码(带有处理程序的初始代码):

.global __RESET

__RESET:
    B RESET_HANDLER /* Reset */
    B . /* Undefined */
    B SWI_HANDLER   /* SWI */
    B . /* Prefetch Abort */
    B . /* Data Abort */
    B . /* reserved */
    B . /* IRQ */
    B . /* FIQ */

RESET_HANDLER:
    MSR CPSR_c, 0x13 /* Supervisor mode */
    LDR SP, =stack_top
    MSR CPSR_c, 0x10 /* User mode */
    LDR SP, =usr_stack_top
    BL  usermode_function
    B   .

SWI_HANDLER:
    PUSH    {LR}
    BL      syscall
    POP     {LR}
    MOVS    PC, LR

这就是我制作syscall的方法:

void usermode_function() {
    __asm__("SVC #0x00"); // Make syscall
}

和syscall 实现:

void syscall() {
    // NEVER CALLED
    __asm__("PUSH {r0-r7}");
    __asm__("POP {r0-r7}");
}

但SWI_HANDLER 下的代码甚至从未被调用过。

我什至不知道如何问这个问题,因为我似乎错过了一些非常基本的信息。

那么可能是什么问题?我应该提供哪些信息才能让您能够帮助我?

这里也是链接描述文件:

ENTRY(__RESET)
SECTIONS
{
    . = 0x10000;
    .ivt .  : { ivt.o(.text) }
    .text   : { *(.text) }
    .data   : { *(.data) }
    .bss    : { *(.bss COMMON) }
    . = ALIGN(8);
    . = . + 0x1000; /* 4KB of stack memory */
    stack_top = .;
    . = . + 0x100;
    usr_stack_top = .;
}

【问题讨论】:

  • 可能不相关,但您的 malloc 中的代码已损坏。对于初学者,它应该是一个单一的 asm 块。在这两种情况下,r 约束也很可能会选择与您的使用冲突的寄存器。您的syscall 中的r0 处理也已损坏。只需在那里使用正常的调用约定。
  • 例如r 约束可能会选择r7 作为输入,编译器可能会在MOV r7, #0x01 之前加载r7,因此您会覆盖大小参数。同样对于结果,编译器可能会选择r0-r7 中的任何一个,但您随后会选择pop 所有这些,从而破坏结果。这对实际的svc 呼叫重置没有影响。
  • 您将异常表复制到地址 0 某处?通常 qemu 加载在 0x80000 或者是什么?
  • 那个链接器脚本将 ivt 放在 0x10000 而不是 0x0 这是 @old_timer 已经明确要求的。
  • 这是错误的。它必须在0x0,因为那是cpu 寻找它的地方。如果您坚持使用原始二进制文件,则必须在运行时将 ivt 复制到地址 0。否则,您可以将链接描述文件编辑为地址 0 并使用 ELF 映像,因为 qemu 知道要正确加载它。

标签: c assembly arm kernel-mode usermode


【解决方案1】:

非常感谢@Jester 和@old_timer,问题解决了。

问题不在于代码,而在于链接描述文件。正如您在链接器脚本中看到的那样,我已将向量表放在0x10000,但它应该放在0x0。所以SVC没有被正确处理,因为处理程序被放置在错误的地方。

当我更改ld 脚本中的基地址并尝试将固件加载为ELF 时,一切都开始正常工作。

【讨论】:

    【解决方案2】:

    你以一种方式解决了它,但我仍然会写下我的答案。

    非常裸露的金属示例...

    皮带.s

    .globl _start
    _start:
        b reset
        b hang
        b swi_handler
        b hang
    
    reset:
        msr cpsr_c, 0x13 /* Supervisor mode */
        mov sp,#0x10000
        msr cpsr_c, 0x10 /* User mode */
        mov sp,#0x9000
        bl  notmain
    hang:
        b hang
    
    swi_handler:
        push {r0,r1,r2,r3,r4,lr}
        pop  {r0,r1,r2,r3,r4,lr}
        movs pc,lr
    
    .globl GETPC
    GETPC:
        mov r0,pc
        bx lr
    
    .globl PUT32
    PUT32:
        str r1,[r0]
        bx lr
    
    .globl GET32
    GET32:
        ldr r0,[r0]
        bx lr
    

    notmain.c

    void PUT32 ( unsigned int, unsigned int );
    unsigned int GET32 ( unsigned int );
    unsigned int GETPC ( void );
    
    #define UART_BASE 0x101F1000
    #define UARTDR    (UART_BASE+0x000)
    
    static void uart_send ( unsigned int x )
    {
        PUT32(UARTDR,x);
    }
    
    static void hexstrings ( unsigned int d )
    {
        unsigned int rb;
        unsigned int rc;
    
        rb=32;
        while(1)
        {
            rb-=4;
            rc=(d>>rb)&0xF;
            if(rc>9) rc+=0x37; else rc+=0x30;
            uart_send(rc);
            if(rb==0) break;
        }
        uart_send(0x20);
    }
    
    static void hexstring ( unsigned int d )
    {
        hexstrings(d);
        uart_send(0x0D);
        uart_send(0x0A);
    }
    
    int notmain ( void )
    {
        unsigned int ra;
    
        hexstring(0x12345678);
        hexstring(GETPC());
        for(ra=0;ra<0x20;ra+=4)
        {
            hexstrings(ra);
            hexstring(GET32(ra));
        }
    
        return(0);
    }
    

    内存映射

    MEMORY
    {
        ram  : ORIGIN = 0x00010000, LENGTH = 32K
    }
    SECTIONS
    {
       .text : { *(.text*) } > ram
       .bss  : { *(.text*) } > ram
    }
    

    构建

    arm-linux-gnueabi-as --warn --fatal-warnings -march=armv5t strap.s -o strap.o
    arm-linux-gnueabi-gcc -c -Wall -O2 -nostdlib -nostartfiles -ffreestanding -march=armv5t notmain.c -o notmain.o
    arm-linux-gnueabi-ld strap.o notmain.o -T memmap -o notmain.elf
    arm-linux-gnueabi-objdump -D notmain.elf > notmain.list
    arm-linux-gnueabi-objcopy notmain.elf -O binary notmain.bin
    

    执行

    qemu-system-arm -M versatilepb -m 128M -nographic -kernel notmain.bin
    

    输出

    12345678 
    0001003C 
    00000000 E3A00000 
    00000004 E59F1004 
    00000008 E59F2004 
    0000000C E59FF004 
    00000010 00000183 
    00000014 00000100 
    00000018 00010000 
    0000001C 00000000 
    

    检查,组装拆卸

    .word 0xE3A00000
    .word 0xE59F1004
    .word 0xE59F2004
    .word 0xE59FF004
    .word 0x00000183
    .word 0x00000100
    .word 0x00010000
    .word 0x00000000
    
       0:   e3a00000    mov r0, #0
       4:   e59f1004    ldr r1, [pc, #4]    ; 10 <.text+0x10>
       8:   e59f2004    ldr r2, [pc, #4]    ; 14 <.text+0x14>
       c:   e59ff004    ldr pc, [pc, #4]    ; 18 <.text+0x18>
      10:   00000183    andeq   r0, r0, r3, lsl #3
      14:   00000100    andeq   r0, r0, r0, lsl #2
      18:   00010000    andeq   r0, r1, r0
      1c:   00000000    andeq   r0, r0, r0
    

    所以你可以看到他们基本上是在启动一个 Linux 内核,ATAGS/dtb 可能在 0x100 的 ram 中。他们跳到0x10000。 0001003C 是程序显示的使用 -O 二进制版本加载该命令行的 pc,在 0x10000 加载并在那里执行。如果你有一个 swi 事件,那么你会从 ldr r2 指令开始执行,然后在你的代码中使用 rest 处理程序。

    (请注意,qemu 不能正确模拟 uart,至少就我所发现的而言,因此您不必初始化它们,您不必等待 tx 缓冲区为空,您只需阻塞字节进入 tx 缓冲区,然后它们出来)。

    如果你在不改变链接描述文件的情况下运行精灵

    qemu-system-arm -M versatilepb -m 128M -nographic -kernel notmain.elf
    
    12345678 
    0001003C 
    00000000 00000000 
    00000004 00000000 
    00000008 00000000 
    0000000C 00000000 
    00000010 00000000 
    00000014 00000000 
    00000018 00000000 
    0000001C 00000000 
    

    有趣的是,它在 0x10000 加载并运行,这是它被链接的目的,但不会因为在 0x00000000 处重置而费心设置和/或这是导致错误 elf 文件的链接器问题,它用零填充这是

      1c:   00000000    andeq   r0, r0, r0
    

    所以它可能从 0x00000000 执行到 0x10000 并运行到我们的代码中。

    如果我们更改链接描述文件

    ram  : ORIGIN = 0x00000000, LENGTH = 32K
    

    运行精灵而不是垃圾箱

    qemu-system-arm -M versatilepb -m 128M -nographic -kernel notmain.elf
    
    12345678 
    0000003C 
    00000000 EA000002 
    00000004 EA000006 
    00000008 EA000006 
    0000000C EA000004 
    00000010 E321F013 
    00000014 E3A0D801 
    00000018 E321F010 
    0000001C E3A0DA09 
    

    正如预期的那样。

    现在是 swi。

    皮带.s

    .globl _start
    _start:
        b reset
        b hang
        b swi_handler
        b hang
    
    reset:
        msr cpsr_c, 0x13 /* Supervisor mode */
        mov sp,#0x10000
        msr cpsr_c, 0x10 /* User mode */
        mov sp,#0x9000
        bl  notmain
    hang:
        b hang
    
    swi_handler:
        push {r0,r1,r2,r3,r4,lr}
        bl handler
        pop  {r0,r1,r2,r3,r4,lr}
        movs pc,lr
    
    .globl GETPC
    GETPC:
        mov r0,pc
        bx lr
    
    .globl PUT32
    PUT32:
        str r1,[r0]
        bx lr
    
    .globl GET32
    GET32:
        ldr r0,[r0]
        bx lr
    
    .globl do_swi
    do_swi:
        svc #0x08
        bx lr
    

    notmain.c

    void PUT32 ( unsigned int, unsigned int );
    unsigned int GET32 ( unsigned int );
    unsigned int GETPC ( void );
    void do_swi ( void );
    
    #define UART_BASE 0x101F1000
    #define UARTDR    (UART_BASE+0x000)
    
    static void uart_send ( unsigned int x )
    {
        PUT32(UARTDR,x);
    }
    
    static void hexstring ( unsigned int d )
    {
        unsigned int rb;
        unsigned int rc;
    
        rb=32;
        while(1)
        {
            rb-=4;
            rc=(d>>rb)&0xF;
            if(rc>9) rc+=0x37; else rc+=0x30;
            uart_send(rc);
            if(rb==0) break;
        }
        uart_send(0x0D);
        uart_send(0x0A);
    }
    
    void handler ( void )
    {
        hexstring(0x11223344);
    }
    
    int notmain ( void )
    {
        hexstring(0x12345678);
        do_swi();
        hexstring(0x12345678);
        return(0);
    }
    

    内存映射

    MEMORY
    {
        ram  : ORIGIN = 0x00000000, LENGTH = 32K
    }
    SECTIONS
    {
       .text : { *(.text*) } > ram
       .bss  : { *(.text*) } > ram
    }
    

    运行精灵,输出为

    12345678
    11223344
    12345678
    

    根据需要。但你也可以这样做

    皮带.s

    .globl _start
    _start:
        ldr pc,reset_addr
        ldr pc,hang_addr
        ldr pc,swi_handler_addr
        ldr pc,hang_addr
    reset_addr:         .word reset
    hang_addr:          .word hang
    swi_handler_addr:   .word swi_handler
    
    reset:
        mov r0,#0x10000
        mov r1,#0x00000
        ldmia r0!,{r2,r3,r4,r5}
        stmia r1!,{r2,r3,r4,r5}
        ldmia r0!,{r2,r3,r4,r5}
        stmia r1!,{r2,r3,r4,r5}
    
        msr cpsr_c, 0x13 /* Supervisor mode */
        mov sp,#0x10000
        msr cpsr_c, 0x10 /* User mode */
        mov sp,#0x9000
        bl  notmain
    hang:
        b hang
    
    swi_handler:
        push {r0,r1,r2,r3,r4,lr}
        bl handler
        pop  {r0,r1,r2,r3,r4,lr}
        movs pc,lr
    
    .globl GETPC
    GETPC:
        mov r0,pc
        bx lr
    
    .globl PUT32
    PUT32:
        str r1,[r0]
        bx lr
    
    .globl GET32
    GET32:
        ldr r0,[r0]
        bx lr
    
    .globl do_swi
    do_swi:
        svc #0x08
        bx lr
    

    notmain.c

    void PUT32 ( unsigned int, unsigned int );
    unsigned int GET32 ( unsigned int );
    unsigned int GETPC ( void );
    void do_swi ( void );
    
    #define UART_BASE 0x101F1000
    #define UARTDR    (UART_BASE+0x000)
    
    static void uart_send ( unsigned int x )
    {
        PUT32(UARTDR,x);
    }
    
    static void hexstring ( unsigned int d )
    {
        unsigned int rb;
        unsigned int rc;
    
        rb=32;
        while(1)
        {
            rb-=4;
            rc=(d>>rb)&0xF;
            if(rc>9) rc+=0x37; else rc+=0x30;
            uart_send(rc);
            if(rb==0) break;
        }
        uart_send(0x0D);
        uart_send(0x0A);
    }
    
    void handler ( void )
    {
        hexstring(0x11223344);
    }
    
    int notmain ( void )
    {
        unsigned int ra;
    
        hexstring(0x12345678);
        for(ra=0x10000;ra<0x10020;ra+=4) hexstring(GET32(ra));
        for(ra=0x00000;ra<0x00020;ra+=4) hexstring(GET32(ra));
        do_swi();
        hexstring(0x12345678);
        return(0);
    }
    

    内存映射

    MEMORY
    {
        ram  : ORIGIN = 0x00010000, LENGTH = 32K
    }
    SECTIONS
    {
       .text : { *(.text*) } > ram
       .bss  : { *(.text*) } > ram
    }
    

    现在精灵和二进制图像版本都可以使用。我让工具链为我完成工作:

    00010010 <reset_addr>:
       10010:   0001001c
    
    00010014 <hang_addr>:
       10014:   00010048
    
    00010018 <swi_handler_addr>:
       10018:   0001004c
    

    ldr pc 与位置无关。我复制四个条目加上四个(以及三个)地址,以便 0x00000 与 0x10000 匹配,现在异常表(它不是向量表 btw)可以工作。

    对于较新的 arm 处理器,您可以改为将 VTOR 设置为 0x10000,它将使用二进制文件中内置的那个,无需复制。或者当你解决了从 0x00000 构建并运行你的程序时,你就可以了。我想展示替代方案以及如何弄清楚(通过作弊,你必须喜欢 qemu 中的 uart)qemu 正在做什么以及它在哪里加载,而无需使用调试器。

    【讨论】:

      猜你喜欢
      • 2013-12-12
      • 1970-01-01
      • 1970-01-01
      • 2015-02-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-27
      • 1970-01-01
      相关资源
      最近更新 更多