qemu-system-riscv64 virt平台ROM代码启动分析

为什么下面qemu启动elf时,text地址要从0x80000000开始?

qemu-system-riscv64 -machine virt -cpu c910v -nographic -smp 1 -bios none -kernel xxx.elf 

从memory mapping角度

下面是qemu virt平台的memory mapping

https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c

static const MemMapEntry virt_memmap[] = {
    [VIRT_DEBUG] =        {        0x0,         0x100 },
    [VIRT_MROM] =         {     0x1000,        0xf000 },
    [VIRT_TEST] =         {   0x100000,        0x1000 },
    [VIRT_RTC] =          {   0x101000,        0x1000 },
    [VIRT_CLINT] =        {  0x2000000,       0x10000 },
    [VIRT_ACLINT_SSWI] =  {  0x2F00000,        0x4000 },
    [VIRT_PCIE_PIO] =     {  0x3000000,       0x10000 },
    [VIRT_PLATFORM_BUS] = {  0x4000000,     0x2000000 },
    [VIRT_PLIC] =         {  0xc000000, VIRT_PLIC_SIZE(VIRT_CPUS_MAX * 2) },
    [VIRT_APLIC_M] =      {  0xc000000, APLIC_SIZE(VIRT_CPUS_MAX) },
    [VIRT_APLIC_S] =      {  0xd000000, APLIC_SIZE(VIRT_CPUS_MAX) },
    [VIRT_UART0] =        { 0x10000000,         0x100 },
    [VIRT_VIRTIO] =       { 0x10001000,        0x1000 },
    [VIRT_FW_CFG] =       { 0x10100000,          0x18 },
    [VIRT_FLASH] =        { 0x20000000,     0x4000000 },
    [VIRT_IMSIC_M] =      { 0x24000000, VIRT_IMSIC_MAX_SIZE },
    [VIRT_IMSIC_S] =      { 0x28000000, VIRT_IMSIC_MAX_SIZE },
    [VIRT_PCIE_ECAM] =    { 0x30000000,    0x10000000 },
    [VIRT_PCIE_MMIO] =    { 0x40000000,    0x40000000 },
    [VIRT_DRAM] =         { 0x80000000,           0x0 },
};
  • MROM: 是virt平台的rom化启动代码,所以qemu启动时会默认从该地址开始运行。
  • DRAM:DRAM的映射地址为0x80000000,从这里大概能猜出来elf的地址为什么要那么设置了。

qemu启动时,使用-kernel参数带上elf,与实际硬件平台的差异相比,就会默认根据elf的程序入口地址将elf加载到对应DRAM内存上,如下图所示。

跟踪启动流程

上面是启动qemu后的初始化代码,连接上gdb后,可以使用layout asm查看汇编指令,并且使用si可进行单步调试。可见qemu启动就会跳转到0x1000进行执行,即对应上面memory mapping的VIRT_MROM。上面代码主要逻辑是,跳转到t0,传递参数a0/a1/a2。下面来看看具体的值。

0x0000000000001000:  97 02 00 00     auipc   t0,0x0 //t0=PC+0x0<<12,即t0=0x1000
0x0000000000001004:  13 86 82 02     addi    a2,t0,40//a2=t0+40,即0x1028
0x0000000000001008:  73 25 40 f1     csrr    a0,mhartid//a0=0
0x000000000000100c:  83 b5 02 02     ld      a1,32(t0) //a1=t0+0x20地址的值即0x1020地址处
                     ld是加载8字节,由于是小端,所以a1=0x87e00000
0x0000000000001010:  83 b2 82 01     ld      t0,24(t0) //t0=t0+0x18地址的值即0x1018地址处
                     同理ld是加载8字节且小端,所有t0=80000000
0x0000000000001014:  67 80 02 00     jr      t0 //跳转到0x80000000执行。