1# Custom VM 2 3## Headless VMs 4 5If your VM is headless (i.e. console in/out is the primary way of interacting 6with it), you can spawn it by passing a JSON config file to the 7VirtualizationService via the `vm` tool on a rooted AVF-enabled device. If your 8device is attached over ADB, you can run: 9 10```shell 11cat > vm_config.json <<EOF 12{ 13 "kernel": "/data/local/tmp/kernel", 14 "initrd": "/data/local/tmp/ramdisk", 15 "params": "rdinit=/bin/init" 16} 17EOF 18adb root 19adb push <kernel> /data/local/tmp/kernel 20adb push <ramdisk> /data/local/tmp/ramdisk 21adb push vm_config.json /data/local/tmp/vm_config.json 22adb shell "/apex/com.android.virt/bin/vm run /data/local/tmp/vm_config.json" 23``` 24 25The `vm` command also has other subcommands for debugging; run 26`/apex/com.android.virt/bin/vm help` for details. 27 28### Running Debian with u-boot 291. Prepare u-boot binary from `u-boot_crosvm_aarch64` in https://ci.android.com/builds/branches/aosp_u-boot-mainline/grid 30or build it by https://source.android.com/docs/devices/cuttlefish/bootloader-dev#develop-bootloader 312. Prepare Debian image from https://cloud.debian.org/images/cloud/ (We tested nocloud image) 323. Copy `u-boot.bin`, Debian image file(like `debian-12-nocloud-arm64.raw`) and `vm_config.json` to `/data/local/tmp` 33```shell 34cat > vm_config.json <<EOF 35{ 36 "name": "debian", 37 "bootloader": "/data/local/tmp/u-boot.bin", 38 "disks": [ 39 { 40 "image": "/data/local/tmp/debian-12-nocloud-arm64.raw", 41 "partitions": [], 42 "writable": true 43 } 44 ], 45 "protected": false, 46 "cpu_topology": "match_host", 47 "platform_version": "~1.0", 48 "memory_mib" : 8096 49} 50EOF 51adb push `u-boot.bin` /data/local/tmp 52adb push `debian-12-nocloud-arm64.raw` /data/local/tmp 53adb push vm_config.json /data/local/tmp/vm_config.json 54``` 554. Launch VmLauncherApp(the detail will be explain below) 56 57## Graphical VMs 58 59To run OSes with graphics support, follow the instruction below. 60 61### Prepare a guest image 62 63As of today (April 2024), ChromiumOS is the only officially supported guest 64payload. We will be adding more OSes in the future. 65 66#### Download from build server 67 68 - Step 1) Go to the link https://ci.chromium.org/ui/p/chromeos/builders/chromiumos/ferrochrome-public-main/ 69 - Note: I 'searched' the ferrochrome target with builder search. 70 - Step 2) Click a build number 71 - Step 3) Expand steps and find `48. upload artifacts`. 72 - Step 4) Click `gs upload dir`. You'll see Cloud storage with comprehensive artifacts (e.g. [Here](https://pantheon.corp.google.com/storage/browser/chromiumos-image-archive/ferrochrome-public/R126-15883.0.0) is the initial build of ferrochrome) 73 - Step 5) Download `image.zip`, which contains working vmlinuz. 74 - Note: DO NOT DOWNLOAD `vmlinuz.tar.xz` from the CI. 75 - Step 6) Uncompress `image.zip`, and boot with `chromiumos_test_image.bin` and `boot_images/vmlinuz`. 76 - Note: DO NOT USE `vmlinuz.bin`. 77 78IMPORTANT: DO NOT USE `vmlinuz.bin` for passing to crosvm. It doesn't pick-up the correct `init` process (picks `/init` instead of `/sbin/init`, and `cfg80211` keeps crashing (i.e. no network) 79 80 81#### Build ChromiumOS for VM 82 83First, check out source code from the ChromiumOS and Chromium projects. 84 85* Checking out ChromiumOS: https://www.chromium.org/chromium-os/developer-library/guides/development/developer-guide/ 86* Checking out Chromium: https://g3doc.corp.google.com/chrome/chromeos/system_services_team/dev_instructions/g3doc/setup_checkout.md?cl=head 87 88Important: When you are at the step “Set up gclient args” in the Chromium checkout instruction, configure .gclient as follows. 89 90``` 91$ cat ~/chromium/.gclient 92solutions = [ 93 { 94 "name": "src", 95 "url": "https://chromium.googlesource.com/chromium/src.git", 96 "managed": False, 97 "custom_deps": {}, 98 "custom_vars": { 99 "checkout_src_internal": True, 100 }, 101 }, 102] 103target_os = ['chromeos'] 104``` 105 106In this doc, it is assumed that ChromiumOS is checked out at `~/chromiumos` and 107Chromium is at `~/chromium`. If you downloaded to different places, you can 108create symlinks. 109 110Then enter into the cros sdk. 111 112``` 113$ cd ~/chromiumos 114$ cros_sdk --chrome-root=$(readlink -f ~/chromium) 115``` 116 117Now you are in the cros sdk. `(cr)` below means that the commands should be 118executed inside the sdk. 119 120First, choose the target board. `ferrochrome` is the name of the virtual board 121for AVF-compatible VM. 122 123``` 124(cr) setup_board --board=ferrochrome 125``` 126 127Then, tell the cros sdk that you want to build chrome (the browser) from the 128local checkout and also with your local modifications instead of prebuilts. 129 130``` 131(cr) CHROME_ORIGIN=LOCAL_SOURCE 132(cr) ACCEPT_LICENSES='*' 133(cr) cros workon -b ferrochrome start \ 134chromeos-base/chromeos-chrome \ 135chromeos-base/chrome-icu 136``` 137 138Optionally, if you have touched the kernel source code (which is under 139~/chromiumos/src/third_party/kernel/v5.15), you have to tell the cros sdk that 140you want it also to be built from the modified source code, not from the 141official HEAD. 142 143``` 144(cr) cros workon -b ferrochrome start chromeos-kernel-5_15 145``` 146 147Finally, build individual packages, and build the disk image out of the packages. 148 149``` 150(cr) cros build-packages --board=ferrochrome --chromium --accept-licenses='*' 151(cr) cros build-image --board=ferrochrome --no-enable-rootfs-verification test 152``` 153 154This takes some time. When the build is done, exit from the sdk. 155 156Note: If build-packages doesn’t seem to include your local changes, try 157invoking emerge directly: 158 159``` 160(cr) emerge-ferrochrome -av chromeos-base/chromeos-chrome 161``` 162 163Don’t forget to call `build-image` afterwards. 164 165You need two outputs: 166 167* ChromiumOS disk image: ~/chromiumos/src/build/images/ferrochrome/latest/chromiumos_test_image.bin 168* The kernel: ~/chromiumos/src/build/images/ferrochrome/latest/boot_images/vmlinuz 169 170### Create a guest VM configuration 171 172Push the kernel and the main image to the Android device. 173 174``` 175$ adb push ~/chromiumos/src/build/images/ferrochrome/latest/chromiumos_test_image.bin /data/local/tmp/ 176$ adb push ~/chromiumos/out/build/ferrochrome/boot/vmlinuz /data/local/tmp/kernel 177``` 178 179Create a VM config file as below. 180 181``` 182$ cat > vm_config.json; adb push vm_config.json /data/local/tmp 183{ 184 "name": "cros", 185 "kernel": "/data/local/tmp/kernel", 186 "disks": [ 187 { 188 "image": "/data/local/tmp/chromiumos_test_image.bin", 189 "partitions": [], 190 "writable": true 191 } 192 ], 193 "gpu": { 194 "backend": "virglrenderer", 195 "context_types": ["virgl2"] 196 }, 197 "params": "root=/dev/vda3 rootwait noinitrd ro enforcing=0 cros_debug cros_secure", 198 "protected": false, 199 "cpu_topology": "match_host", 200 "platform_version": "~1.0", 201 "memory_mib" : 8096, 202 "console_input_device": "ttyS0" 203} 204``` 205 206### Running the VM 207 208First, enable the `VmLauncherApp` app. This needs to be done only once. In the 209future, this step won't be necesssary. 210 211``` 212$ adb root 213$ adb shell pm enable com.android.virtualization.vmlauncher/.MainActivity 214$ adb unroot 215``` 216 217If virt apex is Google-signed, you need to enable the app and grant the 218permission to the app. 219``` 220$ adb root 221$ adb shell pm enable com.google.android.virtualization.vmlauncher/com.android.virtualization.vmlauncher.MainActivity 222$ adb shell pm grant com.google.android.virtualization.vmlauncher android.permission.USE_CUSTOM_VIRTUAL_MACHINE 223$ adb unroot 224``` 225Then execute the below to set up the network. In the future, this step won't be necessary. 226 227``` 228$ cat > setup_network.sh; adb push setup_network.sh /data/local/tmp 229#!/system/bin/sh 230 231set -e 232 233TAP_IFACE=crosvm_tap 234TAP_ADDR=192.168.1.1 235TAP_NET=192.168.1.0 236 237function setup_network() { 238 local WAN_IFACE=$(ip route get 8.8.8.8 2> /dev/null | awk -- '{printf $5}') 239 if [ "${WAN_IFACE}" == "" ]; then 240 echo "No network. Connect to a WiFi network and start again" 241 return 1 242 fi 243 244 if ip link show ${TAP_IFACE} &> /dev/null ; then 245 echo "TAP interface ${TAP_IFACE} already exists" 246 return 1 247 fi 248 249 ip tuntap add mode tap group virtualmachine vnet_hdr ${TAP_IFACE} 250 ip addr add ${TAP_ADDR}/24 dev ${TAP_IFACE} 251 ip link set ${TAP_IFACE} up 252 ip rule flush 253 ip rule add from all lookup ${WAN_IFACE} 254 ip route add ${TAP_NET}/24 dev ${TAP_IFACE} table ${WAN_IFACE} 255 sysctl net.ipv4.ip_forward=1 256 iptables -t filter -F 257 iptables -t nat -A POSTROUTING -s ${TAP_NET}/24 -j MASQUERADE 258} 259 260function setup_if_necessary() { 261 if [ "$(getprop ro.crosvm.network.setup.done)" == 1 ]; then 262 return 263 fi 264 echo "Setting up..." 265 check_privilege 266 setup_network 267 setenforce 0 268 chmod 666 /dev/tun 269 setprop ro.crosvm.network.setup.done 1 270} 271 272function check_privilege() { 273 if [ "$(id -u)" -ne 0 ]; then 274 echo "Run 'adb root' first" 275 return 1 276 fi 277} 278 279setup_if_necessary 280^D 281 282adb root; adb shell /data/local/tmp/setup_network.sh 283``` 284 285Then, finally tap the VmLauncherApp app from the launcher UI. You will see 286Ferrochrome booting! 287 288If it doesn’t work well, try 289 290``` 291$ adb shell pm clear com.android.virtualization.vmlauncher 292``` 293 294### Inside guest OS (for ChromiumOS only) 295 296Go to the network setting and configure as below. 297 298* IP: 192.168.1.2 (other addresses in the 192.168.1.0/24 subnet also works) 299* netmask: 255.255.255.0 300* gateway: 192.168.1.1 301* DNS: 8.8.8.8 (or any DNS server you know) 302 303These settings are persistent; stored in chromiumos_test_image.bin. So you 304don’t have to repeat this next time. 305 306### Debugging 307 308To see console log, check 309`/data/data/com.android.virtualization.vmlauncher/files/console.log` 310 311For ChromiumOS, you can ssh-in. Use following commands after network setup. 312 313```shell 314$ adb kill-server ; adb start-server; adb forward tcp:9222 tcp:9222 315$ ssh -oProxyCommand=none -o UserKnownHostsFile=/dev/null root@localhost -p 9222 316``` 317