前言

关于QEMU的基本知识,可以参考其官方网站[1]。简单来说,QEMU自身支持“系统模拟”和“用户态模拟”;除此之外,QEMU可以配合KVM或Xen创建接近原始性能的虚拟机。

本笔记的主要参考资料是文献[4]。

本文采用文本形式记录命令行操作过程。对于那些输出不重要的执行步骤,本文仅给出命令本身;如果输出内容能够增进理解,本文会将命令与输出一并展示。

为避免混淆,除特别指出外,本文中所有“宿主机”均指运行实验虚拟机的外层机器,而不是笔者自己的实体机。

1. 从源码构建QEMU

获取QEMU的源码有两种方式:

  • 从GitLab上clone源码仓库[2]。
  • 从QEMU官网[3]下载源码包。

前者有利于我们切换查看不同分支、版本的源码,然而在编译阶段对于国内用户不太友好,无论是clone还是下载子模块(submodule)都非常耗时。因此,我们更推荐第二种方法,在确定版本后(如果是研究QEMU漏洞的话,需要先确定版本)直接下载源码包。

这里也给出从GitLab上clone源码仓库的步骤[10]:

git clone git://git.qemu-project.org/qemu.git
cd qemu
git submodule init
git submodule update --recursive
git submodule status --recursive
mkdir build
cd build
../configure

获取源码后即可进行编译。编译时间与当前机器性能有关。为了加快编译速度,建议仅针对自己打算用到的目标架构进行编译。

下面是我们在Ubuntu 18.04机器上下载v3.1.0版本源码并仅针对x86_64架构进行编译的步骤:

VERSION=3.1.0
sudo apt-get install -y git libglib2.0-dev libfdt-dev libpixman-1-dev zlib1g-dev gcc python
wget https://download.qemu.org/qemu-$VERSION.tar.xz
tar xf qemu-$VERSION.tar.xz
cd qemu-$VERSION
./configure --target-list="x86_64-softmmu" --python=`which python2`
make
make install

如果编译其他版本或针对其他目标架构进行编译,自行修改上述步骤即可。

编译完成后,执行qemu-system-x86_64确认编译成功:

➜  ~ qemu-system-x86_64 --version
QEMU emulator version 3.1.0
Copyright (c) 2003-2018 Fabrice Bellard and the QEMU Project developers

2. 使用qemu-img管理磁盘镜像

为了运行虚拟机,QEMU需要使用磁盘镜像文件存储虚拟机文件系统。QEMU支持多种类型的磁盘镜像文件,并提供了qemu-img工具来创建和管理它们。

执行qemu-img查看支持的磁盘镜像类型:

➜  ~ qemu-img -h | grep Supported
Supported formats: blkdebug blklogwrites blkreplay blkverify bochs cloop copy-on-read dmg file host_cdrom host_device luks nbd null-aio null-co nvme parallels qcow qcow2 qed quorum raw replication sheepdog throttle vdi vhdx vmdk vpc vvfat

其中,最常用的两种类型是rawqcow2。前者缺乏一些特性,但是方便快速测试;后者支持虚拟机快照、压缩和加密等特性,但是牺牲了一些性能[4]。

除此之外,还有一些常见的镜像类型也值得了解:

  • qcow:旧版的QEMU镜像格式。支持文件备份、镜像文件压缩和加密等特性。
  • dmg:Mac磁盘镜像格式。支持密码保护、压缩等特性,除了用于创建虚拟机,还常用来分发软件。
  • nbd:网络块设备格式。常用于访问远程存储设备。
  • vdi:VirtualBox使用的磁盘镜像格式。
  • vmdk:VMware使用的磁盘镜像格式。
  • vhdx:Microsoft Hyper-V使用的镜像格式。支持大容量存储、数据损坏保护和读写优化等特性。

介绍完毕,开始行动。创建一个10GB大小的空白镜像文件,类型为raw,并使用各种工具查看该镜像文件的属性:

➜  ~ qemu-img create -f raw debian.img 10G
Formatting 'debian.img', fmt=raw size=10737418240

➜  ~ ls -lah ./debian.img
-rw-r--r-- 1 root root 10G May  7 02:39 ./debian.img
➜  ~ file -s ./debian.img
./debian.img: data

➜  ~ qemu-img info ./debian.img
image: ./debian.img
file format: raw
virtual size: 10G (10737418240 bytes)
disk size: 0

3. 使用qemu-nbd处理磁盘镜像

关于Linux NBD和QEMU对NBD的使用,可以参考文献[8]和文献[9]。

我们已经创建了一个格式为raw的空镜像文件。接下来需要在上面创建分区和文件系统,从而安装操作系统。

首先加载nbd内核模块,然后将我们之前创建的debian.img空镜像文件与/dev/nbd0块设备关联起来:

modprobe nbd
qemu-nbd --format=raw --connect=/dev/nbd0 ./debian.img

接着在块设备上创建两个分区,分别用于swap和虚拟机操作系统的根分区:

➜  ~ sfdisk /dev/nbd0 << EOF
heredoc> ,1024,82
heredoc> ;
heredoc> EOF
Checking that no-one is using this disk right now ... OK

Disk /dev/nbd0: 10 GiB, 10737418240 bytes, 20971520 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes

>>> Created a new DOS disklabel with disk identifier 0xd6d2dca3.
/dev/nbd0p1: Created a new partition 1 of type 'Linux swap / Solaris' and of size 512 KiB.
/dev/nbd0p2: Created a new partition 2 of type 'Linux' and of size 10 GiB.
/dev/nbd0p3: Done.

New situation:
Disklabel type: dos
Disk identifier: 0xd6d2dca3

Device      Boot Start      End  Sectors  Size Id Type
/dev/nbd0p1       2048     3071     1024  512K 82 Linux swap / Solaris
/dev/nbd0p2       4096 20971519 20967424   10G 83 Linux

The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.

注:其实也可以用fdisk代替sfdisk来分区。操作不同,结果一致,这里不再展开。

完成后,查看分区后的块设备:

➜  ~ ls -la /dev/nbd0*
brw-rw---- 1 root disk 43, 0 May  7 02:57 /dev/nbd0
brw-rw---- 1 root disk 43, 1 May  7 02:57 /dev/nbd0p1
brw-rw---- 1 root disk 43, 2 May  7 02:57 /dev/nbd0p2

创建swap分区并在虚拟机操作系统根分区上建立EXT4文件系统:

➜  ~ mkswap /dev/nbd0p1
Setting up swapspace version 1, size = 508 KiB (520192 bytes)
no label, UUID=ef312e03-3f56-4408-8bfa-f65fd2ecb7ff

➜  ~ mkfs.ext4 /dev/nbd0p2
mke2fs 1.44.1 (24-Mar-2018)
Discarding device blocks: failed - Input/output error
Creating filesystem with 2620928 4k blocks and 655360 inodes
Filesystem UUID: 7e8c3c8a-ca64-43a1-9356-1ba1e75998dc
Superblock backups stored on blocks:
	32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632

Allocating group tables: done
Writing inode tables: done
Creating journal (16384 blocks): done
Writing superblocks and filesystem accounting information: done

最后,看一下镜像文件的类型,已经发生改变:

➜  ~ file -s debian.img
debian.img: DOS/MBR boot sector; partition 1 : ID=0x82, start-CHS (0x0,32,33), end-CHS (0x0,48,48), startsector 2048, 1024 sectors; partition 2 : ID=0x83, start-CHS (0x0,65,2), end-CHS (0x119,106,17), startsector 4096, 20967424 sectors

现在我们有包含两个分区和文件系统的镜像文件了。

4. 使用debootstrap安装操作系统

我们将使用debootstrap来引导一个Debian系统。

要确保上一节创建的块设备存在。如果重启过宿主机,则需要按照上一节开头部分再次使用qemu-nbd来将debian.img与块设备关联起来(不必再分区了)。

接下来安装debootstrap

apt install -y debootstrap

准备完成,可以开动了。

首先,将根分区对应的块设备(Network Block Device, NBD)挂载到当前文件系统下,并确保挂载成功(提问:为什么需要通过挂载nbd块设备间接挂载虚拟机镜像根文件系统,而非直接挂载虚拟机镜像?):

➜  ~ mount /dev/nbd0p2 /mnt
➜  ~ mount | grep mnt
/dev/nbd0p2 on /mnt type ext4 (rw,relatime)

接着,在挂载的根分区上安装最新版的Debian(这个过程可能会持续一段时间):

debootstrap --arch=amd64 --include="openssh-server vim" stable /mnt http://httpredir.debian.org/debian/
# 最后输出以下内容,说明安装成功:
# I: Base system installed successfully.

完成后,应该可以看到挂载点/mnt目录结构已经是一个常见的Linux系统根目录:

➜  ~ ls /mnt
bin  boot  dev  etc  home  lib  lib64  lost+found  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

/dev目录bind mount到镜像文件系统下,并确保nbd块设备也在其中:

➜  ~ mount --bind /dev /mnt/dev
➜  ~ ls /mnt/dev/ | grep nbd0
nbd0
nbd0p1
nbd0p2

chroot到镜像文件系统下。由于我默认使用了zsh,第一次操作失败:

➜  ~ chroot /mnt
chroot: failed to run command ‘/usr/bin/zsh’: No such file or directory

指定一个镜像文件系统中有的shell即可,第二次切换成功:

➜  ~ chroot /mnt /bin/bash
bash: warning: setlocale: LC_ALL: cannot change locale (en_US.UTF-8)
root@ubuntu:/# cat /etc/debian_version
11.3

把procfs和sysfs两个伪文件系统挂载到新的环境下:

mount -t proc none /proc
mount -t sysfs none /sys

安装Debian内核metapackage和grub2

apt install -y --force-yes linux-image-amd64 grub2

在根设备上安装GRUB,并更新GRUB配置:

root@ubuntu:/# grub-install /dev/nbd0 --force
Installing for i386-pc platform.
Installation finished. No error reported.
root@ubuntu:/# update-grub2
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-5.10.0-13-amd64
Found initrd image: /boot/initrd.img-5.10.0-13-amd64
Found Ubuntu 18.04.6 LTS (18.04) on /dev/sda1
done

使用passwd命令修改虚拟机镜像的root密码,我们修改为root

允许访问虚拟机镜像中的伪终端:

echo "pts/0" >> /etc/securetty

systemd调整到multi-user

root@ubuntu:/# systemctl set-default multi-user.target
Created symlink /etc/systemd/system/default.target → /lib/systemd/system/multi-user.target.

将根挂载点加入到fstab文件,使之在重启之后依然保留(提问:文献[4]使用的是/dev/sda2,这里为什么是sda2?):

echo "/dev/sda2 / ext4 defaults,discard 0 0" > /etc/fstab

卸载之前挂载的文件系统(注意,不要在将卸载的挂载点目录下执行umount,可能会执行失败),退出chroot环境:

umount /proc /sys /dev
exit

在镜像根分区上安装GRUB(提问:为什么这里又安装一次?):

➜  ~ grub-install /dev/nbd0 --root-directory=/mnt --modules="biosdisk part_msdos" --force
Installing for i386-pc platform.
Installation finished. No error reported.

更正GRUB配置文件中的设备名:

sed -i 's/nbd0p2/sda2/g' /mnt/boot/grub/grub.cfg

卸载nbd0设备,并取消关联:

➜  ~ umount /mnt
➜  ~ qemu-nbd --disconnect /dev/nbd0
/dev/nbd0 disconnected

至此,我们完成了在虚拟机镜像中安装Debian操作系统。

提问:为什么在为虚拟机安装操作系统的过程中需要把宿主机上的/dev和procfs、sysfs挂载到虚拟机根文件系统下?

5. 调整虚拟机镜像大小

我们将学习如何调整上一节创建的镜像文件、其中的分区和分区上的文件系统的大小。注意,对于创建可运行的虚拟机镜像来说,这一步是可选的。

在此之前,先安装一下必要的工具(不再列出Ubuntu自带的工具):

apt install -y kpartx

上一节操作完成后,debian.img镜像文件大小已经发生了变化:

➜  ~ qemu-img info debian.img
image: debian.img
file format: raw
virtual size: 10G (10737418240 bytes)
disk size: 1.3G

我们为镜像文件增加2GB,并再次查看其大小:

➜  ~ qemu-img resize -f raw debian.img +2G
Image resized.

➜  ~ qemu-img info debian.img
image: debian.img
file format: raw
virtual size: 12G (12884901888 bytes)
disk size: 1.3G

注意,不是所有的镜像类型都支持调整大小。如果需要调整其他类型的镜像的大小,可能需要先将其用qemu-img转换为raw类型。

我们发现,镜像文件大小已经改变。接下来,我们将调整分区和分区文件系统的大小。

查看第一个未被使用的回环设备(回环设备用来将普通文件模拟成块设备,详见Linux用户手册[6]):

➜  ~ losetup -f
/dev/loop19

把镜像文件与上述回环设备关联起来:

losetup /dev/loop19 debian.img

从关联的回环设备读取分区信息,并创建设备映射,然后查看设备映射:

➜  ~ kpartx -av /dev/loop19
add map loop19p1 (253:0): 0 1024 linear 7:19 2048
add map loop19p2 (253:1): 0 20967424 linear 7:19 4096

➜  ~ ls -la /dev/mapper | grep loop
lrwxrwxrwx  1 root root       7 May  8 00:04 loop19p1 -> ../dm-0
lrwxrwxrwx  1 root root       7 May  8 00:04 loop19p2 -> ../dm-1

从根分区映射中获取信息:

➜  ~ tune2fs -l /dev/mapper/loop19p2
tune2fs 1.44.1 (24-Mar-2018)
Filesystem volume name:   <none>
Last mounted on:          /mnt
Filesystem UUID:          7e8c3c8a-ca64-43a1-9356-1ba1e75998dc
Filesystem magic number:  0xEF53
Filesystem revision #:    1 (dynamic)
Filesystem features:      has_journal ext_attr resize_inode dir_index filetype extent 64bit flex_bg sparse_super large_file huge_file dir_nlink extra_isize metadata_csum
Filesystem flags:         signed_directory_hash
Default mount options:    user_xattr acl
Filesystem state:         clean
Errors behavior:          Continue
Filesystem OS type:       Linux
Inode count:              655360
Block count:              2620928
Reserved block count:     131046
Free blocks:              2353841
Free inodes:              636395
First block:              0
Block size:               4096
Fragment size:            4096
Group descriptor size:    64
Reserved GDT blocks:      1024
Blocks per group:         32768
Fragments per group:      32768
Inodes per group:         8192
Inode blocks per group:   512
Flex block group size:    16
Filesystem created:       Sat May  7 03:01:11 2022
Last mount time:          Sat May  7 07:59:00 2022
Last write time:          Sat May  7 08:34:14 2022
Mount count:              1
Maximum mount count:      -1
Last checked:             Sat May  7 03:01:11 2022
Check interval:           0 (<none>)
Lifetime writes:          1768 MB
Reserved blocks uid:      0 (user root)
Reserved blocks gid:      0 (group root)
First inode:              11
Inode size:	          256
Required extra isize:     32
Desired extra isize:      32
Journal inode:            8
Default directory hash:   half_md4
Directory Hash Seed:      c681cdb3-cd48-4754-8504-8b1e8d3dab09
Journal backup:           inode blocks
Checksum type:            crc32c
Checksum:                 0xb3c7927c

检查根分区文件系统:

➜  ~ e2fsck /dev/mapper/loop19p2
e2fsck 1.44.1 (24-Mar-2018)
/dev/mapper/loop19p2: clean, 18965/655360 files, 267087/2620928 blocks

删除根分区设备的日志,并确保日志已经移除:

➜  ~ tune2fs -O ^has_journal /dev/mapper/loop19p2
tune2fs 1.44.1 (24-Mar-2018)

➜  ~ tune2fs -l /dev/mapper/loop19p2 | grep "features"
Filesystem features:      ext_attr resize_inode dir_index filetype extent 64bit flex_bg sparse_super large_file huge_file dir_nlink extra_isize metadata_csum

移除分区映射,解除回环设备关联:

➜  ~ kpartx -dv /dev/loop19
del devmap : loop19p2
del devmap : loop19p1
➜  ~ losetup -d /dev/loop19

再次将镜像文件与网络块设备关联:

qemu-nbd --format=raw --connect=/dev/nbd0 ./debian.img

使用fdisk列出可用分区,删除根分区,重新创建,并确认修改:

➜  ~ fdisk /dev/nbd0

Welcome to fdisk (util-linux 2.31.1).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.


Command (m for help): p
Disk /dev/nbd0: 12 GiB, 12884901888 bytes, 25165824 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xc45020cb

Device      Boot Start      End  Sectors  Size Id Type
/dev/nbd0p1       2048     3071     1024  512K 82 Linux swap / Solaris
/dev/nbd0p2       4096 20971519 20967424   10G 83 Linux

Command (m for help): d
Partition number (1,2, default 2): 2

Partition 2 has been deleted.

Command (m for help): n
Partition type
   p   primary (1 primary, 0 extended, 3 free)
   e   extended (container for logical partitions)
Select (default p): p
Partition number (2-4, default 2): 2
First sector (3072-25165823, default 4096):
Last sector, +sectors or +size{K,M,G,T,P} (4096-25165823, default 25165823):

Created a new partition 2 of type 'Linux' and of size 12 GiB.
Partition #2 contains a ext4 signature.

Do you want to remove the signature? [Y]es/[N]o: N

Command (m for help): w

The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.

再次把镜像文件与上述回环设备关联起来:

losetup /dev/loop19 debian.img

再次从关联的回环设备读取分区信息,并创建设备映射:

➜  ~ kpartx -av /dev/loop19
add map loop19p1 (253:0): 0 1024 linear 7:19 2048
add map loop19p2 (253:1): 0 25161728 linear 7:19 4096

再次检查根分区文件系统:

➜  ~ e2fsck -f /dev/mapper/loop19p2
e2fsck 1.44.1 (24-Mar-2018)
Pass 1: Checking inodes, blocks, and sizes
Pass 2: Checking directory structure
Pass 3: Checking directory connectivity
Pass 4: Checking reference counts
Pass 5: Checking group summary information
/dev/mapper/loop19p2: 19007/655360 files (0.2% non-contiguous), 251765/2620928 blocks

调整根分区文件系统大小:

➜  ~ resize2fs /dev/nbd0p2
resize2fs 1.44.1 (24-Mar-2018)
Resizing the filesystem on /dev/nbd0p2 to 3145216 (4k) blocks.
The filesystem on /dev/nbd0p2 is now 3145216 (4k) blocks long.

重新创建文件系统日志(前面删除了):

➜  ~ tune2fs -j /dev/mapper/loop19p2
tune2fs 1.44.1 (24-Mar-2018)
Creating journal inode: done

最后移除分区映射,解除回环设备关联:

➜  ~ kpartx -dv /dev/loop19
del devmap : loop19p2
del devmap : loop19p1
➜  ~ losetup -d /dev/loop19

6. 使用qemu-system-*命令运行虚拟机

除了自制虚拟机镜像外,各大Linux发行版也都提供了预制镜像:

我们可以直接下载预制镜像使用,例如:

wget https://cloud.debian.org/images/cloud/buster/latest/debian-10-nocloud-amd64.qcow2
wget https://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud.qcow2.xz

可以执行qemu-system-*系列命令来获得支持的CPU架构:

qemu-system-x86_64 --cpu help

尝试启动一个Debian虚拟机实例(注意,命令中的index=2是虚拟机操作系统所在的分区索引号):

# IP需替换为宿主机IP
qemu-system-x86_64 -name debian -vnc 172.16.56.199:0 -cpu Nehalem -m 1024 -drive format=raw,index=2,file=debian.img -daemonize

确认该实例在运行:

➜  ~ pgrep -lfa qemu
1684 qemu-system-x86_64 -name debian -vnc 172.16.56.199:0 -cpu Nehalem -m 1024 -drive format=raw,index=2,file=debian.img -daemonize

结束该实例:

pkill qemu

还可以尝试使用CentOS官方的预制镜像[7]启动一个虚拟机实例(大多数预制镜像使用第一分区或仅有一个分区,故不必指出索引号,-hda-drive选项效果类似):

# IP需替换为宿主机IP
qemu-system-x86_64 -vnc 172.16.56.199:0 -m 1024 -hda CentOS-7-x86_64-GenericCloud.qcow2 -daemonize

7. 启动KVM加速的QEMU虚拟机

KVM主要由两个LKM内核模块组成,第一个是kvm.ko,提供主要的虚拟化功能;第二个是kvm-intel.kokvm-amd.ko,分别对应不同的CPU厂商。

在一些Linux发行版中存在名为qemu-kvm的软件包,它提供了一个名为kvm的脚本,在Ubuntu上可以通过apt install -y qemu-kvm来安装。该工具实际上是qemu-system-x86_64命令的封装,自动为其加上了enable-kvm参数,其内容通常如下:

#!/bin/sh
exec qemu-system-x86_64 -enable-kvm "$@"

为完成本节实验,需要确保宿主机支持硬件虚拟化功能。在笔者的环境中,宿主机本身就是一台虚拟机,因此需要在虚拟机管理器中开启该虚拟机的硬件虚拟化功能。可以在宿主机中执行命令查看CPU是否支持硬件虚拟化:

➜  ~ egrep "vmx|svm" /proc/cpuinfo | uniq
flags		: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon nopl xtopology tsc_reliable nonstop_tsc cpuid pni pclmulqdq vmx ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch cpuid_fault invpcid_single pti ssbd ibrs ibpb stibp tpr_shadow vnmi ept vpid ept_ad fsgsbase tsc_adjust bmi1 avx2 smep bmi2 invpcid rdseed adx smap clflushopt xsaveopt xsavec xgetbv1 xsaves arat md_clear flush_l1d arch_capabilities

如上面的输出所示,输出包括vmx,说明当前宿主机支持硬件虚拟化功能。

手动载入KVM内核模块,并确保载入成功:

➜  ~ modprobe kvm
➜  ~ lsmod | grep kvm
kvm_intel             253952  0
kvm                   659456  1 kvm_intel

OK,环境准备完成,开始行动。

创建一个KVM支持的QEMU虚拟机实例:

# IP需替换为宿主机IP
qemu-system-x86_64 -name debian -vnc 172.16.56.199:0 -m 1024 -drive format=raw,index=2,file=debian.img -enable-kvm -daemonize

确认该实例在运行:

➜  ~ pgrep -lfa qemu
1930 qemu-system-x86_64 -name debian -vnc 172.16.56.199:0 -m 1024 -drive format=raw,index=2,file=debian.img -enable-kvm -daemonize

结束该实例:

pkill qemu

可以看到,实际上我们只是增加了-enable-kvm选项而已。

8. 使用VNC连接到正在运行的虚拟机实例

首先启动一个虚拟机:

qemu-system-x86_64 -name debian -vnc 172.16.56.199:0 -m 1024 -drive format=raw,index=2,file=debian.img -enable-kvm -daemonize

接下来,就可以使用VNC客户端(例如VNC Viewer)连接172.16.56.199:5900来访问虚拟机了。

9. 总结

从零开始创建并运行一个虚拟机的步骤如下:

apt install -y debootstrap
qemu-img create -f raw debian.img 10G
modprobe nbd
qemu-nbd --format=raw --connect=/dev/nbd0 ./debian.img
sfdisk /dev/nbd0 << EOF
,1024,82
;
EOF
mkswap /dev/nbd0p1
mkfs.ext4 /dev/nbd0p2
mount /dev/nbd0p2 /mnt
debootstrap --arch=amd64 --include="openssh-server vim" stable /mnt http://httpredir.debian.org/debian/
mount --bind /dev /mnt/dev
chroot /mnt /bin/bash
mount -t proc none /proc
mount -t sysfs none /sys
apt install -y --force-yes linux-image-amd64 grub2
grub-install /dev/nbd0 --force
update-grub2
passwd # change password to root
echo "pts/0" >> /etc/securetty
systemctl set-default multi-user.target
echo "/dev/sda2 / ext4 defaults,discard 0 0" > /etc/fstab
umount /proc /sys /dev
exit
grub-install /dev/nbd0 --root-directory=/mnt --modules="biosdisk part_msdos" --force
sed -i 's/nbd0p2/sda2/g' /mnt/boot/grub/grub.cfg
umount /mnt
qemu-nbd --disconnect /dev/nbd0

参考文献

  1. https://www.qemu.org
  2. https://gitlab.com/qemu-project/qemu.git
  3. https://download.qemu.org/
  4. KVM Virtualization Cookbook
  5. https://github.com/PacktPublishing/KVM-Virtualization-Cookbook.git
  6. https://man7.org/linux/man-pages/man4/loop.4.html
  7. https://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud.qcow2.xz
  8. https://zhuanlan.zhihu.com/p/50460919
  9. https://en.wikipedia.org/wiki/Network_block_device
  10. https://en.wikibooks.org/wiki/QEMU/Installing_QEMU