| #!/bin/bash | |
| # Copyright 2019 The Fuchsia Authors. All rights reserved. | |
| # Use of this source code is governed by a BSD-style license that can be | |
| # found in the LICENSE file. | |
| #### CATEGORY=Run, inspect and debug | |
| ### start fuchsia in an emulator | |
| ## usage: fx emu [-a <mode>] [-c <text>] [-N [-I <ifname>]] [-u <path>] [-g <port> [-r <fps>] [-t <cmd>]] [-x <port> [-X <directory>]] [-e <directory>] [-w <size>] [-s <cpus>] [-m <mb>] [-k <authorized_keys_file>] [-K <kernel_image>] [-z <zbi_image>] [-f <fvm_image>] [-p mouse|touch] [-H|--headless] [--audio] [--hidpi-scaling] [--software-gpu] [--debugger] | |
| ## -a <mode> acceleration mode (auto, off, kvm, hvf, hax), default is auto | |
| ## -c <text> append item to kernel command line | |
| ## -ds <size> extends the fvm image size to <size> bytes. Default is twice the original size | |
| ## -N run with emulated nic via tun/tap | |
| ## -I <ifname> uses the tun/tap interface named ifname | |
| ## -u <path> execute emu if-up script, default: linux: no script, macos: tap ifup script. | |
| ## -e <directory> location of emulator, defaults to looking in prebuilt/third_party/aemu/PLATFORM | |
| ## -g <port> enable gRPC service on port to control the emulator, default is 5556 when WebRTC service is enabled | |
| ## -r <fps> webrtc frame rate when using gRPC service, default is 30 | |
| ## -t <cmd> execute the given command to obtain turn configuration | |
| ## -x <port> enable WebRTC HTTP service on port | |
| ## -X <directory> location of grpcwebproxy, defaults to looking in prebuilt/third_party/grpcwebproxy | |
| ## -w <size> window size, default is 1280x800 | |
| ## -s <cpus> number of cpus, 1 for uniprocessor | |
| ## -m <mb> total memory, in MB | |
| ## -k <authorized_keys_file> SSH authorized keys file, otherwise defaults to ~/.ssh/fuchsia_authorized_keys | |
| ## -K <kernel_image> path to image to use as kernel, defaults to kernel generated by the current build. | |
| ## -z <zbi_image> path to image to use as ZBI, defaults to zircon-a generated by the current build. | |
| ## -Z <zbi tool> path to a version of the `zbi` tool to use; defaults to the one in the current build directory. | |
| ## -f <fvm_image> path to full FVM image, defaults to image generated by the current build. | |
| ## -F <fvm tool> path to a version of the `fvm` tool to use; defaults to the one in the current build directory. | |
| ## -A <arch> architecture of images (x64, arm64), default is the current build. | |
| ## -p mouse|touch set pointing device used on emulator: mouse or multitouch screen; defaults to mouse. | |
| ## -H|--headless run in headless mode | |
| ## --audio run with audio hardware added to the virtual machine | |
| ## --hidpi-scaling enable pixel scaling on HiDPI devices | |
| ## --host-gpu run with host GPU acceleration | |
| ## --software-gpu run without host GPU acceleration | |
| ## --debugger pause on launch and wait for a debugger process to attach before resuming | |
| ## --no-build do not attempt to build the fvm and zbi tools and the zbi image | |
| set -e | |
| DEVSHELL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" | |
| source "${DEVSHELL_DIR}"/lib/image_build_vars.sh || exit $? | |
| source "${DEVSHELL_DIR}"/lib/fvm.sh || exit $? | |
| # Note these match the defaults in `fx qemu` (//zircon/scripts/run-zircon) and the ram value in | |
| # `fx vdl` (//src/developer/ffx/plugins/emulator/src/vdl_files.rs). This can be overriden by the | |
| # emulator configs in the infra/recipe repo | |
| # (https://fuchsia.googlesource.com/infra/recipes/+/refs/heads/main/recipe_modules/emu/api.py). | |
| readonly MEMSIZE_DEFAULT=8192 | |
| readonly SMP_DEFAULT=4 | |
| ACCEL="auto" | |
| NET=0 | |
| DEBUGGER=0 | |
| IFNAME="" | |
| AUDIO=0 | |
| HEADLESS=0 | |
| HIDPI_SCALING=0 | |
| AEMU="emulator" | |
| AEMU_DIR="" | |
| UPSCRIPT= | |
| DOWNSCRIPT=no | |
| WINDOW_SIZE="1280x800" | |
| GRPC= | |
| RTCFPS="30" | |
| TURNCFG="" | |
| GPU="auto" | |
| VULKAN=1 | |
| HTTP=0 | |
| GRPCWEBPROXY_DIR="" | |
| CMDLINE="" | |
| OPT_CMDLINE="" | |
| SMP=$SMP_DEFAULT | |
| MEMSIZE=$MEMSIZE_DEFAULT | |
| IMAGE_SIZE= | |
| EXPERIMENT_ARM64=false | |
| ARCH="${FUCHSIA_ARCH}" | |
| KERNEL_IMAGE="" | |
| FVM_IMAGE="" | |
| ZBI_IMAGE="" | |
| ZBI_TOOL="" | |
| FVM_TOOL="" | |
| POINTING_DEVICE="mouse" | |
| BUILD=1 | |
| EMU_ARGS=() | |
| # Propagate our TERM environment variable as a kernel command line | |
| # argument. This is first so that an explicit -c TERM=foo argument | |
| # goes into CMDLINE later and overrides this. | |
| if [[ -n $TERM ]]; then | |
| CMDLINE+="TERM=$TERM " | |
| fi | |
| while [[ $# -ge 1 ]]; do | |
| case "$1" in | |
| -h|--help) | |
| fx-command-help | |
| exit 0 | |
| ;; | |
| -a) | |
| shift | |
| ACCEL="$1" | |
| ;; | |
| -c) | |
| shift | |
| OPT_CMDLINE+="$1 " | |
| ;; | |
| -ds) | |
| shift | |
| IMAGE_SIZE="$1" | |
| ;; | |
| -N) | |
| NET=1 | |
| ;; | |
| -I) | |
| shift | |
| IFNAME="$1" | |
| ;; | |
| -u) | |
| shift | |
| UPSCRIPT="$1" | |
| ;; | |
| -e) | |
| shift | |
| AEMU_DIR="$1" | |
| ;; | |
| -x) | |
| shift | |
| HTTP="$1" | |
| ;; | |
| -X) | |
| shift | |
| GRPCWEBPROXY_DIR="$1" | |
| ;; | |
| -g) | |
| shift | |
| GRPC="$1" | |
| ;; | |
| -r) | |
| shift | |
| RTCFPS="$1" | |
| ;; | |
| -t) | |
| shift | |
| TURNCFG="$1" | |
| ;; | |
| -w) | |
| shift | |
| WINDOW_SIZE="$1" | |
| ;; | |
| -s) | |
| shift | |
| SMP="$1" | |
| ;; | |
| -m) | |
| shift | |
| MEMSIZE="$1" | |
| ;; | |
| -k) | |
| shift | |
| AUTHORIZED_KEYS="$1" | |
| ;; | |
| -K) | |
| shift | |
| KERNEL_IMAGE="$1" | |
| ;; | |
| -z) | |
| shift | |
| ZBI_IMAGE="$1" | |
| ;; | |
| -Z) | |
| shift | |
| ZBI_TOOL="$1" | |
| ;; | |
| -f) | |
| shift | |
| FVM_IMAGE="$1" | |
| ;; | |
| -F) | |
| shift | |
| FVM_TOOL="$1" | |
| ;; | |
| -A) | |
| shift | |
| ARCH="$1" | |
| ;; | |
| -p) | |
| shift | |
| POINTING_DEVICE="$1" | |
| ;; | |
| -H|--headless) | |
| HEADLESS=1 | |
| ;; | |
| --audio) | |
| AUDIO=1 | |
| ;; | |
| --debugger) | |
| DEBUGGER=1 | |
| ;; | |
| --hidpi-scaling) | |
| HIDPI_SCALING=1 | |
| ;; | |
| --host-gpu) | |
| GPU="host" | |
| ;; | |
| --software-gpu) | |
| GPU="swiftshader_indirect" | |
| ;; | |
| --no-build) | |
| BUILD=0 | |
| ;; | |
| --experiment-arm64) | |
| EXPERIMENT_ARM64=true | |
| ;; | |
| *) | |
| EMU_ARGS+=("$1") | |
| ;; | |
| esac | |
| shift | |
| done | |
| build_targets=() | |
| if [[ -z "${KERNEL_IMAGE}" ]] ; then | |
| KERNEL_IMAGE="$(fx-command-run list-build-artifacts --type kernel --name qemu-kernel images)" | |
| build_targets+=( "${KERNEL_IMAGE}" ) | |
| KERNEL_IMAGE="${FUCHSIA_BUILD_DIR}/${KERNEL_IMAGE}" | |
| fi | |
| if [[ -z "${ZBI_IMAGE}" ]] ; then | |
| ZBI_IMAGE="$(fx-command-run list-build-artifacts --type zbi --name zircon-a images)" | |
| build_targets+=( "${ZBI_IMAGE}" ) | |
| ZBI_IMAGE="${FUCHSIA_BUILD_DIR}/${ZBI_IMAGE}" | |
| fi | |
| if [[ -z "${FVM_IMAGE}" ]] ; then | |
| # Not all builds use an FVM so failing to find a source FVM is OK. | |
| FVM_IMAGE="$(fx-fvm-find-raw-source)" | |
| if [[ -n "${FVM_IMAGE}" ]]; then | |
| build_targets+=( "${FVM_IMAGE}" ) | |
| FVM_IMAGE="${FUCHSIA_BUILD_DIR}/${FVM_IMAGE}" | |
| fi | |
| fi | |
| if (( BUILD )) && [[ ${#build_targets[@]} -gt 0 ]]; then | |
| _targets="$(printf ", %s" "${build_targets[@]}")" | |
| fx-info "Building ${_targets:2}" | |
| fx-command-run build "${build_targets[@]}" | |
| fi | |
| if [[ -z "${ZBI_TOOL}" ]] ; then | |
| (( BUILD )) && build_flag="" || build_flag="--no-build" | |
| ZBI_TOOL="$(fx-command-run host-tool ${build_flag} --print zbi)" | |
| fi | |
| if [[ -z "${FVM_TOOL}" ]] ; then | |
| (( BUILD )) && build_flag="" || build_flag="--no-build" | |
| FVM_TOOL="$(fx-command-run host-tool ${build_flag} --print fvm)" | |
| fi | |
| # TODO(fxbug.dev/62631): femu.sh relies on "-a auto", which makes use of | |
| # HOST_CPU, which is an fx internal variable. In that case, fall back to | |
| # defining it so that things work as expected. | |
| if [[ -z "${HOST_CPU}" ]]; then | |
| case "$(uname -m)" in | |
| x86_64) | |
| readonly HOST_CPU="x64" | |
| ;; | |
| aarch64) | |
| readonly HOST_CPU="arm64" | |
| ;; | |
| *) | |
| echo >&2 "Unknown architecture: $(uname -m)." | |
| exit 1 | |
| ;; | |
| esac | |
| fi | |
| if [[ "$ARCH" != "$HOST_CPU" ]]; then | |
| ACCEL=off | |
| fi | |
| if [[ -z "$AEMU_DIR" && -d "$PREBUILT_AEMU_DIR" ]]; then | |
| AEMU_DIR="$PREBUILT_AEMU_DIR" | |
| fi | |
| if [[ -z "$GRPCWEBPROXY_DIR" && -d "$PREBUILT_GRPCWEBPROXY_DIR" ]]; then | |
| GRPCWEBPROXY_DIR="$PREBUILT_GRPCWEBPROXY_DIR" | |
| fi | |
| if [[ -z "$AUTHORIZED_KEYS" ]]; then | |
| AUTHORIZED_KEYS="$(get-ssh-authkeys)" | |
| fi | |
| # construct the args for aemu | |
| ARGS=() | |
| ARGS+=("-m" "$MEMSIZE") | |
| ARGS+=("-serial" "stdio") | |
| ARGS+=("-vga" "none") | |
| ARGS+=("-device" "virtio-keyboard-pci") | |
| if [[ $SMP != 1 ]]; then | |
| ARGS+=("-smp" "${SMP}") | |
| fi | |
| # TODO(raggi): do we want to use hda on arm64? | |
| if (( AUDIO )); then | |
| ARGS+=("-soundhw" "hda") | |
| fi | |
| FEATURES="VirtioInput,GLDirectMem" | |
| if [[ "$POINTING_DEVICE" == "mouse" ]]; then | |
| ARGS+=("-device" "virtio-mouse-pci") | |
| FEATURES+=",VirtioMouse" | |
| elif [[ "$POINTING_DEVICE" == "touch" ]]; then | |
| ARGS+=("-device" "virtio_input_multi_touch_pci_1") | |
| else | |
| fx-error "Unsupported pointing device: $POINTING_DEVICE" | |
| exit 1 | |
| fi | |
| if [[ "$ACCEL" == "auto" ]]; then | |
| if [[ "$(uname -s)" == "Darwin" ]]; then | |
| ACCEL="hvf" | |
| else | |
| ACCEL="kvm" | |
| fi | |
| fi | |
| case "$ARCH" in | |
| x64) | |
| ARGS+=("-machine" "q35") | |
| ARGS+=("-device" "isa-debug-exit,iobase=0xf4,iosize=0x04") | |
| ;; | |
| arm64) | |
| ARGS+=("-machine" "virt") | |
| ;; | |
| *) | |
| fx-error "Unsupported architecture: $(uname -m)" | |
| exit 1 | |
| esac | |
| if [[ "$ACCEL" == "kvm" ]]; then | |
| if [[ ! -w "/dev/kvm" ]]; then | |
| echo "Error: No write permission on /dev/kvm." | |
| echo "To use KVM acceleration, adjust permissions to /dev/kvm using:" | |
| echo | |
| echo "sudo usermod -a -G kvm $USER" | |
| echo | |
| echo "Exit the current ssh session. From the remote host:" | |
| echo | |
| echo "exit" | |
| echo | |
| echo "On the local host:" | |
| echo | |
| echo "ssh <remote_host_name> -O exit" | |
| echo | |
| echo "Log back in to remote host and run the emulator again." | |
| exit 1 | |
| fi | |
| ARGS+=("-enable-kvm" "-cpu" "host,migratable=no,+invtsc") | |
| FEATURES+=",KVM" | |
| elif [[ "$ACCEL" == "hvf" ]]; then | |
| ARGS+=("-enable-hvf" "-cpu" "Haswell") | |
| FEATURES+=",HVF" | |
| elif [[ "$ACCEL" == "hax" ]]; then | |
| ARGS+=("-enable-hax" "-cpu" "Haswell") | |
| FEATURES+=",HAXM" | |
| elif [[ "$ACCEL" == "off" ]]; then | |
| case "$ARCH" in | |
| x64) | |
| ARGS+=("-cpu" "Haswell,+smap,-check,-fsgsbase") | |
| ;; | |
| arm64) | |
| ARGS+=("-cpu" "cortex-a53") | |
| ;; | |
| esac | |
| else | |
| fx-error Unsupported acceleration mode | |
| exit 1 | |
| fi | |
| if (( VULKAN )); then | |
| FEATURES+=",Vulkan" | |
| else | |
| FEATURES+=",-Vulkan" | |
| fi | |
| OPTIONS=() | |
| OPTIONS+=("-feature" "$FEATURES") | |
| OPTIONS+=("-window-size" "$WINDOW_SIZE") | |
| OPTIONS+=("-gpu" "$GPU") | |
| OPTIONS+=("-no-location-ui") | |
| if (( DEBUGGER )); then | |
| OPTIONS+=("-wait-for-debugger") | |
| fi | |
| if (( HEADLESS )); then | |
| OPTIONS+=("-no-window") | |
| fi | |
| if (( ! HIDPI_SCALING )); then | |
| OPTIONS+=("-no-hidpi-scaling") | |
| fi | |
| if [[ "$ARCH" == "arm64" ]]; then | |
| if ! "$EXPERIMENT_ARM64"; then | |
| fx-error "arm64 support is still experimental, requires --experiment-arm64" | |
| exit 1 | |
| fi | |
| OPTIONS+=("-avd-arch" "arm64") | |
| fi | |
| # use port 5556 by default | |
| if (( HTTP )); then | |
| GRPC="${GRPC:-5556}" | |
| fi | |
| if (( GRPC )); then | |
| if [[ "$(uname -s)" == "Darwin" ]]; then | |
| echo "WebRTC feature is not supported on macOS" | |
| exit 1 | |
| fi | |
| OPTIONS+=("-grpc" "$GRPC") | |
| OPTIONS+=("-rtcfps" "$RTCFPS") | |
| if [[ -n "$TURNCFG" ]]; then | |
| OPTIONS+=("-turncfg" "$TURNCFG") | |
| fi | |
| fi | |
| if (( NET )); then | |
| if [[ "$(uname -s)" == "Darwin" ]]; then | |
| if [[ -z "$IFNAME" ]]; then | |
| IFNAME="tap0" | |
| fi | |
| if [[ "$IFNAME" != "fakenetwork" && ! -c "/dev/$IFNAME" ]]; then | |
| echo "To use emu with networking on macOS, install the tun/tap driver:" | |
| echo "http://tuntaposx.sourceforge.net/download.xhtml" | |
| exit 1 | |
| fi | |
| if [[ "$IFNAME" != "fakenetwork" && ! -w "/dev/$IFNAME" ]]; then | |
| echo "For networking /dev/$IFNAME must be owned by $USER. Please run:" | |
| echo " sudo chown $USER /dev/$IFNAME" | |
| exit 1 | |
| fi | |
| if [[ -z "${UPSCRIPT}" ]]; then | |
| echo "sudo follows to configure the tap interface:" | |
| UPSCRIPT="${DEVSHELL_DIR}"/lib/emu-ifup-macos.sh | |
| fi | |
| else | |
| if [[ -z "$IFNAME" ]]; then | |
| IFNAME="qemu" | |
| fi | |
| TAP_IFS=$(ip tuntap show 2>/dev/null) | |
| if [[ "$IFNAME" != "fakenetwork" && ! "$TAP_IFS" =~ ${IFNAME}: ]]; then | |
| echo "To use emu with networking on Linux, configure tun/tap:" | |
| echo | |
| echo "sudo ip tuntap add dev $IFNAME mode tap user $USER && \\" | |
| echo "sudo ip link set $IFNAME up" | |
| exit 1 | |
| fi | |
| # Try to detect if a firewall is active. There are only few ways to do that | |
| # without being root. Unfortunately, using systemd or systemctl does not work | |
| # (at least on Debian), so use the following hack instead: | |
| if (command -v ufw && grep -q "^ENABLED=yes" /etc/ufw/ufw.conf) >/dev/null 2>&1; then | |
| fx-warn "Active firewall detected: If this emulator is unreachable, run: fx setup-ufw" | |
| fi | |
| if [[ -z "${UPSCRIPT}" ]]; then | |
| UPSCRIPT=no | |
| fi | |
| fi | |
| if [[ -n "${UPSCRIPT}" && "${UPSCRIPT}" != "no" && ! -x "${UPSCRIPT}" ]]; then | |
| echo "The upscript ${UPSCRIPT} is not a valid executable" | |
| exit 1 | |
| fi | |
| if [[ -n "${DOWNSCRIPT}" && "${DOWNSCRIPT}" != "no" && ! -x "${DOWNSCRIPT}" ]]; then | |
| echo "The downscript ${DOWNSCRIPT} is not a valid executable" | |
| exit 1 | |
| fi | |
| ARGS+=("-netdev" "type=tap,ifname=$IFNAME,id=net0${UPSCRIPT:+,script=${UPSCRIPT}}${DOWNSCRIPT:+,downscript=${DOWNSCRIPT}}") | |
| HASH=$(echo $IFNAME | shasum) | |
| SUFFIX=$(for i in {0..2}; do echo -n ":${HASH:$(( 2 * i )):2}"; done) | |
| MAC=",mac=52:54:00$SUFFIX" | |
| ARGS+=("-device" "virtio-net-pci,vectors=8,netdev=net0${MAC}") | |
| else | |
| ARGS+=("-net" "none") | |
| fi | |
| # Check for X11 on Linux | |
| if [[ "$(uname -s)" == "Linux" ]] && (( ! HEADLESS )); then | |
| if [[ -z "$DISPLAY" ]]; then | |
| echo "To use emulator on Linux, start X, or use a virtual framebuffer:" | |
| echo | |
| echo "xvfb-run fx emu" | |
| exit 1 | |
| else | |
| if [[ "$DISPLAY" != "fakedisplay" ]] && ! xset q >/dev/null 2>&1; then | |
| echo "clients must be allowed to connect to DISPLAY. Please run:" | |
| echo | |
| echo "DISPLAY=$DISPLAY XAUTHORITY=/run/user/`getent passwd | grep /var/lib/gdm | cut -d: -f3`/gdm/Xauthority sudo xhost +" | |
| echo | |
| echo "or if using lightdm:" | |
| echo | |
| echo "DISPLAY=$DISPLAY XAUTHORITY=/var/run/lightdm/root/$DISPLAY sudo xhost +" | |
| exit 1 | |
| fi | |
| fi | |
| fi | |
| # Construction of a qcow image prevents aemu from writing back to the | |
| # build-produced image file, which could cause timestamp issues with that file. | |
| # Construction of the new ZBI adds ~/.ssh/fuchsia_authorized_keys for SSH access. | |
| img_dir="$(mktemp -d)" | |
| if [[ ! -d "${img_dir}" ]]; then | |
| fx-error "Failed to create temporary directory" | |
| exit 1 | |
| fi | |
| # Restore the terminal only if the standard input (fd 0) is associated with a | |
| # terminal device. We check this using test operator "-t". | |
| trap 'rm -Rf "${img_dir}"; [[ "${GRPCWEBPROXY_PID}" ]] && kill "${GRPCWEBPROXY_PID}"; [[ -t 0 ]] && stty sane' EXIT | |
| KERNEL_ZBI="${img_dir}/fuchsia-ssh.zbi" | |
| "${ZBI_TOOL}" -o "${KERNEL_ZBI}" "${ZBI_IMAGE}" \ | |
| --entry "data/ssh/authorized_keys=${AUTHORIZED_KEYS}" | |
| if [[ -n "${FVM_IMAGE}" ]]; then | |
| fvm_raw="${img_dir}/fvm_raw.blk" | |
| fx-fvm-extend-image "${FVM_TOOL}" "${FVM_IMAGE}" "${fvm_raw}" "${IMAGE_SIZE}" | |
| ARGS+=("-drive" "file=${fvm_raw},format=raw,if=none,id=vdisk") | |
| ARGS+=("-device" "virtio-blk-pci,drive=vdisk") | |
| fi | |
| # Start gRPC web proxy if HTTP port is set | |
| if (( HTTP )); then | |
| "${GRPCWEBPROXY_DIR}/grpcwebproxy" \ | |
| --backend_addr localhost:"$GRPC" --server_http_debug_port "$HTTP" \ | |
| --backend_tls=false --run_tls_server=false --allow_all_origins & | |
| GRPCWEBPROXY_PID=$! | |
| fi | |
| # construct the kernel cmd line for aemu | |
| CMDLINE+="kernel.serial=legacy " | |
| # Add entropy to the kernel | |
| CMDLINE+="kernel.entropy-mixin=$(head -c 32 /dev/urandom | shasum -a 256 | awk '{ print $1 }') " | |
| # Don't 'reboot' the emulator if the kernel crashes | |
| CMDLINE+="kernel.halt-on-panic=true " | |
| # TODO(fxbug.dev/74233): FEMU using HVF accelerator may encounter a race | |
| # condition between VCPU threads which could cause emulator to crash if page | |
| # table eviction is enabled; As a workaround, we disable page table eviction if | |
| # we are using HVF. | |
| if [[ "$ACCEL" == "hvf" ]]; then | |
| CMDLINE+="kernel.page-scanner.page-table-eviction-policy=never " | |
| fi | |
| # Finally, append any values received via option. We save them for last so that | |
| # they can override others since "last value wins". | |
| CMDLINE+=$OPT_CMDLINE | |
| # run aemu | |
| set -x | |
| "${AEMU_DIR}/${AEMU}" "${OPTIONS[@]}" -fuchsia \ | |
| -kernel "${KERNEL_IMAGE}" \ | |
| -initrd "$KERNEL_ZBI" "${ARGS[@]}" -append "$CMDLINE" "${EMU_ARGS[@]}" |