Merge "Microdroid low memory test."
diff --git a/OWNERS b/OWNERS
index 7e479c4..5bd97f3 100644
--- a/OWNERS
+++ b/OWNERS
@@ -12,6 +12,7 @@
 ardb@google.com
 ascull@google.com
 inseob@google.com
+jeffv@google.com
 jooyung@google.com
 mzyngier@google.com
 ptosi@google.com
diff --git a/TEST_MAPPING b/TEST_MAPPING
index cf90c76..33498d3 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -25,6 +25,9 @@
     },
     {
       "name": "MicrodroidBenchmarkApp"
+    },
+    {
+      "name": "ComposBenchmarkApp"
     }
   ],
   "imports": [
diff --git a/apkdmverity/Android.bp b/apkdmverity/Android.bp
index 06d4500..d7aa921 100644
--- a/apkdmverity/Android.bp
+++ b/apkdmverity/Android.bp
@@ -11,7 +11,7 @@
     rustlibs: [
         "libanyhow",
         "libbitflags",
-        "libclap",
+        "libclap_deprecated",
         "libdata_model",
         "libidsig",
         "libitertools",
diff --git a/authfs/fd_server/Android.bp b/authfs/fd_server/Android.bp
index 9499cd2..c4aa1d8 100644
--- a/authfs/fd_server/Android.bp
+++ b/authfs/fd_server/Android.bp
@@ -13,7 +13,7 @@
         "libbinder_common",
         "libbinder_rpc_unstable_bindgen",
         "libbinder_rs",
-        "libclap",
+        "libclap_deprecated",
         "liblibc",
         "liblog_rust",
         "libnix",
diff --git a/authfs/tests/Android.bp b/authfs/tests/Android.bp
index 61a6ef5..a886d10 100644
--- a/authfs/tests/Android.bp
+++ b/authfs/tests/Android.bp
@@ -11,7 +11,7 @@
         "compatibility-host-util",
     ],
     static_libs: [
-        "VirtualizationTestHelper",
+        "MicrodroidHostTestHelper",
     ],
     test_suites: ["general-tests"],
     //TODO(b/235263148) use data_device_bins_64
@@ -31,7 +31,7 @@
     rustlibs: [
         "libandroid_logger",
         "libanyhow",
-        "libclap",
+        "libclap_deprecated",
         "libcommand_fds",
         "liblog_rust",
         "libnix",
diff --git a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
index f5254bc..fcd6753 100644
--- a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
+++ b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
@@ -16,9 +16,8 @@
 
 package com.android.virt.fs;
 
-import static android.virt.test.CommandResultSubject.assertThat;
-import static android.virt.test.LogArchiver.archiveLogThenDelete;
-
+import static com.android.microdroid.test.CommandResultSubject.assertThat;
+import static com.android.microdroid.test.LogArchiver.archiveLogThenDelete;
 import static com.android.tradefed.device.TestDevice.MicrodroidBuilder;
 import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
 
@@ -30,10 +29,10 @@
 import static org.junit.Assume.assumeTrue;
 
 import android.platform.test.annotations.RootPermissionTest;
-import android.virt.test.CommandRunner;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.compatibility.common.util.PollingCheck;
+import com.android.microdroid.test.CommandRunner;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
diff --git a/avmd/Android.bp b/avmd/Android.bp
new file mode 100644
index 0000000..e31e103
--- /dev/null
+++ b/avmd/Android.bp
@@ -0,0 +1,38 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+    name: "libavmd_defaults",
+    crate_name: "avmd",
+    host_supported: true,
+    srcs: ["src/lib.rs"],
+    prefer_rlib: true,
+    rustlibs: [
+        "libserde",
+        "libapexutil_rust", // TODO(b/239413416): Remove this after adding hex
+    ],
+}
+
+rust_library {
+    name: "libavmd",
+    defaults: ["libavmd_defaults"],
+}
+
+rust_binary {
+    name: "avmdtool",
+    srcs: ["src/main.rs"],
+    required: ["avbtool"],
+    host_supported: true,
+    prefer_rlib: true,
+    rustlibs: [
+        "libanyhow",
+        "libapexutil_rust",
+        "libapkverify",
+        "libavmd",
+        "libclap_deprecated",
+        "libserde",
+        "libserde_cbor",
+        "libvbmeta_rust",
+    ],
+}
diff --git a/avmd/README.md b/avmd/README.md
new file mode 100644
index 0000000..ae813a0
--- /dev/null
+++ b/avmd/README.md
@@ -0,0 +1,48 @@
+# The AVMD image format
+---
+
+The AVMD image format is used to descibe the verified code that a VM will
+load. This repository contains tools and libraries for working with the AVMD
+image format.
+
+# What is it?
+
+When a VM boots, it loads and verifies a set of images that control execution
+within the VM. Therefore, describing what executes in a VM means describing
+what is loaded. The AVMD image format is designed, for this purpose, to
+describe the closure of images that can be loaded and how they should be
+verified.
+
+# Caveats
+
+The AVMD image format will only allow Android supported signing formats. The
+supported formats are currently limited to [AVB][] and [APK][].
+
+[AVB]: https://android.googlesource.com/platform/external/avb/+/master/README.md
+[APK]: https://source.android.com/security/apksigning#schemes
+
+Verification of the images as they are loaded is the responsibility of the VM.
+The VM is required to only load the images described and to verify them against
+the included parameters. If the VM does not follow this requirement, the
+description of the VM may not be accurate and must not be trusted. Validating
+that the VM behaves as expected requires audit of all boot stages of the VM.
+
+# Using avmdtool
+
+The `.avmd` file can be created as follows
+
+```bash
+avmdtool create /tmp/out.avmd \
+   --vbmeta pvmfw preload u-boot.bin \
+   --vbmeta uboot env_vbmeta disk1/vbmeta.imb \
+   --vbmeta uboot vbmeta micordoid/vbmeta.img \
+   --apk microdroid payload compos.apk \
+   --apk microdroid extra_apk extra_apk.apk \
+   --apex-payload microdroid art_apex art.apex
+```
+
+You can read the `.avmd` file with
+
+```bash
+avmdtool dump /tmp/out.avmd
+```
diff --git a/avmd/src/avmd.rs b/avmd/src/avmd.rs
new file mode 100644
index 0000000..50cdfdf
--- /dev/null
+++ b/avmd/src/avmd.rs
@@ -0,0 +1,154 @@
+// Copyright 2022, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+extern crate alloc;
+
+use alloc::{
+    string::{String, ToString},
+    vec::Vec,
+};
+use apexutil::to_hex_string;
+use core::fmt;
+use serde::{Deserialize, Serialize};
+
+/// An Avmd struct contains
+/// - A header with version information that allows rollback when needed.
+/// - A list of descriptors that describe different images.
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct Avmd {
+    header: Header,
+    descriptors: Vec<Descriptor>,
+}
+
+impl fmt::Display for Avmd {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        writeln!(f, "Descriptors:")?;
+        for descriptor in &self.descriptors {
+            write!(f, "{}", descriptor)?;
+        }
+        Ok(())
+    }
+}
+
+impl Avmd {
+    /// Creates an instance of Avmd with a given list of descriptors.
+    pub fn new(descriptors: Vec<Descriptor>) -> Avmd {
+        Avmd { header: Header::default(), descriptors }
+    }
+}
+
+static AVMD_MAGIC: u32 = 0x444d5641;
+static AVMD_VERSION_MAJOR: u16 = 1;
+static AVMD_VERSION_MINOR: u16 = 0;
+
+/// Header information for AVMD.
+#[derive(Serialize, Deserialize, Debug, Clone)]
+struct Header {
+    magic: u32,
+    version_major: u16,
+    version_minor: u16,
+}
+
+impl Default for Header {
+    fn default() -> Self {
+        Header {
+            magic: AVMD_MAGIC,
+            version_major: AVMD_VERSION_MAJOR,
+            version_minor: AVMD_VERSION_MINOR,
+        }
+    }
+}
+
+/// AVMD descriptor.
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub enum Descriptor {
+    /// Descriptor type for the VBMeta images.
+    VbMeta(VbMetaDescriptor),
+    /// Descriptor type for APK.
+    Apk(ApkDescriptor),
+}
+
+impl fmt::Display for Descriptor {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            Descriptor::VbMeta(descriptor) => write!(f, "{}", descriptor),
+            Descriptor::Apk(descriptor) => write!(f, "{}", descriptor),
+        }
+    }
+}
+
+/// VbMeta descriptor.
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct VbMetaDescriptor {
+    /// The identifier of this resource.
+    #[serde(flatten)]
+    pub resource: ResourceIdentifier,
+    /// The SHA-512 [VBMeta digest][] calculated from the top-level VBMeta image.
+    ///
+    /// [VBMeta digest]: https://android.googlesource.com/platform/external/avb/+/master/README.md#the-vbmeta-digest
+    pub vbmeta_digest: Vec<u8>,
+}
+
+impl fmt::Display for VbMetaDescriptor {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        writeln!(f, "  VBMeta descriptor:")?;
+        writeln!(f, "    namespace:             {}", self.resource.namespace)?;
+        writeln!(f, "    name:                  {}", self.resource.name)?;
+        writeln!(f, "    vbmeta digest:         {}", to_hex_string(&self.vbmeta_digest))?;
+        Ok(())
+    }
+}
+
+/// APK descriptor.
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct ApkDescriptor {
+    /// The identifier of this resource.
+    #[serde(flatten)]
+    pub resource: ResourceIdentifier,
+    /// The ID of the algoithm used to sign the APK.
+    /// It should be one of the algorithms in the [list][].
+    ///
+    /// [list]: https://source.android.com/security/apksigning/v2#signature-algorithm-ids
+    pub signature_algorithm_id: u32,
+    /// Digest of the APK's v3 signing block. TODO: fix
+    pub apk_digest: Vec<u8>,
+}
+
+impl fmt::Display for ApkDescriptor {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        writeln!(f, "  APK descriptor:")?;
+        writeln!(f, "    namespace:             {}", self.resource.namespace)?;
+        writeln!(f, "    name:                  {}", self.resource.name)?;
+        writeln!(f, "    Signing algorithm ID:  {:#x}", self.signature_algorithm_id)?;
+        writeln!(f, "    APK digest:            {}", to_hex_string(&self.apk_digest))?;
+        Ok(())
+    }
+}
+
+/// Resource identifier regroups information to identify resources.
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct ResourceIdentifier {
+    /// Namespace of the resource.
+    namespace: String,
+    /// Name of the resource.
+    name: String,
+}
+
+impl ResourceIdentifier {
+    /// Creates an instance of ResourceIdentifier with the given
+    /// namespace and name.
+    pub fn new(namespace: &str, name: &str) -> ResourceIdentifier {
+        ResourceIdentifier { namespace: namespace.to_string(), name: name.to_string() }
+    }
+}
diff --git a/avmd/src/lib.rs b/avmd/src/lib.rs
new file mode 100644
index 0000000..7a06e6a
--- /dev/null
+++ b/avmd/src/lib.rs
@@ -0,0 +1,21 @@
+// Copyright 2022, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Library for handling AVMD blobs.
+
+#![no_std]
+
+mod avmd;
+
+pub use avmd::{ApkDescriptor, Avmd, Descriptor, ResourceIdentifier, VbMetaDescriptor};
diff --git a/avmd/src/main.rs b/avmd/src/main.rs
new file mode 100644
index 0000000..b156a66
--- /dev/null
+++ b/avmd/src/main.rs
@@ -0,0 +1,157 @@
+// Copyright 2022, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Tool for handling AVMD blobs.
+
+use anyhow::{anyhow, bail, Result};
+use apexutil::get_payload_vbmeta_image_hash;
+use apkverify::pick_v4_apk_digest;
+use avmd::{ApkDescriptor, Avmd, Descriptor, ResourceIdentifier, VbMetaDescriptor};
+use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
+use serde::ser::Serialize;
+use std::fs::File;
+use vbmeta::VbMetaImage;
+
+fn get_vbmeta_image_hash(file: &str) -> Result<Vec<u8>> {
+    let img = VbMetaImage::verify_path(file)?;
+    Ok(img.hash().ok_or_else(|| anyhow!("No hash as VBMeta image isn't signed"))?.to_vec())
+}
+
+/// Iterate over a set of argument values, that could be empty or come in
+/// (<index>, <namespace>, <name>, <file>) tuple.
+struct NamespaceNameFileIterator<'a> {
+    indices: Option<clap::Indices<'a>>,
+    values: Option<clap::Values<'a>>,
+}
+
+impl<'a> NamespaceNameFileIterator<'a> {
+    fn new(args: &'a ArgMatches, name: &'a str) -> Self {
+        NamespaceNameFileIterator { indices: args.indices_of(name), values: args.values_of(name) }
+    }
+}
+
+impl<'a> Iterator for NamespaceNameFileIterator<'a> {
+    type Item = (usize, &'a str, &'a str, &'a str);
+
+    fn next(&mut self) -> Option<Self::Item> {
+        match (self.indices.as_mut(), self.values.as_mut()) {
+            (Some(indices), Some(values)) => {
+                match (indices.nth(2), values.next(), values.next(), values.next()) {
+                    (Some(index), Some(namespace), Some(name), Some(file)) => {
+                        Some((index, namespace, name, file))
+                    }
+                    _ => None,
+                }
+            }
+            _ => None,
+        }
+    }
+}
+
+fn create(args: &ArgMatches) -> Result<()> {
+    // Store descriptors in the order they were given in the arguments
+    // TODO: instead, group them by namespace?
+    let mut descriptors = std::collections::BTreeMap::new();
+    for (i, namespace, name, file) in NamespaceNameFileIterator::new(args, "vbmeta") {
+        descriptors.insert(
+            i,
+            Descriptor::VbMeta(VbMetaDescriptor {
+                resource: ResourceIdentifier::new(namespace, name),
+                vbmeta_digest: get_vbmeta_image_hash(file)?,
+            }),
+        );
+    }
+    for (i, namespace, name, file) in NamespaceNameFileIterator::new(args, "apk") {
+        let file = File::open(file)?;
+        let (signature_algorithm_id, apk_digest) = pick_v4_apk_digest(file)?;
+        descriptors.insert(
+            i,
+            Descriptor::Apk(ApkDescriptor {
+                resource: ResourceIdentifier::new(namespace, name),
+                signature_algorithm_id,
+                apk_digest: apk_digest.to_vec(),
+            }),
+        );
+    }
+    for (i, namespace, name, file) in NamespaceNameFileIterator::new(args, "apex-payload") {
+        descriptors.insert(
+            i,
+            Descriptor::VbMeta(VbMetaDescriptor {
+                resource: ResourceIdentifier::new(namespace, name),
+                vbmeta_digest: get_payload_vbmeta_image_hash(file)?,
+            }),
+        );
+    }
+    let avmd = Avmd::new(descriptors.into_values().collect());
+    let mut bytes = Vec::new();
+    avmd.serialize(
+        &mut serde_cbor::Serializer::new(&mut serde_cbor::ser::IoWrite::new(&mut bytes))
+            .packed_format()
+            .legacy_enums(),
+    )?;
+    std::fs::write(args.value_of("file").unwrap(), &bytes)?;
+    Ok(())
+}
+
+fn dump(args: &ArgMatches) -> Result<()> {
+    let file = std::fs::read(args.value_of("file").unwrap())?;
+    let avmd: Avmd = serde_cbor::from_slice(&file)?;
+    println!("{}", avmd);
+    Ok(())
+}
+
+fn main() -> Result<()> {
+    let namespace_name_file = ["namespace", "name", "file"];
+    let app = App::new("avmdtool")
+        .setting(AppSettings::SubcommandRequiredElseHelp)
+        .subcommand(
+            SubCommand::with_name("create")
+                .setting(AppSettings::ArgRequiredElseHelp)
+                .arg(Arg::with_name("file").required(true).takes_value(true))
+                .arg(
+                    Arg::with_name("vbmeta")
+                        .long("vbmeta")
+                        .takes_value(true)
+                        .value_names(&namespace_name_file)
+                        .multiple(true),
+                )
+                .arg(
+                    Arg::with_name("apk")
+                        .long("apk")
+                        .takes_value(true)
+                        .value_names(&namespace_name_file)
+                        .multiple(true),
+                )
+                .arg(
+                    Arg::with_name("apex-payload")
+                        .long("apex-payload")
+                        .takes_value(true)
+                        .value_names(&namespace_name_file)
+                        .multiple(true),
+                ),
+        )
+        .subcommand(
+            SubCommand::with_name("dump")
+                .setting(AppSettings::ArgRequiredElseHelp)
+                .arg(Arg::with_name("file").required(true).takes_value(true)),
+        );
+
+    let args = app.get_matches();
+    match args.subcommand() {
+        ("create", Some(sub_args)) => create(sub_args)?,
+        ("dump", Some(sub_args)) => dump(sub_args)?,
+        _ => bail!("Invalid arguments"),
+    }
+    Ok(())
+}
diff --git a/compos/Android.bp b/compos/Android.bp
index 69b22d6..7ef9e75 100644
--- a/compos/Android.bp
+++ b/compos/Android.bp
@@ -14,7 +14,7 @@
         "libbinder_common",
         "libbinder_rpc_unstable_bindgen",
         "libbinder_rs",
-        "libclap",
+        "libclap_deprecated",
         "libcompos_common",
         "liblibc",
         "liblog_rust",
diff --git a/compos/aidl/com/android/compos/ICompOsService.aidl b/compos/aidl/com/android/compos/ICompOsService.aidl
index 9cf99be..4df22da 100644
--- a/compos/aidl/com/android/compos/ICompOsService.aidl
+++ b/compos/aidl/com/android/compos/ICompOsService.aidl
@@ -74,4 +74,10 @@
      * hardware/interfaces/security/dice/aidl/android/hardware/security/dice/Bcc.aidl.
      */
     byte[] getAttestationChain();
+
+    /**
+     * Request the service to exit, triggering the termination of the VM. This may cause any
+     * requests in flight to fail.
+     */
+    oneway void quit();
 }
diff --git a/compos/benchmark/Android.bp b/compos/benchmark/Android.bp
index 10f8015..9353771 100644
--- a/compos/benchmark/Android.bp
+++ b/compos/benchmark/Android.bp
@@ -11,6 +11,7 @@
     static_libs: [
         "androidx.test.runner",
         "androidx.test.ext.junit",
+        "MicroroidDeviceTestHelper",
         "truth-prebuilt",
     ],
     platform_apis: true,
diff --git a/compos/benchmark/src/java/com/android/compos/benchmark/ComposBenchmark.java b/compos/benchmark/src/java/com/android/compos/benchmark/ComposBenchmark.java
index e4fb5ff..b4f55f9 100644
--- a/compos/benchmark/src/java/com/android/compos/benchmark/ComposBenchmark.java
+++ b/compos/benchmark/src/java/com/android/compos/benchmark/ComposBenchmark.java
@@ -17,6 +17,8 @@
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
+import static com.google.common.truth.TruthJUnit.assume;
+
 import static org.junit.Assert.assertTrue;
 
 import android.app.Instrumentation;
@@ -24,7 +26,8 @@
 import android.os.ParcelFileDescriptor;
 import android.util.Log;
 
-import org.junit.After;
+import com.android.microdroid.test.MicrodroidDeviceTestBase;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -37,17 +40,18 @@
 import java.text.DateFormat;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
-import java.time.Duration;
 import java.util.Date;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 
 @RunWith(JUnit4.class)
-public class ComposBenchmark {
+public class ComposBenchmark extends MicrodroidDeviceTestBase {
     private static final String TAG = "ComposBenchmark";
     private static final int BUFFER_SIZE = 1024;
-    private static final int ROUND_COUNT = 10;
+    private static final int ROUND_COUNT = 5;
+    private static final double NANOS_IN_SEC = 1_000_000_000.0;
+    private static final String METRIC_PREFIX = "avf_perf/compos/";
 
     private Instrumentation mInstrumentation;
 
@@ -56,9 +60,29 @@
         mInstrumentation = getInstrumentation();
     }
 
-    @After
-    public void cleanup() {
+    private void reportMetric(String name, String unit, double[] values) {
+        double sum = 0;
+        double squareSum = 0;
+        double min = Double.MAX_VALUE;
+        double max = Double.MIN_VALUE;
 
+        for (double val : values) {
+            sum += val;
+            squareSum += val * val;
+            min = val < min ? val : min;
+            max = val > max ? val : max;
+        }
+
+        double average = sum / values.length;
+        double variance = squareSum / values.length - average * average;
+        double stdev = Math.sqrt(variance);
+
+        Bundle bundle = new Bundle();
+        bundle.putDouble(METRIC_PREFIX + name + "_average_" + unit, average);
+        bundle.putDouble(METRIC_PREFIX + name + "_min_" + unit, min);
+        bundle.putDouble(METRIC_PREFIX + name + "_max_" + unit, max);
+        bundle.putDouble(METRIC_PREFIX + name + "_stdev_" + unit, stdev);
+        mInstrumentation.sendStatus(0, bundle);
     }
 
     public byte[] executeCommandBlocking(String command) {
@@ -98,34 +122,26 @@
     }
 
     @Test
-    public void testCompilationInVM()
-            throws InterruptedException, IOException {
+    public void testGuestCompileTime() throws InterruptedException, IOException {
+        assume().withMessage("Skip on CF; too slow").that(isCuttlefish()).isFalse();
 
         final String command = "/apex/com.android.compos/bin/composd_cmd test-compile";
 
-        Long[] compileSecArray = new Long[ROUND_COUNT];
+        double[] compileTime = new double[ROUND_COUNT];
 
         for (int round = 0; round < ROUND_COUNT; ++round) {
             Long compileStartTime = System.nanoTime();
             String output = executeCommand(command);
             Long compileEndTime = System.nanoTime();
-            Long compileSec = Duration.ofNanos(compileEndTime - compileStartTime).getSeconds();
 
             Pattern pattern = Pattern.compile("All Ok");
             Matcher matcher = pattern.matcher(output);
             assertTrue(matcher.find());
 
-            compileSecArray[round] = compileSec;
+            compileTime[round] = (compileEndTime - compileStartTime) / NANOS_IN_SEC;
         }
 
-        Long compileSecSum = 0L;
-        for (Long num: compileSecArray) {
-           compileSecSum += num;
-        }
-
-        Bundle bundle = new Bundle();
-        bundle.putLong("compliation_in_vm_elapse_second", compileSecSum / compileSecArray.length);
-        mInstrumentation.sendStatus(0, bundle);
+        reportMetric("guest_compile_time", "s", compileTime);
     }
 
     private Timestamp getLatestDex2oatSuccessTime()
@@ -133,7 +149,7 @@
 
         final String command = "logcat -d -e dex2oat";
         String output = executeCommand(command);
-        String latestTime = "";
+        String latestTime = null;
 
         for (String line : output.split("[\r\n]+")) {
             Pattern pattern = Pattern.compile("dex2oat64: dex2oat took");
@@ -143,6 +159,10 @@
             }
         }
 
+        if (latestTime == null) {
+            return null;
+        }
+
         DateFormat formatter = new SimpleDateFormat("MM-dd hh:mm:ss.SSS");
         Date date = formatter.parse(latestTime);
         Timestamp timeStampDate = new Timestamp(date.getTime());
@@ -151,35 +171,28 @@
     }
 
     @Test
-    public void testCompilationInAndroid()
+    public void testHostCompileTime()
             throws InterruptedException, IOException, ParseException {
 
         final String command = "/apex/com.android.art/bin/odrefresh --force-compile";
 
-        Long[] compileSecArray = new Long[ROUND_COUNT];
+        double[] compileTime = new double[ROUND_COUNT];
 
         for (int round = 0; round < ROUND_COUNT; ++round) {
             Timestamp beforeCompileLatestTime = getLatestDex2oatSuccessTime();
             Long compileStartTime = System.nanoTime();
             String output = executeCommand(command);
             Long compileEndTime = System.nanoTime();
-            Long compileSec = Duration.ofNanos(compileEndTime - compileStartTime).getSeconds();
             Timestamp afterCompileLatestTime = getLatestDex2oatSuccessTime();
 
-            assertTrue(beforeCompileLatestTime.before(afterCompileLatestTime));
+            assertTrue(afterCompileLatestTime != null);
+            assertTrue(beforeCompileLatestTime == null
+                    || beforeCompileLatestTime.before(afterCompileLatestTime));
 
-            compileSecArray[round] = compileSec;
+            compileTime[round] = (compileEndTime - compileStartTime) / NANOS_IN_SEC;
         }
 
-        Long compileSecSum = 0L;
-        for (Long num: compileSecArray) {
-            compileSecSum += num;
-        }
-
-        Bundle bundle = new Bundle();
-        bundle.putLong("compliation_in_android_elapse_second",
-                compileSecSum / compileSecArray.length);
-        mInstrumentation.sendStatus(0, bundle);
+        reportMetric("host_compile_time", "s", compileTime);
     }
 
 }
diff --git a/compos/common/Android.bp b/compos/common/Android.bp
index d1b45c4..1a69b1a 100644
--- a/compos/common/Android.bp
+++ b/compos/common/Android.bp
@@ -13,6 +13,7 @@
         "libanyhow",
         "libbinder_common",
         "libbinder_rpc_unstable_bindgen",
+        "liblazy_static",
         "liblog_rust",
         "libnested_virt",
         "libnum_traits",
diff --git a/compos/common/compos_client.rs b/compos/common/compos_client.rs
index 23cd505..30c55b3 100644
--- a/compos/common/compos_client.rs
+++ b/compos/common/compos_client.rs
@@ -16,7 +16,7 @@
 
 //! Support for starting CompOS in a VM and connecting to the service
 
-use crate::timeouts::timeouts;
+use crate::timeouts::TIMEOUTS;
 use crate::{COMPOS_APEX_ROOT, COMPOS_DATA_ROOT, COMPOS_VSOCK_PORT, DEFAULT_VM_CONFIG_PATH};
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
     DeathReason::DeathReason,
@@ -37,7 +37,7 @@
 use std::num::NonZeroU32;
 use std::path::{Path, PathBuf};
 use std::thread;
-use vmclient::VmInstance;
+use vmclient::{VmInstance, VmWaitError};
 
 /// This owns an instance of the CompOS VM.
 pub struct ComposClient(VmInstance);
@@ -128,14 +128,37 @@
 
         instance.start()?;
 
-        instance.wait_until_ready(timeouts()?.vm_max_time_to_ready)?;
+        let ready = instance.wait_until_ready(TIMEOUTS.vm_max_time_to_ready);
+        if ready == Err(VmWaitError::Finished) && debug_level != DebugLevel::NONE {
+            // The payload has (unexpectedly) finished, but the VM is still running. Give it
+            // some time to shutdown to maximize our chances of getting useful logs.
+            if let Some(death_reason) =
+                instance.wait_for_death_with_timeout(TIMEOUTS.vm_max_time_to_exit)
+            {
+                bail!("VM died during startup - reason {:?}", death_reason);
+            }
+        }
+        ready?;
 
         Ok(Self(instance))
     }
 
     /// Create and return an RPC Binder connection to the Comp OS service in the VM.
-    pub fn get_service(&self) -> Result<Strong<dyn ICompOsService>> {
-        self.0.get_service(COMPOS_VSOCK_PORT).context("Connecting to CompOS service")
+    pub fn connect_service(&self) -> Result<Strong<dyn ICompOsService>> {
+        self.0.connect_service(COMPOS_VSOCK_PORT).context("Connecting to CompOS service")
+    }
+
+    /// Wait for the instance to shut down. If it fails to shutdown within a reasonable time the
+    /// instance is dropped, which forcibly terminates it.
+    /// This should only be called when the instance has been requested to quit, or we believe that
+    /// it is already in the process of exiting due to some failure.
+    pub fn wait_for_shutdown(self) {
+        let death_reason = self.0.wait_for_death_with_timeout(TIMEOUTS.vm_max_time_to_exit);
+        match death_reason {
+            Some(vmclient::DeathReason::Shutdown) => info!("VM has exited normally"),
+            Some(reason) => warn!("VM died with reason {:?}", reason),
+            None => warn!("VM failed to exit, dropping"),
+        }
     }
 }
 
diff --git a/compos/common/timeouts.rs b/compos/common/timeouts.rs
index d0d107f..952be0a 100644
--- a/compos/common/timeouts.rs
+++ b/compos/common/timeouts.rs
@@ -17,7 +17,7 @@
 //! Timeouts for common situations, with support for longer timeouts when using nested
 //! virtualization.
 
-use anyhow::Result;
+use lazy_static::lazy_static;
 use std::time::Duration;
 
 /// Holder for the various timeouts we use.
@@ -27,27 +27,32 @@
     pub odrefresh_max_execution_time: Duration,
     /// Time allowed for the CompOS VM to start up and become ready.
     pub vm_max_time_to_ready: Duration,
+    /// Time we wait for a VM to exit once the payload has finished.
+    pub vm_max_time_to_exit: Duration,
 }
 
-/// Return the timeouts that are appropriate on the current platform.
-pub fn timeouts() -> Result<&'static Timeouts> {
+lazy_static! {
+/// The timeouts that are appropriate on the current platform.
+pub static ref TIMEOUTS: Timeouts = if nested_virt::is_nested_virtualization().unwrap() {
     // Nested virtualization is slow.
-    if nested_virt::is_nested_virtualization()? {
-        Ok(&EXTENDED_TIMEOUTS)
-    } else {
-        Ok(&NORMAL_TIMEOUTS)
-    }
+    EXTENDED_TIMEOUTS
+} else {
+    NORMAL_TIMEOUTS
+};
 }
 
 /// The timeouts that we use normally.
 const NORMAL_TIMEOUTS: Timeouts = Timeouts {
-    // Note: the source of truth for these odrefresh timeouts is art/odrefresh/odr_config.h.
+    // Note: the source of truth for this odrefresh timeout is art/odrefresh/odrefresh.cc.
     odrefresh_max_execution_time: Duration::from_secs(300),
     vm_max_time_to_ready: Duration::from_secs(15),
+    vm_max_time_to_exit: Duration::from_secs(3),
 };
 
-/// The timeouts that we use when need_extra_time() returns true.
+/// The timeouts that we use when running under nested virtualization.
 const EXTENDED_TIMEOUTS: Timeouts = Timeouts {
+    // Note: the source of truth for this odrefresh timeout is art/odrefresh/odrefresh.cc.
     odrefresh_max_execution_time: Duration::from_secs(480),
     vm_max_time_to_ready: Duration::from_secs(120),
+    vm_max_time_to_exit: Duration::from_secs(10),
 };
diff --git a/compos/composd/src/instance_starter.rs b/compos/composd/src/instance_starter.rs
index 340e8b7..6e6253e 100644
--- a/compos/composd/src/instance_starter.rs
+++ b/compos/composd/src/instance_starter.rs
@@ -113,7 +113,7 @@
             &self.vm_parameters,
         )
         .context("Starting VM")?;
-        let service = vm_instance.get_service().context("Connecting to CompOS")?;
+        let service = vm_instance.connect_service().context("Connecting to CompOS")?;
         Ok(CompOsInstance { vm_instance, service, lazy_service_guard: Default::default() })
     }
 
diff --git a/compos/composd_cmd/Android.bp b/compos/composd_cmd/Android.bp
index c230e13..1ede0ba 100644
--- a/compos/composd_cmd/Android.bp
+++ b/compos/composd_cmd/Android.bp
@@ -10,7 +10,7 @@
         "android.system.composd-rust",
         "libanyhow",
         "libbinder_rs",
-        "libclap",
+        "libclap_deprecated",
         "libcompos_common",
     ],
     prefer_rlib: true,
diff --git a/compos/composd_cmd/composd_cmd.rs b/compos/composd_cmd/composd_cmd.rs
index 6afd711..c6a5479 100644
--- a/compos/composd_cmd/composd_cmd.rs
+++ b/compos/composd_cmd/composd_cmd.rs
@@ -31,7 +31,7 @@
     },
 };
 use anyhow::{bail, Context, Result};
-use compos_common::timeouts::timeouts;
+use compos_common::timeouts::TIMEOUTS;
 use std::sync::{Arc, Condvar, Mutex};
 use std::time::Duration;
 
@@ -147,7 +147,7 @@
 
     println!("Waiting");
 
-    match state.wait(timeouts()?.odrefresh_max_execution_time) {
+    match state.wait(TIMEOUTS.odrefresh_max_execution_time) {
         Ok(Outcome::Succeeded) => Ok(()),
         Ok(Outcome::TaskDied) => bail!("Compilation task died"),
         Ok(Outcome::Failed(reason, message)) => {
diff --git a/compos/src/compsvc.rs b/compos/src/compsvc.rs
index 91415bb..9fa68d6 100644
--- a/compos/src/compsvc.rs
+++ b/compos/src/compsvc.rs
@@ -151,6 +151,12 @@
     fn getAttestationChain(&self) -> BinderResult<Vec<u8>> {
         to_binder_result(compos_key::get_attestation_chain())
     }
+
+    fn quit(&self) -> BinderResult<()> {
+        // TODO(b/236581575) Consider shutting down the binder server a bit more gracefully.
+        // When our process exits, Microdroid will shut down the VM.
+        std::process::exit(0);
+    }
 }
 
 fn add_artifacts(target_dir: &Path, artifact_signer: &mut ArtifactSigner) -> Result<()> {
diff --git a/compos/tests/Android.bp b/compos/tests/Android.bp
index b77a7e4..72da97f 100644
--- a/compos/tests/Android.bp
+++ b/compos/tests/Android.bp
@@ -12,7 +12,7 @@
     ],
     data_native_bins: ["bcc_validator"],
     static_libs: [
-        "VirtualizationTestHelper",
+        "MicrodroidHostTestHelper",
     ],
     test_suites: [
         "general-tests",
diff --git a/compos/tests/java/android/compos/test/ComposTestCase.java b/compos/tests/java/android/compos/test/ComposTestCase.java
index 51f0a1f..4f33afd 100644
--- a/compos/tests/java/android/compos/test/ComposTestCase.java
+++ b/compos/tests/java/android/compos/test/ComposTestCase.java
@@ -16,18 +16,17 @@
 
 package android.compos.test;
 
-import static android.virt.test.CommandResultSubject.assertThat;
-import static android.virt.test.CommandResultSubject.command_results;
-
+import static com.android.microdroid.test.CommandResultSubject.assertThat;
+import static com.android.microdroid.test.CommandResultSubject.command_results;
 import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.platform.test.annotations.RootPermissionTest;
-import android.virt.test.CommandRunner;
-import android.virt.test.VirtualizationTestCaseBase;
 
+import com.android.microdroid.test.CommandRunner;
+import com.android.microdroid.test.MicrodroidHostTestCaseBase;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.result.FileInputStreamSource;
 import com.android.tradefed.result.LogDataType;
@@ -46,7 +45,7 @@
 
 @RootPermissionTest
 @RunWith(DeviceJUnit4ClassRunner.class)
-public final class ComposTestCase extends VirtualizationTestCaseBase {
+public final class ComposTestCase extends MicrodroidHostTestCaseBase {
 
     // Binaries used in test. (These paths are valid both in host and Microdroid.)
     private static final String ODREFRESH_BIN = "/apex/com.android.art/bin/odrefresh";
diff --git a/compos/verify/Android.bp b/compos/verify/Android.bp
index 5c74e4f..38edf1c 100644
--- a/compos/verify/Android.bp
+++ b/compos/verify/Android.bp
@@ -11,7 +11,7 @@
         "libandroid_logger",
         "libanyhow",
         "libbinder_rs",
-        "libclap",
+        "libclap_deprecated",
         "libcompos_common",
         "libcompos_verify_native_rust",
         "liblog_rust",
diff --git a/compos/verify/verify.rs b/compos/verify/verify.rs
index 64ae75f..e6848c7 100644
--- a/compos/verify/verify.rs
+++ b/compos/verify/verify.rs
@@ -106,11 +106,15 @@
         &idsig_manifest_apk,
         &VmParameters { debug_mode, ..Default::default() },
     )?;
-    let service = vm_instance.get_service()?;
 
-    let public_key = service.getPublicKey().context("Getting public key")?;
+    let service = vm_instance.connect_service()?;
+    let public_key = service.getPublicKey().context("Getting public key");
 
-    if !compos_verify_native::verify(&public_key, &signature, &info) {
+    // Shut down the VM cleanly, giving time for any relevant logs to be written
+    let _ = service.quit(); // If this fails, the VM is probably dying anyway
+    vm_instance.wait_for_shutdown();
+
+    if !compos_verify_native::verify(&public_key?, &signature, &info) {
         bail!("Signature verification failed");
     }
 
diff --git a/tests/aidl/com/android/microdroid/testservice/IBenchmarkService.aidl b/tests/aidl/com/android/microdroid/testservice/IBenchmarkService.aidl
new file mode 100644
index 0000000..afcf989
--- /dev/null
+++ b/tests/aidl/com/android/microdroid/testservice/IBenchmarkService.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.microdroid.testservice;
+
+/** {@hide} */
+interface IBenchmarkService {
+    const int SERVICE_PORT = 5677;
+
+    /** Reads a file and returns the elapsed seconds for the reading. */
+    double readFile(String filename, long fileSizeBytes, boolean isRand);
+}
diff --git a/tests/benchmark/Android.bp b/tests/benchmark/Android.bp
index e6d5b83..2111620 100644
--- a/tests/benchmark/Android.bp
+++ b/tests/benchmark/Android.bp
@@ -12,6 +12,7 @@
         "MicroroidDeviceTestHelper",
         "androidx.test.runner",
         "androidx.test.ext.junit",
+        "com.android.microdroid.testservice-java",
         "truth-prebuilt",
     ],
     libs: ["android.system.virtualmachine"],
@@ -24,4 +25,12 @@
 cc_library_shared {
     name: "MicrodroidBenchmarkNativeLib",
     srcs: ["src/native/benchmarkbinary.cpp"],
+    shared_libs: [
+        "android.system.virtualmachineservice-ndk",
+        "com.android.microdroid.testservice-ndk",
+        "libbase",
+        "libbinder_ndk",
+        "libbinder_rpc_unstable",
+        "liblog",
+    ],
 }
diff --git a/tests/benchmark/assets/vm_config.json b/tests/benchmark/assets/vm_config.json
index 67e3d21..e8f43e0 100644
--- a/tests/benchmark/assets/vm_config.json
+++ b/tests/benchmark/assets/vm_config.json
@@ -4,7 +4,10 @@
   },
   "task": {
     "type": "microdroid_launcher",
-    "command": "MicrodroidBenchmarkNativeLib.so"
+    "command": "MicrodroidBenchmarkNativeLib.so",
+    "args": [
+      "no_io"
+    ]
   },
   "export_tombstones": true
 }
diff --git a/tests/benchmark/assets/vm_config_io.json b/tests/benchmark/assets/vm_config_io.json
new file mode 100644
index 0000000..1a5a9e5
--- /dev/null
+++ b/tests/benchmark/assets/vm_config_io.json
@@ -0,0 +1,18 @@
+{
+  "os": {
+    "name": "microdroid"
+  },
+  "task": {
+    "type": "microdroid_launcher",
+    "command": "MicrodroidBenchmarkNativeLib.so",
+    "args": [
+      "io"
+    ]
+  },
+  "apexes": [
+    {
+      "name": "com.android.virt"
+    }
+  ],
+  "export_tombstones": true
+}
diff --git a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
index e96f58b..7ee2d39 100644
--- a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
+++ b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package com.android.microdroid.benchmark;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -22,14 +23,14 @@
 
 import android.app.Instrumentation;
 import android.os.Bundle;
-import android.os.SystemProperties;
+import android.system.virtualmachine.VirtualMachine;
 import android.system.virtualmachine.VirtualMachineConfig;
 import android.system.virtualmachine.VirtualMachineConfig.DebugLevel;
 import android.system.virtualmachine.VirtualMachineException;
 
 import com.android.microdroid.test.MicrodroidDeviceTestBase;
+import com.android.microdroid.testservice.IBenchmarkService;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -37,24 +38,23 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
+import java.io.File;
 import java.io.IOException;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.List;
 
 @RunWith(Parameterized.class)
 public class MicrodroidBenchmarks extends MicrodroidDeviceTestBase {
     private static final String TAG = "MicrodroidBenchmarks";
+    private static final int VIRTIO_BLK_TRIAL_COUNT = 5;
 
     @Rule public Timeout globalTimeout = Timeout.seconds(300);
 
-    private static final String KERNEL_VERSION = SystemProperties.get("ro.kernel.version");
-
-    private boolean isCuttlefish() {
-        String productName = SystemProperties.get("ro.product.name");
-        return (null != productName)
-                && (productName.startsWith("aosp_cf_x86")
-                        || productName.startsWith("aosp_cf_arm")
-                        || productName.startsWith("cf_x86")
-                        || productName.startsWith("cf_arm"));
-    }
+    private static final String APEX_ETC_FS = "/apex/com.android.virt/etc/fs/";
+    private static final double SIZE_MB = 1024.0 * 1024.0;
+    private static final String MICRODROID_IMG_PREFIX = "microdroid_";
+    private static final String MICRODROID_IMG_SUFFIX = ".img";
 
     @Parameterized.Parameters(name = "protectedVm={0}")
     public static Object[] protectedVmConfigs() {
@@ -71,11 +71,6 @@
         mInstrumentation = getInstrumentation();
     }
 
-    @After
-    public void cleanup() throws VirtualMachineException {
-        cleanupTestSetup();
-    }
-
     private boolean canBootMicrodroidWithMemory(int mem)
             throws VirtualMachineException, InterruptedException, IOException {
         final int trialCount = 5;
@@ -85,7 +80,7 @@
             VirtualMachineConfig.Builder builder =
                     mInner.newVmConfigBuilder("assets/vm_config.json");
             VirtualMachineConfig normalConfig =
-                    builder.debugLevel(DebugLevel.FULL).memoryMib(mem).build();
+                    builder.debugLevel(DebugLevel.NONE).memoryMib(mem).build();
             mInner.forceCreateNewVirtualMachine("test_vm_minimum_memory", normalConfig);
 
             if (tryBootVm(TAG, "test_vm_minimum_memory").payloadStarted) return true;
@@ -159,4 +154,111 @@
         bundle.putDouble("avf_perf/microdroid/boot_time_stdev_ms", stdev);
         mInstrumentation.sendStatus(0, bundle);
     }
+
+    @Test
+    public void testMicrodroidImageSize() throws IOException {
+        Bundle bundle = new Bundle();
+        for (File file : new File(APEX_ETC_FS).listFiles()) {
+            String name = file.getName();
+
+            if (!name.startsWith(MICRODROID_IMG_PREFIX) || !name.endsWith(MICRODROID_IMG_SUFFIX)) {
+                continue;
+            }
+
+            String base =
+                    name.substring(
+                            MICRODROID_IMG_PREFIX.length(),
+                            name.length() - MICRODROID_IMG_SUFFIX.length());
+            String metric = "avf_perf/microdroid/img_size_" + base + "_MB" + "+" + name;
+            double size = Files.size(file.toPath()) / SIZE_MB;
+            bundle.putDouble(metric, size);
+        }
+        mInstrumentation.sendStatus(0, bundle);
+    }
+
+    @Test
+    public void testVirtioBlkSeqReadRate() throws Exception {
+        testVirtioBlkReadRate(/*isRand=*/ false);
+    }
+
+    @Test
+    public void testVirtioBlkRandReadRate() throws Exception {
+        testVirtioBlkReadRate(/*isRand=*/ true);
+    }
+
+    private void testVirtioBlkReadRate(boolean isRand) throws Exception {
+        VirtualMachineConfig.Builder builder =
+                mInner.newVmConfigBuilder("assets/vm_config_io.json");
+        VirtualMachineConfig config = builder.debugLevel(DebugLevel.FULL).build();
+        List<Double> readRates = new ArrayList<>();
+
+        for (int i = 0; i < VIRTIO_BLK_TRIAL_COUNT; ++i) {
+            String vmName = "test_vm_io_" + i;
+            mInner.forceCreateNewVirtualMachine(vmName, config);
+            VirtualMachine vm = mInner.getVirtualMachineManager().get(vmName);
+            VirtioBlkVmEventListener listener = new VirtioBlkVmEventListener(readRates, isRand);
+            listener.runToFinish(TAG, vm);
+        }
+        reportMetrics(readRates, isRand);
+    }
+
+    private void reportMetrics(List<Double> readRates, boolean isRand) {
+        double sum = 0;
+        for (double rate : readRates) {
+            sum += rate;
+        }
+        double mean = sum / readRates.size();
+        double sqSum = 0;
+        for (double rate : readRates) {
+            sqSum += (rate - mean) * (rate - mean);
+        }
+        double stdDev = Math.sqrt(sqSum / (readRates.size() - 1));
+
+        Bundle bundle = new Bundle();
+        String metricNamePrefix =
+                "avf_perf/virtio-blk/"
+                        + (mProtectedVm ? "protected-vm/" : "unprotected-vm/")
+                        + (isRand ? "rand_read_" : "seq_read_");
+        String unit = "_mb_per_sec";
+
+        bundle.putDouble(metricNamePrefix + "mean" + unit, mean);
+        bundle.putDouble(metricNamePrefix + "std" + unit, stdDev);
+        mInstrumentation.sendStatus(0, bundle);
+    }
+
+    private static class VirtioBlkVmEventListener extends VmEventListener {
+        private static final String FILENAME = APEX_ETC_FS + "microdroid_super.img";
+
+        private final long mFileSizeBytes;
+        private final List<Double> mReadRates;
+        private final boolean mIsRand;
+
+        VirtioBlkVmEventListener(List<Double> readRates, boolean isRand) {
+            File file = new File(FILENAME);
+            try {
+                mFileSizeBytes = Files.size(file.toPath());
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+            assertThat(mFileSizeBytes).isGreaterThan((long) SIZE_MB);
+            mReadRates = readRates;
+            mIsRand = isRand;
+        }
+
+        @Override
+        public void onPayloadReady(VirtualMachine vm) {
+            try {
+                IBenchmarkService benchmarkService =
+                        IBenchmarkService.Stub.asInterface(
+                                vm.connectToVsockServer(IBenchmarkService.SERVICE_PORT).get());
+                double elapsedSeconds =
+                        benchmarkService.readFile(FILENAME, mFileSizeBytes, mIsRand);
+                double fileSizeMb = mFileSizeBytes / SIZE_MB;
+                mReadRates.add(fileSizeMb / elapsedSeconds);
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+            forceStop(vm);
+        }
+    }
 }
diff --git a/tests/benchmark/src/native/benchmarkbinary.cpp b/tests/benchmark/src/native/benchmarkbinary.cpp
index b5ec49c..6a5b764 100644
--- a/tests/benchmark/src/native/benchmarkbinary.cpp
+++ b/tests/benchmark/src/native/benchmarkbinary.cpp
@@ -13,11 +13,123 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
+#include <aidl/android/system/virtualmachineservice/IVirtualMachineService.h>
+#include <aidl/com/android/microdroid/testservice/BnBenchmarkService.h>
+#include <android-base/result.h>
+#include <android-base/unique_fd.h>
+#include <fcntl.h>
+#include <linux/vm_sockets.h>
+#include <stdio.h>
 #include <unistd.h>
 
-extern "C" int android_native_main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) {
-    // do nothing for now; just leave it alive. good night.
-    for (;;) {
-        sleep(1000);
+#include <binder_rpc_unstable.hpp>
+#include <chrono>
+#include <random>
+#include <string>
+
+#include "android-base/logging.h"
+
+using aidl::android::system::virtualmachineservice::IVirtualMachineService;
+using android::base::ErrnoError;
+using android::base::Error;
+using android::base::Result;
+using android::base::unique_fd;
+
+namespace {
+constexpr uint64_t kBlockSizeBytes = 4096;
+
+class IOBenchmarkService : public aidl::com::android::microdroid::testservice::BnBenchmarkService {
+public:
+    ndk::ScopedAStatus readFile(const std::string& filename, int64_t fileSizeBytes, bool isRand,
+                                double* out) override {
+        if (auto res = read_file(filename, fileSizeBytes, isRand); res.ok()) {
+            *out = res.value();
+        } else {
+            std::stringstream error;
+            error << "Failed reading file: " << res.error();
+            return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
+                                                                    error.str().c_str());
+        }
+        return ndk::ScopedAStatus::ok();
     }
+
+private:
+    /** Returns the elapsed seconds for reading the file. */
+    Result<double> read_file(const std::string& filename, int64_t fileSizeBytes, bool is_rand) {
+        const int64_t block_count = fileSizeBytes / kBlockSizeBytes;
+        std::vector<uint64_t> offsets;
+        if (is_rand) {
+            std::mt19937 rd{std::random_device{}()};
+            offsets.reserve(block_count);
+            for (auto i = 0; i < block_count; ++i) offsets.push_back(i * kBlockSizeBytes);
+            std::shuffle(offsets.begin(), offsets.end(), rd);
+        }
+        char buf[kBlockSizeBytes];
+
+        clock_t start = clock();
+        unique_fd fd(open(filename.c_str(), O_RDONLY));
+        if (fd.get() == -1) {
+            return ErrnoError() << "Read: opening " << filename << " failed";
+        }
+        for (auto i = 0; i < block_count; ++i) {
+            if (is_rand) {
+                if (lseek(fd.get(), offsets[i], SEEK_SET) == -1) {
+                    return ErrnoError() << "failed to lseek";
+                }
+            }
+            auto bytes = read(fd.get(), buf, kBlockSizeBytes);
+            if (bytes == 0) {
+                return Error() << "unexpected end of file";
+            } else if (bytes == -1) {
+                return ErrnoError() << "failed to read";
+            }
+        }
+        return {((double)clock() - start) / CLOCKS_PER_SEC};
+    }
+};
+
+Result<void> run_io_benchmark_tests() {
+    auto test_service = ndk::SharedRefBase::make<IOBenchmarkService>();
+    auto callback = []([[maybe_unused]] void* param) {
+        // Tell microdroid_manager that we're ready.
+        // If we can't, abort in order to fail fast - the host won't proceed without
+        // receiving the onReady signal.
+        ndk::SpAIBinder binder(
+                RpcClient(VMADDR_CID_HOST, IVirtualMachineService::VM_BINDER_SERVICE_PORT));
+        auto vm_service = IVirtualMachineService::fromBinder(binder);
+        if (vm_service == nullptr) {
+            LOG(ERROR) << "failed to connect VirtualMachineService\n";
+            abort();
+        }
+        if (auto status = vm_service->notifyPayloadReady(); !status.isOk()) {
+            LOG(ERROR) << "failed to notify payload ready to virtualizationservice: "
+                       << status.getDescription();
+            abort();
+        }
+    };
+
+    if (!RunRpcServerCallback(test_service->asBinder().get(), test_service->SERVICE_PORT, callback,
+                              nullptr)) {
+        return Error() << "RPC Server failed to run";
+    }
+    return {};
+}
+} // Anonymous namespace
+
+extern "C" int android_native_main([[maybe_unused]] int argc, char* argv[]) {
+    if (strcmp(argv[1], "no_io") == 0) {
+        // do nothing for now; just leave it alive. good night.
+        for (;;) {
+            sleep(1000);
+        }
+    } else if (strcmp(argv[1], "io") == 0) {
+        if (auto res = run_io_benchmark_tests(); res.ok()) {
+            return 0;
+        } else {
+            LOG(ERROR) << "IO benchmark test failed: " << res.error() << "\n";
+            return 1;
+        }
+    }
+    return 0;
 }
diff --git a/tests/helper/Android.bp b/tests/helper/Android.bp
index 679fbfe..e7760e2 100644
--- a/tests/helper/Android.bp
+++ b/tests/helper/Android.bp
@@ -3,11 +3,18 @@
 }
 
 java_library_static {
+    name: "VirtualizationTestHelper",
+    srcs: ["src/java/com/android/virt/**/*.java"],
+    host_supported: true,
+}
+
+java_library_static {
     name: "MicroroidDeviceTestHelper",
-    srcs: ["src/java/**/*.java"],
+    srcs: ["src/java/com/android/microdroid/**/*.java"],
     static_libs: [
         "androidx.test.runner",
         "androidx.test.ext.junit",
+        "VirtualizationTestHelper",
         "truth-prebuilt",
     ],
     libs: ["android.system.virtualmachine"],
diff --git a/tests/helper/src/java/com/android/microdroid/test/MicrodroidDeviceTestBase.java b/tests/helper/src/java/com/android/microdroid/test/MicrodroidDeviceTestBase.java
index 87c53a7..1f57634 100644
--- a/tests/helper/src/java/com/android/microdroid/test/MicrodroidDeviceTestBase.java
+++ b/tests/helper/src/java/com/android/microdroid/test/MicrodroidDeviceTestBase.java
@@ -21,6 +21,7 @@
 
 import android.content.Context;
 import android.os.ParcelFileDescriptor;
+import android.os.SystemProperties;
 import android.sysprop.HypervisorProperties;
 import android.system.virtualizationservice.DeathReason;
 import android.system.virtualmachine.VirtualMachine;
@@ -33,6 +34,8 @@
 import androidx.annotation.CallSuper;
 import androidx.test.core.app.ApplicationProvider;
 
+import com.android.virt.VirtualizationTestHelper;
+
 import java.io.BufferedReader;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -60,13 +63,15 @@
                 }).start();
     }
 
-    private boolean mPkvmSupported;
+    public static boolean isCuttlefish() {
+        return VirtualizationTestHelper.isCuttlefish(SystemProperties.get("ro.product.name"));
+    }
 
     // TODO(b/220920264): remove Inner class; this is a hack to hide virt APEX types
     protected static class Inner {
-        private boolean mProtectedVm;
-        private Context mContext;
-        private VirtualMachineManager mVmm;
+        private final boolean mProtectedVm;
+        private final Context mContext;
+        private final VirtualMachineManager mVmm;
 
         public Inner(Context context, boolean protectedVm, VirtualMachineManager vmm) {
             mProtectedVm = protectedVm;
@@ -114,7 +119,6 @@
         // classes, check the existence of a class in the package and skip this test if not exist.
         try {
             Class.forName("android.system.virtualmachine.VirtualMachineManager");
-            mPkvmSupported = true;
         } catch (ClassNotFoundException e) {
             assumeNoException(e);
             return;
@@ -132,16 +136,10 @@
         mInner = new Inner(context, protectedVm, VirtualMachineManager.getInstance(context));
     }
 
-    public void cleanupTestSetup() throws VirtualMachineException {
-        if (!mPkvmSupported) {
-            return;
-        }
-    }
-
     protected abstract static class VmEventListener implements VirtualMachineCallback {
         private ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
 
-        void runToFinish(String logTag, VirtualMachine vm)
+        public void runToFinish(String logTag, VirtualMachine vm)
                 throws VirtualMachineException, InterruptedException {
             vm.setCallback(mExecutorService, this);
             vm.run();
@@ -150,7 +148,7 @@
             mExecutorService.awaitTermination(300, TimeUnit.SECONDS);
         }
 
-        void forceStop(VirtualMachine vm) {
+        protected void forceStop(VirtualMachine vm) {
             try {
                 vm.clearCallback();
                 vm.stop();
diff --git a/tests/helper/src/java/com/android/virt/VirtualizationTestHelper.java b/tests/helper/src/java/com/android/virt/VirtualizationTestHelper.java
new file mode 100644
index 0000000..c6c0ad0
--- /dev/null
+++ b/tests/helper/src/java/com/android/virt/VirtualizationTestHelper.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.virt;
+
+public abstract class VirtualizationTestHelper {
+    public static boolean isCuttlefish(String productName) {
+        return (null != productName)
+                && (productName.startsWith("aosp_cf_x86")
+                        || productName.startsWith("aosp_cf_arm")
+                        || productName.startsWith("cf_x86")
+                        || productName.startsWith("cf_arm"));
+    }
+}
diff --git a/tests/hostside/Android.bp b/tests/hostside/Android.bp
index dfc2f2b..24288ee 100644
--- a/tests/hostside/Android.bp
+++ b/tests/hostside/Android.bp
@@ -13,7 +13,7 @@
         "tradefed",
     ],
     static_libs: [
-        "VirtualizationTestHelper",
+        "MicrodroidHostTestHelper",
     ],
     per_testcase_directory: true,
     data: [
diff --git a/tests/hostside/helper/Android.bp b/tests/hostside/helper/Android.bp
index 6ab02f8..af88bb6 100644
--- a/tests/hostside/helper/Android.bp
+++ b/tests/hostside/helper/Android.bp
@@ -3,11 +3,14 @@
 }
 
 java_library_host {
-    name: "VirtualizationTestHelper",
+    name: "MicrodroidHostTestHelper",
     srcs: ["java/**/*.java"],
     libs: [
         "compatibility-tradefed",
         "tradefed",
         "truth-prebuilt",
     ],
+    static_libs: [
+        "VirtualizationTestHelper",
+    ],
 }
diff --git a/tests/hostside/helper/java/android/virt/test/CommandResultSubject.java b/tests/hostside/helper/java/com/android/microdroid/test/CommandResultSubject.java
similarity index 98%
rename from tests/hostside/helper/java/android/virt/test/CommandResultSubject.java
rename to tests/hostside/helper/java/com/android/microdroid/test/CommandResultSubject.java
index 5312f5a..2271325 100644
--- a/tests/hostside/helper/java/android/virt/test/CommandResultSubject.java
+++ b/tests/hostside/helper/java/com/android/microdroid/test/CommandResultSubject.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.virt.test;
+package com.android.microdroid.test;
 
 import static com.google.common.truth.Truth.assertAbout;
 
diff --git a/tests/hostside/helper/java/android/virt/test/CommandRunner.java b/tests/hostside/helper/java/com/android/microdroid/test/CommandRunner.java
similarity index 98%
rename from tests/hostside/helper/java/android/virt/test/CommandRunner.java
rename to tests/hostside/helper/java/com/android/microdroid/test/CommandRunner.java
index f1bdb8e..2f9b3df 100644
--- a/tests/hostside/helper/java/android/virt/test/CommandRunner.java
+++ b/tests/hostside/helper/java/com/android/microdroid/test/CommandRunner.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.virt.test;
+package com.android.microdroid.test;
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.fail;
diff --git a/tests/hostside/helper/java/android/virt/test/LogArchiver.java b/tests/hostside/helper/java/com/android/microdroid/test/LogArchiver.java
similarity index 97%
rename from tests/hostside/helper/java/android/virt/test/LogArchiver.java
rename to tests/hostside/helper/java/com/android/microdroid/test/LogArchiver.java
index b6cae95..be638ab 100644
--- a/tests/hostside/helper/java/android/virt/test/LogArchiver.java
+++ b/tests/hostside/helper/java/com/android/microdroid/test/LogArchiver.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.virt.test;
+package com.android.microdroid.test;
 
 import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
 
diff --git a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java b/tests/hostside/helper/java/com/android/microdroid/test/MicrodroidHostTestCaseBase.java
similarity index 96%
rename from tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
rename to tests/hostside/helper/java/com/android/microdroid/test/MicrodroidHostTestCaseBase.java
index 10d1e8e..0712323 100644
--- a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
+++ b/tests/hostside/helper/java/com/android/microdroid/test/MicrodroidHostTestCaseBase.java
@@ -14,11 +14,10 @@
  * limitations under the License.
  */
 
-package android.virt.test;
+package com.android.microdroid.test;
 
-import static android.virt.test.CommandResultSubject.assertThat;
-import static android.virt.test.CommandResultSubject.command_results;
-
+import static com.android.microdroid.test.CommandResultSubject.assertThat;
+import static com.android.microdroid.test.CommandResultSubject.command_results;
 import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
 
 import static com.google.common.truth.Truth.assertWithMessage;
@@ -35,6 +34,7 @@
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 import com.android.tradefed.util.CommandResult;
 import com.android.tradefed.util.RunUtil;
+import com.android.virt.VirtualizationTestHelper;
 
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -46,7 +46,7 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-public abstract class VirtualizationTestCaseBase extends BaseHostJUnit4Test {
+public abstract class MicrodroidHostTestCaseBase extends BaseHostJUnit4Test {
     protected static final String TEST_ROOT = "/data/local/tmp/virt/";
     protected static final String VIRT_APEX = "/apex/com.android.virt/";
     protected static final String LOG_PATH = TEST_ROOT + "log.txt";
@@ -92,6 +92,10 @@
         android.tryRun("rm", "-rf", "/data/misc/virtualizationservice/*");
     }
 
+    protected boolean isCuttlefish() throws Exception {
+        return VirtualizationTestHelper.isCuttlefish(getDevice().getProperty("ro.product.name"));
+    }
+
     public static void testIfDeviceIsCapable(ITestDevice androidDevice) throws Exception {
         assumeTrue("Need an actual TestDevice", androidDevice instanceof TestDevice);
         TestDevice testDevice = (TestDevice) androidDevice;
diff --git a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java
similarity index 88%
rename from tests/hostside/java/android/virt/test/MicrodroidTestCase.java
rename to tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java
index d25868e..c4ea80a 100644
--- a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java
@@ -14,10 +14,9 @@
  * limitations under the License.
  */
 
-package android.virt.test;
+package com.android.microdroid.test;
 
-import static android.virt.test.CommandResultSubject.command_results;
-
+import static com.android.microdroid.test.CommandResultSubject.command_results;
 import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -27,7 +26,6 @@
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.nullValue;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -63,7 +61,7 @@
 import java.util.regex.Pattern;
 
 @RunWith(DeviceJUnit4ClassRunner.class)
-public class MicrodroidTestCase extends VirtualizationTestCaseBase {
+public class MicrodroidTestCase extends MicrodroidHostTestCaseBase {
     private static final String APK_NAME = "MicrodroidTestApp.apk";
     private static final String PACKAGE_NAME = "com.android.microdroid.test";
     private static final String SHELL_PACKAGE_NAME = "com.android.shell";
@@ -78,16 +76,6 @@
     @Rule public TestLogData mTestLogs = new TestLogData();
     @Rule public TestName mTestName = new TestName();
 
-    // TODO(b/176805428): remove this
-    private boolean isCuttlefish() throws Exception {
-        String productName = getDevice().getProperty("ro.product.name");
-        return (null != productName)
-                && (productName.startsWith("aosp_cf_x86")
-                        || productName.startsWith("aosp_cf_arm")
-                        || productName.startsWith("cf_x86")
-                        || productName.startsWith("cf_arm"));
-    }
-
     private int minMemorySize() throws DeviceNotAvailableException {
         CommandRunner android = new CommandRunner(getDevice());
         String abi = android.run("getprop", "ro.product.cpu.abi");
@@ -404,53 +392,6 @@
     }
 
     @Test
-    public void testTombstonesAreBeingForwarded() throws Exception {
-        // This test requires rooting. Skip on user builds where rooting is impossible.
-        final String buildType = getDevice().getProperty("ro.build.type");
-        assumeTrue("userdebug".equals(buildType) || "eng".equals(buildType));
-
-        // Note this test relies on logcat values being printed by tombstone_transmit on
-        // and the reeceiver on host (virtualization_service)
-        final String configPath = "assets/vm_config.json"; // path inside the APK
-        final String cid =
-                startMicrodroid(
-                        getDevice(),
-                        getBuild(),
-                        APK_NAME,
-                        PACKAGE_NAME,
-                        configPath,
-                        /* debug */ true,
-                        minMemorySize(),
-                        Optional.of(NUM_VCPUS),
-                        Optional.of(CPU_AFFINITY));
-        adbConnectToMicrodroid(getDevice(), cid);
-        waitForBootComplete();
-        runOnMicrodroidRetryingOnFailure(
-                MICRODROID_COMMAND_TIMEOUT_MILLIS, MICRODROID_ADB_CONNECT_MAX_ATTEMPTS, "true");
-        // We need root permission to write to /data/tombstones/
-        rootMicrodroid();
-        // Write a test tombstone file in /data/tombstones
-        runOnMicrodroid("echo -n \'Test tombstone in VM with 34 bytes\'"
-                    + "> /data/tombstones/transmit.txt");
-        // check if the tombstone have been tranferred from VM. This is a bit flaky - increasing
-        // timeout to 30s can result in SIGKILL inside microdroid due to logcat memory issue
-        CommandRunner android = new CommandRunner(getDevice());
-        android.runWithTimeout(
-                15000,
-                "grep",
-                "-m",
-                "1",
-                "'tombstone_transmit.microdroid:.*data/tombstones/transmit.txt'",
-                LOG_PATH);
-
-        // Confirm that tombstone is received (from host logcat)
-        assertNotEquals(runOnHost("adb", "-s", getDevice().getSerialNumber(),
-                            "logcat", "-d", "-e",
-                            "Received 34 bytes from guest & wrote to tombstone file.*"),
-                "");
-    }
-
-    @Test
     public void testMicrodroidBoots() throws Exception {
         final String configPath = "assets/vm_config.json"; // path inside the APK
         final String cid =
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index 448f150..b429e4d 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -32,7 +32,6 @@
 
 import com.android.microdroid.testservice.ITestService;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -77,11 +76,6 @@
         prepareTestSetup(mProtectedVm);
     }
 
-    @After
-    public void cleanup() throws VirtualMachineException {
-        cleanupTestSetup();
-    }
-
     private static final int MIN_MEM_ARM64 = 150;
     private static final int MIN_MEM_X86_64 = 196;
 
diff --git a/tests/vsock_guest.cc b/tests/vsock_guest.cc
index 7a72e11..884c8a4 100644
--- a/tests/vsock_guest.cc
+++ b/tests/vsock_guest.cc
@@ -62,6 +62,16 @@
         PLOG(ERROR) << "WriteStringToFd";
         return EXIT_FAILURE;
     }
+    shutdown(fd.get(), SHUT_WR); // close socket for writing
+
+    // Must not shut down until the server ACKs the message. Shutting down
+    // the VM would otherwise terminate the VMM and reset the server's socket.
+    LOG(INFO) << "Waiting for ACK from the server...";
+    if (!ReadFdToString(fd, &msg)) {
+        PLOG(ERROR) << "ReadFdToString";
+        return EXIT_FAILURE;
+    }
+    shutdown(fd.get(), SHUT_RD); // close socket for reading
 
     LOG(INFO) << "Exiting...";
     return EXIT_SUCCESS;
diff --git a/tests/vsock_test.cc b/tests/vsock_test.cc
index 0fc451d..1460660 100644
--- a/tests/vsock_test.cc
+++ b/tests/vsock_test.cc
@@ -48,6 +48,7 @@
 static constexpr const char kVmInitrdPath[] = "/data/local/tmp/virt-test/initramfs";
 static constexpr const char kVmParams[] = "rdinit=/bin/init bin/vsock_client 2 45678 HelloWorld";
 static constexpr const char kTestMessage[] = "HelloWorld";
+static constexpr const char kAckMessage[] = "ACK";
 static constexpr const char kPlatformVersion[] = "~1.0";
 
 /** Returns true if the kernel supports unprotected VMs. */
@@ -108,9 +109,13 @@
 
     LOG(INFO) << "Reading message from the client...";
     std::string msg;
-    ASSERT_TRUE(ReadFdToString(client_fd, &msg));
-
+    ASSERT_TRUE(ReadFdToString(client_fd, &msg)) << strerror(errno);
     LOG(INFO) << "Received message: " << msg;
+
+    // The client is waiting for a response to signal it can shut down.
+    LOG(INFO) << "Replying with '" << kAckMessage << "'...";
+    ASSERT_TRUE(WriteStringToFd(kAckMessage, client_fd));
+
     ASSERT_EQ(msg, kTestMessage);
 }
 
diff --git a/vmbase/Android.bp b/vmbase/Android.bp
index 6400ccd..fb7f6a6 100644
--- a/vmbase/Android.bp
+++ b/vmbase/Android.bp
@@ -43,9 +43,9 @@
 cc_defaults {
     name: "vmbase_elf_defaults",
     defaults: ["vmbase_cc_defaults"],
-    system_shared_libs: ["libc"],
     static_executable: true,
     static_libs: [
+        "libarm-optimized-routines-mem",
         "libvmbase_entry",
     ],
 }
diff --git a/vmclient/src/errors.rs b/vmclient/src/errors.rs
index 532706d..43db7f9 100644
--- a/vmclient/src/errors.rs
+++ b/vmclient/src/errors.rs
@@ -17,7 +17,7 @@
 use thiserror::Error;
 
 /// An error while waiting for a VM to do something.
-#[derive(Clone, Debug, Error)]
+#[derive(Clone, Debug, Eq, Error, PartialEq)]
 pub enum VmWaitError {
     /// Timed out waiting for the VM.
     #[error("Timed out waiting for VM.")]
@@ -33,9 +33,9 @@
     Finished,
 }
 
-/// An error connection to a VM RPC Binder service.
-#[derive(Clone, Debug, Error)]
-pub enum GetServiceError {
+/// An error connecting to a VM RPC Binder service.
+#[derive(Clone, Debug, Eq, Error, PartialEq)]
+pub enum ConnectServiceError {
     /// The RPC binder connection failed.
     #[error("Vsock connection to RPC binder failed.")]
     ConnectionFailed,
diff --git a/vmclient/src/lib.rs b/vmclient/src/lib.rs
index 867c3a7..9b5b8dd 100644
--- a/vmclient/src/lib.rs
+++ b/vmclient/src/lib.rs
@@ -20,7 +20,7 @@
 mod sync;
 
 pub use crate::death_reason::DeathReason;
-pub use crate::errors::{GetServiceError, VmWaitError};
+pub use crate::errors::{ConnectServiceError, VmWaitError};
 use crate::{rpc_binder::VsockFactory, sync::Monitor};
 use android_system_virtualizationservice::{
     aidl::android::system::virtualizationservice::{
@@ -111,6 +111,15 @@
         self.state.wait_while(|state| state.death_reason.is_none()).unwrap().death_reason.unwrap()
     }
 
+    /// Blocks until the VM or the VirtualizationService itself dies, or the given timeout expires.
+    /// Returns the reason why it died if it did so.
+    pub fn wait_for_death_with_timeout(&self, timeout: Duration) -> Option<DeathReason> {
+        let (state, _timeout_result) =
+            self.state.wait_timeout_while(timeout, |state| state.death_reason.is_none()).unwrap();
+        // We don't care if it timed out - we just return the reason if there now is one
+        state.death_reason
+    }
+
     /// Waits until the VM reports that it is ready.
     ///
     /// Returns an error if the VM dies first, or the `timeout` elapses before the VM is ready.
@@ -133,15 +142,15 @@
     }
 
     /// Tries to connect to an RPC Binder service provided by the VM on the given vsock port.
-    pub fn get_service<T: FromIBinder + ?Sized>(
+    pub fn connect_service<T: FromIBinder + ?Sized>(
         &self,
         port: u32,
-    ) -> Result<Strong<T>, GetServiceError> {
+    ) -> Result<Strong<T>, ConnectServiceError> {
         let mut vsock_factory = VsockFactory::new(&*self.vm, port);
 
         let ibinder = vsock_factory.connect_rpc_client()?;
 
-        FromIBinder::try_from(ibinder).map_err(GetServiceError::WrongServiceType)
+        FromIBinder::try_from(ibinder).map_err(ConnectServiceError::WrongServiceType)
     }
 
     /// Get ramdump
diff --git a/vmclient/src/rpc_binder.rs b/vmclient/src/rpc_binder.rs
index fee643f..7c2992b 100644
--- a/vmclient/src/rpc_binder.rs
+++ b/vmclient/src/rpc_binder.rs
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use crate::errors::GetServiceError;
+use crate::errors::ConnectServiceError;
 use android_system_virtualizationservice::{
     aidl::android::system::virtualizationservice::IVirtualMachine::IVirtualMachine,
 };
@@ -30,7 +30,7 @@
         Self { vm, port }
     }
 
-    pub fn connect_rpc_client(&mut self) -> Result<binder::SpIBinder, GetServiceError> {
+    pub fn connect_rpc_client(&mut self) -> Result<binder::SpIBinder, ConnectServiceError> {
         let param = self.as_void_ptr();
 
         unsafe {
@@ -41,7 +41,7 @@
             let binder =
                 binder_rpc_unstable_bindgen::RpcPreconnectedClient(Some(Self::request_fd), param)
                     as *mut AIBinder;
-            new_spibinder(binder).ok_or(GetServiceError::ConnectionFailed)
+            new_spibinder(binder).ok_or(ConnectServiceError::ConnectionFailed)
         }
     }
 
diff --git a/zipfuse/Android.bp b/zipfuse/Android.bp
index e10fc31..d07a8e1 100644
--- a/zipfuse/Android.bp
+++ b/zipfuse/Android.bp
@@ -10,7 +10,7 @@
     prefer_rlib: true,
     rustlibs: [
         "libanyhow",
-        "libclap",
+        "libclap_deprecated",
         "libfuse_rust",
         "liblibc",
         "libzip",