Enable exporting of tombstones out of guest VMs
Microdroid init will start the tombstone_transmit service which will connect to a server (thread of virtualization service). virtualizationservice listens for incoming connections from guests VMs. The client (tombstone_transmit in guest) sends each tombstone file content through a separate stream. For each received connection, handle_tombstone would send a connect request to tombstoned(via libtombstoned_client_rust), which provide a file to write the content in (actually managed by the client libary). Once write is complete notify_completion() would send completion notification to tombstoned (which then will close the file and rename it). Test: atest MicrodroidHostTestCases and/or crash a process in microdroid, take and check bugreport Bug: 202153827 Change-Id: Ifcaa5da968ef39fdd05612d3e0baca4fd1c5eaf1
This commit is contained in:
parent
0689e7e162
commit
7afc13991c
|
@ -77,6 +77,7 @@ android_system_image {
|
|||
"linkerconfig",
|
||||
"servicemanager.microdroid",
|
||||
"tombstoned",
|
||||
"tombstone_transmit.microdroid",
|
||||
"cgroups.json",
|
||||
"public.libraries.android.txt",
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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"));
|
||||
|
|
|
@ -45,6 +45,7 @@ rust_defaults {
|
|||
"libserde_xml_rs",
|
||||
"libshared_child",
|
||||
"libstatslog_virtualization_rust",
|
||||
"libtombstoned_client_rust",
|
||||
"libvmconfig",
|
||||
"libzip",
|
||||
"libvsock",
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in New Issue