• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2021, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! Android VM control tool.
16 
17 mod create_idsig;
18 mod create_partition;
19 mod run;
20 
21 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
22     CpuOptions::CpuTopology::CpuTopology, IVirtualizationService::IVirtualizationService,
23     PartitionType::PartitionType, VirtualMachineAppConfig::DebugLevel::DebugLevel,
24 };
25 use anyhow::{bail, Context, Error};
26 use binder::{ProcessState, Strong};
27 use clap::{Args, Parser};
28 use create_idsig::command_create_idsig;
29 use create_partition::command_create_partition;
30 use run::{command_run, command_run_app, command_run_microdroid};
31 use serde::Serialize;
32 use std::io::{self, IsTerminal};
33 use std::num::NonZeroU16;
34 use std::os::unix::process::CommandExt;
35 use std::path::{Path, PathBuf};
36 use std::process::Command;
37 
38 #[derive(Args, Default)]
39 /// Collection of flags that are at VM level and therefore applicable to all subcommands
40 pub struct CommonConfig {
41     /// Name of VM
42     #[arg(long)]
43     name: Option<String>,
44 
45     /// Run VM with vCPU topology matching that of the host. If unspecified, defaults to 1 vCPU.
46     #[arg(long, default_value = "one_cpu", value_parser = parse_cpu_topology)]
47     cpu_topology: CpuTopology,
48 
49     /// Memory size (in MiB) of the VM. If unspecified, defaults to the value of `memory_mib`
50     /// in the VM config file.
51     #[arg(short, long)]
52     mem: Option<u32>,
53 
54     /// Run VM in protected mode.
55     #[arg(short, long)]
56     protected: bool,
57 
58     /// Ask the kernel for transparent huge-pages (THP). This is only a hint and
59     /// the kernel will allocate THP-backed memory only if globally enabled by
60     /// the system and if any can be found. See
61     /// https://docs.kernel.org/admin-guide/mm/transhuge.html
62     #[arg(short, long)]
63     hugepages: bool,
64 
65     /// Run VM with network feature.
66     #[cfg(network)]
67     #[arg(short, long)]
68     network_supported: bool,
69 
70     /// Boost uclamp to stablise results for benchmarks.
71     #[arg(short, long)]
72     boost_uclamp: bool,
73 
74     /// Secure services this VM wants to access.
75     #[cfg(tee_services_allowlist)]
76     #[arg(long)]
77     tee_services: Vec<String>,
78 }
79 
80 impl CommonConfig {
network_supported(&self) -> bool81     fn network_supported(&self) -> bool {
82         cfg_if::cfg_if! {
83             if #[cfg(network)] {
84                 self.network_supported
85             } else {
86                 false
87             }
88         }
89     }
90 
tee_services(&self) -> &[String]91     fn tee_services(&self) -> &[String] {
92         cfg_if::cfg_if! {
93             if #[cfg(tee_services_allowlist)] {
94                 &self.tee_services
95             } else {
96                 &[]
97             }
98         }
99     }
100 }
101 
102 #[derive(Args, Default)]
103 /// Collection of flags for debugging
104 pub struct DebugConfig {
105     /// Debug level of the VM. Supported values: "full" (default), and "none".
106     #[arg(long, default_value = "full", value_parser = parse_debug_level)]
107     debug: DebugLevel,
108 
109     /// Path to file for VM console output.
110     #[arg(long)]
111     console: Option<PathBuf>,
112 
113     /// Path to file for VM console input.
114     #[arg(long)]
115     console_in: Option<PathBuf>,
116 
117     /// Path to file for VM log output.
118     #[arg(long)]
119     log: Option<PathBuf>,
120 
121     /// Port at which crosvm will start a gdb server to debug guest kernel.
122     /// Note: this is only supported on Android kernels android14-5.15 and higher.
123     #[arg(long)]
124     gdb: Option<NonZeroU16>,
125 
126     /// Whether to enable earlycon. Only supported for debuggable Linux-based VMs.
127     #[cfg(debuggable_vms_improvements)]
128     #[arg(long)]
129     enable_earlycon: bool,
130 
131     /// Path to file to dump VM device tree.
132     #[arg(long)]
133     dump_device_tree: Option<PathBuf>,
134 }
135 
136 impl DebugConfig {
enable_earlycon(&self) -> bool137     fn enable_earlycon(&self) -> bool {
138         cfg_if::cfg_if! {
139             if #[cfg(debuggable_vms_improvements)] {
140                 self.enable_earlycon
141             } else {
142                 false
143             }
144         }
145     }
146 }
147 
148 #[derive(Args, Default)]
149 /// Collection of flags that are Microdroid specific
150 pub struct MicrodroidConfig {
151     /// Path to the file backing the storage.
152     /// Created if the option is used but the path does not exist in the device.
153     #[arg(long)]
154     storage: Option<PathBuf>,
155 
156     /// Size of the storage. Used only if --storage is supplied but path does not exist
157     /// Default size is 10*1024*1024
158     #[arg(long)]
159     storage_size: Option<u64>,
160 
161     /// Path to disk image containing vendor-specific modules.
162     #[cfg(vendor_modules)]
163     #[arg(long)]
164     vendor: Option<PathBuf>,
165 
166     /// SysFS nodes of devices to assign to VM
167     #[cfg(device_assignment)]
168     #[arg(long)]
169     devices: Vec<PathBuf>,
170 
171     /// Version of OS to use. If not set, defaults to microdroid.
172     /// You can list all available OSes via `vm info` command.
173     #[arg(long)]
174     os: Option<String>,
175 }
176 
177 impl MicrodroidConfig {
vendor(&self) -> Option<&PathBuf>178     fn vendor(&self) -> Option<&PathBuf> {
179         cfg_if::cfg_if! {
180             if #[cfg(vendor_modules)] {
181                 self.vendor.as_ref()
182             } else {
183                 None
184             }
185         }
186     }
187 
devices(&self) -> &[PathBuf]188     fn devices(&self) -> &[PathBuf] {
189         cfg_if::cfg_if! {
190             if #[cfg(device_assignment)] {
191                 &self.devices
192             } else {
193                 &[]
194             }
195         }
196     }
197 }
198 
199 #[derive(Args, Default)]
200 /// Flags for the run_app subcommand
201 pub struct RunAppConfig {
202     #[command(flatten)]
203     common: CommonConfig,
204 
205     #[command(flatten)]
206     debug: DebugConfig,
207 
208     #[command(flatten)]
209     microdroid: MicrodroidConfig,
210 
211     /// Path to VM Payload APK
212     apk: PathBuf,
213 
214     /// Path to idsig of the APK
215     idsig: PathBuf,
216 
217     /// Path to the instance image. Created if not exists.
218     instance: PathBuf,
219 
220     /// Path to file containing instance_id. Required iff llpvm feature is enabled.
221     #[arg(long = "instance-id-file")]
222     instance_id: PathBuf,
223 
224     /// Path to VM config JSON within APK (e.g. assets/vm_config.json)
225     #[arg(long)]
226     config_path: Option<String>,
227 
228     /// Name of VM payload binary within APK (e.g. MicrodroidTestNativeLib.so)
229     #[arg(long)]
230     #[arg(alias = "payload_path")]
231     payload_binary_name: Option<String>,
232 
233     /// Paths to extra apk files.
234     #[cfg(multi_tenant)]
235     #[arg(long = "extra-apk")]
236     #[clap(conflicts_with = "config_path")]
237     extra_apks: Vec<PathBuf>,
238 
239     /// Paths to extra idsig files.
240     #[arg(long = "extra-idsig")]
241     extra_idsigs: Vec<PathBuf>,
242 }
243 
244 impl RunAppConfig {
extra_apks(&self) -> &[PathBuf]245     fn extra_apks(&self) -> &[PathBuf] {
246         cfg_if::cfg_if! {
247             if #[cfg(multi_tenant)] {
248                 &self.extra_apks
249             } else {
250                 &[]
251             }
252         }
253     }
254 
set_instance_id(&mut self, instance_id_file: PathBuf)255     fn set_instance_id(&mut self, instance_id_file: PathBuf) {
256         self.instance_id = instance_id_file;
257     }
258 }
259 
260 #[derive(Args, Default)]
261 /// Flags for the run_microdroid subcommand
262 pub struct RunMicrodroidConfig {
263     #[command(flatten)]
264     common: CommonConfig,
265 
266     #[command(flatten)]
267     debug: DebugConfig,
268 
269     #[command(flatten)]
270     microdroid: MicrodroidConfig,
271 
272     /// Path to the directory where VM-related files (e.g. instance.img, apk.idsig, etc.) will
273     /// be stored. If not specified a random directory under /data/local/tmp/microdroid will be
274     /// created and used.
275     #[arg(long)]
276     work_dir: Option<PathBuf>,
277 }
278 
279 #[derive(Args, Default)]
280 /// Flags for the run subcommand
281 pub struct RunCustomVmConfig {
282     #[command(flatten)]
283     common: CommonConfig,
284 
285     #[command(flatten)]
286     debug: DebugConfig,
287 
288     /// Path to VM config JSON
289     config: PathBuf,
290 }
291 
292 #[derive(Parser)]
293 enum Opt {
294     /// Check if the feature is enabled on device.
295     CheckFeatureEnabled { feature: String },
296     /// Run a virtual machine with a config in APK
297     RunApp {
298         #[command(flatten)]
299         config: RunAppConfig,
300     },
301     /// Run a virtual machine with Microdroid inside
302     RunMicrodroid {
303         #[command(flatten)]
304         config: RunMicrodroidConfig,
305     },
306     /// Run a virtual machine
307     Run {
308         #[command(flatten)]
309         config: RunCustomVmConfig,
310     },
311     /// List running virtual machines
312     List,
313     /// Print information about virtual machine support
314     Info,
315     /// Create a new empty partition to be used as a writable partition for a VM
316     CreatePartition {
317         /// Path at which to create the image file
318         path: PathBuf,
319 
320         /// The desired size of the partition, in bytes.
321         size: u64,
322 
323         /// Type of the partition
324         #[arg(short = 't', long = "type", default_value = "raw",
325                value_parser = parse_partition_type)]
326         partition_type: PartitionType,
327     },
328     /// Creates or update the idsig file by digesting the input APK file.
329     CreateIdsig {
330         /// Path to VM Payload APK
331         apk: PathBuf,
332 
333         /// Path to idsig of the APK
334         path: PathBuf,
335     },
336     /// Connect to the serial console of a VM
337     Console {
338         /// CID of the VM
339         cid: Option<i32>,
340     },
341 }
342 
parse_debug_level(s: &str) -> Result<DebugLevel, String>343 fn parse_debug_level(s: &str) -> Result<DebugLevel, String> {
344     match s {
345         "none" => Ok(DebugLevel::NONE),
346         "full" => Ok(DebugLevel::FULL),
347         _ => Err(format!("Invalid debug level {}", s)),
348     }
349 }
350 
parse_partition_type(s: &str) -> Result<PartitionType, String>351 fn parse_partition_type(s: &str) -> Result<PartitionType, String> {
352     match s {
353         "raw" => Ok(PartitionType::RAW),
354         "instance" => Ok(PartitionType::ANDROID_VM_INSTANCE),
355         _ => Err(format!("Invalid partition type {}", s)),
356     }
357 }
358 
parse_cpu_topology(s: &str) -> Result<CpuTopology, String>359 fn parse_cpu_topology(s: &str) -> Result<CpuTopology, String> {
360     match s {
361         "one_cpu" => Ok(CpuTopology::CpuCount(1)),
362         "match_host" => Ok(CpuTopology::MatchHost(true)),
363         _ if s.starts_with("cpu_count=") => {
364             // Safe to unwrap as it's validated the string starts with cpu_count=
365             let val = s.strip_prefix("cpu_count=").unwrap();
366             Ok(CpuTopology::CpuCount(val.parse().map_err(|e| format!("Invalid CPU Count: {}", e))?))
367         }
368         _ => Err(format!("Invalid cpu topology {}", s)),
369     }
370 }
371 
get_service() -> Result<Strong<dyn IVirtualizationService>, Error>372 fn get_service() -> Result<Strong<dyn IVirtualizationService>, Error> {
373     let virtmgr =
374         vmclient::VirtualizationService::new().context("Failed to spawn VirtualizationService")?;
375     virtmgr.connect().context("Failed to connect to VirtualizationService")
376 }
377 
command_check_feature_enabled(feature: &str)378 fn command_check_feature_enabled(feature: &str) {
379     println!(
380         "Feature {feature} is {}",
381         if avf_features::is_feature_enabled(feature) { "enabled" } else { "disabled" }
382     );
383 }
384 
main() -> Result<(), Error>385 fn main() -> Result<(), Error> {
386     env_logger::init();
387     let opt = Opt::parse();
388 
389     // We need to start the thread pool for Binder to work properly, especially link_to_death.
390     ProcessState::start_thread_pool();
391 
392     match opt {
393         Opt::CheckFeatureEnabled { feature } => {
394             command_check_feature_enabled(&feature);
395             Ok(())
396         }
397         Opt::RunApp { config } => command_run_app(config),
398         Opt::RunMicrodroid { config } => command_run_microdroid(config),
399         Opt::Run { config } => command_run(config),
400         Opt::List => command_list(get_service()?.as_ref()),
401         Opt::Info => command_info(),
402         Opt::CreatePartition { path, size, partition_type } => {
403             command_create_partition(get_service()?.as_ref(), &path, size, partition_type)
404         }
405         Opt::CreateIdsig { apk, path } => {
406             command_create_idsig(get_service()?.as_ref(), &apk, &path)
407         }
408         Opt::Console { cid } => command_console(cid),
409     }
410 }
411 
412 /// List the VMs currently running.
command_list(service: &dyn IVirtualizationService) -> Result<(), Error>413 fn command_list(service: &dyn IVirtualizationService) -> Result<(), Error> {
414     let vms = service.debugListVms().context("Failed to get list of VMs")?;
415     println!("Running VMs: {:#?}", vms);
416     Ok(())
417 }
418 
419 /// Print information about supported VM types.
command_info() -> Result<(), Error>420 fn command_info() -> Result<(), Error> {
421     let non_protected_vm_supported = hypervisor_props::is_vm_supported()?;
422     let protected_vm_supported = hypervisor_props::is_protected_vm_supported()?;
423     match (non_protected_vm_supported, protected_vm_supported) {
424         (false, false) => println!("VMs are not supported."),
425         (false, true) => println!("Only protected VMs are supported."),
426         (true, false) => println!("Only non-protected VMs are supported."),
427         (true, true) => println!("Both protected and non-protected VMs are supported."),
428     }
429 
430     if let Some(version) = hypervisor_props::version()? {
431         println!("Hypervisor version: {}", version);
432     } else {
433         println!("Hypervisor version not set.");
434     }
435 
436     if Path::new("/dev/kvm").exists() {
437         println!("/dev/kvm exists.");
438     } else {
439         println!("/dev/kvm does not exist.");
440     }
441 
442     if Path::new("/dev/vfio/vfio").exists() {
443         println!("/dev/vfio/vfio exists.");
444     } else {
445         println!("/dev/vfio/vfio does not exist.");
446     }
447 
448     if Path::new("/sys/bus/platform/drivers/vfio-platform").exists() {
449         println!("VFIO-platform is supported.");
450     } else {
451         println!("VFIO-platform is not supported.");
452     }
453 
454     #[derive(Serialize)]
455     struct AssignableDevice {
456         node: String,
457         dtbo_label: String,
458     }
459 
460     let devices = get_service()?.getAssignableDevices()?;
461     let devices: Vec<_> = devices
462         .into_iter()
463         .map(|device| AssignableDevice { node: device.node, dtbo_label: device.dtbo_label })
464         .collect();
465     println!("Assignable devices: {}", serde_json::to_string(&devices)?);
466 
467     let os_list = get_service()?.getSupportedOSList()?;
468     println!("Available OS list: {}", serde_json::to_string(&os_list)?);
469 
470     let debug_policy = get_service()?.getDebugPolicy()?;
471     println!("Debug policy: {}", debug_policy);
472 
473     Ok(())
474 }
475 
command_console(cid: Option<i32>) -> Result<(), Error>476 fn command_console(cid: Option<i32>) -> Result<(), Error> {
477     if !io::stdin().is_terminal() {
478         bail!("Stdin must be a terminal (tty). Use 'adb shell -t' to force allocate tty.");
479     }
480     let mut vms = get_service()?.debugListVms().context("Failed to get list of VMs")?;
481     if let Some(cid) = cid {
482         vms.retain(|vm_info| vm_info.cid == cid);
483     }
484     let host_console_name = vms
485         .into_iter()
486         .find_map(|vm_info| vm_info.hostConsoleName)
487         .context("Failed to get VM with console")?;
488     Err(Command::new("microcom").arg(host_console_name).exec().into())
489 }
490 
491 #[cfg(test)]
492 mod tests {
493     use super::*;
494     use clap::CommandFactory;
495 
496     #[test]
verify_app()497     fn verify_app() {
498         // Check that the command parsing has been configured in a valid way.
499         Opt::command().debug_assert();
500     }
501 }
502