Merge changes Id95c91a1,I83da1c26,Ie76b9d15

* changes:
  Add payload linker namespace test
  Remove checkstyle_hook
  Emit launcher error to kernel log
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/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/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/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..95b4760 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -828,6 +828,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