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

huangapple go评论80阅读模式
英文:

How to implement SVC handler on ARM926EJ-S?

问题

我正在为基于ARM的设备编写一个业余操作系统,并且目前正在尝试在QEMU的versatilepb(ARM926EJ-S)上使其工作。

当我尝试实现系统调用到我的内核时出现问题。想法非常简单:通过SVCSWI)指令来实现系统调用。因此,应用程序在用户模式下运行,要调用内核函数,它们执行SVC <code>指令,因此ARM处理器切换到监管模式并调用适当的SVC处理程序。

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

我已经花了几个小时来弄清楚问题是什么,但仍然毫无头绪。

以下是ivt.s(具有处理程序的初始代码)的代码:

  1. .global __RESET
  2. __RESET:
  3. B RESET_HANDLER /* Reset */
  4. B . /* Undefined */
  5. B SWI_HANDLER /* SWI */
  6. B . /* Prefetch Abort */
  7. B . /* Data Abort */
  8. B . /* reserved */
  9. B . /* IRQ */
  10. B . /* FIQ */
  11. RESET_HANDLER:
  12. MSR CPSR_c, 0x13 /* Supervisor mode */
  13. LDR SP, =stack_top
  14. MSR CPSR_c, 0x10 /* User mode */
  15. LDR SP, =usr_stack_top
  16. BL usermode_function
  17. B .
  18. SWI_HANDLER:
  19. PUSH {LR}
  20. BL syscall
  21. POP {LR}
  22. MOVS PC, LR

这是我进行系统调用的方式:

  1. void usermode_function() {
  2. __asm__("SVC #0x00"); // 进行系统调用
  3. }

以及系统调用的实现:

  1. void syscall() {
  2. // 永远不会被调用
  3. __asm__("PUSH {r0-r7}");
  4. __asm__("POP {r0-r7}");
  5. }

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

我真的不知道如何提问这个问题,因为看起来我在我的头脑中遗漏了一些非常基本的信息。

那么问题可能出在哪里?我应该提供哪些信息才能帮助你?以下是链接脚本:

  1. ENTRY(__RESET)
  2. SECTIONS
  3. {
  4. . = 0x10000;
  5. .ivt . : { ivt.o(.text) }
  6. .text : { *(.text) }
  7. .data : { *(.data) }
  8. .bss : { *(.bss COMMON) }
  9. . = ALIGN(8);
  10. . = . + 0x1000; /* 4KB of stack memory */
  11. stack_top = .;
  12. . = . + 0x100;
  13. usr_stack_top = .;
  14. }

希望这些信息能帮助您解决问题。

英文:

I'm writing an amateur operating system for ARM-based devices and currently trying to make it working in QEMU's versatilepb (ARM926EJ-S).

The problem arrives when I try to implement syscalls to my kernel. The idea is pretty simple: to implement system calls via SVC (SWI) instruction. So applications work in user mode, and to call a kernel function, they do SVC &lt;code&gt; instruction, so ARM processor switches to supervisor mode and calls the appropriate SVC handler.

But the problem is that when I call __asm__(&quot;SVC #0x08&quot;);, the device just resets and calls RESET_HANDLER, so it looks like the emulator just reboots.

I spent a few hours already to figure out what is the problem, but still got no idea.

Here is the code of ivt.s (the initial code with handlers):

  1. .global __RESET
  2. __RESET:
  3. B RESET_HANDLER /* Reset */
  4. B . /* Undefined */
  5. B SWI_HANDLER /* SWI */
  6. B . /* Prefetch Abort */
  7. B . /* Data Abort */
  8. B . /* reserved */
  9. B . /* IRQ */
  10. B . /* FIQ */
  11. RESET_HANDLER:
  12. MSR CPSR_c, 0x13 /* Supervisor mode */
  13. LDR SP, =stack_top
  14. MSR CPSR_c, 0x10 /* User mode */
  15. LDR SP, =usr_stack_top
  16. BL usermode_function
  17. B .
  18. SWI_HANDLER:
  19. PUSH {LR}
  20. BL syscall
  21. POP {LR}
  22. MOVS PC, LR

This is how I make the syscall:

  1. void usermode_function() {
  2. __asm__(&quot;SVC #0x00&quot;); // Make syscall
  3. }

And syscall implementation:

  1. void syscall() {
  2. // NEVER CALLED
  3. __asm__(&quot;PUSH {r0-r7}&quot;);
  4. __asm__(&quot;POP {r0-r7}&quot;);
  5. }

But the code under SWI_HANDLER even never invoked.

I really even don't know how to ask the question, since it looks like I'm missing some very basic information in my mind.

So what could be the problem? Which information I should provide to make you able to help me?

Here is also the linker script:

  1. ENTRY(__RESET)
  2. SECTIONS
  3. {
  4. . = 0x10000;
  5. .ivt . : { ivt.o(.text) }
  6. .text : { *(.text) }
  7. .data : { *(.data) }
  8. .bss : { *(.bss COMMON) }
  9. . = ALIGN(8);
  10. . = . + 0x1000; /* 4KB of stack memory */
  11. stack_top = .;
  12. . = . + 0x100;
  13. usr_stack_top = .;
  14. }

答案1

得分: 1

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

问题并不是出在代码上,而是出在链接脚本上。我将向量表放在了0x10000,如你在链接脚本中所见,但它应该放在0x0处。因此,SVC 没有得到正确处理,因为处理程序放在了错误的位置。

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

英文:

Many thanks to @Jester and @old_timer, the problem is solved.

The problem was not with code, but with linker script. I have put my vector table at 0x10000, as you can see in the linker script, but it should be placed at 0x0. So SVC was not handled properly because the handler was placed in a wrong place.

When I changed the base address in my ld script and tried to load the firmware as ELF, everything starts to work perfectly.

答案2

得分: 1

你用一种方法解决了问题,但我还是会写出我的答案。

非常简单的底层示例...

strap.s

  1. .globl _start
  2. _start:
  3. b reset
  4. b hang
  5. b swi_handler
  6. b hang
  7. reset:
  8. msr cpsr_c, 0x13 /* Supervisor mode */
  9. mov sp,#0x10000
  10. msr cpsr_c, 0x10 /* User mode */
  11. mov sp,#0x9000
  12. bl notmain
  13. hang:
  14. b hang
  15. swi_handler:
  16. push {r0,r1,r2,r3,r4,lr}
  17. pop {r0,r1,r2,r3,r4,lr}
  18. movs pc,lr
  19. .globl GETPC
  20. GETPC:
  21. mov r0,pc
  22. bx lr
  23. .globl PUT32
  24. PUT32:
  25. str r1,[r0]
  26. bx lr
  27. .globl GET32
  28. GET32:
  29. ldr r0,[r0]
  30. bx lr

notmain.c

  1. void PUT32 ( unsigned int, unsigned int );
  2. unsigned int GET32 ( unsigned int );
  3. unsigned int GETPC ( void );
  4. #define UART_BASE 0x101F1000
  5. #define UARTDR (UART_BASE+0x000)
  6. static void uart_send ( unsigned int x )
  7. {
  8. PUT32(UARTDR,x);
  9. }
  10. static void hexstrings ( unsigned int d )
  11. {
  12. unsigned int rb;
  13. unsigned int rc;
  14. rb=32;
  15. while(1)
  16. {
  17. rb-=4;
  18. rc=(d>>rb)&0xF;
  19. if(rc>9) rc+=0x37; else rc+=0x30;
  20. uart_send(rc);
  21. if(rb==0) break;
  22. }
  23. uart_send(0x20);
  24. }
  25. static void hexstring ( unsigned int d )
  26. {
  27. hexstrings(d);
  28. uart_send(0x0D);
  29. uart_send(0x0A);
  30. }
  31. int notmain ( void )
  32. {
  33. unsigned int ra;
  34. hexstring(0x12345678);
  35. hexstring(GETPC());
  36. for(ra=0;ra<0x20;ra+=4)
  37. {
  38. hexstrings(ra);
  39. hexstring(GET32(ra));
  40. }
  41. return(0);
  42. }

memmap

  1. MEMORY
  2. {
  3. ram : ORIGIN = 0x00010000, LENGTH = 32K
  4. }
  5. SECTIONS
  6. {
  7. .text : { *(.text*) } > ram
  8. .bss : { *(.text*) } > ram
  9. }

Build

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

Execute

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

Output

  1. 12345678
  2. 0001003C
  3. 00000000 E3A00000
  4. 00000004 E59F1004
  5. 00000008 E59F2004
  6. 0000000C E59FF004
  7. 00000010 00000183
  8. 00000014 00000100
  9. 00000018 00010000
  10. 0000001C 00000000

检查、汇编和反汇编

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

所以你可以看到,它基本上是在启动Linux内核,ATAGS/dtb位于0x100的RAM中。他们跳转到0x10000。0001003C是程序加载的PC,使用-O binary版本加载到0x10000并在那里执行。如果有一个swi事件,你将从ldr r2指令开始执行,并在代码中的rest处理程序处停止。

(值得注意的是,qemu不正确地模拟了uart,至少我找到的是如此,因此你不必初始化它们,也不必等待tx缓冲区为空,只需将字节塞入tx缓冲区,它们就会输出)。

如果你运行elf而不更改链接器脚本

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

输出为

  1. 12345678
  2. 0001003C
  3. 00000000 00000000
  4. 00000004 00000000
  5. 00000008 00000000
  6. 0000000C 00000000
  7. 00000010 00000000
  8. 00000014 00000000
  9. 00000018 00000000
  10. 0000001C 00000000

有趣的是,它加载并运行在0x10000,这是它链接的地方,但它不会在0x00000000处设置复位,或者这是导致错误的链接器问题,生成了错误的elf文件,并且用零填充了

  1. 1c: 00000000 andeq r0, r0, r0

所以它可能已经执行了从0x00000000到0x10000的代码,并且进入了我们的代码。

如果我们更改链接器脚

英文:

You solved it one way but I'll still write my answer.

Very bare bare metal example...

strap.s

  1. .globl _start
  2. _start:
  3. b reset
  4. b hang
  5. b swi_handler
  6. b hang
  7. reset:
  8. msr cpsr_c, 0x13 /* Supervisor mode */
  9. mov sp,#0x10000
  10. msr cpsr_c, 0x10 /* User mode */
  11. mov sp,#0x9000
  12. bl notmain
  13. hang:
  14. b hang
  15. swi_handler:
  16. push {r0,r1,r2,r3,r4,lr}
  17. pop {r0,r1,r2,r3,r4,lr}
  18. movs pc,lr
  19. .globl GETPC
  20. GETPC:
  21. mov r0,pc
  22. bx lr
  23. .globl PUT32
  24. PUT32:
  25. str r1,[r0]
  26. bx lr
  27. .globl GET32
  28. GET32:
  29. ldr r0,[r0]
  30. bx lr

notmain.c

  1. void PUT32 ( unsigned int, unsigned int );
  2. unsigned int GET32 ( unsigned int );
  3. unsigned int GETPC ( void );
  4. #define UART_BASE 0x101F1000
  5. #define UARTDR (UART_BASE+0x000)
  6. static void uart_send ( unsigned int x )
  7. {
  8. PUT32(UARTDR,x);
  9. }
  10. static void hexstrings ( unsigned int d )
  11. {
  12. unsigned int rb;
  13. unsigned int rc;
  14. rb=32;
  15. while(1)
  16. {
  17. rb-=4;
  18. rc=(d&gt;&gt;rb)&amp;0xF;
  19. if(rc&gt;9) rc+=0x37; else rc+=0x30;
  20. uart_send(rc);
  21. if(rb==0) break;
  22. }
  23. uart_send(0x20);
  24. }
  25. static void hexstring ( unsigned int d )
  26. {
  27. hexstrings(d);
  28. uart_send(0x0D);
  29. uart_send(0x0A);
  30. }
  31. int notmain ( void )
  32. {
  33. unsigned int ra;
  34. hexstring(0x12345678);
  35. hexstring(GETPC());
  36. for(ra=0;ra&lt;0x20;ra+=4)
  37. {
  38. hexstrings(ra);
  39. hexstring(GET32(ra));
  40. }
  41. return(0);
  42. }

memmap

  1. MEMORY
  2. {
  3. ram : ORIGIN = 0x00010000, LENGTH = 32K
  4. }
  5. SECTIONS
  6. {
  7. .text : { *(.text*) } &gt; ram
  8. .bss : { *(.text*) } &gt; ram
  9. }

Build

  1. arm-linux-gnueabi-as --warn --fatal-warnings -march=armv5t strap.s -o strap.o
  2. arm-linux-gnueabi-gcc -c -Wall -O2 -nostdlib -nostartfiles -ffreestanding -march=armv5t notmain.c -o notmain.o
  3. arm-linux-gnueabi-ld strap.o notmain.o -T memmap -o notmain.elf
  4. arm-linux-gnueabi-objdump -D notmain.elf &gt; notmain.list
  5. arm-linux-gnueabi-objcopy notmain.elf -O binary notmain.bin

Execute

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

Output

  1. 12345678
  2. 0001003C
  3. 00000000 E3A00000
  4. 00000004 E59F1004
  5. 00000008 E59F2004
  6. 0000000C E59FF004
  7. 00000010 00000183
  8. 00000014 00000100
  9. 00000018 00010000
  10. 0000001C 00000000

Examine, assemble disassemble

  1. .word 0xE3A00000
  2. .word 0xE59F1004
  3. .word 0xE59F2004
  4. .word 0xE59FF004
  5. .word 0x00000183
  6. .word 0x00000100
  7. .word 0x00010000
  8. .word 0x00000000
  9. 0: e3a00000 mov r0, #0
  10. 4: e59f1004 ldr r1, [pc, #4] ; 10 &lt;.text+0x10&gt;
  11. 8: e59f2004 ldr r2, [pc, #4] ; 14 &lt;.text+0x14&gt;
  12. c: e59ff004 ldr pc, [pc, #4] ; 18 &lt;.text+0x18&gt;
  13. 10: 00000183 andeq r0, r0, r3, lsl #3
  14. 14: 00000100 andeq r0, r0, r0, lsl #2
  15. 18: 00010000 andeq r0, r1, r0
  16. 1c: 00000000 andeq r0, r0, r0

So you can see that they are basically launching a Linux kernel the ATAGS/dtb is in ram at 0x100 perhaps. And they jump to 0x10000. 0001003C being the pc shown by the program as loaded with that command line using the -O binary version was loaded at 0x10000 and executed there. If you were to have an swi event then you would execute starting with the ldr r2 instruction and land on the rest handler in your code.

(Note incidentally that qemu doesn't properly model uarts, at least so far as I have found so you don't have to initialize them you don't have to wait for the tx buffer to be empty you just jam bytes into the tx buffer and they come out).

If you run the elf without changing the linker script

  1. qemu-system-arm -M versatilepb -m 128M -nographic -kernel notmain.elf
  2. 12345678
  3. 0001003C
  4. 00000000 00000000
  5. 00000004 00000000
  6. 00000008 00000000
  7. 0000000C 00000000
  8. 00000010 00000000
  9. 00000014 00000000
  10. 00000018 00000000
  11. 0000001C 00000000

Interesting it loads and runs at 0x10000 which is what it was linked for but doesn't bother to setup for coming out of reset at 0x00000000 and/or this is that linker issue that makes for bad elf files and it padded with zeros which is

  1. 1c: 00000000 andeq r0, r0, r0

So it could have executed from 0x00000000 to 0x10000 and run into our code.

If we change the linker script

  1. ram : ORIGIN = 0x00000000, LENGTH = 32K

Run the elf not the bin

  1. qemu-system-arm -M versatilepb -m 128M -nographic -kernel notmain.elf
  2. 12345678
  3. 0000003C
  4. 00000000 EA000002
  5. 00000004 EA000006
  6. 00000008 EA000006
  7. 0000000C EA000004
  8. 00000010 E321F013
  9. 00000014 E3A0D801
  10. 00000018 E321F010
  11. 0000001C E3A0DA09

as expected.

Now for the swi.

strap.s

  1. .globl _start
  2. _start:
  3. b reset
  4. b hang
  5. b swi_handler
  6. b hang
  7. reset:
  8. msr cpsr_c, 0x13 /* Supervisor mode */
  9. mov sp,#0x10000
  10. msr cpsr_c, 0x10 /* User mode */
  11. mov sp,#0x9000
  12. bl notmain
  13. hang:
  14. b hang
  15. swi_handler:
  16. push {r0,r1,r2,r3,r4,lr}
  17. bl handler
  18. pop {r0,r1,r2,r3,r4,lr}
  19. movs pc,lr
  20. .globl GETPC
  21. GETPC:
  22. mov r0,pc
  23. bx lr
  24. .globl PUT32
  25. PUT32:
  26. str r1,[r0]
  27. bx lr
  28. .globl GET32
  29. GET32:
  30. ldr r0,[r0]
  31. bx lr
  32. .globl do_swi
  33. do_swi:
  34. svc #0x08
  35. bx lr

notmain.c

  1. void PUT32 ( unsigned int, unsigned int );
  2. unsigned int GET32 ( unsigned int );
  3. unsigned int GETPC ( void );
  4. void do_swi ( void );
  5. #define UART_BASE 0x101F1000
  6. #define UARTDR (UART_BASE+0x000)
  7. static void uart_send ( unsigned int x )
  8. {
  9. PUT32(UARTDR,x);
  10. }
  11. static void hexstring ( unsigned int d )
  12. {
  13. unsigned int rb;
  14. unsigned int rc;
  15. rb=32;
  16. while(1)
  17. {
  18. rb-=4;
  19. rc=(d&gt;&gt;rb)&amp;0xF;
  20. if(rc&gt;9) rc+=0x37; else rc+=0x30;
  21. uart_send(rc);
  22. if(rb==0) break;
  23. }
  24. uart_send(0x0D);
  25. uart_send(0x0A);
  26. }
  27. void handler ( void )
  28. {
  29. hexstring(0x11223344);
  30. }
  31. int notmain ( void )
  32. {
  33. hexstring(0x12345678);
  34. do_swi();
  35. hexstring(0x12345678);
  36. return(0);
  37. }

memmap

  1. MEMORY
  2. {
  3. ram : ORIGIN = 0x00000000, LENGTH = 32K
  4. }
  5. SECTIONS
  6. {
  7. .text : { *(.text*) } &gt; ram
  8. .bss : { *(.text*) } &gt; ram
  9. }

Run the elf, output is

  1. 12345678
  2. 11223344
  3. 12345678

as desired. But you could have also done this

strap.s

  1. .globl _start
  2. _start:
  3. ldr pc,reset_addr
  4. ldr pc,hang_addr
  5. ldr pc,swi_handler_addr
  6. ldr pc,hang_addr
  7. reset_addr: .word reset
  8. hang_addr: .word hang
  9. swi_handler_addr: .word swi_handler
  10. reset:
  11. mov r0,#0x10000
  12. mov r1,#0x00000
  13. ldmia r0!,{r2,r3,r4,r5}
  14. stmia r1!,{r2,r3,r4,r5}
  15. ldmia r0!,{r2,r3,r4,r5}
  16. stmia r1!,{r2,r3,r4,r5}
  17. msr cpsr_c, 0x13 /* Supervisor mode */
  18. mov sp,#0x10000
  19. msr cpsr_c, 0x10 /* User mode */
  20. mov sp,#0x9000
  21. bl notmain
  22. hang:
  23. b hang
  24. swi_handler:
  25. push {r0,r1,r2,r3,r4,lr}
  26. bl handler
  27. pop {r0,r1,r2,r3,r4,lr}
  28. movs pc,lr
  29. .globl GETPC
  30. GETPC:
  31. mov r0,pc
  32. bx lr
  33. .globl PUT32
  34. PUT32:
  35. str r1,[r0]
  36. bx lr
  37. .globl GET32
  38. GET32:
  39. ldr r0,[r0]
  40. bx lr
  41. .globl do_swi
  42. do_swi:
  43. svc #0x08
  44. bx lr

notmain.c

  1. void PUT32 ( unsigned int, unsigned int );
  2. unsigned int GET32 ( unsigned int );
  3. unsigned int GETPC ( void );
  4. void do_swi ( void );
  5. #define UART_BASE 0x101F1000
  6. #define UARTDR (UART_BASE+0x000)
  7. static void uart_send ( unsigned int x )
  8. {
  9. PUT32(UARTDR,x);
  10. }
  11. static void hexstring ( unsigned int d )
  12. {
  13. unsigned int rb;
  14. unsigned int rc;
  15. rb=32;
  16. while(1)
  17. {
  18. rb-=4;
  19. rc=(d&gt;&gt;rb)&amp;0xF;
  20. if(rc&gt;9) rc+=0x37; else rc+=0x30;
  21. uart_send(rc);
  22. if(rb==0) break;
  23. }
  24. uart_send(0x0D);
  25. uart_send(0x0A);
  26. }
  27. void handler ( void )
  28. {
  29. hexstring(0x11223344);
  30. }
  31. int notmain ( void )
  32. {
  33. unsigned int ra;
  34. hexstring(0x12345678);
  35. for(ra=0x10000;ra&lt;0x10020;ra+=4) hexstring(GET32(ra));
  36. for(ra=0x00000;ra&lt;0x00020;ra+=4) hexstring(GET32(ra));
  37. do_swi();
  38. hexstring(0x12345678);
  39. return(0);
  40. }

memmap

  1. MEMORY
  2. {
  3. ram : ORIGIN = 0x00010000, LENGTH = 32K
  4. }
  5. SECTIONS
  6. {
  7. .text : { *(.text*) } &gt; ram
  8. .bss : { *(.text*) } &gt; ram
  9. }

And now both the elf and the binary image versions work. I let the toolchain do the work for me:

  1. 00010010 &lt;reset_addr&gt;:
  2. 10010: 0001001c
  3. 00010014 &lt;hang_addr&gt;:
  4. 10014: 00010048
  5. 00010018 &lt;swi_handler_addr&gt;:
  6. 10018: 0001004c

The ldr pc, is position independent. I copy the four entries plus the four (well three) addresses so that 0x00000 matches 0x10000 and now the exception table (it is not a vector table btw) works.

With newer arm processors you could instead set VTOR to 0x10000 and it would use the one built into the binary, no copying necessary. Or as you solved just build and run your program from 0x00000 and there you go. I wanted to show the alternatives as well as how to figure out (by cheating, you have to love uarts in qemu) what qemu is doing and where it is loading without having to use a debugger.

huangapple
  • 本文由 发表于 2020年1月4日 01:13:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/59582630.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定