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()?;