Add command to start an empty Microdroid instance
This command can be used by the developer to start an empty microdroid
instance to play with. Or to do quick prototyping of their payload code.
Additionally, it can be used by the test infrastructure to run native
tests (e.g. bionic tests) inside the microdroid.
Bug: 254912288
Test: m
Test: adb shell /apex/com.android.virt/bin/vm run-microdroid --debug full
Change-Id: Id7c0e9c046b04d2567ab76fb48c90dbc5ebac803
diff --git a/apex/Android.bp b/apex/Android.bp
index 4e64e50..596493a 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -104,6 +104,9 @@
host_required: [
"vm_shell",
],
+ apps: [
+ "EmptyPayloadApp",
+ ],
}
apex_defaults {
diff --git a/apex/empty-payload-apk/Android.bp b/apex/empty-payload-apk/Android.bp
new file mode 100644
index 0000000..70e6754
--- /dev/null
+++ b/apex/empty-payload-apk/Android.bp
@@ -0,0 +1,26 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_app {
+ name: "EmptyPayloadApp",
+ installable: true,
+ jni_libs: ["MicrodroidEmptyPayloadJniLib"],
+ apex_available: ["com.android.virt"],
+ sdk_version: "system_current",
+ jni_uses_platform_apis: true,
+ min_sdk_version: "UpsideDownCake",
+ target_sdk_version: "UpsideDownCake",
+ compile_multilib: "first",
+ stl: "none",
+}
+
+cc_library {
+ name: "MicrodroidEmptyPayloadJniLib",
+ srcs: ["empty_binary.cpp"],
+ shared_libs: ["libvm_payload#current"],
+ installable: true,
+ apex_available: ["com.android.virt"],
+ compile_multilib: "first",
+ stl: "none",
+}
diff --git a/apex/empty-payload-apk/AndroidManifest.xml b/apex/empty-payload-apk/AndroidManifest.xml
new file mode 100644
index 0000000..e649744
--- /dev/null
+++ b/apex/empty-payload-apk/AndroidManifest.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.microdroid.empty_payload">
+
+ <uses-permission android:name="android.permission.MANAGE_VIRTUAL_MACHINE" />
+ <uses-feature android:name="android.software.virtualization_framework" android:required="true" />
+ <application android:testOnly="true" android:hasCode="false" />
+
+</manifest>
diff --git a/apex/empty-payload-apk/empty_binary.cpp b/apex/empty-payload-apk/empty_binary.cpp
new file mode 100644
index 0000000..4308954
--- /dev/null
+++ b/apex/empty-payload-apk/empty_binary.cpp
@@ -0,0 +1,22 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <vm_main.h>
+#include <vm_payload.h>
+
+extern "C" int AVmPayload_main() {
+ // disable buffering to communicate seamlessly
+ setvbuf(stdin, nullptr, _IONBF, 0);
+ setvbuf(stdout, nullptr, _IONBF, 0);
+ setvbuf(stderr, nullptr, _IONBF, 0);
+
+ printf("Hello Microdroid\n");
+
+ AVmPayload_notifyPayloadReady();
+
+ // Wait forever to allow developer to interact with Microdroid shell
+ for (;;) {
+ pause();
+ }
+
+ return 0;
+}
diff --git a/docs/getting_started/index.md b/docs/getting_started/index.md
index 5f552f9..a15c4c7 100644
--- a/docs/getting_started/index.md
+++ b/docs/getting_started/index.md
@@ -123,7 +123,16 @@
## Spawning your own VMs with Microdroid
[Microdroid](../../microdroid/README.md) is a lightweight version of Android that is intended to run
-on pVM. You can manually run the demo app on top of Microdroid as follows:
+on pVM. You can run a Microdroid with empty payload using the following command:
+
+```shell
+adb shell /apex/com.android.virt/bin/vm run-microdroid --debug full
+```
+
+The `instance.img` and `apk.idsig` files will be stored in a subdirectory under
+`/data/local/tmp/microdroid`, that `vm` will create.
+
+Atlernatively, you can manually run the demo app on top of Microdroid as follows:
```shell
UNBUNDLED_BUILD_SDKS_FROM_SOURCE=true TARGET_BUILD_APPS=MicrodroidDemoApp m apps_only dist
diff --git a/vm/Android.bp b/vm/Android.bp
index 7b016d4..95ef082 100644
--- a/vm/Android.bp
+++ b/vm/Android.bp
@@ -17,6 +17,7 @@
"liblibc",
"liblog_rust",
"libmicrodroid_payload_config",
+ "librand",
"librustutils",
"libserde_json",
"libserde",
diff --git a/vm/src/main.rs b/vm/src/main.rs
index 89d56d4..b5046fb 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -27,7 +27,7 @@
use clap::Parser;
use create_idsig::command_create_idsig;
use create_partition::command_create_partition;
-use run::{command_run, command_run_app};
+use run::{command_run, command_run_app, command_run_microdroid};
use rustutils::system_properties;
use std::path::{Path, PathBuf};
@@ -110,6 +110,65 @@
#[clap(long = "extra-idsig")]
extra_idsigs: Vec<PathBuf>,
},
+ /// Run a virtual machine with Microdroid inside
+ RunMicrodroid {
+ /// Path to the directory where VM-related files (e.g. instance.img, apk.idsig, etc.) will
+ /// be stored. If not specified a random directory under /data/local/tmp/microdroid will be
+ /// created and used.
+ #[clap(long)]
+ work_dir: Option<PathBuf>,
+
+ /// Name of VM
+ #[clap(long)]
+ name: Option<String>,
+
+ /// Detach VM from the terminal and run in the background
+ #[clap(short, long)]
+ daemonize: bool,
+
+ /// Path to the file backing the storage.
+ /// Created if the option is used but the path does not exist in the device.
+ #[clap(long)]
+ storage: Option<PathBuf>,
+
+ /// Size of the storage. Used only if --storage is supplied but path does not exist
+ /// Default size is 10*1024*1024
+ #[clap(long)]
+ storage_size: Option<u64>,
+
+ /// Path to file for VM console output.
+ #[clap(long)]
+ console: Option<PathBuf>,
+
+ /// Path to file for VM log output.
+ #[clap(long)]
+ log: Option<PathBuf>,
+
+ /// Path to file where ramdump is recorded on kernel panic
+ #[clap(long)]
+ ramdump: Option<PathBuf>,
+
+ /// Debug level of the VM. Supported values: "none" (default), "app_only", and "full".
+ #[clap(long, default_value = "none", value_parser = parse_debug_level)]
+ debug: DebugLevel,
+
+ /// Run VM in protected mode.
+ #[clap(short, long)]
+ protected: bool,
+
+ /// Memory size (in MiB) of the VM. If unspecified, defaults to the value of `memory_mib`
+ /// in the VM config file.
+ #[clap(short, long)]
+ mem: Option<u32>,
+
+ /// Number of vCPUs in the VM. If unspecified, defaults to 1.
+ #[clap(long)]
+ cpus: Option<u32>,
+
+ /// Comma separated list of task profile names to apply to the VM
+ #[clap(long)]
+ task_profiles: Vec<String>,
+ },
/// Run a virtual machine
Run {
/// Path to VM config JSON
@@ -238,6 +297,36 @@
task_profiles,
&extra_idsigs,
),
+ Opt::RunMicrodroid {
+ name,
+ work_dir,
+ storage,
+ storage_size,
+ daemonize,
+ console,
+ log,
+ ramdump,
+ debug,
+ protected,
+ mem,
+ cpus,
+ task_profiles,
+ } => command_run_microdroid(
+ name,
+ service.as_ref(),
+ work_dir,
+ storage.as_deref(),
+ storage_size,
+ daemonize,
+ console.as_deref(),
+ log.as_deref(),
+ ramdump.as_deref(),
+ debug,
+ protected,
+ mem,
+ cpus,
+ task_profiles,
+ ),
Opt::Run { name, config, daemonize, cpus, task_profiles, console, log } => {
command_run(
name,
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 01b916b..3f25bba 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -23,13 +23,16 @@
VirtualMachinePayloadConfig::VirtualMachinePayloadConfig,
VirtualMachineState::VirtualMachineState,
};
-use anyhow::{bail, Context, Error};
+use anyhow::{anyhow, bail, Context, Error};
use binder::ParcelFileDescriptor;
use microdroid_payload_config::VmPayloadConfig;
+use rand::{distributions::Alphanumeric, Rng};
+use std::fs;
use std::fs::File;
use std::io;
use std::os::unix::io::{AsRawFd, FromRawFd};
use std::path::{Path, PathBuf};
+use std::process::Command;
use vmclient::{ErrorCode, VmInstance};
use vmconfig::{open_parcel_file, VmConfig};
use zip::ZipArchive;
@@ -144,6 +147,83 @@
run(service, &config, &payload_config_str, daemonize, console_path, log_path, ramdump_path)
}
+const EMPTY_PAYLOAD_APK: &str = "com.android.microdroid.empty_payload";
+
+fn find_empty_payload_apk_path() -> Result<PathBuf, Error> {
+ let output = Command::new("/system/bin/pm")
+ .arg("path")
+ .arg(EMPTY_PAYLOAD_APK)
+ .output()
+ .context("failed to execute pm path")?;
+ let output_str = String::from_utf8(output.stdout).context("failed to parse output")?;
+ match output_str.strip_prefix("package:") {
+ None => Err(anyhow!("Unexpected output {}", output_str)),
+ Some(apk_path) => Ok(PathBuf::from(apk_path.trim())),
+ }
+}
+
+fn create_work_dir() -> Result<PathBuf, Error> {
+ let s: String =
+ rand::thread_rng().sample_iter(&Alphanumeric).take(17).map(char::from).collect();
+ let work_dir = PathBuf::from("/data/local/tmp/microdroid").join(s);
+ println!("creating work dir {}", work_dir.display());
+ fs::create_dir_all(&work_dir).context("failed to mkdir")?;
+ Ok(work_dir)
+}
+
+/// Run a VM with Microdroid
+#[allow(clippy::too_many_arguments)]
+pub fn command_run_microdroid(
+ name: Option<String>,
+ service: &dyn IVirtualizationService,
+ work_dir: Option<PathBuf>,
+ storage: Option<&Path>,
+ storage_size: Option<u64>,
+ daemonize: bool,
+ console_path: Option<&Path>,
+ log_path: Option<&Path>,
+ ramdump_path: Option<&Path>,
+ debug_level: DebugLevel,
+ protected: bool,
+ mem: Option<u32>,
+ cpus: Option<u32>,
+ task_profiles: Vec<String>,
+) -> Result<(), Error> {
+ let apk = find_empty_payload_apk_path()
+ .context(anyhow!("failed to find path for {} apk", EMPTY_PAYLOAD_APK))?;
+ println!("found path for {} apk: {}", EMPTY_PAYLOAD_APK, apk.display());
+
+ let work_dir = work_dir.unwrap_or(create_work_dir()?);
+ let idsig = work_dir.join("apk.idsig");
+ println!("apk.idsig path: {}", idsig.display());
+ let instance_img = work_dir.join("instance.img");
+ println!("instance.img path: {}", instance_img.display());
+
+ let payload_path = "MicrodroidEmptyPayloadJniLib.so";
+ let extra_sig = [];
+ command_run_app(
+ name,
+ service,
+ &apk,
+ &idsig,
+ &instance_img,
+ storage,
+ storage_size,
+ /* config_path= */ None,
+ Some(payload_path.to_owned()),
+ daemonize,
+ console_path,
+ log_path,
+ ramdump_path,
+ debug_level,
+ protected,
+ mem,
+ cpus,
+ task_profiles,
+ &extra_sig,
+ )
+}
+
/// Run a VM from the given configuration file.
#[allow(clippy::too_many_arguments)]
pub fn command_run(