| #!/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>] [-k <authorized_keys_file>] [-K <kernel_image>] [-z <zbi_image>] [-f <fvm_image>] [--audio] [--headless] [--software-gpu] [--debugger] |
| ## -a <mode> acceleration mode (auto, off, kvm, hvf, hax), default is auto |
| ## -c <text> add 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, default is 4 |
| ## -k <authorized_keys_file> SSH authorized keys file, otherwise defaults to //.ssh/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. |
| ## -f <fvm_image> path to full FVM image, defaults to image generated by the current build. |
| ## --audio run with audio hardware added to the virtual machine |
| ## --headless run in headless mode |
| ## --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 |
| |
| 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 $? |
| |
| ACCEL="auto" |
| NET=0 |
| DEBUGGER=0 |
| IFNAME="" |
| AUDIO=0 |
| HEADLESS=0 |
| AEMU="emulator" |
| AEMU_DIR="" |
| UPSCRIPT= |
| WINDOW_SIZE="1280x800" |
| GRPC= |
| RTCFPS="30" |
| TURNCFG="" |
| GPU="auto" |
| VULKAN=1 |
| HTTP=0 |
| GRPCWEBPROXY_DIR="" |
| CMDLINE="" |
| SMP=4 |
| AUTHORIZED_KEYS="${FUCHSIA_DIR}/.ssh/authorized_keys" |
| IMAGE_SIZE= |
| EXPERIMENT_ARM64=false |
| KERNEL_IMAGE="${FUCHSIA_BUILD_DIR}/${IMAGE_QEMU_KERNEL_RAW}" |
| ZBI_IMAGE="${FUCHSIA_BUILD_DIR}/${IMAGE_ZIRCONA_ZBI}" |
| if [[ -n "${IMAGE_FVM_RAW}" ]]; then |
| FVM_IMAGE="${FUCHSIA_BUILD_DIR}/${IMAGE_FVM_RAW}" |
| else |
| FVM_IMAGE="" |
| fi |
| |
| |
| case $(uname -m) in |
| x86_64) |
| HOST_ARCH=x64 |
| ;; |
| aarch64) |
| HOST_ARCH=arm64 |
| ;; |
| *) |
| fx-error "Unsupported host architecture: $(uname -m)" |
| exit 1 |
| esac |
| |
| if [[ "$FUCHSIA_ARCH" != "$HOST_ARCH" ]]; then |
| ACCEL=off |
| fi |
| |
| # 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 |
| 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" |
| ;; |
| -k) |
| shift |
| AUTHORIZED_KEYS="$1" |
| ;; |
| -K) |
| shift |
| KERNEL_IMAGE="$1" |
| ;; |
| -z) |
| shift |
| ZBI_IMAGE="$1" |
| ;; |
| -f) |
| shift |
| FVM_IMAGE="$1" |
| ;; |
| --audio) |
| AUDIO=1 |
| ;; |
| --headless) |
| HEADLESS=1 |
| ;; |
| --debugger) |
| DEBUGGER=1 |
| ;; |
| --host-gpu) |
| GPU="host" |
| ;; |
| --software-gpu) |
| GPU="swiftshader_indirect" |
| ;; |
| --experiment-arm64) |
| EXPERIMENT_ARM64=true |
| ;; |
| *) |
| break |
| esac |
| shift |
| done |
| |
| 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 |
| |
| # construct the args for aemu |
| ARGS=() |
| ARGS+=("-m" "2048") |
| ARGS+=("-serial" "stdio") |
| ARGS+=("-vga" "none") |
| ARGS+=("-device" "virtio-keyboard-pci") |
| ARGS+=("-device" "virtio_input_multi_touch_pci_1") |
| |
| if [[ $SMP != 1 ]]; then |
| ARGS+=("-smp" "${SMP},threads=2") |
| fi |
| |
| # TODO(raggi): do we want to use hda on arm64? |
| if (( AUDIO )); then |
| ARGS+=("-soundhw" "hda") |
| fi |
| |
| FEATURES="VirtioInput,RefCountPipe" |
| |
| if [[ "$ACCEL" == "auto" ]]; then |
| if [[ "$(uname -s)" == "Darwin" ]]; then |
| ACCEL="hvf" |
| else |
| ACCEL="kvm" |
| fi |
| fi |
| |
| case "$FUCHSIA_ARCH" in |
| x64) |
| ARGS+=("-machine" "q35") |
| ARGS+=("-device" "isa-debug-exit,iobase=0xf4,iosize=0x04") |
| ;; |
| arm64) |
| ARGS+=("-machine" "virt") |
| ;; |
| esac |
| |
| if [[ "$ACCEL" == "kvm" ]]; then |
| if [[ ! -w "/dev/kvm" ]]; then |
| echo "To use KVM acceleration, adjust permissions to /dev/kvm using:" |
| echo |
| echo "sudo chmod 666 /dev/kvm" |
| exit 1 |
| fi |
| ARGS+=("-enable-kvm" "-cpu" "host,migratable=no,+invtsc") |
| FEATURES+=",KVM,GLDirectMem" |
| elif [[ "$ACCEL" == "hvf" ]]; then |
| ARGS+=("-enable-hvf" "-cpu" "Haswell") |
| FEATURES+=",HVF,GLDirectMem" |
| elif [[ "$ACCEL" == "hax" ]]; then |
| ARGS+=("-enable-hax" "-cpu" "Haswell") |
| FEATURES+=",HAXM,GLDirectMem" |
| elif [[ "$ACCEL" == "off" ]]; then |
| case "$FUCHSIA_ARCH" in |
| x64) |
| ARGS+=("-cpu" "Haswell,+smap,-check,-fsgsbase") |
| ;; |
| arm64) |
| ARGS+=("-cpu" "cortex-a53") |
| ;; |
| esac |
| # disable vulkan as not useful without kvm,hvf,hax and support |
| # for coherent host visible memory. |
| VULKAN=0 |
| FEATURES+=",-GLDirectMem" |
| 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") |
| if (( DEBUGGER )); then |
| OPTIONS+=("-wait-for-debugger") |
| fi |
| |
| if (( HEADLESS )); then |
| OPTIONS+=("-no-window") |
| fi |
| |
| if [[ "$FUCHSIA_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 |
| ARGS+=("-netdev" "type=tap,ifname=$IFNAME,id=net0${UPSCRIPT:+,script=${UPSCRIPT}}") |
| 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" "e1000,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=/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/authorized_keys for SSH access. |
| img_dir="$(mktemp -d)" |
| if [[ ! -d "${img_dir}" ]]; then |
| fx-error "Failed to create temporary directory" |
| exit 1 |
| fi |
| trap 'rm -Rf "${img_dir}" ; [[ "${GRPCWEBPROXY_PID}" ]] && kill "${GRPCWEBPROXY_PID}"; stty sane' EXIT |
| |
| KERNEL_ZBI="${img_dir}/fuchsia-ssh.zbi" |
| fx-zbi -o "${KERNEL_ZBI}" "${ZBI_IMAGE}" \ |
| --entry "data/ssh/authorized_keys=${AUTHORIZED_KEYS}" |
| |
| if [[ -n "$FVM_IMAGE" ]]; then |
| fvmimg="${img_dir}/fvm.blk" |
| fx-fvm-extend-image "${FVM_IMAGE}" "${fvmimg}" "${IMAGE_SIZE}" |
| ARGS+=("-drive" "file=${fvmimg},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 " |
| |
| # run aemu |
| set -x |
| "${AEMU_DIR}/${AEMU}" "${OPTIONS[@]}" -fuchsia \ |
| -kernel "${KERNEL_IMAGE}" \ |
| -initrd "$KERNEL_ZBI" "${ARGS[@]}" -append "$CMDLINE" "$@" |