Merge "Add safe wrapper for RpcPreconnectedClient."
diff --git a/.gitignore b/.gitignore
index 96ef6c0..5917dfb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,5 @@
-/target
+apkdmverity/target/
+zipfuse/target/
+policy
+zipfuse/target/
 Cargo.lock
diff --git a/apkdmverity/Android.bp b/apkdmverity/Android.bp
index d7aa921..06d4500 100644
--- a/apkdmverity/Android.bp
+++ b/apkdmverity/Android.bp
@@ -11,7 +11,7 @@
     rustlibs: [
         "libanyhow",
         "libbitflags",
-        "libclap_deprecated",
+        "libclap",
         "libdata_model",
         "libidsig",
         "libitertools",
diff --git a/apkdmverity/src/main.rs b/apkdmverity/src/main.rs
index 16dd480..de7f5bb 100644
--- a/apkdmverity/src/main.rs
+++ b/apkdmverity/src/main.rs
@@ -45,7 +45,7 @@
                             block device is created at \"/dev/mapper/<name>\".' root_hash is \
                             optional; idsig file's root hash will be used if specified as \"none\"."
             ))
-        .arg(Arg::with_name("verbose").short("v").long("verbose").help("Shows verbose output"))
+        .arg(Arg::with_name("verbose").short('v').long("verbose").help("Shows verbose output"))
         .get_matches();
 
     let apks = matches.values_of("apk").unwrap();
diff --git a/authfs/fd_server/Android.bp b/authfs/fd_server/Android.bp
index 77bed8b..943eec1 100644
--- a/authfs/fd_server/Android.bp
+++ b/authfs/fd_server/Android.bp
@@ -12,7 +12,7 @@
         "libauthfs_fsverity_metadata",
         "libbinder_common",
         "libbinder_rs",
-        "libclap_deprecated",
+        "libclap",
         "liblibc",
         "liblog_rust",
         "libnix",
diff --git a/authfs/tests/Android.bp b/authfs/tests/Android.bp
index a886d10..ebc6dd4 100644
--- a/authfs/tests/Android.bp
+++ b/authfs/tests/Android.bp
@@ -31,7 +31,7 @@
     rustlibs: [
         "libandroid_logger",
         "libanyhow",
-        "libclap_deprecated",
+        "libclap",
         "libcommand_fds",
         "liblog_rust",
         "libnix",
diff --git a/avmd/Android.bp b/avmd/Android.bp
index e31e103..9f0b28b 100644
--- a/avmd/Android.bp
+++ b/avmd/Android.bp
@@ -30,7 +30,7 @@
         "libapexutil_rust",
         "libapkverify",
         "libavmd",
-        "libclap_deprecated",
+        "libclap",
         "libserde",
         "libserde_cbor",
         "libvbmeta_rust",
diff --git a/avmd/src/avmd.rs b/avmd/src/avmd.rs
index e3bc7a7..50cdfdf 100644
--- a/avmd/src/avmd.rs
+++ b/avmd/src/avmd.rs
@@ -12,9 +12,15 @@
 // 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};
-use std::fmt;
 
 /// An Avmd struct contains
 /// - A header with version information that allows rollback when needed.
diff --git a/avmd/src/lib.rs b/avmd/src/lib.rs
index 9722518..7a06e6a 100644
--- a/avmd/src/lib.rs
+++ b/avmd/src/lib.rs
@@ -14,6 +14,8 @@
 
 //! 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
index b156a66..ca28f42 100644
--- a/avmd/src/main.rs
+++ b/avmd/src/main.rs
@@ -149,8 +149,8 @@
 
     let args = app.get_matches();
     match args.subcommand() {
-        ("create", Some(sub_args)) => create(sub_args)?,
-        ("dump", Some(sub_args)) => dump(sub_args)?,
+        Some(("create", sub_args)) => create(sub_args)?,
+        Some(("dump", sub_args)) => dump(sub_args)?,
         _ => bail!("Invalid arguments"),
     }
     Ok(())
diff --git a/compos/Android.bp b/compos/Android.bp
index b5f9c5b..0f1675b 100644
--- a/compos/Android.bp
+++ b/compos/Android.bp
@@ -13,7 +13,7 @@
         "libanyhow",
         "libbinder_common",
         "libbinder_rs",
-        "libclap_deprecated",
+        "libclap",
         "libcompos_common",
         "liblibc",
         "liblog_rust",
diff --git a/compos/composd_cmd/Android.bp b/compos/composd_cmd/Android.bp
index 1ede0ba..c230e13 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_deprecated",
+        "libclap",
         "libcompos_common",
     ],
     prefer_rlib: true,
diff --git a/compos/composd_cmd/composd_cmd.rs b/compos/composd_cmd/composd_cmd.rs
index c6a5479..d5feed8 100644
--- a/compos/composd_cmd/composd_cmd.rs
+++ b/compos/composd_cmd/composd_cmd.rs
@@ -49,8 +49,8 @@
     ProcessState::start_thread_pool();
 
     match args.subcommand() {
-        ("staged-apex-compile", _) => run_staged_apex_compile()?,
-        ("test-compile", Some(sub_matches)) => {
+        Some(("staged-apex-compile", _)) => run_staged_apex_compile()?,
+        Some(("test-compile", sub_matches)) => {
             let prefer_staged = sub_matches.is_present("prefer-staged");
             run_test_compile(prefer_staged)?;
         }
diff --git a/compos/verify/Android.bp b/compos/verify/Android.bp
index 38edf1c..5c74e4f 100644
--- a/compos/verify/Android.bp
+++ b/compos/verify/Android.bp
@@ -11,7 +11,7 @@
         "libandroid_logger",
         "libanyhow",
         "libbinder_rs",
-        "libclap_deprecated",
+        "libclap",
         "libcompos_common",
         "libcompos_verify_native_rust",
         "liblog_rust",
diff --git a/libs/avb_bindgen/Android.bp b/libs/avb_bindgen/Android.bp
index 1e62864..6dc16e8 100644
--- a/libs/avb_bindgen/Android.bp
+++ b/libs/avb_bindgen/Android.bp
@@ -7,6 +7,7 @@
     host_supported: true,
     wrapper_src: "bindgen/avb.h",
     crate_name: "avb_bindgen",
+    visibility: ["//packages/modules/Virtualization:__subpackages__"],
     source_stem: "bindings",
     bindgen_flags: [
         "--size_t-is-usize",
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index dce6c9d..fa064a7 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -179,7 +179,6 @@
             Ok(())
         }
         Err(err) => {
-            error!("task terminated: {:?}", err);
             let (error_code, message) = translate_error(&err);
             service.notifyError(error_code, &message)?;
             Err(err)
diff --git a/rialto/tests/test.rs b/rialto/tests/test.rs
index 6cd3f2f..b6ccd9e 100644
--- a/rialto/tests/test.rs
+++ b/rialto/tests/test.rs
@@ -21,13 +21,14 @@
     },
     binder::{ParcelFileDescriptor, ProcessState},
 };
-use anyhow::{Context, Error};
+use anyhow::{anyhow, Context, Error};
 use log::info;
 use std::fs::File;
 use std::io::{self, BufRead, BufReader};
 use std::os::unix::io::FromRawFd;
 use std::panic;
 use std::thread;
+use std::time::Duration;
 use vmclient::{DeathReason, VmInstance};
 
 const RIALTO_PATH: &str = "/data/local/tmp/rialto_test/arm64/rialto.bin";
@@ -71,7 +72,9 @@
     vm.start().context("Failed to start VM")?;
 
     // Wait for VM to finish, and check that it shut down cleanly.
-    let death_reason = vm.wait_for_death();
+    let death_reason = vm
+        .wait_for_death_with_timeout(Duration::from_secs(10))
+        .ok_or_else(|| anyhow!("Timed out waiting for VM exit"))?;
     assert_eq!(death_reason, DeathReason::Shutdown);
 
     Ok(())
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 7ee2d39..90aac1e 100644
--- a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
+++ b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
@@ -122,37 +122,34 @@
 
         final int trialCount = 10;
 
-        double sum = 0;
-        double squareSum = 0;
-        double min = Double.MAX_VALUE;
-        double max = Double.MIN_VALUE;
+        List<Double> bootTimeMetrics = new ArrayList<>();
+        List<Double> bootloaderTimeMetrics = new ArrayList<>();
+        List<Double> kernelBootTimeMetrics = new ArrayList<>();
+        List<Double> userspaceBootTimeMetrics = new ArrayList<>();
+
         for (int i = 0; i < trialCount; i++) {
             VirtualMachineConfig.Builder builder =
                     mInner.newVmConfigBuilder("assets/vm_config.json");
+
+            // To grab boot events from log, set debug mode to FULL
             VirtualMachineConfig normalConfig =
-                    builder.debugLevel(DebugLevel.NONE).memoryMib(256).build();
+                    builder.debugLevel(DebugLevel.FULL).memoryMib(256).build();
             mInner.forceCreateNewVirtualMachine("test_vm_boot_time", normalConfig);
 
             BootResult result = tryBootVm(TAG, "test_vm_boot_time");
             assertThat(result.payloadStarted).isTrue();
 
-            double elapsedMilliseconds = result.elapsedNanoTime / 1000000.0;
-
-            sum += elapsedMilliseconds;
-            squareSum += elapsedMilliseconds * elapsedMilliseconds;
-            if (min > elapsedMilliseconds) min = elapsedMilliseconds;
-            if (max < elapsedMilliseconds) max = elapsedMilliseconds;
+            final Double nanoToMilli = 1000000.0;
+            bootTimeMetrics.add(result.endToEndNanoTime / nanoToMilli);
+            bootloaderTimeMetrics.add(result.getBootloaderElapsedNanoTime() / nanoToMilli);
+            kernelBootTimeMetrics.add(result.getKernelElapsedNanoTime() / nanoToMilli);
+            userspaceBootTimeMetrics.add(result.getUserspaceElapsedNanoTime() / nanoToMilli);
         }
 
-        Bundle bundle = new Bundle();
-        double average = sum / trialCount;
-        double variance = squareSum / trialCount - average * average;
-        double stdev = Math.sqrt(variance);
-        bundle.putDouble("avf_perf/microdroid/boot_time_average_ms", average);
-        bundle.putDouble("avf_perf/microdroid/boot_time_min_ms", min);
-        bundle.putDouble("avf_perf/microdroid/boot_time_max_ms", max);
-        bundle.putDouble("avf_perf/microdroid/boot_time_stdev_ms", stdev);
-        mInstrumentation.sendStatus(0, bundle);
+        reportMetrics(bootTimeMetrics,          "avf_perf/microdroid/boot_time_",           "_ms");
+        reportMetrics(bootloaderTimeMetrics,    "avf_perf/microdroid/bootloader_time_",     "_ms");
+        reportMetrics(kernelBootTimeMetrics,    "avf_perf/microdroid/kernel_boot_time_",    "_ms");
+        reportMetrics(userspaceBootTimeMetrics, "avf_perf/microdroid/userspace_boot_time_", "_ms");
     }
 
     @Test
@@ -199,30 +196,36 @@
             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";
+        reportMetrics(readRates, metricNamePrefix, unit);
+    }
 
-        bundle.putDouble(metricNamePrefix + "mean" + unit, mean);
-        bundle.putDouble(metricNamePrefix + "std" + unit, stdDev);
+    private void reportMetrics(List<Double> data, String prefix, String suffix) {
+        double sum = 0;
+        double min = Double.MAX_VALUE;
+        double max = Double.MIN_VALUE;
+        for (double d : data) {
+            sum += d;
+            if (min > d) min = d;
+            if (max < d) max = d;
+        }
+        double avg = sum / data.size();
+        double sqSum = 0;
+        for (double d : data) {
+            sqSum += (d - avg) * (d - avg);
+        }
+        double stdDev = Math.sqrt(sqSum / (data.size() - 1));
+
+        Bundle bundle = new Bundle();
+        bundle.putDouble(prefix + "min" + suffix, min);
+        bundle.putDouble(prefix + "max" + suffix, max);
+        bundle.putDouble(prefix + "average" + suffix, avg);
+        bundle.putDouble(prefix + "stdev" + suffix, stdDev);
         mInstrumentation.sendStatus(0, bundle);
     }
 
diff --git a/tests/benchmark/src/native/benchmarkbinary.cpp b/tests/benchmark/src/native/benchmarkbinary.cpp
index 6a5b764..5523579 100644
--- a/tests/benchmark/src/native/benchmarkbinary.cpp
+++ b/tests/benchmark/src/native/benchmarkbinary.cpp
@@ -68,7 +68,7 @@
         char buf[kBlockSizeBytes];
 
         clock_t start = clock();
-        unique_fd fd(open(filename.c_str(), O_RDONLY));
+        unique_fd fd(open(filename.c_str(), O_RDONLY | O_CLOEXEC));
         if (fd.get() == -1) {
             return ErrnoError() << "Read: opening " << filename << " failed";
         }
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 1f57634..84e189a 100644
--- a/tests/helper/src/java/com/android/microdroid/test/MicrodroidDeviceTestBase.java
+++ b/tests/helper/src/java/com/android/microdroid/test/MicrodroidDeviceTestBase.java
@@ -39,30 +39,13 @@
 import java.io.BufferedReader;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.util.OptionalLong;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 
 public abstract class MicrodroidDeviceTestBase {
-    /** Copy output from the VM to logcat. This is helpful when things go wrong. */
-    protected static void logVmOutput(String tag, InputStream vmOutputStream, String name) {
-        new Thread(
-                () -> {
-                    try {
-                        BufferedReader reader =
-                                new BufferedReader(new InputStreamReader(vmOutputStream));
-                        String line;
-                        while ((line = reader.readLine()) != null
-                                && !Thread.interrupted()) {
-                            Log.i(tag, name + ": " + line);
-                        }
-                    } catch (Exception e) {
-                        Log.w(tag, name, e);
-                    }
-                }).start();
-    }
-
     public static boolean isCuttlefish() {
         return VirtualizationTestHelper.isCuttlefish(SystemProperties.get("ro.product.name"));
     }
@@ -138,16 +121,84 @@
 
     protected abstract static class VmEventListener implements VirtualMachineCallback {
         private ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
+        private OptionalLong mVcpuStartedNanoTime = OptionalLong.empty();
+        private OptionalLong mKernelStartedNanoTime = OptionalLong.empty();
+        private OptionalLong mInitStartedNanoTime = OptionalLong.empty();
+        private OptionalLong mPayloadStartedNanoTime = OptionalLong.empty();
+
+        private void processBootEvents(String log) {
+            if (!mVcpuStartedNanoTime.isPresent()) {
+                mVcpuStartedNanoTime = OptionalLong.of(System.nanoTime());
+            }
+            if (log.contains("Starting kernel") && !mKernelStartedNanoTime.isPresent()) {
+                mKernelStartedNanoTime = OptionalLong.of(System.nanoTime());
+            }
+            if (log.contains("Run /init as init process") && !mInitStartedNanoTime.isPresent()) {
+                mInitStartedNanoTime = OptionalLong.of(System.nanoTime());
+            }
+            if (log.contains("microdroid_manager") && log.contains("executing main task")
+                    && !mPayloadStartedNanoTime.isPresent()) {
+                mPayloadStartedNanoTime = OptionalLong.of(System.nanoTime());
+            }
+        }
+
+        private void logVmOutputAndMonitorBootEvents(String tag,
+                InputStream vmOutputStream,
+                String name,
+                boolean monitorEvents) {
+            new Thread(
+                    () -> {
+                        try {
+                            BufferedReader reader =
+                                    new BufferedReader(new InputStreamReader(vmOutputStream));
+                            String line;
+                            while ((line = reader.readLine()) != null
+                                    && !Thread.interrupted()) {
+                                if (monitorEvents) processBootEvents(line);
+                                Log.i(tag, name + ": " + line);
+                            }
+                        } catch (Exception e) {
+                            Log.w(tag, name, e);
+                        }
+                    }).start();
+        }
+
+        private void logVmOutputAndMonitorBootEvents(String tag,
+                InputStream vmOutputStream,
+                String name) {
+            logVmOutputAndMonitorBootEvents(tag, vmOutputStream, name, true);
+        }
+
+        /** Copy output from the VM to logcat. This is helpful when things go wrong. */
+        protected void logVmOutput(String tag, InputStream vmOutputStream, String name) {
+            logVmOutputAndMonitorBootEvents(tag, vmOutputStream, name, false);
+        }
 
         public void runToFinish(String logTag, VirtualMachine vm)
                 throws VirtualMachineException, InterruptedException {
             vm.setCallback(mExecutorService, this);
             vm.run();
-            logVmOutput(logTag, vm.getConsoleOutputStream(), "Console");
+            logVmOutputAndMonitorBootEvents(logTag, vm.getConsoleOutputStream(), "Console");
             logVmOutput(logTag, vm.getLogOutputStream(), "Log");
             mExecutorService.awaitTermination(300, TimeUnit.SECONDS);
         }
 
+        public OptionalLong getVcpuStartedNanoTime() {
+            return mVcpuStartedNanoTime;
+        }
+
+        public OptionalLong getKernelStartedNanoTime() {
+            return mKernelStartedNanoTime;
+        }
+
+        public OptionalLong getInitStartedNanoTime() {
+            return mInitStartedNanoTime;
+        }
+
+        public OptionalLong getPayloadStartedNanoTime() {
+            return mPayloadStartedNanoTime;
+        }
+
         protected void forceStop(VirtualMachine vm) {
             try {
                 vm.clearCallback();
@@ -183,12 +234,55 @@
     public static class BootResult {
         public final boolean payloadStarted;
         public final int deathReason;
-        public final long elapsedNanoTime;
+        public final long endToEndNanoTime;
 
-        BootResult(boolean payloadStarted, int deathReason, long elapsedNanoTime) {
+        public final OptionalLong vcpuStartedNanoTime;
+        public final OptionalLong kernelStartedNanoTime;
+        public final OptionalLong initStartedNanoTime;
+        public final OptionalLong payloadStartedNanoTime;
+
+        BootResult(boolean payloadStarted,
+                int deathReason,
+                long endToEndNanoTime,
+                OptionalLong vcpuStartedNanoTime,
+                OptionalLong kernelStartedNanoTime,
+                OptionalLong initStartedNanoTime,
+                OptionalLong payloadStartedNanoTime) {
             this.payloadStarted = payloadStarted;
             this.deathReason = deathReason;
-            this.elapsedNanoTime = elapsedNanoTime;
+            this.endToEndNanoTime = endToEndNanoTime;
+            this.vcpuStartedNanoTime = vcpuStartedNanoTime;
+            this.kernelStartedNanoTime = kernelStartedNanoTime;
+            this.initStartedNanoTime = initStartedNanoTime;
+            this.payloadStartedNanoTime = payloadStartedNanoTime;
+        }
+
+        private long getVcpuStartedNanoTime() {
+            return vcpuStartedNanoTime.getAsLong();
+        }
+
+        private long getKernelStartedNanoTime() {
+            return kernelStartedNanoTime.getAsLong();
+        }
+
+        private long getInitStartedNanoTime() {
+            return initStartedNanoTime.getAsLong();
+        }
+
+        private long getPayloadStartedNanoTime() {
+            return payloadStartedNanoTime.getAsLong();
+        }
+
+        public long getBootloaderElapsedNanoTime() {
+            return getKernelStartedNanoTime() - getVcpuStartedNanoTime();
+        }
+
+        public long getKernelElapsedNanoTime() {
+            return getInitStartedNanoTime() - getKernelStartedNanoTime();
+        }
+
+        public long getUserspaceElapsedNanoTime() {
+            return getPayloadStartedNanoTime() - getInitStartedNanoTime();
         }
     }
 
@@ -218,6 +312,10 @@
         return new BootResult(
                 payloadStarted.getNow(false),
                 deathReason.getNow(DeathReason.INFRASTRUCTURE_ERROR),
-                endTime.getNow(beginTime) - beginTime);
+                endTime.getNow(beginTime) - beginTime,
+                listener.getVcpuStartedNanoTime(),
+                listener.getKernelStartedNanoTime(),
+                listener.getInitStartedNanoTime(),
+                listener.getPayloadStartedNanoTime());
     }
 }
diff --git a/tests/hostside/Android.bp b/tests/hostside/Android.bp
index 24288ee..b7f34e7 100644
--- a/tests/hostside/Android.bp
+++ b/tests/hostside/Android.bp
@@ -22,8 +22,6 @@
         ":test.com.android.virt.pem",
         ":test2.com.android.virt.pem",
         ":test-payload-metadata",
-        ":com.android.adbd{.apex}",
-        ":com.android.os.statsd{.apex}",
     ],
     data_native_bins: [
         "sepolicy-analyze",
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java
index c4ea80a..07b8679 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java
@@ -39,6 +39,7 @@
 import com.android.tradefed.util.CommandResult;
 import com.android.tradefed.util.FileUtil;
 import com.android.tradefed.util.RunUtil;
+import com.android.tradefed.util.xml.AbstractXmlParser;
 
 import org.json.JSONArray;
 import org.json.JSONException;
@@ -49,7 +50,10 @@
 import org.junit.Test;
 import org.junit.rules.TestName;
 import org.junit.runner.RunWith;
+import org.xml.sax.Attributes;
+import org.xml.sax.helpers.DefaultHandler;
 
+import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -175,9 +179,55 @@
         }
     }
 
+    static class ActiveApexInfo {
+        public String name;
+        public String path;
+        ActiveApexInfo(String name, String path) {
+            this.name = name;
+            this.path = path;
+        }
+    }
+
+    static class ActiveApexInfoList {
+        private List<ActiveApexInfo> mList;
+        ActiveApexInfoList(List<ActiveApexInfo> list) {
+            this.mList = list;
+        }
+        ActiveApexInfo get(String apexName) {
+            for (ActiveApexInfo info: mList) {
+                if (info.name.equals(apexName)) {
+                    return info;
+                }
+            }
+            return null;
+        }
+    }
+
+    private ActiveApexInfoList getActiveApexInfoList() throws Exception {
+        String apexInfoListXml = getDevice().pullFileContents("/apex/apex-info-list.xml");
+        List<ActiveApexInfo> list = new ArrayList<>();
+        new AbstractXmlParser() {
+            @Override
+            protected DefaultHandler createXmlHandler() {
+                return new DefaultHandler() {
+                    @Override
+                    public void startElement(String uri, String localName, String qName,
+                            Attributes attributes) {
+                        if (localName.equals("apex-info")
+                                && attributes.getValue("isActive").equals("true")) {
+                            list.add(new ActiveApexInfo(attributes.getValue("moduleName"),
+                                    attributes.getValue("modulePath")));
+                        }
+                    }
+                };
+            }
+        }.parse(new ByteArrayInputStream(apexInfoListXml.getBytes()));
+        return new ActiveApexInfoList(list);
+    }
+
     private String runMicrodroidWithResignedImages(File key, Map<String, File> keyOverrides,
             boolean isProtected, boolean daemonize, String consolePath)
-            throws DeviceNotAvailableException, IOException, JSONException {
+            throws Exception {
         CommandRunner android = new CommandRunner(getDevice());
 
         File virtApexDir = FileUtil.createTempDir("virt_apex");
@@ -207,11 +257,10 @@
         final String payloadMetadataPath = TEST_ROOT + "payload-metadata.img";
         getDevice().pushFile(findTestFile("test-payload-metadata.img"), payloadMetadataPath);
 
-        // push APEXes required for the VM.
-        final String statsdApexPath = TEST_ROOT + "com.android.os.statsd.apex";
-        final String adbdApexPath = TEST_ROOT + "com.android.adbd.apex";
-        getDevice().pushFile(findTestFile("com.android.os.statsd.apex"), statsdApexPath);
-        getDevice().pushFile(findTestFile("com.android.adbd.apex"), adbdApexPath);
+        // get paths to the two APEXes required for the VM.
+        ActiveApexInfoList list = getActiveApexInfoList();
+        final String statsdApexPath = list.get("com.android.os.statsd").path;
+        final String adbdApexPath = list.get("com.android.adbd").path;
 
         // Since Java APP can't start a VM with a custom image, here, we start a VM using `vm run`
         // command with a VM Raw config which is equiv. to what virtualizationservice creates with
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 1141106..b429e4d 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -157,6 +157,26 @@
     }
 
     @Test
+    public void bootFailsWhenLowMem() throws VirtualMachineException, InterruptedException {
+        VirtualMachineConfig lowMemConfig = mInner.newVmConfigBuilder("assets/vm_config.json")
+                .memoryMib(20)
+                .debugLevel(DebugLevel.NONE)
+                .build();
+        VirtualMachine vm = mInner.forceCreateNewVirtualMachine("low_mem", lowMemConfig);
+        final CompletableFuture<Integer> exception = new CompletableFuture<>();
+        VmEventListener listener =
+                new VmEventListener() {
+                    @Override
+                    public void onDied(VirtualMachine vm, @DeathReason int reason) {
+                        exception.complete(reason);
+                        super.onDied(vm, reason);
+                    }
+                };
+        listener.runToFinish(TAG, vm);
+        assertThat(exception.getNow(0)).isAnyOf(DeathReason.REBOOT, DeathReason.HANGUP);
+    }
+
+    @Test
     public void changingDebugLevelInvalidatesVmIdentity()
             throws VirtualMachineException, InterruptedException, IOException {
         assume()
diff --git a/zipfuse/Android.bp b/zipfuse/Android.bp
index d07a8e1..e10fc31 100644
--- a/zipfuse/Android.bp
+++ b/zipfuse/Android.bp
@@ -10,7 +10,7 @@
     prefer_rlib: true,
     rustlibs: [
         "libanyhow",
-        "libclap_deprecated",
+        "libclap",
         "libfuse_rust",
         "liblibc",
         "libzip",
diff --git a/zipfuse/src/main.rs b/zipfuse/src/main.rs
index 874056a..8400a72 100644
--- a/zipfuse/src/main.rs
+++ b/zipfuse/src/main.rs
@@ -41,7 +41,7 @@
     let matches = App::new("zipfuse")
         .arg(
             Arg::with_name("options")
-                .short("o")
+                .short('o')
                 .takes_value(true)
                 .required(false)
                 .help("Comma separated list of mount options"),