diff --git a/.gitignore b/.gitignore index e7df6b3..3d319b2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ qemu-binaries/ -rootfs.cpio diff --git a/README.txt b/README.txt index 8081124..02a3944 100644 --- a/README.txt +++ b/README.txt @@ -30,6 +30,22 @@ Optional parameters: -d | --debug: Invokes 'set -x' for debugging the script. + --debian: + By default, the script boots a very simple Busybox based root filesystem. + This option allows the script to boot a full Debian root filesystem, + which can be built using 'build.sh' in the debian folder. Run + + $ sudo debian/build.sh -h + + for more information on that script. + + The kernel should be built with the 'kvm_guest.config' target to boot + successfully. For example on an x86_64 host, + + $ make defconfig kvm_guest.config bzImage + + will produce a bootable kernel image. + -g | --gdb: Add '-s -S' to the QEMU invocation to allow debugging via GDB (will invoke `$GDB_BIN` env var else `gdb-multiarch`). diff --git a/boot-qemu.sh b/boot-qemu.sh index 36ef937..98a45a1 100755 --- a/boot-qemu.sh +++ b/boot-qemu.sh @@ -48,6 +48,11 @@ function parse_parameters() { set -x ;; + --debian) + DEBIAN=true + INTERACTIVE=true + ;; + -g | --gdb) GDB=true INTERACTIVE=true @@ -90,6 +95,10 @@ function sanity_check() { [[ -z ${ARCH} ]] && die "Architecture ('-a') is required but not specified!" [[ -z ${KERNEL_LOCATION} ]] && die "Kernel image or kernel build folder ('-k') is required but not specified!" + # Some default values + [[ -z ${DEBIAN} ]] && DEBIAN=false + [[ -z ${INTERACTIVE} ]] && INTERACTIVE=false + # KERNEL_LOCATION could be a relative path; turn it into an absolute one with readlink KERNEL_LOCATION=$(readlink -f "${KERNEL_LOCATION}") @@ -103,11 +112,20 @@ function setup_qemu_args() { [[ ${ARCH} =~ arm32 ]] && ARCH_RTFS_DIR=arm IMAGES_DIR=${BASE}/images/${ARCH_RTFS_DIR:-${ARCH}} - ROOTFS=${IMAGES_DIR}/rootfs.cpio + if ${DEBIAN}; then + ROOTFS=${IMAGES_DIR}/debian.img + [[ -f ${ROOTFS} ]] || die "'--debian' requires a debian.img. Run 'sudo debian/build.sh -a ${IMAGES_DIR##*/}' to generate it." + else + ROOTFS=${IMAGES_DIR}/rootfs.cpio + fi APPEND_STRING="" - if ${INTERACTIVE:=false}; then - APPEND_STRING+="rdinit=/bin/sh " + if ${INTERACTIVE}; then + if ${DEBIAN}; then + APPEND_STRING+="root=/dev/vda " + else + APPEND_STRING+="rdinit=/bin/sh " + fi fi if ${GDB:=false}; then APPEND_STRING+="nokaslr " @@ -119,7 +137,8 @@ function setup_qemu_args() { DTB=aspeed-bmc-opp-palmetto.dtb QEMU_ARCH_ARGS=( -machine palmetto-bmc - -no-reboot) + -no-reboot + ) QEMU=(qemu-system-arm) ;; @@ -128,16 +147,21 @@ function setup_qemu_args() { DTB=aspeed-bmc-opp-romulus.dtb QEMU_ARCH_ARGS=( -machine romulus-bmc - -no-reboot) + -no-reboot + ) QEMU=(qemu-system-arm) ;; arm32_v7) ARCH=arm APPEND_STRING+="console=ttyAMA0 " + # https://lists.nongnu.org/archive/html/qemu-discuss/2018-08/msg00030.html + # VFS: Cannot open root device "vda" or unknown-block(0,0): error -6 + ${DEBIAN} && HIGHMEM=,highmem=off QEMU_ARCH_ARGS=( - -machine virt - -no-reboot) + -machine "virt${HIGHMEM}" + -no-reboot + ) QEMU=(qemu-system-arm) ;; @@ -145,14 +169,20 @@ function setup_qemu_args() { ARCH=arm64 KIMAGE=Image.gz APPEND_STRING+="console=ttyAMA0 " + QEMU_ARCH_ARGS=( + -cpu max + -machine "virt,gic-version=max" + ) if [[ "$(uname -m)" = "aarch64" && -e /dev/kvm ]]; then - ARM64_CPU=host - ARM64_KVM_FLAGS=(-enable-kvm) + QEMU_ARCH_ARGS+=(-enable-kvm) + else + QEMU_ARCH_ARGS+=(-machine "virtualization=true") + fi + if ${DEBIAN}; then + # Booting is so slow without these + QEMU_RAM=2G + QEMU_ARCH_ARGS+=(-smp 4) fi - QEMU_ARCH_ARGS=( - "${ARM64_KVM_FLAGS[@]}" - -cpu "${ARM64_CPU:-max}" - -machine virt) QEMU=(qemu-system-aarch64) ;; @@ -160,7 +190,8 @@ function setup_qemu_args() { KIMAGE=vmlinux QEMU_ARCH_ARGS=( -cpu 24Kf - -machine malta) + -machine malta + ) QEMU=(qemu-system-"${ARCH}") ARCH=mips ;; @@ -171,7 +202,8 @@ function setup_qemu_args() { APPEND_STRING+="console=ttyS0 " QEMU_ARCH_ARGS=( -machine bamboo - -no-reboot) + -no-reboot + ) QEMU_RAM=128m QEMU=(qemu-system-ppc) ;; @@ -181,7 +213,8 @@ function setup_qemu_args() { KIMAGE=vmlinux QEMU_ARCH_ARGS=( -machine pseries - -vga none) + -vga none + ) QEMU_RAM=1G QEMU=(qemu-system-ppc64) ;; @@ -193,7 +226,8 @@ function setup_qemu_args() { -device "ipmi-bmc-sim,id=bmc0" -device "isa-ipmi-bt,bmc=bmc0,irq=10" -L "${IMAGES_DIR}/" -bios skiboot.lid - -machine powernv) + -machine powernv + ) QEMU_RAM=2G QEMU=(qemu-system-ppc64) ;; @@ -219,8 +253,14 @@ function setup_qemu_args() { KIMAGE=bzImage APPEND_STRING+="console=ttyS0 " # Use KVM if the processor supports it and the KVM module is loaded (i.e. /dev/kvm exists) - [[ $(grep -c -E 'vmx|svm' /proc/cpuinfo) -gt 0 && -e /dev/kvm ]] && - QEMU_ARCH_ARGS=("${QEMU_ARCH_ARGS[@]}" -cpu host -d "unimp,guest_errors" -enable-kvm -smp "$(nproc)") + if [[ $(grep -c -E 'vmx|svm' /proc/cpuinfo) -gt 0 && -e /dev/kvm ]]; then + QEMU_ARCH_ARGS=( + -cpu host + -d "unimp,guest_errors" + -enable-kvm + -smp "$(nproc)" + ) + fi case ${ARCH} in x86) QEMU=(qemu-system-i386) ;; x86_64) QEMU=(qemu-system-x86_64) ;; @@ -268,13 +308,19 @@ function setup_qemu_args() { # Invoke QEMU function invoke_qemu() { - rm -rf "${ROOTFS}" - zstd -q -d "${ROOTFS}".zst -o "${ROOTFS}" - green "QEMU location: " "$(dirname "$(command -v "${QEMU[*]}")")" '\n' green "QEMU version: " "$("${QEMU[@]}" --version | head -n1)" '\n' [[ -z ${QEMU_RAM} ]] && QEMU_RAM=512m + if ${DEBIAN}; then + QEMU+=(-drive "file=${ROOTFS},format=raw,if=virtio,index=0,media=disk") + else + rm -rf "${ROOTFS}" + zstd -q -d "${ROOTFS}".zst -o "${ROOTFS}" + QEMU+=(-initrd "${ROOTFS}") + fi + # Removing trailing space for aesthetic purposes + [[ -n ${APPEND_STRING} ]] && QEMU+=(-append "${APPEND_STRING%* }") if ${GDB:=false}; then while true; do if lsof -i:1234 &>/dev/null; then @@ -285,9 +331,7 @@ function invoke_qemu() { # Note: no -serial mon:stdio "${QEMU[@]}" \ "${QEMU_ARCH_ARGS[@]}" \ - -append "${APPEND_STRING}" \ -display none \ - -initrd "${ROOTFS}" \ -kernel "${KERNEL}" \ -m "${QEMU_RAM}" \ -nodefaults \ @@ -313,9 +357,7 @@ function invoke_qemu() { set -x "${QEMU[@]}" \ "${QEMU_ARCH_ARGS[@]}" \ - -append "${APPEND_STRING}" \ -display none \ - -initrd "${ROOTFS}" \ -kernel "${KERNEL}" \ -m "${QEMU_RAM}" \ -nodefaults \ diff --git a/debian/.gitignore b/debian/.gitignore new file mode 100644 index 0000000..53f2697 --- /dev/null +++ b/debian/.gitignore @@ -0,0 +1 @@ +tmp.*/ diff --git a/debian/README.txt b/debian/README.txt new file mode 100644 index 0000000..66eb681 --- /dev/null +++ b/debian/README.txt @@ -0,0 +1,39 @@ +Usage: ./build.sh + +Script description: Builds a Debian filesystem image that can be booted in QEMU. + +Required parameters: + -a | --arch: + The architecture to build the image for. Possible values are: + * arm + * arm64 + * ppc64le + * s390 + * x86_64 + +Optional parameters: + -l | --ltp: + Builds some test cases from the Linux Test Project that are useful for + finding issues. + + -m | --modules-folder: + Path to the "modules" folder in a Linux kernel build tree. They will be + copied into /lib within the image. For example, + + $ make INSTALL_MOD_PATH=rootfs modules_install + + in a kernel tree will place the modules folder within rootfs/lib/modules + so the value that is passed to this script would be + /rootfs/lib/modules. This is useful for + testing that kernel modules can load as well as verifying additional + functionality within QEMU. + + -p | --password: + The created user account's password. By default, it is just "password". + + -u | --user: + The created user account's name. By default, it is just "user". + + -v | --version: + The version of Debian to build. By default, it is the latest stable which + is currently Buster. diff --git a/debian/build.sh b/debian/build.sh new file mode 100755 index 0000000..f8f8c3e --- /dev/null +++ b/debian/build.sh @@ -0,0 +1,184 @@ +#!/usr/bin/env bash + +trap 'umount "${MOUNT_DIR}" 2>/dev/null; rm -rf "${WORK_DIR}"' INT TERM EXIT + +# Prints a message in color +function print_color() { + # Reset escape code + RST="\033[0m" + printf "\n%b%s%b\n" "${1}" "${2}" "${RST}" +} + +# Prints an error message in bold red then exits +function die() { + print_color "\033[01;31m" "${1}" + exit "${2:-33}" +} + +# Prints a warning message in bold yellow +function warn() { + print_color "\033[01;33m" "${1}" +} + +# The script requires root in several places, re-run the script with sudo if necessary +function run_as_root() { + [[ ${EUID} -eq 0 ]] && return 0 + warn "Script needs to be run as root, invoking sudo on script..." + echo + echo "$ exec sudo bash ${*}" + exec sudo PATH="${PATH}" bash "${@}" +} + +# Get user inputs +function get_parameters() { + DEBIAN=$(dirname "$(readlink -f "${0}")") + + while ((${#})); do + case ${1} in + -a | --arch) + shift + case ${1} in + arm64) + DEB_ARCH=${1} + OUR_ARCH=${DEB_ARCH} + ;; + arm) + DEB_ARCH=${1}hf + OUR_ARCH=${1} + ;; + ppc64le) + DEB_ARCH=ppc64el + OUR_ARCH=${1} + ;; + s390) + DEB_ARCH=${1}x + OUR_ARCH=${1} + ;; + x86_64) + DEB_ARCH=amd64 + OUR_ARCH=${1} + ;; + *) die "${1} is not supported by this script!" ;; + esac + ;; + -h | --help) + echo + cat "${DEBIAN}"/README.txt + echo + exit 0 + ;; + -l | --ltp) + LTP=true + ;; + -m | --modules-folder) + shift + MODULES_FOLDER=${1} + [[ -d ${MODULES_FOLDER} ]] || die "${MODULES_FOLDER} specified but it does not exist!" + ;; + -p | --password) + shift + DEB_PASS=${1} + ;; + -u | --user) + shift + DEB_USER=${1} + ;; + -v | --version) + shift + DEB_VERSION=${1} + ;; + esac + shift + done +} + +# Checks if command is available +function is_available() { + command -v "${1}" &>/dev/null || die "${1} needs to be installed!" +} + +# Do some initial checks for environment and configuration +function reality_checks() { + # Validity checks + [[ -z ${DEB_ARCH} ]] && die "'-a' is required but not specified!" + + # Some tools are in /usr/sbin or /sbin but they might not be in PATH by default + [[ ${PATH} =~ /sbin ]] || PATH=${PATH}:/usr/sbin:/sbin + is_available blkid + is_available findmnt + is_available mkfs.ext4 + is_available qemu-debootstrap + is_available qemu-img + + # Default values + [[ -z ${DEB_VERSION} ]] && DEB_VERSION=buster + [[ -z ${DEB_USER} ]] && DEB_USER=user + [[ -z ${DEB_PASS} ]] && DEB_PASS=password + [[ -z ${LTP} ]] && LTP=false +} + +# Build image +function create_img() { + WORK_DIR=$(mktemp -d -p "${DEBIAN}") + ORIG_USER=$(logname) + + set -x + + # Create the image that we will use and mount it + IMG=${WORK_DIR}/debian.img + qemu-img create "${IMG}" 5g + mkfs.ext4 "${IMG}" + MOUNT_DIR=${WORK_DIR}/rootfs + mkdir -p "${MOUNT_DIR}" + mount -o loop "${IMG}" "${MOUNT_DIR}" + + # Install packages + PACKAGES=( + autoconf + automake + bash + bison + build-essential + ca-certificates + flex + git + libtool + m4 + pkg-config + stress-ng + sudo + vim + ) + qemu-debootstrap --arch "${DEB_ARCH}" --include="${PACKAGES[*]//${IFS:0:1}/,}" "${DEB_VERSION}" "${MOUNT_DIR}" || exit ${?} + + # Setup user account + chroot "${MOUNT_DIR}" bash -c "useradd -m -G sudo ${DEB_USER} -s /bin/bash && echo ${DEB_USER}:${DEB_PASS} | chpasswd" + + # Add fstab so that / mounts as rw instead of ro + printf "UUID=%s\t/\text4\terrors=remount-ro\t0\t1\n" "$(blkid -o value -s UUID "$(findmnt -n -o SOURCE "${MOUNT_DIR}")")" | tee -a "${MOUNT_DIR}"/etc/fstab + + # Add hostname entry to /etc/hosts so sudo does not complain + printf "127.0.0.1\t%s\n" "$(hostname)" | tee -a "${MOUNT_DIR}"/etc/hosts + + # Install some problematic LTP testcases for debugging if requested + if ${LTP}; then + LTP_SCRIPT=/home/${DEB_USER}/ltp.sh + cp -v "${DEBIAN}"/ltp.sh "${MOUNT_DIR}${LTP_SCRIPT}" + chroot "${MOUNT_DIR}" bash "${LTP_SCRIPT}" + rm -rf "${LTP_SCRIPT}" + fi + + # Install modules if requested + [[ -n ${MODULES_FOLDER} ]] && cp -rv "${MODULES_FOLDER}" "${MOUNT_DIR}"/lib + + # Unmount, move image, and clean up + umount "${MOUNT_DIR}" + chown -R "${ORIG_USER}:${ORIG_USER}" "${IMG}" + mv -v "${IMG}" "${DEBIAN%/*}/images/${OUR_ARCH}" + rm -rf "${WORK_DIR}" +} + +run_as_root "${0}" "${@}" +get_parameters "${@}" +reality_checks +create_img diff --git a/debian/ltp.sh b/debian/ltp.sh new file mode 100755 index 0000000..517e4fe --- /dev/null +++ b/debian/ltp.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# This should be run in the Debian chroot + +LTP=$(dirname "$(readlink -f "${0}")")/ltp +MAKE=(make -skj"$(nproc)") + +set -x + +git clone --depth=1 https://github.com/linux-test-project/ltp "${LTP}" +cd "${LTP}" || exit ${?} + +"${MAKE[@]}" autotools +./configure + +TEST_CASES=( + kernel/fs/proc + kernel/fs/read_all + lib +) +for TEST_CASE in "${TEST_CASES[@]}"; do + cd "${LTP}"/testcases/"${TEST_CASE}" || exit ${?} + "${MAKE[@]}" +done diff --git a/images/.gitignore b/images/.gitignore new file mode 100644 index 0000000..446d49c --- /dev/null +++ b/images/.gitignore @@ -0,0 +1,2 @@ +debian.img +rootfs.cpio