Merge "Add vsock RPC Binder roundtrip microbenchmark"
diff --git a/compos/common/compos_client.rs b/compos/common/compos_client.rs
index 59fdd9f..92c9a3c 100644
--- a/compos/common/compos_client.rs
+++ b/compos/common/compos_client.rs
@@ -139,6 +139,7 @@
memoryMib: parameters.memory_mib.unwrap_or(0), // 0 means use the default
cpuTopology: cpu_topology,
taskProfiles: parameters.task_profiles.clone(),
+ gdbPort: 0, // Don't start gdb-server
});
let callback = Box::new(Callback {});
diff --git a/docs/debug/gdb.md b/docs/debug/gdb.md
new file mode 100644
index 0000000..316faad
--- /dev/null
+++ b/docs/debug/gdb.md
@@ -0,0 +1,46 @@
+# Debugging guest kernels with gdb
+
+Note: this feature is only available on android14-5.15 and newer host kernels.
+
+Starting with Android U it is possible to attach a gdb to the guest kernel, when
+starting a debuggable and non-protected guest VM.
+
+You can do this by passing `--gdb <port>` argument to the `vm run`, `vm run-app`
+and `vm run-microdroid` commands. The `crosvm` will start the gdb server on the
+provided port. It will wait for the gdb client to connect to it before
+proceeding with the VM boot.
+
+Here is an example invocation:
+
+```shell
+adb forward tcp:3456 tcp:3456
+adb shell /apex/com.android.virt/bin/vm run-microdroid --gdb 3456
+```
+
+Then in another shell:
+
+```shell
+gdb vmlinux
+(gdb) target remote :3456
+(gdb) hbreak start_kernel
+(gdb) c
+```
+
+The [kernel documentation](
+https://www.kernel.org/doc/html/latest/dev-tools/gdb-kernel-debugging.html) has
+some general techniques on how to debug kernel with gdb.
+
+## Obtaining vmlinux for Microdroid kernels
+
+If you are debugging Microdroid kernel that you have built [locally](
+../../microdroid/kernel/README.md), then look for `out/dist/vmlinux` in your
+kernel repository.
+
+If you are debugging Microdroid kernel bundled with the `com.android.virt` APEX,
+then you need to obtain the build ID of this kernel. You can do this by
+checking the prebuilt-info.txt file in the
+`packages/modules/Virtualization/microdroid/kernel/arm64` or
+`packages/modules/Virtualization/microdroid/kernel/x86_64` directories.
+
+Using that build ID you can download the vmlinux from the build server via:
+https://ci.android.com/builds/submitted/${BUILD_ID}/kernel_microdroid_aarch64/latest/vmlinux
diff --git a/javalib/jni/android_system_virtualmachine_VirtualMachine.cpp b/javalib/jni/android_system_virtualmachine_VirtualMachine.cpp
index 9281e73..b3354cc 100644
--- a/javalib/jni/android_system_virtualmachine_VirtualMachine.cpp
+++ b/javalib/jni/android_system_virtualmachine_VirtualMachine.cpp
@@ -58,9 +58,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);
+ // (b/268335700). These threads are currently created eagerly, so we don't
+ // want too many. The number 1 is chosen after some discussion, and to match
+ // the server-side default (mMaxThreads on RpcServer).
+ ARpcSession_setMaxIncomingThreads(session.get(), 1);
auto client = ARpcSession_setupPreconnectedClient(session.get(), requestFunc, &args);
return AIBinder_toJavaBinder(env, client);
}
diff --git a/libs/libfdt/src/lib.rs b/libs/libfdt/src/lib.rs
index 29d7abe..927bf50 100644
--- a/libs/libfdt/src/lib.rs
+++ b/libs/libfdt/src/lib.rs
@@ -614,7 +614,8 @@
fdt_err_expect_zero(ret)
}
- fn as_ptr(&self) -> *const c_void {
+ /// Return a shared pointer to the device tree.
+ pub fn as_ptr(&self) -> *const c_void {
self as *const _ as *const c_void
}
diff --git a/microdroid_manager/src/dice.rs b/microdroid_manager/src/dice.rs
index fd22198..c3136e8 100644
--- a/microdroid_manager/src/dice.rs
+++ b/microdroid_manager/src/dice.rs
@@ -17,8 +17,8 @@
use anyhow::{anyhow, bail, Context, Error, Result};
use byteorder::{NativeEndian, ReadBytesExt};
use diced_open_dice::{
- bcc_handover_parse, retry_bcc_main_flow, BccHandover, Cdi, Config, DiceMode, Hash, Hidden,
- InputValues, OwnedDiceArtifacts,
+ bcc_handover_parse, retry_bcc_main_flow, BccHandover, Config, DiceArtifacts, DiceMode, Hash,
+ Hidden, InputValues, OwnedDiceArtifacts,
};
use keystore2_crypto::ZVec;
use libc::{c_void, mmap, munmap, MAP_FAILED, MAP_PRIVATE, PROT_READ};
@@ -32,14 +32,12 @@
/// Derives a sealing key from the DICE sealing CDI.
pub fn derive_sealing_key(
- cdi_seal: &Cdi,
+ dice_artifacts: &dyn DiceArtifacts,
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)
+ key: &mut [u8],
+) -> Result<()> {
+ Ok(hkdf(key, Md::sha256(), dice_artifacts.cdi_seal(), salt, info)?)
}
/// Artifacts that are mapped into the process address space from the driver.
@@ -54,6 +52,13 @@
}
impl DiceDriver<'_> {
+ fn dice_artifacts(&self) -> &dyn DiceArtifacts {
+ match self {
+ Self::Real { bcc_handover, .. } => bcc_handover,
+ Self::Fake(owned_dice_artifacts) => owned_dice_artifacts,
+ }
+ }
+
pub fn new(driver_path: &Path) -> Result<Self> {
if driver_path.exists() {
log::info!("Using DICE values from driver");
@@ -95,16 +100,15 @@
})
}
- pub fn get_sealing_key(&self, identifier: &[u8]) -> Result<ZVec> {
+ /// Derives a sealing key of `key_length` bytes from the DICE sealing CDI.
+ pub fn get_sealing_key(&self, identifier: &[u8], key_length: usize) -> Result<ZVec> {
// Deterministically derive a key to use for sealing data, rather than using the CDI
// directly, so we have the chance to rotate the key if needed. A salt isn't needed as the
// input key material is already cryptographically strong.
- let cdi_seal = match self {
- Self::Real { bcc_handover, .. } => bcc_handover.cdi_seal,
- Self::Fake(fake) => &fake.cdi_values.cdi_seal,
- };
+ let mut key = ZVec::new(key_length)?;
let salt = &[];
- derive_sealing_key(cdi_seal, salt, identifier, 32)
+ derive_sealing_key(self.dice_artifacts(), salt, identifier, &mut key)?;
+ Ok(key)
}
pub fn derive(
@@ -122,25 +126,21 @@
if debug { DiceMode::kDiceModeDebug } else { DiceMode::kDiceModeNormal },
hidden,
);
- let (cdi_attest, cdi_seal, bcc) = match &self {
- Self::Real { bcc_handover, .. } => (
- bcc_handover.cdi_attest,
- bcc_handover.cdi_seal,
- bcc_handover.bcc.ok_or_else(|| anyhow!("bcc is none"))?,
- ),
- Self::Fake(fake) => {
- (&fake.cdi_values.cdi_attest, &fake.cdi_values.cdi_seal, fake.bcc.as_slice())
- }
- };
- let dice_artifacts = retry_bcc_main_flow(cdi_attest, cdi_seal, bcc, &input_values)
- .context("DICE derive from driver")?;
+ let current_dice_artifacts = self.dice_artifacts();
+ let next_dice_artifacts = retry_bcc_main_flow(
+ current_dice_artifacts.cdi_attest(),
+ current_dice_artifacts.cdi_seal(),
+ current_dice_artifacts.bcc().ok_or_else(|| anyhow!("bcc is none"))?,
+ &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
// included for documentation.
fs::write(driver_path, "wipe")
.map_err(|err| Error::new(err).context("Wiping driver"))?;
}
- Ok(dice_artifacts)
+ Ok(next_dice_artifacts)
}
}
diff --git a/microdroid_manager/src/instance.rs b/microdroid_manager/src/instance.rs
index 96e9360..6900ea5 100644
--- a/microdroid_manager/src/instance.rs
+++ b/microdroid_manager/src/instance.rs
@@ -142,9 +142,9 @@
self.file.read_exact(&mut header)?;
// Decrypt and authenticate the data (along with the header).
- let key = dice.get_sealing_key(INSTANCE_KEY_IDENTIFIER)?;
- let plaintext =
- decrypt_aead(Cipher::aes_256_gcm(), &key, Some(&nonce), &header, &data, &tag)?;
+ let cipher = Cipher::aes_256_gcm();
+ let key = dice.get_sealing_key(INSTANCE_KEY_IDENTIFIER, cipher.key_len())?;
+ let plaintext = decrypt_aead(cipher, &key, Some(&nonce), &header, &data, &tag)?;
let microdroid_data = serde_cbor::from_slice(plaintext.as_slice())?;
Ok(Some(microdroid_data))
@@ -188,10 +188,10 @@
self.file.write_all(nonce.as_ref())?;
// Then encrypt and sign the data.
- let key = dice.get_sealing_key(INSTANCE_KEY_IDENTIFIER)?;
+ let cipher = Cipher::aes_256_gcm();
+ let key = dice.get_sealing_key(INSTANCE_KEY_IDENTIFIER, cipher.key_len())?;
let mut tag = [0; AES_256_GCM_TAG_LENGTH];
- let ciphertext =
- encrypt_aead(Cipher::aes_256_gcm(), &key, Some(&nonce), &header, &data, &mut tag)?;
+ let ciphertext = encrypt_aead(cipher, &key, Some(&nonce), &header, &data, &mut tag)?;
// Persist the encrypted payload data and the tag.
self.file.write_all(&ciphertext)?;
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 7ca0d3c..1148c31 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -40,6 +40,7 @@
use itertools::sorted;
use libc::VMADDR_CID_HOST;
use log::{error, info, warn};
+use keystore2_crypto::ZVec;
use microdroid_metadata::{write_metadata, Metadata, PayloadMetadata};
use microdroid_payload_config::{OsConfig, Task, TaskType, VmPayloadConfig};
use nix::fcntl::{fcntl, F_SETFD, FdFlag};
@@ -917,12 +918,8 @@
0x6F, 0xB3, 0xF9, 0x40, 0xCE, 0xDD, 0x99, 0x40, 0xAA, 0xA7, 0x0E, 0x92, 0x73, 0x90, 0x86,
0x4A, 0x75,
];
- let key = derive_sealing_key(
- &dice_artifacts.cdi_values.cdi_seal,
- &salt,
- ENCRYPTEDSTORE_KEY_IDENTIFIER.as_bytes(),
- ENCRYPTEDSTORE_KEYSIZE,
- )?;
+ let mut key = ZVec::new(ENCRYPTEDSTORE_KEYSIZE)?;
+ derive_sealing_key(dice_artifacts, &salt, ENCRYPTEDSTORE_KEY_IDENTIFIER.as_bytes(), &mut key)?;
let mut cmd = Command::new(ENCRYPTEDSTORE_BIN);
cmd.arg("--blkdevice")
diff --git a/microdroid_manager/src/vm_payload_service.rs b/microdroid_manager/src/vm_payload_service.rs
index ac8f60a..96f51f0 100644
--- a/microdroid_manager/src/vm_payload_service.rs
+++ b/microdroid_manager/src/vm_payload_service.rs
@@ -14,15 +14,14 @@
//! Implementation of the AIDL interface `IVmPayloadService`.
+use crate::dice::derive_sealing_key;
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 diced_open_dice::{DiceArtifacts, OwnedDiceArtifacts};
use log::{error, info};
-use openssl::hkdf::hkdf;
-use openssl::md::Md;
use rpcbinder::RpcServer;
/// Implementation of `IVmPayloadService`.
@@ -48,22 +47,25 @@
0xB7, 0xA8, 0x43, 0x92,
];
let mut secret = vec![0; size.try_into().unwrap()];
- 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)
- })?;
+ derive_sealing_key(&self.dice, &salt, identifier, &mut secret).map_err(|e| {
+ error!("Failed to derive VM instance secret: {:?}", e);
+ Status::new_service_specific_error(-1, None)
+ })?;
Ok(secret)
}
fn getDiceAttestationChain(&self) -> binder::Result<Vec<u8>> {
self.check_restricted_apis_allowed()?;
- Ok(self.dice.bcc.clone())
+ if let Some(bcc) = self.dice.bcc() {
+ Ok(bcc.to_vec())
+ } else {
+ Err(Status::new_exception_str(ExceptionCode::ILLEGAL_STATE, Some("bcc is none")))
+ }
}
fn getDiceAttestationCdi(&self) -> binder::Result<Vec<u8>> {
self.check_restricted_apis_allowed()?;
- Ok(self.dice.cdi_values.cdi_attest.to_vec())
+ Ok(self.dice.cdi_attest().to_vec())
}
}
diff --git a/pvmfw/src/entry.rs b/pvmfw/src/entry.rs
index ddde50a..964ded1 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -391,7 +391,6 @@
}
}
- #[allow(dead_code)] // TODO(b/232900974)
fn get_debug_policy(&mut self) -> Option<&mut [u8]> {
match self {
Self::Config(ref mut cfg) => cfg.get_debug_policy(),
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index ba26114..2e56597 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -63,7 +63,7 @@
memory: &mut MemoryTracker,
) -> Result<(), RebootReason> {
info!("pVM firmware");
- debug!("FDT: {:?}", fdt as *const libfdt::Fdt);
+ debug!("FDT: {:?}", fdt.as_ptr());
debug!("Signed kernel: {:?} ({:#x} bytes)", signed_kernel.as_ptr(), signed_kernel.len());
debug!("AVB public key: addr={:?}, size={:#x} ({1})", PUBLIC_KEY.as_ptr(), PUBLIC_KEY.len());
if let Some(rd) = ramdisk {
diff --git a/pvmfw/src/memory.rs b/pvmfw/src/memory.rs
index 7eecb97..86fcd00 100644
--- a/pvmfw/src/memory.rs
+++ b/pvmfw/src/memory.rs
@@ -267,7 +267,7 @@
for region in &self.regions {
match region.mem_type {
MemoryType::ReadWrite => {
- // TODO: Use page table's dirty bit to only flush pages that were touched.
+ // TODO(b/269738062): Use PT's dirty bit to only flush pages that were touched.
helpers::flush_region(region.range.start, region.range.len())
}
MemoryType::ReadOnly => {}
@@ -314,10 +314,7 @@
// non-zero size.
let buffer = unsafe { alloc_zeroed(layout) };
- // TODO: Use let-else once we have Rust 1.65 in AOSP.
- let buffer = if let Some(buffer) = NonNull::new(buffer) {
- buffer
- } else {
+ let Some(buffer) = NonNull::new(buffer) else {
handle_alloc_error(layout);
};
diff --git a/pvmfw/src/virtio/pci.rs b/pvmfw/src/virtio/pci.rs
index dd9e34e..b61403b 100644
--- a/pvmfw/src/virtio/pci.rs
+++ b/pvmfw/src/virtio/pci.rs
@@ -93,9 +93,7 @@
info, device_function, status, command
);
- let virtio_type = if let Some(t) = virtio_device_type(&info) {
- t
- } else {
+ let Some(virtio_type) = virtio_device_type(&info) else {
continue;
};
debug!(" VirtIO {:?}", virtio_type);
diff --git a/rialto/tests/test.rs b/rialto/tests/test.rs
index 04a87f3..be5f118 100644
--- a/rialto/tests/test.rs
+++ b/rialto/tests/test.rs
@@ -68,6 +68,7 @@
cpuTopology: CpuTopology::ONE_CPU,
platformVersion: "~1.0".to_string(),
taskProfiles: vec![],
+ gdbPort: 0, // No gdb
});
let vm = VmInstance::create(service.as_ref(), &config, Some(console), Some(log), None)
.context("Failed to create VM")?;
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 a81d817..fd0158b 100644
--- a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
+++ b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
@@ -251,7 +251,7 @@
VirtualMachineConfig config =
newVmConfigBuilder()
.setPayloadConfigPath("assets/vm_config_io.json")
- .setDebugLevel(DEBUG_LEVEL_FULL)
+ .setDebugLevel(DEBUG_LEVEL_NONE)
.build();
List<Double> transferRates = new ArrayList<>(IO_TEST_TRIAL_COUNT);
@@ -278,7 +278,7 @@
VirtualMachineConfig config =
newVmConfigBuilder()
.setPayloadConfigPath("assets/vm_config_io.json")
- .setDebugLevel(DEBUG_LEVEL_FULL)
+ .setDebugLevel(DEBUG_LEVEL_NONE)
.build();
List<Double> readRates = new ArrayList<>(IO_TEST_TRIAL_COUNT);
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 9b29fa3..a66f9c3 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -133,9 +133,7 @@
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 {
+ private void createAndConnectToVmHelper(int cpuTopology) throws Exception {
assumeSupportedKernel();
VirtualMachineConfig config =
@@ -143,6 +141,7 @@
.setPayloadBinaryName("MicrodroidTestNativeLib.so")
.setMemoryBytes(minMemoryRequired())
.setDebugLevel(DEBUG_LEVEL_FULL)
+ .setCpuTopology(cpuTopology)
.build();
VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
@@ -166,6 +165,18 @@
@Test
@CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+ public void createAndConnectToVm() throws Exception {
+ createAndConnectToVmHelper(CPU_TOPOLOGY_ONE_CPU);
+ }
+
+ @Test
+ @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+ public void createAndConnectToVm_HostCpuTopology() throws Exception {
+ createAndConnectToVmHelper(CPU_TOPOLOGY_MATCH_HOST);
+ }
+
+ @Test
+ @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
public void createAndRunNoDebugVm() throws Exception {
assumeSupportedKernel();
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index 89f74d6..aceb319 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -63,7 +63,7 @@
use std::ffi::CStr;
use std::fs::{read_dir, remove_file, File, OpenOptions};
use std::io::{BufRead, BufReader, Error, ErrorKind, Write};
-use std::num::NonZeroU32;
+use std::num::{NonZeroU16, NonZeroU32};
use std::os::unix::io::{FromRawFd, IntoRawFd};
use std::os::unix::raw::pid_t;
use std::path::{Path, PathBuf};
@@ -299,14 +299,24 @@
// Some features are reserved for platform apps only, even when using
// VirtualMachineAppConfig:
// - controlling CPUs;
- // - specifying a config file in the APK.
- !config.taskProfiles.is_empty() || matches!(config.payload, Payload::ConfigPath(_))
+ // - specifying a config file in the APK;
+ // - gdbPort is set, meaning that crosvm will start a gdb server.
+ !config.taskProfiles.is_empty()
+ || matches!(config.payload, Payload::ConfigPath(_))
+ || config.gdbPort > 0
}
};
if is_custom {
check_use_custom_virtual_machine()?;
}
+ let gdb_port = extract_gdb_port(config);
+
+ // Additional permission checks if caller request gdb.
+ if gdb_port.is_some() {
+ check_gdb_allowed(config)?;
+ }
+
let state = &mut *self.state.lock().unwrap();
let console_fd =
clone_or_prepare_logger_fd(config, console_fd, format!("Console({})", cid))?;
@@ -430,6 +440,7 @@
indirect_files,
platform_version: parse_platform_version_req(&config.platformVersion)?,
detect_hangup: is_app_config,
+ gdb_port,
};
let instance = Arc::new(
VmInstance::new(
@@ -583,6 +594,7 @@
vm_config.protectedVm = config.protectedVm;
vm_config.cpuTopology = config.cpuTopology;
vm_config.taskProfiles = config.taskProfiles.clone();
+ vm_config.gdbPort = config.gdbPort;
// Microdroid takes additional init ramdisk & (optionally) storage image
add_microdroid_system_images(config, instance_file, storage_image, &mut vm_config)?;
@@ -973,6 +985,43 @@
}
}
+fn is_protected(config: &VirtualMachineConfig) -> bool {
+ match config {
+ VirtualMachineConfig::RawConfig(config) => config.protectedVm,
+ VirtualMachineConfig::AppConfig(config) => config.protectedVm,
+ }
+}
+
+fn check_gdb_allowed(config: &VirtualMachineConfig) -> binder::Result<()> {
+ if is_protected(config) {
+ return Err(Status::new_exception_str(
+ ExceptionCode::SECURITY,
+ Some("can't use gdb with protected VMs"),
+ ));
+ }
+
+ match config {
+ VirtualMachineConfig::RawConfig(_) => Ok(()),
+ VirtualMachineConfig::AppConfig(config) => {
+ if config.debugLevel != DebugLevel::FULL {
+ Err(Status::new_exception_str(
+ ExceptionCode::SECURITY,
+ Some("can't use gdb with non-debuggable VMs"),
+ ))
+ } else {
+ Ok(())
+ }
+ }
+ }
+}
+
+fn extract_gdb_port(config: &VirtualMachineConfig) -> Option<NonZeroU16> {
+ match config {
+ VirtualMachineConfig::RawConfig(config) => NonZeroU16::new(config.gdbPort as u16),
+ VirtualMachineConfig::AppConfig(config) => NonZeroU16::new(config.gdbPort as u16),
+ }
+}
+
fn clone_or_prepare_logger_fd(
config: &VirtualMachineConfig,
fd: Option<&ParcelFileDescriptor>,
diff --git a/virtualizationmanager/src/atom.rs b/virtualizationmanager/src/atom.rs
index 5c1f394..567fce9 100644
--- a/virtualizationmanager/src/atom.rs
+++ b/virtualizationmanager/src/atom.rs
@@ -82,7 +82,7 @@
// Returns the number of CPUs configured in the host system.
// This matches how crosvm determines the number of logical cores.
// For telemetry purposes only.
-fn get_num_cpus() -> Option<usize> {
+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 {
diff --git a/virtualizationmanager/src/crosvm.rs b/virtualizationmanager/src/crosvm.rs
index 66efba9..09605a4 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;
@@ -32,7 +32,7 @@
use std::fs::{read_to_string, File};
use std::io::{self, Read};
use std::mem;
-use std::num::NonZeroU32;
+use std::num::{NonZeroU16, NonZeroU32};
use std::os::unix::io::{AsRawFd, RawFd, FromRawFd};
use std::os::unix::process::ExitStatusExt;
use std::path::{Path, PathBuf};
@@ -105,6 +105,7 @@
pub indirect_files: Vec<File>,
pub platform_version: VersionReq,
pub detect_hangup: bool,
+ pub gdb_port: Option<NonZeroU16>,
}
/// A disk image to pass to crosvm for a VM.
@@ -734,13 +735,22 @@
}
if config.host_cpu_topology {
- command.arg("--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(","));
}
+ if let Some(gdb_port) = config.gdb_port {
+ command.arg("--gdb").arg(gdb_port.to_string());
+ }
+
// Keep track of what file descriptors should be mapped to the crosvm process.
let mut preserved_fds = config.indirect_files.iter().map(|file| file.as_raw_fd()).collect();
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
index 7f90ed6..c467c2f 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
@@ -70,6 +70,12 @@
/** Debug level of the VM */
DebugLevel debugLevel = DebugLevel.NONE;
+ /**
+ * Port at which crosvm will start a gdb server to debug guest kernel.
+ * If set to zero, then gdb server won't be started.
+ */
+ int gdbPort = 0;
+
/** Whether the VM should be a protected VM. */
boolean protectedVm;
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
index 1cda163..87d4ba2 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
@@ -63,4 +63,10 @@
* List of task profile names to apply for the VM
*/
String[] taskProfiles;
+
+ /**
+ * Port at which crosvm will start a gdb server to debug guest kernel.
+ * If set to zero, then gdb server won't be started.
+ */
+ int gdbPort = 0;
}
diff --git a/vm/src/main.rs b/vm/src/main.rs
index 6c08a19..1d9f50b 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -28,6 +28,7 @@
use create_idsig::command_create_idsig;
use create_partition::command_create_partition;
use run::{command_run, command_run_app, command_run_microdroid};
+use std::num::NonZeroU16;
use std::path::{Path, PathBuf};
#[derive(Debug)]
@@ -101,6 +102,11 @@
/// Paths to extra idsig files.
#[clap(long = "extra-idsig")]
extra_idsigs: Vec<PathBuf>,
+
+ /// Port at which crosvm will start a gdb server to debug guest kernel.
+ /// Note: this is only supported on Android kernels android14-5.15 and higher.
+ #[clap(long)]
+ gdb: Option<NonZeroU16>,
},
/// Run a virtual machine with Microdroid inside
RunMicrodroid {
@@ -152,6 +158,11 @@
/// Comma separated list of task profile names to apply to the VM
#[clap(long)]
task_profiles: Vec<String>,
+
+ /// Port at which crosvm will start a gdb server to debug guest kernel.
+ /// Note: this is only supported on Android kernels android14-5.15 and higher.
+ #[clap(long)]
+ gdb: Option<NonZeroU16>,
},
/// Run a virtual machine
Run {
@@ -177,6 +188,11 @@
/// Path to file for VM log output.
#[clap(long)]
log: Option<PathBuf>,
+
+ /// Port at which crosvm will start a gdb server to debug guest kernel.
+ /// Note: this is only supported on Android kernels android14-5.15 and higher.
+ #[clap(long)]
+ gdb: Option<NonZeroU16>,
},
/// List running virtual machines
List,
@@ -260,6 +276,7 @@
cpu_topology,
task_profiles,
extra_idsigs,
+ gdb,
} => command_run_app(
name,
get_service()?.as_ref(),
@@ -278,6 +295,7 @@
cpu_topology,
task_profiles,
&extra_idsigs,
+ gdb,
),
Opt::RunMicrodroid {
name,
@@ -291,6 +309,7 @@
mem,
cpu_topology,
task_profiles,
+ gdb,
} => command_run_microdroid(
name,
get_service()?.as_ref(),
@@ -304,8 +323,9 @@
mem,
cpu_topology,
task_profiles,
+ gdb,
),
- Opt::Run { name, config, cpu_topology, task_profiles, console, log } => {
+ Opt::Run { name, config, cpu_topology, task_profiles, console, log, gdb } => {
command_run(
name,
get_service()?.as_ref(),
@@ -315,6 +335,7 @@
/* mem */ None,
cpu_topology,
task_profiles,
+ gdb,
)
}
Opt::List => command_list(get_service()?.as_ref()),
diff --git a/vm/src/run.rs b/vm/src/run.rs
index fa84591..5d785de 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -32,6 +32,7 @@
use std::fs;
use std::fs::File;
use std::io;
+use std::num::NonZeroU16;
use std::os::unix::io::{AsRawFd, FromRawFd};
use std::path::{Path, PathBuf};
use vmclient::{ErrorCode, VmInstance};
@@ -58,6 +59,7 @@
cpu_topology: CpuTopology,
task_profiles: Vec<String>,
extra_idsigs: &[PathBuf],
+ gdb: Option<NonZeroU16>,
) -> Result<(), Error> {
let apk_file = File::open(apk).context("Failed to open APK file")?;
@@ -144,6 +146,7 @@
memoryMib: mem.unwrap_or(0) as i32, // 0 means use the VM default
cpuTopology: cpu_topology,
taskProfiles: task_profiles,
+ gdbPort: gdb.map(u16::from).unwrap_or(0) as i32, // 0 means no gdb
});
run(service, &config, &payload_config_str, console_path, log_path)
}
@@ -185,6 +188,7 @@
mem: Option<u32>,
cpu_topology: CpuTopology,
task_profiles: Vec<String>,
+ gdb: Option<NonZeroU16>,
) -> Result<(), Error> {
let apk = find_empty_payload_apk_path()?;
println!("found path {}", apk.display());
@@ -215,6 +219,7 @@
cpu_topology,
task_profiles,
&extra_sig,
+ gdb,
)
}
@@ -229,6 +234,7 @@
mem: Option<u32>,
cpu_topology: CpuTopology,
task_profiles: Vec<String>,
+ gdb: Option<NonZeroU16>,
) -> Result<(), Error> {
let config_file = File::open(config_path).context("Failed to open config file")?;
let mut config =
@@ -241,6 +247,9 @@
} else {
config.name = String::from("VmRun");
}
+ if let Some(gdb) = gdb {
+ config.gdbPort = gdb.get() as i32;
+ }
config.cpuTopology = cpu_topology;
config.taskProfiles = task_profiles;
run(
diff --git a/vmbase/example/tests/test.rs b/vmbase/example/tests/test.rs
index cfb0225..930e137 100644
--- a/vmbase/example/tests/test.rs
+++ b/vmbase/example/tests/test.rs
@@ -87,6 +87,7 @@
cpuTopology: CpuTopology::ONE_CPU,
platformVersion: "~1.0".to_string(),
taskProfiles: vec![],
+ gdbPort: 0, // no gdb
});
let console = android_log_fd()?;
let log = android_log_fd()?;