Search code examples
linuxarmqemu

Qemu doesn't load my Image file at my specified address


My linker script:

SECTIONS{
    . = 0xC0008000;
    __text_start = .;
    .text :
    {
        boot/start.o
        *(.text)
    }
    __text_end = .;
    .rodata ALIGN(4) : {*(.rodata*)}     
    .data ALIGN(4)   : { *(.data) }    
    __bss_start = .;    
    .bss ALIGN(4)  : { *(.bss)  *(COMMON) }    
    __bss_end = .;
}

0xC0008000 is the address of bootloader, and I want to specify it in QEMU.
Here is what I've tried. \

    qemu-system-arm \
        -machine virt \
        -cpu cortex-a15 \
        -nographic \
        -S -s \
        -m 4096M \
        -kernel Image \
        # -device loader,file=Image,addr=0xC0008000

It will set the PC at 0x40000000 and after few instructions, PC will jump to the bootloader at 0x40010000. This is acceptable according to documents offered by QEMU, but it is not what I expect to do.

RAM starts at 0x4000_0000 https://qemu.readthedocs.io/en/latest/system/arm/virt.html

But when I change the command to

    qemu-system-arm \
        -machine virt \
        -cpu cortex-a15 \
        -nographic \
        -S -s \
        -m 4096M \
        -kernel Image \
        -device loader,file=Image,addr=0xC0008000

The image is loaded correctly, and it seems to be duplicate instructions in 0x40010000 of the instructions in 0xC0008000, but it can't jump to 0xC0008000 which makes hard to debug. I wonder why the PC won't be able to jump to where I expect. Thanks!


Solution

  • You don't say what format your Image file is. Is it an ELF file, or a raw binary? I'm guessing from the described behaviour that it's a raw binary.

    If you pass QEMU the -kernel option you are saying "this file should be started the way the Linux kernel expects to be started". That means, among other things, that you don't care where in the memory map it is placed, and that it should be started by first setting up a few registers and then jumping to the start of the image. (There is an exception: ELF files are loaded according to where the ELF file says they should be placed, and started by jumping to the ELF entrypoint. But ELF files are better loaded with the generic loader these days.)

    (The specifics of what "boot me like a Linux kernel" means are listed in this bit of the kernel documentation. The virt board will always pass a device tree blob, not ATAGs.)

    If your Image file is not expecting to be booted like a Linux kernel then you should not use the -kernel option.

    Instead, you can use the generic loader (-device loader). Your second command line gets this almost right, but it is wrong in two ways:

    1. you haven't removed the -kernel option, so your image file will be loaded by QEMU twice, in two different places. This is just confusing, so delete the -kernel option.

    2. to get the generic loader to set the starting PC for the CPU you need to explicitly ask for that. Since the starting PC you want is the same as the image load address, you can do that with -device loader,file=Image,addr=0xc0008000,cpu-num=0. The cpu-num part says which CPU to set the PC for. (There is more complicated syntax for setting the PC to some other address than the image load address if you need it.)

    Note 1: the above is for raw image files. If you have an ELF file then the generic loader will load the file to the addresses the ELF file specifies and you don't need to specify addr=. It will use the ELF entrypoint for the initial PC if you use cpu-num=.

    Note 2: 4GB of RAM for a 32-bit CPU is rather a lot. The Cortex-A15 has LPAE, so it's not like it's impossible to access the part of RAM past the 4GB physical address mark, but it seems odd. 3GB (i.e. ram from 0x4000_0000 to 0xFFFF_FFFF) might be just as useful and save on how much of your host's resources the VM is using.