QEMU开源软件在Linux上进行开发。在windows上可以采用WSL Linux,也可以自行在电脑上安装Linux原生系统。一般采用vscode作为IDE 开发QEMU。
本文采用 vscode 连接校内 ubuntu 服务器的方式进行开发环境搭建。ubuntu服务器多人使用,采用docker 容器建立各自独立的开发环境。
docker环境
使用docker启动作为编译的系统环境。可以将 docker 视作轻量级虚拟机,先创建 docker镜像,再以镜像为基础启动容器。每一个容器视作虚拟机,系统环境数据存在容器中,工作文件夹等需要保存在硬盘上的重要文件,以共享文件夹的方式映射到容器。镜像不保存任何运行数据。
dockfile
创建名为 Dockerfile
的文件,并填入下列内容。并可以根据自行需要(用户\组 id,软件依赖等),增删其中的内容。
注意:存放 Dockerfile
的文件夹中最好不要存放任何其他无关的文件,在创建镜像时,docker 会将该文件夹中的所有内容都复制到容器中,这会大大增加镜像创建的时间。
1FROM ubuntu:24.042
3# 定义构建时变量,可以在docker build构建通过--build-arg来修改4ARG GID=10005ARG UID=10006ARG username=developer7
8# 设置 DEBIAN_FRONTEND 环境变量以避免交互式对话框,否则可能会卡在一些交互式输入中9ENV DEBIAN_FRONTEND=noninteractive10
11# 修改ubuntu的镜像源为阿里云12# ubuntu24.04版本修改软件源的位置:/etc/apt/sources.list 替换为 /etc/apt/sources.list.d/ubuntu.sources13RUN sed -i s@/archive.ubuntu.com/@/mirrors.aliyun.com/@g /etc/apt/sources.list.d/ubuntu.sources \14&& sed -i s@/security.ubuntu.com/@/mirrors.aliyun.com/@g /etc/apt/sources.list.d/ubuntu.sources \15&& apt-get update -y && apt-get install -y sudo locales \49 collapsed lines
16&& apt-get clean \17&& echo "%sudo ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers18# 修改容器或系统中的 sudoers 文件,允许属于 sudo 组的用户执行 sudo 命令时无需输入密码。19
20# 修改语言环境(locale)设置21RUN locale-gen en_US.UTF-8 && update-locale22
23# 添加用户:赋予sudo权限,指定密码123,建议docker的密码不要太复杂,太多了很容易忘记。24
25# 注:用户id和组id尽可能的和当前用户id一致,使得读写共享文件时的权限一致,否则可能出现docker无法写入共享文件的问题。26# 命令 id 可以查看用户的各种id27RUN getent group ${GID} || groupadd -g ${GID} dev \28 && if getent passwd ${UID}; then \29 usermod -l ${username} $(getent passwd ${UID} | cut -d: -f1) && \30 usermod -d /home/${username} -m ${username} && \31 usermod -aG sudo ${username}; \32 else \33 useradd -ms /bin/bash -u ${UID} -g ${GID} -G sudo ${username}; \34 fi \35 && echo "${username}:123" | chpasswd \36 && echo 'root:123' | chpasswd37
38# 安装各种以依赖软件,可以根据需要定制,也可以后续手动安装。39
40# QEMU编译需要的依赖41RUN apt-get install -y build-essential meson ninja-build pkg-config \42 diffutils python3 python3-venv \43 libglib2.0-dev libusb-1.0-0-dev libncursesw5-dev \44 libpixman-1-dev libepoxy-dev libv4l-dev libpng-dev \45 libsdl2-dev libsdl2-image-dev libgtk-3-dev libgdk-pixbuf2.0-dev \46 libasound2-dev libpulse-dev \47 libx11-dev libfdt-dev libiscsi-dev48
49# riscv-gnu-toolchain 需要的依赖50RUN apt-get install -y autoconf automake autotools-dev curl \51python3 python3-pip libmpc-dev libmpfr-dev libgmp-dev gawk build-essential \52bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev \53ninja-build git cmake libglib2.0-dev libslirp-dev54
55RUN apt-get install -y git gdb clang cmake vim gdb56
57# 还原 DEBIAN_FRONTEND 环境变量(可选)58ENV DEBIAN_FRONTEND=dialog59
60# 指定容器启动后的工作目录61WORKDIR /home/${username}62
63# 指定容器启动后的登录用户64USER ${username}
创建镜像
使用 docker 的 buildx 来创建,buildx 命令在 docker 19.03 版本引入。
确认安装了 docker-buildx:docker buildx version
,有输出即可,如果没有,在 manjaro 上可以通过 yay -S docker-buildx
安装。
使用下列命令,基于 Dockerfile 构建镜像,如果下载 apt 过慢,则可尝试使用 --network=host
选项,在安装软件时可能会快一些:
1docker build --network=host -t lyndra-qemu - < Dockerfile
注意,此方法没有传入任何上下文,如果需要构建时使用的上下文,参考 Docker中创建镜像。
若在创建镜像时希望动态修改 Dockerfile
中的ARG参数,则在构建时,添加参数 --build-arg ARG_name=value
,将 ARG_name
修改为 value
。
由于每个用户 id 和组 id 不一样,需要在终端中运行命令 id
查询到当前用户的 group 和 user id,并在构建时修改参数,假设用户和组 id 都为 1004:
1docker build --network=host --build-arg GID=1004 --build-arg UID=1004 -t lyndra-qemu - < Dockerfile
修改用户 id 和组 id 和当前的账户一致,是为了确保在写入共享文件夹的时候有相同的权限,否则可能造成在容器中无法正常写入的情况。
创建容器
下面的命令创建一个名为 lyndra_qemu_dev 的容器,并将主机的文件夹 /home/lyndra/Desktop/work/
共享到容器中的 /home/developer/work
文件夹。
1docker run -it --name lyndra_qemu_dev --mount type=bind,source=/home/lyndra/Desktop/work/,target=/home/developer/work lyndra-qemu
注:如果需要使用代理网络,可以通过ssh隧道将流量代理到本机(例如:个人Linux主机通过SSH隧道使服务器访问外网),在创建容器时添加参数--network host
,让docker位于host网络中。
1docker run -it --network host --name lyndra_qemu_dev --mount type=bind,source=/home/lyndra/Desktop/work/,target=/home/developer/work lyndra-qemu
在设置代理时,可以手动设置 export http_proxy="localhost:7897" export https_proxy="localhost:7897"
,也可以在启动容器时,添加参数直接设置代理环境 -e HTTP_PROXY=http://localhost:7897
1docker run -it --network host -e HTTP_PROXY=http://localhost:7897 -e HTTPS_PROXY=http://localhost:7897 --name lyndra_qemu_dev --mount type=bind,source=/home/lyndra/Desktop/work/,target=/home/developer/work lyndra-qemu
上述命令的含义可询问GPT或者查询网络。
QEMU 源码
QEMU 是一个大型开源软件,会不断的发布稳定版本,并在 master 主分支上不断更新新的功能。为了便于对旧版本进行维护、修复和发布更新,QEMU 为每一个稳定版本都创建了 stable 分支,并会不断的维护。
本文以 qemu 9.0 分支版本作为基础,进行开发。
官方源码仓库:github.com/qemu/qemu.git
官方仓库 fork 到私人仓库
以 gitee 为例
将私人仓库 clone 到本地:
1git clone https://gitee.com/code-tang/qemu-sayram2.0.git
私人仓库建立工作分支
在稳定分支 9.0 基础上,在本地新建新分支 sayram2
、sayram2-dev
,并将本地新建的两个分支 push 远程仓库。
1git fetch origin stable-9.0:sayram22git push --set-upstream origin sayram23
4git checkout -b sayram2-dev5git push --set-upstream origin sayram2-dev
然后查看本地分支情况:
1$ git branch -v2 master 58d49b5895 Merge tag 'net-pull-request' of https://github.com/jasowang/qemu into staging3 sayram2 6a54d5cf55 Update version for 9.0.3 release4* sayram2-dev 6a54d5cf55 Update version for 9.0.3 release
其中,sayram2-dev
作为开发分支,开发稳定后,merge 到稳定分支 sayram2
中。若后续官方的 stable-9.0
分支出现重大更新,可将其 pull 到本地,和 sayram2
、sayram2-dev
两个分支进行 merge
。
注意:需要更新官方仓库时,不要在仓库点击强制同步,这样会覆盖仓库代码。
若要更新官方仓库,需在本地添加官方 github 仓库源,并 pull 代码到对应的分支,完成 merge 工作后,再推送到私有仓库。
1git remote add official https://github.com/qemu/qemu.git2git checkout sayram2-dev3git pull official stable-9.0 --rebase
执行该命令后,本地分支将基于 official
的 stable-9.0
分支的最新提交进行变基,相当于先将远程的更改应用在当前分支上,再重新应用本地的更改,从而避免出现合并提交。出现冲突需要手动解决。
如果后续 stable-9.0
出现更新,并希望将更新应用到 sayram2
分支,则可以直接 pull stable-9.0
。
1git checkout sayram22git pull official stable-9.0
常规编译流程
参考官方文档:www.qemu.org/docs/master/devel/build-system.html
一般的开发仅关注于源码,不会对编译脚本做过多改动。
从源码编译 qemu 总共两步:1. configure 2. build
-
configure
1cd qemu2./configure --target-list=riscv32-softmmu --enable-debug关于
configure
的更多选项可以参考./configure --help
的输出。1developer@ubuntu-AS-4124GS-TNR:~/work/qemu/qemu-official$ ./configure --help2Using './build' as the directory for build output34Usage: configure [options]5Options: [defaults in brackets after descriptions]67Standard options:8--help print this message9--target-list=LIST set target list (default: build all)10Available targets: aarch64-linux-user11aarch64_be-linux-user alpha-linux-user -
build
可以使用 make 和 ninja 进行编译,两者都行。区别是 ninja 自动开启多线程加速编译,而 make 需要手动加上参数
-j
。1cd build2ninja34or5cd build6make -j个人喜欢用 ninja,输出的编译信息不会占满整个屏幕。
vscode远程开发项目
vscode 实际上就是一个编辑器,但是可以通过各种插件将其变为一个IDE。例如远程开发功能,vscode 需要安装扩展:Remote - SSH
、 Dev Containers
。vscode 可以运行在任意电脑上,通过远程网络连接到同一个工作服务器上开发。
SSH连接服务器
新建远程后,会在顶部弹出界面,提示输入 ssh 连接命令。如图所示输入账号和ip地址
顶部会弹出选项框,要求选择一个配置文件,保存当前连接的服务器信息。选择第一个即可。
这里的配置文件可以后续再进行修改,其中 Host
表示 ssh 服务器名称,可以任意更改,Hostname
保存服务器 ip地址, User
保存用户名。
然后远程资源管理器会更新输入的服务器信息。
点击连接按钮后,顶部弹出输入密码。
首次连接,或者更新 vscode 后,服务器会下载 vscode 服务器,需要一定时间。
vscode连接容器
当安装完 Dev Containers
扩展后,远程资源管理器会多一个开发容器的选项。
已经连接的容器会显示在 开发容器
这一栏,没有连接过的容器显示在 其他容器
这一栏。和 SSH 同理,点击容器连接即可,不需要输入密码。
连接后,所有的开发环境都存在于 docker 容器,在本文创建镜像时,共享了主机的 work
目录,因此,将代码存放于该目录。
vscode 中调试
vscode 只是编辑器,还需要配置 task.json
调试文件,和 launch.json
文件来决定调试需要运行的文件,调试工具的位置等。
qemu 在 Linux 上的调试文件如下:
.vscode/launch.json
:调试文件,F5快捷键进入调试模式
1{2 // Use IntelliSense to learn about possible attributes.3 // Hover to view descriptions of existing attributes.4 // For more information, visit: https://go.microsoft.com/fwlink/?linkid=8303875 "version": "0.2.0",6 "configurations": [7
8 {//"-init_data","${workspaceFolder}/Hmatrix0.dat "ultichip-u1,pipeline=true",",9 //配置编译./configure --enable-debug --target-list=riscv32-softmmu10 //配置编译./configure --enable-debug --disable-werror --target-list=riscv32-softmmu11
12 "name": "(gdb) Launch",13 "type": "cppdbg",14 "request": "launch",15 "program": "${workspaceFolder}/build/qemu-system-riscv32",29 collapsed lines
16
17 // QEMU 启动时的参数,需要根据需要,自行调整。18 "args": ["-machine", "ub", "-cpu", "ultichip-u1,pipeline=true", "-bios", "''", "-m", "128M","-d","in_asm", "-display", "none", "-ulog", "./test_sayram/outlog/runtime.log",19 "-rlog", "./test_sayram/outlog/reg.log","-savedmem","./test_sayram/outlog/dmemory.dat","-nographic","-kernel",20 "./test_sayram/input/dl/vp0", "-device", "loader,file=./test_sayram/input/dl/qemu_indmem.bin,addr=0x17b40", "-lbr", "32"],21
22 "stopAtEntry": true,23 "cwd": "${workspaceFolder}",24 "environment": [25 {26 "name":"PATH",27 "value":"%PATH%;\bin"28 }29 ],30 //"console": "externalTerminal",31 "MIMode": "gdb",32 "miDebuggerArgs": "-q -ex quit; wait() { fg >/dev/null; }; /bin/gdb -q --interpreter=mi",33// "miDebuggerPath": "F:/msys/mingw64/bin/gdb.exe",34 "setupCommands": [35 {36 "description": "Enable pretty-printing for gdb",37 "text": "-enable-pretty-printing",38 "ignoreFailures": true39 }40 ],41 "preLaunchTask": "build",42 }43 ]44}
.vscode/task.json
:编译任务,决定如何编译项目
1{2 // See https://go.microsoft.com/fwlink/?LinkId=7335583 // for the documentation about the tasks.json format4 "version": "2.0.0",5 "tasks": [6 {7 "label": "build",8 "type": "shell",9 // 手动运行一次:./configure --enable-debug --target-list=riscv32-softmmu --disable-werror10 "command": "cd build; ninja; cd ..",11 "problemMatcher": [],12 "group": {13 "kind": "build",14 "isDefault": true15 }3 collapsed lines
16 }17 ]18}