Merge "apkverify: Migrate from ring to openssl crate"
diff --git a/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb b/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb
index 33dfb62..ea17dfc 100644
--- a/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb
+++ b/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb
@@ -1,6 +1,6 @@
 drops {
   android_build_drop {
-    build_id: "8494941"
+    build_id: "8597076"
     target: "u-boot_pvmfw"
     source_file: "pvmfw.img"
   }
diff --git a/compos/aidl/com/android/compos/ICompOsService.aidl b/compos/aidl/com/android/compos/ICompOsService.aidl
index 48a46b1..9cf99be 100644
--- a/compos/aidl/com/android/compos/ICompOsService.aidl
+++ b/compos/aidl/com/android/compos/ICompOsService.aidl
@@ -19,6 +19,18 @@
 /** {@hide} */
 interface ICompOsService {
     /**
+     * Initializes system properties. ART expects interesting properties that have to be passed from
+     * Android. The API client should call this method once with all desired properties, since once
+     * the call completes, the service is considered initialized and cannot be re-initialized again.
+     *
+     * <p>If the initialization failed, Microdroid may already have some properties set. It is up to
+     * the service to reject further calls by the client.
+     *
+     * <p>The service may reject unrecognized names, but it does not interpret values.
+     */
+    void initializeSystemProperties(in String[] names, in String[] values);
+
+    /**
      * What type of compilation to perform.
      */
     @Backing(type="int")
diff --git a/compos/common/odrefresh.rs b/compos/common/odrefresh.rs
index 390e50c..2d7635b 100644
--- a/compos/common/odrefresh.rs
+++ b/compos/common/odrefresh.rs
@@ -35,6 +35,10 @@
 /// The directory under ODREFRESH_OUTPUT_ROOT_DIR where the current (active) artifacts are stored
 pub const CURRENT_ARTIFACTS_SUBDIR: &str = "dalvik-cache";
 
+/// Prefixes of system properties that are interested to odrefresh and dex2oat.
+const ALLOWLIST_SYSTEM_PROPERTY_PREFIXES: &[&str] =
+    &["dalvik.vm.", "ro.dalvik.vm.", "persist.device_config.runtime_native_boot."];
+
 // The highest "standard" exit code defined in sysexits.h (as EX__MAX); odrefresh error codes
 // start above here to avoid clashing.
 // TODO: What if this changes?
@@ -63,3 +67,13 @@
             .ok_or_else(|| anyhow!("Unexpected odrefresh exit code: {}", exit_code))
     }
 }
+
+/// Returns whether the system property name is interesting to odrefresh and dex2oat.
+pub fn is_system_property_interesting(name: &str) -> bool {
+    for prefix in ALLOWLIST_SYSTEM_PROPERTY_PREFIXES {
+        if name.starts_with(prefix) {
+            return true;
+        }
+    }
+    false
+}
diff --git a/compos/composd/src/odrefresh_task.rs b/compos/composd/src/odrefresh_task.rs
index e06e5fe..51e866f 100644
--- a/compos/composd/src/odrefresh_task.rs
+++ b/compos/composd/src/odrefresh_task.rs
@@ -27,7 +27,9 @@
 use compos_aidl_interface::aidl::com::android::compos::ICompOsService::{
     CompilationMode::CompilationMode, ICompOsService,
 };
-use compos_common::odrefresh::{ExitCode, ODREFRESH_OUTPUT_ROOT_DIR};
+use compos_common::odrefresh::{
+    is_system_property_interesting, ExitCode, ODREFRESH_OUTPUT_ROOT_DIR,
+};
 use log::{error, info, warn};
 use rustutils::system_properties;
 use std::fs::{remove_dir_all, File, OpenOptions};
@@ -124,6 +126,16 @@
     compilation_mode: CompilationMode,
     target_dir_name: &str,
 ) -> Result<ExitCode> {
+    let mut names = Vec::new();
+    let mut values = Vec::new();
+    system_properties::foreach(|name, value| {
+        if is_system_property_interesting(name) {
+            names.push(name.to_owned());
+            values.push(value.to_owned());
+        }
+    })?;
+    service.initializeSystemProperties(&names, &values).context("initialize system properties")?;
+
     let output_root = Path::new(ODREFRESH_OUTPUT_ROOT_DIR);
 
     // We need to remove the target directory because odrefresh running in compos will create it
diff --git a/compos/src/compsvc.rs b/compos/src/compsvc.rs
index e21aa7d..91415bb 100644
--- a/compos/src/compsvc.rs
+++ b/compos/src/compsvc.rs
@@ -19,9 +19,14 @@
 //! actual compiler.
 
 use anyhow::{bail, Context, Result};
+use binder_common::new_binder_exception;
+use log::error;
+use rustutils::system_properties;
 use std::default::Default;
 use std::fs::read_dir;
+use std::iter::zip;
 use std::path::{Path, PathBuf};
+use std::sync::RwLock;
 
 use crate::artifact_signer::ArtifactSigner;
 use crate::compilation::{odrefresh, OdrefreshContext};
@@ -29,25 +34,73 @@
 use compos_aidl_interface::aidl::com::android::compos::ICompOsService::{
     BnCompOsService, CompilationMode::CompilationMode, ICompOsService,
 };
-use compos_aidl_interface::binder::{BinderFeatures, Interface, Result as BinderResult, Strong};
+use compos_aidl_interface::binder::{
+    BinderFeatures, ExceptionCode, Interface, Result as BinderResult, Strong,
+};
 use compos_common::binder::to_binder_result;
-use compos_common::odrefresh::ODREFRESH_PATH;
+use compos_common::odrefresh::{is_system_property_interesting, ODREFRESH_PATH};
 
 const AUTHFS_SERVICE_NAME: &str = "authfs_service";
 
 /// Constructs a binder object that implements ICompOsService.
 pub fn new_binder() -> Result<Strong<dyn ICompOsService>> {
-    let service = CompOsService { odrefresh_path: PathBuf::from(ODREFRESH_PATH) };
+    let service = CompOsService {
+        odrefresh_path: PathBuf::from(ODREFRESH_PATH),
+        initialized: RwLock::new(None),
+    };
     Ok(BnCompOsService::new_binder(service, BinderFeatures::default()))
 }
 
 struct CompOsService {
     odrefresh_path: PathBuf,
+
+    /// A locked protected tri-state.
+    ///  * None: uninitialized
+    ///  * Some(true): initialized successfully
+    ///  * Some(false): failed to initialize
+    initialized: RwLock<Option<bool>>,
 }
 
 impl Interface for CompOsService {}
 
 impl ICompOsService for CompOsService {
+    fn initializeSystemProperties(&self, names: &[String], values: &[String]) -> BinderResult<()> {
+        let mut initialized = self.initialized.write().unwrap();
+        if initialized.is_some() {
+            return Err(new_binder_exception(
+                ExceptionCode::ILLEGAL_STATE,
+                format!("Already initialized: {:?}", initialized),
+            ));
+        }
+        *initialized = Some(false);
+
+        if names.len() != values.len() {
+            return Err(new_binder_exception(
+                ExceptionCode::ILLEGAL_ARGUMENT,
+                format!(
+                    "Received inconsistent number of keys ({}) and values ({})",
+                    names.len(),
+                    values.len()
+                ),
+            ));
+        }
+        for (name, value) in zip(names, values) {
+            if !is_system_property_interesting(name) {
+                return Err(new_binder_exception(
+                    ExceptionCode::ILLEGAL_ARGUMENT,
+                    format!("Received invalid system property {}", &name),
+                ));
+            }
+            let result = system_properties::write(name, value);
+            if result.is_err() {
+                error!("Failed to setprop {}", &name);
+                return to_binder_result(result);
+            }
+        }
+        *initialized = Some(true);
+        Ok(())
+    }
+
     fn odrefresh(
         &self,
         compilation_mode: CompilationMode,
@@ -58,6 +111,14 @@
         zygote_arch: &str,
         system_server_compiler_filter: &str,
     ) -> BinderResult<i8> {
+        let initialized = *self.initialized.read().unwrap();
+        if !initialized.unwrap_or(false) {
+            return Err(new_binder_exception(
+                ExceptionCode::ILLEGAL_STATE,
+                "Service has not been initialized",
+            ));
+        }
+
         let context = to_binder_result(OdrefreshContext::new(
             compilation_mode,
             system_dir_fd,
diff --git a/compos/tests/Android.bp b/compos/tests/Android.bp
index c178ddd..b77a7e4 100644
--- a/compos/tests/Android.bp
+++ b/compos/tests/Android.bp
@@ -10,6 +10,7 @@
         "compatibility-tradefed",
         "compatibility-host-util",
     ],
+    data_native_bins: ["bcc_validator"],
     static_libs: [
         "VirtualizationTestHelper",
     ],
diff --git a/compos/tests/java/android/compos/test/ComposTestCase.java b/compos/tests/java/android/compos/test/ComposTestCase.java
index eec9e39..51f0a1f 100644
--- a/compos/tests/java/android/compos/test/ComposTestCase.java
+++ b/compos/tests/java/android/compos/test/ComposTestCase.java
@@ -16,17 +16,24 @@
 
 package android.compos.test;
 
+import static android.virt.test.CommandResultSubject.assertThat;
+import static android.virt.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.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.FileInputStreamSource;
+import com.android.tradefed.result.LogDataType;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.RunUtil;
 
 import org.junit.After;
 import org.junit.Before;
@@ -35,6 +42,8 @@
 import org.junit.rules.TestName;
 import org.junit.runner.RunWith;
 
+import java.io.File;
+
 @RootPermissionTest
 @RunWith(DeviceJUnit4ClassRunner.class)
 public final class ComposTestCase extends VirtualizationTestCaseBase {
@@ -127,7 +136,7 @@
             long start = System.currentTimeMillis();
             CommandResult result = runOdrefresh(android, "--force-compile");
             long elapsed = System.currentTimeMillis() - start;
-            assertThat(result.getExitCode()).isEqualTo(COMPILATION_SUCCESS);
+            assertThat(result).exitCode().isEqualTo(COMPILATION_SUCCESS);
             CLog.i("Local compilation took " + elapsed + "ms");
         }
 
@@ -137,12 +146,7 @@
 
         // --check may delete the output.
         CommandResult result = runOdrefresh(android, "--check");
-        assertThat(result.getExitCode()).isEqualTo(OKAY);
-
-        // Make sure we generate a fresh instance.
-        android.tryRun("rm", "-rf", COMPOS_TEST_ROOT);
-        // TODO: remove once composd starts to clean up the directory.
-        android.tryRun("rm", "-rf", ODREFRESH_OUTPUT_DIR);
+        assertThat(result).exitCode().isEqualTo(OKAY);
 
         // Expect the compilation in Compilation OS to finish successfully.
         {
@@ -151,11 +155,14 @@
                     android.runForResultWithTimeout(
                             ODREFRESH_TIMEOUT_MS, COMPOSD_CMD_BIN, "test-compile");
             long elapsed = System.currentTimeMillis() - start;
-            assertThat(result.getExitCode()).isEqualTo(0);
+            assertThat(result).exitCode().isEqualTo(0);
             CLog.i("Comp OS compilation took " + elapsed + "ms");
         }
         killVmAndReconnectAdb();
 
+        // Expect the BCC extracted from the BCC to be well-formed.
+        assertVmBccIsValid();
+
         // Save the actual checksum for the output directory.
         String actualChecksumSnapshot = checksumDirectoryContentPartial(android,
                 ODREFRESH_OUTPUT_DIR);
@@ -171,6 +178,24 @@
         android.run(COMPOS_VERIFY_BIN + " --debug --instance test");
     }
 
+    private void assertVmBccIsValid() throws Exception {
+        File bcc_file = getDevice().pullFile(COMPOS_APEXDATA_DIR + "/test/bcc");
+        assertThat(bcc_file).isNotNull();
+
+        // Add the BCC to test artifacts, in case it is ill-formed or otherwise interesting.
+        mTestLogs.addTestLog(bcc_file.getPath(), LogDataType.UNKNOWN,
+                new FileInputStreamSource(bcc_file));
+
+        // Find the validator binary - note that it's specified as a dependency in our Android.bp.
+        File validator = getTestInformation().getDependencyFile("bcc_validator", /*targetFirst=*/
+                false);
+
+        CommandResult result = new RunUtil().runTimedCmd(10000,
+                validator.getAbsolutePath(), "verify-chain", bcc_file.getAbsolutePath());
+        assertWithMessage("bcc_validator failed").about(command_results())
+                .that(result).isSuccess();
+    }
+
     private CommandResult runOdrefresh(CommandRunner android, String command) throws Exception {
         return android.runForResultWithTimeout(
                 ODREFRESH_TIMEOUT_MS,
diff --git a/libs/apkverify/Android.bp b/libs/apkverify/Android.bp
index 4c0e1f8..db217c8 100644
--- a/libs/apkverify/Android.bp
+++ b/libs/apkverify/Android.bp
@@ -5,6 +5,7 @@
 rust_defaults {
     name: "libapkverify.defaults",
     crate_name: "apkverify",
+    host_supported: true,
     srcs: ["src/lib.rs"],
     prefer_rlib: true,
     edition: "2018",
diff --git a/microdroid/README.md b/microdroid/README.md
index a652139..681c23a 100644
--- a/microdroid/README.md
+++ b/microdroid/README.md
@@ -7,7 +7,7 @@
 
 ## Prerequisites
 
-Any 64-bit target (either x86_64 or arm64) is supported. 32-bit target is not
+Any 64-bit target (either x86\_64 or arm64) is supported. 32-bit target is not
 supported. Note that we currently don't support user builds; only userdebug
 builds are supported.
 
@@ -36,7 +36,7 @@
 adb reboot
 ```
 
-If your target is x86_64 (e.g. `aosp_cf_x86_64_phone`), replace `aosp_arm64`
+If your target is x86\_64 (e.g. `aosp_cf_x86_64_phone`), replace `aosp_arm64`
 with `aosp_x86_64`.
 
 ## Building an app
diff --git a/pvmfw/idmap.S b/pvmfw/idmap.S
index f1df6cc..62555f9 100644
--- a/pvmfw/idmap.S
+++ b/pvmfw/idmap.S
@@ -39,12 +39,14 @@
 idmap:
 	/* level 1 */
 	.quad		.L_BLOCK_DEV | 0x0		// 1 GB of device mappings
-	.quad		.L_BLOCK_DEV | 0x40000000	// Another 1 GB of device mapppings
-	.quad		.L_TT_TYPE_TABLE + 0f		// up to 1 GB of DRAM
+	.quad		.L_TT_TYPE_TABLE + 0f		// Unmapped device memory, and pVM firmware
+	.quad		.L_TT_TYPE_TABLE + 1f		// up to 1 GB of DRAM
 	.fill		509, 8, 0x0			// 509 GB of remaining VA space
 
 	/* level 2 */
-0:	.quad		.L_BLOCK_RO  | 0x80000000	// DT provided by VMM
-	.quad		.L_BLOCK_MEM_XIP | 0x80200000	// 2 MB of DRAM containing image
-	.quad		.L_BLOCK_MEM | 0x80400000	// 2 MB of writable DRAM
+0:	.fill		511, 8, 0x0
+	.quad		.L_BLOCK_MEM_XIP | 0x7fe00000	// pVM firmware image
+1:	.quad		.L_BLOCK_RO	 | 0x80000000	// DT provided by VMM
+	.quad		.L_BLOCK_RO	 | 0x80200000	// 2 MB of DRAM containing payload image
+	.quad		.L_BLOCK_MEM	 | 0x80400000	// Writable memory for stack, heap &c.
 	.fill		509, 8, 0x0
diff --git a/pvmfw/image.ld b/pvmfw/image.ld
index 4655f68..e122c72 100644
--- a/pvmfw/image.ld
+++ b/pvmfw/image.ld
@@ -16,8 +16,8 @@
 
 MEMORY
 {
+	image		: ORIGIN = 0x7fe00000, LENGTH = 2M
 	dtb_region	: ORIGIN = 0x80000000, LENGTH = 2M
-	image		: ORIGIN = 0x80200000, LENGTH = 2M
 	writable_data	: ORIGIN = 0x80400000, LENGTH = 2M
 }
 
diff --git a/pvmfw/pvmfw.img b/pvmfw/pvmfw.img
index 6060f4b..9afb092 100644
--- a/pvmfw/pvmfw.img
+++ b/pvmfw/pvmfw.img
Binary files differ
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index d98ea8e..280e1ce 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -24,6 +24,10 @@
 main!(main);
 
 /// Entry point for pVM firmware.
-pub fn main() {
-    println!("Hello world");
+pub fn main(fdt_address: u64, payload_start: u64, payload_size: u64, arg3: u64) {
+    println!("pVM firmware");
+    println!(
+        "fdt_address={:#010x}, payload_start={:#010x}, payload_size={:#010x}, x3={:#010x}",
+        fdt_address, payload_start, payload_size, arg3,
+    );
 }
diff --git a/tests/hostside/AndroidTest.xml b/tests/hostside/AndroidTest.xml
index 79428ce..5c3e5d1 100644
--- a/tests/hostside/AndroidTest.xml
+++ b/tests/hostside/AndroidTest.xml
@@ -19,6 +19,12 @@
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+        <!-- Permission checks are bypassed if shell is root -->
+        <option name="force-root" value="false"/>
+    </target_preparer>
+
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="MicrodroidHostTestCases.jar" />
     </test>
diff --git a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
index 9ee95c0..ec2afaa 100644
--- a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
+++ b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
@@ -16,7 +16,6 @@
 
 package android.virt.test;
 
-import static android.virt.test.CommandResultSubject.assertThat;
 import static android.virt.test.CommandResultSubject.command_results;
 
 import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
@@ -478,6 +477,7 @@
     @Test
     public void testCustomVirtualMachinePermission()
             throws DeviceNotAvailableException, IOException, JSONException {
+        assumeTrue(isProtectedVmSupported());
         CommandRunner android = new CommandRunner(getDevice());
 
         // Pull etc/microdroid.json
@@ -502,11 +502,8 @@
         final String ret =
                 android.runForResult(VIRT_APEX + "bin/vm run", configPath).getStderr().trim();
 
-        assertTrue(
-                "The test should fail with a permission error",
-                ret.contains(
-                        "does not have the android.permission.USE_CUSTOM_VIRTUAL_MACHINE"
-                            + " permission"));
+        assertThat(ret).contains("does not have the android.permission.USE_CUSTOM_VIRTUAL_MACHINE"
+                + " permission");
     }
 
     @Before
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 b3b8816..5c48a41 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -33,6 +33,7 @@
 import android.system.virtualmachine.VirtualMachineConfig.DebugLevel;
 import android.system.virtualmachine.VirtualMachineException;
 import android.system.virtualmachine.VirtualMachineManager;
+import android.util.Log;
 
 import androidx.annotation.CallSuper;
 import androidx.test.core.app.ApplicationProvider;
@@ -47,9 +48,13 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
+import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.io.RandomAccessFile;
 import java.nio.file.Files;
 import java.util.List;
@@ -68,10 +73,27 @@
 
 @RunWith(Parameterized.class)
 public class MicrodroidTests {
+    private static final String TAG = "MicrodroidTests";
+
     @Rule public Timeout globalTimeout = Timeout.seconds(300);
 
     private static final String KERNEL_VERSION = SystemProperties.get("ro.kernel.version");
 
+    /** Copy output from the VM to logcat. This is helpful when things go wrong. */
+    private static void logVmOutput(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();
+    }
+
     private static class Inner {
         public boolean mProtectedVm;
         public Context mContext;
@@ -149,6 +171,8 @@
         void runToFinish(VirtualMachine vm) throws VirtualMachineException, InterruptedException {
             vm.setCallback(mExecutorService, this);
             vm.run();
+            logVmOutput(vm.getConsoleOutputStream(), "Console");
+            logVmOutput(vm.getLogOutputStream(), "Log");
             mExecutorService.awaitTermination(300, TimeUnit.SECONDS);
         }
 
@@ -238,6 +262,7 @@
 
                     @Override
                     public void onPayloadReady(VirtualMachine vm) {
+                        Log.i(TAG, "onPayloadReady");
                         payloadReady.complete(true);
                         testVMService(vm);
                         forceStop(vm);
@@ -245,7 +270,9 @@
 
                     @Override
                     public void onPayloadStarted(VirtualMachine vm, ParcelFileDescriptor stream) {
+                        Log.i(TAG, "onPayloadStarted");
                         payloadStarted.complete(true);
+                        logVmOutput(new FileInputStream(stream.getFileDescriptor()), "Payload");
                     }
                 };
         listener.runToFinish(mInner.mVm);
diff --git a/tests/testapk/src/native/testbinary.cpp b/tests/testapk/src/native/testbinary.cpp
index 89570c0..422afca 100644
--- a/tests/testapk/src/native/testbinary.cpp
+++ b/tests/testapk/src/native/testbinary.cpp
@@ -134,16 +134,19 @@
 
     auto callback = []([[maybe_unused]] void* param) {
         // Tell microdroid_manager that we're ready.
-        // Failing to notify is not a fatal error; the payload can continue.
+        // 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 virtualMachineService = IVirtualMachineService::fromBinder(binder);
         if (virtualMachineService == nullptr) {
-            std::cerr << "failed to connect VirtualMachineService";
-            return;
+            std::cerr << "failed to connect VirtualMachineService\n";
+            abort();
         }
-        if (!virtualMachineService->notifyPayloadReady().isOk()) {
-            std::cerr << "failed to notify payload ready to virtualizationservice";
+        if (auto status = virtualMachineService->notifyPayloadReady(); !status.isOk()) {
+            std::cerr << "failed to notify payload ready to virtualizationservice: "
+                      << status.getDescription() << std::endl;
+            abort();
         }
     };
 
@@ -197,7 +200,7 @@
     if (auto res = start_test_service(); res.ok()) {
         return 0;
     } else {
-        std::cerr << "starting service failed: " << res.error();
+        std::cerr << "starting service failed: " << res.error() << "\n";
         return 1;
     }
 }
diff --git a/vm/src/create_idsig.rs b/vm/src/create_idsig.rs
index a0d64d5..fe7cd82 100644
--- a/vm/src/create_idsig.rs
+++ b/vm/src/create_idsig.rs
@@ -15,14 +15,14 @@
 //! Command to create or update an idsig for APK
 
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::IVirtualizationService::IVirtualizationService;
-use android_system_virtualizationservice::binder::{ParcelFileDescriptor, Strong};
+use android_system_virtualizationservice::binder::ParcelFileDescriptor;
 use anyhow::{Context, Error};
 use std::fs::{File, OpenOptions};
 use std::path::Path;
 
 /// Creates or update the idsig file by digesting the input APK file.
 pub fn command_create_idsig(
-    service: Strong<dyn IVirtualizationService>,
+    service: &dyn IVirtualizationService,
     apk: &Path,
     idsig: &Path,
 ) -> Result<(), Error> {
diff --git a/vm/src/create_partition.rs b/vm/src/create_partition.rs
index 22f7bea..049c201 100644
--- a/vm/src/create_partition.rs
+++ b/vm/src/create_partition.rs
@@ -16,7 +16,7 @@
 
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::IVirtualizationService::IVirtualizationService;
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::PartitionType::PartitionType;
-use android_system_virtualizationservice::binder::{ParcelFileDescriptor, Strong};
+use android_system_virtualizationservice::binder::ParcelFileDescriptor;
 use anyhow::{Context, Error};
 use std::convert::TryInto;
 use std::fs::OpenOptions;
@@ -24,7 +24,7 @@
 
 /// Initialise an empty partition image of the given size to be used as a writable partition.
 pub fn command_create_partition(
-    service: Strong<dyn IVirtualizationService>,
+    service: &dyn IVirtualizationService,
     image_path: &Path,
     size: u64,
     partition_type: PartitionType,
diff --git a/vm/src/main.rs b/vm/src/main.rs
index 8b438b4..705e38f 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -191,8 +191,9 @@
     // We need to start the thread pool for Binder to work properly, especially link_to_death.
     ProcessState::start_thread_pool();
 
-    let service = wait_for_interface(VIRTUALIZATION_SERVICE_BINDER_SERVICE_IDENTIFIER)
-        .context("Failed to find VirtualizationService")?;
+    let service: Strong<dyn IVirtualizationService> =
+        wait_for_interface(VIRTUALIZATION_SERVICE_BINDER_SERVICE_IDENTIFIER)
+            .context("Failed to find VirtualizationService")?;
 
     match opt {
         Opt::RunApp {
@@ -211,7 +212,7 @@
             task_profiles,
             extra_idsigs,
         } => command_run_app(
-            service,
+            service.as_ref(),
             &apk,
             &idsig,
             &instance,
@@ -229,7 +230,7 @@
         ),
         Opt::Run { config, daemonize, cpus, cpu_affinity, task_profiles, console, log } => {
             command_run(
-                service,
+                service.as_ref(),
                 &config,
                 daemonize,
                 console.as_deref(),
@@ -240,18 +241,18 @@
                 task_profiles,
             )
         }
-        Opt::Stop { cid } => command_stop(service, cid),
-        Opt::List => command_list(service),
+        Opt::Stop { cid } => command_stop(service.as_ref(), cid),
+        Opt::List => command_list(service.as_ref()),
         Opt::Info => command_info(),
         Opt::CreatePartition { path, size, partition_type } => {
-            command_create_partition(service, &path, size, partition_type)
+            command_create_partition(service.as_ref(), &path, size, partition_type)
         }
-        Opt::CreateIdsig { apk, path } => command_create_idsig(service, &apk, &path),
+        Opt::CreateIdsig { apk, path } => command_create_idsig(service.as_ref(), &apk, &path),
     }
 }
 
 /// Retrieve reference to a previously daemonized VM and stop it.
-fn command_stop(service: Strong<dyn IVirtualizationService>, cid: u32) -> Result<(), Error> {
+fn command_stop(service: &dyn IVirtualizationService, cid: u32) -> Result<(), Error> {
     service
         .debugDropVmRef(cid as i32)
         .context("Failed to get VM from VirtualizationService")?
@@ -260,7 +261,7 @@
 }
 
 /// List the VMs currently running.
-fn command_list(service: Strong<dyn IVirtualizationService>) -> Result<(), Error> {
+fn command_list(service: &dyn IVirtualizationService) -> Result<(), Error> {
     let vms = service.debugListVms().context("Failed to get list of VMs")?;
     println!("Running VMs: {:#?}", vms);
     Ok(())
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 3d3d703..2ae2c95 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -26,9 +26,9 @@
     VirtualMachineState::VirtualMachineState,
 };
 use android_system_virtualizationservice::binder::{
-    BinderFeatures, DeathRecipient, IBinder, ParcelFileDescriptor, Strong,
+    BinderFeatures, DeathRecipient, IBinder, Interface, ParcelFileDescriptor,
+    Result as BinderResult,
 };
-use android_system_virtualizationservice::binder::{Interface, Result as BinderResult};
 use anyhow::{bail, Context, Error};
 use microdroid_payload_config::VmPayloadConfig;
 use std::fs::File;
@@ -41,7 +41,7 @@
 /// Run a VM from the given APK, idsig, and config.
 #[allow(clippy::too_many_arguments)]
 pub fn command_run_app(
-    service: Strong<dyn IVirtualizationService>,
+    service: &dyn IVirtualizationService,
     apk: &Path,
     idsig: &Path,
     instance: &Path,
@@ -85,7 +85,7 @@
     if !instance.exists() {
         const INSTANCE_FILE_SIZE: u64 = 10 * 1024 * 1024;
         command_create_partition(
-            service.clone(),
+            service,
             instance,
             INSTANCE_FILE_SIZE,
             PartitionType::ANDROID_VM_INSTANCE,
@@ -121,7 +121,7 @@
 /// Run a VM from the given configuration file.
 #[allow(clippy::too_many_arguments)]
 pub fn command_run(
-    service: Strong<dyn IVirtualizationService>,
+    service: &dyn IVirtualizationService,
     config_path: &Path,
     daemonize: bool,
     console_path: Option<&Path>,
@@ -165,7 +165,7 @@
 }
 
 fn run(
-    service: Strong<dyn IVirtualizationService>,
+    service: &dyn IVirtualizationService,
     config: &VirtualMachineConfig,
     config_path: &str,
     daemonize: bool,
@@ -213,12 +213,12 @@
     } else {
         // Wait until the VM or VirtualizationService dies. If we just returned immediately then the
         // IVirtualMachine Binder object would be dropped and the VM would be killed.
-        wait_for_vm(vm)
+        wait_for_vm(vm.as_ref())
     }
 }
 
 /// Wait until the given VM or the VirtualizationService itself dies.
-fn wait_for_vm(vm: Strong<dyn IVirtualMachine>) -> Result<(), Error> {
+fn wait_for_vm(vm: &dyn IVirtualMachine) -> Result<(), Error> {
     let dead = AtomicFlag::default();
     let callback = BnVirtualMachineCallback::new_binder(
         VirtualMachineCallback { dead: dead.clone() },
@@ -306,6 +306,18 @@
             DeathReason::ERROR => println!("Error starting VM."),
             DeathReason::REBOOT => println!("VM tried to reboot, possibly due to a kernel panic."),
             DeathReason::CRASH => println!("VM crashed."),
+            DeathReason::PVM_FIRMWARE_PUBLIC_KEY_MISMATCH => println!(
+                "pVM firmware failed to verify the VM because the public key doesn't match."
+            ),
+            DeathReason::PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED => {
+                println!("pVM firmware failed to verify the VM because the instance image changed.")
+            }
+            DeathReason::BOOTLOADER_PUBLIC_KEY_MISMATCH => {
+                println!("Bootloader failed to verify the VM because the public key doesn't match.")
+            }
+            DeathReason::BOOTLOADER_INSTANCE_IMAGE_CHANGED => {
+                println!("Bootloader failed to verify the VM because the instance image changed.")
+            }
             _ => println!("VM died for an unrecognised reason."),
         }
         Ok(())
diff --git a/vmbase/entry.S b/vmbase/entry.S
index a12e1aa..490e841 100644
--- a/vmbase/entry.S
+++ b/vmbase/entry.S
@@ -74,31 +74,30 @@
 .set .Lsctlrval, .Lsctlrval | .L_SCTLR_ELx_I | .L_SCTLR_EL1_SPAN | .L_SCTLR_EL1_RES1 | .L_SCTLR_EL1_WXN
 
 /**
- * This is a generic entry point for an image. It carries out the operations
- * required to prepare the loaded image to be run. Specifically, it zeroes the
- * bss section using registers x25 and above, prepares the stack, enables
- * floating point, and sets up the exception vector.
+ * This is a generic entry point for an image. It carries out the operations required to prepare the
+ * loaded image to be run. Specifically, it zeroes the bss section using registers x25 and above,
+ * prepares the stack, enables floating point, and sets up the exception vector. It preserves x0-x3
+ * for the Rust entry point, as these may contain boot parameters.
  */
 .section .init.entry, "ax"
 .global entry
 entry:
-	/* Enable MMU and caches. */
+	/* Load and apply the memory management configuration, ready to enable MMU and caches. */
 
-	/*
-	 * Load and apply the memory management configuration.
-	 */
-	adrp x1, idmap
-	mov_i x2, .Lmairval
-	mov_i x3, .Ltcrval
-	mov_i x4, .Lsctlrval
+	adrp x30, idmap
+	msr ttbr0_el1, x30
 
+	mov_i x30, .Lmairval
+	msr mair_el1, x30
+
+	mov_i x30, .Ltcrval
 	/* Copy the supported PA range into TCR_EL1.IPS. */
-	mrs x6, id_aa64mmfr0_el1
-	bfi x3, x6, #32, #4
+	mrs x29, id_aa64mmfr0_el1
+	bfi x30, x29, #32, #4
 
-	msr ttbr0_el1, x1
-	msr mair_el1, x2
-	msr tcr_el1, x3
+	msr tcr_el1, x30
+
+	mov_i x30, .Lsctlrval
 
 	/*
 	 * Ensure everything before this point has completed, then invalidate any potentially stale
@@ -111,10 +110,9 @@
 	isb
 
 	/*
-	 * Configure sctlr_el1 to enable MMU and cache and don't proceed until
-	 * this has completed.
+	 * Configure sctlr_el1 to enable MMU and cache and don't proceed until this has completed.
 	 */
-	msr sctlr_el1, x4
+	msr sctlr_el1, x30
 	isb
 
 	/* Disable trapping floating point access in EL1. */
diff --git a/vmbase/example/Android.bp b/vmbase/example/Android.bp
index 4cc4bf3..9c19693 100644
--- a/vmbase/example/Android.bp
+++ b/vmbase/example/Android.bp
@@ -13,6 +13,7 @@
         "libcore.rust_sysroot",
     ],
     rustlibs: [
+        "libbuddy_system_allocator",
         "libvmbase",
     ],
     enabled: false,
diff --git a/vmbase/example/src/main.rs b/vmbase/example/src/main.rs
index bbb64d9..3b1786c 100644
--- a/vmbase/example/src/main.rs
+++ b/vmbase/example/src/main.rs
@@ -16,14 +16,139 @@
 
 #![no_main]
 #![no_std]
+#![feature(default_alloc_error_handler)]
 
 mod exceptions;
 
+extern crate alloc;
+
+use alloc::{vec, vec::Vec};
+use buddy_system_allocator::LockedHeap;
 use vmbase::{main, println};
 
+static INITIALISED_DATA: [u32; 4] = [1, 2, 3, 4];
+static mut ZEROED_DATA: [u32; 10] = [0; 10];
+static mut MUTABLE_DATA: [u32; 4] = [1, 2, 3, 4];
+
+#[global_allocator]
+static HEAP_ALLOCATOR: LockedHeap<32> = LockedHeap::<32>::new();
+
+static mut HEAP: [u8; 65536] = [0; 65536];
+
 main!(main);
 
 /// Entry point for VM bootloader.
-pub fn main() {
+pub fn main(arg0: u64, arg1: u64, arg2: u64, arg3: u64) {
     println!("Hello world");
+    println!("x0={:#010x}, x1={:#010x}, x2={:#010x}, x3={:#010x}", arg0, arg1, arg2, arg3);
+    print_addresses();
+    check_data();
+
+    unsafe {
+        HEAP_ALLOCATOR.lock().init(&mut HEAP as *mut u8 as usize, HEAP.len());
+    }
+
+    check_alloc();
+}
+
+fn print_addresses() {
+    unsafe {
+        println!(
+            "dtb:        {:#010x}-{:#010x} ({} bytes)",
+            &dtb_begin as *const u8 as usize,
+            &dtb_end as *const u8 as usize,
+            &dtb_end as *const u8 as usize - &dtb_begin as *const u8 as usize,
+        );
+        println!(
+            "text:       {:#010x}-{:#010x} ({} bytes)",
+            &text_begin as *const u8 as usize,
+            &text_end as *const u8 as usize,
+            &text_end as *const u8 as usize - &text_begin as *const u8 as usize,
+        );
+        println!(
+            "rodata:     {:#010x}-{:#010x} ({} bytes)",
+            &rodata_begin as *const u8 as usize,
+            &rodata_end as *const u8 as usize,
+            &rodata_end as *const u8 as usize - &rodata_begin as *const u8 as usize,
+        );
+        println!(
+            "data:       {:#010x}-{:#010x} ({} bytes, loaded at {:#010x})",
+            &data_begin as *const u8 as usize,
+            &data_end as *const u8 as usize,
+            &data_end as *const u8 as usize - &data_begin as *const u8 as usize,
+            &data_lma as *const u8 as usize,
+        );
+        println!(
+            "bss:        {:#010x}-{:#010x} ({} bytes)",
+            &bss_begin as *const u8 as usize,
+            &bss_end as *const u8 as usize,
+            &bss_end as *const u8 as usize - &bss_begin as *const u8 as usize,
+        );
+        println!(
+            "boot_stack: {:#010x}-{:#010x} ({} bytes)",
+            &boot_stack_begin as *const u8 as usize,
+            &boot_stack_end as *const u8 as usize,
+            &boot_stack_end as *const u8 as usize - &boot_stack_begin as *const u8 as usize,
+        );
+    }
+}
+
+fn check_data() {
+    println!("INITIALISED_DATA: {:#010x}", &INITIALISED_DATA as *const u32 as usize);
+    unsafe {
+        println!("ZEROED_DATA: {:#010x}", &ZEROED_DATA as *const u32 as usize);
+        println!("MUTABLE_DATA: {:#010x}", &MUTABLE_DATA as *const u32 as usize);
+        println!("HEAP: {:#010x}", &HEAP as *const u8 as usize);
+    }
+
+    assert_eq!(INITIALISED_DATA[0], 1);
+    assert_eq!(INITIALISED_DATA[1], 2);
+    assert_eq!(INITIALISED_DATA[2], 3);
+    assert_eq!(INITIALISED_DATA[3], 4);
+
+    unsafe {
+        for element in ZEROED_DATA.iter() {
+            assert_eq!(*element, 0);
+        }
+        ZEROED_DATA[0] = 13;
+        assert_eq!(ZEROED_DATA[0], 13);
+        ZEROED_DATA[0] = 0;
+        assert_eq!(ZEROED_DATA[0], 0);
+
+        assert_eq!(MUTABLE_DATA[0], 1);
+        assert_eq!(MUTABLE_DATA[1], 2);
+        assert_eq!(MUTABLE_DATA[2], 3);
+        assert_eq!(MUTABLE_DATA[3], 4);
+        MUTABLE_DATA[0] += 41;
+        assert_eq!(MUTABLE_DATA[0], 42);
+    }
+    println!("Data looks good");
+}
+
+fn check_alloc() {
+    println!("Allocating a Vec...");
+    let mut vector: Vec<u32> = vec![1, 2, 3, 4];
+    assert_eq!(vector[0], 1);
+    assert_eq!(vector[1], 2);
+    assert_eq!(vector[2], 3);
+    assert_eq!(vector[3], 4);
+    vector[2] = 42;
+    assert_eq!(vector[2], 42);
+    println!("Vec seems to work.");
+}
+
+extern "C" {
+    static dtb_begin: u8;
+    static dtb_end: u8;
+    static text_begin: u8;
+    static text_end: u8;
+    static rodata_begin: u8;
+    static rodata_end: u8;
+    static data_begin: u8;
+    static data_end: u8;
+    static data_lma: u8;
+    static bss_begin: u8;
+    static bss_end: u8;
+    static boot_stack_begin: u8;
+    static boot_stack_end: u8;
 }
diff --git a/vmbase/src/entry.rs b/vmbase/src/entry.rs
index dd7f6db..1510ae2 100644
--- a/vmbase/src/entry.rs
+++ b/vmbase/src/entry.rs
@@ -18,17 +18,17 @@
 
 /// This is the entry point to the Rust code, called from the binary entry point in `entry.S`.
 #[no_mangle]
-extern "C" fn rust_entry() -> ! {
+extern "C" fn rust_entry(x0: u64, x1: u64, x2: u64, x3: u64) -> ! {
     console::init();
     unsafe {
-        main();
+        main(x0, x1, x2, x3);
     }
     shutdown();
 }
 
 extern "Rust" {
     /// Main function provided by the application using the `main!` macro.
-    fn main();
+    fn main(arg0: u64, arg1: u64, arg2: u64, arg3: u64);
 }
 
 /// Marks the main function of the binary.
@@ -49,9 +49,9 @@
     ($name:path) => {
         // Export a symbol with a name matching the extern declaration above.
         #[export_name = "main"]
-        fn __main() {
+        fn __main(arg0: u64, arg1: u64, arg2: u64, arg3: u64) {
             // Ensure that the main function provided by the application has the correct type.
-            $name()
+            $name(arg0, arg1, arg2, arg3)
         }
     };
 }