Merge "Capture stderr when we run composd_cmd"
diff --git a/authfs/Android.bp b/authfs/Android.bp
index 154a1d6..2532026 100644
--- a/authfs/Android.bp
+++ b/authfs/Android.bp
@@ -23,7 +23,7 @@
         "liblog_rust",
         "libnix",
         "libopenssl",
-        "libprotobuf",
+        "libprotobuf_deprecated",
         "librpcbinder_rs",
         "libthiserror",
     ],
diff --git a/compos/Android.bp b/compos/Android.bp
index 2f6be98..c120b0f 100644
--- a/compos/Android.bp
+++ b/compos/Android.bp
@@ -18,7 +18,7 @@
         "libminijail_rust",
         "libnix",
         "libodsign_proto_rust",
-        "libprotobuf",
+        "libprotobuf_deprecated",
         "libregex",
         "librpcbinder_rs",
         "librustutils",
diff --git a/compos/composd/Android.bp b/compos/composd/Android.bp
index b0294dd..f66de32 100644
--- a/compos/composd/Android.bp
+++ b/compos/composd/Android.bp
@@ -22,7 +22,7 @@
         "liblibc",
         "liblog_rust",
         "libodsign_proto_rust",
-        "libprotobuf",
+        "libprotobuf_deprecated",
         "librustutils",
         "libshared_child",
         "libvmclient",
diff --git a/compos/tests/java/android/compos/test/ComposTestCase.java b/compos/tests/java/android/compos/test/ComposTestCase.java
index 8a1b41a..eb2e072 100644
--- a/compos/tests/java/android/compos/test/ComposTestCase.java
+++ b/compos/tests/java/android/compos/test/ComposTestCase.java
@@ -80,7 +80,7 @@
 
     @Before
     public void setUp() throws Exception {
-        testIfDeviceIsCapable(getDevice());
+        assumeDeviceIsCapable(getDevice());
 
         String value = getDevice().getProperty(SYSTEM_SERVER_COMPILER_FILTER_PROP_NAME);
         if (value == null) {
diff --git a/encryptedstore/src/main.rs b/encryptedstore/src/main.rs
index 7a41f13..86fa6da 100644
--- a/encryptedstore/src/main.rs
+++ b/encryptedstore/src/main.rs
@@ -125,9 +125,13 @@
 
 fn format_ext4(device: &Path) -> Result<()> {
     let mkfs_options = [
-        "-j",               // Create appropriate sized journal
-        "-O metadata_csum", // Metadata checksum for filesystem integrity
-        "-b 4096",          // block size in the filesystem
+        "-j", // Create appropriate sized journal
+        /* metadata_csum: enabled for filesystem integrity
+         * extents: Not enabling extents reduces the coverage of metadata checksumming.
+         * 64bit: larger fields afforded by this feature enable full-strength checksumming.
+         */
+        "-O metadata_csum, extents, 64bit",
+        "-b 4096", // block size in the filesystem
     ];
     let mut cmd = Command::new(MK2FS_BIN);
     let status = cmd
diff --git a/libs/devicemapper/src/lib.rs b/libs/devicemapper/src/lib.rs
index 4cf4e99..fec0114 100644
--- a/libs/devicemapper/src/lib.rs
+++ b/libs/devicemapper/src/lib.rs
@@ -227,8 +227,8 @@
     let context = Context::new(0);
     let now = SystemTime::now().duration_since(UNIX_EPOCH)?;
     let ts = Timestamp::from_unix(context, now.as_secs(), now.subsec_nanos());
-    let uuid = Uuid::new_v1(ts, node_id)?;
-    Ok(String::from(uuid.to_hyphenated().encode_lower(&mut Uuid::encode_buffer())))
+    let uuid = Uuid::new_v1(ts, node_id.try_into()?);
+    Ok(String::from(uuid.hyphenated().encode_lower(&mut Uuid::encode_buffer())))
 }
 
 #[cfg(test)]
diff --git a/microdroid/README.md b/microdroid/README.md
index f70965a..71be7d0 100644
--- a/microdroid/README.md
+++ b/microdroid/README.md
@@ -181,7 +181,8 @@
 
 microdroid can be started with debugging features by debug policies from the
 host. Host bootloader may provide debug policies to host OS's device tree for
-VMs.
+VMs. Host bootloader MUST NOT provide debug policies for locked devices for
+security reasons.
 
 For protected VM, such device tree will be available in microdroid. microdroid
 can check which debuging features is enabled.
diff --git a/microdroid/payload/metadata/Android.bp b/microdroid/payload/metadata/Android.bp
index cd182fc..e3138e8 100644
--- a/microdroid/payload/metadata/Android.bp
+++ b/microdroid/payload/metadata/Android.bp
@@ -12,7 +12,7 @@
     rustlibs: [
         "libanyhow",
         "libmicrodroid_metadata_proto_rust",
-        "libprotobuf",
+        "libprotobuf_deprecated",
     ],
     apex_available: [
         "com.android.virt",
diff --git a/pvmfw/README.md b/pvmfw/README.md
index 04ad8c4..2d2b253 100644
--- a/pvmfw/README.md
+++ b/pvmfw/README.md
@@ -197,16 +197,21 @@
 that it differs from the `BccHandover` defined by the specification in that its
 `Bcc` field is mandatory (while optional in the original).
 
-The handover expected by pvmfw can be generated as follows:
+Devices that fully implement DICE should provide a certificate rooted at the
+Unique Device Secret (UDS) in a boot stage preceding the pvmfw loader (typically
+ABL), in such a way that it would receive a valid `BccHandover`, that can be
+passed to [`BccHandoverMainFlow`][BccHandoverMainFlow] along with the inputs
+described below.
 
-- by passing a `BccHandover` received from a previous boot stage (_e.g._ Trusted
-  Firmware, ROM bootloader, ...) to
-  [`BccHandoverMainFlow`][BccHandoverMainFlow];
+Otherwise, as an intermediate step towards supporting DICE throughout the
+software stack of the device, incomplete implementations may root the BCC at the
+pvmfw loader, using an arbitrary constant as initial CDI. The pvmfw loader can
+easily do so by:
 
-- by generating a `BccHandover` (as an example, see [Trusty][Trusty-BCC]) with
-  both CDIs set to an arbitrary constant value and no `Bcc`, and pass it to
-  `BccHandoverMainFlow`, which will both derive the pvmfw CDIs and start a
-  valid certificate chain, making the pvmfw loader the root of the BCC.
+1. Building a BCC-less `BccHandover` using CBOR operations
+   ([example][Trusty-BCC]) and containing the constant CDIs
+1. Passing the resulting `BccHandover` to `BccHandoverMainFlow` as described
+   above
 
 The recommended DICE inputs at this stage are:
 
@@ -240,7 +245,10 @@
 Config header can provide a DTBO to be overlaid on top of the baseline device
 tree from crosvm.
 
-The DTBO may contain debug policies as follows.
+The DTBO may contain debug policies. Debug policies MUST NOT be provided for
+locked devices for security reasons.
+
+Here are an example of DTBO.
 
 ```
 / {
diff --git a/pvmfw/src/crypto.rs b/pvmfw/src/crypto.rs
index 275de7a..0785a7a 100644
--- a/pvmfw/src/crypto.rs
+++ b/pvmfw/src/crypto.rs
@@ -14,8 +14,6 @@
 
 //! Wrapper around BoringSSL/OpenSSL symbols.
 
-use crate::cstr;
-
 use core::convert::AsRef;
 use core::ffi::{c_char, c_int, CStr};
 use core::fmt;
@@ -23,6 +21,9 @@
 use core::num::NonZeroU32;
 use core::ptr;
 
+use crate::cstr;
+
+use bssl_ffi::CRYPTO_library_init;
 use bssl_ffi::ERR_get_error_line;
 use bssl_ffi::ERR_lib_error_string;
 use bssl_ffi::ERR_reason_error_string;
@@ -298,3 +299,8 @@
         Err(ErrorIterator {})
     }
 }
+
+pub fn init() {
+    // SAFETY - Configures the internal state of the library - may be called multiple times.
+    unsafe { CRYPTO_library_init() }
+}
diff --git a/pvmfw/src/debug_policy.rs b/pvmfw/src/debug_policy.rs
index f4b99a6..bbf7e04 100644
--- a/pvmfw/src/debug_policy.rs
+++ b/pvmfw/src/debug_policy.rs
@@ -14,12 +14,8 @@
 
 //! Support for the debug policy overlay in pvmfw
 
-use crate::cstr;
-use alloc::vec::Vec;
-use core::ffi::CStr;
 use core::fmt;
 use libfdt::FdtError;
-use log::info;
 
 #[derive(Debug, Clone)]
 pub enum DebugPolicyError {
@@ -64,60 +60,6 @@
     fdt.pack().map_err(|e| DebugPolicyError::OverlaidFdt("Failed to re-pack", e))
 }
 
-/// Enables console output by adding kernel.printk.devkmsg and kernel.console to bootargs.
-/// This uses hardcoded console name 'hvc0' and it should be match with microdroid's bootconfig.debuggable.
-fn enable_console_output(fdt: &mut libfdt::Fdt) -> Result<(), DebugPolicyError> {
-    let chosen = match fdt
-        .node(cstr!("/chosen"))
-        .map_err(|e| DebugPolicyError::Fdt("Failed to find /chosen", e))?
-    {
-        Some(node) => node,
-        None => return Ok(()),
-    };
-
-    let bootargs = match chosen
-        .getprop_str(cstr!("bootargs"))
-        .map_err(|e| DebugPolicyError::Fdt("Failed to find bootargs prop", e))?
-    {
-        Some(value) if !value.to_bytes().is_empty() => value,
-        _ => return Ok(()),
-    };
-
-    let mut new_bootargs = Vec::from(bootargs.to_bytes());
-    new_bootargs.extend_from_slice(b" printk.devkmsg=on console=hvc0\0");
-
-    // We'll set larger prop, and need to prepare some room first.
-    fdt.unpack().map_err(|e| DebugPolicyError::OverlaidFdt("Failed to unpack", e))?;
-
-    // We've checked existence of /chosen node at the beginning.
-    let mut chosen_mut = fdt.node_mut(cstr!("/chosen")).unwrap().unwrap();
-    chosen_mut.setprop(cstr!("bootargs"), new_bootargs.as_slice()).map_err(|e| {
-        DebugPolicyError::OverlaidFdt("Failed to enabled console output. FDT might be corrupted", e)
-    })?;
-
-    fdt.pack().map_err(|e| DebugPolicyError::OverlaidFdt("Failed to pack", e))?;
-    Ok(())
-}
-
-/// Returns true only if fdt has log prop in the /avf/guest/common node with value <1>
-fn is_console_output_enabled(fdt: &libfdt::Fdt) -> Result<bool, DebugPolicyError> {
-    let common = match fdt
-        .node(cstr!("/avf/guest/common"))
-        .map_err(|e| DebugPolicyError::DebugPolicyFdt("Failed to find /avf/guest/common node", e))?
-    {
-        Some(node) => node,
-        None => return Ok(false),
-    };
-
-    match common
-        .getprop_u32(cstr!("log"))
-        .map_err(|e| DebugPolicyError::DebugPolicyFdt("Failed to find log prop", e))?
-    {
-        Some(1) => Ok(true),
-        _ => Ok(false),
-    }
-}
-
 /// Handles debug policies.
 ///
 /// # Safety
@@ -132,10 +74,5 @@
         apply_debug_policy(fdt, dp)?;
     }
 
-    // Handles console output in the debug policy
-    if is_console_output_enabled(fdt)? {
-        enable_console_output(fdt)?;
-        info!("console output is enabled by debug policy");
-    }
     Ok(())
 }
diff --git a/pvmfw/src/entry.rs b/pvmfw/src/entry.rs
index 8219882..ffbc4a8 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -15,6 +15,7 @@
 //! Low-level entry and exit points of pvmfw.
 
 use crate::config;
+use crate::crypto;
 use crate::debug_policy::{handle_debug_policy, DebugPolicyError};
 use crate::fdt;
 use crate::heap;
@@ -192,6 +193,8 @@
         RebootReason::InternalError
     })?;
 
+    crypto::init();
+
     // SAFETY - We only get the appended payload from here, once. It is mapped and the linker
     // script prevents it from overlapping with other objects.
     let appended_data = unsafe { get_appended_data_slice() };
diff --git a/pvmfw/src/exceptions.rs b/pvmfw/src/exceptions.rs
index 03fc220..42f4c3b 100644
--- a/pvmfw/src/exceptions.rs
+++ b/pvmfw/src/exceptions.rs
@@ -14,22 +14,21 @@
 
 //! Exception handlers.
 
-use crate::helpers::page_4kb_of;
-use core::arch::asm;
+use crate::{helpers::page_4kb_of, read_sysreg};
 use vmbase::console;
 use vmbase::{console::emergency_write_str, eprintln, power::reboot};
 
-const ESR_32BIT_EXT_DABT: u64 = 0x96000010;
+const ESR_32BIT_EXT_DABT: usize = 0x96000010;
 const UART_PAGE: usize = page_4kb_of(console::BASE_ADDRESS);
 
 #[no_mangle]
 extern "C" fn sync_exception_current(_elr: u64, _spsr: u64) {
-    let esr = read_esr();
-    let far = read_far();
+    let esr = read_sysreg!("esr_el1");
+    let far = read_sysreg!("far_el1");
     // Don't print to the UART if we're handling the exception it could raise.
-    if esr != ESR_32BIT_EXT_DABT || page_4kb_of(far as usize) != UART_PAGE {
+    if esr != ESR_32BIT_EXT_DABT || page_4kb_of(far) != UART_PAGE {
         emergency_write_str("sync_exception_current\n");
-        print_esr(esr);
+        eprintln!("esr={esr:#08x}");
     }
     reboot();
 }
@@ -48,17 +47,17 @@
 
 #[no_mangle]
 extern "C" fn serr_current(_elr: u64, _spsr: u64) {
-    let esr = read_esr();
+    let esr = read_sysreg!("esr_el1");
     emergency_write_str("serr_current\n");
-    print_esr(esr);
+    eprintln!("esr={esr:#08x}");
     reboot();
 }
 
 #[no_mangle]
 extern "C" fn sync_lower(_elr: u64, _spsr: u64) {
-    let esr = read_esr();
+    let esr = read_sysreg!("esr_el1");
     emergency_write_str("sync_lower\n");
-    print_esr(esr);
+    eprintln!("esr={esr:#08x}");
     reboot();
 }
 
@@ -76,31 +75,8 @@
 
 #[no_mangle]
 extern "C" fn serr_lower(_elr: u64, _spsr: u64) {
-    let esr = read_esr();
+    let esr = read_sysreg!("esr_el1");
     emergency_write_str("serr_lower\n");
-    print_esr(esr);
+    eprintln!("esr={esr:#08x}");
     reboot();
 }
-
-#[inline]
-fn read_esr() -> u64 {
-    let mut esr: u64;
-    unsafe {
-        asm!("mrs {esr}, esr_el1", esr = out(reg) esr);
-    }
-    esr
-}
-
-#[inline]
-fn print_esr(esr: u64) {
-    eprintln!("esr={:#08x}", esr);
-}
-
-#[inline]
-fn read_far() -> u64 {
-    let mut far: u64;
-    unsafe {
-        asm!("mrs {far}, far_el1", far = out(reg) far);
-    }
-    far
-}
diff --git a/pvmfw/src/helpers.rs b/pvmfw/src/helpers.rs
index 4df9386..6310826 100644
--- a/pvmfw/src/helpers.rs
+++ b/pvmfw/src/helpers.rs
@@ -22,6 +22,39 @@
 
 pub const GUEST_PAGE_SIZE: usize = SIZE_4KB;
 
+/// Read a value from a system register.
+#[macro_export]
+macro_rules! read_sysreg {
+    ($sysreg:literal) => {{
+        let mut r: usize;
+        // Safe because it reads a system register and does not affect Rust.
+        unsafe {
+            core::arch::asm!(
+                concat!("mrs {}, ", $sysreg),
+                out(reg) r,
+                options(nomem, nostack, preserves_flags),
+            )
+        }
+        r
+    }};
+}
+
+/// Write a value to a system register.
+#[macro_export]
+macro_rules! write_sysreg {
+    ($sysreg:literal, $val:expr) => {{
+        let value: usize = $val;
+        // Safe because it writes a system register and does not affect Rust.
+        unsafe {
+            core::arch::asm!(
+                concat!("msr ", $sysreg, ", {}"),
+                in(reg) value,
+                options(nomem, nostack, preserves_flags),
+            )
+        }
+    }};
+}
+
 /// Computes the largest multiple of the provided alignment smaller or equal to the address.
 ///
 /// Note: the result is undefined if alignment isn't a power of two.
@@ -78,9 +111,7 @@
 fn min_dcache_line_size() -> usize {
     const DMINLINE_SHIFT: usize = 16;
     const DMINLINE_MASK: usize = 0xf;
-    let ctr_el0: usize;
-
-    unsafe { asm!("mrs {x}, ctr_el0", x = out(reg) ctr_el0) }
+    let ctr_el0 = read_sysreg!("ctr_el0");
 
     // DminLine: log2 of the number of words in the smallest cache line of all the data caches.
     let dminline = (ctr_el0 >> DMINLINE_SHIFT) & DMINLINE_MASK;
@@ -97,7 +128,13 @@
 
     for line in (start..end).step_by(line_size) {
         // SAFETY - Clearing cache lines shouldn't have Rust-visible side effects.
-        unsafe { asm!("dc cvau, {x}", x = in(reg) line) }
+        unsafe {
+            asm!(
+                "dc cvau, {x}",
+                x = in(reg) line,
+                options(nomem, nostack, preserves_flags),
+            )
+        }
     }
 }
 
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 9851a17..c210ea6 100644
--- a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
+++ b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
@@ -45,6 +45,7 @@
 import com.android.microdroid.testservice.IBenchmarkService;
 import com.android.microdroid.testservice.ITestService;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -54,6 +55,7 @@
 
 import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -97,6 +99,19 @@
 
     private Instrumentation mInstrumentation;
 
+    private boolean mTeardownDebugfs;
+
+    private void setupDebugfs() throws IOException {
+        BufferedReader reader = new BufferedReader(new FileReader("/proc/mounts"));
+
+        mTeardownDebugfs =
+                !reader.lines().filter(line -> line.startsWith("debugfs ")).findAny().isPresent();
+
+        if (mTeardownDebugfs) {
+            executeCommand("mount -t debugfs none /sys/kernel/debug");
+        }
+    }
+
     @Before
     public void setup() throws IOException {
         grantPermission(VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION);
@@ -106,6 +121,13 @@
         mInstrumentation = getInstrumentation();
     }
 
+    @After
+    public void tearDown() throws IOException {
+        if (mTeardownDebugfs) {
+            executeCommand("umount /sys/kernel/debug");
+        }
+    }
+
     private boolean canBootMicrodroidWithMemory(int mem)
             throws VirtualMachineException, InterruptedException, IOException {
         VirtualMachineConfig normalConfig =
@@ -346,17 +368,15 @@
         public final long mGuestRss;
         public final long mGuestPss;
 
-        CrosvmStats(Function<String, String> shellExecutor) {
+        CrosvmStats(int vmPid, Function<String, String> shellExecutor) {
             try {
-                int crosvmPid = ProcessUtil.getCrosvmPid(Os.getpid(), shellExecutor);
-
                 long hostRss = 0;
                 long hostPss = 0;
                 long guestRss = 0;
                 long guestPss = 0;
                 boolean hasGuestMaps = false;
                 for (ProcessUtil.SMapEntry entry :
-                        ProcessUtil.getProcessSmaps(crosvmPid, shellExecutor)) {
+                        ProcessUtil.getProcessSmaps(vmPid, shellExecutor)) {
                     long rss = entry.metrics.get("Rss");
                     long pss = entry.metrics.get("Pss");
                     if (entry.name.contains("crosvm_guest")) {
@@ -383,6 +403,54 @@
         }
     }
 
+    private static class KvmVmStats {
+        public final long mProtectedHyp;
+        public final long mProtectedShared;
+        private final Function<String, String> mShellExecutor;
+        private static final String KVM_STATS_FS = "/sys/kernel/debug/kvm";
+
+        public static KvmVmStats createIfSupported(
+                int vmPid, Function<String, String> shellExecutor) {
+
+            if (!new File(KVM_STATS_FS + "/protected_hyp_mem").exists()) {
+                return null;
+            }
+
+            return new KvmVmStats(vmPid, shellExecutor);
+        }
+
+        KvmVmStats(int vmPid, Function<String, String> shellExecutor) {
+            mShellExecutor = shellExecutor;
+
+            try {
+                String dir = getKvmVmStatDir(vmPid);
+
+                mProtectedHyp = getKvmVmStat(dir, "protected_hyp_mem");
+                mProtectedShared = getKvmVmStat(dir, "protected_shared_mem");
+
+            } catch (Exception e) {
+                Log.e(TAG, "Error inside onPayloadReady():" + e);
+                throw new RuntimeException(e);
+            }
+        }
+
+        private String getKvmVmStatDir(int vmPid) {
+            String output = mShellExecutor.apply("find " + KVM_STATS_FS + " -type d");
+
+            for (String line : output.split("\n")) {
+                if (line.startsWith(KVM_STATS_FS + "/" + Integer.toString(vmPid) + "-")) {
+                    return line;
+                }
+            }
+
+            throw new IllegalStateException("KVM stat folder for PID " + vmPid + " not found");
+        }
+
+        private int getKvmVmStat(String dir, String name) throws IOException {
+            return Integer.parseInt(mShellExecutor.apply("cat " + dir + "/" + name).trim());
+        }
+    }
+
     @Test
     public void testMemoryUsage() throws Exception {
         final String vmName = "test_vm_mem_usage";
@@ -394,6 +462,9 @@
                         .build();
         VirtualMachine vm = forceCreateNewVirtualMachine(vmName, config);
         MemoryUsageListener listener = new MemoryUsageListener(this::executeCommand);
+
+        setupDebugfs();
+
         BenchmarkVmListener.create(listener).runToFinish(TAG, vm);
 
         double mem_overall = 256.0;
@@ -423,6 +494,12 @@
         bundle.putDouble(METRIC_NAME_PREFIX + "mem_crosvm_host_pss_MB", mem_crosvm_host_pss);
         bundle.putDouble(METRIC_NAME_PREFIX + "mem_crosvm_guest_rss_MB", mem_crosvm_guest_rss);
         bundle.putDouble(METRIC_NAME_PREFIX + "mem_crosvm_guest_pss_MB", mem_crosvm_guest_pss);
+        if (listener.mKvm != null) {
+            double mem_protected_shared = (double) listener.mKvm.mProtectedShared / 1048576.0;
+            double mem_protected_hyp = (double) listener.mKvm.mProtectedHyp / 1048576.0;
+            bundle.putDouble(METRIC_NAME_PREFIX + "mem_protected_shared_MB", mem_protected_shared);
+            bundle.putDouble(METRIC_NAME_PREFIX + "mem_protected_hyp_MB", mem_protected_hyp);
+        }
         mInstrumentation.sendStatus(0, bundle);
     }
 
@@ -441,17 +518,21 @@
         public long mSlab;
 
         public CrosvmStats mCrosvm;
+        public KvmVmStats mKvm;
 
         @Override
         public void onPayloadReady(VirtualMachine vm, IBenchmarkService service)
                 throws RemoteException {
+            int vmPid = ProcessUtil.getCrosvmPid(Os.getpid(), mShellExecutor);
+
             mMemTotal = service.getMemInfoEntry("MemTotal");
             mMemFree = service.getMemInfoEntry("MemFree");
             mMemAvailable = service.getMemInfoEntry("MemAvailable");
             mBuffers = service.getMemInfoEntry("Buffers");
             mCached = service.getMemInfoEntry("Cached");
             mSlab = service.getMemInfoEntry("Slab");
-            mCrosvm = new CrosvmStats(mShellExecutor);
+            mCrosvm = new CrosvmStats(vmPid, mShellExecutor);
+            mKvm = KvmVmStats.createIfSupported(vmPid, mShellExecutor);
         }
     }
 
@@ -511,10 +592,12 @@
         @SuppressWarnings("ReturnValueIgnored")
         public void onPayloadReady(VirtualMachine vm, IBenchmarkService service)
                 throws RemoteException {
+            int vmPid = ProcessUtil.getCrosvmPid(Os.getpid(), mShellExecutor);
+
             // Allocate 256MB of anonymous memory. This will fill all guest
             // memory and cause swapping to start.
             service.allocAnonMemory(256);
-            mPreCrosvm = new CrosvmStats(mShellExecutor);
+            mPreCrosvm = new CrosvmStats(vmPid, mShellExecutor);
             // Send a memory trim hint to cause memory reclaim.
             mShellExecutor.apply("am send-trim-memory " + Process.myPid() + " RUNNING_CRITICAL");
             // Give time for the memory reclaim to do its work.
@@ -524,7 +607,7 @@
                 Log.e(TAG, "Interrupted sleep:" + e);
                 Thread.currentThread().interrupt();
             }
-            mPostCrosvm = new CrosvmStats(mShellExecutor);
+            mPostCrosvm = new CrosvmStats(vmPid, mShellExecutor);
         }
     }
 
diff --git a/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java b/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
index 9c8714f..8c6218c 100644
--- a/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
+++ b/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
@@ -85,9 +85,14 @@
 
     private boolean mNeedTearDown = false;
 
+    private boolean mNeedToRestartPkvmStatus = false;
+
     @Before
     public void setUp() throws Exception {
-        testIfDeviceIsCapable(getDevice());
+        mNeedTearDown = false;
+        mNeedToRestartPkvmStatus = false;
+
+        assumeDeviceIsCapable(getDevice());
         mNeedTearDown = true;
 
         getDevice().installPackage(findTestFile(APK_NAME), /* reinstall */ false);
@@ -99,12 +104,12 @@
     public void tearDown() throws Exception {
         if (!mNeedTearDown) {
             // If we skipped setUp, we don't need to undo it, and that avoids potential exceptions
-            // incompatible hardware. (Note that tests can change what testIfDeviceIsCapable()
+            // incompatible hardware. (Note that tests can change what assumeDeviceIsCapable()
             // sees, so we can't rely on that - b/268688303.)
             return;
         }
-        // Set PKVM enable and reboot to prevent previous staged session.
-        if (!isCuttlefish()) {
+        // Restore PKVM status and reboot to prevent previous staged session, if switched.
+        if (mNeedToRestartPkvmStatus) {
             setPKVMStatusWithRebootToBootloader(true);
             rebootFromBootloaderAndWaitBootCompleted();
         }
@@ -422,7 +427,7 @@
     }
 
     private void enableDisablePKVMTestHelper(boolean isEnable) throws Exception {
-        skipIfPKVMStatusSwitchNotSupported();
+        assumePKVMStatusSwitchSupported();
 
         List<Double> bootDmesgTime = new ArrayList<>(ROUND_COUNT);
         Map<String, List<Double>> bootloaderTime = new HashMap<>();
@@ -478,9 +483,15 @@
         reportMetric(bootDmesgTime, "dmesg_boot_time_" + suffix, "s");
     }
 
-    private void skipIfPKVMStatusSwitchNotSupported() throws Exception {
+    private void assumePKVMStatusSwitchSupported() throws Exception {
         assumeFalse("Skip on CF; can't reboot to bootloader", isCuttlefish());
 
+        // This is an overkill. The intention is to exclude remote_device_proxy, which uses
+        // different serial for fastboot. But there's no good way to distinguish from regular IP
+        // transport. This is currently not a problem until someone really needs to run the test
+        // over regular IP transport.
+        assumeFalse("Skip over IP (overkill for remote_device_proxy)", getDevice().isAdbTcp());
+
         if (!getDevice().isStateBootloaderOrFastbootd()) {
             getDevice().rebootIntoBootloader();
         }
@@ -513,6 +524,7 @@
     }
 
     private void setPKVMStatusWithRebootToBootloader(boolean isEnable) throws Exception {
+        mNeedToRestartPkvmStatus = true;
 
         if (!getDevice().isStateBootloaderOrFastbootd()) {
             getDevice().rebootIntoBootloader();
diff --git a/tests/hostside/Android.bp b/tests/hostside/Android.bp
index 4b5cbda..248755f 100644
--- a/tests/hostside/Android.bp
+++ b/tests/hostside/Android.bp
@@ -9,20 +9,6 @@
 }
 
 genrule {
-    name: "test_avf_debug_policy_with_log.dtbo",
-    defaults: ["test_avf_debug_policy_overlay"],
-    srcs: ["assets/avf_debug_policy_with_log.dts"],
-    out: ["avf_debug_policy_with_log.dtbo"],
-}
-
-genrule {
-    name: "test_avf_debug_policy_without_log.dtbo",
-    defaults: ["test_avf_debug_policy_overlay"],
-    srcs: ["assets/avf_debug_policy_without_log.dts"],
-    out: ["avf_debug_policy_without_log.dtbo"],
-}
-
-genrule {
     name: "test_avf_debug_policy_with_adb",
     defaults: ["test_avf_debug_policy_overlay"],
     srcs: ["assets/avf_debug_policy_with_adb.dts"],
@@ -60,8 +46,6 @@
         ":test.com.android.virt.pem",
         ":test2.com.android.virt.pem",
         ":pvmfw_test",
-        ":test_avf_debug_policy_with_log.dtbo",
-        ":test_avf_debug_policy_without_log.dtbo",
         ":test_avf_debug_policy_with_adb",
         ":test_avf_debug_policy_without_adb",
         "assets/bcc.dat",
diff --git a/tests/hostside/assets/avf_debug_policy_with_log.dts b/tests/hostside/assets/avf_debug_policy_with_log.dts
deleted file mode 100644
index 8cf19d6..0000000
--- a/tests/hostside/assets/avf_debug_policy_with_log.dts
+++ /dev/null
@@ -1,18 +0,0 @@
-/dts-v1/;
-/plugin/;
-
-/ {
-    fragment@avf {
-        target-path = "/";
-
-        __overlay__ {
-            avf {
-                guest {
-                    common {
-                        log = <1>;
-                    };
-                };
-            };
-        };
-    };
-};
\ No newline at end of file
diff --git a/tests/hostside/assets/avf_debug_policy_without_log.dts b/tests/hostside/assets/avf_debug_policy_without_log.dts
deleted file mode 100644
index da6400c..0000000
--- a/tests/hostside/assets/avf_debug_policy_without_log.dts
+++ /dev/null
@@ -1,18 +0,0 @@
-/dts-v1/;
-/plugin/;
-
-/ {
-    fragment@avf {
-        target-path = "/";
-
-        __overlay__ {
-            avf {
-                guest {
-                    common {
-                        log = <0>;
-                    };
-                };
-            };
-        };
-    };
-};
\ No newline at end of file
diff --git a/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java b/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
index a7f7906..4807c0f 100644
--- a/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
+++ b/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
@@ -97,7 +97,7 @@
                 DeviceProperties.create(getDevice()::getProperty).getMetricsTag());
     }
 
-    public static void testIfDeviceIsCapable(ITestDevice androidDevice) throws Exception {
+    public static void assumeDeviceIsCapable(ITestDevice androidDevice) throws Exception {
         assumeTrue("Need an actual TestDevice", androidDevice instanceof TestDevice);
         TestDevice testDevice = (TestDevice) androidDevice;
         assumeTrue(
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index 687756e..dabb31d 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -24,7 +24,6 @@
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
@@ -82,6 +81,7 @@
 import java.util.function.Function;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class MicrodroidHostTests extends MicrodroidHostTestCaseBase {
@@ -682,19 +682,24 @@
         microdroid.waitForBootComplete(BOOT_COMPLETE_TIMEOUT);
         device.shutdownMicrodroid(microdroid);
 
+        // Try to collect atoms for 60000 milliseconds.
         List<StatsLog.EventMetricData> data = new ArrayList<>();
-        assertThatEventually(
-                10000,
-                () -> {
-                    data.addAll(ReportUtils.getEventMetricDataList(getDevice()));
-                    return data.size();
-                },
-                is(3)
-        );
+        long start = System.currentTimeMillis();
+        while ((System.currentTimeMillis() - start < 60000) && data.size() < 3) {
+            data.addAll(ReportUtils.getEventMetricDataList(getDevice()));
+            Thread.sleep(500);
+        }
+        assertThat(
+                        data.stream()
+                                .map(x -> x.getAtom().getPushedCase().getNumber())
+                                .collect(Collectors.toList()))
+                .containsExactly(
+                        AtomsProto.Atom.VM_CREATION_REQUESTED_FIELD_NUMBER,
+                        AtomsProto.Atom.VM_BOOTED_FIELD_NUMBER,
+                        AtomsProto.Atom.VM_EXITED_FIELD_NUMBER)
+                .inOrder();
 
         // Check VmCreationRequested atom
-        assertThat(data.get(0).getAtom().getPushedCase().getNumber()).isEqualTo(
-                AtomsProto.Atom.VM_CREATION_REQUESTED_FIELD_NUMBER);
         AtomsProto.VmCreationRequested atomVmCreationRequested =
                 data.get(0).getAtom().getVmCreationRequested();
         assertThat(atomVmCreationRequested.getHypervisor())
@@ -711,14 +716,10 @@
                 .isEqualTo("com.android.art:com.android.compos:com.android.sdkext");
 
         // Check VmBooted atom
-        assertThat(data.get(1).getAtom().getPushedCase().getNumber())
-                .isEqualTo(AtomsProto.Atom.VM_BOOTED_FIELD_NUMBER);
         AtomsProto.VmBooted atomVmBooted = data.get(1).getAtom().getVmBooted();
         assertThat(atomVmBooted.getVmIdentifier()).isEqualTo("VmRunApp");
 
         // Check VmExited atom
-        assertThat(data.get(2).getAtom().getPushedCase().getNumber())
-                .isEqualTo(AtomsProto.Atom.VM_EXITED_FIELD_NUMBER);
         AtomsProto.VmExited atomVmExited = data.get(2).getAtom().getVmExited();
         assertThat(atomVmExited.getVmIdentifier()).isEqualTo("VmRunApp");
         assertThat(atomVmExited.getDeathReason()).isEqualTo(AtomsProto.VmExited.DeathReason.KILLED);
@@ -927,6 +928,21 @@
         checkHashAlgorithm(virtApexEtcDir);
     }
 
+    @Test
+    @CddTest
+    public void testNoAvfDebugPolicyInLockedDevice() throws Exception {
+        ITestDevice device = getDevice();
+
+        // Check device's locked state with ro.boot.verifiedbootstate. ro.boot.flash.locked
+        // may not be set if ro.oem_unlock_supported is false.
+        String lockProp = device.getProperty("ro.boot.verifiedbootstate");
+        assumeFalse("Unlocked devices may have AVF debug policy", lockProp.equals("orange"));
+
+        // Test that AVF debug policy doesn't exist.
+        boolean hasDebugPolicy = device.doesFileExist("/sys/firmware/devicetree/base/avf");
+        assertThat(hasDebugPolicy).isFalse();
+    }
+
     private String avbInfo(String image_path) throws Exception {
         File avbtool = findTestFile("avbtool");
         List<String> command =
@@ -970,7 +986,7 @@
 
     @Before
     public void setUp() throws Exception {
-        testIfDeviceIsCapable(getDevice());
+        assumeDeviceIsCapable(getDevice());
         mMetricPrefix = getMetricPrefix() + "microdroid/";
         mMicrodroidDevice = null;
 
diff --git a/tests/hostside/java/com/android/microdroid/test/PvmfwDebugPolicyHostTests.java b/tests/hostside/java/com/android/microdroid/test/PvmfwDebugPolicyHostTests.java
index 18aa273..0f6d095 100644
--- a/tests/hostside/java/com/android/microdroid/test/PvmfwDebugPolicyHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/PvmfwDebugPolicyHostTests.java
@@ -97,6 +97,9 @@
     @Before
     public void setUp() throws Exception {
         mAndroidDevice = (TestDevice) Objects.requireNonNull(getDevice());
+
+        // Check device capabilities
+        assumeDeviceIsCapable(mAndroidDevice);
         assumeTrue(
                 "Skip if protected VMs are not supported",
                 mAndroidDevice.supportsMicrodroid(/* protectedVm= */ true));
@@ -111,12 +114,6 @@
         mBccFileOnHost =
                 getTestInformation().getDependencyFile(BCC_FILE_NAME, /* targetFirst= */ false);
 
-        // Check device capability
-        testIfDeviceIsCapable(mAndroidDevice);
-        assumeTrue(
-                "Protected VMs are not supported",
-                mAndroidDevice.supportsMicrodroid(/*protectedVm=*/ true));
-
         // Prepare for loading pvmfw.bin
         // File will be setup in individual test,
         // and then pushed to device in launchProtectedVmAndWaitForBootCompleted.
@@ -151,56 +148,6 @@
     }
 
     @Test
-    public void testLog_consoleOutput() throws Exception {
-        Pvmfw pvmfw = createPvmfw("avf_debug_policy_with_log.dtbo");
-        pvmfw.serialize(mCustomPvmfwBinFileOnHost);
-
-        CommandResult result = tryLaunchProtectedNonDebuggableVm();
-
-        assertWithMessage("Microdroid's console message should have been enabled")
-                .that(hasConsoleOutput(result))
-                .isTrue();
-    }
-
-    @Test
-    public void testLog_logcat() throws Exception {
-        Pvmfw pvmfw = createPvmfw("avf_debug_policy_with_log.dtbo");
-        pvmfw.serialize(mCustomPvmfwBinFileOnHost);
-
-        tryLaunchProtectedNonDebuggableVm();
-
-        assertWithMessage("Microdroid's logcat should have been enabled")
-                .that(hasMicrodroidLogcatOutput())
-                .isTrue();
-    }
-
-    @Test
-    public void testNoLog_noConsoleOutput() throws Exception {
-        Pvmfw pvmfw = createPvmfw("avf_debug_policy_without_log.dtbo");
-        pvmfw.serialize(mCustomPvmfwBinFileOnHost);
-
-        CommandResult result = tryLaunchProtectedNonDebuggableVm();
-
-        assertWithMessage("Microdroid's console message shouldn't have been disabled")
-                .that(hasConsoleOutput(result))
-                .isFalse();
-    }
-
-    @Test
-    public void testNoLog_noLogcat() throws Exception {
-        Pvmfw pvmfw = createPvmfw("avf_debug_policy_without_log.dtbo");
-        pvmfw.serialize(mCustomPvmfwBinFileOnHost);
-
-        assertThrows(
-                "Microdroid shouldn't be recognized because of missing adb connection",
-                DeviceRuntimeException.class,
-                () ->
-                        launchProtectedVmAndWaitForBootCompleted(
-                                MICRODROID_DEBUG_NONE, BOOT_FAILURE_WAIT_TIME_MS));
-        assertThat(hasMicrodroidLogcatOutput()).isFalse();
-    }
-
-    @Test
     public void testAdb_boots() throws Exception {
         assumeTrue(
                 "Skip if host wouldn't install adbd",
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index 749d75f..468ee19 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -19,8 +19,7 @@
     write_vm_booted_stats, write_vm_creation_stats};
 use crate::composite::make_composite_image;
 use crate::crosvm::{CrosvmConfig, DiskFile, PayloadState, VmContext, VmInstance, VmState};
-use crate::debug_config::should_prepare_console_output;
-use crate::debug_config::is_ramdump_needed;
+use crate::debug_config::DebugConfig;
 use crate::payload::{add_microdroid_payload_images, add_microdroid_system_images};
 use crate::selinux::{getfilecon, SeContext};
 use android_os_permissions_aidl::aidl::android::os::IPermissionController;
@@ -327,21 +326,22 @@
             check_gdb_allowed(config)?;
         }
 
-        let ramdump = if is_ramdump_needed(config) {
+        let debug_level = match config {
+            VirtualMachineConfig::AppConfig(config) => config.debugLevel,
+            _ => DebugLevel::NONE,
+        };
+        let debug_config = DebugConfig::new(debug_level);
+
+        let ramdump = if debug_config.is_ramdump_needed() {
             Some(prepare_ramdump_file(&temporary_directory)?)
         } else {
             None
         };
 
-        let debug_level = match config {
-            VirtualMachineConfig::AppConfig(app_config) => app_config.debugLevel,
-            _ => DebugLevel::NONE,
-        };
-
         let state = &mut *self.state.lock().unwrap();
         let console_fd =
-            clone_or_prepare_logger_fd(config, console_fd, format!("Console({})", cid))?;
-        let log_fd = clone_or_prepare_logger_fd(config, log_fd, format!("Log({})", cid))?;
+            clone_or_prepare_logger_fd(&debug_config, console_fd, format!("Console({})", cid))?;
+        let log_fd = clone_or_prepare_logger_fd(&debug_config, log_fd, format!("Log({})", cid))?;
 
         // Counter to generate unique IDs for temporary image files.
         let mut next_temporary_image_id = 0;
@@ -352,12 +352,13 @@
         let (is_app_config, config) = match config {
             VirtualMachineConfig::RawConfig(config) => (false, BorrowedOrOwned::Borrowed(config)),
             VirtualMachineConfig::AppConfig(config) => {
-                let config = load_app_config(config, &temporary_directory).map_err(|e| {
-                    *is_protected = config.protectedVm;
-                    let message = format!("Failed to load app config: {:?}", e);
-                    error!("{}", message);
-                    Status::new_service_specific_error_str(-1, Some(message))
-                })?;
+                let config =
+                    load_app_config(config, &debug_config, &temporary_directory).map_err(|e| {
+                        *is_protected = config.protectedVm;
+                        let message = format!("Failed to load app config: {:?}", e);
+                        error!("{}", message);
+                        Status::new_service_specific_error_str(-1, Some(message))
+                    })?;
                 (true, BorrowedOrOwned::Owned(config))
             }
         };
@@ -438,7 +439,7 @@
             disks,
             params: config.params.to_owned(),
             protected: *is_protected,
-            debug_level,
+            debug_config,
             memory_mib: config.memoryMib.try_into().ok().and_then(NonZeroU32::new),
             cpus,
             host_cpu_topology,
@@ -559,6 +560,7 @@
 
 fn load_app_config(
     config: &VirtualMachineAppConfig,
+    debug_config: &DebugConfig,
     temporary_directory: &Path,
 ) -> Result<VirtualMachineRawConfig> {
     let apk_file = clone_file(config.apk.as_ref().unwrap())?;
@@ -607,6 +609,7 @@
     // Include Microdroid payload disk (contains apks, idsigs) in vm config
     add_microdroid_payload_images(
         config,
+        debug_config,
         temporary_directory,
         apk_file,
         idsig_file,
@@ -1038,7 +1041,7 @@
 }
 
 fn clone_or_prepare_logger_fd(
-    config: &VirtualMachineConfig,
+    debug_config: &DebugConfig,
     fd: Option<&ParcelFileDescriptor>,
     tag: String,
 ) -> Result<Option<File>, Status> {
@@ -1046,10 +1049,7 @@
         return Ok(Some(clone_file(fd)?));
     }
 
-    let VirtualMachineConfig::AppConfig(app_config) = config else {
-        return Ok(None);
-    };
-    if !should_prepare_console_output(app_config.debugLevel) {
+    if !debug_config.should_prepare_console_output() {
         return Ok(None);
     };
 
diff --git a/virtualizationmanager/src/atom.rs b/virtualizationmanager/src/atom.rs
index 567fce9..02d46ec 100644
--- a/virtualizationmanager/src/atom.rs
+++ b/virtualizationmanager/src/atom.rs
@@ -32,7 +32,7 @@
 };
 use anyhow::{anyhow, Result};
 use binder::ParcelFileDescriptor;
-use log::warn;
+use log::{info, warn};
 use microdroid_payload_config::VmPayloadConfig;
 use statslog_virtualization_rust::vm_creation_requested;
 use std::thread;
@@ -149,6 +149,7 @@
         apexes,
     };
 
+    info!("Writing VmCreationRequested atom into statsd.");
     thread::spawn(move || {
         GLOBAL_SERVICE.atomVmCreationRequested(&atom).unwrap_or_else(|e| {
             warn!("Failed to write VmCreationRequested atom: {e}");
@@ -172,9 +173,10 @@
         elapsedTimeMillis: duration.as_millis() as i64,
     };
 
+    info!("Writing VmBooted atom into statsd.");
     thread::spawn(move || {
         GLOBAL_SERVICE.atomVmBooted(&atom).unwrap_or_else(|e| {
-            warn!("Failed to write VmCreationRequested atom: {e}");
+            warn!("Failed to write VmBooted atom: {e}");
         });
     });
 }
@@ -204,6 +206,7 @@
         exitSignal: exit_signal.unwrap_or_default(),
     };
 
+    info!("Writing VmExited atom into statsd.");
     thread::spawn(move || {
         GLOBAL_SERVICE.atomVmExited(&atom).unwrap_or_else(|e| {
             warn!("Failed to write VmExited atom: {e}");
diff --git a/virtualizationmanager/src/crosvm.rs b/virtualizationmanager/src/crosvm.rs
index 7201670..a8cad94 100644
--- a/virtualizationmanager/src/crosvm.rs
+++ b/virtualizationmanager/src/crosvm.rs
@@ -16,7 +16,7 @@
 
 use crate::aidl::{remove_temporary_files, Cid, VirtualMachineCallbacks};
 use crate::atom::{get_num_cpus, write_vm_exited_stats};
-use crate::debug_config::should_prepare_console_output;
+use crate::debug_config::DebugConfig;
 use anyhow::{anyhow, bail, Context, Error, Result};
 use command_fds::CommandFdExt;
 use lazy_static::lazy_static;
@@ -102,7 +102,7 @@
     pub disks: Vec<DiskFile>,
     pub params: Option<String>,
     pub protected: bool,
-    pub debug_level: DebugLevel,
+    pub debug_config: DebugConfig,
     pub memory_mib: Option<NonZeroU32>,
     pub cpus: Option<NonZeroU32>,
     pub host_cpu_topology: bool,
@@ -732,18 +732,15 @@
             let ramdump_reserve = RAMDUMP_RESERVED_MIB + swiotlb_size_mib;
             command.arg("--params").arg(format!("crashkernel={ramdump_reserve}M"));
         }
-    } else {
-        if config.ramdump.is_some() {
-            command.arg("--params").arg(format!("crashkernel={RAMDUMP_RESERVED_MIB}M"));
-        }
-        if config.debug_level == DebugLevel::NONE
-            && should_prepare_console_output(config.debug_level)
-        {
-            // bootconfig.normal will be used, but we need log.
-            // pvmfw will add following commands by itself, but non-protected VM should do so here.
-            command.arg("--params").arg("printk.devkmsg=on");
-            command.arg("--params").arg("console=hvc0");
-        }
+    } else if config.ramdump.is_some() {
+        command.arg("--params").arg(format!("crashkernel={RAMDUMP_RESERVED_MIB}M"));
+    }
+    if config.debug_config.debug_level == DebugLevel::NONE
+        && config.debug_config.should_prepare_console_output()
+    {
+        // bootconfig.normal will be used, but we need log.
+        command.arg("--params").arg("printk.devkmsg=on");
+        command.arg("--params").arg("console=hvc0");
     }
 
     if let Some(memory_mib) = config.memory_mib {
diff --git a/virtualizationmanager/src/debug_config.rs b/virtualizationmanager/src/debug_config.rs
index ec3d591..e8863c7 100644
--- a/virtualizationmanager/src/debug_config.rs
+++ b/virtualizationmanager/src/debug_config.rs
@@ -15,10 +15,27 @@
 //! Functions for AVF debug policy and debug level
 
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
-    VirtualMachineAppConfig::DebugLevel::DebugLevel, VirtualMachineConfig::VirtualMachineConfig,
+    VirtualMachineAppConfig::DebugLevel::DebugLevel,
 };
 use std::fs::File;
 use std::io::Read;
+use log::info;
+use rustutils::system_properties;
+
+const DEBUG_POLICY_LOG_PATH: &str = "/proc/device-tree/avf/guest/common/log";
+const DEBUG_POLICY_RAMDUMP_PATH: &str = "/proc/device-tree/avf/guest/common/ramdump";
+const DEBUG_POLICY_ADB_PATH: &str = "/proc/device-tree/avf/guest/microdroid/adb";
+
+const SYSPROP_CUSTOM_DEBUG_POLICY_PATH: &str = "hypervisor.virtualizationmanager.debug_policy.path";
+
+/// Debug configurations for both debug level and debug policy
+#[derive(Debug)]
+pub struct DebugConfig {
+    pub debug_level: DebugLevel,
+    debug_policy_log: bool,
+    debug_policy_ramdump: bool,
+    debug_policy_adb: bool,
+}
 
 /// Get debug policy value in bool. It's true iff the value is explicitly set to <1>.
 fn get_debug_policy_bool(path: &'static str) -> Option<bool> {
@@ -29,28 +46,49 @@
     Some(u32::from_be_bytes(log) == 1)
 }
 
-/// Get whether console output should be configred for VM to leave console and adb log.
-/// Caller should create pipe and prepare for receiving VM log with it.
-pub fn should_prepare_console_output(debug_level: DebugLevel) -> bool {
-    debug_level != DebugLevel::NONE
-        || get_debug_policy_bool("/proc/device-tree/avf/guest/common/log").unwrap_or_default()
-        || get_debug_policy_bool("/proc/device-tree/avf/guest/microdroid/adb").unwrap_or_default()
-}
+impl DebugConfig {
+    pub fn new(debug_level: DebugLevel) -> Self {
+        match system_properties::read(SYSPROP_CUSTOM_DEBUG_POLICY_PATH).unwrap_or_default() {
+            Some(debug_policy_path) if !debug_policy_path.is_empty() => {
+                // TODO: Read debug policy file and override log, adb, ramdump for testing.
+                info!("Debug policy is disabled by sysprop");
+                Self {
+                    debug_level,
+                    debug_policy_log: false,
+                    debug_policy_ramdump: false,
+                    debug_policy_adb: false,
+                }
+            }
+            _ => {
+                let debug_config = Self {
+                    debug_level,
+                    debug_policy_log: get_debug_policy_bool(DEBUG_POLICY_LOG_PATH)
+                        .unwrap_or_default(),
+                    debug_policy_ramdump: get_debug_policy_bool(DEBUG_POLICY_RAMDUMP_PATH)
+                        .unwrap_or_default(),
+                    debug_policy_adb: get_debug_policy_bool(DEBUG_POLICY_ADB_PATH)
+                        .unwrap_or_default(),
+                };
+                info!("Loaded debug policy from host OS: {:?}", debug_config);
 
-/// Get whether debug apexes (MICRODROID_REQUIRED_APEXES_DEBUG) are required.
-pub fn should_include_debug_apexes(debug_level: DebugLevel) -> bool {
-    debug_level != DebugLevel::NONE
-        || get_debug_policy_bool("/proc/device-tree/avf/guest/microdroid/adb").unwrap_or_default()
-}
+                debug_config
+            }
+        }
+    }
 
-/// Decision to support ramdump
-pub fn is_ramdump_needed(config: &VirtualMachineConfig) -> bool {
-    let enabled_in_dp =
-        get_debug_policy_bool("/proc/device-tree/avf/guest/common/ramdump").unwrap_or_default();
-    let debuggable = match config {
-        VirtualMachineConfig::RawConfig(_) => false,
-        VirtualMachineConfig::AppConfig(config) => config.debugLevel == DebugLevel::FULL,
-    };
+    /// Get whether console output should be configred for VM to leave console and adb log.
+    /// Caller should create pipe and prepare for receiving VM log with it.
+    pub fn should_prepare_console_output(&self) -> bool {
+        self.debug_level != DebugLevel::NONE || self.debug_policy_log || self.debug_policy_adb
+    }
 
-    enabled_in_dp || debuggable
+    /// Get whether debug apexes (MICRODROID_REQUIRED_APEXES_DEBUG) are required.
+    pub fn should_include_debug_apexes(&self) -> bool {
+        self.debug_level != DebugLevel::NONE || self.debug_policy_adb
+    }
+
+    /// Decision to support ramdump
+    pub fn is_ramdump_needed(&self) -> bool {
+        self.debug_level != DebugLevel::NONE || self.debug_policy_ramdump
+    }
 }
diff --git a/virtualizationmanager/src/payload.rs b/virtualizationmanager/src/payload.rs
index 99aea01..33659d4 100644
--- a/virtualizationmanager/src/payload.rs
+++ b/virtualizationmanager/src/payload.rs
@@ -14,7 +14,7 @@
 
 //! Payload disk image
 
-use crate::debug_config::should_include_debug_apexes;
+use crate::debug_config::DebugConfig;
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
     DiskImage::DiskImage,
     Partition::Partition,
@@ -257,6 +257,7 @@
 ///   ..
 fn make_payload_disk(
     app_config: &VirtualMachineAppConfig,
+    debug_config: &DebugConfig,
     apk_file: File,
     idsig_file: File,
     vm_payload_config: &VmPayloadConfig,
@@ -274,8 +275,7 @@
     let apex_list = pm.get_apex_list(vm_payload_config.prefer_staged)?;
 
     // collect APEXes from config
-    let mut apex_infos =
-        collect_apex_infos(&apex_list, &vm_payload_config.apexes, app_config.debugLevel);
+    let mut apex_infos = collect_apex_infos(&apex_list, &vm_payload_config.apexes, debug_config);
 
     // Pass sorted list of apexes. Sorting key shouldn't use `path` because it will change after
     // reboot with prefer_staged. `last_update_seconds` is added to distinguish "samegrade"
@@ -380,10 +380,10 @@
 fn collect_apex_infos<'a>(
     apex_list: &'a ApexInfoList,
     apex_configs: &[ApexConfig],
-    debug_level: DebugLevel,
+    debug_config: &DebugConfig,
 ) -> Vec<&'a ApexInfo> {
     let mut additional_apexes: Vec<&str> = MICRODROID_REQUIRED_APEXES.to_vec();
-    if should_include_debug_apexes(debug_level) {
+    if debug_config.should_include_debug_apexes() {
         additional_apexes.extend(MICRODROID_REQUIRED_APEXES_DEBUG.to_vec());
     }
 
@@ -437,6 +437,7 @@
 
 pub fn add_microdroid_payload_images(
     config: &VirtualMachineAppConfig,
+    debug_config: &DebugConfig,
     temporary_directory: &Path,
     apk_file: File,
     idsig_file: File,
@@ -445,6 +446,7 @@
 ) -> Result<()> {
     vm_config.disks.push(make_payload_disk(
         config,
+        debug_config,
         apk_file,
         idsig_file,
         vm_payload_config,
@@ -582,7 +584,7 @@
             ApexConfig { name: "{CLASSPATH}".to_string() },
         ];
         assert_eq!(
-            collect_apex_infos(&apex_info_list, &apex_configs, DebugLevel::FULL),
+            collect_apex_infos(&apex_info_list, &apex_configs, &DebugConfig::new(DebugLevel::FULL)),
             vec![
                 // Pass active/required APEXes
                 &apex_info_list.list[0],