# Copyright 2018 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Implements commands for running and interacting with Fuchsia on QEMU.""" import boot_data import logging import target import os import platform import socket import subprocess import time from common import SDK_ROOT, EnsurePathExists # Virtual networking configuration data for QEMU. GUEST_NET = '192.168.3.0/24' GUEST_IP_ADDRESS = '192.168.3.9' HOST_IP_ADDRESS = '192.168.3.2' GUEST_MAC_ADDRESS = '52:54:00:63:5e:7b' def _GetAvailableTcpPort(): """Finds a (probably) open port by opening and closing a listen socket.""" sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(("", 0)) port = sock.getsockname()[1] sock.close() return port class QemuTarget(target.Target): def __init__(self, output_dir, target_cpu, system_log_file, ram_size_mb=2048): """output_dir: The directory which will contain the files that are generated to support the QEMU deployment. target_cpu: The emulated target CPU architecture. Can be 'x64' or 'arm64'.""" super(QemuTarget, self).__init__(output_dir, target_cpu) self._qemu_process = None self._ram_size_mb = ram_size_mb self._system_log_file = system_log_file def __enter__(self): return self # Used by the context manager to ensure that QEMU is killed when the Python # process exits. def __exit__(self, exc_type, exc_val, exc_tb): if self.IsStarted(): self.Shutdown() def Start(self): qemu_path = os.path.join(SDK_ROOT, 'qemu', 'bin', 'qemu-system-' + self._GetTargetSdkArch()) kernel_args = boot_data.GetKernelArgs(self._output_dir) # TERM=dumb tells the guest OS to not emit ANSI commands that trigger # noisy ANSI spew from the user's terminal emulator. kernel_args.append('TERM=dumb') # Enable logging to the serial port. This is a temporary fix to investigate # the root cause for https://crbug.com/869753 . kernel_args.append('kernel.serial=legacy') qemu_command = [qemu_path, '-m', str(self._ram_size_mb), '-nographic', '-kernel', EnsurePathExists( boot_data.GetTargetFile(self._GetTargetSdkArch(), 'zircon.bin')), '-initrd', EnsurePathExists( boot_data.GetTargetFile(self._GetTargetSdkArch(), 'bootdata-blob.bin')), '-smp', '4', # Attach the blobstore and data volumes. Use snapshot mode to discard # any changes. '-snapshot', '-drive', 'file=%s,format=qcow2,if=none,id=data,snapshot=on' % EnsurePathExists(os.path.join(self._output_dir, 'fvm.blk.qcow2')), '-drive', 'file=%s,format=qcow2,if=none,id=blobstore,snapshot=on' % EnsurePathExists( boot_data.ConfigureDataFVM(self._output_dir, boot_data.FVM_TYPE_QCOW)), '-device', 'virtio-blk-pci,drive=data', '-device', 'virtio-blk-pci,drive=blobstore', # Use stdio for the guest OS only; don't attach the QEMU interactive # monitor. '-serial', 'stdio', '-monitor', 'none', '-append', ' '.join(kernel_args) ] # Configure the machine & CPU to emulate, based on the target architecture. # Enable lightweight virtualization (KVM) if the host and guest OS run on # the same architecture. if self._target_cpu == 'arm64': qemu_command.extend([ '-machine','virt', '-cpu', 'cortex-a53', ]) netdev_type = 'virtio-net-pci' if platform.machine() == 'aarch64': qemu_command.append('-enable-kvm') else: qemu_command.extend([ '-machine', 'q35', '-cpu', 'host,migratable=no', ]) netdev_type = 'e1000' if platform.machine() == 'x86_64': qemu_command.append('-enable-kvm') # Configure virtual network. It is used in the tests to connect to # testserver running on the host. netdev_config = 'user,id=net0,net=%s,dhcpstart=%s,host=%s' % \ (GUEST_NET, GUEST_IP_ADDRESS, HOST_IP_ADDRESS) self._host_ssh_port = _GetAvailableTcpPort() netdev_config += ",hostfwd=tcp::%s-:22" % self._host_ssh_port qemu_command.extend([ '-netdev', netdev_config, '-device', '%s,netdev=net0,mac=%s' % (netdev_type, GUEST_MAC_ADDRESS), ]) # We pass a separate stdin stream to qemu. Sharing stdin across processes # leads to flakiness due to the OS prematurely killing the stream and the # Python script panicking and aborting. # The precise root cause is still nebulous, but this fix works. # See crbug.com/741194. logging.debug('Launching QEMU.') logging.debug(' '.join(qemu_command)) # Zircon sends debug logs to serial port (see kernel.serial=legacy flag # above). Serial port is redirected to a file through QEMU stdout. # This approach is used instead of loglistener to debug # https://crbug.com/86975 . if self._system_log_file: stdout = open(self._system_log_file, 'w') stderr = subprocess.STDOUT else: stdout = open(os.devnull) stderr = open(os.devnull) self._qemu_process = subprocess.Popen(qemu_command, stdin=open(os.devnull), stdout=stdout, stderr=stderr) self._WaitUntilReady(); def Shutdown(self): logging.info('Shutting down QEMU.') self._qemu_process.kill() def GetQemuStdout(self): return self._qemu_process.stdout def _GetEndpoint(self): return ('localhost', self._host_ssh_port) def _GetSshConfigPath(self): return boot_data.GetSSHConfigPath(self._output_dir)