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