Add --gdb flag to vm run,run-app and run-microdroid commands
This flag will be passed through to crosvm, which in turn will start a
gdb server at the given port. This will allow us to attach gdb to guest
kernel.
There are following rules on how --gdb flag can be used:
1. Only caller with USE_CUSTOM_VIRTUAL_MACHINE permission (a.k.a. shell)
can specify gdbPort in the payload config
2. The --gdb flag is only supported for non-protected VMs.
3. The --gdb flag is only supported for fully debuggable app payloads.
Bug: 242057159
Test: run-microdroid --gdb 3456
Change-Id: Iea93d8e6b37d23801d84cc1dc5fc1a250b54a047
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/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/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/crosvm.rs b/virtualizationmanager/src/crosvm.rs
index ea1146e..09605a4 100644
--- a/virtualizationmanager/src/crosvm.rs
+++ b/virtualizationmanager/src/crosvm.rs
@@ -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.
@@ -746,6 +747,10 @@
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()?;