1

I have an .elf file created for a cortex-m3 processor. I want to run this in Qemu.

The .elf should start execution with this assembly file:

.thumb
.syntax unified

.global ResetHandler
ResetHandler:
    LDR SP, =stack_top
    NOP
    BL main
    B .

the associated linker script:

ENTRY(ResetHandler)
SECTIONS {
    . = 0x08000000;
    .startup : { startup.o(.text) }
    .text : { *(.text) }
    . = 0x20000000;
    __bss_start__ = .;
    .bss : { *(.bss) }
    __bss_end__ = .;
    .data : { *(.data) }
    . = . + 0x100;
    stack_top = .;
}

If I run the following command:

qemu-system-arm -s -S -machine stm32vldiscovery -cpu cortex-m3 -nographic -kernel myfile.elf

Qemu starts up and halts (as it should). However, when I connect gdb like so...

arm-none-eabi-gdb
(gdb) file myfile.elf
Reading symbols from myfile.elf...
(gdb) target remote localhost:1234
Remote debugging using localhost:1234
0xf002bf00 in ?? ()
(gdb) si
0x200001f8 in stack_top ()

You can see that GDB doesn't understand the .elf file. If I step through this, Qemu interprets my assembly language incorrectly and it will error and exit. But if I load the .elf file in GDB...

(gdb) load myfile.elf
Start address 0x08000000, load size 21891
Transfer rate: 16 KB/sec, 266 bytes/write.
(gdb) si
ResetHandler () at startup.s:7
7       NOP
(gdb) si
8       BL main

You can see that the .elf file is loaded correctly and can be stepped through.

My overall questions are: What is load doing? The docs state:

Where it exists, it is meant to make filename (an executable) available for debugging on the remote system

But that is not clear to me. How assembly code is being executed changes, so I have to imagine "making a file available for debugging" is doing quite a bit.

edit (adding compilation steps and versions): assembly and compilation...

arm-none-eabi-as -mcpu=cortex-m3 startup.s -g -o  startup.o
arm-none-eabi-gcc \
        -Tcortex-m3-tests.ld \
        -mcpu=cortex-m3 \
        -mthumb \
        mysrcfile.c \
        -g -o myfile.elf

versions...

qemu-system-arm --version
QEMU emulator version 6.2.0
Copyright (c) 2003-2021 Fabrice Bellard and the QEMU Project developers

arm-none-eabi-gcc --version
arm-none-eabi-gcc (GNU Toolchain for the Arm Architecture 11.2-2022.02 (arm-11.14)) 11.2.1 20220111
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
northsideknight
  • 1,519
  • 1
  • 15
  • 24

1 Answers1

4

What is happening here is that your ELF file does not include a valid exception vector table at the correct address, and QEMU requires one. gdb is correctly showing you the results of the emulated CPU crashing as a result.

When QEMU starts for an M-profile Arm CPU, it tries to load the starting PC and SP values out of the vector table (this is how real hardware M-profile CPUs start). On this particular board, the vector table is at address 0x0000_0000, and address 0x0800_0000 is an alias for this. The initial SP and PC are at word offsets 0 and 1 in the table; it happens that your object file has words 0xd008f8df and 0xf002bf00 at those offsets, and you can see in gdb that gdb is correctly telling you that the initial PC is that bogus 0xf002bf00 value.

When you single-step, QEMU tries to load from 0xf002bf00, which has no memory there. It therefore takes a BusFault exception, which at this point in M-profile startup will always escalate to HardFault. That's exception number 3, whose entry point is stored at offset 3 in the vector table. As it happens with the way you've written your assembly, the word there is the address of stack_top, so QEMU will try to execute from there next. Since that's data and not a valid instruction, it goes downhill from there -- we will take another exception, which results in the CPU going into the Lockup state, which is fatal. You can see some of this if you tell QEMU to execute without talking to gdb and with some extra debug logging:

$ qemu-system-arm  -machine stm32vldiscovery -cpu cortex-m3 -display none -serial stdio -kernel myfile.elf  -d in_asm,cpu,exec,int
Taking exception 3 [Prefetch Abort] on CPU 0
...with CFSR.IACCVIOL
...BusFault with BFSR.STKERR
...taking pending nonsecure exception 3
----------------
IN: 
0x20000558:  08000079  stmdaeq  r0, {r0, r3, r4, r5, r6}

Trace 0: 0x7fd68be0e100 [00000401/20000558/00000130/ff000000] 
R00=00000000 R01=00000000 R02=00000000 R03=00000000
R04=00000000 R05=00000000 R06=00000000 R07=00000000
R08=00000000 R09=00000000 R10=00000000 R11=00000000
R12=00000000 R13=d008f8b8 R14=fffffff9 R15=20000558
XPSR=40000003 -Z-- A handler
Taking exception 18 [v7M INVSTATE UsageFault] on CPU 0
qemu: fatal: Lockup: can't escalate 3 to HardFault (current priority -1)

R00=00000000 R01=00000000 R02=00000000 R03=00000000
R04=00000000 R05=00000000 R06=00000000 R07=00000000
R08=00000000 R09=00000000 R10=00000000 R11=00000000
R12=00000000 R13=d008f8b8 R14=fffffff9 R15=20000558
XPSR=40000003 -Z-- A handler
FPSCR: 00000000
Aborted (core dumped)

(You can't see all of the steps I describe above, you have to infer them, because QEMU doesn't currently log all the exception table loads or the initial PC/SP values. But you can see the BusFault, the attempt to execute at 0x20000558, the second exception and the Lockup. I've submitted some QEMU patches which improve the logging a little so that QEMU 7.0 and up should print the PC values being loaded from the vector table.)

The difference when you use the gdb 'load' command is that gdb both downloads the ELF file data into memory and also sets the initial PC value to the ELF file's entry-point address. So execution starts at 08000000 and continues from there.

Anyway, the way to fix this is to make sure the start of your ELF file that gets loaded at address 0 (or the 0x0800_0000 alias to 0) has a valid vector table. For an example look at https://git.linaro.org/people/peter.maydell/semihosting-tests.git/tree/start-microbit.S or https://git.linaro.org/people/peter.maydell/m-profile-tests.git/tree/init-m.S for instance.

Peter Maydell
  • 9,707
  • 1
  • 19
  • 25