QEMU 仿真入门

学习使用 QEMU 运行 arm

Posted by Jerry Chen on July 8, 2020

Windows 版本

下载 QEMU

官网 下载页 提供了 x64 位 Windows QEMU 下载;

下载安装时,只用选择 qemu-system-arm 即可;

接着添加安装路径 D:\Program Files\qemu 到系统全局环境变量 Path 即可;

查看支持仿真的信息

1
2
3
4
5
# 查看支持仿真的终端板(machines)
qemu-system-arm.exe -M help

# 查看支持仿真的设备(输入、显示等)
qemu-system-arm.exe -device help

仿真 mcimx6ul-evk

运行 mcimx6ul-evk(空程序)

注意这样运行没有卵用,只是看看能运行;

1
2
3
4
# 弹出 qemu 的控制台窗口(还是字符界面)
qemu-system-arm.exe -M mcimx6ul-evk
# 无图形窗口
qemu-system-arm.exe -M mcimx6ul-evk -nographic

Linux 版本

下载 QEMU 源码

官网 下载页 提供了 5.0.0 的源码;

编译 QEMU

安装依赖:

1
2
3
sudo apt install pkg-config
sudo apt install libglib2.0-dev
sudo apt install libpixman-1-dev

接下来进入源码目录,按需编译即可,直接 ./configure 会生成编译全部架构的配置,没有必要;

如下两个架构选择需要的一个即可;

1
2
3
4
# 32位arm配置
./configure --target-list=arm-softmmu --audio-drv-list=
# aarch64配置
./configure --target-list=aarch64-softmmu

进行编译(建议加 -j4 等并行编译):

1
make

生成的目标文件在 arm-softmmu/qemu-system-arm

1
2
# 链接到 bin 目录
sudo ln -sf $(pwd)/arm-softmmu/qemu-system-arm /bin/qemu-system-arm

简单验证

查看版本:

1
2
3
jerry@CLOU-PC ~ → qemu-system-arm --version
QEMU emulator version 5.0.0
Copyright (c) 2003-2020 Fabrice Bellard and the QEMU Project developers

简单运行(空程序):

1
qemu-system-arm -M vexpress-a9 -nographic

按下 Ctrl 键和 A 键,然后释放这两个键,再按 X 键即可退出 QEMU;

运行 versatilepb

PB926EJ-S 是开发板型号,主控芯片是 arm926ejs;

部分资源地址如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
Ethernet Interface FPGA PIC 25, SIC 25 0x10010000–0x1001FFFF 64KB
===
Vectored Interrupt Controller (PIC) Dev. chip - 0x10140000–0x1014FFFF 64KB
===
Timer modules 0 and 1 interface Dev. chip PIC 4 0x101E2000–0x101E2FFF 4KB
(Timer 1 starts at 0x101E2020)
Timer modules 2 and 3 interface Dev. chip PIC 5 0x101E3000–0x101E3FFF 4KB
(Timer 3 starts at 0x101E3020)
===
UART 0 Interface Dev. chip PIC 12 0x101F1000–0x101F1FFF 4KB
===
UART 1 Interface Dev. chip PIC 13 0x101F2000–0x101F2FFF 4KB
===
1.1 编写一个裸机程序

startup.S 如下:

.global _Reset
_Reset:
    ldr sp, =stack_top
    bl c_entry
    b .

test.c 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
volatile unsigned int *const UART0DR = (unsigned int *)0x101f1000;

void print_uart0(const char *s)
{
    while (*s) {
        *UART0DR = (unsigned int)(*s);
        s++;
    }
}

void c_entry()
{
    print_uart0("Hello World\n");
}

test.lds(设置程序入口,栈起始地址等)如下:

ENTRY(_Reset)
SECTIONS
{
    . = 0x10000;
    .startup . : { startup.o(.text) }
    .text : { *(.text) }
    .data : { *(.data) }
    .bss : { *(.bss COMMON) }
    . = ALIGN(8);
    . = . + 0x1000;
    stack_top = .;
}
1.2 编译上面的程序
1
2
3
4
arm-linux-gnueabi-as -mcpu=arm926ej-s -g startup.S -o startup.o
arm-linux-gnueabi-gcc -c -mcpu=arm926ej-s -g test.c -o test.o
arm-linux-gnueabi-ld -T test.lds test.o startup.o -o test.elf
arm-linux-gnueabi-objcopy -O binary test.elf test.bin
1.3 使用 qemu 运行上面的程序
1
qemu-system-arm -M versatilepb -m 128M -nographic -kernel test.bin

运行 vexpress-a9

介绍

vexpress(Versatile Express Family)是 ARM 推出的开发板,主要是为了方便 SOC 厂商设计、验证和测试 SOC 芯片设计;

官网资料已丢失,建议参考 github-LKDemo 进行实验;

1. 1 下载 uboot2017.11-rc4

点这里下载

1.2 编译 uboot

自行下载 4.9.4编译器 arm-linux-gnueabi-

1
2
make vexpress_ca9x4_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-

在 uboot 工程根目录生成了 u-bootu-boot.bin 等目标文件;

大概 u-boot 是 qemu 使用的,u-boot.bin 是烧写到目标终端上的;

1.3 运行 u-boot
1
2
3
4
5
qemu-system-arm \
-M vexpress-a9 \
-nographic \
-m 512M \
-kernel u-boot

仿真结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
U-Boot 2017.11-rc4 (Jul 09 2020 - 09:56:58 +0800)

DRAM:  512 MiB
WARNING: Caches not enabled
Flash: 128 MiB
MMC:   MMC: 0
*** Warning - bad CRC, using default environment

In:    serial
Out:   serial
Err:   serial
Net:   smc911x-0
Hit any key to stop autoboot:  0
=>
=> version
U-Boot 2017.11-rc4 (Jul 09 2020 - 09:56:58 +0800)

arm-linux-gnueabi-gcc (Linaro GCC 4.9-2017.01) 4.9.4
GNU ld (Linaro_Binutils-2017.01) 2.24.0.20141017 Linaro 2014_11-3-git
=>
2.1 下载 linux-5.7.7

官网下载最新稳定版 linux:点这里下载

2.2 编译 linux

安装依赖:

1
sudo apt install flex bison

进行编译:

注意 export xxx=yyy 后不要有空格;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabi-

# 生成 .config 配置文件
make vexpress_defconfig

# 编译内核
make zImage

# [可选]编译模块
make modules

# 编译设备树
make dtbs

会生成 arch/arm/boot/zImage 内核文件;

会生成 arch/arm/boot/dts/vexpress-v2p-ca9.dtb 设备树文件;

3.1 下载 buildroot-2020.05

点这里下载

3.2 构建 rootfs

安装依赖:

1
sudo apt install unzip

图形化配置生成 .config 配置文件:

1
make menuconfig

Target options 按照以下配置:

  • Target Architecture:目标架构,这里选择 ARM(little endian),ARM小端模式;

  • Target Binary Format:二进制格式为 ELF;

  • Target Architecture Variant:架构体为 cortex-A9;

  • Target ABI:应用程序二进制接口,为EABI;

  • Floating point strategy:浮点数的策略,选择为 Soft float;

    注意 qemu 仿真都需要选软浮点;

    非仿真情况下,如终端支持硬浮点,就选择硬浮点;

  • ARM instruction set:arm 汇编指令集,选择 ARM;

Build options 是下载和编译用到的选项,一般默认就好;

Toolchain 是工具链选项:

  • Toolchain type:编译工具链选择 external toolthain
  • Toolchain:选择 Custom toolchain
  • Toolchain origin:选择 Pre-installed toolchain
  • Toolchain path:填入 /home/jerry/public/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabi,也就是交叉编译器所在根文件夹;
  • Toolchain prefix:填入 $(ARCH)-linux-gnueabi
  • External toolchain gcc version 选择交叉编译工具的 gcc 版本;
  • External toolchain kernel headers series 百度教你选择,也可随便选择,报错后会提示正确的版本;
  • External toolchain C library:选择 glibc/eglibc;
  • Toolchain has SSP support? 一般选 glibc 后自动勾选。如未请手动勾选,否则 glibc 报错;

System configuration 是文件系统设置项:

  • Root FS skeleton 默认即可;

  • System hostname:填写 myhost

  • System banner:填写 Welcome to jerry's host

  • Init system:填写 BusyBox

    各个初始化系统的区别看:这篇文章

  • /dev management:选择 Dynamic using devtmpfs + eudev

    mdev 和 eudev 区别请百度,也建议先百度一下再选适合的;

  • Path to the permission tables:一定填写 system/device_table_dev.txt

  • Root password:是 root 账户的密码。默认登录 root 账户,如果为空,那么登录就不需要密码;

  • Run a getty (login prompt) after boot:

    • TTY port:配置为 ttyAMA0

      这个需要确定调试串口在 kernel 上对应的设备节点;

      vexpress-a9 默认是 ttyAMA0;

    • Baudrate:配置为 115200

Filesystem images 文件系统镜像相关设置项:

  • ext2/3/4 root filesystem:选择 ext3;

以上完成后,进行编译:

1
make
  1. 编译后生成 tar 文件: output/image/rootfs.tar(默认配置了 tar 打包,可自行二次打包)
  2. 编译后生成 ext3 文件:output/image/rootfs.ext3(QEMU 仿真的目标文件系统镜像)
4. 运行 kernel + 文件系统

先将 zImage、vexpress-v2p-ca9.dtb、rootfs.ext3 拷贝到同一目录,接着执行以下命令运行 kernel + 文件系统(SD卡):

1
2
3
4
5
6
7
8
qemu-system-arm \
-nographic \
-M vexpress-a9 \
-m 512M \
-kernel zImage \
-dtb vexpress-v2p-ca9.dtb \
-sd rootfs.ext3 \
-append "root=/dev/mmcblk0 rw console=ttyAMA0"

运行成功的结果:

1
2
3
4
5
6
Welcome to jerry's host
myhost login: root
Password:
#
# pwd
/root
5. 运行 kernel + ramfs

让我们制作最简 ramfs 镜像:

  1. 创建 hello.c 文件

    1
    2
    3
    4
    5
    6
    7
    8
    
    #include <stdio.h>
    void main(){
        printf("================\n");
        printf("Hello ramfs\n");
        printf("================\n");
        fflush(stdout);
        while(1);
    }
    
  2. 静态编译 hello.c

    1
    
    arm-linux-gnueabi-gcc hello.c -o hello -static
    
  3. 创建 ramfs 镜像文件 —— rootfs

    给 cpio 传递文件名打包到 rootfs 中;

    如果需要文件夹打包,可用:find . | cpio -o --format=newc > rootfs

    1
    
    echo ./hello | cpio -o --format=newc > rootfs
    

接着将 zImage、vexpress-v2p-ca9.dtb、rootfs 拷贝到同一目录,然后执行以下命令运行 kernel + ramfs:

其中 -initrd 用于指定 ramfs 镜像文件,qemu 会自动将系统拷贝到内存中(对应设备 /dev/ram0 或者 /dev/ram);

-append 中的 rdinit 指定初始程序,如果没有指定默认会去找 /init

1
2
3
4
5
6
7
8
qemu-system-arm \
-nographic \
-M vexpress-a9 \
-m 512M \
-kernel zImage \
-dtb vexpress-v2p-ca9.dtb \
-initrd rootfs \
-append "root=/dev/ram rdinit=/hello console=ttyAMA0"

运行成功的结果:

1
2
3
4
Run /hello as init process
================
Hello ramfs
================

chroot+qemu-user-static 仿真各平台程序

安装 qemu-user-static
1
sudo apt install qemu-user-static
1.1 创建 hello.c 文件
1
2
3
4
5
6
7
8
#include <stdio.h>
void main(){
    printf("================\n");
    printf("Hello\n");
    printf("================\n");
    fflush(stdout);
    while(1);
}
1.2 调试 x86_64 版本的 hello

编译 hello.c 为 x86_64 版本

1
gcc -g hello.c -o hello

使用 chroot+qemu-x86_64-static 调试 hello,其中 gdbserver 的端口是 1234;

1
2
3
4
cp /usr/bin/qemu-x86_64-static .
# 下面的命令怎么看:sudo chroot <dir> <cmd...>
# 制作 rootfs 时常用的命令:sudo chroot <rootfs_dir> ./usr/bin/bash 进入根文件系统用 apt 安装软件包
sudo chroot . ./qemu-x86_64-static -g 1234 ./hello

接着使用 gdb 调试 hello:

1
2
3
4
gdb hello
(gdb) target remote :1234
(gdb) b main
(gdb) r
1.3 调试 arm 版本的 hello

编译 hello.c 为 arm 版本

1
arm-linux-gnueabi-gcc hello.c -o hello

使用 chroot+qemu-arm-static 调试 hello,其中 gdbserver 的端口是 1234;

1
2
3
cp /usr/bin/qemu-arm-static .
# 下面的命令怎么看:sudo chroot <dir> <cmd...>
sudo chroot . ./qemu-arm-static -g 1234 ./hello

接着使用 arm-linux-gnueabi-gdb 调试 hello:

1
2
3
4
arm-linux-gnueabi-gdb hello
(gdb) target remote :1234
(gdb) b main
(gdb) r