首先尝试使用 docker 自带的兼容层

https://www.cnblogs.com/LynneHuan/p/17822138.html

docker pull skysider/pwndocker
 
pwnenv() {
    ctf_name=${1:-$(basename "$PWD")}  # 使用当前目录名作为默认名称
    docker run -it --rm \
        -v "$(pwd):/ctf/work" \
        -w /ctf/work \
        --cap-add=SYS_PTRACE \
        --add-host=host.docker.internal:host-gateway\
        skysider/pwndocker \
        "$@"
}
pwnenv_port() {
  ctf_name=${1:-$(basename "$PWD")}  # 使用当前目录名作为默认名称
  docker run -it --rm \
    -p 23946:23946 \
    -v "$(pwd):/ctf/work" \
    -w /ctf/work \
    --cap-add=SYS_PTRACE \
    skysider/pwndocker \
    "$@"
}

用 host.docker.internal 访问(默认在 bridge 模式下


我去,折腾一圈还是不行,有些断点就是断不下来……看来还是得自己模拟一次。下面开始折腾:

准备工作

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
 
brew install qemu

https://ubuntu.com/download/server 下载 Ubuntu 24.04.2 LTS (amd64): https://mirror.nyist.edu.cn/ubuntu-releases/24.04.2/ubuntu-24.04.2-live-server-amd64.iso

创建虚拟机工作目录

mkdir -p ~/qemu_vms/ubuntu_x86_64

创建 QEMU 虚拟机磁盘镜像

可以使用 qemu-img create 命令创建一个新的磁盘镜像。推荐使用 qcow2 (QEMU Copy On Write 2) 格式,因为它支持快照、动态扩展、压缩和加密等高级功能 。以下命令将在之前创建的虚拟机工作目录中创建一个名为 ubuntu_x86_64.qcow2、大小为 64GB 的 qcow2 格式磁盘镜像:

qemu-img create -f qcow2 ~/qemu_vms/ubuntu_x86_64/ubuntu_x86_64.qcow2 64G

这个大小是最大容量,会动态增长。

设置 UEFI 固件

EMU 通过 OVMF (Open Virtual Machine Firmware) 项目来支持 UEFI 引导 。OVMF 是 EDK II (EFI Development Kit II) 的一个目标,它为 QEMU 虚拟机提供了一个开源的 UEFI 固件实现。

当通过 Homebrew 安装 QEMU 时,通常会附带预编译的 OVMF 固件文件。这些文件一般位于 QEMU 的共享数据目录中。在 Apple Silicon Mac 上,此路径通常是 /opt/homebrew/share/qemu/

启动

#!/bin/bash
VM_NAME="ubuntu_x86_64_install"
QEMU_INSTALL_DIR="/opt/homebrew/share/qemu" # QEMU共享文件目录
VM_DIR="$HOME/qemu_vms/ubuntu_x86_64" # 虚拟机文件存放目录
DISK_IMG="$VM_DIR/ubuntu_x86_64.qcow2"
OVMF_CODE_FILE="edk2-x86_64-code.fd" # UEFI固件代码文件名
OVMF_CODE_PATH="$QEMU_INSTALL_DIR/$OVMF_CODE_FILE" # 只读固件代码完整路径
ISO_PATH="$VM_DIR/ubuntu-24.04.2-live-server-amd64.iso" # 替换为Ubuntu Server ISO实际路径
 
qemu-system-x86_64 \
    -name "$VM_NAME" \
    -machine q35,accel=tcg \
    -cpu max \
    -smp 2 \
    -m 4G \
    -drive if=pflash,format=raw,readonly=on,file="$OVMF_CODE_PATH" \
    -drive file="$DISK_IMG",format=qcow2,if=virtio,cache=writeback \
    -cdrom "$ISO_PATH" \
    -boot menu=on,order=d \
    -netdev user,id=usernet \
    -device virtio-net-pci,netdev=usernet \
    -display cocoa,show-cursor=on \
    -usb \
    -device usb-tablet \
    -vga std
 
echo "虚拟机 $VM_NAME 已关闭或启动失败。"
  • qemu-system-x86_64: 指定使用 x86_64 架构的系统模拟器。
  • -name "$VM_NAME": 为虚拟机窗口指定一个名称。
  • -machine q35,accel=tcg:
    • q35: 选择现代的 Q35 机器类型,它基于较新的芯片组(ICH9),支持 PCIe 等现代特性,通常是 x86_64 虚拟机的推荐选项 。
    • accel=tcg: 指定使用 Tiny Code Generator (TCG) 作为加速器。由于宿主机是 ARM64 (M2 Mac),而客户机是 x86_64,必须使用 TCG 进行纯软件模拟 。
  • -cpu max: 指示 QEMU 模拟一个具有其所能支持的最多 x86_64 特性的 CPU 。对于 TCG,这通常意味着模拟一个功能丰富的通用 x86_64 CPU。在安装阶段,也可以选用一个更通用的型号如 qemu64,但在日常使用中 max 或特定的现代 CPU 型号(如 Haswell-v4)通常更好。
  • -smp 2: 为虚拟机分配 2 个虚拟 CPU 核心。
  • -m 4G: 为虚拟机分配 4GB 内存。
  • -drive if=pflash,format=raw,readonly=on,file="$OVMF_CODE_PATH": 加载只读的 OVMF 固件代码 (现在是 edk2-x86_64-code.fd) 。
  • -drive file="$DISK_IMG",format=qcow2,if=virtio,cache=writeback:
    • file="$DISK_IMG",format=qcow2: 指定之前创建的 qcow2 磁盘镜像。
    • if=virtio: 将磁盘接口类型设置为 virtio-blk。VirtIO 是一种为虚拟机设计的半虚拟化接口,能提供比传统模拟设备(如 IDE)更好的性能 。
    • cache=writeback: 磁盘缓存模式。writeback 通常能提供较好的性能,但有在宿主机崩溃时数据丢失的微小风险 。其他选项包括 none, writethrough, directsync, unsafe
  • -cdrom "$ISO_PATH": 将 Ubuntu Server ISO 镜像作为虚拟 CD-ROM 加载。
  • -boot menu=on,order=d: 启用引导菜单,并将默认引导设备设置为 CD-ROM (d=cdrom),以便从 ISO 安装。
  • -netdev user,id=usernet: 配置一个用户模式网络后端 (SLIRP)。这是最简单的网络配置方式,虚拟机将通过 QEMU 共享宿主机的网络连接,并处于一个虚拟的 NAT 网络中 。
  • -device virtio-net-pci,netdev=usernet: 创建一个 VirtIO 类型的虚拟网卡,并连接到名为 usernet 的网络后端,以获得较好的网络性能 。
  • -display cocoa,show-cursor=on:
    • cocoa: 在 macOS 上使用原生的 Cocoa 显示后端 。
    • show-cursor=on: 强制显示鼠标光标。
  • -usb: 启用 USB 总线控制器。
  • -device usb-tablet: 添加一个 USB 平板输入设备。这使得鼠标在虚拟机中的定位是绝对的,避免了在图形界面中鼠标指针漂移或需要“捕获/释放”的问题,提升了用户体验 。
  • -vga virtio: 使用 VirtIO VGA 显卡。这同样是为了获得比标准 VGA 更好的图形性能和特性,如动态分辨率调整(需要客户机驱动支持)。

在 ARM64 架构的 macOS M2 系统上通过 QEMU 的 TCG (Tiny Code Generator) 模拟 x86_64 架构,性能会显著低于原生执行或同架构虚拟化 (如使用 Hypervisor Framework 在 Intel Mac 上运行 x86_64 VM,或在 M2 Mac 上运行 ARM64 VM)。这是因为 TCG 需要动态地将每一条 x86_64 指令翻译成 ARM64 指令执行,这个过程本身有相当大的开销 。

接下来进入 gurb,按照提示进入 Ubuntu 安装系统

执行上述 QEMU 命令后,虚拟机会启动并从 ISO 镜像引导进入 Ubuntu Server 安装程序。请按照屏幕提示完成安装:

  1. 语言选择: 选择安装过程中使用的语言(例如,English)。
  2. 键盘布局: 选择适合您键盘的布局(例如,English (US))。
  3. 网络配置: 安装程序会自动尝试通过 DHCP 配置网络。由于使用了 QEMU 的用户模式网络,虚拟机应该能自动获取到一个 IP 地址 (通常是 10.0.2.15) 。如果需要,可以手动配置。
  4. 代理配置: 如果您的网络环境需要通过代理访问互联网,请在此处配置。否则,留空即可。
  5. 镜像源配置: 通常使用默认的 Ubuntu 归档镜像源。
  6. 存储布局:
    • 选择 “Use an entire disk”。
    • 选中 QEMU 虚拟磁盘 (通常显示为 VirtIO Block Device)。
    • 确认将对磁盘进行分区并格式化。默认分区方案通常是合适的。
  7. Profile 设置:
    • 设置您的姓名、服务器名称 (hostname)、用户名和密码。请务必记住这些凭据。
  8. SSH 设置:
    • 当提示 “SSH Setup” 时,务必选择 “Install OpenSSH server” 。这会在系统中安装 OpenSSH 服务器,以便后续通过 SSH 远程连接到虚拟机。
    • 可以选择从 GitHub 或 Launchpad 导入 SSH 密钥,如果需要的话。
  9. Featured Server Snaps: 此步骤允许您选择安装一些预置的服务器应用程序(以 Snap 包的形式)。可以根据需求选择,或者直接跳过(选择 “Done”)。
  10. 安装开始: 确认所有设置后,安装过程将开始。系统文件将被复制到虚拟硬盘,并进行配置。
  11. 安装完成与重启: 安装完成后,会提示移除安装介质并重启。此时,可以关闭 QEMU 虚拟机。在下次启动时,需要修改 QEMU 命令,移除 -cdrom-boot d=cdrom 选项,以便从虚拟硬盘引导。

在菜单栏关机就行,多按几次 enter

准备主机共享文件夹

为了在 macOS 宿主机和 Ubuntu 客户机之间方便地传输文件,我们将使用 QEMU 的 VirtFS (基于 9P 协议的半虚拟化文件系统) 功能。

首先,在 macOS 宿主机上创建一个用于共享的目录。例如,在用户主目录下创建一个名为 vm_shared 的文件夹,并为其下的 ubuntu_x86_64 虚拟机创建一个特定的子目录:

mkdir -p ~/vm_shared/ubuntu_x86_64

这个 ~/vm_shared/ubuntu_x86_64 目录将作为宿主机端共享点,客户机 Ubuntu 系统将能够访问此目录中的内容。

使用 VirtFS 时,文件权限和所有权的处理是一个重要方面。QEMU 提供了多种 security_model 选项来控制权限映射 。  

  • passthrough: 客户机中的 UID/GID 直接映射到宿主机。这通常要求 QEMU 以 root 权限运行,存在安全风险,因此不推荐 。  
  • mapped-xattr (或简称 mapped): 客户机文件的 UID/GID 和权限模式等元数据作为扩展属性 (xattrs) 存储在宿主机的文件上。这是推荐的安全模型,因为它不要求 QEMU 以 root 身份运行,并且能较好地处理权限映射问题 。
  • none: 类似于 passthrough,但在设置所有权失败时不报告错误。

这里采用 security_model=mapped-xattr。这意味着宿主机上共享目录内文件的实际所有者可能是运行 QEMU 的用户,而客户机看到的权限和所有权信息则通过扩展属性进行管理。后续在客户机挂载时,可能需要额外步骤 (如使用 bindfs) 来确保客户机用户对共享文件有便捷的读写权限,特别是当宿主机用户 UID/GID 与客户机用户 UID/GID 不一致时 。

启动命令

#!/bin/zsh
 
# --- 配置变量 ---
VM_NAME="ubuntu_x86_64"
QEMU_BIN="qemu-system-x86_64" # 确保 qemu-system-x86_64 在 PATH 中,或使用绝对路径如 /opt/homebrew/bin/qemu-system-x86_64
 
# QEMU 和虚拟机文件路径
QEMU_INSTALL_DIR="/opt/homebrew/share/qemu" # QEMU 共享文件目录,请确认此路径
VM_BASE_DIR="$HOME/qemu_vms" # 虚拟机文件根目录
VM_DIR="$VM_BASE_DIR/$VM_NAME" # 特定虚拟机的目录
 
# 核心文件
DISK_IMG="$VM_DIR/$VM_NAME.qcow2" # 虚拟机磁盘镜像
OVMF_CODE_FILE="edk2-x86_64-code.fd" # OVMF 固件文件名 (只读)
OVMF_CODE_PATH="$QEMU_INSTALL_DIR/$OVMF_CODE_FILE" # OVMF 固件完整路径
 
 
# 共享配置
SHARED_DIR="$HOME/vm_shared/$VM_NAME" # macOS 上的共享目录路径
HOST_SSH_PORT=2222 # 宿主机用于转发到客户机 SSH (22) 的端口
HOST_SERVER_PORT_1=10086 # 示例:宿主机用于转发到客户机某个服务 (如 Web 服务 80) 的端口
GUEST_SERVER_PORT_1=10086  # 示例:客户机上运行的服务端口
 
# 生成一个随机的本地管理 MAC 地址 (52:54:00:xx:xx:xx)
MAC_ADDR=$(echo -n 52:54:00; for i in $(seq 1 3); do echo -n ":$(openssl rand -hex 1)"; done)
 
# --- 检查文件是否存在 ---
if [ ! -f "$DISK_IMG" ]; then
    echo "错误: 磁盘镜像未找到于 $DISK_IMG"
    return 1
fi
if [ ! -f "$OVMF_CODE_PATH" ]; then
    echo "错误: $OVMF_CODE_FILE 未找到于 $OVMF_CODE_PATH"
    echo "请确认 QEMU 安装路径及 OVMF 文件位置。"
    return 1
fi
 
 
# 确保共享目录存在
mkdir -p "$SHARED_DIR"
 
# --- 启动 QEMU ---
echo "正在启动 Ubuntu x86_64 虚拟机 ($VM_NAME)..."
echo "客户机 SSH 服务将通过宿主机端口 $HOST_SSH_PORT 访问。"
echo "客户机服务 (端口 $GUEST_SERVER_PORT_1) 将通过宿主机端口 $HOST_SERVER_PORT_1 访问。"
echo "宿主机共享目录: $SHARED_DIR (在客户机中挂载为 host_share)"
 
"$QEMU_BIN" \
    -name "$VM_NAME" \
    -machine q35,accel=tcg \
    -cpu max \
    -smp 4,cores=2,threads=2,sockets=1 \
    -m 4G \
    -drive if=pflash,format=raw,readonly=on,file="$OVMF_CODE_PATH" \
    -drive file="$DISK_IMG",format=qcow2,if=virtio,cache=writeback,aio=threads \
    -netdev user,id=vmnic,hostfwd=tcp::${HOST_SSH_PORT}-:22,hostfwd=tcp::${HOST_SERVER_PORT_1}-:${GUEST_SERVER_PORT_1} \
    -device virtio-net-pci,netdev=vmnic,mac="$MAC_ADDR" \
    -virtfs local,path="$SHARED_DIR",mount_tag=host_share,security_model=mapped-xattr \
    -usb \
    -device usb-tablet \
    -device virtio-rng-pci \
    -vga std \
    -display cocoa,show-cursor=on,zoom-to-fit=on \
    # -soundhw hda \
    # -nographic
 
 
 
    # 如需无图形界面启动 (headless),可添加 -nographic 并移除 -display cocoa... 相关选项,
    # 同时确保串口控制台配置正确 (例如,客户机内核参数 console=ttyS0 配合 QEMU -serial stdio)
  • -name "$VM_NAME": 为虚拟机实例和窗口指定一个易于识别的名称。
  • -machine q35,accel=tcg: 保持使用 Q35 机器类型和 TCG 模拟。QEMU 文档指出,当 smp 值大于 1 时,TCG 默认会尝试启用多线程 (thread=multi) 以利用宿主机多核,前提是前后端都支持且没有启用不兼容的 TCG 特性(如 icount)。
  • -cpu max: 继续使用 max CPU 模型,以便客户机能够利用 QEMU 所能模拟的全部 x86_64 CPU 特性。对于纯 TCG 仿真,这通常比尝试指定一个非常具体的、可能与 ARM 主机差异巨大的型号要好。或者,可以考虑如 Haswell-v4, EPYC-Rome 或通用的 x86-64-v3 等模型 。
  • -smp 4,cores=2,threads=2,sockets=1: 配置虚拟机拥有 4 个虚拟 CPU,模拟为 1 个插槽 (socket),每个插槽 2 个核心 (cores),每个核心 2 个线程 (threads)。这是一个示例配置,可以根据需求调整。
  • -m 4G: 分配 4GB 内存。
  • UEFI 固件驱动器 (-drive if=pflash...): 与安装时相同,分别加载只读的固件代码 (edk2-x86_64-code.fd) 和可写的 UEFI 变量。
  • 虚拟机主磁盘驱动器 (-drive file="$DISK_IMG",format=qcow2,if=virtio,cache=writeback,aio=native):
    • 不再需要 -cdrom 或从 CD 引导。
    • aio=native: 尝试启用原生异步 I/O (Linux AIO)。在 macOS 上,QEMU 对此选项的行为可能不同,但通常包含此选项无害,QEMU 会选择合适的后端。aio=threads 是另一种选择。
    • cache=writeback: 保持使用写回缓存模式,以平衡性能和数据一致性。
  • 网络配置 (-netdev user,id=vmnic,...-device virtio-net-pci,netdev=vmnic,...):
    • -netdev user,id=vmnic,hostfwd=tcp::${HOST_SSH_PORT}-:22,hostfwd=tcp::${HOST_SERVER_PORT_1}-:${GUEST_SERVER_PORT_1}:
      • user: 定义一个用户模式网络栈。
      • id=vmnic: 为此网络后端指定一个 ID。
      • hostfwd=tcp::${HOST_SSH_PORT}-:22: 关键配置。将宿主机的 HOST_SSH_PORT (例如 2222) TCP 端口的连接转发到客户机的 22 端口 (SSH 服务) 。
      • hostfwd=tcp::${HOST_SERVER_PORT_1}-:${GUEST_SERVER_PORT_1}: 关键配置。示例性地将宿主机的 HOST_SERVER_PORT_1 (例如 8080) TCP 端口转发到客户机的 GUEST_SERVER_PORT_1 (例如 80) 端口。用户可以根据需要在客户机中运行的服务,添加更多的 hostfwd 条目。
    • -device virtio-net-pci,netdev=vmnic,mac="$MAC_ADDR":
      • 将一个 VirtIO 虚拟网卡连接到名为 vmnic 的网络后端。
      • mac="$MAC_ADDR": 为虚拟机网卡分配一个唯一的 MAC 地址。这有助于避免在同一网络中运行多个虚拟机时可能发生的 MAC 地址冲突。脚本中生成的是一个 QEMU/KVM 常用的本地管理 MAC 地址范围 (52:54:00:xx:xx:xx)。
  • 文件共享 (-virtfs local,path="$SHARED_DIR",mount_tag=host_share,security_model=mapped-xattr):
    • local: 表示共享路径是宿主机上的本地路径。
    • path="$SHARED_DIR": 指定在 macOS 宿主机上创建的共享目录的完整路径。
    • mount_tag=host_share: 定义一个挂载标签。客户机将使用此标签来识别和挂载这个共享点 。
    • security_model=mapped-xattr: 关键配置。使用此安全模型,客户机中文件的权限和所有权信息将作为扩展属性存储在宿主机的文件上。这是在非 root QEMU 环境下推荐的模型,能较好地平衡安全性和易用性 。
  • 显示 (-display cocoa,show-cursor=on,zoom-to-fit=on):
    • zoom-to-fit=on: 允许虚拟机显示内容缩放以适应窗口大小,提升视觉体验 。
  • 输入设备 (-usb -device usb-tablet): 与安装时相同,提供流畅的鼠标体验。
  • 随机数生成器 (-device virtio-rng-pci): 为客户机提供一个半虚拟化的随机数生成器设备。这对于客户机内部的加密操作、熵池填充以及整体系统稳定性非常重要 。
  • 声卡 (-soundhw hda): 添加一个模拟的 Intel HDA 声卡设备,如果需要在虚拟机中使用音频,则此选项有用 。
  • 显卡 (-vga virtio): 继续使用 VirtIO 显卡以获得较好的图形性能。

可以有一个小小的模拟窗口出来了

关机可以直接关,可以 <ctrl+a>+x(qemu 控制台),可以 systemctl poweroff

/etc/default/grub 文件需要修改才能很好地支持无头 (headless) 串行控制台操作,这里修改一下:

可以直接先复制一份系统镜像

cp ~/qemu_vms/ubuntu_x86_64/ubuntu_x86_64.qcow2 ~/qemu_vms/ubuntu_x86_64/ubuntu_x86_64.qcow2.backup-$(date +%Y%m%d-%H%M%S)
GRUB_TIMEOUT_STYLE=menu  # 或者 hidden
GRUB_TIMEOUT=5           # 或者 0

# ...

GRUB_CMDLINE_LINUX_DEFAULT="console=ttyS0,115200n8"
GRUB_CMDLINE_LINUX="console=ttyS0,115200n8"

# ...

# Uncomment to disable graphical terminal (enable serial console)
GRUB_TERMINAL=serial
GRUB_SERIAL_COMMAND="serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1"

# ... (其他配置,如 GRUB_GFXMODE 可以保持注释或删除) ...
sudo update-grub

使用配置的 QEMU 无头启动脚本 (-vga std 或 -vga none, -nographic, -serial stdio, -monitor none) 再次尝试启动虚拟机

#!/bin/zsh
 
# --- 配置变量 ---
VM_NAME="ubuntu_x86_64"
QEMU_BIN="qemu-system-x86_64" # 确保 qemu-system-x86_64 在 PATH 中,或使用绝对路径如 /opt/homebrew/bin/qemu-system-x86_64
 
# QEMU 和虚拟机文件路径
QEMU_INSTALL_DIR="/opt/homebrew/share/qemu" # QEMU 共享文件目录,请确认此路径
VM_BASE_DIR="$HOME/qemu_vms" # 虚拟机文件根目录
VM_DIR="$VM_BASE_DIR/$VM_NAME" # 特定虚拟机的目录
 
# 核心文件
DISK_IMG="$VM_DIR/$VM_NAME.qcow2" # 虚拟机磁盘镜像
OVMF_CODE_FILE="edk2-x86_64-code.fd" # OVMF 固件文件名 (只读)
OVMF_CODE_PATH="$QEMU_INSTALL_DIR/$OVMF_CODE_FILE" # OVMF 固件完整路径
 
 
# 共享配置
SHARED_DIR="$HOME/vm_shared/$VM_NAME" # macOS 上的共享目录路径
HOST_SSH_PORT=2222 # 宿主机用于转发到客户机 SSH (22) 的端口
HOST_SERVER_PORT_1=10086 # 示例:宿主机用于转发到客户机某个服务 (如 Web 服务 80) 的端口
GUEST_SERVER_PORT_1=10086  # 示例:客户机上运行的服务端口
 
# 生成一个随机的本地管理 MAC 地址 (52:54:00:xx:xx:xx)
MAC_ADDR=$(echo -n 52:54:00; for i in $(seq 1 3); do echo -n ":$(openssl rand -hex 1)"; done)
 
# --- 检查文件是否存在 ---
if [ ! -f "$DISK_IMG" ]; then
    echo "错误: 磁盘镜像未找到于 $DISK_IMG"
    return 1
fi
if [ ! -f "$OVMF_CODE_PATH" ]; then
    echo "错误: $OVMF_CODE_FILE 未找到于 $OVMF_CODE_PATH"
    echo "请确认 QEMU 安装路径及 OVMF 文件位置。"
    return 1
fi
 
 
# 确保共享目录存在
mkdir -p "$SHARED_DIR"
 
# --- 启动 QEMU ---
echo "正在启动 Ubuntu x86_64 虚拟机 ($VM_NAME)..."
echo "客户机 SSH 服务将通过宿主机端口 $HOST_SSH_PORT 访问。"
echo "客户机服务 (端口 $GUEST_SERVER_PORT_1) 将通过宿主机端口 $HOST_SERVER_PORT_1 访问。"
echo "宿主机共享目录: $SHARED_DIR (在客户机中挂载为 host_share)"
 
"$QEMU_BIN" \
    -name "$VM_NAME" \
    -machine q35,accel=tcg \
    -cpu max \
    -smp 4,cores=2,threads=2,sockets=1 \
    -m 4G \
    -drive if=pflash,format=raw,readonly=on,file="$OVMF_CODE_PATH" \
    -drive file="$DISK_IMG",format=qcow2,if=virtio,cache=writeback,aio=threads \
    -netdev user,id=vmnic,hostfwd=tcp::${HOST_SSH_PORT}-:22,hostfwd=tcp::${HOST_SERVER_PORT_1}-:${GUEST_SERVER_PORT_1} \
    -device virtio-net-pci,netdev=vmnic,mac="$MAC_ADDR" \
    -virtfs local,path="$SHARED_DIR",mount_tag=host_share,security_model=mapped-xattr \
    -usb \
    -device usb-tablet \
    -device virtio-rng-pci \
    -vga std \
    -serial mon:stdio\
    -display none
    # -nographic
    # -display cocoa,show-cursor=on,zoom-to-fit=on \
    # -soundhw hda \
 
 
 
    # 如需无图形界面启动 (headless),可添加 -nographic 并移除 -display cocoa... 相关选项,
    # 同时确保串口控制台配置正确 (例如,客户机内核参数 console=ttyS0 配合 QEMU -serial stdio)

完美!

配置系统

检查网络

cyril@ubuntuemu:~$ ip addr show
2: enp0s2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 52:54:00:89:7a:ac brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 metric 100 brd 10.0.2.255 scope global dynamic enp0s2
       valid_lft 86104sec preferred_lft 86104sec
    inet6 fec0::5054:ff:fe89:7aac/64 scope site dynamic mngtmpaddr noprefixroute 
       valid_lft 86107sec preferred_lft 14107sec
    inet6 fe80::5054:ff:fe89:7aac/64 scope link 
       valid_lft forever preferred_lft forever
cyril@ubuntuemu:~$ ping -c 3 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=255 time=1.40 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=255 time=1.37 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=255 time=1.16 ms
 
--- 8.8.8.8 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2007ms
rtt min/avg/max/mdev = 1.160/1.310/1.399/0.106 ms
cyril@ubuntuemu:~$ curl -I https://www.google.com
cyril@ubuntuemu:~$ sudo systemctl status ssh

哦,ssh 默认没开,这里可以打开

sudo systemctl enable ssh

然后挂载共享文件夹

sudo mkdir -p /mnt/host_share
sudo mount -t 9p -o trans=virtio,version=9p2000.L,msize=524288,access=user host_share /mnt/host_share
  • -t 9p: 指定文件系统类型为 9p (Plan 9 Filesystem)。
  • trans=virtio: 指定传输协议为 virtio
  • version=9p2000.L: 指定 9P 协议的版本。.L 后缀表示支持 Linux 特有的扩展,如 POSIX 权限。
  • msize=524288: 关键性能选项。设置消息大小 (message size) 为 524288 字节 (512KB)。9P 的默认 msize (早期内核为 8KB,较新内核为 128KB) 非常小,会导致文件传输性能低下。将其增大到 512KB (Linux 客户端当前支持的最大值) 可以显著提升性能 。
  • access=user: 权限相关选项。此选项使得文件系统操作是以执行挂载命令的用户(此处为 root,因为用了 sudo mount)或后续访问该挂载点的用户的身份进行的。其他选项包括:
    • access=any: 所有客户机用户对共享的访问都映射到执行初始 9P “attach” 操作的单一用户身份(通常是 QEMU 进程的用户,或在 security_model=passthrough 时是客户机 root)。这可能导致权限管理混乱。
    • access=<uid>: 只允许特定 UID 的客户机用户访问。 对于 security_model=mapped-xattraccess=user 通常是一个合理的选择,它会尝试根据访问文件的客户机用户的 UID/GID 来读写扩展属性中存储的权限信息。
  • host_share: 这是在 QEMU 启动命令中 -virtfs 选项里指定的 mount_tag
  • /mnt/host_share: 这是在客户机上创建的挂载点。

当使用 security_model=mapped-xattr 时,宿主机 macOS 用户的 UID (例如 501) 和 GID (例如 20, 即 staff 组) 通常与客户机 Ubuntu 中创建的普通用户的 UID/GID (例如 1000/1000) 不同。直接挂载 9P 共享后,客户机用户在访问 /mnt/host_share 中的文件时,可能会看到文件所有者是 UID 501,导致写入权限问题或不便。

bindfs 是一个 FUSE 文件系统,它允许以不同的权限和所有权重新挂载一个目录。我们可以用它来将 9P 挂载点重新映射为客户机当前用户拥有所有权。

sudo apt update && sudo apt install -y bindfs

创建目标挂载点(这里创建在 home 下平时好用)

mkdir -p /home/cyril/host_files
GUEST_UID=$(id -u)
GUEST_GID=$(id -g)

然后执行 bindfs 命令。假设宿主机 macOS 用户的 UID 是 501,主要组 GID 是 20 (通常是 staff 组,在某些 Linux 发行版中 GID 20 可能对应 dialout 组,需要确认客户机中 GID 20 的名称或直接使用 GID):

sudo bindfs --map=501/$GUEST_UID:@20/@$GUEST_GID /mnt/host_share /home/cyril/host_files

将源目录 /mnt/host_share (这是 QEMU 9P 共享在客户机中挂载的原始位置),重新挂载到目标目录 /home/cyril/host_files,并且在这个新的挂载点 (/home/cyril/host_files) 上:

  • 原来属于宿主机 macOS 用户 UID 501 的文件,其所有权将被映射为属于客户机当前用户的 UID (由 $GUEST_UID 变量的值决定)。
  • 原来属于宿主机 macOS 组 GID 20 的文件,其组所有权将被映射为属于客户机当前用户的 GID (由 $GUEST_GID 变量的值决定)。

然后让它每次登录的时候自动挂载,编辑 /etc/fstab,加上这两行

# 1. Mount the raw 9P share
host_share /mnt/host_share 9p trans=virtio,version=9p2000.L,msize=524288,access=user,rw,_netdev,nofail 0 0

# 2. Remap permissions with bindfs
#    Replace 1000 with your actual guest user's UID and GID if different.
#    Replace 501 and 20 with your macOS host's UID and GID.
/mnt/host_share /home/cyril/host_files fuse.bindfs map=501/1000:@20/@1000,x-systemd.requires=/mnt/host_share,_netdev,nofail,rw,user 0 0

要注意这里是没有可执行权限的,如果要跑程序要复制到其他目录下面去

测试挂载

sudo mount -a

在 mac 上直接放一个文件上去,可以看到直接出现啦,权限也对得上

把脚本用 alias 写成命令放在 ~/.zshrc 中就能一键在命令行中启动虚拟机

alias ubuntu_emu='/Users/cyril/qemu_vms/ubuntu_x86_64/launch.sh'

测试连接性

虚拟机上:

nc -l -p 10086

宿主机上:

nc 127.0.0.1 10086

现在输入文字应该能够传输上了

内存占用情况

刚进入系统大概在 1.5 个 G 的水平,真是优秀的模拟软件

关于终端大小

如果调整终端窗口大小,因为虚拟机并不能获取外部窗口大小的信息,所以它不知道改变了大小,所以输入到超过原来大小的时候会出现一些比较奇怪的信息

这个时候需要我们手动调整大小:

stty rows 24 columns 80

尽量保持窗口大小不动吧,其他方法感觉挺麻烦的