diff --git a/microdroid/Android.bp b/microdroid/Android.bp index 49013518..77c0b683 100644 --- a/microdroid/Android.bp +++ b/microdroid/Android.bp @@ -77,6 +77,7 @@ android_system_image { "linkerconfig", "servicemanager.microdroid", "tombstoned", + "tombstone_transmit.microdroid", "cgroups.json", "public.libraries.android.txt", diff --git a/microdroid/bootconfig.common b/microdroid/bootconfig.common index eda95a2a..c202ba07 100644 --- a/microdroid/bootconfig.common +++ b/microdroid/bootconfig.common @@ -1,2 +1,6 @@ androidboot.first_stage_console = 1 androidboot.hardware = microdroid + +# tombstone_transmit is enabled. Tombstones will be transmitted to the host +# TODO(b/227443903) : Make this configurable by VM owners +androidboot.tombstone_transmit.enabled=1 diff --git a/microdroid/init.rc b/microdroid/init.rc index f6d50923..dbe5ac74 100644 --- a/microdroid/init.rc +++ b/microdroid/init.rc @@ -173,6 +173,13 @@ on post-fs-data mkdir /data/local 0751 root root mkdir /data/local/tmp 0771 shell shell +on init && property:ro.boot.tombstone_transmit.enabled=1 + start tombstone_transmit + +service tombstone_transmit /system/bin/tombstone_transmit.microdroid -cid 2 -port 2000 + user root + group system + service apexd-vm /system/bin/apexd --vm user root group system diff --git a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java b/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java index d5c5d62a..df3e247a 100644 --- a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java +++ b/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java @@ -105,7 +105,7 @@ public abstract class VirtualizationTestCaseBase extends BaseHostJUnit4Test { } // Run an arbitrary command in the host side and returns the result - private static String runOnHost(String... cmd) { + public static String runOnHost(String... cmd) { return runOnHostWithTimeout(10000, cmd); } diff --git a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java index 5b71ebae..79442452 100644 --- a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java +++ b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java @@ -22,6 +22,7 @@ import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -100,6 +101,13 @@ public class MicrodroidTestCase extends VirtualizationTestCaseBase { false); } + // Wait until logd-init starts. The service is one of the last services that are started in + // the microdroid boot procedure. Therefore, waiting for the service means that we wait for + // the boot to complete. TODO: we need a better marker eventually. + private void waitForLogdInit() { + tryRunOnMicrodroid("watch -e \"getprop init.svc.logd-reinit | grep '^$'\""); + } + @Test public void testCreateVmRequiresPermission() throws Exception { // Revoke the MANAGE_VIRTUAL_MACHINE permission for the test app @@ -349,6 +357,41 @@ public class MicrodroidTestCase extends VirtualizationTestCaseBase { shutdownMicrodroid(getDevice(), cid); } + @Test + public void testTombstonesAreBeingForwarded() throws Exception { + // Note this test relies on logcat values being printed by tombstone_transmit on + // and the reeceiver on host (virtualization_service) + final String configPath = "assets/vm_config.json"; // path inside the APK + final String cid = + startMicrodroid( + getDevice(), + getBuild(), + APK_NAME, + PACKAGE_NAME, + configPath, + /* debug */ true, + minMemorySize(), + Optional.of(NUM_VCPUS), + Optional.of(CPU_AFFINITY)); + adbConnectToMicrodroid(getDevice(), cid); + waitForLogdInit(); + runOnMicrodroid("logcat -c"); + // We need root permission to write to /data/tombstones/ + rootMicrodroid(); + // Write a test tombstone file in /data/tombstones + runOnMicrodroid("echo -n \'Test tombstone in VM with 34 bytes\'" + + "> /data/tombstones/transmit.txt"); + // check if the tombstone have been tranferred from VM + assertNotEquals(runOnMicrodroid("timeout 15s logcat | grep -m 1 " + + "'tombstone_transmit.microdroid:.*data/tombstones/transmit.txt'"), + ""); + // Confirm that tombstone is received (from host logcat) + assertNotEquals(runOnHost("adb", "-s", getDevice().getSerialNumber(), + "logcat", "-d", "-e", + "Received 34 bytes from guest & wrote to tombstone file.*"), + ""); + } + @Test public void testMicrodroidBoots() throws Exception { final String configPath = "assets/vm_config.json"; // path inside the APK @@ -364,12 +407,7 @@ public class MicrodroidTestCase extends VirtualizationTestCaseBase { Optional.of(NUM_VCPUS), Optional.of(CPU_AFFINITY)); adbConnectToMicrodroid(getDevice(), cid); - - // Wait until logd-init starts. The service is one of the last services that are started in - // the microdroid boot procedure. Therefore, waiting for the service means that we wait for - // the boot to complete. TODO: we need a better marker eventually. - tryRunOnMicrodroid("watch -e \"getprop init.svc.logd-reinit | grep '^$'\""); - + waitForLogdInit(); // Test writing to /data partition runOnMicrodroid("echo MicrodroidTest > /data/local/tmp/test.txt"); assertThat(runOnMicrodroid("cat /data/local/tmp/test.txt"), is("MicrodroidTest")); diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp index 9b2b740d..7a8da969 100644 --- a/virtualizationservice/Android.bp +++ b/virtualizationservice/Android.bp @@ -45,6 +45,7 @@ rust_defaults { "libserde_xml_rs", "libshared_child", "libstatslog_virtualization_rust", + "libtombstoned_client_rust", "libvmconfig", "libzip", "libvsock", diff --git a/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl b/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl index 1a16f2a5..dff5d466 100644 --- a/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl +++ b/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl @@ -29,6 +29,12 @@ interface IVirtualMachineService { */ const int VM_BINDER_SERVICE_PORT = 5000; + /** + * Port number that VirtualMachineService listens on connections from the guest VMs for the + * tombtones + */ + const int VM_TOMBSTONES_SERVICE_PORT = 2000; + /** * Notifies that the payload has started. */ diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs index d9825dc4..73cbcfae 100644 --- a/virtualizationservice/src/aidl.rs +++ b/virtualizationservice/src/aidl.rs @@ -42,7 +42,7 @@ use android_system_virtualizationservice::binder::{ use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::{ IVirtualMachineService::{ BnVirtualMachineService, IVirtualMachineService, VM_BINDER_SERVICE_PORT, - VM_STREAM_SERVICE_PORT, + VM_STREAM_SERVICE_PORT, VM_TOMBSTONES_SERVICE_PORT, }, }; use anyhow::{anyhow, bail, Context, Result}; @@ -57,13 +57,14 @@ use statslog_virtualization_rust::vm_creation_requested::{stats_write, Hyperviso use std::convert::TryInto; use std::ffi::CStr; use std::fs::{create_dir, File, OpenOptions}; -use std::io::{Error, ErrorKind, Write}; +use std::io::{Error, ErrorKind, Write, Read}; use std::num::NonZeroU32; use std::os::raw; use std::os::unix::io::{FromRawFd, IntoRawFd}; use std::path::{Path, PathBuf}; use std::ptr::null_mut; use std::sync::{Arc, Mutex, Weak}; +use tombstoned_client::{TombstonedConnection, DebuggerdDumpType}; use vmconfig::VmConfig; use vsock::{SockAddr, VsockListener, VsockStream}; use zip::ZipArchive; @@ -86,6 +87,8 @@ const ANDROID_VM_INSTANCE_MAGIC: &str = "Android-VM-instance"; /// Version of the instance image format const ANDROID_VM_INSTANCE_VERSION: u16 = 1; +const CHUNK_RECV_MAX_LEN: usize = 1024; + /// Implementation of `IVirtualizationService`, the entry point of the AIDL service. #[derive(Debug, Default)] pub struct VirtualizationService { @@ -371,6 +374,55 @@ impl IVirtualizationService for VirtualizationService { } } +fn handle_stream_connection_tombstoned() -> Result<()> { + let listener = + VsockListener::bind_with_cid_port(VMADDR_CID_HOST, VM_TOMBSTONES_SERVICE_PORT as u32)?; + info!("Listening to tombstones from guests ..."); + for incoming_stream in listener.incoming() { + let mut incoming_stream = match incoming_stream { + Err(e) => { + warn!("invalid incoming connection: {}", e); + continue; + } + Ok(s) => s, + }; + std::thread::spawn(move || { + if let Err(e) = handle_tombstone(&mut incoming_stream) { + error!("Failed to write tombstone- {:?}", e); + } + }); + } + Ok(()) +} + +fn handle_tombstone(stream: &mut VsockStream) -> Result<()> { + if let Ok(SockAddr::Vsock(addr)) = stream.peer_addr() { + info!("Vsock Stream connected to cid={} for tombstones", addr.cid()); + } + let tb_connection = + TombstonedConnection::connect(std::process::id() as i32, DebuggerdDumpType::Tombstone) + .context("Failed to connect to tombstoned")?; + let mut text_output = tb_connection + .text_output + .as_ref() + .ok_or_else(|| anyhow!("Could not get file to write the tombstones on"))?; + let mut num_bytes_read = 0; + loop { + let mut chunk_recv = [0; CHUNK_RECV_MAX_LEN]; + let n = stream + .read(&mut chunk_recv) + .context("Failed to read tombstone data from Vsock stream")?; + if n == 0 { + break; + } + num_bytes_read += n; + text_output.write_all(&chunk_recv[0..n]).context("Failed to write guests tombstones")?; + } + info!("Received {} bytes from guest & wrote to tombstone file", num_bytes_read); + tb_connection.notify_completion()?; + Ok(()) +} + impl VirtualizationService { pub fn init() -> VirtualizationService { let service = VirtualizationService::default(); @@ -381,8 +433,15 @@ impl VirtualizationService { handle_stream_connection_from_vm(state).unwrap(); }); + std::thread::spawn(|| { + if let Err(e) = handle_stream_connection_tombstoned() { + warn!("Error receiving tombstone from guest or writing them. Error: {}", e); + } + }); + // binder server for vm - let mut state = service.state.clone(); // reference to state (not the state itself) is copied + // reference to state (not the state itself) is copied + let mut state = service.state.clone(); std::thread::spawn(move || { let state_ptr = &mut state as *mut _ as *mut raw::c_void;