编程开源技术交流,分享技术与知识

网站首页 > 开源技术 正文

qemu linux内核(5.10.209)开发环境搭建

wxchong 2025-05-02 13:57:25 开源技术 4 ℃ 0 评论

版本信息

宿主机:ubuntu 20.04.6 LTS (Focal Fossa)

虚拟机:ubuntu 20.04.6 LTS (Focal Fossa)

安装宿主机的步骤省略,和一般的在vmware中安装虚拟机没有任何区别。

需要注意的是需要打开Intel VT-x

如果启动虚拟机报告此平台不支持虚拟化的Intel VT-x/EPT. 不使用虚拟化的Intel VT-x/EPT,是否继续,参考下面的文章解决.

https://blog.csdn.net/2301_77695535/article/details/146309899

宿主机安装QEMU/KVM和Virsh

Virsh是Virtual Shell的缩写,是一个用于管理虚拟机的命令行工具。你可以使用Virsh创建、编辑、启动、停止、关闭和删除VM。Virsh目前支持KVM,LXC,Xen,QEMU,OpenVZ,VirtualBox和VMware ESX。这里我们使用Virsh管理QEMU/KVM虚拟机。

在安装之前,首先要确认你的CPU是否支持虚拟化技术。使用grep查看cpuinfo是否有"vmx"(Intel-VT 技术)或"svm"(AMD-V 支持)输出:

egrep "(svm|vmx)" /proc/cpuinfo

某些CPU型号在默认情况下,BIOS中可能禁用了VT支持。我们需要再检查BIOS设置是否启用了VT的支持。使用kvm-ok命令进行检查:

sudo apt install cpu-checker
kvm-ok

如果输出为:

INFO: /dev/kvm exists
KVM acceleration can be used

证明CPU的虚拟化支持已经在BIOS中启用。

运行下面的命令安装QEMU/KVM和Virsh:

sudo apt install qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virtinst virt-manager

检查libvirt守护程序是否已经启动:

sudo systemctl is-active libvirtd
active

如果没有输出active,运行下面的命令启动libvertd服务:

sudo systemctl enable libvirtd
sudo systemctl start libvirtd

在宿主机中安装qemu虚拟机

创建一个虚拟机镜像,大小为40G,qow2格式动态分配磁盘占用空间。

qemu-img create -f qcow2 ubuntutest.img 40G

创建虚拟机系统,安装操作系统:

qemu-system-x86_64 \
-name ubuntutest \
-smp 2 \  
-m 4096 \
-hda ubuntutest.img \
-cdrom ubuntu-20.04.6-live-server-amd64.iso \
-boot d

按照步骤,配置安装即可,这一步和正常的虚拟机安装没有什么区别。

注意这里我没有添加-enable-kvm,这可能会影响gdb的软件断点。

配置上网

方案1:自建tap网卡NAT上网

宿主机创建TAP设备

sudo ip tuntap add dev tap0 mode tap
sudo ip addr add 192.168.100.1/24 dev tap0
sudo ip link set tap0 up

宿主机配置IP转发及NAT规则

sudo sysctl -w net.ipv4.ip_forward=1
sudo iptables -t nat -A POSTROUTING -s 192.168.100.0/24 -o ens33 -j MASQUERADE
sudo iptables -A FORWARD -i tap0 -j ACCEPT

启动带TAP的QEMU

qemu-system-x86_64 \
-enable-kvm \
-m 4096 \
-drive file=./ubuntutest.img \
-boot d \
-net nic -net tap,ifname=tap0,script=no,downscript=no

配置虚拟机的网络为192.168.100.0/24网段中的任意一个地址(例如192.168.100.10)

打开netplan的配置文件,修改地址后,使用sudo netplan apply重启网络。

# This is the network config written by 'subiquity'
network:
  ethernets:
    ens3:
      dhcp4: no
      addresses:
        - 192.168.100.10/24
      routes:
        - to: default
          via: 192.168.100.1
      nameservers:
        addresses: [8.8.8.8]
  version: 2

利用virbr0实现nat上网

创建并配置bridge.conf

  • 文件路径:通常位于/etc/qemu/或/usr/local/etc/qemu/(取决于安装方式)。使用以下命令创建:
sudo mkdir -p /etc/qemu
sudo vim /etc/qemu/bridge.conf

内容示例:

allow virbr0  # 若使用libvirt默认桥接接口

接下来启动时指定使用virbr0 bridge上网。

qemu-system-x86_64 \
-enable-kvm \
-m 4096 \
-drive file=./ubuntutest.img -boot d \
-netdev bridge,id=net0,br=virbr0 \
-device virtio-net-pci,netdev=net0

配置虚拟机网络时,配置和virbr0一个网段的,即192.168.122.0/24(例如192.168.122.10)

下载linux内核并且编译

安装编译依赖

首先我们需要安装编译内核用到的依赖包,我是在宿主机上编译linux内核代码的,因此下面的语句直接在宿主机上执行。

sudo apt install libncurses5-dev libssl-dev bison flex libelf-dev gcc make openssl libc6-dev dwarves

下载linux内核代码并构建

下载Linux内核代码,这里我使用的是5.10.209版本

https://www.kernel.org/pub/linux/kernel/v5.x/linux-5.10.209.tar.gz

使用下面的语句make kernel编译参数:

sudo make menuconfig

为了构建能够调试的内核,我们需要配置以下几个参数。

  • CONFIG_DEBUG_INFO 在内核和内核模块中包含调试信息,这个选项在幕后为gcc使用的编译器参数增加了-g选项。

这个选项的菜单路径为:

Kernel hacking  --->
Compile-time checks and compiler options  ---> 
 [*] Compile the kernel with debug info

也可以直接在.config中设置CONFIG_DEBUG_INFO。

CONFIG_DEBUG_INFO=y

  • CONFIG_FRAME_POINTER 这个选项会将调用帧信息保存在寄存器或堆栈上的不同位置,使gdb在调试内核时可以更准确地构造堆栈回溯跟踪(stack back traces)。

也可以在.config中设置:

CONFIG_FRAME_POINTER=y

  • 启用CONFIG_GDB_SCRIPTS,但要关闭CONFIG_DEBUG_INFO_REDUCED。
CONFIG_GDB_SCRIPTS=y
CONFIG_DEBUG_INFO_REDUCED=n

  • CONFIG_KGDB 启用内置的内核调试器,该调试器允许进行远程调试。
Kernel hacking  --->
Generic Kernel Debugging Instruments  ---> 
 [*] KGDB: kernel debugger
CONFIG_KGDB=y
  • 关闭CONFIG_RANDOMIZE_BASE设置

CONFIG_RANDOMIZE_BASE的位置在如下位置可以找到:

Processor type and features --->
Randomize the address of the kernel image (KASLR)

或者直接在.config中添加下面的语句

CONFIG_RANDOMIZE_BASE=n

KASLR会更改引导时放置内核代码的基地址。如果你在内核配置中启用了KASLR(CONFIG_RANDOMIZE_BASE=y),则无法从gdb设置断点。设置完必要的内核参数后,我们开始编译内核:

sudo make -j8 
sudo make INSTALL_MOD_STRIP=1 modules_install
sudo make install

make modules_install会将module文件安装到/lib/modules/5.10.209,并且最好添加上INSTALL_MOD_STRIP=1,否则initrd.img体积会很大。

编译大概需要30G空间,因此需要事先准备好>=30G的磁盘。

这些步骤执行完毕之后,我们就得到了需要的linux内核镜像bzImage(vmlinuz)和initrd.img

xu@xu-dev:~/work/linux-5.10.209$ ls /boot/ -alh |grep 5.10.209
-rw-r--r--  1 root root 243K 4月  20 19:34 config-5.10.209
lrwxrwxrwx  1 root root   19 4月  20 19:34 initrd.img -> initrd.img-5.10.209
-rw-r--r--  1 root root  60M 4月  20 19:34 initrd.img-5.10.209
-rw-r--r--  1 root root 5.5M 4月  20 19:34 System.map-5.10.209
lrwxrwxrwx  1 root root   16 4月  20 19:34 vmlinuz -> vmlinuz-5.10.209
-rw-r--r--  1 root root  14M 4月  20 19:34 vmlinuz-5.10.209

编译的产物介绍

在Linux内核编译过程中,生成的文件根据功能可分为以下几类,以下是详细介绍及对应的文件作用与来源:

核心可执行文件

  1. vmlinux 描述:原始的、未经压缩的内核可执行文件,包含完整的符号表和调试信息,体积较大。 生成路径:位于内核源码根目录或 arch/<架构>/boot/compressed/ 目录下。 用途:用于调试和分析内核崩溃问题,不直接用于启动系统。 来源:编译过程通过链接 head-y、init-y、core-y 等目标文件生成 。
  2. zImage** 与 **bzImage 描述:压缩后的内核镜像文件,bzImage(Big zImage)支持更大体积的内核(超过512KB时使用)。 生成路径:位于 arch/<架构>/boot/ 目录下(如 arch/x86/boot/bzImage)。 结构:由 setup.bin(引导程序)和压缩后的 vmlinux 拼接而成,附加解压头信息。 用途:直接用于系统引导,是大多数Linux发行版的默认内核文件 。
  3. uImage 描述:专为U-Boot引导程序设计的镜像文件,在 zImage 基础上添加U-Boot头部信息。 生成路径:需通过 mkimage 工具处理 zImage 生成。 用途:嵌入式系统中配合U-Boot使用 。

引导相关文件

  1. System.map 描述:内核符号映射文件,记录所有函数和变量的内存地址。 生成路径:内核源码根目录下。 用途:调试时关联地址与符号,例如分析内核崩溃日志 。
  2. initrd.img** 或 **initramfs 描述:初始内存文件系统,包含启动早期所需的临时驱动和工具(如磁盘驱动、文件系统模块)。 生成路径:通过 mkinitramfs 或 dracut 生成,存放于 /boot/ 目录。 用途:解决根文件系统挂载前的依赖问题 。

内核模块文件

  1. .ko** 文件(Kernel Object)** 描述:动态可加载内核模块,按需插入内核运行。 生成路径:各驱动或功能模块目录下(如 drivers/net/ethernet/)。 安装路径:通过 make modules_install 安装到 /lib/modules/<内核版本>/ 目录。 用途:灵活扩展内核功能(如添加新硬件驱动) 。

配置文件与日志

  1. .config 描述:内核编译的配置文件,记录所有选中的功能选项(如 CONFIG_DEBUG_INFO=y)。 生成路径:内核源码根目录下。 来源:通过 make menuconfig 或复制默认配置(如 arch/arm/configs/s5pv210_defconfig)生成 。
  2. vmlinux.lds 描述:链接脚本文件,定义内核各段(代码、数据、堆栈)的内存布局。 生成路径:arch/<架构>/kernel/ 目录下。 用途:指导链接器生成 vmlinux 。

中间文件与工具生成文件

  1. .o** 与 **built-in.o 描述:编译过程中生成的中间目标文件,built-in.o 是同一目录下所有 .o 文件的合并。 生成路径:各子模块目录下(如 init/built-in.o)。 用途:逐步构建最终内核镜像 。
  2. 设备树文件(.dtb 描述:描述硬件拓扑结构的二进制文件,用于嵌入式系统(如ARM架构)。 生成路径:通过设备树编译器(DTC)从 .dts 文件生成,位于 arch/<架构>/boot/dts/。 用途:适配不同硬件平台 。

启动管理与版本标识

  1. vmlinuz-<版本号> 描述:安装到 /boot/ 目录的压缩内核镜像,通常为 bzImage 的符号链接。 用途:Grub等引导程序通过该文件加载内核 。
  2. config-<版本号> 描述:.config 文件的备份,保存编译时的完整配置。 生成路径:/boot/ 目录下 。

总结对比表

文件类型

关键文件

作用

生成命令

核心镜像

vmlinux, bzImage

内核执行与引导

make, make bzImage

模块

.ko

动态扩展内核功能

make modules

配置

.config

记录编译选项

make menuconfig

引导支持

initrd.img

早期启动依赖加载

mkinitramfs

符号映射

System.map

调试符号地址映射

自动生成

设备树

.dtb

嵌入式硬件描述

make dtbs

内核debug

在获取内核镜像bzImage和initrd.img之后,就可以使用其启动qemu虚拟机。

注意我这里的调试是使用qemu的-kernel和-initrd去直接加载内核,而没有使用grub去加载内核。

我的qemu的启动脚本如下所示:

qemu-system-x86_64 \
-smp 2 \
-m 4096 \
-S -s \
-drive file=/home/xu/work/ubuntutest.img \
-netdev bridge,id=net0,br=virbr0 \
-device virtio-net-pci,netdev=net0  \
-kernel /home/xu/work/kernel-with-rwx/bzImage \
-initrd /home/xu/work/kernel-with-rwx/initrd.img-5.10.209 \
-append "root=/dev/mapper/ubuntu--vg-ubuntu--lv ro maybe-ubiquity console=ttyS0\
-nographic
  • -smp 2 分配2个虚拟CPU核心(vCPU),默认情况下每个核心为单线程、单插槽。若需更细粒度控制(如多插槽、多线程),可扩展为-smp 2,sockets=1,cores=2,threads=1
  • -m 4096 为虚拟机分配4096MB(4GB)内存。QEMU默认分配128MB,此参数需根据宿主机资源和虚拟机需求调整
  • -S 启动时暂停CPU执行,等待外部调试器(如GDB)连接后继续运行,常用于内核调试
  • -s 启用GDB调试服务器,默认监听本地端口1234。结合-S可实现从启动阶段调试内核
  • -drive file=/home/xu/work/ubuntutest.img 加载名为ubuntutest.img的磁盘镜像作为主硬盘。默认接口类型为if=virtio(高性能虚拟化驱动),若未指定格式,QEMU自动检测(如qcow2或raw)
  • -netdev bridge,id=net0,br=virbr0 创建桥接网络后端,连接到宿主机的virbr0网桥,允许虚拟机通过宿主机网络接口访问外部 。
  • -device virtio-net-pci,netdev=net0 为虚拟机添加虚拟网卡,使用virtio-net-pci驱动(高性能半虚拟化网卡),绑定到上述网络后端。
  • -kernel /home/xu/work/kernel-with-rwx/bzImage 指定Linux内核镜像文件bzImage,绕过虚拟机的BIOS引导,直接加载内核
  • -initrd /home/xu/work/kernel-with-rwx/initrd.img-5.10.209 使用initrd.img-5.10.209作为初始内存文件系统,包含启动初期所需的模块和工具
  • -append "root=/dev/mapper/ubuntu--vg-ubuntu--lv ro maybe-ubiquity console=ttyS0"``root=/dev/mapper/ubuntu--vg-ubuntu--lv:指定根文件系统位置(LVM逻辑卷)。ro:以只读模式挂载根文件系统,通常需后续切换为读写模式。console=ttyS0:将控制台输出重定向到串口ttyS0,配合-nographic使用。(ubuntu系统通过df -h查看根文件系统的路径)。

上面提到,还有一种是使用grub启动内核,相对要麻烦一点,需要把宿主机上的内核拷贝到虚拟机上,并执行make modules_install和make install,然后启动时使用-boot d从硬盘加载内核。

qemu-system-x86_64 \
-enable-kvm \
-smp 2 \
-m 4096 \
-drive file=./ubuntutest.img -boot d \
-netdev bridge,id=net0,br=virbr0 \
-device virtio-net-pci,netdev=net0

不过我觉得使用-kernel和-initrd更为方便,也更加推荐。

接下来启动gdb调试,使用另一个终端打开gdb:

xu@xu-dev:~/work/linux-5.10.209$ gdb vmlinux  -q
Reading symbols from vmlinux...
warning: File "/home/xu/work/linux-5.10.209/scripts/gdb/vmlinux-gdb.py" auto-loading has been declined by your `auto-load safe-path' set to "$debugdir:$datadir/auto-load".
To enable execution of this file add
    add-auto-load-safe-path /home/xu/work/linux-5.10.209/scripts/gdb/vmlinux-gdb.py
line to your configuration file "/home/xu/.gdbinit".
To completely disable this security protection add
    set auto-load safe-path /
line to your configuration file "/home/xu/.gdbinit".
For more information about this security protection see the
"Auto-loading safe path" section in the GDB manual.  E.g., run from the shell:
    info "(gdb)Auto-loading safe path"

使用gdb远程连接:

(gdb) target remote :1234
Remote debugging using :1234
0x000000000000fff0 in exception_stacks ()

下断点

(gdb) b start_kernel
Breakpoint 1 at 0xffffffff82daad61: file init/main.c, line 847.

继续执行,触发断点:

(gdb) c
Continuing.

Thread 1 hit Breakpoint 1, start_kernel () at init/main.c:847

单步调试:

847	{
(gdb) n
851		set_task_stack_end_magic(&init_task);
(gdb) n
852		smp_setup_processor_id();
(gdb) n
855		cgroup_init_early();
(gdb)

问题

证书找不到

  CC [M]  kernel/kheaders.o
  CC      certs/system_keyring.o
make[1]: *** No rule to make target 'debian/canonical-certs.pem', needed by 'certs/x509_certificate_list'.  Stop.
make: *** [Makefile:1832: certs] Error 2

解决办法:

1.修改
CONFIG_SYSTEM_TRUSTED_KEYS

修改前:原变量有值

CONFIG_SYSTEM_TRUSTED_KEYS="
debian/canonical-certs.pem"

修改后:将该变量赋空值

CONFIG_SYSTEM_TRUSTED_KEYS=""

2.修改
CONFIG_SYSTEM_REVOCATION_KEYS(可选)

如果
CONFIG_SYSTEM_REVOCATION_KEYS的值不为空的话,也将其赋空值。

修改前:原变量有值

CONFIG_SYSTEM_REVOCATION_KEYS="
debian/canonical-revoked-certs.pem"

修改后:将该变量赋空值

CONFIG_SYSTEM_REVOCATION_KEYS=""

内核停留在“loading initial ramdisk”很长时间并且启动之后kernel crash报告引导内核时报错Kernel panic not syncing : System is deadlocked on memory

编译内核的过程中,当安装内核模块未使用INSTALL_MOD_STRIP=1时,会导致initrd文件过大,导致启动时卡在解压initrd上花费过度的时间。

例如这里,我的initrd.img是1.2G。

为了解决该问题,需要在安装module的时候添加INSTALL_MOD_STRIP=1

sudo make INSTALL_MOD_STRIP=1 modules_install
sudo make install

kernel crash报告引导内核时报错Kernel panic not syncing : System is deadlocked on memory

这个问题和initrd.img相关。一种方式是通过减小initrd.img解决,还有一种方式就是通过调大虚拟机内存解决。

gdb软件中断无法触发,硬件中断可以触发

qemu如果启动时指定了-enable-kvm,会导致该问题。

如果启动的时候指定了-enable-kvm,则设置断点时需要使用硬件断点,例如

hb start_kernel

PS

如何清理内核编译产物

若在编译并安装 Linux 内核后希望卸载,以下是具体操作步骤及注意事项:

步骤 1:确认内核版本及安装路径

  1. 查看已安装的内核版本
ls /boot | grep vmlinuz    # 列出所有内核文件
uname -r                  # 查看当前正在运行的内核版本(避免误删)
  1. 确认手动编译安装的内核版本
    假设目标卸载的内核版本为 5.18.8,需确保其不在 uname -r 的输出中,否则删除后可能导致系统无法启动。

步骤 2:手动删除内核相关文件

手动编译安装的内核文件分散在多个目录中,需按路径逐一删除:

  1. 删除 /boot 目录下的内核文件
sudo rm -rf /boot/vmlinuz-5.18.8
sudo rm -rf /boot/initrd.img-5.18.8
sudo rm -rf /boot/config-5.18.8
sudo rm -rf /boot/System.map-5.18.8
  1. 删除内核模块目录
sudo rm -rf /lib/modules/5.18.8
  1. 删除内核源码目录(可选)
sudo rm -rf /usr/src/linux-5.18.8
  1. 清理 initramfs 残留
sudo rm -rf /var/lib/initramfs-tools/5.18.8

步骤 3:更新 GRUB 引导配置

删除内核后需更新 GRUB,避免残留无效启动项:

sudo update-grub

步骤 4:重启系统

sudo reboot

重启后通过 uname -r 确认当前内核版本是否已切换至其他可用版本。

参考文章

https://github.com/mz1999/blog/blob/master/docs/gdb-kernel-debugging.md

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表