Merge "Re-enable checksum checks for .odex and .art"
diff --git a/apex/Android.bp b/apex/Android.bp
index b1c5190..19f5428 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -61,7 +61,9 @@
         "microdroid.json",
         "microdroid_uboot_env",
         "microdroid_bootloader",
-        "microdroid_bootconfig_debug",
+        "microdroid_bootconfig_normal",
+        "microdroid_bootconfig_app_debuggable",
+        "microdroid_bootconfig_full_debuggable",
     ],
     file_contexts: ":com.android.virt-file_contexts",
 }
diff --git a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
index 3d97ee7..f06c8f5 100644
--- a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
+++ b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
@@ -111,7 +111,7 @@
                         apkName,
                         packageName,
                         configPath,
-                        /* debug */ false,
+                        /* debug */ true,
                         /* use default memoryMib */ 0);
         adbConnectToMicrodroid(androidDevice, sCid);
 
diff --git a/compos/apk/assets/vm_config.json b/compos/apk/assets/vm_config.json
index 9c71942..3a6eff4 100644
--- a/compos/apk/assets/vm_config.json
+++ b/compos/apk/assets/vm_config.json
@@ -16,6 +16,15 @@
     },
     {
       "name": "com.android.compos"
+    },
+    {
+      "name": "{DEX2OATBOOTCLASSPATH}"
+    },
+    {
+      "name": "{BOOTCLASSPATH}"
+    },
+    {
+      "name": "{SYSTEMSERVERCLASSPATH}"
     }
   ]
 }
\ No newline at end of file
diff --git a/compos/apk/assets/vm_test_config.json b/compos/apk/assets/vm_test_config.json
index 54a0aac..22f8293 100644
--- a/compos/apk/assets/vm_test_config.json
+++ b/compos/apk/assets/vm_test_config.json
@@ -13,6 +13,15 @@
         },
         {
             "name": "com.android.compos"
+        },
+        {
+            "name": "{DEX2OATBOOTCLASSPATH}"
+        },
+        {
+            "name": "{BOOTCLASSPATH}"
+        },
+        {
+            "name": "{SYSTEMSERVERCLASSPATH}"
         }
     ]
 }
\ No newline at end of file
diff --git a/compos/common/lib.rs b/compos/common/lib.rs
index 104b8e5..0b84a28 100644
--- a/compos/common/lib.rs
+++ b/compos/common/lib.rs
@@ -33,11 +33,15 @@
 
 /// The sub-directory where we store information relating to the pending instance
 /// of CompOS (based on staged APEXes).
-pub const PENDING_DIR: &str = "pending";
+pub const PENDING_INSTANCE_DIR: &str = "pending";
 
 /// The sub-directory where we store information relating to the current instance
 /// of CompOS (based on active APEXes).
-pub const CURRENT_DIR: &str = "current";
+pub const CURRENT_INSTANCE_DIR: &str = "current";
+
+/// The sub-directory where we store information relating to the instance of CompOS used for
+/// tests.
+pub const TEST_INSTANCE_DIR: &str = "test";
 
 /// The file that holds the encrypted private key for a CompOS instance.
 pub const PRIVATE_KEY_BLOB_FILE: &str = "key.blob";
diff --git a/compos/compos_key_cmd/compos_key_cmd.cpp b/compos/compos_key_cmd/compos_key_cmd.cpp
index f495816..7bf622d 100644
--- a/compos/compos_key_cmd/compos_key_cmd.cpp
+++ b/compos/compos_key_cmd/compos_key_cmd.cpp
@@ -53,6 +53,7 @@
 using aidl::android::system::virtualizationservice::IVirtualMachine;
 using aidl::android::system::virtualizationservice::IVirtualMachineCallback;
 using aidl::android::system::virtualizationservice::PartitionType;
+using aidl::android::system::virtualizationservice::VirtualMachineAppConfig;
 using aidl::android::system::virtualizationservice::VirtualMachineConfig;
 using aidl::com::android::compos::CompOsKeyData;
 using aidl::com::android::compos::ICompOsService;
@@ -234,7 +235,7 @@
         appConfig.idsig = std::move(idsigFd);
         appConfig.instanceImage = std::move(instanceFd);
         appConfig.configPath = kConfigFilePath;
-        appConfig.debug = false; // Don't disable selinux in VM
+        appConfig.debugLevel = VirtualMachineAppConfig::DebugLevel::NONE;
         appConfig.memoryMib = 0; // Use default
 
         LOG(INFO) << "Starting VM";
diff --git a/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl b/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
index a1bb92c..3d0ad31 100644
--- a/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
+++ b/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
@@ -19,8 +19,13 @@
 import com.android.compos.FdAnnotation;
 
 interface IIsolatedCompilationService {
-    /** Run "odrefresh --force-compile" in CompOS. */
-    void runForcedCompile();
+    /**
+     * Run "odrefresh --dalvik-cache=pending-test --force-compile" in a test instance of CompOS.
+     * This compiles BCP extensions and system server, even if the system artifacts are up to date,
+     * and writes the results to a test directory to avoid disrupting any real artifacts in
+     * existence.
+     */
+    void runForcedCompileForTest();
 
     /**
      * Run dex2oat in the currently running instance of the CompOS VM. This is a simple proxy
diff --git a/compos/composd/src/instance_manager.rs b/compos/composd/src/instance_manager.rs
index 6b36ed8..e31296d 100644
--- a/compos/composd/src/instance_manager.rs
+++ b/compos/composd/src/instance_manager.rs
@@ -22,7 +22,7 @@
 use anyhow::{bail, Context, Result};
 use compos_aidl_interface::aidl::com::android::compos::ICompOsService::ICompOsService;
 use compos_aidl_interface::binder::Strong;
-use compos_common::CURRENT_DIR;
+use compos_common::{CURRENT_INSTANCE_DIR, TEST_INSTANCE_DIR};
 use std::sync::{Arc, Mutex, Weak};
 use virtualizationservice::IVirtualizationService::IVirtualizationService;
 
@@ -42,13 +42,22 @@
         Ok(instance.get_service())
     }
 
+    #[allow(dead_code)] // TODO: Make use of this
     pub fn start_current_instance(&self) -> Result<Arc<CompOsInstance>> {
+        self.start_instance(CURRENT_INSTANCE_DIR)
+    }
+
+    pub fn start_test_instance(&self) -> Result<Arc<CompOsInstance>> {
+        self.start_instance(TEST_INSTANCE_DIR)
+    }
+
+    fn start_instance(&self, instance_name: &str) -> Result<Arc<CompOsInstance>> {
         let mut state = self.state.lock().unwrap();
         state.mark_starting()?;
         // Don't hold the lock while we start the instance to avoid blocking other callers.
         drop(state);
 
-        let instance = self.try_start_current_instance();
+        let instance = self.try_start_instance(instance_name);
 
         let mut state = self.state.lock().unwrap();
         if let Ok(ref instance) = instance {
@@ -59,8 +68,8 @@
         instance
     }
 
-    fn try_start_current_instance(&self) -> Result<Arc<CompOsInstance>> {
-        let instance_starter = InstanceStarter::new(CURRENT_DIR);
+    fn try_start_instance(&self, instance_name: &str) -> Result<Arc<CompOsInstance>> {
+        let instance_starter = InstanceStarter::new(instance_name);
         let compos_instance = instance_starter.create_or_start_instance(&*self.service)?;
 
         Ok(Arc::new(compos_instance))
diff --git a/compos/composd/src/odrefresh.rs b/compos/composd/src/odrefresh.rs
index 2d880e2..2dfc3a1 100644
--- a/compos/composd/src/odrefresh.rs
+++ b/compos/composd/src/odrefresh.rs
@@ -37,10 +37,11 @@
     CleanupFailed = EX_MAX + 4,
 }
 
-pub fn run_forced_compile() -> Result<ExitCode> {
+pub fn run_forced_compile(target_dir: &str) -> Result<ExitCode> {
     // We don`t need to capture stdout/stderr - odrefresh writes to the log
     let mut odrefresh = Command::new(ODREFRESH_BIN)
         .arg(format!("--use-compilation-os={}", VMADDR_CID_ANY as i32))
+        .arg(format!("--dalvik-cache={}", target_dir))
         .arg("--force-compile")
         .spawn()
         .context("Running odrefresh")?;
diff --git a/compos/composd/src/service.rs b/compos/composd/src/service.rs
index be9c30c..d3b73a1 100644
--- a/compos/composd/src/service.rs
+++ b/compos/composd/src/service.rs
@@ -42,9 +42,9 @@
 impl Interface for IsolatedCompilationService {}
 
 impl IIsolatedCompilationService for IsolatedCompilationService {
-    fn runForcedCompile(&self) -> binder::Result<()> {
+    fn runForcedCompileForTest(&self) -> binder::Result<()> {
         // TODO - check caller is system or shell/root?
-        to_binder_result(self.do_run_forced_compile())
+        to_binder_result(self.do_run_forced_compile_for_test())
     }
 
     fn compile_cmd(
@@ -70,12 +70,12 @@
 }
 
 impl IsolatedCompilationService {
-    fn do_run_forced_compile(&self) -> Result<()> {
-        info!("runForcedCompile");
+    fn do_run_forced_compile_for_test(&self) -> Result<()> {
+        info!("runForcedCompileForTest");
 
-        let comp_os = self.instance_manager.start_current_instance().context("Starting CompOS")?;
+        let comp_os = self.instance_manager.start_test_instance().context("Starting CompOS")?;
 
-        let exit_code = odrefresh::run_forced_compile()?;
+        let exit_code = odrefresh::run_forced_compile("test-artifacts")?;
 
         if exit_code != odrefresh::ExitCode::CompilationSuccess {
             bail!("Unexpected odrefresh result: {:?}", exit_code);
diff --git a/compos/composd_cmd/Android.bp b/compos/composd_cmd/Android.bp
index 5fef86f..0081a0d 100644
--- a/compos/composd_cmd/Android.bp
+++ b/compos/composd_cmd/Android.bp
@@ -10,6 +10,7 @@
         "android.system.composd-rust",
         "libanyhow",
         "libbinder_rs",
+        "libclap",
     ],
     prefer_rlib: true,
     apex_available: [
diff --git a/compos/composd_cmd/composd_cmd.rs b/compos/composd_cmd/composd_cmd.rs
index e4884e3..04398c0 100644
--- a/compos/composd_cmd/composd_cmd.rs
+++ b/compos/composd_cmd/composd_cmd.rs
@@ -23,12 +23,25 @@
 use anyhow::{Context, Result};
 
 fn main() -> Result<()> {
+    let app = clap::App::new("composd_cmd").arg(
+        clap::Arg::with_name("command")
+            .index(1)
+            .takes_value(true)
+            .required(true)
+            .possible_values(&["forced-compile-test"]),
+    );
+    let args = app.get_matches();
+    let command = args.value_of("command").unwrap();
+
     ProcessState::start_thread_pool();
 
     let service = wait_for_interface::<dyn IIsolatedCompilationService>("android.system.composd")
         .context("Failed to connect to composd service")?;
 
-    service.runForcedCompile().context("Compilation failed")?;
+    match command {
+        "forced-compile-test" => service.runForcedCompileForTest().context("Compilation failed")?,
+        _ => panic!("Unexpected command {}", command),
+    }
 
     println!("All Ok!");
 
diff --git a/compos/tests/AndroidTest.xml b/compos/tests/AndroidTest.xml
index 940531b..2a84291 100644
--- a/compos/tests/AndroidTest.xml
+++ b/compos/tests/AndroidTest.xml
@@ -18,6 +18,11 @@
         <option name="force-root" value="true" />
     </target_preparer>
 
+    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+        <!-- Run in single thread to avoid nondeterministics. -->
+        <option name="set-property" key="dalvik.vm.boot-dex2oat-threads" value="1" />
+    </target_preparer>
+
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="ComposHostTestCases.jar" />
     </test>
diff --git a/compos/tests/java/android/compos/test/ComposKeyTestCase.java b/compos/tests/java/android/compos/test/ComposKeyTestCase.java
index d9f7065..140f74b 100644
--- a/compos/tests/java/android/compos/test/ComposKeyTestCase.java
+++ b/compos/tests/java/android/compos/test/ComposKeyTestCase.java
@@ -159,7 +159,7 @@
                         apkName,
                         packageName,
                         "assets/vm_test_config.json",
-                        /* debug */ false,
+                        /* debug */ true,
                         /* use default memoryMib */ 0);
         adbConnectToMicrodroid(getDevice(), mCid);
     }
diff --git a/compos/tests/java/android/compos/test/ComposTestCase.java b/compos/tests/java/android/compos/test/ComposTestCase.java
index 6c3bc65..5f4bd00 100644
--- a/compos/tests/java/android/compos/test/ComposTestCase.java
+++ b/compos/tests/java/android/compos/test/ComposTestCase.java
@@ -37,12 +37,13 @@
 
     // Binaries used in test. (These paths are valid both in host and Microdroid.)
     private static final String ODREFRESH_BIN = "/apex/com.android.art/bin/odrefresh";
-    private static final String COMPOS_KEY_CMD_BIN = "/apex/com.android.compos/bin/compos_key_cmd";
     private static final String COMPOSD_CMD_BIN = "/apex/com.android.compos/bin/composd_cmd";
 
     /** Output directory of odrefresh */
+    private static final String TEST_ARTIFACTS_DIR = "test-artifacts";
+
     private static final String ODREFRESH_OUTPUT_DIR =
-            "/data/misc/apexdata/com.android.art/dalvik-cache";
+            "/data/misc/apexdata/com.android.art/" + TEST_ARTIFACTS_DIR;
 
     /** Timeout of odrefresh to finish */
     private static final int ODREFRESH_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes
@@ -51,12 +52,8 @@
     private static final int OKAY = 0;
     private static final int COMPILATION_SUCCESS = 80;
 
-    // Files that define the "current" instance of CompOS
-    private static final String COMPOS_CURRENT_ROOT =
-            "/data/misc/apexdata/com.android.compos/current/";
-    private static final String INSTANCE_IMAGE = COMPOS_CURRENT_ROOT + "instance.img";
-    private static final String PUBLIC_KEY = COMPOS_CURRENT_ROOT + "key.pubkey";
-    private static final String PRIVATE_KEY_BLOB = COMPOS_CURRENT_ROOT + "key.blob";
+    // Files that define the "test" instance of CompOS
+    private static final String COMPOS_TEST_ROOT = "/data/misc/apexdata/com.android.compos/test/";
 
     @Before
     public void setUp() throws Exception {
@@ -66,6 +63,14 @@
     @After
     public void tearDown() throws Exception {
         killVmAndReconnectAdb();
+
+        CommandRunner android = new CommandRunner(getDevice());
+
+        // Clear up any CompOS instance files we created
+        android.tryRun("rm", "-rf", COMPOS_TEST_ROOT);
+
+        // And any artifacts generated by odrefresh
+        android.tryRun("rm", "-rf", ODREFRESH_OUTPUT_DIR);
     }
 
     @Test
@@ -75,9 +80,7 @@
         // Prepare the groundtruth. The compilation on Android should finish successfully.
         {
             long start = System.currentTimeMillis();
-            CommandResult result =
-                    android.runForResultWithTimeout(
-                            ODREFRESH_TIMEOUT_MS, ODREFRESH_BIN, "--force-compile");
+            CommandResult result = runOdrefresh(android, "--force-compile");
             long elapsed = System.currentTimeMillis() - start;
             assertThat(result.getExitCode()).isEqualTo(COMPILATION_SUCCESS);
             CLog.i("Local compilation took " + elapsed + "ms");
@@ -87,17 +90,18 @@
         String expectedChecksumSnapshot = checksumDirectoryContent(android, ODREFRESH_OUTPUT_DIR);
 
         // Let --check clean up the output.
-        CommandResult result =
-                android.runForResultWithTimeout(ODREFRESH_TIMEOUT_MS, ODREFRESH_BIN, "--check");
+        CommandResult result = runOdrefresh(android, "--check");
         assertThat(result.getExitCode()).isEqualTo(OKAY);
 
         // Make sure we generate a fresh instance
-        android.tryRun("rm", "-rf", COMPOS_CURRENT_ROOT);
+        android.tryRun("rm", "-rf", COMPOS_TEST_ROOT);
 
         // Expect the compilation in Compilation OS to finish successfully.
         {
             long start = System.currentTimeMillis();
-            result = android.runForResultWithTimeout(ODREFRESH_TIMEOUT_MS, COMPOSD_CMD_BIN);
+            result =
+                    android.runForResultWithTimeout(
+                            ODREFRESH_TIMEOUT_MS, COMPOSD_CMD_BIN, "forced-compile-test");
             long elapsed = System.currentTimeMillis() - start;
             assertThat(result.getExitCode()).isEqualTo(0);
             CLog.i("Comp OS compilation took " + elapsed + "ms");
@@ -108,16 +112,24 @@
         String actualChecksumSnapshot = checksumDirectoryContent(android, ODREFRESH_OUTPUT_DIR);
 
         // Expect the output to be valid.
-        result = android.runForResultWithTimeout(ODREFRESH_TIMEOUT_MS, ODREFRESH_BIN, "--verify");
+        result = runOdrefresh(android, "--verify");
         assertThat(result.getExitCode()).isEqualTo(OKAY);
         // --check can delete the output, so run later.
-        result = android.runForResultWithTimeout(ODREFRESH_TIMEOUT_MS, ODREFRESH_BIN, "--check");
+        result = runOdrefresh(android, "--check");
         assertThat(result.getExitCode()).isEqualTo(OKAY);
 
         // Expect the output of Comp OS to be the same as compiled on Android.
         assertThat(actualChecksumSnapshot).isEqualTo(expectedChecksumSnapshot);
     }
 
+    private CommandResult runOdrefresh(CommandRunner android, String command) throws Exception {
+        return android.runForResultWithTimeout(
+                ODREFRESH_TIMEOUT_MS,
+                ODREFRESH_BIN,
+                "--dalvik-cache=" + TEST_ARTIFACTS_DIR,
+                command);
+    }
+
     private void killVmAndReconnectAdb() throws Exception {
         CommandRunner android = new CommandRunner(getDevice());
 
diff --git a/compos/verify_key/verify_key.rs b/compos/verify_key/verify_key.rs
index 0cc6473..a5b0b6b 100644
--- a/compos/verify_key/verify_key.rs
+++ b/compos/verify_key/verify_key.rs
@@ -21,8 +21,8 @@
 use compos_aidl_interface::binder::ProcessState;
 use compos_common::compos_client::VmInstance;
 use compos_common::{
-    COMPOS_DATA_ROOT, CURRENT_DIR, INSTANCE_IMAGE_FILE, PENDING_DIR, PRIVATE_KEY_BLOB_FILE,
-    PUBLIC_KEY_FILE,
+    COMPOS_DATA_ROOT, CURRENT_INSTANCE_DIR, INSTANCE_IMAGE_FILE, PENDING_INSTANCE_DIR,
+    PRIVATE_KEY_BLOB_FILE, PUBLIC_KEY_FILE,
 };
 use std::fs::{self, File};
 use std::io::Read;
@@ -49,7 +49,9 @@
     let do_pending = matches.value_of("instance").unwrap() == "pending";
 
     let instance_dir: PathBuf =
-        [COMPOS_DATA_ROOT, if do_pending { PENDING_DIR } else { CURRENT_DIR }].iter().collect();
+        [COMPOS_DATA_ROOT, if do_pending { PENDING_INSTANCE_DIR } else { CURRENT_INSTANCE_DIR }]
+            .iter()
+            .collect();
 
     if !instance_dir.is_dir() {
         bail!("{} is not a directory", instance_dir.display());
@@ -112,7 +114,7 @@
 }
 
 fn promote_to_current(instance_dir: &Path) -> Result<()> {
-    let current_dir: PathBuf = [COMPOS_DATA_ROOT, CURRENT_DIR].iter().collect();
+    let current_dir: PathBuf = [COMPOS_DATA_ROOT, CURRENT_INSTANCE_DIR].iter().collect();
 
     // This may fail if the directory doesn't exist - which is fine, we only care about the rename
     // succeeding.
diff --git a/demo/java/com/android/microdroid/demo/MainActivity.java b/demo/java/com/android/microdroid/demo/MainActivity.java
index ce21fdf..bc87c3c 100644
--- a/demo/java/com/android/microdroid/demo/MainActivity.java
+++ b/demo/java/com/android/microdroid/demo/MainActivity.java
@@ -24,6 +24,7 @@
 import android.system.virtualmachine.VirtualMachine;
 import android.system.virtualmachine.VirtualMachineCallback;
 import android.system.virtualmachine.VirtualMachineConfig;
+import android.system.virtualmachine.VirtualMachineConfig.DebugLevel;
 import android.system.virtualmachine.VirtualMachineException;
 import android.system.virtualmachine.VirtualMachineManager;
 import android.util.Log;
@@ -253,8 +254,10 @@
 
             try {
                 VirtualMachineConfig.Builder builder =
-                        new VirtualMachineConfig.Builder(getApplication(), "assets/vm_config.json")
-                                .debugMode(debug);
+                        new VirtualMachineConfig.Builder(getApplication(), "assets/vm_config.json");
+                if (debug) {
+                    builder.debugLevel(DebugLevel.FULL);
+                }
                 VirtualMachineConfig config = builder.build();
                 VirtualMachineManager vmm = VirtualMachineManager.getInstance(getApplication());
                 mVirtualMachine = vmm.getOrCreate("demo_vm", config);
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
index 2550087..dba4c8f 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -49,13 +49,36 @@
     private static final String KEY_CERTS = "certs";
     private static final String KEY_APKPATH = "apkPath";
     private static final String KEY_PAYLOADCONFIGPATH = "payloadConfigPath";
-    private static final String KEY_DEBUGMODE = "debugMode";
+    private static final String KEY_DEBUGLEVEL = "debugLevel";
     private static final String KEY_MEMORY_MIB = "memoryMib";
 
     // Paths to the APK file of this application.
     private final @NonNull String mApkPath;
     private final @NonNull Signature[] mCerts;
-    private final boolean mDebugMode;
+
+    /** A debug level defines the set of debug features that the VM can be configured to. */
+    public enum DebugLevel {
+        /**
+         * Not debuggable at all. No log is exported from the VM. Debugger can't be attached to the
+         * app process running in the VM. This is the default level.
+         */
+        NONE,
+
+        /**
+         * Only the app is debuggable. Log from the app is exported from the VM. Debugger can be
+         * attached to the app process. Rest of the VM is not debuggable.
+         */
+        APP_ONLY,
+
+        /**
+         * Fully debuggable. All logs (both logcat and kernel message) are exported. All processes
+         * running in the VM can be attached to the debugger. Rooting is possible.
+         */
+        FULL,
+    }
+
+    private final DebugLevel mDebugLevel;
+
     /**
      * The amount of RAM to give the VM, in MiB. If this is 0 or negative the default will be used.
      */
@@ -72,12 +95,12 @@
             @NonNull String apkPath,
             @NonNull Signature[] certs,
             @NonNull String payloadConfigPath,
-            boolean debugMode,
+            DebugLevel debugLevel,
             int memoryMib) {
         mApkPath = apkPath;
         mCerts = certs;
         mPayloadConfigPath = payloadConfigPath;
-        mDebugMode = debugMode;
+        mDebugLevel = debugLevel;
         mMemoryMib = memoryMib;
     }
 
@@ -106,9 +129,9 @@
         if (payloadConfigPath == null) {
             throw new VirtualMachineException("No payloadConfigPath");
         }
-        final boolean debugMode = b.getBoolean(KEY_DEBUGMODE);
+        final DebugLevel debugLevel = DebugLevel.values()[b.getInt(KEY_DEBUGLEVEL)];
         final int memoryMib = b.getInt(KEY_MEMORY_MIB);
-        return new VirtualMachineConfig(apkPath, certs, payloadConfigPath, debugMode, memoryMib);
+        return new VirtualMachineConfig(apkPath, certs, payloadConfigPath, debugLevel, memoryMib);
     }
 
     /** Persists this config to a stream, for example a file. */
@@ -123,7 +146,7 @@
         String[] certs = certList.toArray(new String[0]);
         b.putStringArray(KEY_CERTS, certs);
         b.putString(KEY_PAYLOADCONFIGPATH, mPayloadConfigPath);
-        b.putBoolean(KEY_DEBUGMODE, mDebugMode);
+        b.putInt(KEY_DEBUGLEVEL, mDebugLevel.ordinal());
         if (mMemoryMib > 0) {
             b.putInt(KEY_MEMORY_MIB, mMemoryMib);
         }
@@ -146,7 +169,8 @@
         if (!Arrays.equals(this.mCerts, other.mCerts)) {
             return false;
         }
-        if (this.mDebugMode != other.mDebugMode) {
+        if (this.mDebugLevel != other.mDebugLevel) {
+            // TODO(jiyong): should we treat APP_ONLY and FULL the same?
             return false;
         }
         return true;
@@ -162,7 +186,17 @@
         VirtualMachineAppConfig parcel = new VirtualMachineAppConfig();
         parcel.apk = ParcelFileDescriptor.open(new File(mApkPath), MODE_READ_ONLY);
         parcel.configPath = mPayloadConfigPath;
-        parcel.debug = mDebugMode;
+        switch (mDebugLevel) {
+            case NONE:
+                parcel.debugLevel = VirtualMachineAppConfig.DebugLevel.NONE;
+                break;
+            case APP_ONLY:
+                parcel.debugLevel = VirtualMachineAppConfig.DebugLevel.APP_ONLY;
+                break;
+            case FULL:
+                parcel.debugLevel = VirtualMachineAppConfig.DebugLevel.FULL;
+                break;
+        }
         parcel.memoryMib = mMemoryMib;
         return parcel;
     }
@@ -171,7 +205,7 @@
     public static class Builder {
         private Context mContext;
         private String mPayloadConfigPath;
-        private boolean mDebugMode;
+        private DebugLevel mDebugLevel;
         private int mMemoryMib;
         // TODO(jiyong): add more items like # of cpu, size of ram, debuggability, etc.
 
@@ -179,12 +213,12 @@
         public Builder(@NonNull Context context, @NonNull String payloadConfigPath) {
             mContext = context;
             mPayloadConfigPath = payloadConfigPath;
-            mDebugMode = false;
+            mDebugLevel = DebugLevel.NONE;
         }
 
-        /** Enables or disables the debug mode */
-        public Builder debugMode(boolean enableOrDisable) {
-            mDebugMode = enableOrDisable;
+        /** Sets the debug level */
+        public Builder debugLevel(DebugLevel debugLevel) {
+            mDebugLevel = debugLevel;
             return this;
         }
 
@@ -215,7 +249,7 @@
             }
 
             return new VirtualMachineConfig(
-                    apkPath, certs, mPayloadConfigPath, mDebugMode, mMemoryMib);
+                    apkPath, certs, mPayloadConfigPath, mDebugLevel, mMemoryMib);
         }
     }
 }
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 9683eb1..389ebb0 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -228,11 +228,6 @@
             cmdline: microdroid_boot_cmdline + ["acpi=noirq"],
         },
     },
-    product_variables: {
-        debuggable: {
-            cmdline: ["printk.devkmsg=on"],
-        },
-    },
 
     dtb_prebuilt: "dummy_dtb.img",
     header_version: "4",
@@ -325,10 +320,23 @@
     cmd: "cat $(in) > $(out)",
 }
 
+// TODO(b/203031847) sign these bootconfig images using avb
 prebuilt_etc {
-    name: "microdroid_bootconfig_debug",
-    src: "bootconfig.debug",
-    filename: "microdroid_bootconfig.debug",
+    name: "microdroid_bootconfig_normal",
+    src: "bootconfig.normal",
+    filename: "microdroid_bootconfig.normal",
+}
+
+prebuilt_etc {
+    name: "microdroid_bootconfig_app_debuggable",
+    src: "bootconfig.app_debuggable",
+    filename: "microdroid_bootconfig.app_debuggable",
+}
+
+prebuilt_etc {
+    name: "microdroid_bootconfig_full_debuggable",
+    src: "bootconfig.full_debuggable",
+    filename: "microdroid_bootconfig.full_debuggable",
 }
 
 prebuilt_etc {
diff --git a/microdroid/bootconfig.app_debuggable b/microdroid/bootconfig.app_debuggable
new file mode 100644
index 0000000..79e2b08
--- /dev/null
+++ b/microdroid/bootconfig.app_debuggable
@@ -0,0 +1 @@
+kernel.console = null
diff --git a/microdroid/bootconfig.debug b/microdroid/bootconfig.debug
deleted file mode 100644
index d83ecb9..0000000
--- a/microdroid/bootconfig.debug
+++ /dev/null
@@ -1 +0,0 @@
-androidboot.selinux = permissive
diff --git a/microdroid/bootconfig.full_debuggable b/microdroid/bootconfig.full_debuggable
new file mode 100644
index 0000000..d67cd76
--- /dev/null
+++ b/microdroid/bootconfig.full_debuggable
@@ -0,0 +1 @@
+kernel.printk.devkmsg=on
diff --git a/microdroid/bootconfig.normal b/microdroid/bootconfig.normal
new file mode 100644
index 0000000..79e2b08
--- /dev/null
+++ b/microdroid/bootconfig.normal
@@ -0,0 +1 @@
+kernel.console = null
diff --git a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java b/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
index d62892c..b3a76ce 100644
--- a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
+++ b/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
@@ -87,7 +87,7 @@
 
         // Make sure we're connected to the host adb; this connection seems to get dropped when a VM
         // exits.
-        for (int retry = 0; retry < 3; ++retry) {
+        for (int retry = 0; retry < 10; ++retry) {
             if (android.tryRun("true") != null) {
                 break;
             }
@@ -198,7 +198,7 @@
 
         final String instanceImg = TEST_ROOT + INSTANCE_IMG;
         final String logPath = TEST_ROOT + "log.txt";
-        final String debugFlag = debug ? "--debug " : "";
+        final String debugFlag = debug ? "--debug full" : "";
 
         // Run the VM
         String ret =
@@ -259,7 +259,7 @@
         }
     }
 
-    public static void rootMicrodroid() throws DeviceNotAvailableException {
+    public static void rootMicrodroid() {
         runOnHost("adb", "-s", MICRODROID_SERIAL, "root");
 
         // TODO(192660959): Figure out the root cause and remove the sleep. For unknown reason,
@@ -280,8 +280,7 @@
 
     // Establish an adb connection to microdroid by letting Android forward the connection to
     // microdroid. Wait until the connection is established and microdroid is booted.
-    public static void adbConnectToMicrodroid(ITestDevice androidDevice, String cid)
-            throws DeviceNotAvailableException {
+    public static void adbConnectToMicrodroid(ITestDevice androidDevice, String cid) {
         long start = System.currentTimeMillis();
         long timeoutMillis = MICRODROID_ADB_CONNECT_TIMEOUT_MINUTES * 60 * 1000;
         long elapsed = 0;
diff --git a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
index 6548428..3f80cdf 100644
--- a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
+++ b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
@@ -35,7 +35,7 @@
     private static final String PACKAGE_NAME = "com.android.microdroid.test";
 
     private static final int MIN_MEM_ARM64 = 125;
-    private static final int MIN_MEM_X86_64 = 270;
+    private static final int MIN_MEM_X86_64 = 400;
 
     private int minMemorySize() throws DeviceNotAvailableException {
         CommandRunner android = new CommandRunner(getDevice());
@@ -60,7 +60,7 @@
                         APK_NAME,
                         PACKAGE_NAME,
                         configPath,
-                        /* debug */ false,
+                        /* debug */ true,
                         minMemorySize());
         adbConnectToMicrodroid(getDevice(), cid);
 
@@ -104,26 +104,6 @@
         shutdownMicrodroid(getDevice(), cid);
     }
 
-    @Test
-    public void testDebugMode() throws Exception {
-        final String configPath = "assets/vm_config.json"; // path inside the APK
-        final boolean debug = true;
-        final String cid =
-                startMicrodroid(
-                        getDevice(),
-                        getBuild(),
-                        APK_NAME,
-                        PACKAGE_NAME,
-                        configPath,
-                        debug,
-                        minMemorySize());
-        adbConnectToMicrodroid(getDevice(), cid);
-
-        assertThat(runOnMicrodroid("getenforce"), is("Permissive"));
-
-        shutdownMicrodroid(getDevice(), cid);
-    }
-
     @Before
     public void setUp() throws Exception {
         testIfDeviceIsCapable(getDevice());
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
index a522dee..073c088 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
@@ -29,8 +29,20 @@
     /** Path to a configuration in an APK. This is the actual configuration for a VM. */
     @utf8InCpp String configPath;
 
-    /** Whether to run the VM in debug mode or not */
-    boolean debug;
+    enum DebugLevel {
+        /** Not debuggable at all */
+        NONE,
+        /** Only the logs from app is shown */
+        APP_ONLY,
+        /**
+         * Fully debuggable. All logs are shown, kernel messages are shown, and adb shell is
+         * supported
+         */
+        FULL,
+    }
+
+    /** Debug level of the VM */
+    DebugLevel debugLevel;
 
     /**
      * The amount of RAM to give the VM, in MiB. If this is 0 or negative then it will default to
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index dbcc5ce..fa6ee40 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -18,7 +18,6 @@
 use crate::crosvm::{CrosvmConfig, DiskFile, PayloadState, VmInstance, VmState};
 use crate::payload::add_microdroid_images;
 use crate::{Cid, FIRST_GUEST_CID, SYSPROP_LAST_CID};
-
 use ::binder::unstable_api::AsNative;
 use android_os_permissions_aidl::aidl::android::os::IPermissionController;
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
@@ -27,6 +26,7 @@
     IVirtualMachineCallback::IVirtualMachineCallback,
     IVirtualizationService::IVirtualizationService,
     PartitionType::PartitionType,
+    VirtualMachineAppConfig::DebugLevel::DebugLevel,
     VirtualMachineAppConfig::VirtualMachineAppConfig,
     VirtualMachineConfig::VirtualMachineConfig,
     VirtualMachineDebugInfo::VirtualMachineDebugInfo,
@@ -34,7 +34,7 @@
     VirtualMachineState::VirtualMachineState,
 };
 use android_system_virtualizationservice::binder::{
-    self, BinderFeatures, ExceptionCode, Interface, ParcelFileDescriptor, Status, Strong,
+    self, BinderFeatures, ExceptionCode, Interface, ParcelFileDescriptor, Status, StatusCode, Strong,
     ThreadState,
 };
 use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::{
@@ -51,6 +51,7 @@
 use microdroid_payload_config::VmPayloadConfig;
 use rustutils::system_properties;
 use std::convert::TryInto;
+use std::ffi::CStr;
 use std::fs::{create_dir, File, OpenOptions};
 use std::io::{Error, ErrorKind, Write};
 use std::num::NonZeroU32;
@@ -85,7 +86,31 @@
     state: Arc<Mutex<State>>,
 }
 
-impl Interface for VirtualizationService {}
+impl Interface for VirtualizationService {
+    fn dump(&self, mut file: &File, _args: &[&CStr]) -> Result<(), StatusCode> {
+        check_permission("android.permission.DUMP").or(Err(StatusCode::PERMISSION_DENIED))?;
+        let state = &mut *self.state.lock().unwrap();
+        let vms = state.vms();
+        writeln!(file, "Running {0} VMs:", vms.len()).or(Err(StatusCode::UNKNOWN_ERROR))?;
+        for vm in vms {
+            writeln!(file, "VM CID: {}", vm.cid).or(Err(StatusCode::UNKNOWN_ERROR))?;
+            writeln!(file, "\tState: {:?}", vm.vm_state.lock().unwrap())
+                .or(Err(StatusCode::UNKNOWN_ERROR))?;
+            writeln!(file, "\tPayload state {:?}", vm.payload_state())
+                .or(Err(StatusCode::UNKNOWN_ERROR))?;
+            writeln!(file, "\tProtected: {}", vm.protected).or(Err(StatusCode::UNKNOWN_ERROR))?;
+            writeln!(file, "\ttemporary_directory: {}", vm.temporary_directory.to_string_lossy())
+                .or(Err(StatusCode::UNKNOWN_ERROR))?;
+            writeln!(file, "\trequester_uid: {}", vm.requester_uid)
+                .or(Err(StatusCode::UNKNOWN_ERROR))?;
+            writeln!(file, "\trequester_sid: {}", vm.requester_sid)
+                .or(Err(StatusCode::UNKNOWN_ERROR))?;
+            writeln!(file, "\trequester_debug_pid: {}", vm.requester_debug_pid)
+                .or(Err(StatusCode::UNKNOWN_ERROR))?;
+        }
+        Ok(())
+    }
+}
 
 impl IVirtualizationService for VirtualizationService {
     /// Creates (but does not start) a new VM with the given configuration, assigning it the next
@@ -99,7 +124,7 @@
     ) -> binder::Result<Strong<dyn IVirtualMachine>> {
         check_manage_access()?;
         let state = &mut *self.state.lock().unwrap();
-        let log_fd = log_fd.map(clone_file).transpose()?;
+        let mut log_fd = log_fd.map(clone_file).transpose()?;
         let requester_uid = ThreadState::get_calling_uid();
         let requester_sid = get_calling_sid()?;
         let requester_debug_pid = ThreadState::get_calling_pid();
@@ -127,6 +152,16 @@
             )
         })?;
 
+        // Disable console logging if debug level != full. Note that kernel anyway doesn't use the
+        // console output when debug level != full. So, users won't be able to see the kernel
+        // output even without this overriding. This is to silence output from the bootloader which
+        // doesn't understand the bootconfig parameters.
+        if let VirtualMachineConfig::AppConfig(config) = config {
+            if config.debugLevel != DebugLevel::FULL {
+                log_fd = None;
+            }
+        }
+
         let config = match config {
             VirtualMachineConfig::AppConfig(config) => BorrowedOrOwned::Owned(
                 load_app_config(config, &temporary_directory).map_err(|e| {
diff --git a/virtualizationservice/src/payload.rs b/virtualizationservice/src/payload.rs
index 2d238a8..4c71c37 100644
--- a/virtualizationservice/src/payload.rs
+++ b/virtualizationservice/src/payload.rs
@@ -15,7 +15,8 @@
 //! Payload disk image
 
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
-    DiskImage::DiskImage, Partition::Partition, VirtualMachineAppConfig::VirtualMachineAppConfig,
+    DiskImage::DiskImage, Partition::Partition, VirtualMachineAppConfig::DebugLevel::DebugLevel,
+    VirtualMachineAppConfig::VirtualMachineAppConfig,
     VirtualMachineRawConfig::VirtualMachineRawConfig,
 };
 use android_system_virtualizationservice::binder::ParcelFileDescriptor;
@@ -290,16 +291,18 @@
         temporary_directory,
     )?);
 
-    if config.debug {
-        vm_config.disks[1].partitions.push(Partition {
-            label: "bootconfig".to_owned(),
-            image: Some(open_parcel_file(
-                Path::new("/apex/com.android.virt/etc/microdroid_bootconfig.debug"),
-                false,
-            )?),
-            writable: false,
-        });
-    }
+    let bootconfig_image = "/apex/com.android.virt/etc/microdroid_bootconfig.".to_owned()
+        + match config.debugLevel {
+            DebugLevel::NONE => "normal",
+            DebugLevel::APP_ONLY => "app_debuggable",
+            DebugLevel::FULL => "full_debuggable",
+            _ => return Err(anyhow!("unsupported debug level: {:?}", config.debugLevel)),
+        };
+    vm_config.disks[1].partitions.push(Partition {
+        label: "bootconfig".to_owned(),
+        image: Some(open_parcel_file(Path::new(&bootconfig_image), false)?),
+        writable: false,
+    });
 
     // instance image is at the second partition in the second disk.
     vm_config.disks[1].partitions.push(Partition {
diff --git a/vm/src/main.rs b/vm/src/main.rs
index 062773b..7e2a925 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -18,8 +18,10 @@
 mod run;
 mod sync;
 
-use android_system_virtualizationservice::aidl::android::system::virtualizationservice::IVirtualizationService::IVirtualizationService;
-use android_system_virtualizationservice::aidl::android::system::virtualizationservice::PartitionType::PartitionType;
+use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
+    IVirtualizationService::IVirtualizationService, PartitionType::PartitionType,
+    VirtualMachineAppConfig::DebugLevel::DebugLevel,
+};
 use android_system_virtualizationservice::binder::{wait_for_interface, ProcessState, Strong};
 use anyhow::{Context, Error};
 use create_partition::command_create_partition;
@@ -59,9 +61,9 @@
         #[structopt(short, long)]
         log: Option<PathBuf>,
 
-        /// Whether to run VM in debug mode.
-        #[structopt(short, long)]
-        debug: bool,
+        /// Debug level of the VM. Supported values: "none" (default), "app_only", and "full".
+        #[structopt(short, long, default_value = "none", parse(try_from_str=parse_debug_level))]
+        debug: DebugLevel,
 
         /// Memory size (in MiB) of the VM. If unspecified, defaults to the value of `memory_mib`
         /// in the VM config file.
@@ -104,6 +106,15 @@
     },
 }
 
+fn parse_debug_level(s: &str) -> Result<DebugLevel, String> {
+    match s {
+        "none" => Ok(DebugLevel::NONE),
+        "app_only" => Ok(DebugLevel::APP_ONLY),
+        "full" => Ok(DebugLevel::FULL),
+        _ => Err(format!("Invalid debug level {}", s)),
+    }
+}
+
 fn parse_partition_type(s: &str) -> Result<PartitionType, String> {
     match s {
         "raw" => Ok(PartitionType::RAW),
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 42da6a3..2d771fc 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -16,15 +16,12 @@
 
 use crate::create_partition::command_create_partition;
 use crate::sync::AtomicFlag;
-use android_system_virtualizationservice::aidl::android::system::virtualizationservice::IVirtualMachineCallback::{
-    BnVirtualMachineCallback, IVirtualMachineCallback,
-};
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
-    IVirtualMachine::IVirtualMachine,
-    IVirtualizationService::IVirtualizationService,
-    PartitionType::PartitionType,
-    VirtualMachineAppConfig::VirtualMachineAppConfig,
-    VirtualMachineConfig::VirtualMachineConfig,
+    IVirtualMachine::IVirtualMachine, IVirtualMachineCallback::BnVirtualMachineCallback,
+    IVirtualMachineCallback::IVirtualMachineCallback,
+    IVirtualizationService::IVirtualizationService, PartitionType::PartitionType,
+    VirtualMachineAppConfig::DebugLevel::DebugLevel,
+    VirtualMachineAppConfig::VirtualMachineAppConfig, VirtualMachineConfig::VirtualMachineConfig,
     VirtualMachineState::VirtualMachineState,
 };
 use android_system_virtualizationservice::binder::{
@@ -48,7 +45,7 @@
     config_path: &str,
     daemonize: bool,
     log_path: Option<&Path>,
-    debug: bool,
+    debug_level: DebugLevel,
     mem: Option<u32>,
 ) -> Result<(), Error> {
     let apk_file = File::open(apk).context("Failed to open APK file")?;
@@ -76,7 +73,7 @@
         idsig: idsig_fd.into(),
         instanceImage: open_parcel_file(instance, true /* writable */)?.into(),
         configPath: config_path.to_owned(),
-        debug,
+        debugLevel: debug_level,
         memoryMib: mem.unwrap_or(0) as i32, // 0 means use the VM default
     });
     run(service, &config, &format!("{:?}!{:?}", apk, config_path), daemonize, log_path)