Merge "Drop unnecessary dependencies to libbinder_rpc_unstable"
diff --git a/compos/common/compos_client.rs b/compos/common/compos_client.rs
index 59fdd9f..92c9a3c 100644
--- a/compos/common/compos_client.rs
+++ b/compos/common/compos_client.rs
@@ -139,6 +139,7 @@
             memoryMib: parameters.memory_mib.unwrap_or(0), // 0 means use the default
             cpuTopology: cpu_topology,
             taskProfiles: parameters.task_profiles.clone(),
+            gdbPort: 0, // Don't start gdb-server
         });
 
         let callback = Box::new(Callback {});
diff --git a/docs/debug/gdb.md b/docs/debug/gdb.md
new file mode 100644
index 0000000..316faad
--- /dev/null
+++ b/docs/debug/gdb.md
@@ -0,0 +1,46 @@
+# Debugging guest kernels with gdb
+
+Note: this feature is only available on android14-5.15 and newer host kernels.
+
+Starting with Android U it is possible to attach a gdb to the guest kernel, when
+starting a debuggable and non-protected guest VM.
+
+You can do this by passing `--gdb <port>` argument to the `vm run`, `vm run-app`
+and `vm run-microdroid` commands. The `crosvm` will start the gdb server on the
+provided port. It will wait for the gdb client to connect to it before
+proceeding with the VM boot.
+
+Here is an example invocation:
+
+```shell
+adb forward tcp:3456 tcp:3456
+adb shell /apex/com.android.virt/bin/vm run-microdroid --gdb 3456
+```
+
+Then in another shell:
+
+```shell
+gdb vmlinux
+(gdb) target remote :3456
+(gdb) hbreak start_kernel
+(gdb) c
+```
+
+The [kernel documentation](
+https://www.kernel.org/doc/html/latest/dev-tools/gdb-kernel-debugging.html) has
+some general techniques on how to debug kernel with gdb.
+
+## Obtaining vmlinux for Microdroid kernels
+
+If you are debugging Microdroid kernel that you have built [locally](
+../../microdroid/kernel/README.md), then look for `out/dist/vmlinux` in your
+kernel repository.
+
+If you are debugging Microdroid kernel bundled with the `com.android.virt` APEX,
+then you need to obtain the build ID of this kernel. You can do this by
+checking the prebuilt-info.txt file in the
+`packages/modules/Virtualization/microdroid/kernel/arm64` or
+`packages/modules/Virtualization/microdroid/kernel/x86_64` directories.
+
+Using that build ID you can download the vmlinux from the build server via:
+https://ci.android.com/builds/submitted/${BUILD_ID}/kernel_microdroid_aarch64/latest/vmlinux
diff --git a/javalib/jni/android_system_virtualmachine_VirtualMachine.cpp b/javalib/jni/android_system_virtualmachine_VirtualMachine.cpp
index 9281e73..b3354cc 100644
--- a/javalib/jni/android_system_virtualmachine_VirtualMachine.cpp
+++ b/javalib/jni/android_system_virtualmachine_VirtualMachine.cpp
@@ -58,9 +58,10 @@
 
     RpcSessionHandle session;
     // We need a thread pool to be able to support linkToDeath, or callbacks
-    // (b/268335700). This if a fairly arbitrary number, although it happens to
-    // match the default max outgoing threads.
-    ARpcSession_setMaxIncomingThreads(session.get(), 10);
+    // (b/268335700). These threads are currently created eagerly, so we don't
+    // want too many. The number 1 is chosen after some discussion, and to match
+    // the server-side default (mMaxThreads on RpcServer).
+    ARpcSession_setMaxIncomingThreads(session.get(), 1);
     auto client = ARpcSession_setupPreconnectedClient(session.get(), requestFunc, &args);
     return AIBinder_toJavaBinder(env, client);
 }
diff --git a/libs/avb/Android.bp b/libs/avb/Android.bp
index c173f1c..7bfea3f 100644
--- a/libs/avb/Android.bp
+++ b/libs/avb/Android.bp
@@ -2,9 +2,8 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-rust_bindgen {
-    name: "libavb_bindgen",
-    host_supported: true,
+rust_defaults {
+    name: "libavb_bindgen.defaults",
     wrapper_src: "bindgen/avb.h",
     crate_name: "avb_bindgen",
     edition: "2021",
@@ -19,13 +18,28 @@
         "--raw-line=#![no_std]",
         "--ctypes-prefix=core::ffi",
     ],
+    cflags: ["-DBORINGSSL_NO_CXX"],
+}
+
+rust_bindgen {
+    name: "libavb_bindgen",
+    defaults: ["libavb_bindgen.defaults"],
+    host_supported: true,
     static_libs: [
-        "libavb_non_debug",
+        "libavb",
     ],
     shared_libs: [
         "libcrypto",
     ],
-    cflags: ["-DBORINGSSL_NO_CXX"],
+}
+
+rust_bindgen {
+    name: "libavb_bindgen_nostd",
+    defaults: ["libavb_bindgen.defaults"],
+    static_libs: [
+        "libavb_baremetal",
+        "libcrypto_baremetal",
+    ],
 }
 
 rust_test {
diff --git a/libs/libfdt/src/lib.rs b/libs/libfdt/src/lib.rs
index 29d7abe..927bf50 100644
--- a/libs/libfdt/src/lib.rs
+++ b/libs/libfdt/src/lib.rs
@@ -614,7 +614,8 @@
         fdt_err_expect_zero(ret)
     }
 
-    fn as_ptr(&self) -> *const c_void {
+    /// Return a shared pointer to the device tree.
+    pub fn as_ptr(&self) -> *const c_void {
         self as *const _ as *const c_void
     }
 
diff --git a/microdroid/payload/Android.bp b/microdroid/payload/Android.bp
index 77d7036..c8d1044 100644
--- a/microdroid/payload/Android.bp
+++ b/microdroid/payload/Android.bp
@@ -36,6 +36,17 @@
     ],
 }
 
+java_library_host {
+    name: "microdroid_payload_metadata",
+    srcs: [
+        "src/**/*.java",
+        "metadata.proto",
+    ],
+    proto: {
+        type: "lite",
+    },
+}
+
 cc_binary_host {
     name: "mk_payload",
     srcs: [
diff --git a/microdroid/payload/metadata.proto b/microdroid/payload/metadata.proto
index c74c23b..6b999af 100644
--- a/microdroid/payload/metadata.proto
+++ b/microdroid/payload/metadata.proto
@@ -18,6 +18,9 @@
 
 package android.microdroid;
 
+option java_package = "com.android.virt";
+option java_outer_classname = "PayloadMetadataProtos";
+
 // Metadata is the body of the "metadata" partition
 message Metadata {
   uint32 version = 1;
diff --git a/microdroid/payload/src/com/android/virt/PayloadMetadata.java b/microdroid/payload/src/com/android/virt/PayloadMetadata.java
new file mode 100644
index 0000000..c2f0a7f
--- /dev/null
+++ b/microdroid/payload/src/com/android/virt/PayloadMetadata.java
@@ -0,0 +1,49 @@
+package com.android.virt;
+
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/** Provides utility to create/read/write PayloadMetadata */
+public class PayloadMetadata {
+    public static void write(PayloadMetadataProtos.Metadata metadata, File file)
+            throws IOException {
+        byte[] message = metadata.toByteArray();
+
+        try (DataOutputStream os = new DataOutputStream(new FileOutputStream(file))) {
+            // write length prefix (4-byte, big-endian)
+            os.writeInt(message.length);
+            // write the message
+            os.write(message);
+        }
+    }
+
+    public static PayloadMetadataProtos.Metadata metadata(
+            String configPath,
+            PayloadMetadataProtos.ApkPayload apk,
+            Iterable<? extends PayloadMetadataProtos.ApexPayload> apexes) {
+        return PayloadMetadataProtos.Metadata.newBuilder()
+                .setVersion(1)
+                .setConfigPath(configPath)
+                .setApk(apk)
+                .addAllApexes(apexes)
+                .build();
+    }
+
+    public static PayloadMetadataProtos.ApkPayload apk(String name) {
+        return PayloadMetadataProtos.ApkPayload.newBuilder()
+                .setName(name)
+                .setPayloadPartitionName("microdroid-apk")
+                .setIdsigPartitionName("microdroid-apk-idsig")
+                .build();
+    }
+
+    public static PayloadMetadataProtos.ApexPayload apex(String name) {
+        return PayloadMetadataProtos.ApexPayload.newBuilder()
+                .setName(name)
+                .setIsFactory(true)
+                .setPartitionName(name)
+                .build();
+    }
+}
diff --git a/microdroid_manager/src/dice.rs b/microdroid_manager/src/dice.rs
index fd22198..c3136e8 100644
--- a/microdroid_manager/src/dice.rs
+++ b/microdroid_manager/src/dice.rs
@@ -17,8 +17,8 @@
 use anyhow::{anyhow, bail, Context, Error, Result};
 use byteorder::{NativeEndian, ReadBytesExt};
 use diced_open_dice::{
-    bcc_handover_parse, retry_bcc_main_flow, BccHandover, Cdi, Config, DiceMode, Hash, Hidden,
-    InputValues, OwnedDiceArtifacts,
+    bcc_handover_parse, retry_bcc_main_flow, BccHandover, Config, DiceArtifacts, DiceMode, Hash,
+    Hidden, InputValues, OwnedDiceArtifacts,
 };
 use keystore2_crypto::ZVec;
 use libc::{c_void, mmap, munmap, MAP_FAILED, MAP_PRIVATE, PROT_READ};
@@ -32,14 +32,12 @@
 
 /// Derives a sealing key from the DICE sealing CDI.
 pub fn derive_sealing_key(
-    cdi_seal: &Cdi,
+    dice_artifacts: &dyn DiceArtifacts,
     salt: &[u8],
     info: &[u8],
-    keysize: usize,
-) -> Result<ZVec> {
-    let mut key = ZVec::new(keysize)?;
-    hkdf(&mut key, Md::sha256(), cdi_seal, salt, info)?;
-    Ok(key)
+    key: &mut [u8],
+) -> Result<()> {
+    Ok(hkdf(key, Md::sha256(), dice_artifacts.cdi_seal(), salt, info)?)
 }
 
 /// Artifacts that are mapped into the process address space from the driver.
@@ -54,6 +52,13 @@
 }
 
 impl DiceDriver<'_> {
+    fn dice_artifacts(&self) -> &dyn DiceArtifacts {
+        match self {
+            Self::Real { bcc_handover, .. } => bcc_handover,
+            Self::Fake(owned_dice_artifacts) => owned_dice_artifacts,
+        }
+    }
+
     pub fn new(driver_path: &Path) -> Result<Self> {
         if driver_path.exists() {
             log::info!("Using DICE values from driver");
@@ -95,16 +100,15 @@
         })
     }
 
-    pub fn get_sealing_key(&self, identifier: &[u8]) -> Result<ZVec> {
+    /// Derives a sealing key of `key_length` bytes from the DICE sealing CDI.
+    pub fn get_sealing_key(&self, identifier: &[u8], key_length: usize) -> Result<ZVec> {
         // Deterministically derive a key to use for sealing data, rather than using the CDI
         // directly, so we have the chance to rotate the key if needed. A salt isn't needed as the
         // input key material is already cryptographically strong.
-        let cdi_seal = match self {
-            Self::Real { bcc_handover, .. } => bcc_handover.cdi_seal,
-            Self::Fake(fake) => &fake.cdi_values.cdi_seal,
-        };
+        let mut key = ZVec::new(key_length)?;
         let salt = &[];
-        derive_sealing_key(cdi_seal, salt, identifier, 32)
+        derive_sealing_key(self.dice_artifacts(), salt, identifier, &mut key)?;
+        Ok(key)
     }
 
     pub fn derive(
@@ -122,25 +126,21 @@
             if debug { DiceMode::kDiceModeDebug } else { DiceMode::kDiceModeNormal },
             hidden,
         );
-        let (cdi_attest, cdi_seal, bcc) = match &self {
-            Self::Real { bcc_handover, .. } => (
-                bcc_handover.cdi_attest,
-                bcc_handover.cdi_seal,
-                bcc_handover.bcc.ok_or_else(|| anyhow!("bcc is none"))?,
-            ),
-            Self::Fake(fake) => {
-                (&fake.cdi_values.cdi_attest, &fake.cdi_values.cdi_seal, fake.bcc.as_slice())
-            }
-        };
-        let dice_artifacts = retry_bcc_main_flow(cdi_attest, cdi_seal, bcc, &input_values)
-            .context("DICE derive from driver")?;
+        let current_dice_artifacts = self.dice_artifacts();
+        let next_dice_artifacts = retry_bcc_main_flow(
+            current_dice_artifacts.cdi_attest(),
+            current_dice_artifacts.cdi_seal(),
+            current_dice_artifacts.bcc().ok_or_else(|| anyhow!("bcc is none"))?,
+            &input_values,
+        )
+        .context("DICE derive from driver")?;
         if let Self::Real { driver_path, .. } = &self {
             // Writing to the device wipes the artifacts. The string is ignored by the driver but
             // included for documentation.
             fs::write(driver_path, "wipe")
                 .map_err(|err| Error::new(err).context("Wiping driver"))?;
         }
-        Ok(dice_artifacts)
+        Ok(next_dice_artifacts)
     }
 }
 
diff --git a/microdroid_manager/src/instance.rs b/microdroid_manager/src/instance.rs
index 96e9360..6900ea5 100644
--- a/microdroid_manager/src/instance.rs
+++ b/microdroid_manager/src/instance.rs
@@ -142,9 +142,9 @@
         self.file.read_exact(&mut header)?;
 
         // Decrypt and authenticate the data (along with the header).
-        let key = dice.get_sealing_key(INSTANCE_KEY_IDENTIFIER)?;
-        let plaintext =
-            decrypt_aead(Cipher::aes_256_gcm(), &key, Some(&nonce), &header, &data, &tag)?;
+        let cipher = Cipher::aes_256_gcm();
+        let key = dice.get_sealing_key(INSTANCE_KEY_IDENTIFIER, cipher.key_len())?;
+        let plaintext = decrypt_aead(cipher, &key, Some(&nonce), &header, &data, &tag)?;
 
         let microdroid_data = serde_cbor::from_slice(plaintext.as_slice())?;
         Ok(Some(microdroid_data))
@@ -188,10 +188,10 @@
         self.file.write_all(nonce.as_ref())?;
 
         // Then encrypt and sign the data.
-        let key = dice.get_sealing_key(INSTANCE_KEY_IDENTIFIER)?;
+        let cipher = Cipher::aes_256_gcm();
+        let key = dice.get_sealing_key(INSTANCE_KEY_IDENTIFIER, cipher.key_len())?;
         let mut tag = [0; AES_256_GCM_TAG_LENGTH];
-        let ciphertext =
-            encrypt_aead(Cipher::aes_256_gcm(), &key, Some(&nonce), &header, &data, &mut tag)?;
+        let ciphertext = encrypt_aead(cipher, &key, Some(&nonce), &header, &data, &mut tag)?;
 
         // Persist the encrypted payload data and the tag.
         self.file.write_all(&ciphertext)?;
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 7ca0d3c..1148c31 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -40,6 +40,7 @@
 use itertools::sorted;
 use libc::VMADDR_CID_HOST;
 use log::{error, info, warn};
+use keystore2_crypto::ZVec;
 use microdroid_metadata::{write_metadata, Metadata, PayloadMetadata};
 use microdroid_payload_config::{OsConfig, Task, TaskType, VmPayloadConfig};
 use nix::fcntl::{fcntl, F_SETFD, FdFlag};
@@ -917,12 +918,8 @@
         0x6F, 0xB3, 0xF9, 0x40, 0xCE, 0xDD, 0x99, 0x40, 0xAA, 0xA7, 0x0E, 0x92, 0x73, 0x90, 0x86,
         0x4A, 0x75,
     ];
-    let key = derive_sealing_key(
-        &dice_artifacts.cdi_values.cdi_seal,
-        &salt,
-        ENCRYPTEDSTORE_KEY_IDENTIFIER.as_bytes(),
-        ENCRYPTEDSTORE_KEYSIZE,
-    )?;
+    let mut key = ZVec::new(ENCRYPTEDSTORE_KEYSIZE)?;
+    derive_sealing_key(dice_artifacts, &salt, ENCRYPTEDSTORE_KEY_IDENTIFIER.as_bytes(), &mut key)?;
 
     let mut cmd = Command::new(ENCRYPTEDSTORE_BIN);
     cmd.arg("--blkdevice")
diff --git a/microdroid_manager/src/vm_payload_service.rs b/microdroid_manager/src/vm_payload_service.rs
index ac8f60a..96f51f0 100644
--- a/microdroid_manager/src/vm_payload_service.rs
+++ b/microdroid_manager/src/vm_payload_service.rs
@@ -14,15 +14,14 @@
 
 //! Implementation of the AIDL interface `IVmPayloadService`.
 
+use crate::dice::derive_sealing_key;
 use android_system_virtualization_payload::aidl::android::system::virtualization::payload::IVmPayloadService::{
     BnVmPayloadService, IVmPayloadService, VM_PAYLOAD_SERVICE_SOCKET_NAME};
 use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::IVirtualMachineService;
 use anyhow::Result;
 use binder::{Interface, BinderFeatures, ExceptionCode, Status, Strong};
-use diced_open_dice::OwnedDiceArtifacts;
+use diced_open_dice::{DiceArtifacts, OwnedDiceArtifacts};
 use log::{error, info};
-use openssl::hkdf::hkdf;
-use openssl::md::Md;
 use rpcbinder::RpcServer;
 
 /// Implementation of `IVmPayloadService`.
@@ -48,22 +47,25 @@
             0xB7, 0xA8, 0x43, 0x92,
         ];
         let mut secret = vec![0; size.try_into().unwrap()];
-        hkdf(&mut secret, Md::sha256(), &self.dice.cdi_values.cdi_seal, &salt, identifier)
-            .map_err(|e| {
-                error!("Failed to derive VM instance secret: {:?}", e);
-                Status::new_service_specific_error(-1, None)
-            })?;
+        derive_sealing_key(&self.dice, &salt, identifier, &mut secret).map_err(|e| {
+            error!("Failed to derive VM instance secret: {:?}", e);
+            Status::new_service_specific_error(-1, None)
+        })?;
         Ok(secret)
     }
 
     fn getDiceAttestationChain(&self) -> binder::Result<Vec<u8>> {
         self.check_restricted_apis_allowed()?;
-        Ok(self.dice.bcc.clone())
+        if let Some(bcc) = self.dice.bcc() {
+            Ok(bcc.to_vec())
+        } else {
+            Err(Status::new_exception_str(ExceptionCode::ILLEGAL_STATE, Some("bcc is none")))
+        }
     }
 
     fn getDiceAttestationCdi(&self) -> binder::Result<Vec<u8>> {
         self.check_restricted_apis_allowed()?;
-        Ok(self.dice.cdi_values.cdi_attest.to_vec())
+        Ok(self.dice.cdi_attest().to_vec())
     }
 }
 
diff --git a/pvmfw/avb/Android.bp b/pvmfw/avb/Android.bp
index 0c1e392..7ed4895 100644
--- a/pvmfw/avb/Android.bp
+++ b/pvmfw/avb/Android.bp
@@ -8,11 +8,11 @@
     srcs: ["src/lib.rs"],
     prefer_rlib: true,
     rustlibs: [
-        "libavb_bindgen",
+        "libavb_bindgen_nostd",
         "libtinyvec_nostd",
     ],
     whole_static_libs: [
-        "libavb_non_debug",
+        "libavb_baremetal",
     ],
 }
 
diff --git a/pvmfw/src/entry.rs b/pvmfw/src/entry.rs
index ddde50a..964ded1 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -391,7 +391,6 @@
         }
     }
 
-    #[allow(dead_code)] // TODO(b/232900974)
     fn get_debug_policy(&mut self) -> Option<&mut [u8]> {
         match self {
             Self::Config(ref mut cfg) => cfg.get_debug_policy(),
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index ba26114..2e56597 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -63,7 +63,7 @@
     memory: &mut MemoryTracker,
 ) -> Result<(), RebootReason> {
     info!("pVM firmware");
-    debug!("FDT: {:?}", fdt as *const libfdt::Fdt);
+    debug!("FDT: {:?}", fdt.as_ptr());
     debug!("Signed kernel: {:?} ({:#x} bytes)", signed_kernel.as_ptr(), signed_kernel.len());
     debug!("AVB public key: addr={:?}, size={:#x} ({1})", PUBLIC_KEY.as_ptr(), PUBLIC_KEY.len());
     if let Some(rd) = ramdisk {
diff --git a/pvmfw/src/memory.rs b/pvmfw/src/memory.rs
index 7eecb97..86fcd00 100644
--- a/pvmfw/src/memory.rs
+++ b/pvmfw/src/memory.rs
@@ -267,7 +267,7 @@
         for region in &self.regions {
             match region.mem_type {
                 MemoryType::ReadWrite => {
-                    // TODO: Use page table's dirty bit to only flush pages that were touched.
+                    // TODO(b/269738062): Use PT's dirty bit to only flush pages that were touched.
                     helpers::flush_region(region.range.start, region.range.len())
                 }
                 MemoryType::ReadOnly => {}
@@ -314,10 +314,7 @@
     // non-zero size.
     let buffer = unsafe { alloc_zeroed(layout) };
 
-    // TODO: Use let-else once we have Rust 1.65 in AOSP.
-    let buffer = if let Some(buffer) = NonNull::new(buffer) {
-        buffer
-    } else {
+    let Some(buffer) = NonNull::new(buffer) else {
         handle_alloc_error(layout);
     };
 
diff --git a/pvmfw/src/virtio/pci.rs b/pvmfw/src/virtio/pci.rs
index dd9e34e..b61403b 100644
--- a/pvmfw/src/virtio/pci.rs
+++ b/pvmfw/src/virtio/pci.rs
@@ -93,9 +93,7 @@
                 info, device_function, status, command
             );
 
-            let virtio_type = if let Some(t) = virtio_device_type(&info) {
-                t
-            } else {
+            let Some(virtio_type) = virtio_device_type(&info) else {
                 continue;
             };
             debug!("  VirtIO {:?}", virtio_type);
diff --git a/rialto/tests/test.rs b/rialto/tests/test.rs
index 04a87f3..be5f118 100644
--- a/rialto/tests/test.rs
+++ b/rialto/tests/test.rs
@@ -68,6 +68,7 @@
         cpuTopology: CpuTopology::ONE_CPU,
         platformVersion: "~1.0".to_string(),
         taskProfiles: vec![],
+        gdbPort: 0, // No gdb
     });
     let vm = VmInstance::create(service.as_ref(), &config, Some(console), Some(log), None)
         .context("Failed to create VM")?;
diff --git a/tests/aidl/com/android/microdroid/testservice/IBenchmarkService.aidl b/tests/aidl/com/android/microdroid/testservice/IBenchmarkService.aidl
index c8c8660..1eda67e 100644
--- a/tests/aidl/com/android/microdroid/testservice/IBenchmarkService.aidl
+++ b/tests/aidl/com/android/microdroid/testservice/IBenchmarkService.aidl
@@ -41,4 +41,7 @@
 
     /** Runs the vsock server on VM and receives data. */
     void runVsockServerAndReceiveData(int serverFd, int numBytesToReceive);
+
+    /** Adds two numbers and returns the result. */
+    int add(int a, int b);
 }
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 e979f30..fd0158b 100644
--- a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
+++ b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
@@ -57,6 +57,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.OptionalLong;
+import java.util.Random;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Function;
 
@@ -250,7 +251,7 @@
         VirtualMachineConfig config =
                 newVmConfigBuilder()
                         .setPayloadConfigPath("assets/vm_config_io.json")
-                        .setDebugLevel(DEBUG_LEVEL_FULL)
+                        .setDebugLevel(DEBUG_LEVEL_NONE)
                         .build();
         List<Double> transferRates = new ArrayList<>(IO_TEST_TRIAL_COUNT);
 
@@ -277,7 +278,7 @@
         VirtualMachineConfig config =
                 newVmConfigBuilder()
                         .setPayloadConfigPath("assets/vm_config_io.json")
-                        .setDebugLevel(DEBUG_LEVEL_FULL)
+                        .setDebugLevel(DEBUG_LEVEL_NONE)
                         .build();
         List<Double> readRates = new ArrayList<>(IO_TEST_TRIAL_COUNT);
 
@@ -566,4 +567,66 @@
             }
         }
     }
+
+    @Test
+    public void testVsockRpcBinderLatency() throws Exception {
+        VirtualMachineConfig config =
+                newVmConfigBuilder()
+                        .setPayloadConfigPath("assets/vm_config_io.json")
+                        .setDebugLevel(DEBUG_LEVEL_NONE)
+                        .build();
+
+        List<Double> requestLatencies = new ArrayList<>(IO_TEST_TRIAL_COUNT);
+        for (int i = 0; i < IO_TEST_TRIAL_COUNT; ++i) {
+            String vmName = "test_vm_request_" + i;
+            VirtualMachine vm = forceCreateNewVirtualMachine(vmName, config);
+            BenchmarkVmListener.create(new VsockRpcBinderLatencyListener(requestLatencies))
+                    .runToFinish(TAG, vm);
+        }
+        reportMetrics(requestLatencies, "vsock/rpcbinder/request_latency", "ms");
+    }
+
+    private static class VsockRpcBinderLatencyListener
+            implements BenchmarkVmListener.InnerListener {
+        private static final int NUM_REQUESTS = 10000;
+        private static final int NUM_WARMUP_REQUESTS = 10;
+
+        private final List<Double> mResults;
+
+        VsockRpcBinderLatencyListener(List<Double> results) {
+            mResults = results;
+        }
+
+        @Override
+        public void onPayloadReady(VirtualMachine vm, IBenchmarkService benchmarkService)
+                throws RemoteException {
+            // Warm up a few times.
+            Random rand = new Random();
+            for (int i = 0; i < NUM_WARMUP_REQUESTS; i++) {
+                int a = rand.nextInt();
+                int b = rand.nextInt();
+                int c = benchmarkService.add(a, b);
+                assertThat(c).isEqualTo(a + b);
+            }
+
+            // Use the VM to compute Fibonnacci numbers, save timestamps between requests.
+            int a = 0;
+            int b = 1;
+            int c;
+            long timestamps[] = new long[NUM_REQUESTS + 1];
+            for (int i = 0; i < NUM_REQUESTS; i++) {
+                timestamps[i] = System.nanoTime();
+                c = benchmarkService.add(a, b);
+                a = b;
+                b = c;
+            }
+            timestamps[NUM_REQUESTS] = System.nanoTime();
+
+            // Log individual request latencies.
+            for (int i = 0; i < NUM_REQUESTS; i++) {
+                long diff = timestamps[i + 1] - timestamps[i];
+                mResults.add((double) diff / NANO_TO_MILLI);
+            }
+        }
+    }
 }
diff --git a/tests/benchmark/src/native/benchmarkbinary.cpp b/tests/benchmark/src/native/benchmarkbinary.cpp
index 6cfc71d..022698f 100644
--- a/tests/benchmark/src/native/benchmarkbinary.cpp
+++ b/tests/benchmark/src/native/benchmarkbinary.cpp
@@ -96,6 +96,11 @@
         return resultStatus(res);
     }
 
+    ndk::ScopedAStatus add(int32_t a, int32_t b, int32_t* out) override {
+        *out = a + b;
+        return ndk::ScopedAStatus::ok();
+    }
+
 private:
     /**
      * Measures the read rate for reading the given file.
diff --git a/tests/hostside/Android.bp b/tests/hostside/Android.bp
index 046a9d6..f1e5054 100644
--- a/tests/hostside/Android.bp
+++ b/tests/hostside/Android.bp
@@ -37,6 +37,7 @@
         "MicrodroidHostTestHelper",
         "compatibility-host-util",
         "cts-statsd-atom-host-test-utils",
+        "microdroid_payload_metadata",
     ],
     per_testcase_directory: true,
     data: [
@@ -57,7 +58,6 @@
         "initrd_bootconfig",
         "lpmake",
         "lpunpack",
-        "mk_payload",
         "sign_virt_apex",
         "simg2img",
     ],
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index 726fb4a..0be6a62 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -50,6 +50,7 @@
 import com.android.tradefed.util.FileUtil;
 import com.android.tradefed.util.RunUtil;
 import com.android.tradefed.util.xml.AbstractXmlParser;
+import com.android.virt.PayloadMetadata;
 
 import org.json.JSONArray;
 import org.json.JSONObject;
@@ -133,42 +134,14 @@
 
     private void createPayloadMetadata(List<ActiveApexInfo> apexes, File payloadMetadata)
             throws Exception {
-        // mk_payload's config
-        File configFile = new File(payloadMetadata.getParentFile(), "payload_config.json");
-        JSONObject config = new JSONObject();
-        config.put(
-                "apk",
-                new JSONObject(Map.of("name", "microdroid-apk", "path", "", "idsig_path", "")));
-        config.put("payload_config_path", "/mnt/apk/assets/vm_config.json");
-        config.put(
-                "apexes",
-                new JSONArray(
+        PayloadMetadata.write(
+                PayloadMetadata.metadata(
+                        "/mnt/apk/assets/vm_config.json",
+                        PayloadMetadata.apk("microdroid-apk"),
                         apexes.stream()
-                                .map(apex -> new JSONObject(Map.of("name", apex.name, "path", "")))
-                                .collect(toList())));
-        FileUtil.writeToFile(config.toString(), configFile);
-
-        RunUtil runUtil = new RunUtil();
-        String command =
-                String.join(
-                        " ",
-                        findTestFile("mk_payload").getAbsolutePath(),
-                        "--metadata-only",
-                        configFile.getAbsolutePath(),
-                        payloadMetadata.getAbsolutePath());
-        // mk_payload should run fast enough
-        CommandResult result = runUtil.runTimedCmd(5000, "/bin/bash", "-c", command);
-        String out = result.getStdout();
-        String err = result.getStderr();
-        assertWithMessage(
-                        "creating payload metadata failed:\n\tout: "
-                                + out
-                                + "\n\terr: "
-                                + err
-                                + "\n")
-                .about(command_results())
-                .that(result)
-                .isSuccess();
+                                .map(apex -> PayloadMetadata.apex(apex.name))
+                                .collect(toList())),
+                payloadMetadata);
     }
 
     private void resignVirtApex(
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 9b29fa3..a66f9c3 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -133,9 +133,7 @@
 
     private static final String VM_SHARE_APP_PACKAGE_NAME = "com.android.microdroid.vmshare_app";
 
-    @Test
-    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
-    public void createAndConnectToVm() throws Exception {
+    private void createAndConnectToVmHelper(int cpuTopology) throws Exception {
         assumeSupportedKernel();
 
         VirtualMachineConfig config =
@@ -143,6 +141,7 @@
                         .setPayloadBinaryName("MicrodroidTestNativeLib.so")
                         .setMemoryBytes(minMemoryRequired())
                         .setDebugLevel(DEBUG_LEVEL_FULL)
+                        .setCpuTopology(cpuTopology)
                         .build();
         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
 
@@ -166,6 +165,18 @@
 
     @Test
     @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+    public void createAndConnectToVm() throws Exception {
+        createAndConnectToVmHelper(CPU_TOPOLOGY_ONE_CPU);
+    }
+
+    @Test
+    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+    public void createAndConnectToVm_HostCpuTopology() throws Exception {
+        createAndConnectToVmHelper(CPU_TOPOLOGY_MATCH_HOST);
+    }
+
+    @Test
+    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
     public void createAndRunNoDebugVm() throws Exception {
         assumeSupportedKernel();
 
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index 89f74d6..aceb319 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -63,7 +63,7 @@
 use std::ffi::CStr;
 use std::fs::{read_dir, remove_file, File, OpenOptions};
 use std::io::{BufRead, BufReader, Error, ErrorKind, Write};
-use std::num::NonZeroU32;
+use std::num::{NonZeroU16, NonZeroU32};
 use std::os::unix::io::{FromRawFd, IntoRawFd};
 use std::os::unix::raw::pid_t;
 use std::path::{Path, PathBuf};
@@ -299,14 +299,24 @@
                 // Some features are reserved for platform apps only, even when using
                 // VirtualMachineAppConfig:
                 // - controlling CPUs;
-                // - specifying a config file in the APK.
-                !config.taskProfiles.is_empty() || matches!(config.payload, Payload::ConfigPath(_))
+                // - specifying a config file in the APK;
+                // - gdbPort is set, meaning that crosvm will start a gdb server.
+                !config.taskProfiles.is_empty()
+                    || matches!(config.payload, Payload::ConfigPath(_))
+                    || config.gdbPort > 0
             }
         };
         if is_custom {
             check_use_custom_virtual_machine()?;
         }
 
+        let gdb_port = extract_gdb_port(config);
+
+        // Additional permission checks if caller request gdb.
+        if gdb_port.is_some() {
+            check_gdb_allowed(config)?;
+        }
+
         let state = &mut *self.state.lock().unwrap();
         let console_fd =
             clone_or_prepare_logger_fd(config, console_fd, format!("Console({})", cid))?;
@@ -430,6 +440,7 @@
             indirect_files,
             platform_version: parse_platform_version_req(&config.platformVersion)?,
             detect_hangup: is_app_config,
+            gdb_port,
         };
         let instance = Arc::new(
             VmInstance::new(
@@ -583,6 +594,7 @@
     vm_config.protectedVm = config.protectedVm;
     vm_config.cpuTopology = config.cpuTopology;
     vm_config.taskProfiles = config.taskProfiles.clone();
+    vm_config.gdbPort = config.gdbPort;
 
     // Microdroid takes additional init ramdisk & (optionally) storage image
     add_microdroid_system_images(config, instance_file, storage_image, &mut vm_config)?;
@@ -973,6 +985,43 @@
     }
 }
 
+fn is_protected(config: &VirtualMachineConfig) -> bool {
+    match config {
+        VirtualMachineConfig::RawConfig(config) => config.protectedVm,
+        VirtualMachineConfig::AppConfig(config) => config.protectedVm,
+    }
+}
+
+fn check_gdb_allowed(config: &VirtualMachineConfig) -> binder::Result<()> {
+    if is_protected(config) {
+        return Err(Status::new_exception_str(
+            ExceptionCode::SECURITY,
+            Some("can't use gdb with protected VMs"),
+        ));
+    }
+
+    match config {
+        VirtualMachineConfig::RawConfig(_) => Ok(()),
+        VirtualMachineConfig::AppConfig(config) => {
+            if config.debugLevel != DebugLevel::FULL {
+                Err(Status::new_exception_str(
+                    ExceptionCode::SECURITY,
+                    Some("can't use gdb with non-debuggable VMs"),
+                ))
+            } else {
+                Ok(())
+            }
+        }
+    }
+}
+
+fn extract_gdb_port(config: &VirtualMachineConfig) -> Option<NonZeroU16> {
+    match config {
+        VirtualMachineConfig::RawConfig(config) => NonZeroU16::new(config.gdbPort as u16),
+        VirtualMachineConfig::AppConfig(config) => NonZeroU16::new(config.gdbPort as u16),
+    }
+}
+
 fn clone_or_prepare_logger_fd(
     config: &VirtualMachineConfig,
     fd: Option<&ParcelFileDescriptor>,
diff --git a/virtualizationmanager/src/crosvm.rs b/virtualizationmanager/src/crosvm.rs
index ea1146e..09605a4 100644
--- a/virtualizationmanager/src/crosvm.rs
+++ b/virtualizationmanager/src/crosvm.rs
@@ -32,7 +32,7 @@
 use std::fs::{read_to_string, File};
 use std::io::{self, Read};
 use std::mem;
-use std::num::NonZeroU32;
+use std::num::{NonZeroU16, NonZeroU32};
 use std::os::unix::io::{AsRawFd, RawFd, FromRawFd};
 use std::os::unix::process::ExitStatusExt;
 use std::path::{Path, PathBuf};
@@ -105,6 +105,7 @@
     pub indirect_files: Vec<File>,
     pub platform_version: VersionReq,
     pub detect_hangup: bool,
+    pub gdb_port: Option<NonZeroU16>,
 }
 
 /// A disk image to pass to crosvm for a VM.
@@ -746,6 +747,10 @@
         command.arg("--task-profiles").arg(config.task_profiles.join(","));
     }
 
+    if let Some(gdb_port) = config.gdb_port {
+        command.arg("--gdb").arg(gdb_port.to_string());
+    }
+
     // Keep track of what file descriptors should be mapped to the crosvm process.
     let mut preserved_fds = config.indirect_files.iter().map(|file| file.as_raw_fd()).collect();
 
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
index 7f90ed6..c467c2f 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
@@ -70,6 +70,12 @@
     /** Debug level of the VM */
     DebugLevel debugLevel = DebugLevel.NONE;
 
+    /**
+     * Port at which crosvm will start a gdb server to debug guest kernel.
+     * If set to zero, then gdb server won't be started.
+     */
+    int gdbPort = 0;
+
     /** Whether the VM should be a protected VM. */
     boolean protectedVm;
 
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
index 1cda163..87d4ba2 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
@@ -63,4 +63,10 @@
      * List of task profile names to apply for the VM
      */
     String[] taskProfiles;
+
+    /**
+     * Port at which crosvm will start a gdb server to debug guest kernel.
+     * If set to zero, then gdb server won't be started.
+     */
+    int gdbPort = 0;
 }
diff --git a/vm/src/main.rs b/vm/src/main.rs
index 6c08a19..1d9f50b 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -28,6 +28,7 @@
 use create_idsig::command_create_idsig;
 use create_partition::command_create_partition;
 use run::{command_run, command_run_app, command_run_microdroid};
+use std::num::NonZeroU16;
 use std::path::{Path, PathBuf};
 
 #[derive(Debug)]
@@ -101,6 +102,11 @@
         /// Paths to extra idsig files.
         #[clap(long = "extra-idsig")]
         extra_idsigs: Vec<PathBuf>,
+
+        /// Port at which crosvm will start a gdb server to debug guest kernel.
+        /// Note: this is only supported on Android kernels android14-5.15 and higher.
+        #[clap(long)]
+        gdb: Option<NonZeroU16>,
     },
     /// Run a virtual machine with Microdroid inside
     RunMicrodroid {
@@ -152,6 +158,11 @@
         /// Comma separated list of task profile names to apply to the VM
         #[clap(long)]
         task_profiles: Vec<String>,
+
+        /// Port at which crosvm will start a gdb server to debug guest kernel.
+        /// Note: this is only supported on Android kernels android14-5.15 and higher.
+        #[clap(long)]
+        gdb: Option<NonZeroU16>,
     },
     /// Run a virtual machine
     Run {
@@ -177,6 +188,11 @@
         /// Path to file for VM log output.
         #[clap(long)]
         log: Option<PathBuf>,
+
+        /// Port at which crosvm will start a gdb server to debug guest kernel.
+        /// Note: this is only supported on Android kernels android14-5.15 and higher.
+        #[clap(long)]
+        gdb: Option<NonZeroU16>,
     },
     /// List running virtual machines
     List,
@@ -260,6 +276,7 @@
             cpu_topology,
             task_profiles,
             extra_idsigs,
+            gdb,
         } => command_run_app(
             name,
             get_service()?.as_ref(),
@@ -278,6 +295,7 @@
             cpu_topology,
             task_profiles,
             &extra_idsigs,
+            gdb,
         ),
         Opt::RunMicrodroid {
             name,
@@ -291,6 +309,7 @@
             mem,
             cpu_topology,
             task_profiles,
+            gdb,
         } => command_run_microdroid(
             name,
             get_service()?.as_ref(),
@@ -304,8 +323,9 @@
             mem,
             cpu_topology,
             task_profiles,
+            gdb,
         ),
-        Opt::Run { name, config, cpu_topology, task_profiles, console, log } => {
+        Opt::Run { name, config, cpu_topology, task_profiles, console, log, gdb } => {
             command_run(
                 name,
                 get_service()?.as_ref(),
@@ -315,6 +335,7 @@
                 /* mem */ None,
                 cpu_topology,
                 task_profiles,
+                gdb,
             )
         }
         Opt::List => command_list(get_service()?.as_ref()),
diff --git a/vm/src/run.rs b/vm/src/run.rs
index fa84591..5d785de 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -32,6 +32,7 @@
 use std::fs;
 use std::fs::File;
 use std::io;
+use std::num::NonZeroU16;
 use std::os::unix::io::{AsRawFd, FromRawFd};
 use std::path::{Path, PathBuf};
 use vmclient::{ErrorCode, VmInstance};
@@ -58,6 +59,7 @@
     cpu_topology: CpuTopology,
     task_profiles: Vec<String>,
     extra_idsigs: &[PathBuf],
+    gdb: Option<NonZeroU16>,
 ) -> Result<(), Error> {
     let apk_file = File::open(apk).context("Failed to open APK file")?;
 
@@ -144,6 +146,7 @@
         memoryMib: mem.unwrap_or(0) as i32, // 0 means use the VM default
         cpuTopology: cpu_topology,
         taskProfiles: task_profiles,
+        gdbPort: gdb.map(u16::from).unwrap_or(0) as i32, // 0 means no gdb
     });
     run(service, &config, &payload_config_str, console_path, log_path)
 }
@@ -185,6 +188,7 @@
     mem: Option<u32>,
     cpu_topology: CpuTopology,
     task_profiles: Vec<String>,
+    gdb: Option<NonZeroU16>,
 ) -> Result<(), Error> {
     let apk = find_empty_payload_apk_path()?;
     println!("found path {}", apk.display());
@@ -215,6 +219,7 @@
         cpu_topology,
         task_profiles,
         &extra_sig,
+        gdb,
     )
 }
 
@@ -229,6 +234,7 @@
     mem: Option<u32>,
     cpu_topology: CpuTopology,
     task_profiles: Vec<String>,
+    gdb: Option<NonZeroU16>,
 ) -> Result<(), Error> {
     let config_file = File::open(config_path).context("Failed to open config file")?;
     let mut config =
@@ -241,6 +247,9 @@
     } else {
         config.name = String::from("VmRun");
     }
+    if let Some(gdb) = gdb {
+        config.gdbPort = gdb.get() as i32;
+    }
     config.cpuTopology = cpu_topology;
     config.taskProfiles = task_profiles;
     run(
diff --git a/vmbase/example/tests/test.rs b/vmbase/example/tests/test.rs
index cfb0225..930e137 100644
--- a/vmbase/example/tests/test.rs
+++ b/vmbase/example/tests/test.rs
@@ -87,6 +87,7 @@
         cpuTopology: CpuTopology::ONE_CPU,
         platformVersion: "~1.0".to_string(),
         taskProfiles: vec![],
+        gdbPort: 0, // no gdb
     });
     let console = android_log_fd()?;
     let log = android_log_fd()?;