Merge "Add command to start an empty Microdroid instance"
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(