Merge "Use microdroid-specific apexd"
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 43c89d4..45519d4 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -19,4 +19,3 @@
 
 [Hook Scripts]
 aosp_hook = ${REPO_ROOT}/frameworks/base/tools/aosp/aosp_sha.sh ${PREUPLOAD_COMMIT} "."
-checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
diff --git a/apex/Android.bp b/apex/Android.bp
index e580cc2..579d7c7 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -4,7 +4,7 @@
 
 microdroid_filesystem_images = [
     "microdroid_boot",
-    "microdroid_bootconfig_full_debuggable",
+    "microdroid_bootconfig_debuggable",
     "microdroid_bootconfig_normal",
     "microdroid_init_boot",
     "microdroid_super",
@@ -92,7 +92,7 @@
     prebuilts: [
         "com.android.virt.init.rc",
         "features_com.android.virt.xml",
-        "microdroid_initrd_full_debuggable",
+        "microdroid_initrd_debuggable",
         "microdroid_initrd_normal",
         "microdroid.json",
         "microdroid_bootloader",
diff --git a/apex/sign_virt_apex.py b/apex/sign_virt_apex.py
index 1d41543..4d83c5f 100644
--- a/apex/sign_virt_apex.py
+++ b/apex/sign_virt_apex.py
@@ -352,7 +352,7 @@
     'vbmeta.img': 'etc/fs/microdroid_vbmeta.img',
     'vbmeta_bootconfig.img': 'etc/fs/microdroid_vbmeta_bootconfig.img',
     'bootconfig.normal': 'etc/fs/microdroid_bootconfig.normal',
-    'bootconfig.full_debuggable': 'etc/fs/microdroid_bootconfig.full_debuggable',
+    'bootconfig.debuggable': 'etc/fs/microdroid_bootconfig.debuggable',
     'uboot_env.img': 'etc/fs/uboot_env.img'
 }
 
@@ -399,7 +399,7 @@
     # Re-sign bootconfigs and the uboot_env with the same key
     bootconfig_sign_key = key
     Async(AddHashFooter, args, bootconfig_sign_key, files['bootconfig.normal'])
-    Async(AddHashFooter, args, bootconfig_sign_key, files['bootconfig.full_debuggable'])
+    Async(AddHashFooter, args, bootconfig_sign_key, files['bootconfig.debuggable'])
     Async(AddHashFooter, args, bootconfig_sign_key, files['uboot_env.img'])
 
     # Re-sign vbmeta_bootconfig with chained_partitions to "bootconfig" and
diff --git a/launcher/Android.bp b/launcher/Android.bp
index 123ec2e..6c6417f 100644
--- a/launcher/Android.bp
+++ b/launcher/Android.bp
@@ -6,8 +6,10 @@
     name: "microdroid_launcher",
     srcs: ["main.cpp"],
     shared_libs: [
+        "libbase",
         "libdl",
         "libdl_android",
+        "liblog",
     ],
     header_libs: ["vm_payload_headers"],
 }
diff --git a/launcher/main.cpp b/launcher/main.cpp
index ae55be9..c3f9988 100644
--- a/launcher/main.cpp
+++ b/launcher/main.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include <android-base/logging.h>
+#include <android-base/result.h>
 #include <android/dlext.h>
 #include <dlfcn.h>
 
@@ -23,6 +25,9 @@
 
 #include "vm_main.h"
 
+using android::base::Error;
+using android::base::Result;
+
 extern "C" {
 enum {
     ANDROID_NAMESPACE_TYPE_REGULAR = 0,
@@ -40,7 +45,7 @@
                                     const char* shared_libs_sonames);
 } // extern "C"
 
-static void* load(const std::string& libname);
+static Result<void*> load(const std::string& libname);
 
 constexpr char entrypoint_name[] = "AVmPayload_main";
 
@@ -56,16 +61,18 @@
         return EXIT_FAILURE;
     }
 
+    android::base::InitLogging(argv, &android::base::KernelLogger);
+
     const char* libname = argv[1];
-    void* handle = load(libname);
-    if (handle == nullptr) {
-        std::cerr << "Failed to load " << libname << ": " << dlerror() << "\n";
+    auto handle = load(libname);
+    if (!handle.ok()) {
+        LOG(ERROR) << "Failed to load " << libname << ": " << handle.error().message();
         return EXIT_FAILURE;
     }
 
-    AVmPayload_main_t* entry = reinterpret_cast<decltype(entry)>(dlsym(handle, entrypoint_name));
+    AVmPayload_main_t* entry = reinterpret_cast<decltype(entry)>(dlsym(*handle, entrypoint_name));
     if (entry == nullptr) {
-        std::cerr << "Failed to find entrypoint `" << entrypoint_name << "`: " << dlerror() << "\n";
+        LOG(ERROR) << "Failed to find entrypoint `" << entrypoint_name << "`: " << dlerror();
         return EXIT_FAILURE;
     }
 
@@ -75,7 +82,7 @@
 // Create a new linker namespace whose search path is set to the directory of the library. Then
 // load it from there. Returns the handle to the loaded library if successful. Returns nullptr
 // if failed.
-void* load(const std::string& libname) {
+Result<void*> load(const std::string& libname) {
     // Parent as nullptr means the default namespace
     android_namespace_t* parent = nullptr;
     // The search paths of the new namespace are isolated to restrict system private libraries.
@@ -89,8 +96,7 @@
     new_ns = android_create_namespace("microdroid_app", ld_library_path, default_library_path, type,
                                       /* permitted_when_isolated_path */ nullptr, parent);
     if (new_ns == nullptr) {
-        std::cerr << "Failed to create linker namespace: " << dlerror() << "\n";
-        return nullptr;
+        return Error() << "Failed to create linker namespace: " << dlerror();
     }
 
     std::string libs;
@@ -98,11 +104,17 @@
         if (!libs.empty()) libs += ':';
         libs += lib;
     }
-    android_link_namespaces(new_ns, nullptr, libs.c_str());
+    if (!android_link_namespaces(new_ns, nullptr, libs.c_str())) {
+        return Error() << "Failed to link namespace: " << dlerror();
+    }
 
     const android_dlextinfo info = {
             .flags = ANDROID_DLEXT_USE_NAMESPACE,
             .library_namespace = new_ns,
     };
-    return android_dlopen_ext(libname.c_str(), RTLD_NOW, &info);
+    if (auto ret = android_dlopen_ext(libname.c_str(), RTLD_NOW, &info); ret) {
+        return ret;
+    } else {
+        return Error() << "Failed to dlopen: " << dlerror();
+    }
 }
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index f62d07b..028ac1f 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -373,9 +373,9 @@
 }
 
 avb_add_hash_footer {
-    name: "microdroid_bootconfig_full_debuggable",
-    src: "bootconfig.full_debuggable",
-    filename: "microdroid_bootconfig.full_debuggable",
+    name: "microdroid_bootconfig_debuggable",
+    src: "bootconfig.debuggable",
+    filename: "microdroid_bootconfig.debuggable",
     partition_name: "bootconfig",
     private_key: ":microdroid_sign_key",
     salt: bootconfig_salt,
@@ -526,8 +526,8 @@
 }
 
 filegroup {
-    name: "microdroid_bootconfig_full_debuggable_src",
-    srcs: ["bootconfig.full_debuggable"],
+    name: "microdroid_bootconfig_debuggable_src",
+    srcs: ["bootconfig.debuggable"],
 }
 
 filegroup {
diff --git a/microdroid/bootconfig.full_debuggable b/microdroid/bootconfig.debuggable
similarity index 100%
rename from microdroid/bootconfig.full_debuggable
rename to microdroid/bootconfig.debuggable
diff --git a/microdroid/initrd/Android.bp b/microdroid/initrd/Android.bp
index 8d7794d..d05ea86 100644
--- a/microdroid/initrd/Android.bp
+++ b/microdroid/initrd/Android.bp
@@ -50,24 +50,24 @@
 ]
 
 genrule {
-    name: "microdroid_initrd_full_debuggable_arm64",
+    name: "microdroid_initrd_debuggable_arm64",
     tools: ["initrd_bootconfig"],
     srcs: [
         ":microdroid_initrd_gen",
-        ":microdroid_bootconfig_full_debuggable_src",
+        ":microdroid_bootconfig_debuggable_src",
     ] + bootconfigs_arm64,
-    out: ["microdroid_initrd_full_debuggable_arm64"],
+    out: ["microdroid_initrd_debuggable_arm64"],
     cmd: "$(location initrd_bootconfig) --output $(out) $(in)",
 }
 
 genrule {
-    name: "microdroid_initrd_full_debuggable_x86_64",
+    name: "microdroid_initrd_debuggable_x86_64",
     tools: ["initrd_bootconfig"],
     srcs: [
         ":microdroid_initrd_gen",
-        ":microdroid_bootconfig_full_debuggable_src",
+        ":microdroid_bootconfig_debuggable_src",
     ] + bootconfigs_x86_64,
-    out: ["microdroid_initrd_full_debuggable_x86_64"],
+    out: ["microdroid_initrd_debuggable_x86_64"],
     cmd: "$(location initrd_bootconfig) --output $(out) $(in)",
 }
 
@@ -94,18 +94,18 @@
 }
 
 prebuilt_etc {
-    name: "microdroid_initrd_full_debuggable",
+    name: "microdroid_initrd_debuggable",
     // We don't have ramdisk for architectures other than x86_64 & arm64
     src: "empty_file",
     arch: {
         x86_64: {
-            src: ":microdroid_initrd_full_debuggable_x86_64",
+            src: ":microdroid_initrd_debuggable_x86_64",
         },
         arm64: {
-            src: ":microdroid_initrd_full_debuggable_arm64",
+            src: ":microdroid_initrd_debuggable_arm64",
         },
     },
-    filename: "microdroid_initrd_full_debuggable.img",
+    filename: "microdroid_initrd_debuggable.img",
 }
 
 prebuilt_etc {
@@ -131,8 +131,8 @@
 }
 
 genrule {
-    name: "microdroid_initrd_full_debuggable.sha256",
-    srcs: [":microdroid_initrd_full_debuggable"],
+    name: "microdroid_initrd_debuggable.sha256",
+    srcs: [":microdroid_initrd_debuggable"],
     cmd: "cat $(in) | sha256sum | cut -d' ' -f1 > $(out)",
     out: ["hash"],
 }
@@ -141,7 +141,7 @@
     name: "microdroid_initrd_hashes",
     srcs: [
         ":microdroid_initrd_normal.sha256",
-        ":microdroid_initrd_full_debuggable.sha256",
+        ":microdroid_initrd_debuggable.sha256",
     ],
     // join the hashes with commas
     cmd: "cat $(in) | tr '\n' ',' > $(out) && truncate -s -1 $(out)",
diff --git a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
index bd5b180..72a0090 100644
--- a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
+++ b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
@@ -132,6 +132,8 @@
         private OptionalLong mKernelStartedNanoTime = OptionalLong.empty();
         private OptionalLong mInitStartedNanoTime = OptionalLong.empty();
         private OptionalLong mPayloadStartedNanoTime = OptionalLong.empty();
+        private StringBuilder mConsoleOutput = new StringBuilder();
+        private StringBuilder mLogOutput = new StringBuilder();
 
         private void processBootEvents(String log) {
             if (!mVcpuStartedNanoTime.isPresent()) {
@@ -149,44 +151,50 @@
             }
         }
 
-        private void logVmOutputAndMonitorBootEvents(String tag,
+        private void logVmOutputAndMonitorBootEvents(
+                String tag,
                 InputStream vmOutputStream,
                 String name,
+                StringBuilder result,
                 boolean monitorEvents) {
             new Thread(
-                    () -> {
-                        try {
-                            BufferedReader reader =
-                                    new BufferedReader(new InputStreamReader(vmOutputStream));
-                            String line;
-                            while ((line = reader.readLine()) != null
-                                    && !Thread.interrupted()) {
-                                if (monitorEvents) processBootEvents(line);
-                                Log.i(tag, name + ": " + line);
-                            }
-                        } catch (Exception e) {
-                            Log.w(tag, name, e);
-                        }
-                    }).start();
+                            () -> {
+                                try {
+                                    BufferedReader reader =
+                                            new BufferedReader(
+                                                    new InputStreamReader(vmOutputStream));
+                                    String line;
+                                    while ((line = reader.readLine()) != null
+                                            && !Thread.interrupted()) {
+                                        if (monitorEvents) processBootEvents(line);
+                                        Log.i(tag, name + ": " + line);
+                                        result.append(line + "\n");
+                                    }
+                                } catch (Exception e) {
+                                    Log.w(tag, name, e);
+                                }
+                            })
+                    .start();
         }
 
-        private void logVmOutputAndMonitorBootEvents(String tag,
-                InputStream vmOutputStream,
-                String name) {
-            logVmOutputAndMonitorBootEvents(tag, vmOutputStream, name, true);
+        private void logVmOutputAndMonitorBootEvents(
+                String tag, InputStream vmOutputStream, String name, StringBuilder result) {
+            logVmOutputAndMonitorBootEvents(tag, vmOutputStream, name, result, true);
         }
 
         /** Copy output from the VM to logcat. This is helpful when things go wrong. */
-        protected void logVmOutput(String tag, InputStream vmOutputStream, String name) {
-            logVmOutputAndMonitorBootEvents(tag, vmOutputStream, name, false);
+        protected void logVmOutput(
+                String tag, InputStream vmOutputStream, String name, StringBuilder result) {
+            logVmOutputAndMonitorBootEvents(tag, vmOutputStream, name, result, false);
         }
 
         public void runToFinish(String logTag, VirtualMachine vm)
                 throws VirtualMachineException, InterruptedException {
             vm.setCallback(mExecutorService, this);
             vm.run();
-            logVmOutputAndMonitorBootEvents(logTag, vm.getConsoleOutput(), "Console");
-            logVmOutput(logTag, vm.getLogOutput(), "Log");
+            logVmOutputAndMonitorBootEvents(
+                    logTag, vm.getConsoleOutput(), "Console", mConsoleOutput);
+            logVmOutput(logTag, vm.getLogOutput(), "Log", mLogOutput);
             mExecutorService.awaitTermination(300, TimeUnit.SECONDS);
         }
 
@@ -206,6 +214,14 @@
             return mPayloadStartedNanoTime;
         }
 
+        public String getConsoleOutput() {
+            return mConsoleOutput.toString();
+        }
+
+        public String getLogOutput() {
+            return mLogOutput.toString();
+        }
+
         protected void forceStop(VirtualMachine vm) {
             try {
                 vm.stop();
@@ -248,14 +264,20 @@
         public final OptionalLong initStartedNanoTime;
         public final OptionalLong payloadStartedNanoTime;
 
-        BootResult(boolean payloadStarted,
+        public final String consoleOutput;
+        public final String logOutput;
+
+        BootResult(
+                boolean payloadStarted,
                 int deathReason,
                 long apiCallNanoTime,
                 long endToEndNanoTime,
                 OptionalLong vcpuStartedNanoTime,
                 OptionalLong kernelStartedNanoTime,
                 OptionalLong initStartedNanoTime,
-                OptionalLong payloadStartedNanoTime) {
+                OptionalLong payloadStartedNanoTime,
+                String consoleOutput,
+                String logOutput) {
             this.apiCallNanoTime = apiCallNanoTime;
             this.payloadStarted = payloadStarted;
             this.deathReason = deathReason;
@@ -264,6 +286,8 @@
             this.kernelStartedNanoTime = kernelStartedNanoTime;
             this.initStartedNanoTime = initStartedNanoTime;
             this.payloadStartedNanoTime = payloadStartedNanoTime;
+            this.consoleOutput = consoleOutput;
+            this.logOutput = logOutput;
         }
 
         private long getVcpuStartedNanoTime() {
@@ -332,7 +356,9 @@
                 listener.getVcpuStartedNanoTime(),
                 listener.getKernelStartedNanoTime(),
                 listener.getInitStartedNanoTime(),
-                listener.getPayloadStartedNanoTime());
+                listener.getPayloadStartedNanoTime(),
+                listener.getConsoleOutput(),
+                listener.getLogOutput());
     }
 
     /** Execute a command. Returns stdout. */
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index 5ce8f60..19ea117 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -334,7 +334,7 @@
         }
 
         // Add partitions to the second disk
-        final String initrdPath = TEST_ROOT + "etc/microdroid_initrd_full_debuggable.img";
+        final String initrdPath = TEST_ROOT + "etc/microdroid_initrd_debuggable.img";
         config.put("initrd", initrdPath);
         // Add instance image as a partition in disks[1]
         disks.put(new JSONObject()
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index 4dc9489..edb4759 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -23,6 +23,8 @@
     jni_libs: [
         "MicrodroidTestNativeLib",
         "MicrodroidIdleNativeLib",
+        "MicrodroidEmptyNativeLib",
+        "MicrodroidPrivateLinkingNativeLib",
     ],
     jni_uses_platform_apis: true,
     use_embedded_native_libs: true,
@@ -62,3 +64,21 @@
     header_libs: ["vm_payload_headers"],
     stl: "libc++_static",
 }
+
+// An empty payload missing AVmPayload_main
+cc_library_shared {
+    name: "MicrodroidEmptyNativeLib",
+    srcs: ["src/native/emptybinary.cpp"],
+    stl: "none",
+}
+
+// A payload which tries to link against libselinux, one of private libraries
+cc_library_shared {
+    name: "MicrodroidPrivateLinkingNativeLib",
+    srcs: ["src/native/idlebinary.cpp"],
+    header_libs: ["vm_payload_headers"],
+    // HACK: linking against "libselinux" will embed libselinux.so into the apk
+    // link against a stub to prevent libselinux.so from being embedded
+    shared_libs: ["libselinux#latest"],
+    stl: "libc++_static",
+}
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index fa8be93..8b0d6d2 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -521,6 +521,12 @@
         changeDebugLevel(DEBUG_LEVEL_NONE, DEBUG_LEVEL_FULL);
     }
 
+    @Test
+    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-7"})
+    public void changingDebuggableVmNonDebuggableInvalidatesVmIdentity() throws Exception {
+        changeDebugLevel(DEBUG_LEVEL_FULL, DEBUG_LEVEL_NONE);
+    }
+
     private void changeDebugLevel(int fromLevel, int toLevel) throws Exception {
         assumeSupportedKernel();
 
@@ -828,6 +834,50 @@
                 VirtualMachineCallback.STOP_REASON_MICRODROID_UNKNOWN_RUNTIME_ERROR);
     }
 
+    // Checks whether microdroid_launcher started but payload failed. reason must be recorded in the
+    // console output.
+    private void assertThatPayloadFailsDueTo(VirtualMachine vm, String reason) throws Exception {
+        final CompletableFuture<Boolean> payloadStarted = new CompletableFuture<>();
+        final CompletableFuture<Integer> exitCodeFuture = new CompletableFuture<>();
+        VmEventListener listener =
+                new VmEventListener() {
+                    @Override
+                    public void onPayloadStarted(VirtualMachine vm) {
+                        payloadStarted.complete(true);
+                    }
+
+                    @Override
+                    public void onPayloadFinished(VirtualMachine vm, int exitCode) {
+                        exitCodeFuture.complete(exitCode);
+                    }
+                };
+        listener.runToFinish(TAG, vm);
+
+        assertThat(payloadStarted.getNow(false)).isTrue();
+        assertThat(exitCodeFuture.getNow(0)).isNotEqualTo(0);
+        assertThat(listener.getConsoleOutput()).contains(reason);
+    }
+
+    @Test
+    public void bootFailsWhenBinaryIsMissingEntryFunction() throws Exception {
+        VirtualMachineConfig.Builder builder =
+                newVmConfigBuilder().setPayloadBinaryPath("MicrodroidEmptyNativeLib.so");
+        VirtualMachineConfig normalConfig = builder.setDebugLevel(DEBUG_LEVEL_FULL).build();
+        VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_missing_entry", normalConfig);
+
+        assertThatPayloadFailsDueTo(vm, "Failed to find entrypoint");
+    }
+
+    @Test
+    public void bootFailsWhenBinaryTriesToLinkAgainstPrivateLibs() throws Exception {
+        VirtualMachineConfig.Builder builder =
+                newVmConfigBuilder().setPayloadBinaryPath("MicrodroidPrivateLinkingNativeLib.so");
+        VirtualMachineConfig normalConfig = builder.setDebugLevel(DEBUG_LEVEL_FULL).build();
+        VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_private_linking", normalConfig);
+
+        assertThatPayloadFailsDueTo(vm, "Failed to dlopen");
+    }
+
     @Test
     public void sameInstancesShareTheSameVmObject() throws Exception {
         VirtualMachineConfig config =
diff --git a/tests/testapk/src/native/emptybinary.cpp b/tests/testapk/src/native/emptybinary.cpp
new file mode 100644
index 0000000..15f3f4d
--- /dev/null
+++ b/tests/testapk/src/native/emptybinary.cpp
@@ -0,0 +1 @@
+// a binary without AVmPayload_main
diff --git a/virtualizationservice/src/crosvm.rs b/virtualizationservice/src/crosvm.rs
index 8ac688d..fc85ca5 100644
--- a/virtualizationservice/src/crosvm.rs
+++ b/virtualizationservice/src/crosvm.rs
@@ -423,7 +423,6 @@
                 let mut vm_metric = self.vm_metric.lock().unwrap();
 
                 // Get CPU Information
-                // TODO: Collect it once right before VM dies using SIGCHLD
                 if let Ok(guest_time) = get_guest_time(pid) {
                     vm_metric.cpu_guest_time = Some(guest_time);
                 } else {
diff --git a/virtualizationservice/src/payload.rs b/virtualizationservice/src/payload.rs
index f84cb86..eb3e9eb 100644
--- a/virtualizationservice/src/payload.rs
+++ b/virtualizationservice/src/payload.rs
@@ -405,7 +405,7 @@
 ) -> Result<()> {
     let debug_suffix = match config.debugLevel {
         DebugLevel::NONE => "normal",
-        DebugLevel::FULL => "full_debuggable",
+        DebugLevel::FULL => "debuggable",
         _ => return Err(anyhow!("unsupported debug level: {:?}", config.debugLevel)),
     };
     let initrd = format!("/apex/com.android.virt/etc/microdroid_initrd_{}.img", debug_suffix);