Merge "EncryptedStore: Derive error msg from onError cb"
diff --git a/compos/common/compos_client.rs b/compos/common/compos_client.rs
index f6811cb..59fdd9f 100644
--- a/compos/common/compos_client.rs
+++ b/compos/common/compos_client.rs
@@ -22,6 +22,7 @@
COMPOS_APEX_ROOT, COMPOS_DATA_ROOT, COMPOS_VSOCK_PORT,
};
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
+ CpuTopology::CpuTopology,
IVirtualizationService::IVirtualizationService,
VirtualMachineAppConfig::{DebugLevel::DebugLevel, Payload::Payload, VirtualMachineAppConfig},
VirtualMachineConfig::VirtualMachineConfig,
@@ -32,20 +33,29 @@
use log::{info, warn};
use rustutils::system_properties;
use std::fs::{self, File};
-use std::num::NonZeroU32;
use std::path::{Path, PathBuf};
use vmclient::{DeathReason, ErrorCode, VmInstance, VmWaitError};
/// This owns an instance of the CompOS VM.
pub struct ComposClient(VmInstance);
+/// CPU topology configuration for a virtual machine.
+#[derive(Default, Debug, Clone)]
+pub enum VmCpuTopology {
+ /// Run VM with 1 vCPU only.
+ #[default]
+ OneCpu,
+ /// Run VM vCPU topology matching that of the host.
+ MatchHost,
+}
+
/// Parameters to be used when creating a virtual machine instance.
#[derive(Default, Debug, Clone)]
pub struct VmParameters {
/// Whether the VM should be debuggable.
pub debug_mode: bool,
- /// Number of vCPUs to have in the VM. If None, defaults to 1.
- pub cpus: Option<NonZeroU32>,
+ /// CPU topology of the VM. Defaults to 1 vCPU.
+ pub cpu_topology: VmCpuTopology,
/// List of task profiles to apply to the VM
pub task_profiles: Vec<String>,
/// If present, overrides the amount of RAM to give the VM
@@ -111,6 +121,11 @@
(Some(console_fd), Some(log_fd))
};
+ let cpu_topology = match parameters.cpu_topology {
+ VmCpuTopology::OneCpu => CpuTopology::ONE_CPU,
+ VmCpuTopology::MatchHost => CpuTopology::MATCH_HOST,
+ };
+
let config = VirtualMachineConfig::AppConfig(VirtualMachineAppConfig {
name: String::from("Compos"),
apk: Some(apk_fd),
@@ -122,7 +137,7 @@
extraIdsigs: extra_idsigs,
protectedVm: protected_vm,
memoryMib: parameters.memory_mib.unwrap_or(0), // 0 means use the default
- numCpus: parameters.cpus.map_or(1, NonZeroU32::get) as i32,
+ cpuTopology: cpu_topology,
taskProfiles: parameters.task_profiles.clone(),
});
diff --git a/compos/common/lib.rs b/compos/common/lib.rs
index 8d49ff0..1f937c9 100644
--- a/compos/common/lib.rs
+++ b/compos/common/lib.rs
@@ -53,9 +53,6 @@
/// /system_ext available in CompOS.
pub const IDSIG_MANIFEST_EXT_APK_FILE: &str = "idsig_manifest_ext_apk";
-/// Number of CPUs to run dex2oat (actually the entire compos VM) with
-pub const DEX2OAT_THREADS_PROP_NAME: &str = "dalvik.vm.boot-dex2oat-threads";
-
/// The Android path of fs-verity build manifest APK for /system.
pub const BUILD_MANIFEST_APK_PATH: &str = "/system/etc/security/fsverity/BuildManifest.apk";
diff --git a/compos/composd/Android.bp b/compos/composd/Android.bp
index 07a9be3..cee4b01 100644
--- a/compos/composd/Android.bp
+++ b/compos/composd/Android.bp
@@ -17,7 +17,6 @@
"libcompos_common",
"libcomposd_native_rust",
"libminijail_rust",
- "libnum_cpus",
"libnix",
"liblibc",
"liblog_rust",
diff --git a/compos/composd/src/instance_manager.rs b/compos/composd/src/instance_manager.rs
index c3d6592..2db13c7 100644
--- a/compos/composd/src/instance_manager.rs
+++ b/compos/composd/src/instance_manager.rs
@@ -21,16 +21,13 @@
use android_system_virtualizationservice::aidl::android::system::virtualizationservice;
use anyhow::{bail, Result};
use binder::Strong;
-use compos_common::compos_client::VmParameters;
-use compos_common::{CURRENT_INSTANCE_DIR, DEX2OAT_THREADS_PROP_NAME, TEST_INSTANCE_DIR};
-use rustutils::system_properties;
-use std::num::NonZeroU32;
-use std::str::FromStr;
+use compos_common::compos_client::{VmCpuTopology, VmParameters};
+use compos_common::{CURRENT_INSTANCE_DIR, TEST_INSTANCE_DIR};
use std::sync::{Arc, Mutex, Weak};
use virtualizationservice::IVirtualizationService::IVirtualizationService;
// Enough memory to complete odrefresh in the VM.
-const VM_MEMORY_MIB: i32 = 1024;
+const VM_MEMORY_MIB: i32 = 1280;
pub struct InstanceManager {
service: Strong<dyn IVirtualizationService>,
@@ -79,16 +76,17 @@
}
fn new_vm_parameters() -> Result<VmParameters> {
- let cpus = match system_properties::read(DEX2OAT_THREADS_PROP_NAME)? {
- Some(s) => Some(NonZeroU32::from_str(&s)?),
- None => {
- // dex2oat uses all CPUs by default. To match the behavior, give the VM all CPUs by
- // default.
- NonZeroU32::new(num_cpus::get() as u32)
- }
- };
+ // By default, dex2oat starts as many threads as there are CPUs. This can be overridden with
+ // a system property. Start the VM with all CPUs and assume the guest will start a suitable
+ // number of dex2oat threads.
+ let cpu_topology = VmCpuTopology::MatchHost;
let task_profiles = vec!["SCHED_SP_COMPUTE".to_string()];
- Ok(VmParameters { cpus, task_profiles, memory_mib: Some(VM_MEMORY_MIB), ..Default::default() })
+ Ok(VmParameters {
+ cpu_topology,
+ task_profiles,
+ memory_mib: Some(VM_MEMORY_MIB),
+ ..Default::default()
+ })
}
// Ensures we only run one instance at a time.
diff --git a/compos/verify/verify.rs b/compos/verify/verify.rs
index 71d8bcc..13e9292 100644
--- a/compos/verify/verify.rs
+++ b/compos/verify/verify.rs
@@ -21,7 +21,7 @@
use anyhow::{bail, Context, Result};
use binder::ProcessState;
use clap::{Parser, ValueEnum};
-use compos_common::compos_client::{ComposClient, VmParameters};
+use compos_common::compos_client::{ComposClient, VmCpuTopology, VmParameters};
use compos_common::odrefresh::{
CURRENT_ARTIFACTS_SUBDIR, ODREFRESH_OUTPUT_ROOT_DIR, PENDING_ARTIFACTS_SUBDIR,
TEST_ARTIFACTS_SUBDIR,
@@ -114,7 +114,11 @@
&idsig,
&idsig_manifest_apk,
&idsig_manifest_ext_apk,
- &VmParameters { debug_mode: args.debug, ..Default::default() },
+ &VmParameters {
+ cpu_topology: VmCpuTopology::OneCpu, // This VM runs very little work at boot
+ debug_mode: args.debug,
+ ..Default::default()
+ },
)?;
let service = vm_instance.connect_service()?;
diff --git a/docs/getting_started/index.md b/docs/getting_started/index.md
index f184862..25eb909 100644
--- a/docs/getting_started/index.md
+++ b/docs/getting_started/index.md
@@ -97,15 +97,6 @@
If you run into problems, inspect the logs produced by `atest`. Their location is printed at the
end. The `host_log_*.zip` file should contain the output of individual commands as well as VM logs.
-### Custom pvmfw
-
-Hostside tests, which run on the PC and extends `MicrodroidHostTestCaseBase`, can be run with
-a custom `pvmfw`. Use `--module-arg` to push `pvmfw` for individual test methods.
-
-```shell
-atest com.android.microdroid.test.MicrodroidHostTests -- --module-arg MicrodroidHostTestCases:set-option:pvmfw:pvmfw.img
-```
-
## Spawning your own VMs with custom kernel
You can spawn your own VMs by passing a JSON config file to the VirtualizationService via the `vm`
diff --git a/javalib/api/system-current.txt b/javalib/api/system-current.txt
index b455c85..1ba479f 100644
--- a/javalib/api/system-current.txt
+++ b/javalib/api/system-current.txt
@@ -57,15 +57,17 @@
public final class VirtualMachineConfig {
method @Nullable public String getApkPath();
- method @NonNull public int getDebugLevel();
+ method public int getCpuTopology();
+ method public int getDebugLevel();
method @IntRange(from=0) public long getEncryptedStorageBytes();
method @IntRange(from=0) public long getMemoryBytes();
- method @IntRange(from=1) public int getNumCpus();
method @Nullable public String getPayloadBinaryName();
method public boolean isCompatibleWith(@NonNull android.system.virtualmachine.VirtualMachineConfig);
method public boolean isEncryptedStorageEnabled();
method public boolean isProtectedVm();
method public boolean isVmOutputCaptured();
+ field public static final int CPU_TOPOLOGY_MATCH_HOST = 1; // 0x1
+ field public static final int CPU_TOPOLOGY_ONE_CPU = 0; // 0x0
field public static final int DEBUG_LEVEL_FULL = 1; // 0x1
field public static final int DEBUG_LEVEL_NONE = 0; // 0x0
}
@@ -74,10 +76,10 @@
ctor public VirtualMachineConfig.Builder(@NonNull android.content.Context);
method @NonNull public android.system.virtualmachine.VirtualMachineConfig build();
method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setApkPath(@NonNull String);
+ method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setCpuTopology(int);
method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setDebugLevel(int);
method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setEncryptedStorageBytes(@IntRange(from=1) long);
method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setMemoryBytes(@IntRange(from=1) long);
- method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setNumCpus(@IntRange(from=1) int);
method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setPayloadBinaryName(@NonNull String);
method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setProtectedVm(boolean);
method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setVmOutputCaptured(boolean);
diff --git a/javalib/jni/android_system_virtualmachine_VirtualMachine.cpp b/javalib/jni/android_system_virtualmachine_VirtualMachine.cpp
index afdc944..9281e73 100644
--- a/javalib/jni/android_system_virtualmachine_VirtualMachine.cpp
+++ b/javalib/jni/android_system_virtualmachine_VirtualMachine.cpp
@@ -57,6 +57,10 @@
};
RpcSessionHandle session;
+ // We need a thread pool to be able to support linkToDeath, or callbacks
+ // (b/268335700). This if a fairly arbitrary number, although it happens to
+ // match the default max outgoing threads.
+ ARpcSession_setMaxIncomingThreads(session.get(), 10);
auto client = ARpcSession_setupPreconnectedClient(session.get(), requestFunc, &args);
return AIBinder_toJavaBinder(env, client);
}
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index ba7174e..902ed72 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -1227,8 +1227,7 @@
if (configPath == null) {
return Collections.emptyList();
}
- try {
- ZipFile zipFile = new ZipFile(context.getPackageCodePath());
+ try (ZipFile zipFile = new ZipFile(context.getPackageCodePath())) {
InputStream inputStream =
zipFile.getInputStream(zipFile.getEntry(configPath));
List<String> apkList =
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
index cb9bad0..c364b42 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -60,7 +60,7 @@
private static final String[] EMPTY_STRING_ARRAY = {};
// These define the schema of the config file persisted on disk.
- private static final int VERSION = 5;
+ private static final int VERSION = 6;
private static final String KEY_VERSION = "version";
private static final String KEY_PACKAGENAME = "packageName";
private static final String KEY_APKPATH = "apkPath";
@@ -69,7 +69,7 @@
private static final String KEY_DEBUGLEVEL = "debugLevel";
private static final String KEY_PROTECTED_VM = "protectedVm";
private static final String KEY_MEMORY_BYTES = "memoryBytes";
- private static final String KEY_NUM_CPUS = "numCpus";
+ private static final String KEY_CPU_TOPOLOGY = "cpuTopology";
private static final String KEY_ENCRYPTED_STORAGE_BYTES = "encryptedStorageBytes";
private static final String KEY_VM_OUTPUT_CAPTURED = "vmOutputCaptured";
@@ -97,6 +97,33 @@
*/
@SystemApi public static final int DEBUG_LEVEL_FULL = 1;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = "CPU_TOPOLOGY_",
+ value = {
+ CPU_TOPOLOGY_ONE_CPU,
+ CPU_TOPOLOGY_MATCH_HOST,
+ })
+ public @interface CpuTopology {}
+
+ /**
+ * Run VM with 1 vCPU. This is the default option, usually the fastest to boot and consuming the
+ * least amount of resources. Typically the best option for small or ephemeral workloads.
+ *
+ * @hide
+ */
+ @SystemApi public static final int CPU_TOPOLOGY_ONE_CPU = 0;
+
+ /**
+ * Run VM with vCPU topology matching the physical CPU topology of the host. Usually takes
+ * longer to boot and cosumes more resources compared to a single vCPU. Typically a good option
+ * for long-running workloads that benefit from parallel execution.
+ *
+ * @hide
+ */
+ @SystemApi public static final int CPU_TOPOLOGY_MATCH_HOST = 1;
+
/** Name of a package whose primary APK contains the VM payload. */
@Nullable private final String mPackageName;
@@ -116,10 +143,8 @@
*/
private final long mMemoryBytes;
- /**
- * Number of vCPUs in the VM. Defaults to 1 when not specified.
- */
- private final int mNumCpus;
+ /** CPU topology configuration of the VM. */
+ @CpuTopology private final int mCpuTopology;
/**
* Path within the APK to the payload config file that defines software aspects of the VM.
@@ -143,7 +168,7 @@
@DebugLevel int debugLevel,
boolean protectedVm,
long memoryBytes,
- int numCpus,
+ @CpuTopology int cpuTopology,
long encryptedStorageBytes,
boolean vmOutputCaptured) {
// This is only called from Builder.build(); the builder handles parameter validation.
@@ -154,7 +179,7 @@
mDebugLevel = debugLevel;
mProtectedVm = protectedVm;
mMemoryBytes = memoryBytes;
- mNumCpus = numCpus;
+ mCpuTopology = cpuTopology;
mEncryptedStorageBytes = encryptedStorageBytes;
mVmOutputCaptured = vmOutputCaptured;
}
@@ -225,7 +250,7 @@
if (memoryBytes != 0) {
builder.setMemoryBytes(memoryBytes);
}
- builder.setNumCpus(b.getInt(KEY_NUM_CPUS));
+ builder.setCpuTopology(b.getInt(KEY_CPU_TOPOLOGY));
long encryptedStorageBytes = b.getLong(KEY_ENCRYPTED_STORAGE_BYTES);
if (encryptedStorageBytes != 0) {
builder.setEncryptedStorageBytes(encryptedStorageBytes);
@@ -258,7 +283,7 @@
b.putString(KEY_PAYLOADBINARYNAME, mPayloadBinaryName);
b.putInt(KEY_DEBUGLEVEL, mDebugLevel);
b.putBoolean(KEY_PROTECTED_VM, mProtectedVm);
- b.putInt(KEY_NUM_CPUS, mNumCpus);
+ b.putInt(KEY_CPU_TOPOLOGY, mCpuTopology);
if (mMemoryBytes > 0) {
b.putLong(KEY_MEMORY_BYTES, mMemoryBytes);
}
@@ -312,7 +337,6 @@
* @hide
*/
@SystemApi
- @NonNull
@DebugLevel
public int getDebugLevel() {
return mDebugLevel;
@@ -341,14 +365,14 @@
}
/**
- * Returns the number of vCPUs that the VM will have.
+ * Returns the CPU topology configuration of the VM.
*
* @hide
*/
@SystemApi
- @IntRange(from = 1)
- public int getNumCpus() {
- return mNumCpus;
+ @CpuTopology
+ public int getCpuTopology() {
+ return mCpuTopology;
}
/**
@@ -455,7 +479,14 @@
}
vsConfig.protectedVm = mProtectedVm;
vsConfig.memoryMib = bytesToMebiBytes(mMemoryBytes);
- vsConfig.numCpus = mNumCpus;
+ switch (mCpuTopology) {
+ case CPU_TOPOLOGY_MATCH_HOST:
+ vsConfig.cpuTopology = android.system.virtualizationservice.CpuTopology.MATCH_HOST;
+ break;
+ default:
+ vsConfig.cpuTopology = android.system.virtualizationservice.CpuTopology.ONE_CPU;
+ break;
+ }
// Don't allow apps to set task profiles ... at least for now.
vsConfig.taskProfiles = EMPTY_STRING_ARRAY;
return vsConfig;
@@ -486,7 +517,7 @@
private boolean mProtectedVm;
private boolean mProtectedVmSet;
private long mMemoryBytes;
- private int mNumCpus = 1;
+ @CpuTopology private int mCpuTopology = CPU_TOPOLOGY_ONE_CPU;
private long mEncryptedStorageBytes;
private boolean mVmOutputCaptured = false;
@@ -555,7 +586,7 @@
mDebugLevel,
mProtectedVm,
mMemoryBytes,
- mNumCpus,
+ mCpuTopology,
mEncryptedStorageBytes,
mVmOutputCaptured);
}
@@ -687,25 +718,21 @@
}
/**
- * Sets the number of vCPUs in the VM. Defaults to 1. Cannot be more than the number of real
- * CPUs (as returned by {@link Runtime#availableProcessors}).
+ * Sets the CPU topology configuration of the VM. Defaults to {@link #CPU_TOPOLOGY_ONE_CPU}.
+ *
+ * <p>This determines how many virtual CPUs will be created, and their performance and
+ * scheduling characteristics, such as affinity masks. Topology also has an effect on memory
+ * usage as each vCPU requires additional memory to keep its state.
*
* @hide
*/
@SystemApi
@NonNull
- public Builder setNumCpus(@IntRange(from = 1) int numCpus) {
- int availableCpus = Runtime.getRuntime().availableProcessors();
- if (numCpus < 1 || numCpus > availableCpus) {
- throw new IllegalArgumentException(
- "Number of vCPUs ("
- + numCpus
- + ") is out of "
- + "range [1, "
- + availableCpus
- + "]");
+ public Builder setCpuTopology(@CpuTopology int cpuTopology) {
+ if (cpuTopology != CPU_TOPOLOGY_ONE_CPU && cpuTopology != CPU_TOPOLOGY_MATCH_HOST) {
+ throw new IllegalArgumentException("Invalid cpuTopology: " + cpuTopology);
}
- mNumCpus = numCpus;
+ mCpuTopology = cpuTopology;
return this;
}
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java b/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
index 483779a..02475a6 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
@@ -34,6 +34,7 @@
*
* @hide
*/
+// TODO(b/268613460): should implement autocloseable.
@SystemApi
public final class VirtualMachineDescriptor implements Parcelable {
@NonNull private final ParcelFileDescriptor mConfigFd;
@@ -49,9 +50,9 @@
@Override
public void writeToParcel(@NonNull Parcel out, int flags) {
- mConfigFd.writeToParcel(out, flags);
- mInstanceImgFd.writeToParcel(out, flags);
- if (mEncryptedStoreFd != null) mEncryptedStoreFd.writeToParcel(out, flags);
+ out.writeParcelable(mConfigFd, flags);
+ out.writeParcelable(mInstanceImgFd, flags);
+ out.writeParcelable(mEncryptedStoreFd, flags);
}
@NonNull
@@ -95,14 +96,19 @@
@NonNull ParcelFileDescriptor configFd,
@NonNull ParcelFileDescriptor instanceImgFd,
@Nullable ParcelFileDescriptor encryptedStoreFd) {
- mConfigFd = configFd;
- mInstanceImgFd = instanceImgFd;
+ mConfigFd = requireNonNull(configFd);
+ mInstanceImgFd = requireNonNull(instanceImgFd);
mEncryptedStoreFd = encryptedStoreFd;
}
private VirtualMachineDescriptor(Parcel in) {
- mConfigFd = requireNonNull(in.readFileDescriptor());
- mInstanceImgFd = requireNonNull(in.readFileDescriptor());
- mEncryptedStoreFd = in.readFileDescriptor();
+ mConfigFd = requireNonNull(readParcelFileDescriptor(in));
+ mInstanceImgFd = requireNonNull(readParcelFileDescriptor(in));
+ mEncryptedStoreFd = readParcelFileDescriptor(in);
+ }
+
+ private ParcelFileDescriptor readParcelFileDescriptor(Parcel in) {
+ return in.readParcelable(
+ ParcelFileDescriptor.class.getClassLoader(), ParcelFileDescriptor.class);
}
}
diff --git a/libs/dice/Android.bp b/libs/dice/Android.bp
index 8017cff..71cf0f1 100644
--- a/libs/dice/Android.bp
+++ b/libs/dice/Android.bp
@@ -11,6 +11,7 @@
prefer_rlib: true,
stdlibs: ["libcore.rust_sysroot"],
rustlibs: [
+ "libdiced_open_dice_nostd",
"libopen_dice_cbor_bindgen_nostd",
"libopen_dice_bcc_bindgen_nostd",
],
diff --git a/libs/dice/src/bcc.rs b/libs/dice/src/bcc.rs
index 6dc0cc3..a7ef882 100644
--- a/libs/dice/src/bcc.rs
+++ b/libs/dice/src/bcc.rs
@@ -16,22 +16,15 @@
//! Wrapper around dice/android/bcc.h.
-use core::ffi::CStr;
use core::mem;
use core::ptr;
-use open_dice_bcc_bindgen::BccConfigValues;
-use open_dice_bcc_bindgen::BccFormatConfigDescriptor;
use open_dice_bcc_bindgen::BccHandoverMainFlow;
use open_dice_bcc_bindgen::BccHandoverParse;
-use open_dice_bcc_bindgen::DiceInputValues;
-use open_dice_bcc_bindgen::BCC_INPUT_COMPONENT_NAME;
-use open_dice_bcc_bindgen::BCC_INPUT_COMPONENT_VERSION;
-use open_dice_bcc_bindgen::BCC_INPUT_RESETTABLE;
-use crate::check_call;
+use crate::check_result;
use crate::Cdi;
-use crate::Error;
+use crate::DiceError;
use crate::InputValues;
use crate::Result;
@@ -57,7 +50,7 @@
// SAFETY - The buffer is only read and never stored and the returned pointers should all
// point within the address range of the buffer or be NULL.
- check_call(unsafe {
+ check_result(unsafe {
BccHandoverParse(
buffer.as_ptr(),
buffer.len(),
@@ -69,20 +62,20 @@
})?;
let cdi_attest = {
- let i = index_from_ptr(buffer, cdi_attest).ok_or(Error::PlatformError)?;
- let s = buffer.get(i..(i + mem::size_of::<Cdi>())).ok_or(Error::PlatformError)?;
- s.try_into().map_err(|_| Error::PlatformError)?
+ let i = index_from_ptr(buffer, cdi_attest).ok_or(DiceError::PlatformError)?;
+ let s = buffer.get(i..(i + mem::size_of::<Cdi>())).ok_or(DiceError::PlatformError)?;
+ s.try_into().map_err(|_| DiceError::PlatformError)?
};
let cdi_seal = {
- let i = index_from_ptr(buffer, cdi_seal).ok_or(Error::PlatformError)?;
- let s = buffer.get(i..(i + mem::size_of::<Cdi>())).ok_or(Error::PlatformError)?;
- s.try_into().map_err(|_| Error::PlatformError)?
+ let i = index_from_ptr(buffer, cdi_seal).ok_or(DiceError::PlatformError)?;
+ let s = buffer.get(i..(i + mem::size_of::<Cdi>())).ok_or(DiceError::PlatformError)?;
+ s.try_into().map_err(|_| DiceError::PlatformError)?
};
let bcc = if bcc.is_null() {
None
} else {
- let i = index_from_ptr(buffer, bcc).ok_or(Error::PlatformError)?;
- Some(buffer.get(i..(i + bcc_size)).ok_or(Error::PlatformError)?)
+ let i = index_from_ptr(buffer, bcc).ok_or(DiceError::PlatformError)?;
+ Some(buffer.get(i..(i + bcc_size)).ok_or(DiceError::PlatformError)?)
};
Ok(Self { buffer, cdi_attest, cdi_seal, bcc })
@@ -94,12 +87,12 @@
let mut size: usize = 0;
// SAFETY - The function only reads `self.buffer`, writes to `buffer` within its bounds,
// reads `input_values` as a constant input and doesn't store any pointer.
- check_call(unsafe {
+ check_result(unsafe {
BccHandoverMainFlow(
context,
self.buffer.as_ptr(),
self.buffer.len(),
- input_values as *const _ as *const DiceInputValues,
+ input_values.as_ptr(),
buffer.len(),
buffer.as_mut_ptr(),
&mut size as *mut usize,
@@ -110,57 +103,6 @@
}
}
-/// Formats a configuration descriptor following the BCC's specification.
-///
-/// ```
-/// BccConfigDescriptor = {
-/// ? -70002 : tstr, ; Component name
-/// ? -70003 : int, ; Component version
-/// ? -70004 : null, ; Resettable
-/// }
-/// ```
-pub fn format_config_descriptor(
- buffer: &mut [u8],
- name: Option<&CStr>,
- version: Option<u64>,
- resettable: bool,
-) -> Result<usize> {
- let mut inputs = 0;
-
- if name.is_some() {
- inputs |= BCC_INPUT_COMPONENT_NAME;
- }
-
- if version.is_some() {
- inputs |= BCC_INPUT_COMPONENT_VERSION;
- }
-
- if resettable {
- inputs |= BCC_INPUT_RESETTABLE;
- }
-
- let values = BccConfigValues {
- inputs,
- component_name: name.map_or(ptr::null(), |p| p.as_ptr()),
- component_version: version.unwrap_or(0),
- };
-
- let mut buffer_size = 0;
-
- // SAFETY - The function writes to the buffer, within the given bounds, and only reads the
- // input values. It writes its result to buffer_size.
- check_call(unsafe {
- BccFormatConfigDescriptor(
- &values as *const _,
- buffer.len(),
- buffer.as_mut_ptr(),
- &mut buffer_size as *mut _,
- )
- })?;
-
- Ok(buffer_size)
-}
-
fn index_from_ptr(slice: &[u8], pointer: *const u8) -> Option<usize> {
if slice.as_ptr_range().contains(&pointer) {
(pointer as usize).checked_sub(slice.as_ptr() as usize)
diff --git a/libs/dice/src/lib.rs b/libs/dice/src/lib.rs
index 5332092..6870eeb 100644
--- a/libs/dice/src/lib.rs
+++ b/libs/dice/src/lib.rs
@@ -18,143 +18,9 @@
#![no_std]
-use core::fmt;
-use core::mem;
-use core::ptr;
-use core::result;
-
-pub use open_dice_cbor_bindgen::DiceMode;
-
-use open_dice_cbor_bindgen::DiceConfigType_kDiceConfigTypeDescriptor as DICE_CONFIG_TYPE_DESCRIPTOR;
-use open_dice_cbor_bindgen::DiceConfigType_kDiceConfigTypeInline as DICE_CONFIG_TYPE_INLINE;
-use open_dice_cbor_bindgen::DiceHash;
-use open_dice_cbor_bindgen::DiceInputValues;
-use open_dice_cbor_bindgen::DiceResult;
-use open_dice_cbor_bindgen::DiceResult_kDiceResultBufferTooSmall as DICE_RESULT_BUFFER_TOO_SMALL;
-use open_dice_cbor_bindgen::DiceResult_kDiceResultInvalidInput as DICE_RESULT_INVALID_INPUT;
-use open_dice_cbor_bindgen::DiceResult_kDiceResultOk as DICE_RESULT_OK;
-use open_dice_cbor_bindgen::DiceResult_kDiceResultPlatformError as DICE_RESULT_PLATFORM_ERROR;
+pub use diced_open_dice::{
+ bcc_format_config_descriptor, check_result, Cdi, Config, DiceError, DiceMode, Hash,
+ InputValues, Result, CDI_SIZE, HASH_SIZE, HIDDEN_SIZE,
+};
pub mod bcc;
-
-const CDI_SIZE: usize = open_dice_cbor_bindgen::DICE_CDI_SIZE as usize;
-const HASH_SIZE: usize = open_dice_cbor_bindgen::DICE_HASH_SIZE as usize;
-const HIDDEN_SIZE: usize = open_dice_cbor_bindgen::DICE_HIDDEN_SIZE as usize;
-const INLINE_CONFIG_SIZE: usize = open_dice_cbor_bindgen::DICE_INLINE_CONFIG_SIZE as usize;
-
-/// Array type of CDIs.
-pub type Cdi = [u8; CDI_SIZE];
-/// Array type of hashes used by DICE.
-pub type Hash = [u8; HASH_SIZE];
-/// Array type of additional input.
-pub type Hidden = [u8; HIDDEN_SIZE];
-/// Array type of inline configuration values.
-pub type InlineConfig = [u8; INLINE_CONFIG_SIZE];
-
-/// Error type used by DICE.
-pub enum Error {
- /// Provided input was invalid.
- InvalidInput,
- /// Provided buffer was too small.
- BufferTooSmall,
- /// Unexpected platform error.
- PlatformError,
- /// Unexpected return value.
- Unknown(DiceResult),
-}
-
-impl fmt::Debug for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match self {
- Error::InvalidInput => write!(f, "invalid input"),
- Error::BufferTooSmall => write!(f, "buffer too small"),
- Error::PlatformError => write!(f, "platform error"),
- Error::Unknown(n) => write!(f, "unknown error: {}", n),
- }
- }
-}
-
-/// Result of DICE functions.
-pub type Result<T> = result::Result<T, Error>;
-
-fn check_call(ret: DiceResult) -> Result<()> {
- match ret {
- DICE_RESULT_OK => Ok(()),
- DICE_RESULT_INVALID_INPUT => Err(Error::InvalidInput),
- DICE_RESULT_BUFFER_TOO_SMALL => Err(Error::BufferTooSmall),
- DICE_RESULT_PLATFORM_ERROR => Err(Error::PlatformError),
- n => Err(Error::Unknown(n)),
- }
-}
-
-/// DICE configuration input type.
-#[derive(Debug)]
-pub enum ConfigType<'a> {
- /// Uses the formatted 64-byte configuration input value (See the Open Profile for DICE).
- Inline(InlineConfig),
- /// Uses the 64-byte hash of more configuration data.
- Descriptor(&'a [u8]),
-}
-
-/// Set of DICE inputs.
-#[repr(transparent)]
-#[derive(Clone, Debug)]
-pub struct InputValues(DiceInputValues);
-
-impl InputValues {
- /// Wrap the DICE inputs in a InputValues, expected by bcc::main_flow().
- pub fn new(
- code_hash: &Hash,
- code_descriptor: Option<&[u8]>,
- config: &ConfigType,
- auth_hash: Option<&Hash>,
- auth_descriptor: Option<&[u8]>,
- mode: DiceMode,
- hidden: Option<&Hidden>,
- ) -> Self {
- const ZEROED_INLINE_CONFIG: InlineConfig = [0; INLINE_CONFIG_SIZE];
- let (config_type, config_value, config_descriptor) = match config {
- ConfigType::Inline(value) => (DICE_CONFIG_TYPE_INLINE, *value, None),
- ConfigType::Descriptor(desc) => {
- (DICE_CONFIG_TYPE_DESCRIPTOR, ZEROED_INLINE_CONFIG, Some(*desc))
- }
- };
- let (code_descriptor, code_descriptor_size) = as_raw_parts(code_descriptor);
- let (config_descriptor, config_descriptor_size) = as_raw_parts(config_descriptor);
- let (authority_descriptor, authority_descriptor_size) = as_raw_parts(auth_descriptor);
-
- Self(DiceInputValues {
- code_hash: *code_hash,
- code_descriptor,
- code_descriptor_size,
- config_type,
- config_value,
- config_descriptor,
- config_descriptor_size,
- authority_hash: auth_hash.map_or([0; mem::size_of::<Hash>()], |h| *h),
- authority_descriptor,
- authority_descriptor_size,
- mode,
- hidden: hidden.map_or([0; mem::size_of::<Hidden>()], |h| *h),
- })
- }
-}
-
-fn ctx() -> *mut core::ffi::c_void {
- core::ptr::null_mut()
-}
-
-/// Hash the provided input using DICE's default hash function.
-pub fn hash(bytes: &[u8]) -> Result<Hash> {
- let mut output: Hash = [0; HASH_SIZE];
- // SAFETY - DiceHash takes a sized input buffer and writes to a constant-sized output buffer.
- check_call(unsafe { DiceHash(ctx(), bytes.as_ptr(), bytes.len(), output.as_mut_ptr()) })?;
- Ok(output)
-}
-
-fn as_raw_parts<T: Sized>(s: Option<&[T]>) -> (*const T, usize) {
- match s {
- Some(s) => (s.as_ptr(), s.len()),
- None => (ptr::null(), 0),
- }
-}
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 9264692..dc59fff 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -85,9 +85,12 @@
"microdroid_property_contexts",
"mke2fs.microdroid",
- // TODO(b/195425111) these should be added automatically
- "libcrypto", // used by many (init_second_stage, microdroid_manager, toybox, etc)
- "liblzma", // used by init_second_stage
+ // Adding more libs manually for unbundled build.
+ // TODO(b/268557568) these should be added automatically.
+ "libcrypto",
+ "liblzma",
+ "libc++",
+ "libssl",
"libvm_payload", // used by payload to interact with microdroid manager
diff --git a/microdroid/payload/config/src/lib.rs b/microdroid/payload/config/src/lib.rs
index 08b8b42..925a543 100644
--- a/microdroid/payload/config/src/lib.rs
+++ b/microdroid/payload/config/src/lib.rs
@@ -40,8 +40,8 @@
pub prefer_staged: bool,
/// Whether to export the tomsbtones (VM crashes) out of VM to host
- /// This does not have a default & the value is expected to be in json for deserialization
- pub export_tombstones: bool,
+ /// Default: true for debuggable VMs, false for non-debuggable VMs
+ pub export_tombstones: Option<bool>,
/// Whether the authfs service should be started in the VM. This enables read or write of host
/// files with integrity checking, but not confidentiality.
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index 383f371..bf2d755 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -19,8 +19,7 @@
"libbinder_rs",
"libbyteorder",
"libcap_rust",
- "libdiced",
- "libdiced_open_dice_cbor",
+ "libdiced_open_dice",
"libdiced_sample_inputs",
"libdiced_utils",
"libglob",
diff --git a/microdroid_manager/src/dice.rs b/microdroid_manager/src/dice.rs
index e740ed4..9a2648f 100644
--- a/microdroid_manager/src/dice.rs
+++ b/microdroid_manager/src/dice.rs
@@ -16,9 +16,8 @@
use anyhow::{bail, Context, Error, Result};
use byteorder::{NativeEndian, ReadBytesExt};
-use diced_open_dice_cbor::{
- Config, ContextImpl, DiceMode, InputValuesOwned, OpenDiceCborContext, CDI_SIZE, HASH_SIZE,
- HIDDEN_SIZE,
+use diced_open_dice::{
+ retry_bcc_main_flow, Cdi, Config, DiceMode, Hash, Hidden, InputValues, OwnedDiceArtifacts,
};
use keystore2_crypto::ZVec;
use libc::{c_void, mmap, munmap, MAP_FAILED, MAP_PRIVATE, PROT_READ};
@@ -30,22 +29,16 @@
use std::ptr::null_mut;
use std::slice;
-/// Artifacts that are kept in the process address space after the artifacts from the driver have
-/// been consumed.
-pub struct DiceContext {
- pub cdi_attest: [u8; CDI_SIZE],
- pub cdi_seal: [u8; CDI_SIZE],
- pub bcc: Vec<u8>,
-}
-
-impl DiceContext {
- pub fn get_sealing_key(&self, salt: &[u8], identifier: &[u8], keysize: u32) -> Result<ZVec> {
- // Deterministically derive a key to use for sealing data based on salt. Use different salt
- // for different keys.
- let mut key = ZVec::new(keysize as usize)?;
- hkdf(&mut key, Md::sha256(), &self.cdi_seal, salt, identifier)?;
- Ok(key)
- }
+/// Derives a sealing key from the DICE sealing CDI.
+pub fn derive_sealing_key(
+ cdi_seal: &Cdi,
+ salt: &[u8],
+ info: &[u8],
+ keysize: usize,
+) -> Result<ZVec> {
+ let mut key = ZVec::new(keysize)?;
+ hkdf(&mut key, Md::sha256(), cdi_seal, salt, info)?;
+ Ok(key)
}
/// Artifacts that are mapped into the process address space from the driver.
@@ -54,11 +47,11 @@
driver_path: PathBuf,
mmap_addr: *mut c_void,
mmap_size: usize,
- cdi_attest: &'a [u8; CDI_SIZE],
- cdi_seal: &'a [u8; CDI_SIZE],
+ cdi_attest: &'a Cdi,
+ cdi_seal: &'a Cdi,
bcc: &'a [u8],
},
- Fake(DiceContext),
+ Fake(OwnedDiceArtifacts),
}
impl DiceDriver<'_> {
@@ -69,13 +62,9 @@
bail!("Strict boot requires DICE value from driver but none were found");
} else {
log::warn!("Using sample DICE values");
- let (cdi_attest, cdi_seal, bcc) = diced_sample_inputs::make_sample_bcc_and_cdis()
+ let dice_artifacts = diced_sample_inputs::make_sample_bcc_and_cdis()
.expect("Failed to create sample dice artifacts.");
- return Ok(Self::Fake(DiceContext {
- cdi_attest: cdi_attest[..].try_into().unwrap(),
- cdi_seal: cdi_seal[..].try_into().unwrap(),
- bcc,
- }));
+ return Ok(Self::Fake(dice_artifacts));
};
let mut file = fs::File::open(driver_path)
@@ -127,36 +116,34 @@
// input key material is already cryptographically strong.
let cdi_seal = match self {
Self::Real { cdi_seal, .. } => cdi_seal,
- Self::Fake(fake) => &fake.cdi_seal,
+ Self::Fake(fake) => &fake.cdi_values.cdi_seal,
};
let salt = &[];
- let mut key = ZVec::new(32)?;
- hkdf(&mut key, Md::sha256(), cdi_seal, salt, identifier)?;
- Ok(key)
+ derive_sealing_key(cdi_seal, salt, identifier, 32)
}
pub fn derive(
self,
- code_hash: [u8; HASH_SIZE],
+ code_hash: Hash,
config_desc: &[u8],
- authority_hash: [u8; HASH_SIZE],
+ authority_hash: Hash,
debug: bool,
- hidden: [u8; HIDDEN_SIZE],
- ) -> Result<DiceContext> {
- let input_values = InputValuesOwned::new(
+ hidden: Hidden,
+ ) -> Result<OwnedDiceArtifacts> {
+ let input_values = InputValues::new(
code_hash,
Config::Descriptor(config_desc),
authority_hash,
- None,
if debug { DiceMode::kDiceModeDebug } else { DiceMode::kDiceModeNormal },
hidden,
);
let (cdi_attest, cdi_seal, bcc) = match &self {
Self::Real { cdi_attest, cdi_seal, bcc, .. } => (*cdi_attest, *cdi_seal, *bcc),
- Self::Fake(fake) => (&fake.cdi_attest, &fake.cdi_seal, fake.bcc.as_slice()),
+ Self::Fake(fake) => {
+ (&fake.cdi_values.cdi_attest, &fake.cdi_values.cdi_seal, fake.bcc.as_slice())
+ }
};
- let (cdi_attest, cdi_seal, bcc) = OpenDiceCborContext::new()
- .bcc_main_flow(cdi_attest, cdi_seal, bcc, &input_values)
+ let dice_artifacts = retry_bcc_main_flow(cdi_attest, cdi_seal, bcc, &input_values)
.context("DICE derive from driver")?;
if let Self::Real { driver_path, .. } = &self {
// Writing to the device wipes the artifacts. The string is ignored by the driver but
@@ -164,11 +151,7 @@
fs::write(driver_path, "wipe")
.map_err(|err| Error::new(err).context("Wiping driver"))?;
}
- Ok(DiceContext {
- cdi_attest: cdi_attest[..].try_into().unwrap(),
- cdi_seal: cdi_seal[..].try_into().unwrap(),
- bcc,
- })
+ Ok(dice_artifacts)
}
}
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 13bc9e3..7ca0d3c 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -21,7 +21,7 @@
mod swap;
mod vm_payload_service;
-use crate::dice::{DiceContext, DiceDriver};
+use crate::dice::{DiceDriver, derive_sealing_key};
use crate::instance::{ApexData, ApkData, InstanceDisk, MicrodroidData, RootHash};
use crate::vm_payload_service::register_vm_payload_service;
use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::ErrorCode::ErrorCode;
@@ -34,6 +34,7 @@
use anyhow::{anyhow, bail, ensure, Context, Error, Result};
use apkverify::{get_public_key_der, verify, V4Signature};
use binder::Strong;
+use diced_open_dice::OwnedDiceArtifacts;
use diced_utils::cbor::{encode_header, encode_number};
use glob::glob;
use itertools::sorted;
@@ -70,13 +71,15 @@
const EXTRA_APK_PATH_PATTERN: &str = "/dev/block/by-name/extra-apk-*";
const EXTRA_IDSIG_PATH_PATTERN: &str = "/dev/block/by-name/extra-idsig-*";
const DM_MOUNTED_APK_PATH: &str = "/dev/block/mapper/microdroid-apk";
-const APKDMVERITY_BIN: &str = "/system/bin/apkdmverity";
-const ZIPFUSE_BIN: &str = "/system/bin/zipfuse";
const AVF_STRICT_BOOT: &str = "/sys/firmware/devicetree/base/chosen/avf,strict-boot";
const AVF_NEW_INSTANCE: &str = "/sys/firmware/devicetree/base/chosen/avf,new-instance";
const DEBUG_MICRODROID_NO_VERIFIED_BOOT: &str =
"/sys/firmware/devicetree/base/virtualization/guest/debug-microdroid,no-verified-boot";
+const APKDMVERITY_BIN: &str = "/system/bin/apkdmverity";
+const ENCRYPTEDSTORE_BIN: &str = "/system/bin/encryptedstore";
+const ZIPFUSE_BIN: &str = "/system/bin/zipfuse";
+
const APEX_CONFIG_DONE_PROP: &str = "apex_config.done";
const TOMBSTONE_TRANSMIT_DONE_PROP: &str = "tombstone_transmit.init_done";
const DEBUGGABLE_PROP: &str = "ro.boot.microdroid.debuggable";
@@ -84,11 +87,9 @@
// SYNC WITH virtualizationservice/src/crosvm.rs
const FAILURE_SERIAL_DEVICE: &str = "/dev/ttyS1";
-/// Identifier for the key used for encrypted store.
const ENCRYPTEDSTORE_BACKING_DEVICE: &str = "/dev/block/by-name/encryptedstore";
-const ENCRYPTEDSTORE_BIN: &str = "/system/bin/encryptedstore";
const ENCRYPTEDSTORE_KEY_IDENTIFIER: &str = "encryptedstore_key";
-const ENCRYPTEDSTORE_KEYSIZE: u32 = 32;
+const ENCRYPTEDSTORE_KEYSIZE: usize = 32;
#[derive(thiserror::Error, Debug)]
enum MicrodroidError {
@@ -268,7 +269,7 @@
dice: DiceDriver,
verified_data: &MicrodroidData,
payload_metadata: &PayloadMetadata,
-) -> Result<DiceContext> {
+) -> Result<OwnedDiceArtifacts> {
// Calculate compound digests of code and authorities
let mut code_hash_ctx = Sha512::new();
let mut authority_hash_ctx = Sha512::new();
@@ -347,6 +348,13 @@
!Path::new(DEBUG_MICRODROID_NO_VERIFIED_BOOT).exists()
}
+fn should_export_tombstones(config: &VmPayloadConfig) -> bool {
+ match config.export_tombstones {
+ Some(b) => b,
+ None => system_properties::read_bool(DEBUGGABLE_PROP, true).unwrap_or(false),
+ }
+}
+
fn try_run_payload(service: &Strong<dyn IVirtualMachineService>) -> Result<i32> {
let metadata = load_metadata().context("Failed to load payload metadata")?;
let dice = DiceDriver::new(Path::new("/dev/open-dice0")).context("Failed to load DICE")?;
@@ -406,12 +414,12 @@
// To minimize the exposure to untrusted data, derive dice profile as soon as possible.
info!("DICE derivation for payload");
- let dice_context = dice_derivation(dice, &verified_data, &payload_metadata)?;
+ let dice_artifacts = dice_derivation(dice, &verified_data, &payload_metadata)?;
// Run encryptedstore binary to prepare the storage
let encryptedstore_child = if Path::new(ENCRYPTEDSTORE_BACKING_DEVICE).exists() {
info!("Preparing encryptedstore ...");
- Some(prepare_encryptedstore(&dice_context).context("encryptedstore run")?)
+ Some(prepare_encryptedstore(&dice_artifacts).context("encryptedstore run")?)
} else {
None
};
@@ -456,7 +464,7 @@
setup_config_sysprops(&config)?;
// Start tombstone_transmit if enabled
- if config.export_tombstones {
+ if should_export_tombstones(&config) {
system_properties::write("tombstone_transmit.start", "1")
.context("set tombstone_transmit.start")?;
} else {
@@ -466,7 +474,7 @@
// Wait until zipfuse has mounted the APKs so we can access the payload
zipfuse.wait_until_done()?;
- register_vm_payload_service(allow_restricted_apis, service.clone(), dice_context)?;
+ register_vm_payload_service(allow_restricted_apis, service.clone(), dice_artifacts)?;
// Wait for encryptedstore to finish mounting the storage (if enabled) before setting
// microdroid_manager.init_done. Reason is init stops uneventd after that.
@@ -481,7 +489,7 @@
.context("set microdroid_manager.init_done")?;
// Wait for tombstone_transmit to init
- if config.export_tombstones {
+ if should_export_tombstones(&config) {
wait_for_tombstone_transmit_done()?;
}
@@ -813,7 +821,7 @@
apexes: vec![],
extra_apks: vec![],
prefer_staged: false,
- export_tombstones: false,
+ export_tombstones: None,
enable_authfs: false,
})
}
@@ -900,7 +908,7 @@
buf.iter().map(|b| format!("{:02X}", b)).collect()
}
-fn prepare_encryptedstore(dice: &DiceContext) -> Result<Child> {
+fn prepare_encryptedstore(dice_artifacts: &OwnedDiceArtifacts) -> Result<Child> {
// Use a fixed salt to scope the derivation to this API.
// Generated using hexdump -vn32 -e'14/1 "0x%02X, " 1 "\n"' /dev/urandom
// TODO(b/241541860) : Move this (& other salts) to a salt container, i.e. a global enum
@@ -909,7 +917,8 @@
0x6F, 0xB3, 0xF9, 0x40, 0xCE, 0xDD, 0x99, 0x40, 0xAA, 0xA7, 0x0E, 0x92, 0x73, 0x90, 0x86,
0x4A, 0x75,
];
- let key = dice.get_sealing_key(
+ let key = derive_sealing_key(
+ &dice_artifacts.cdi_values.cdi_seal,
&salt,
ENCRYPTEDSTORE_KEY_IDENTIFIER.as_bytes(),
ENCRYPTEDSTORE_KEYSIZE,
diff --git a/microdroid_manager/src/vm_payload_service.rs b/microdroid_manager/src/vm_payload_service.rs
index 98b9f2b..ac8f60a 100644
--- a/microdroid_manager/src/vm_payload_service.rs
+++ b/microdroid_manager/src/vm_payload_service.rs
@@ -14,12 +14,12 @@
//! Implementation of the AIDL interface `IVmPayloadService`.
-use crate::dice::DiceContext;
use android_system_virtualization_payload::aidl::android::system::virtualization::payload::IVmPayloadService::{
BnVmPayloadService, IVmPayloadService, VM_PAYLOAD_SERVICE_SOCKET_NAME};
use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::IVirtualMachineService;
use anyhow::Result;
use binder::{Interface, BinderFeatures, ExceptionCode, Status, Strong};
+use diced_open_dice::OwnedDiceArtifacts;
use log::{error, info};
use openssl::hkdf::hkdf;
use openssl::md::Md;
@@ -29,7 +29,7 @@
struct VmPayloadService {
allow_restricted_apis: bool,
virtual_machine_service: Strong<dyn IVirtualMachineService>,
- dice: DiceContext,
+ dice: OwnedDiceArtifacts,
}
impl IVmPayloadService for VmPayloadService {
@@ -48,10 +48,11 @@
0xB7, 0xA8, 0x43, 0x92,
];
let mut secret = vec![0; size.try_into().unwrap()];
- hkdf(&mut secret, Md::sha256(), &self.dice.cdi_seal, &salt, identifier).map_err(|e| {
- error!("Failed to derive VM instance secret: {:?}", e);
- Status::new_service_specific_error(-1, None)
- })?;
+ hkdf(&mut secret, Md::sha256(), &self.dice.cdi_values.cdi_seal, &salt, identifier)
+ .map_err(|e| {
+ error!("Failed to derive VM instance secret: {:?}", e);
+ Status::new_service_specific_error(-1, None)
+ })?;
Ok(secret)
}
@@ -62,7 +63,7 @@
fn getDiceAttestationCdi(&self) -> binder::Result<Vec<u8>> {
self.check_restricted_apis_allowed()?;
- Ok(self.dice.cdi_attest.to_vec())
+ Ok(self.dice.cdi_values.cdi_attest.to_vec())
}
}
@@ -73,7 +74,7 @@
fn new(
allow_restricted_apis: bool,
vm_service: Strong<dyn IVirtualMachineService>,
- dice: DiceContext,
+ dice: OwnedDiceArtifacts,
) -> Self {
Self { allow_restricted_apis, virtual_machine_service: vm_service, dice }
}
@@ -92,7 +93,7 @@
pub(crate) fn register_vm_payload_service(
allow_restricted_apis: bool,
vm_service: Strong<dyn IVirtualMachineService>,
- dice: DiceContext,
+ dice: OwnedDiceArtifacts,
) -> Result<()> {
let vm_payload_binder = BnVmPayloadService::new_binder(
VmPayloadService::new(allow_restricted_apis, vm_service, dice),
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index 21f84a5..0d6a9a4 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -14,7 +14,8 @@
rustlibs: [
"libaarch64_paging",
"libbuddy_system_allocator",
- "libdice_nostd",
+ "libdice_nostd", // TODO(b/267575445): Remove this library once the migration is done.
+ "libdiced_open_dice_nostd",
"libfdtpci",
"liblibfdt",
"liblog_rust_nostd",
diff --git a/pvmfw/src/dice.rs b/pvmfw/src/dice.rs
index 49218b0..f6a1f3d 100644
--- a/pvmfw/src/dice.rs
+++ b/pvmfw/src/dice.rs
@@ -16,12 +16,11 @@
use core::ffi::CStr;
use core::mem::size_of;
-use dice::bcc::format_config_descriptor;
use dice::bcc::Handover;
-use dice::hash;
-use dice::ConfigType;
+use dice::Config;
use dice::DiceMode;
use dice::InputValues;
+use diced_open_dice::{bcc_format_config_descriptor, hash, HIDDEN_SIZE};
use pvmfw_avb::{DebugLevel, Digest, VerifiedBootData};
fn to_dice_mode(debug_level: DebugLevel) -> DiceMode {
@@ -52,23 +51,20 @@
let mode = to_dice_mode(verified_boot_data.debug_level);
let component_name = CStr::from_bytes_with_nul(b"vm_entry\0").unwrap();
let mut config_descriptor_buffer = [0; 128];
- let config_descriptor_size = format_config_descriptor(
- &mut config_descriptor_buffer,
+ let config_descriptor_size = bcc_format_config_descriptor(
Some(component_name),
None, // component_version
false, // resettable
+ &mut config_descriptor_buffer,
)?;
let config = &config_descriptor_buffer[..config_descriptor_size];
- let config = ConfigType::Descriptor(config);
let input_values = InputValues::new(
- &code_hash,
- None, // code_descriptor
- &config,
- Some(&auth_hash),
- None, // auth_descriptor
+ code_hash,
+ Config::Descriptor(config),
+ auth_hash,
mode,
- None, // TODO(b/249723852): Get salt from instance.img (virtio-blk) and/or TRNG.
+ [0u8; HIDDEN_SIZE], // TODO(b/249723852): Get salt from instance.img (virtio-blk) and/or TRNG.
);
bcc.main_flow(&input_values, next_bcc)
diff --git a/rialto/tests/test.rs b/rialto/tests/test.rs
index b25034f..04a87f3 100644
--- a/rialto/tests/test.rs
+++ b/rialto/tests/test.rs
@@ -16,7 +16,7 @@
use android_system_virtualizationservice::{
aidl::android::system::virtualizationservice::{
- VirtualMachineConfig::VirtualMachineConfig,
+ CpuTopology::CpuTopology, VirtualMachineConfig::VirtualMachineConfig,
VirtualMachineRawConfig::VirtualMachineRawConfig,
},
binder::{ParcelFileDescriptor, ProcessState},
@@ -65,7 +65,7 @@
disks: vec![],
protectedVm: false,
memoryMib: 300,
- numCpus: 1,
+ cpuTopology: CpuTopology::ONE_CPU,
platformVersion: "~1.0".to_string(),
taskProfiles: vec![],
});
diff --git a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
index f17000a..e979f30 100644
--- a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
+++ b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
@@ -16,6 +16,8 @@
package com.android.microdroid.benchmark;
+import static android.system.virtualmachine.VirtualMachineConfig.CPU_TOPOLOGY_ONE_CPU;
+import static android.system.virtualmachine.VirtualMachineConfig.CPU_TOPOLOGY_MATCH_HOST;
import static android.system.virtualmachine.VirtualMachineConfig.DEBUG_LEVEL_FULL;
import static android.system.virtualmachine.VirtualMachineConfig.DEBUG_LEVEL_NONE;
@@ -50,8 +52,11 @@
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.OptionalLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
@@ -136,102 +141,87 @@
mInstrumentation.sendStatus(0, bundle);
}
- @Test
- public void testMicrodroidBootTime()
+ private static class BootTimeStats {
+ private final Map<BootTimeMetric, List<Double>> mData = new HashMap<>();
+
+ public BootTimeStats(int trialCount) {
+ for (BootTimeMetric metric : BootTimeMetric.values()) {
+ mData.put(metric, new ArrayList<>(trialCount));
+ }
+ }
+
+ public void collect(BootResult result) {
+ for (BootTimeMetric metric : BootTimeMetric.values()) {
+ OptionalLong value = result.getBootTimeMetricNanoTime(metric);
+ if (value.isPresent()) {
+ mData.get(metric).add(value.getAsLong() / NANO_TO_MILLI);
+ }
+ }
+ }
+
+ public List<Double> get(BootTimeMetric metric) {
+ return Collections.unmodifiableList(mData.get(metric));
+ }
+ }
+
+ private BootTimeStats runBootTimeTest(
+ String name,
+ Function<VirtualMachineConfig.Builder, VirtualMachineConfig.Builder> fnConfig)
throws VirtualMachineException, InterruptedException, IOException {
assume().withMessage("Skip on CF; too slow").that(isCuttlefish()).isFalse();
final int trialCount = 10;
- List<Double> bootTimeMetrics = new ArrayList<>();
+ BootTimeStats stats = new BootTimeStats(trialCount);
for (int i = 0; i < trialCount; i++) {
- VirtualMachineConfig normalConfig =
+ VirtualMachineConfig.Builder builder =
newVmConfigBuilder()
.setPayloadBinaryName("MicrodroidIdleNativeLib.so")
- .setDebugLevel(DEBUG_LEVEL_NONE)
.setMemoryBytes(256 * ONE_MEBI)
- .build();
- forceCreateNewVirtualMachine("test_vm_boot_time", normalConfig);
+ .setDebugLevel(DEBUG_LEVEL_NONE);
+ VirtualMachineConfig config = fnConfig.apply(builder).build();
+ forceCreateNewVirtualMachine(name, config);
- BootResult result = tryBootVm(TAG, "test_vm_boot_time");
+ BootResult result = tryBootVm(TAG, name);
assertThat(result.payloadStarted).isTrue();
-
- bootTimeMetrics.add(result.endToEndNanoTime / NANO_TO_MILLI);
+ stats.collect(result);
}
-
- reportMetrics(bootTimeMetrics, "boot_time", "ms");
+ return stats;
}
@Test
- public void testMicrodroidMulticoreBootTime()
+ public void testMicrodroidBootTime()
throws VirtualMachineException, InterruptedException, IOException {
- assume().withMessage("Skip on CF; too slow").that(isCuttlefish()).isFalse();
+ BootTimeStats stats =
+ runBootTimeTest(
+ "test_vm_boot_time",
+ (builder) -> builder.setCpuTopology(CPU_TOPOLOGY_ONE_CPU));
+ reportMetrics(stats.get(BootTimeMetric.TOTAL), "boot_time", "ms");
+ }
- final int trialCount = 10;
- final int[] trialNumCpus = {2, 4, 8};
-
- for (int numCpus : trialNumCpus) {
- List<Double> bootTimeMetrics = new ArrayList<>();
- for (int i = 0; i < trialCount; i++) {
- VirtualMachineConfig normalConfig =
- newVmConfigBuilder()
- .setPayloadBinaryName("MicrodroidIdleNativeLib.so")
- .setDebugLevel(DEBUG_LEVEL_NONE)
- .setMemoryBytes(256 * ONE_MEBI)
- .setNumCpus(numCpus)
- .build();
- forceCreateNewVirtualMachine("test_vm_boot_time_multicore", normalConfig);
-
- BootResult result = tryBootVm(TAG, "test_vm_boot_time_multicore");
- assertThat(result.payloadStarted).isTrue();
-
- bootTimeMetrics.add(result.endToEndNanoTime / NANO_TO_MILLI);
- }
-
- String metricName = "boot_time_" + numCpus + "cpus";
- reportMetrics(bootTimeMetrics, metricName, "ms");
- }
+ @Test
+ public void testMicrodroidHostCpuTopologyBootTime()
+ throws VirtualMachineException, InterruptedException, IOException {
+ BootTimeStats stats =
+ runBootTimeTest(
+ "test_vm_boot_time_host_topology",
+ (builder) -> builder.setCpuTopology(CPU_TOPOLOGY_MATCH_HOST));
+ reportMetrics(stats.get(BootTimeMetric.TOTAL), "boot_time", "ms");
}
@Test
public void testMicrodroidDebugBootTime()
throws VirtualMachineException, InterruptedException, IOException {
- assume().withMessage("Skip on CF; too slow").that(isCuttlefish()).isFalse();
-
- final int trialCount = 10;
-
- List<Double> vmStartingTimeMetrics = new ArrayList<>();
- List<Double> bootTimeMetrics = new ArrayList<>();
- List<Double> bootloaderTimeMetrics = new ArrayList<>();
- List<Double> kernelBootTimeMetrics = new ArrayList<>();
- List<Double> userspaceBootTimeMetrics = new ArrayList<>();
-
- for (int i = 0; i < trialCount; i++) {
- // To grab boot events from log, set debug mode to FULL
- VirtualMachineConfig normalConfig =
- newVmConfigBuilder()
- .setPayloadBinaryName("MicrodroidIdleNativeLib.so")
- .setDebugLevel(DEBUG_LEVEL_FULL)
- .setVmOutputCaptured(true)
- .setMemoryBytes(256 * ONE_MEBI)
- .build();
- forceCreateNewVirtualMachine("test_vm_boot_time_debug", normalConfig);
-
- BootResult result = tryBootVm(TAG, "test_vm_boot_time_debug");
- assertThat(result.payloadStarted).isTrue();
-
- vmStartingTimeMetrics.add(result.getVMStartingElapsedNanoTime() / NANO_TO_MILLI);
- bootTimeMetrics.add(result.endToEndNanoTime / NANO_TO_MILLI);
- bootloaderTimeMetrics.add(result.getBootloaderElapsedNanoTime() / NANO_TO_MILLI);
- kernelBootTimeMetrics.add(result.getKernelElapsedNanoTime() / NANO_TO_MILLI);
- userspaceBootTimeMetrics.add(result.getUserspaceElapsedNanoTime() / NANO_TO_MILLI);
- }
-
- reportMetrics(vmStartingTimeMetrics, "vm_starting_time", "ms");
- reportMetrics(bootTimeMetrics, "boot_time", "ms");
- reportMetrics(bootloaderTimeMetrics, "bootloader_time", "ms");
- reportMetrics(kernelBootTimeMetrics, "kernel_boot_time", "ms");
- reportMetrics(userspaceBootTimeMetrics, "userspace_boot_time", "ms");
+ BootTimeStats stats =
+ runBootTimeTest(
+ "test_vm_boot_time_debug",
+ (builder) ->
+ builder.setDebugLevel(DEBUG_LEVEL_FULL).setVmOutputCaptured(true));
+ reportMetrics(stats.get(BootTimeMetric.TOTAL), "boot_time", "ms");
+ reportMetrics(stats.get(BootTimeMetric.VM_START), "vm_starting_time", "ms");
+ reportMetrics(stats.get(BootTimeMetric.BOOTLOADER), "bootloader_time", "ms");
+ reportMetrics(stats.get(BootTimeMetric.KERNEL), "kernel_boot_time", "ms");
+ reportMetrics(stats.get(BootTimeMetric.USERSPACE), "userspace_boot_time", "ms");
}
@Test
diff --git a/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java b/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
index c47e915..4dbf4ba 100644
--- a/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
+++ b/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
@@ -79,7 +79,6 @@
private static final int ROUND_IGNORE_STARTUP_TIME = 3;
private static final String APK_NAME = "MicrodroidTestApp.apk";
private static final String PACKAGE_NAME = "com.android.microdroid.test";
- private static final int NUM_VCPUS = 3;
private MetricsProcessor mMetricsProcessor;
@Rule public TestMetrics mMetrics = new TestMetrics();
@@ -95,6 +94,14 @@
@After
public void tearDown() throws Exception {
+ try {
+ testIfDeviceIsCapable(getDevice());
+ } catch (Exception e) {
+ // Suppress execption here.
+ // If we throw exceptions in both setUp() and tearDown(),
+ // then test is reported as fail with org.junit.TestCouldNotBeSkippedException.
+ return;
+ }
// Set PKVM enable and reboot to prevent previous staged session.
if (!isCuttlefish()) {
setPKVMStatusWithRebootToBootloader(true);
@@ -247,7 +254,7 @@
MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath)
.debugLevel("full")
.memoryMib(vm_mem_mb)
- .numCpus(NUM_VCPUS)
+ .cpuTopology("match_host")
.build(device);
microdroidDevice.waitForBootComplete(30000);
microdroidDevice.enableAdbRoot();
diff --git a/tests/helper/src/java/com/android/microdroid/test/common/DeviceProperties.java b/tests/helper/src/java/com/android/microdroid/test/common/DeviceProperties.java
index 94f7e99..ba82c38 100644
--- a/tests/helper/src/java/com/android/microdroid/test/common/DeviceProperties.java
+++ b/tests/helper/src/java/com/android/microdroid/test/common/DeviceProperties.java
@@ -26,9 +26,11 @@
}
private static final String KEY_VENDOR_DEVICE = "ro.product.vendor.device";
+ private static final String KEY_BUILD_TYPE = "ro.build.type";
private static final String KEY_METRICS_TAG = "debug.hypervisor.metrics_tag";
private static final String CUTTLEFISH_DEVICE_PREFIX = "vsoc_";
+ private static final String USER_BUILD_TYPE = "user";
private final PropertyGetter mPropertyGetter;
@@ -49,6 +51,13 @@
return vendorDeviceName != null && vendorDeviceName.startsWith(CUTTLEFISH_DEVICE_PREFIX);
}
+ /**
+ * @return whether the device is user build.
+ */
+ public boolean isUserBuild() {
+ return USER_BUILD_TYPE.equals(getProperty(KEY_BUILD_TYPE));
+ }
+
public String getMetricsTag() {
return getProperty(KEY_METRICS_TAG);
}
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 f1da43a..9ec36b3 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
@@ -148,8 +148,9 @@
private OptionalLong mPayloadStartedNanoTime = OptionalLong.empty();
private StringBuilder mConsoleOutput = new StringBuilder();
private StringBuilder mLogOutput = new StringBuilder();
+ private boolean mProcessedBootTimeMetrics = false;
- private void processBootEvents(String log) {
+ private void processBootTimeMetrics(String log) {
if (!mVcpuStartedNanoTime.isPresent()) {
mVcpuStartedNanoTime = OptionalLong.of(System.nanoTime());
}
@@ -165,12 +166,13 @@
}
}
- private void logVmOutputAndMonitorBootEvents(
+ private void logVmOutputAndMonitorBootTimeMetrics(
String tag,
InputStream vmOutputStream,
String name,
StringBuilder result,
boolean monitorEvents) {
+ mProcessedBootTimeMetrics |= monitorEvents;
new Thread(
() -> {
try {
@@ -180,7 +182,7 @@
String line;
while ((line = reader.readLine()) != null
&& !Thread.interrupted()) {
- if (monitorEvents) processBootEvents(line);
+ if (monitorEvents) processBootTimeMetrics(line);
Log.i(tag, name + ": " + line);
result.append(line + "\n");
}
@@ -191,15 +193,15 @@
.start();
}
- private void logVmOutputAndMonitorBootEvents(
+ private void logVmOutputAndMonitorBootTimeMetrics(
String tag, InputStream vmOutputStream, String name, StringBuilder result) {
- logVmOutputAndMonitorBootEvents(tag, vmOutputStream, name, result, true);
+ logVmOutputAndMonitorBootTimeMetrics(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, StringBuilder result) {
- logVmOutputAndMonitorBootEvents(tag, vmOutputStream, name, result, false);
+ logVmOutputAndMonitorBootTimeMetrics(tag, vmOutputStream, name, result, false);
}
public void runToFinish(String logTag, VirtualMachine vm)
@@ -207,7 +209,7 @@
vm.setCallback(mExecutorService, this);
vm.run();
if (vm.getConfig().isVmOutputCaptured()) {
- logVmOutputAndMonitorBootEvents(
+ logVmOutputAndMonitorBootTimeMetrics(
logTag, vm.getConsoleOutput(), "Console", mConsoleOutput);
logVmOutput(logTag, vm.getLogOutput(), "Log", mLogOutput);
}
@@ -238,6 +240,10 @@
return mLogOutput.toString();
}
+ public boolean hasProcessedBootTimeMetrics() {
+ return mProcessedBootTimeMetrics;
+ }
+
protected void forceStop(VirtualMachine vm) {
try {
vm.stop();
@@ -266,12 +272,21 @@
}
}
+ public enum BootTimeMetric {
+ TOTAL,
+ VM_START,
+ BOOTLOADER,
+ KERNEL,
+ USERSPACE,
+ }
+
public static class BootResult {
public final boolean payloadStarted;
public final int deathReason;
public final long apiCallNanoTime;
public final long endToEndNanoTime;
+ public final boolean processedBootTimeMetrics;
public final OptionalLong vcpuStartedNanoTime;
public final OptionalLong kernelStartedNanoTime;
public final OptionalLong initStartedNanoTime;
@@ -285,6 +300,7 @@
int deathReason,
long apiCallNanoTime,
long endToEndNanoTime,
+ boolean processedBootTimeMetrics,
OptionalLong vcpuStartedNanoTime,
OptionalLong kernelStartedNanoTime,
OptionalLong initStartedNanoTime,
@@ -295,6 +311,7 @@
this.payloadStarted = payloadStarted;
this.deathReason = deathReason;
this.endToEndNanoTime = endToEndNanoTime;
+ this.processedBootTimeMetrics = processedBootTimeMetrics;
this.vcpuStartedNanoTime = vcpuStartedNanoTime;
this.kernelStartedNanoTime = kernelStartedNanoTime;
this.initStartedNanoTime = initStartedNanoTime;
@@ -336,6 +353,27 @@
public long getUserspaceElapsedNanoTime() {
return getPayloadStartedNanoTime() - getInitStartedNanoTime();
}
+
+ public OptionalLong getBootTimeMetricNanoTime(BootTimeMetric metric) {
+ if (metric == BootTimeMetric.TOTAL) {
+ return OptionalLong.of(endToEndNanoTime);
+ }
+
+ if (processedBootTimeMetrics) {
+ switch (metric) {
+ case VM_START:
+ return OptionalLong.of(getVMStartingElapsedNanoTime());
+ case BOOTLOADER:
+ return OptionalLong.of(getBootloaderElapsedNanoTime());
+ case KERNEL:
+ return OptionalLong.of(getKernelElapsedNanoTime());
+ case USERSPACE:
+ return OptionalLong.of(getUserspaceElapsedNanoTime());
+ }
+ }
+
+ return OptionalLong.empty();
+ }
}
public BootResult tryBootVm(String logTag, String vmName)
@@ -366,6 +404,7 @@
deathReason.getNow(VmEventListener.STOP_REASON_INFRASTRUCTURE_ERROR),
apiCallNanoTime,
endTime.getNow(apiCallNanoTime) - apiCallNanoTime,
+ listener.hasProcessedBootTimeMetrics(),
listener.getVcpuStartedNanoTime(),
listener.getKernelStartedNanoTime(),
listener.getInitStartedNanoTime(),
diff --git a/tests/hostside/helper/Android.bp b/tests/hostside/helper/Android.bp
index 6196ec5..e8b6f36 100644
--- a/tests/hostside/helper/Android.bp
+++ b/tests/hostside/helper/Android.bp
@@ -6,6 +6,7 @@
name: "MicrodroidHostTestHelper",
srcs: ["java/**/*.java"],
libs: [
+ "androidx.annotation_annotation",
"compatibility-tradefed",
"tradefed",
"truth-prebuilt",
diff --git a/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java b/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
index 8d328bc..20a6045 100644
--- a/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
+++ b/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
@@ -29,7 +29,6 @@
import com.android.microdroid.test.common.DeviceProperties;
import com.android.microdroid.test.common.MetricsProcessor;
import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.config.Option;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.TestDevice;
@@ -42,15 +41,12 @@
import java.util.Arrays;
public abstract class MicrodroidHostTestCaseBase extends BaseHostJUnit4Test {
-
protected static final String TEST_ROOT = "/data/local/tmp/virt/";
protected static final String LOG_PATH = TEST_ROOT + "log.txt";
protected static final String CONSOLE_PATH = TEST_ROOT + "console.txt";
private static final int TEST_VM_ADB_PORT = 8000;
private static final String MICRODROID_SERIAL = "localhost:" + TEST_VM_ADB_PORT;
private static final String INSTANCE_IMG = "instance.img";
- private static final String PVMFW_IMG_PATH = TEST_ROOT + "pvmfw.img";
- private static final String PVMFW_IMG_PATH_PROP = "hypervisor.pvmfw.path";
private static final long MICRODROID_ADB_CONNECT_TIMEOUT_MINUTES = 5;
protected static final long MICRODROID_COMMAND_TIMEOUT_MILLIS = 30000;
@@ -59,19 +55,6 @@
(int) (MICRODROID_ADB_CONNECT_TIMEOUT_MINUTES * 60 * 1000
/ MICRODROID_COMMAND_RETRY_INTERVAL_MILLIS);
- @Option(
- name = "pvmfw",
- description =
- "Custom pvmfw.img path on host device."
- + " If present, it will be pushed to "
- + PVMFW_IMG_PATH,
- mandatory = false)
- private static String sCustomPvmfwPathOnHost = "";
-
- private static boolean isEmptyText(String str) {
- return str == null || str.length() == 0;
- }
-
public static void prepareVirtualizationTestSetup(ITestDevice androidDevice)
throws DeviceNotAvailableException {
CommandRunner android = new CommandRunner(androidDevice);
@@ -84,13 +67,6 @@
// remove any leftover files under test root
android.tryRun("rm", "-rf", TEST_ROOT + "*");
-
- // prepare custom pvmfw.img if necessary
- if (!isEmptyText(sCustomPvmfwPathOnHost)) {
- runOnHost("adb", "root");
- runOnHost("adb", "push", sCustomPvmfwPathOnHost, PVMFW_IMG_PATH);
- runOnHost("adb", "shell", "setprop", PVMFW_IMG_PATH_PROP, PVMFW_IMG_PATH);
- }
}
public static void cleanUpVirtualizationTestSetup(ITestDevice androidDevice)
@@ -104,10 +80,10 @@
android.tryRun("killall", "crosvm");
android.tryRun("stop", "virtualizationservice");
android.tryRun("rm", "-rf", "/data/misc/virtualizationservice/*");
+ }
- if (!isEmptyText(sCustomPvmfwPathOnHost)) {
- runOnHost("adb", "shell", "setprop", PVMFW_IMG_PATH_PROP, "\"\"");
- }
+ public boolean isUserBuild() {
+ return DeviceProperties.create(getDevice()::getProperty).isUserBuild();
}
protected boolean isCuttlefish() {
diff --git a/tests/hostside/helper/java/com/android/microdroid/test/host/Pvmfw.java b/tests/hostside/helper/java/com/android/microdroid/test/host/Pvmfw.java
new file mode 100644
index 0000000..95eaa58
--- /dev/null
+++ b/tests/hostside/helper/java/com/android/microdroid/test/host/Pvmfw.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.microdroid.test.host;
+
+import static java.nio.ByteOrder.LITTLE_ENDIAN;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Objects;
+import java.nio.ByteBuffer;
+
+/** pvmfw.bin with custom config payloads on host. */
+public final class Pvmfw {
+ private static final int SIZE_8B = 8; // 8 bytes
+ private static final int SIZE_4K = 4 << 10; // 4 KiB, PAGE_SIZE
+ private static final int BUFFER_SIZE = 1024;
+ private static final int HEADER_SIZE = Integer.BYTES * 8; // Header has 8 integers.
+ private static final int HEADER_MAGIC = 0x666d7670;
+ private static final int HEADER_VERSION = getVersion(1, 0);
+ private static final int HEADER_FLAGS = 0;
+
+ @NonNull private final File mPvmfwBinFile;
+ @NonNull private final File mBccFile;
+ @Nullable private final File mDebugPolicyFile;
+
+ private Pvmfw(
+ @NonNull File pvmfwBinFile, @NonNull File bccFile, @Nullable File debugPolicyFile) {
+ mPvmfwBinFile = Objects.requireNonNull(pvmfwBinFile);
+ mBccFile = Objects.requireNonNull(bccFile);
+ mDebugPolicyFile = debugPolicyFile;
+ }
+
+ /**
+ * Serializes pvmfw.bin and its config, as written in the <a
+ * href="https://android.googlesource.com/platform/packages/modules/Virtualization/+/master/pvmfw/README.md">README.md</a>
+ */
+ public void serialize(@NonNull File outFile) throws IOException {
+ Objects.requireNonNull(outFile);
+
+ int bccOffset = HEADER_SIZE;
+ int bccSize = (int) mBccFile.length();
+
+ int debugPolicyOffset = alignTo(bccOffset + bccSize, SIZE_8B);
+ int debugPolicySize = mDebugPolicyFile == null ? 0 : (int) mDebugPolicyFile.length();
+
+ int totalSize = debugPolicyOffset + debugPolicySize;
+
+ ByteBuffer header = ByteBuffer.allocate(HEADER_SIZE).order(LITTLE_ENDIAN);
+ header.putInt(HEADER_MAGIC);
+ header.putInt(HEADER_VERSION);
+ header.putInt(totalSize);
+ header.putInt(HEADER_FLAGS);
+ header.putInt(bccOffset);
+ header.putInt(bccSize);
+ header.putInt(debugPolicyOffset);
+ header.putInt(debugPolicySize);
+
+ try (FileOutputStream pvmfw = new FileOutputStream(outFile)) {
+ appendFile(pvmfw, mPvmfwBinFile);
+ padTo(pvmfw, SIZE_4K);
+ pvmfw.write(header.array());
+ padTo(pvmfw, HEADER_SIZE);
+ appendFile(pvmfw, mBccFile);
+ if (mDebugPolicyFile != null) {
+ padTo(pvmfw, SIZE_8B);
+ appendFile(pvmfw, mDebugPolicyFile);
+ }
+ padTo(pvmfw, SIZE_4K);
+ }
+ }
+
+ private void appendFile(@NonNull FileOutputStream out, @NonNull File inFile)
+ throws IOException {
+ byte buffer[] = new byte[BUFFER_SIZE];
+ try (FileInputStream in = new FileInputStream(inFile)) {
+ int size;
+ while (true) {
+ size = in.read(buffer);
+ if (size < 0) {
+ return;
+ }
+ out.write(buffer, /* offset= */ 0, size);
+ }
+ }
+ }
+
+ private void padTo(@NonNull FileOutputStream out, int size) throws IOException {
+ int streamSize = (int) out.getChannel().size();
+ for (int i = streamSize; i < alignTo(streamSize, size); i++) {
+ out.write(0); // write byte.
+ }
+ }
+
+ private static int alignTo(int x, int size) {
+ return (x + size - 1) & ~(size - 1);
+ }
+
+ private static int getVersion(int major, int minor) {
+ return ((major & 0xFFFF) << 16) | (minor & 0xFFFF);
+ }
+
+ /** Builder for {@link Pvmfw}. */
+ public static final class Builder {
+ @NonNull private final File mPvmfwBinFile;
+ @NonNull private final File mBccFile;
+ @Nullable private File mDebugPolicyFile;
+
+ public Builder(@NonNull File pvmfwBinFile, @NonNull File bccFile) {
+ mPvmfwBinFile = Objects.requireNonNull(pvmfwBinFile);
+ mBccFile = Objects.requireNonNull(bccFile);
+ }
+
+ @NonNull
+ public Builder setDebugPolicyOverlay(@Nullable File debugPolicyFile) {
+ mDebugPolicyFile = debugPolicyFile;
+ return this;
+ }
+
+ @NonNull
+ public Pvmfw build() {
+ return new Pvmfw(mPvmfwBinFile, mBccFile, mDebugPolicyFile);
+ }
+ }
+}
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index 0623ff2..a780a8b 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -55,6 +55,7 @@
import org.json.JSONObject;
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
@@ -91,9 +92,6 @@
private static final int MIN_MEM_ARM64 = 145;
private static final int MIN_MEM_X86_64 = 196;
- // Number of vCPUs for testing purpose
- private static final int NUM_VCPUS = 3;
-
private static final int BOOT_COMPLETE_TIMEOUT = 30000; // 30 seconds
private static final Pattern sCIDPattern = Pattern.compile("with CID (\\d+)");
@@ -221,6 +219,14 @@
assertThat(callable.call(), matcher);
}
+ private int getDeviceNumCpus(CommandRunner runner) throws DeviceNotAvailableException {
+ return Integer.parseInt(runner.run("nproc --all").trim());
+ }
+
+ private int getDeviceNumCpus(ITestDevice device) throws DeviceNotAvailableException {
+ return getDeviceNumCpus(new CommandRunner(device));
+ }
+
static class ActiveApexInfo {
public String name;
public String path;
@@ -442,7 +448,7 @@
MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath)
.debugLevel("full")
.memoryMib(minMemorySize())
- .numCpus(NUM_VCPUS)
+ .cpuTopology("match_host")
.protectedVm(protectedVm)
.build(getAndroidDevice());
@@ -513,24 +519,7 @@
vmInfo.mProcess.destroy();
}
- private boolean isTombstoneGenerated(String configPath, String... crashCommand)
- throws Exception {
- // Note this test relies on logcat values being printed by tombstone_transmit on
- // and the reeceiver on host (virtualization_service)
- mMicrodroidDevice =
- MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath)
- .debugLevel("full")
- .memoryMib(minMemorySize())
- .numCpus(NUM_VCPUS)
- .build(getAndroidDevice());
- mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT);
- mMicrodroidDevice.enableAdbRoot();
-
- CommandRunner microdroid = new CommandRunner(mMicrodroidDevice);
- microdroid.run(crashCommand);
-
- // check until microdroid is shut down
- CommandRunner android = new CommandRunner(getDevice());
+ private void waitForCrosvmExit(CommandRunner android) throws Exception {
// TODO: improve crosvm exit check. b/258848245
android.runWithTimeout(
15000,
@@ -539,8 +528,12 @@
"1",
"-e",
"'virtualizationmanager::crosvm.*exited with status exit status:'");
+ }
- // Check that tombstone is received (from host logcat)
+ private boolean isTombstoneReceivedFromHostLogcat() throws Exception {
+ // Note this method relies on logcat values being printed by the receiver on host
+ // userspace crash log: virtualizationservice/src/aidl.rs
+ // kernel ramdump log: virtualizationmanager/src/crosvm.rs
String ramdumpRegex =
"Received [0-9]+ bytes from guest & wrote to tombstone file|"
+ "Ramdump \"[^ ]+/ramdump\" sent to tombstoned";
@@ -560,11 +553,32 @@
return !result.trim().isEmpty();
}
+ private boolean isTombstoneGeneratedWithCmd(String configPath, String... crashCommand)
+ throws Exception {
+ mMicrodroidDevice =
+ MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath)
+ .debugLevel("full")
+ .memoryMib(minMemorySize())
+ .cpuTopology("match_host")
+ .build(getAndroidDevice());
+ mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT);
+ mMicrodroidDevice.enableAdbRoot();
+
+ CommandRunner microdroid = new CommandRunner(mMicrodroidDevice);
+ microdroid.run(crashCommand);
+
+ // check until microdroid is shut down
+ CommandRunner android = new CommandRunner(getDevice());
+ waitForCrosvmExit(android);
+
+ return isTombstoneReceivedFromHostLogcat();
+ }
+
@Test
public void testTombstonesAreGeneratedUponUserspaceCrash() throws Exception {
assertThat(
- isTombstoneGenerated(
- "assets/vm_config_crash.json",
+ isTombstoneGeneratedWithCmd(
+ "assets/vm_config.json",
"kill",
"-SIGSEGV",
"$(pidof microdroid_launcher)"))
@@ -574,8 +588,8 @@
@Test
public void testTombstonesAreNotGeneratedIfNotExportedUponUserspaceCrash() throws Exception {
assertThat(
- isTombstoneGenerated(
- "assets/vm_config_crash_no_tombstone.json",
+ isTombstoneGeneratedWithCmd(
+ "assets/vm_config_no_tombstone.json",
"kill",
"-SIGSEGV",
"$(pidof microdroid_launcher)"))
@@ -583,18 +597,50 @@
}
@Test
+ @Ignore("b/243630590: Temporal workaround until lab devices has flashed new DPM")
public void testTombstonesAreGeneratedUponKernelCrash() throws Exception {
assumeFalse("Cuttlefish is not supported", isCuttlefish());
+ assumeFalse("Skipping test because ramdump is disabled on user build", isUserBuild());
assertThat(
- isTombstoneGenerated(
- "assets/vm_config_crash.json",
- "echo",
- "c",
- ">",
- "/proc/sysrq-trigger"))
+ isTombstoneGeneratedWithCmd(
+ "assets/vm_config.json", "echo", "c", ">", "/proc/sysrq-trigger"))
.isTrue();
}
+ private boolean isTombstoneGeneratedWithCrashPayload(boolean debuggable) throws Exception {
+ // we can't use microdroid builder as it wants ADB connection (debuggable)
+ CommandRunner android = new CommandRunner(getDevice());
+
+ android.run("rm", "-rf", TEST_ROOT + "*");
+ android.run("mkdir", "-p", TEST_ROOT + "*");
+
+ final String apkPath = getPathForPackage(PACKAGE_NAME);
+ final String idsigPath = TEST_ROOT + "idsig";
+ final String instanceImgPath = TEST_ROOT + "instance.img";
+ android.run(
+ VIRT_APEX + "bin/vm",
+ "run-app",
+ "--payload-binary-name",
+ "MicrodroidCrashNativeLib.so",
+ "--debug",
+ debuggable ? "full" : "none",
+ apkPath,
+ idsigPath,
+ instanceImgPath);
+
+ return isTombstoneReceivedFromHostLogcat();
+ }
+
+ @Test
+ public void testTombstonesAreGeneratedWithCrashPayload() throws Exception {
+ assertThat(isTombstoneGeneratedWithCrashPayload(true /* debuggable */)).isTrue();
+ }
+
+ @Test
+ public void testTombstonesAreNotGeneratedWithCrashPayloadWhenNonDebuggable() throws Exception {
+ assertThat(isTombstoneGeneratedWithCrashPayload(false /* debuggable */)).isFalse();
+ }
+
@Test
public void testTelemetryPushedAtoms() throws Exception {
// Reset statsd config and report before the test
@@ -616,7 +662,7 @@
MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath)
.debugLevel("full")
.memoryMib(minMemorySize())
- .numCpus(NUM_VCPUS)
+ .cpuTopology("match_host")
.build(device);
microdroid.waitForBootComplete(BOOT_COMPLETE_TIMEOUT);
device.shutdownMicrodroid(microdroid);
@@ -644,7 +690,7 @@
assertThat(atomVmCreationRequested.getVmIdentifier()).isEqualTo("VmRunApp");
assertThat(atomVmCreationRequested.getConfigType())
.isEqualTo(AtomsProto.VmCreationRequested.ConfigType.VIRTUAL_MACHINE_APP_CONFIG);
- assertThat(atomVmCreationRequested.getNumCpus()).isEqualTo(NUM_VCPUS);
+ assertThat(atomVmCreationRequested.getNumCpus()).isEqualTo(getDeviceNumCpus(device));
assertThat(atomVmCreationRequested.getMemoryMib()).isEqualTo(minMemorySize());
assertThat(atomVmCreationRequested.getApexes())
.isEqualTo("com.android.art:com.android.compos:com.android.sdkext");
@@ -679,7 +725,7 @@
MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath)
.debugLevel("full")
.memoryMib(minMemorySize())
- .numCpus(NUM_VCPUS)
+ .cpuTopology("match_host")
.build(getAndroidDevice());
mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT);
CommandRunner microdroid = new CommandRunner(mMicrodroidDevice);
@@ -708,8 +754,7 @@
assertThat(android.tryRun("egrep", "'avc:[[:space:]]{1,2}denied'", LOG_PATH)).isNull();
assertThat(android.tryRun("egrep", "'avc:[[:space:]]{1,2}denied'", CONSOLE_PATH)).isNull();
- assertThat(microdroid.run("cat /proc/cpuinfo | grep processor | wc -l"))
- .isEqualTo(Integer.toString(NUM_VCPUS));
+ assertThat(getDeviceNumCpus(microdroid)).isEqualTo(getDeviceNumCpus(android));
// Check that selinux is enabled
assertThat(microdroid.run("getenforce")).isEqualTo("Enforcing");
@@ -745,7 +790,7 @@
MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath)
.debugLevel("full")
.memoryMib(minMemorySize())
- .numCpus(NUM_VCPUS)
+ .cpuTopology("match_host")
.build(getAndroidDevice());
mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT);
mMicrodroidDevice.enableAdbRoot();
diff --git a/tests/hostside/tools/Android.bp b/tests/hostside/tools/Android.bp
new file mode 100644
index 0000000..f3cc275
--- /dev/null
+++ b/tests/hostside/tools/Android.bp
@@ -0,0 +1,10 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_binary_host {
+ name: "pvmfw-tool",
+ manifest: "pvmfw-tool-manifest.txt",
+ srcs: ["PvmfwTool.java"],
+ static_libs: ["MicrodroidHostTestHelper"],
+}
diff --git a/tests/hostside/tools/PvmfwTool.java b/tests/hostside/tools/PvmfwTool.java
new file mode 100644
index 0000000..18dd6d7
--- /dev/null
+++ b/tests/hostside/tools/PvmfwTool.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.microdroid;
+
+import com.android.microdroid.test.host.Pvmfw;
+
+import java.io.File;
+import java.io.IOException;
+
+/** CLI for {@link com.android.microdroid.test.host.Pvmfw}. */
+public class PvmfwTool {
+ public static void printUsage() {
+ System.out.println("pvmfw-tool: Appends pvmfw.bin and config payloads.");
+ System.out.println("Requires BCC and debug policy dtbo files");
+ System.out.println("");
+ System.out.println("Usage: pvmfw-tool <pvmfw_with_config> <pvmfw_bin> <bcc.dat> <dp.dtbo>");
+ }
+
+ public static void main(String[] args) {
+ if (args.length != 4) {
+ printUsage();
+ System.exit(1);
+ }
+
+ File out = new File(args[0]);
+ File pvmfw_bin = new File(args[1]);
+ File bcc_dat = new File(args[2]);
+ File dtbo = new File(args[3]);
+
+ try {
+ Pvmfw pvmfw = new Pvmfw.Builder(pvmfw_bin, bcc_dat).setDebugPolicyOverlay(dtbo).build();
+ pvmfw.serialize(out);
+ } catch (IOException e) {
+ e.printStackTrace();
+ printUsage();
+ System.exit(1);
+ }
+ }
+}
diff --git a/tests/hostside/tools/pvmfw-tool-manifest.txt b/tests/hostside/tools/pvmfw-tool-manifest.txt
new file mode 100644
index 0000000..dc71fd2
--- /dev/null
+++ b/tests/hostside/tools/pvmfw-tool-manifest.txt
@@ -0,0 +1,2 @@
+Manifest-Version: 1.0
+Main-Class: com.android.microdroid.PvmfwTool
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index 5f9b915..bafab53 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -2,12 +2,26 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
-android_test {
- name: "MicrodroidTestApp",
+java_defaults {
+ name: "MicrodroidTestAppsDefaults",
test_suites: [
"cts",
"general-tests",
],
+ static_libs: [
+ "com.android.microdroid.testservice-java",
+ "com.android.microdroid.test.vmshare_service-java",
+ ],
+ sdk_version: "test_current",
+ jni_uses_platform_apis: true,
+ use_embedded_native_libs: true,
+ // We only support 64-bit ABI, but CTS demands all APKs to be multi-ABI.
+ compile_multilib: "both",
+}
+
+android_test {
+ name: "MicrodroidTestApp",
+ defaults: ["MicrodroidTestAppsDefaults"],
srcs: ["src/java/**/*.java"],
static_libs: [
"MicrodroidDeviceTestHelper",
@@ -15,27 +29,27 @@
"androidx.test.ext.junit",
"authfs_test_apk_assets",
"cbor-java",
- "com.android.microdroid.testservice-java",
"truth-prebuilt",
"compatibility-common-util-devicesidelib",
],
- sdk_version: "test_current",
jni_libs: [
"MicrodroidTestNativeLib",
"MicrodroidIdleNativeLib",
"MicrodroidEmptyNativeLib",
"MicrodroidExitNativeLib",
"MicrodroidPrivateLinkingNativeLib",
+ "MicrodroidCrashNativeLib",
],
- jni_uses_platform_apis: true,
- use_embedded_native_libs: true,
- // We only support 64-bit ABI, but CTS demands all APKs to be multi-ABI.
- compile_multilib: "both",
min_sdk_version: "33",
+ // Defined in ../vmshareapp/Android.bp
+ data: [":MicrodroidVmShareApp"],
}
-cc_library_shared {
- name: "MicrodroidTestNativeLib",
+// Defaults shared between MicrodroidTestNativeLib and MicrodroidPayloadInOtherAppNativeLib shared
+// libs. They are expected to share everything apart from the name, so that one app
+// (MicrodroidTestApp) can start a payload defined in the another app (MicrodroidVmShareApp).
+cc_defaults {
+ name: "MicrodroidTestNativeLibDefaults",
srcs: ["src/native/testbinary.cpp"],
stl: "libc++_static",
header_libs: ["vm_payload_restricted_headers"],
@@ -55,6 +69,16 @@
}
cc_library_shared {
+ name: "MicrodroidPayloadInOtherAppNativeLib",
+ defaults: ["MicrodroidTestNativeLibDefaults"],
+}
+
+cc_library_shared {
+ name: "MicrodroidTestNativeLib",
+ defaults: ["MicrodroidTestNativeLibDefaults"],
+}
+
+cc_library_shared {
name: "MicrodroidTestNativeLibSub",
srcs: ["src/native/testlib.cpp"],
stl: "libc++_static",
@@ -92,3 +116,11 @@
shared_libs: ["libselinux#latest"],
stl: "libc++_static",
}
+
+// A payload that crashes immediately on start
+cc_library_shared {
+ name: "MicrodroidCrashNativeLib",
+ srcs: ["src/native/crashbinary.cpp"],
+ header_libs: ["vm_payload_headers"],
+ stl: "libc++_static",
+}
diff --git a/tests/testapk/AndroidManifest.xml b/tests/testapk/AndroidManifest.xml
index fefd20a..2ea3f6c 100644
--- a/tests/testapk/AndroidManifest.xml
+++ b/tests/testapk/AndroidManifest.xml
@@ -19,6 +19,9 @@
<uses-permission android:name="android.permission.USE_CUSTOM_VIRTUAL_MACHINE" />
<uses-sdk android:minSdkVersion="33" android:targetSdkVersion="33" />
<uses-feature android:name="android.software.virtualization_framework" android:required="false" />
+ <queries>
+ <package android:name="com.android.microdroid.vmshare_app" />
+ </queries>
<application>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/testapk/AndroidTest.xml b/tests/testapk/AndroidTest.xml
index 787ebd4..929dd31 100644
--- a/tests/testapk/AndroidTest.xml
+++ b/tests/testapk/AndroidTest.xml
@@ -21,6 +21,7 @@
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="test-file-name" value="MicrodroidTestApp.apk" />
+ <option name="test-file-name" value="MicrodroidVmShareApp.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.microdroid.test" />
diff --git a/tests/testapk/assets/vm_config_crash.json b/tests/testapk/assets/vm_config_crash.json
deleted file mode 100644
index 3ec34a3..0000000
--- a/tests/testapk/assets/vm_config_crash.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "os": {
- "name": "microdroid"
- },
- "task": {
- "type": "microdroid_launcher",
- "command": "MicrodroidIdleNativeLib.so"
- },
- "export_tombstones": true
- }
diff --git a/tests/testapk/assets/vm_config_crash_no_tombstone.json b/tests/testapk/assets/vm_config_crash_no_tombstone.json
deleted file mode 100644
index 9678e38..0000000
--- a/tests/testapk/assets/vm_config_crash_no_tombstone.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "os": {
- "name": "microdroid"
- },
- "task": {
- "type": "microdroid_launcher",
- "command": "MicrodroidIdleNativeLib.so"
- },
- "export_tombstones": false
- }
diff --git a/tests/testapk/assets/vm_config_no_tombstone.json b/tests/testapk/assets/vm_config_no_tombstone.json
new file mode 100644
index 0000000..97e764d
--- /dev/null
+++ b/tests/testapk/assets/vm_config_no_tombstone.json
@@ -0,0 +1,10 @@
+{
+ "os": {
+ "name": "microdroid"
+ },
+ "task": {
+ "type": "microdroid_launcher",
+ "command": "MicrodroidTestNativeLib.so"
+ },
+ "export_tombstones": false
+}
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 afde3cd..4b936f2 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -18,21 +18,29 @@
import static android.system.virtualmachine.VirtualMachine.STATUS_DELETED;
import static android.system.virtualmachine.VirtualMachine.STATUS_RUNNING;
import static android.system.virtualmachine.VirtualMachine.STATUS_STOPPED;
+import static android.system.virtualmachine.VirtualMachineConfig.CPU_TOPOLOGY_ONE_CPU;
+import static android.system.virtualmachine.VirtualMachineConfig.CPU_TOPOLOGY_MATCH_HOST;
import static android.system.virtualmachine.VirtualMachineConfig.DEBUG_LEVEL_FULL;
import static android.system.virtualmachine.VirtualMachineConfig.DEBUG_LEVEL_NONE;
import static android.system.virtualmachine.VirtualMachineManager.CAPABILITY_NON_PROTECTED_VM;
import static android.system.virtualmachine.VirtualMachineManager.CAPABILITY_PROTECTED_VM;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.common.truth.TruthJUnit.assume;
import static org.junit.Assert.assertThrows;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.ServiceConnection;
import android.os.Build;
+import android.os.IBinder;
+import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.ParcelFileDescriptor.AutoCloseInputStream;
import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
@@ -49,6 +57,7 @@
import com.android.compatibility.common.util.CddTest;
import com.android.microdroid.test.device.MicrodroidDeviceTestBase;
+import com.android.microdroid.test.vmshare.IVmShareTestService;
import com.android.microdroid.testservice.ITestService;
import com.google.common.base.Strings;
@@ -85,6 +94,8 @@
import java.util.OptionalLong;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import co.nstant.in.cbor.CborDecoder;
@@ -125,6 +136,8 @@
private static final long MIN_MEM_X86_64 = 196 * ONE_MEBI;
private static final String EXAMPLE_STRING = "Literally any string!! :)";
+ private static final String VM_SHARE_APP_PACKAGE_NAME = "com.android.microdroid.vmshare_app";
+
@Test
@CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
public void createAndConnectToVm() throws Exception {
@@ -348,7 +361,7 @@
assertThat(minimal.getApkPath()).isNull();
assertThat(minimal.getDebugLevel()).isEqualTo(DEBUG_LEVEL_NONE);
assertThat(minimal.getMemoryBytes()).isEqualTo(0);
- assertThat(minimal.getNumCpus()).isEqualTo(1);
+ assertThat(minimal.getCpuTopology()).isEqualTo(CPU_TOPOLOGY_ONE_CPU);
assertThat(minimal.getPayloadBinaryName()).isEqualTo("binary.so");
assertThat(minimal.getPayloadConfigPath()).isNull();
assertThat(minimal.isProtectedVm()).isEqualTo(isProtectedVm());
@@ -364,9 +377,9 @@
.setProtectedVm(mProtectedVm)
.setPayloadConfigPath("config/path")
.setApkPath("/apk/path")
- .setNumCpus(maxCpus)
.setDebugLevel(DEBUG_LEVEL_FULL)
.setMemoryBytes(42)
+ .setCpuTopology(CPU_TOPOLOGY_MATCH_HOST)
.setEncryptedStorageBytes(1_000_000)
.setVmOutputCaptured(true);
VirtualMachineConfig maximal = maximalBuilder.build();
@@ -374,7 +387,7 @@
assertThat(maximal.getApkPath()).isEqualTo("/apk/path");
assertThat(maximal.getDebugLevel()).isEqualTo(DEBUG_LEVEL_FULL);
assertThat(maximal.getMemoryBytes()).isEqualTo(42);
- assertThat(maximal.getNumCpus()).isEqualTo(maxCpus);
+ assertThat(maximal.getCpuTopology()).isEqualTo(CPU_TOPOLOGY_MATCH_HOST);
assertThat(maximal.getPayloadBinaryName()).isNull();
assertThat(maximal.getPayloadConfigPath()).isEqualTo("config/path");
assertThat(maximal.isProtectedVm()).isEqualTo(isProtectedVm());
@@ -406,7 +419,7 @@
IllegalArgumentException.class, () -> builder.setPayloadBinaryName("dir/file.so"));
assertThrows(IllegalArgumentException.class, () -> builder.setDebugLevel(-1));
assertThrows(IllegalArgumentException.class, () -> builder.setMemoryBytes(0));
- assertThrows(IllegalArgumentException.class, () -> builder.setNumCpus(0));
+ assertThrows(IllegalArgumentException.class, () -> builder.setCpuTopology(-1));
assertThrows(IllegalArgumentException.class, () -> builder.setEncryptedStorageBytes(0));
// Consistency checks enforced at build time.
@@ -431,8 +444,6 @@
@Test
@CddTest(requirements = {"9.17/C-1-1"})
public void compatibleConfigTests() {
- int maxCpus = Runtime.getRuntime().availableProcessors();
-
VirtualMachineConfig baseline = newBaselineBuilder().build();
// A config must be compatible with itself
@@ -440,9 +451,9 @@
// Changes that must always be compatible
assertConfigCompatible(baseline, newBaselineBuilder().setMemoryBytes(99)).isTrue();
- if (maxCpus > 1) {
- assertConfigCompatible(baseline, newBaselineBuilder().setNumCpus(2)).isTrue();
- }
+ assertConfigCompatible(
+ baseline, newBaselineBuilder().setCpuTopology(CPU_TOPOLOGY_MATCH_HOST))
+ .isTrue();
// Changes that must be incompatible, since they must change the VM identity.
assertConfigCompatible(baseline, newBaselineBuilder().setDebugLevel(DEBUG_LEVEL_FULL))
@@ -1518,6 +1529,248 @@
assertThat(checkVmOutputIsRedirectedToLogcat(false)).isFalse();
}
+ @Test
+ public void testStartVmWithPayloadOfAnotherApp() throws Exception {
+ assumeSupportedKernel();
+
+ Context ctx = getContext();
+ Context otherAppCtx = ctx.createPackageContext(VM_SHARE_APP_PACKAGE_NAME, 0);
+
+ VirtualMachineConfig config =
+ new VirtualMachineConfig.Builder(otherAppCtx)
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .setProtectedVm(isProtectedVm())
+ .setPayloadBinaryName("MicrodroidPayloadInOtherAppNativeLib.so")
+ .build();
+
+ try (VirtualMachine vm = forceCreateNewVirtualMachine("vm_from_another_app", config)) {
+ TestResults results =
+ runVmTestService(
+ vm,
+ (ts, tr) -> {
+ tr.mAddInteger = ts.addInteger(101, 303);
+ });
+ assertThat(results.mAddInteger).isEqualTo(404);
+ }
+
+ getVirtualMachineManager().delete("vm_from_another_app");
+ }
+
+ @Test
+ public void testVmDescriptorParcelUnparcel_noTrustedStorage() throws Exception {
+ assumeSupportedKernel();
+
+ VirtualMachineConfig config =
+ newVmConfigBuilder()
+ .setPayloadBinaryName("MicrodroidTestNativeLib.so")
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .build();
+
+ VirtualMachine originalVm = forceCreateNewVirtualMachine("original_vm", config);
+ // Just start & stop the VM.
+ runVmTestService(originalVm, (ts, tr) -> {});
+
+ // Now create the descriptor and manually parcel & unparcel it.
+ VirtualMachineDescriptor vmDescriptor = toParcelFromParcel(originalVm.toDescriptor());
+
+ if (getVirtualMachineManager().get("import_vm_from_unparceled") != null) {
+ getVirtualMachineManager().delete("import_vm_from_unparceled");
+ }
+
+ VirtualMachine importVm =
+ getVirtualMachineManager()
+ .importFromDescriptor("import_vm_from_unparceled", vmDescriptor);
+
+ assertFileContentsAreEqualInTwoVms(
+ "config.xml", "original_vm", "import_vm_from_unparceled");
+ assertFileContentsAreEqualInTwoVms(
+ "instance.img", "original_vm", "import_vm_from_unparceled");
+
+ // Check that we can start and stop imported vm as well
+ runVmTestService(importVm, (ts, tr) -> {});
+ }
+
+ @Test
+ public void testVmDescriptorParcelUnparcel_withTrustedStorage() throws Exception {
+ assumeSupportedKernel();
+
+ VirtualMachineConfig config =
+ newVmConfigBuilder()
+ .setPayloadBinaryName("MicrodroidTestNativeLib.so")
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .setEncryptedStorageBytes(1_000_000)
+ .build();
+
+ VirtualMachine originalVm = forceCreateNewVirtualMachine("original_vm", config);
+ // Just start & stop the VM.
+ {
+ TestResults testResults =
+ runVmTestService(
+ originalVm,
+ (ts, tr) -> {
+ ts.writeToFile("not a secret!", "/mnt/encryptedstore/secret.txt");
+ });
+ assertThat(testResults.mException).isNull();
+ }
+
+ // Now create the descriptor and manually parcel & unparcel it.
+ VirtualMachineDescriptor vmDescriptor = toParcelFromParcel(originalVm.toDescriptor());
+
+ if (getVirtualMachineManager().get("import_vm_from_unparceled") != null) {
+ getVirtualMachineManager().delete("import_vm_from_unparceled");
+ }
+
+ VirtualMachine importVm =
+ getVirtualMachineManager()
+ .importFromDescriptor("import_vm_from_unparceled", vmDescriptor);
+
+ assertFileContentsAreEqualInTwoVms(
+ "config.xml", "original_vm", "import_vm_from_unparceled");
+ assertFileContentsAreEqualInTwoVms(
+ "instance.img", "original_vm", "import_vm_from_unparceled");
+ assertFileContentsAreEqualInTwoVms(
+ "storage.img", "original_vm", "import_vm_from_unparceled");
+
+ TestResults testResults =
+ runVmTestService(
+ importVm,
+ (ts, tr) -> {
+ tr.mFileContent = ts.readFromFile("/mnt/encryptedstore/secret.txt");
+ });
+
+ assertThat(testResults.mException).isNull();
+ assertThat(testResults.mFileContent).isEqualTo("not a secret!");
+ }
+
+ @Test
+ public void testShareVmWithAnotherApp() throws Exception {
+ assumeSupportedKernel();
+
+ Context ctx = getContext();
+ Context otherAppCtx = ctx.createPackageContext(VM_SHARE_APP_PACKAGE_NAME, 0);
+
+ VirtualMachineConfig config =
+ new VirtualMachineConfig.Builder(otherAppCtx)
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .setProtectedVm(isProtectedVm())
+ .setPayloadBinaryName("MicrodroidPayloadInOtherAppNativeLib.so")
+ .build();
+
+ VirtualMachine vm = forceCreateNewVirtualMachine("vm_to_share", config);
+ // Just start & stop the VM.
+ runVmTestService(vm, (ts, tr) -> {});
+ // Get a descriptor that we will share with another app (VM_SHARE_APP_PACKAGE_NAME)
+ VirtualMachineDescriptor vmDesc = vm.toDescriptor();
+
+ Intent serviceIntent = new Intent();
+ serviceIntent.setComponent(
+ new ComponentName(
+ VM_SHARE_APP_PACKAGE_NAME,
+ "com.android.microdroid.test.sharevm.VmShareServiceImpl"));
+
+ VmShareServiceConnection connection = new VmShareServiceConnection();
+ boolean ret = ctx.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE);
+ assertWithMessage("Failed to bind to " + serviceIntent).that(ret).isTrue();
+
+ IVmShareTestService service = connection.waitForService();
+ assertWithMessage("Timed out connecting to " + serviceIntent).that(service).isNotNull();
+
+ try {
+ // Send the VM descriptor to the other app. When received, it will reconstruct the VM
+ // from the descriptor, start it, connect to the ITestService in it, creates a "proxy"
+ // ITestService binder that delegates all the calls to the VM, and share it with this
+ // app. It will allow us to verify assertions on the running VM in the other app.
+ ITestService testServiceProxy = service.startVm(vmDesc);
+
+ int result = testServiceProxy.addInteger(37, 73);
+ assertThat(result).isEqualTo(110);
+ } finally {
+ ctx.unbindService(connection);
+ }
+ }
+
+ @Test
+ public void testShareVmWithAnotherApp_encryptedStorage() throws Exception {
+ assumeSupportedKernel();
+
+ Context ctx = getContext();
+ Context otherAppCtx = ctx.createPackageContext(VM_SHARE_APP_PACKAGE_NAME, 0);
+
+ VirtualMachineConfig config =
+ new VirtualMachineConfig.Builder(otherAppCtx)
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .setProtectedVm(isProtectedVm())
+ .setEncryptedStorageBytes(3_000_000)
+ .setPayloadBinaryName("MicrodroidPayloadInOtherAppNativeLib.so")
+ .build();
+
+ VirtualMachine vm = forceCreateNewVirtualMachine("vm_to_share", config);
+ // Just start & stop the VM.
+ runVmTestService(
+ vm,
+ (ts, tr) -> {
+ ts.writeToFile(EXAMPLE_STRING, "/mnt/encryptedstore/private.key");
+ });
+ // Get a descriptor that we will share with another app (VM_SHARE_APP_PACKAGE_NAME)
+ VirtualMachineDescriptor vmDesc = vm.toDescriptor();
+
+ Intent serviceIntent = new Intent();
+ serviceIntent.setComponent(
+ new ComponentName(
+ VM_SHARE_APP_PACKAGE_NAME,
+ "com.android.microdroid.test.sharevm.VmShareServiceImpl"));
+
+ VmShareServiceConnection connection = new VmShareServiceConnection();
+ boolean ret = ctx.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE);
+ assertWithMessage("Failed to bind to " + serviceIntent).that(ret).isTrue();
+
+ IVmShareTestService service = connection.waitForService();
+ assertWithMessage("Timed out connecting to " + serviceIntent).that(service).isNotNull();
+
+ try {
+ // Send the VM descriptor to the other app. When received, it will reconstruct the VM
+ // from the descriptor, start it, connect to the ITestService in it, creates a "proxy"
+ // ITestService binder that delegates all the calls to the VM, and share it with this
+ // app. It will allow us to verify assertions on the running VM in the other app.
+ ITestService testServiceProxy = service.startVm(vmDesc);
+
+ String result = testServiceProxy.readFromFile("/mnt/encryptedstore/private.key");
+ assertThat(result).isEqualTo(EXAMPLE_STRING);
+ } finally {
+ ctx.unbindService(connection);
+ }
+ }
+
+ private static class VmShareServiceConnection implements ServiceConnection {
+
+ private final CountDownLatch mLatch = new CountDownLatch(1);
+
+ private IVmShareTestService mVmShareTestService;
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mVmShareTestService = IVmShareTestService.Stub.asInterface(service);
+ mLatch.countDown();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {}
+
+ private IVmShareTestService waitForService() throws Exception {
+ if (!mLatch.await(1, TimeUnit.MINUTES)) {
+ return null;
+ }
+ return mVmShareTestService;
+ }
+ }
+
+ private VirtualMachineDescriptor toParcelFromParcel(VirtualMachineDescriptor descriptor) {
+ Parcel parcel = Parcel.obtain();
+ descriptor.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ return VirtualMachineDescriptor.CREATOR.createFromParcel(parcel);
+ }
+
private void assertFileContentsAreEqualInTwoVms(String fileName, String vmName1, String vmName2)
throws IOException {
File file1 = getVmFile(vmName1, fileName);
@@ -1591,6 +1844,11 @@
mTestService =
ITestService.Stub.asInterface(
vm.connectToVsockServer(ITestService.SERVICE_PORT));
+ // Make sure linkToDeath works, and include it in the log in case it's
+ // helpful.
+ mTestService
+ .asBinder()
+ .linkToDeath(() -> Log.i(TAG, "ITestService binder died"), 0);
} catch (Exception e) {
testResults.mException = e;
}
diff --git a/tests/testapk/src/native/crashbinary.cpp b/tests/testapk/src/native/crashbinary.cpp
new file mode 100644
index 0000000..27f10ec
--- /dev/null
+++ b/tests/testapk/src/native/crashbinary.cpp
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// A binary killing itself by SIGABRT.
+#include <stdlib.h>
+#include <unistd.h>
+extern "C" int AVmPayload_main() {
+ abort();
+}
diff --git a/tests/vmshareapp/Android.bp b/tests/vmshareapp/Android.bp
new file mode 100644
index 0000000..6c2c9e4
--- /dev/null
+++ b/tests/vmshareapp/Android.bp
@@ -0,0 +1,16 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// Helper app to verify that we can create a VM using others app payload, and share VMs between apps
+android_test_helper_app {
+ name: "MicrodroidVmShareApp",
+ srcs: ["src/java/**/*.java"],
+ // Defaults are defined in ../testapk/Android.bp
+ defaults: ["MicrodroidTestAppsDefaults"],
+ jni_libs: [
+ // Defined in ../testapk/Android.bp
+ "MicrodroidPayloadInOtherAppNativeLib",
+ ],
+ min_sdk_version: "UpsideDownCake",
+}
diff --git a/tests/vmshareapp/AndroidManifest.xml b/tests/vmshareapp/AndroidManifest.xml
new file mode 100644
index 0000000..b623f7f
--- /dev/null
+++ b/tests/vmshareapp/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.microdroid.vmshare_app">
+ <uses-permission android:name="android.permission.MANAGE_VIRTUAL_MACHINE" />
+
+ <uses-feature android:name="android.software.virtualization_framework"
+ android:required="false" />
+
+ <application>
+ <service android:name="com.android.microdroid.test.sharevm.VmShareServiceImpl"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="com.android.microdroid.test.sharevm.VmShareService"/>
+ </intent-filter>
+ </service>
+ </application>
+
+</manifest>
diff --git a/tests/vmshareapp/aidl/Android.bp b/tests/vmshareapp/aidl/Android.bp
new file mode 100644
index 0000000..df4a4b4
--- /dev/null
+++ b/tests/vmshareapp/aidl/Android.bp
@@ -0,0 +1,15 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// Unfortunatelly aidl_interface doesn't work well with .aidl files that depend on java-only
+// parcelables (e.g. Bundle, VirtualMachineDescriptor), hence this java_library.
+java_library {
+ name: "com.android.microdroid.test.vmshare_service-java",
+ srcs: ["com/**/*.aidl"],
+ sdk_version: "test_current",
+ static_libs: ["com.android.microdroid.testservice-java"],
+ aidl: {
+ include_dirs: ["packages/modules/Virtualization/tests/aidl/"],
+ },
+}
diff --git a/tests/vmshareapp/aidl/com/android/microdroid/test/vmshare/IVmShareTestService.aidl b/tests/vmshareapp/aidl/com/android/microdroid/test/vmshare/IVmShareTestService.aidl
new file mode 100644
index 0000000..fe6ca43
--- /dev/null
+++ b/tests/vmshareapp/aidl/com/android/microdroid/test/vmshare/IVmShareTestService.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.microdroid.test.vmshare;
+
+import android.system.virtualmachine.VirtualMachineDescriptor;
+import com.android.microdroid.testservice.ITestService;
+
+/** {@hide} */
+interface IVmShareTestService {
+ ITestService startVm(in VirtualMachineDescriptor vmDesc);
+}
diff --git a/tests/vmshareapp/src/java/com/android/microdroid/test/sharevm/VmShareServiceImpl.java b/tests/vmshareapp/src/java/com/android/microdroid/test/sharevm/VmShareServiceImpl.java
new file mode 100644
index 0000000..278e1a2
--- /dev/null
+++ b/tests/vmshareapp/src/java/com/android/microdroid/test/sharevm/VmShareServiceImpl.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.microdroid.test.sharevm;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.system.virtualmachine.VirtualMachine;
+import android.system.virtualmachine.VirtualMachineCallback;
+import android.system.virtualmachine.VirtualMachineDescriptor;
+import android.system.virtualmachine.VirtualMachineException;
+import android.system.virtualmachine.VirtualMachineManager;
+import android.util.Log;
+
+import com.android.microdroid.test.vmshare.IVmShareTestService;
+import com.android.microdroid.testservice.ITestService;
+
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * A {@link Service} that is used in end-to-end tests of the {@link VirtualMachine} sharing
+ * functionality.
+ *
+ * <p>During the test {@link com.android.microdroid.test.MicrodroidTests} will bind to this service,
+ * and call {@link #startVm(VirtualMachineDescriptor)} to share the VM. This service then will
+ * create a {@link VirtualMachine} from that descriptor, {@link VirtualMachine#run() run} it, and
+ * send back {@link RemoteTestServiceDelegate}. The {@code MicrodroidTests} can use that {@link
+ * RemoteTestServiceDelegate} to assert conditions on the VM running in the {@link
+ * VmShareServiceImpl}.
+ *
+ * <p>The {@link VirtualMachine} running in this service will be stopped on {@link
+ * #onUnbind(Intent)}.
+ *
+ * @see com.android.microdroid.test.MicrodroidTests#testShareVmWithAnotherApp
+ */
+public class VmShareServiceImpl extends Service {
+
+ private static final String TAG = "VmShareApp";
+
+ private IVmShareTestService.Stub mBinder;
+
+ private VirtualMachine mVirtualMachine;
+
+ @Override
+ public void onCreate() {
+ mBinder = new ServiceImpl();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ Log.i(TAG, "onBind " + intent + " binder = " + mBinder);
+ return mBinder;
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ deleteVm();
+ // Tell framework that it shouldn't call onRebind.
+ return false;
+ }
+
+ private void deleteVm() {
+ if (mVirtualMachine == null) {
+ return;
+ }
+ try {
+ mVirtualMachine.stop();
+ String name = mVirtualMachine.getName();
+ VirtualMachineManager vmm = getSystemService(VirtualMachineManager.class);
+ vmm.delete(name);
+ mVirtualMachine = null;
+ } catch (VirtualMachineException e) {
+ Log.e(TAG, "Failed to stop " + mVirtualMachine, e);
+ }
+ }
+
+ public ITestService startVm(VirtualMachineDescriptor vmDesc) throws Exception {
+ // Cleanup VM left from the previous test.
+ deleteVm();
+
+ VirtualMachineManager vmm = getSystemService(VirtualMachineManager.class);
+
+ // Add random uuid to make sure that different tests that bind to this service don't trip
+ // over each other.
+ String vmName = "imported_vm" + UUID.randomUUID();
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ VirtualMachineCallback callback =
+ new VirtualMachineCallback() {
+
+ @Override
+ public void onPayloadStarted(VirtualMachine vm) {
+ // Ignored
+ }
+
+ @Override
+ public void onPayloadReady(VirtualMachine vm) {
+ latch.countDown();
+ }
+
+ @Override
+ public void onPayloadFinished(VirtualMachine vm, int exitCode) {
+ // Ignored
+ }
+
+ @Override
+ public void onError(VirtualMachine vm, int errorCode, String message) {
+ throw new RuntimeException(
+ "VM failed with error " + errorCode + " : " + message);
+ }
+
+ @Override
+ public void onStopped(VirtualMachine vm, int reason) {
+ // Ignored
+ }
+ };
+
+ mVirtualMachine = vmm.importFromDescriptor(vmName, vmDesc);
+ mVirtualMachine.setCallback(getMainExecutor(), callback);
+
+ Log.i(TAG, "Starting VM " + vmName);
+ mVirtualMachine.run();
+ if (!latch.await(1, TimeUnit.MINUTES)) {
+ throw new TimeoutException("Timed out starting VM");
+ }
+
+ Log.i(
+ TAG,
+ "Payload is ready, connecting to the vsock service at port "
+ + ITestService.SERVICE_PORT);
+ ITestService testService =
+ ITestService.Stub.asInterface(
+ mVirtualMachine.connectToVsockServer(ITestService.SERVICE_PORT));
+ return new RemoteTestServiceDelegate(testService);
+ }
+
+ final class ServiceImpl extends IVmShareTestService.Stub {
+
+ @Override
+ public ITestService startVm(VirtualMachineDescriptor vmDesc) {
+ Log.i(TAG, "startVm binder call received");
+ try {
+ return VmShareServiceImpl.this.startVm(vmDesc);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to startVm", e);
+ throw new IllegalStateException("Failed to startVm", e);
+ }
+ }
+ }
+
+ private static class RemoteTestServiceDelegate extends ITestService.Stub {
+
+ private final ITestService mServiceInVm;
+
+ private RemoteTestServiceDelegate(ITestService serviceInVm) {
+ mServiceInVm = serviceInVm;
+ }
+
+ @Override
+ public int addInteger(int a, int b) throws RemoteException {
+ return mServiceInVm.addInteger(a, b);
+ }
+
+ @Override
+ public String readProperty(String prop) throws RemoteException {
+ throw new UnsupportedOperationException("Not supported");
+ }
+
+ @Override
+ public byte[] insecurelyExposeVmInstanceSecret() throws RemoteException {
+ throw new UnsupportedOperationException("Not supported");
+ }
+
+ @Override
+ public byte[] insecurelyExposeAttestationCdi() throws RemoteException {
+ throw new UnsupportedOperationException("Not supported");
+ }
+
+ @Override
+ public byte[] getBcc() throws RemoteException {
+ throw new UnsupportedOperationException("Not supported");
+ }
+
+ @Override
+ public String getApkContentsPath() throws RemoteException {
+ throw new UnsupportedOperationException("Not supported");
+ }
+
+ @Override
+ public String getEncryptedStoragePath() throws RemoteException {
+ throw new UnsupportedOperationException("Not supported");
+ }
+
+ @Override
+ public void runEchoReverseServer() throws RemoteException {
+ throw new UnsupportedOperationException("Not supported");
+ }
+
+ @Override
+ public String[] getEffectiveCapabilities() throws RemoteException {
+ throw new UnsupportedOperationException("Not supported");
+ }
+
+ @Override
+ public void writeToFile(String content, String path) throws RemoteException {
+ throw new UnsupportedOperationException("Not supported");
+ }
+
+ @Override
+ public String readFromFile(String path) throws RemoteException {
+ return mServiceInVm.readFromFile(path);
+ }
+
+ @Override
+ public void quit() throws RemoteException {
+ throw new UnsupportedOperationException("Not supported");
+ }
+ }
+}
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index cab7c71..89f74d6 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -27,6 +27,7 @@
ErrorCode::ErrorCode,
};
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
+ CpuTopology::CpuTopology,
DiskImage::DiskImage,
IVirtualMachine::{BnVirtualMachine, IVirtualMachine},
IVirtualMachineCallback::IVirtualMachineCallback,
@@ -384,6 +385,18 @@
})
.collect::<Result<Vec<DiskFile>, _>>()?;
+ let (cpus, host_cpu_topology) = match config.cpuTopology {
+ CpuTopology::MATCH_HOST => (None, true),
+ CpuTopology::ONE_CPU => (NonZeroU32::new(1), false),
+ val => {
+ error!("Unexpected value of CPU topology: {:?}", val);
+ return Err(Status::new_service_specific_error_str(
+ -1,
+ Some(format!("Failed to parse CPU topology value: {:?}", val)),
+ ));
+ }
+ };
+
// Creating this ramdump file unconditionally is not harmful as ramdump will be created
// only when the VM is configured as such. `ramdump_write` is sent to crosvm and will
// be the backing store for the /dev/hvc1 where VM will emit ramdump to. `ramdump_read`
@@ -408,7 +421,8 @@
params: config.params.to_owned(),
protected: *is_protected,
memory_mib: config.memoryMib.try_into().ok().and_then(NonZeroU32::new),
- cpus: config.numCpus.try_into().ok().and_then(NonZeroU32::new),
+ cpus,
+ host_cpu_topology,
task_profiles: config.taskProfiles.clone(),
console_fd,
log_fd,
@@ -567,7 +581,7 @@
vm_config.name = config.name.clone();
vm_config.protectedVm = config.protectedVm;
- vm_config.numCpus = config.numCpus;
+ vm_config.cpuTopology = config.cpuTopology;
vm_config.taskProfiles = config.taskProfiles.clone();
// Microdroid takes additional init ramdisk & (optionally) storage image
@@ -611,7 +625,7 @@
apexes: vec![],
extra_apks: vec![],
prefer_staged: false,
- export_tombstones: false,
+ export_tombstones: None,
enable_authfs: false,
})
}
diff --git a/virtualizationmanager/src/atom.rs b/virtualizationmanager/src/atom.rs
index c33f262..567fce9 100644
--- a/virtualizationmanager/src/atom.rs
+++ b/virtualizationmanager/src/atom.rs
@@ -19,6 +19,7 @@
use crate::get_calling_uid;
use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::DeathReason::DeathReason;
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
+ CpuTopology::CpuTopology,
IVirtualMachine::IVirtualMachine,
VirtualMachineAppConfig::{Payload::Payload, VirtualMachineAppConfig},
VirtualMachineConfig::VirtualMachineConfig,
@@ -38,6 +39,8 @@
use std::time::{Duration, SystemTime};
use zip::ZipArchive;
+const INVALID_NUM_CPUS: i32 = -1;
+
fn get_apex_list(config: &VirtualMachineAppConfig) -> String {
match &config.payload {
Payload::PayloadConfig(_) => String::new(),
@@ -76,6 +79,19 @@
}
}
+// Returns the number of CPUs configured in the host system.
+// This matches how crosvm determines the number of logical cores.
+// For telemetry purposes only.
+pub(crate) fn get_num_cpus() -> Option<usize> {
+ // SAFETY - Only integer constants passed back and forth.
+ let ret = unsafe { libc::sysconf(libc::_SC_NPROCESSORS_CONF) };
+ if ret > 0 {
+ ret.try_into().ok()
+ } else {
+ None
+ }
+}
+
/// Write the stats of VMCreation to statsd
pub fn write_vm_creation_stats(
config: &VirtualMachineConfig,
@@ -94,23 +110,33 @@
binder_exception_code = e.exception_code() as i32;
}
}
- let (vm_identifier, config_type, num_cpus, memory_mib, apexes) = match config {
+ let (vm_identifier, config_type, cpu_topology, memory_mib, apexes) = match config {
VirtualMachineConfig::AppConfig(config) => (
config.name.clone(),
vm_creation_requested::ConfigType::VirtualMachineAppConfig,
- config.numCpus,
+ config.cpuTopology,
config.memoryMib,
get_apex_list(config),
),
VirtualMachineConfig::RawConfig(config) => (
config.name.clone(),
vm_creation_requested::ConfigType::VirtualMachineRawConfig,
- config.numCpus,
+ config.cpuTopology,
config.memoryMib,
String::new(),
),
};
+ let num_cpus: i32 = match cpu_topology {
+ CpuTopology::MATCH_HOST => {
+ get_num_cpus().and_then(|v| v.try_into().ok()).unwrap_or_else(|| {
+ warn!("Failed to determine the number of CPUs in the host");
+ INVALID_NUM_CPUS
+ })
+ }
+ _ => 1,
+ };
+
let atom = AtomVmCreationRequested {
uid: get_calling_uid() as i32,
vmIdentifier: vm_identifier,
diff --git a/virtualizationmanager/src/crosvm.rs b/virtualizationmanager/src/crosvm.rs
index 8f88daf..ea1146e 100644
--- a/virtualizationmanager/src/crosvm.rs
+++ b/virtualizationmanager/src/crosvm.rs
@@ -15,7 +15,7 @@
//! Functions for running instances of `crosvm`.
use crate::aidl::{remove_temporary_files, Cid, VirtualMachineCallbacks};
-use crate::atom::write_vm_exited_stats;
+use crate::atom::{get_num_cpus, write_vm_exited_stats};
use anyhow::{anyhow, bail, Context, Error, Result};
use command_fds::CommandFdExt;
use lazy_static::lazy_static;
@@ -97,6 +97,7 @@
pub protected: bool,
pub memory_mib: Option<NonZeroU32>,
pub cpus: Option<NonZeroU32>,
+ pub host_cpu_topology: bool,
pub task_profiles: Vec<String>,
pub console_fd: Option<File>,
pub log_fd: Option<File>,
@@ -663,6 +664,24 @@
}
}
+fn should_configure_ramdump(protected: bool) -> bool {
+ if protected {
+ // Protected VM needs ramdump configuration here.
+ // pvmfw will disable ramdump if unnecessary.
+ true
+ } else {
+ // For unprotected VM, ramdump should be handled here.
+ // ramdump wouldn't be enabled if ramdump is explicitly set to <1>.
+ if let Ok(mut file) = File::open("/proc/device-tree/avf/guest/common/ramdump") {
+ let mut ramdump: [u8; 4] = Default::default();
+ file.read_exact(&mut ramdump).map_err(|_| false).unwrap();
+ // DT spec uses big endian although Android is always little endian.
+ return u32::from_be_bytes(ramdump) == 1;
+ }
+ false
+ }
+}
+
/// Starts an instance of `crosvm` to manage a new VM.
fn run_vm(
config: CrosvmConfig,
@@ -714,6 +733,15 @@
command.arg("--cpus").arg(cpus.to_string());
}
+ if config.host_cpu_topology {
+ // TODO(b/266664564): replace with --host-cpu-topology once available
+ if let Some(cpus) = get_num_cpus() {
+ command.arg("--cpus").arg(cpus.to_string());
+ } else {
+ bail!("Could not determine the number of CPUs in the system");
+ }
+ }
+
if !config.task_profiles.is_empty() {
command.arg("--task-profiles").arg(config.task_profiles.join(","));
}
@@ -779,7 +807,10 @@
debug!("Preserving FDs {:?}", preserved_fds);
command.preserved_fds(preserved_fds);
- command.arg("--params").arg("crashkernel=17M");
+ if should_configure_ramdump(config.protected) {
+ command.arg("--params").arg("crashkernel=17M");
+ }
+
print_crosvm_args(&command);
let result = SharedChild::spawn(&mut command)?;
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/CpuTopology.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/CpuTopology.aidl
new file mode 100644
index 0000000..8a8e3d0
--- /dev/null
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/CpuTopology.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.system.virtualizationservice;
+
+/** The vCPU topology that will be generated for the VM. */
+@Backing(type="byte")
+enum CpuTopology {
+ /** One vCPU */
+ ONE_CPU = 0,
+ /** Match physical CPU topology of the host. */
+ MATCH_HOST = 1,
+}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
index 884561d..7f90ed6 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
@@ -15,6 +15,7 @@
*/
package android.system.virtualizationservice;
+import android.system.virtualizationservice.CpuTopology;
import android.system.virtualizationservice.VirtualMachinePayloadConfig;
/** Configuration for running an App in a VM */
@@ -78,10 +79,8 @@
*/
int memoryMib;
- /**
- * Number of vCPUs in the VM. Defaults to 1.
- */
- int numCpus = 1;
+ /** The vCPU topology that will be generated for the VM. Default to 1 vCPU. */
+ CpuTopology cpuTopology = CpuTopology.ONE_CPU;
/**
* List of task profile names to apply for the VM
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
index 993bbb0..1cda163 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
@@ -15,6 +15,7 @@
*/
package android.system.virtualizationservice;
+import android.system.virtualizationservice.CpuTopology;
import android.system.virtualizationservice.DiskImage;
/** Raw configuration for running a VM. */
@@ -49,10 +50,8 @@
/** The amount of RAM to give the VM, in MiB. 0 or negative to use the default. */
int memoryMib;
- /**
- * Number of vCPUs in the VM. Defaults to 1.
- */
- int numCpus = 1;
+ /** The vCPU topology that will be generated for the VM. Default to 1 vCPU. */
+ CpuTopology cpuTopology = CpuTopology.ONE_CPU;
/**
* A version or range of versions of the virtual platform that this config is compatible with.
diff --git a/vm/src/main.rs b/vm/src/main.rs
index ea744f7..e1c3413 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -19,8 +19,8 @@
mod run;
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
- IVirtualizationService::IVirtualizationService, PartitionType::PartitionType,
- VirtualMachineAppConfig::DebugLevel::DebugLevel,
+ CpuTopology::CpuTopology, IVirtualizationService::IVirtualizationService,
+ PartitionType::PartitionType, VirtualMachineAppConfig::DebugLevel::DebugLevel,
};
use anyhow::{Context, Error};
use binder::ProcessState;
@@ -91,9 +91,9 @@
#[clap(short, long)]
mem: Option<u32>,
- /// Number of vCPUs in the VM. If unspecified, defaults to 1.
- #[clap(long)]
- cpus: Option<u32>,
+ /// Run VM with vCPU topology matching that of the host. If unspecified, defaults to 1 vCPU.
+ #[clap(long, default_value = "one_cpu", value_parser = parse_cpu_topology)]
+ cpu_topology: CpuTopology,
/// Comma separated list of task profile names to apply to the VM
#[clap(long)]
@@ -146,9 +146,9 @@
#[clap(short, long)]
mem: Option<u32>,
- /// Number of vCPUs in the VM. If unspecified, defaults to 1.
- #[clap(long)]
- cpus: Option<u32>,
+ /// Run VM with vCPU topology matching that of the host. If unspecified, defaults to 1 vCPU.
+ #[clap(long, default_value = "one_cpu", value_parser = parse_cpu_topology)]
+ cpu_topology: CpuTopology,
/// Comma separated list of task profile names to apply to the VM
#[clap(long)]
@@ -163,9 +163,9 @@
#[clap(long)]
name: Option<String>,
- /// Number of vCPUs in the VM. If unspecified, defaults to 1.
- #[clap(long)]
- cpus: Option<u32>,
+ /// Run VM with vCPU topology matching that of the host. If unspecified, defaults to 1 vCPU.
+ #[clap(long, default_value = "one_cpu", value_parser = parse_cpu_topology)]
+ cpu_topology: CpuTopology,
/// Comma separated list of task profile names to apply to the VM
#[clap(long)]
@@ -222,6 +222,14 @@
}
}
+fn parse_cpu_topology(s: &str) -> Result<CpuTopology, String> {
+ match s {
+ "one_cpu" => Ok(CpuTopology::ONE_CPU),
+ "match_host" => Ok(CpuTopology::MATCH_HOST),
+ _ => Err(format!("Invalid cpu topology {}", s)),
+ }
+}
+
fn main() -> Result<(), Error> {
env_logger::init();
let opt = Opt::parse();
@@ -248,7 +256,7 @@
debug,
protected,
mem,
- cpus,
+ cpu_topology,
task_profiles,
extra_idsigs,
} => command_run_app(
@@ -266,7 +274,7 @@
debug,
protected,
mem,
- cpus,
+ cpu_topology,
task_profiles,
&extra_idsigs,
),
@@ -280,7 +288,7 @@
debug,
protected,
mem,
- cpus,
+ cpu_topology,
task_profiles,
} => command_run_microdroid(
name,
@@ -293,10 +301,10 @@
debug,
protected,
mem,
- cpus,
+ cpu_topology,
task_profiles,
),
- Opt::Run { name, config, cpus, task_profiles, console, log } => {
+ Opt::Run { name, config, cpu_topology, task_profiles, console, log } => {
command_run(
name,
service.as_ref(),
@@ -304,7 +312,7 @@
console.as_deref(),
log.as_deref(),
/* mem */ None,
- cpus,
+ cpu_topology,
task_profiles,
)
}
diff --git a/vm/src/run.rs b/vm/src/run.rs
index e229933..fa84591 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -16,6 +16,7 @@
use crate::create_partition::command_create_partition;
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
+ CpuTopology::CpuTopology,
IVirtualizationService::IVirtualizationService,
PartitionType::PartitionType,
VirtualMachineAppConfig::{DebugLevel::DebugLevel, Payload::Payload, VirtualMachineAppConfig},
@@ -54,7 +55,7 @@
debug_level: DebugLevel,
protected: bool,
mem: Option<u32>,
- cpus: Option<u32>,
+ cpu_topology: CpuTopology,
task_profiles: Vec<String>,
extra_idsigs: &[PathBuf],
) -> Result<(), Error> {
@@ -141,7 +142,7 @@
debugLevel: debug_level,
protectedVm: protected,
memoryMib: mem.unwrap_or(0) as i32, // 0 means use the VM default
- numCpus: cpus.unwrap_or(1) as i32,
+ cpuTopology: cpu_topology,
taskProfiles: task_profiles,
});
run(service, &config, &payload_config_str, console_path, log_path)
@@ -182,7 +183,7 @@
debug_level: DebugLevel,
protected: bool,
mem: Option<u32>,
- cpus: Option<u32>,
+ cpu_topology: CpuTopology,
task_profiles: Vec<String>,
) -> Result<(), Error> {
let apk = find_empty_payload_apk_path()?;
@@ -211,7 +212,7 @@
debug_level,
protected,
mem,
- cpus,
+ cpu_topology,
task_profiles,
&extra_sig,
)
@@ -226,7 +227,7 @@
console_path: Option<&Path>,
log_path: Option<&Path>,
mem: Option<u32>,
- cpus: Option<u32>,
+ cpu_topology: CpuTopology,
task_profiles: Vec<String>,
) -> Result<(), Error> {
let config_file = File::open(config_path).context("Failed to open config file")?;
@@ -235,14 +236,12 @@
if let Some(mem) = mem {
config.memoryMib = mem as i32;
}
- if let Some(cpus) = cpus {
- config.numCpus = cpus as i32;
- }
if let Some(name) = name {
config.name = name;
} else {
config.name = String::from("VmRun");
}
+ config.cpuTopology = cpu_topology;
config.taskProfiles = task_profiles;
run(
service,
diff --git a/vmbase/example/Android.bp b/vmbase/example/Android.bp
index 94eb21a..26be51b 100644
--- a/vmbase/example/Android.bp
+++ b/vmbase/example/Android.bp
@@ -11,7 +11,7 @@
rustlibs: [
"libaarch64_paging",
"libbuddy_system_allocator",
- "libdice_nostd",
+ "libdiced_open_dice_nostd",
"libfdtpci",
"liblibfdt",
"liblog_rust_nostd",
diff --git a/vmbase/example/src/main.rs b/vmbase/example/src/main.rs
index ec28a11..3b0e9db 100644
--- a/vmbase/example/src/main.rs
+++ b/vmbase/example/src/main.rs
@@ -225,7 +225,7 @@
fn check_dice() {
info!("Testing DICE integration...");
- let hash = dice::hash("hello world".as_bytes()).expect("DiceHash failed");
+ let hash = diced_open_dice::hash("hello world".as_bytes()).expect("DiceHash failed");
assert_eq!(
hash,
[
diff --git a/vmbase/example/tests/test.rs b/vmbase/example/tests/test.rs
index c6aea8c..cfb0225 100644
--- a/vmbase/example/tests/test.rs
+++ b/vmbase/example/tests/test.rs
@@ -16,7 +16,7 @@
use android_system_virtualizationservice::{
aidl::android::system::virtualizationservice::{
- DiskImage::DiskImage, VirtualMachineConfig::VirtualMachineConfig,
+ CpuTopology::CpuTopology, DiskImage::DiskImage, VirtualMachineConfig::VirtualMachineConfig,
VirtualMachineRawConfig::VirtualMachineRawConfig,
},
binder::{ParcelFileDescriptor, ProcessState},
@@ -84,7 +84,7 @@
disks: vec![disk_image],
protectedVm: false,
memoryMib: 300,
- numCpus: 1,
+ cpuTopology: CpuTopology::ONE_CPU,
platformVersion: "~1.0".to_string(),
taskProfiles: vec![],
});