如何在 ARM926EJ-S 上实现 SVC 处理器?

How to implement SVC handler on ARM926EJ-S?

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

当我尝试对我的内核实施 syscall 时,问题就来了。思路很简单:通过SVCSWI)指令实现系统调用。所以应用程序在用户模式下工作,为了调用内核函数,它们执行 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 = .;
}

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

问题不在于代码,而在于链接描述文件。正如您在链接描述文件中所见,我已将向量 table 置于 0x10000,但它应置于 0x0。所以SVC没有被正确处理,因为handler放错了地方

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

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

非常裸机的例子...

strap.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 

检查,assembledisassemble

.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 是程序显示的 pc,使用 -O 二进制版本加载该命令行,加载到 0x10000 并在那里执行。如果您要有一个 swi 事件,那么您将从 ldr r2 指令开始执行并在您的代码中登陆其余处理程序。

(顺便说一句,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 处加载并 运行s,这是它​​被链接的目的,但不需要设置以在 0x00000000 处退出重置 and/or 这是链接器问题对于坏的 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。

strap.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

随心所欲。但你也可以这样做

strap.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
}

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

00010010 <reset_addr>:
   10010:   0001001c

00010014 <hang_addr>:
   10014:   00010048

00010018 <swi_handler_addr>:
   10018:   0001004c

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

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