Custom VM

Headless VMs

If your VM is headless (i.e. console in/out is the primary way of interacting with it), you can spawn it by passing a JSON config file to the VirtualizationService via the vm tool on a rooted AVF-enabled device. If your device is attached over ADB, you can run:

cat > vm_config.json <<EOF
{
  "kernel": "/data/local/tmp/kernel",
  "initrd": "/data/local/tmp/ramdisk",
  "params": "rdinit=/bin/init"
}
EOF
adb root
adb push <kernel> /data/local/tmp/kernel
adb push <ramdisk> /data/local/tmp/ramdisk
adb push vm_config.json /data/local/tmp/vm_config.json
adb shell "/apex/com.android.virt/bin/vm run /data/local/tmp/vm_config.json"

The vm command also has other subcommands for debugging; run /apex/com.android.virt/bin/vm help for details.

Running Debian

  1. Download an ARM64 image from https://cloud.debian.org/images/cloud/ (We tested nocloud image)

  2. Resize the image

truncate -s 20G debian.img
virt-resize --expand /dev/sda1 <download_image_file> debian.img
  1. Copy the image file
tar cfS debian.img.tar debian.img
adb push debian.img.tar /data/local/tmp/
adb shell tar xf /data/local/tmp/debian.img.tar -C /data/local/tmp/
adb shell rm /data/local/tmp/debian.img.tar
adb shell chmod a+w /data/local/tmp/debian.img
rm debian.img.tar

Note: we tar and untar to keep the image file sparse.

  1. Make the VM config file
cat > vm_config.json <<EOF
{
    "name": "debian",
    "disks": [
        {
            "image": "/data/local/tmp/debian.img",
            "partitions": [],
            "writable": true
        }
    ],
    "protected": false,
    "cpu_topology": "match_host",
    "platform_version": "~1.0",
    "memory_mib": 8096,
    "debuggable": true,
    "console_out": true,
    "connect_console": true,
    "console_input_device": "ttyS0",
    "network": true,
    "input": {
        "touchscreen": true,
        "keyboard": true,
        "mouse": true,
        "trackpad": true,
        "switches": true
    },
    "audio": {
        "speaker": true,
         "microphone": true
    },
    "gpu": {
        "backend": "virglrenderer",
        "context_types": ["virgl2"]
    },
    "display": {
        "refresh_rate": "30"
    }
}
EOF
adb push vm_config.json /data/local/tmp/
  1. Launch VmLauncherApp(the detail will be explain below)

  2. For console, we can refer to Debugging section below. (id: root)

  3. For graphical shell, you need to install xfce(for now, only xfce is tested)

apt install task-xfce-desktop
dpkg --configure -a (if necessary)
systemctl set-default graphical.target

# need non-root user for graphical shell
adduser linux
# optional
adduser linux sudo
reboot

Graphical VMs

To run OSes with graphics support, simply packages/modules/Virtualization/tests/ferrochrome/ferrochrome.sh --forever. It prepares and launches the ChromiumOS, which is the only officially supported guest payload. We will be adding more OSes in the future.

If you want to do so by yourself, follow the instruction below.

Prepare a guest image

As of today (April 2024), ChromiumOS is the only officially supported guest payload. We will be adding more OSes in the future.

Download ChromiumOS from build server

Download https://storage.googleapis.com/chromiumos-image-archive/ferrochrome-public/R128-15926.0.0/chromiumos_test_image.tar.xz. The above will download ferrochrome test image with version R128-15926.0.0.

To download latest version, use following code.

URL=https://storage.googleapis.com/chromiumos-image-archive/ferrochrome-public
LATEST_VERSION=$(curl -s ${URL}/LATEST-main)
curl -O ${URL}/${LATEST_VERSION}/chromiumos_test_image.tar.xz

To navigate build server artifacts, install gsutil. gs://chromiumos-image-archive/ferrochrome-public is the top level directory for ferrochrome build.

Build ChromiumOS for VM

First, check out source code from the ChromiumOS and Chromium projects.

Important: When you are at the step “Set up gclient args” in the Chromium checkout instruction, configure .gclient as follows.

$ cat ~/chromium/.gclient
solutions = [
  {
    "name": "src",
    "url": "https://chromium.googlesource.com/chromium/src.git",
    "managed": False,
    "custom_deps": {},
    "custom_vars": {},
  },
]
target_os = ['chromeos']

In this doc, it is assumed that ChromiumOS is checked out at ~/chromiumos and Chromium is at ~/chromium. If you downloaded to different places, you can create symlinks.

Then enter into the cros sdk.

$ cd ~/chromiumos
$ cros_sdk --chrome-root=$(readlink -f ~/chromium)

Now you are in the cros sdk. (cr) below means that the commands should be executed inside the sdk.

First, choose the target board. ferrochrome is the name of the virtual board for AVF-compatible VM.

(cr) setup_board --board=ferrochrome

Then, tell the cros sdk that you want to build chrome (the browser) from the local checkout and also with your local modifications instead of prebuilts.

(cr) CHROME_ORIGIN=LOCAL_SOURCE
(cr) ACCEPT_LICENSES='*'
(cr) cros workon -b ferrochrome start \
chromeos-base/chromeos-chrome \
chromeos-base/chrome-icu

Optionally, if you have touched the kernel source code (which is under ~/chromiumos/src/third_party/kernel/v5.15), you have to tell the cros sdk that you want it also to be built from the modified source code, not from the official HEAD.

(cr) cros workon -b ferrochrome start chromeos-kernel-5_15

Finally, build individual packages, and build the disk image out of the packages.

(cr) cros build-packages --board=ferrochrome --chromium --accept-licenses='*'
(cr) cros build-image --board=ferrochrome --no-enable-rootfs-verification test

This takes some time. When the build is done, exit from the sdk.

Note: If build-packages doesn’t seem to include your local changes, try invoking emerge directly:

(cr) emerge-ferrochrome -av chromeos-base/chromeos-chrome

Don’t forget to call build-image afterwards.

You need ChromiumOS disk image: ~/chromiumos/src/build/images/ferrochrome/latest/chromiumos_test_image.bin

Create a guest VM configuration

Push the kernel and the main image to the Android device.

$ adb push  ~/chromiumos/src/build/images/ferrochrome/latest/chromiumos_test_image.bin /data/local/tmp/

Create a VM config file as below.

$ cat > vm_config.json; adb push vm_config.json /data/local/tmp
{
    "name": "cros",
    "disks": [
        {
            "image": "/data/local/tmp/chromiumos_test_image.bin",
            "partitions": [],
            "writable": true
        }
    ],
    "protected": false,
    "cpu_topology": "match_host",
    "platform_version": "~1.0",
    "memory_mib": 8096,
    "debuggable": true,
    "console_out": true,
    "connect_console": true,
    "console_input_device": "hvc0",
    "network": true,
    "input": {
        "touchscreen": true,
        "keyboard": true,
        "mouse": true,
        "trackpad": true,
        "switches": true
    },
    "audio": {
        "speaker": true,
        "microphone": true
    },
    "gpu": {
        "backend": "virglrenderer",
        "context_types": ["virgl2"]
    },
    "display": {
        "scale": "0.77",
        "refresh_rate": "30"
    }
}

Running the VM

  1. Grant permission to the VmLauncherApp if the virt apex is Google-signed.

    $ adb shell su root pm grant com.google.android.virtualization.vmlauncher android.permission.USE_CUSTOM_VIRTUAL_MACHINE
    
  2. Ensure your device is connected to the Internet.

  3. Launch the app with adb.

    $ adb shell su root am start-activity -a android.virtualization.VM_LAUNCHER
    

If it doesn’t work well, try

$ adb shell pm clear com.android.virtualization.vmlauncher
# or
$ adb shell pm clear com.google.android.virtualization.vmlauncher

Debugging

To open the serial console (interactive terminal):

$ adb shell -t /apex/com.android.virt/bin/vm console

To see console logs only, check /data/user/${current_user_id}/com{,.google}.android.virtualization.vmlauncher/files/${vm_name}.log

You can monitor console out as follows

$ adb shell 'su root tail +0 -F /data/user/$(am get-current-user)/com{,.google}.android.virtualization.vmlauncher/files/${vm_name}.log'

For ChromiumOS, you can enter to the console via SSH connection. Check your IP address of ChromiumOS VM from the ethernet network setting page and follow commands below.

$ adb kill-server ; adb start-server
$ adb shell nc -s localhost -L -p 9222 nc ${CHROMIUMOS_IPV4_ADDR} 22 # This command won't be terminated.
$ adb forward tcp:9222 tcp:9222
$ ssh -oProxyCommand=none -o UserKnownHostsFile=/dev/null root@localhost -p 9222

For ChromiumOS, you would need to login after enthering its console. The user ID and the password is root and test0000 respectively.