diff --git a/.gitignore b/.gitignore index cbf9e7fea..f6dd2427f 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,6 @@ qemu.log # packet dump file virtio-net.pcap + +# vscode launch config file +.vscode/launch.json diff --git a/osdk/src/cli.rs b/osdk/src/cli.rs index 5fdc6003c..534a9dde3 100644 --- a/osdk/src/cli.rs +++ b/osdk/src/cli.rs @@ -6,13 +6,13 @@ use clap::{crate_version, Args, Parser}; use crate::{ commands::{ - execute_build_command, execute_forwarded_command, execute_new_command, execute_run_command, - execute_test_command, + execute_build_command, execute_debug_command, execute_forwarded_command, + execute_new_command, execute_run_command, execute_test_command, }, config_manager::{ boot::{BootLoader, BootProtocol}, qemu::QemuMachine, - BuildConfig, RunConfig, TestConfig, + BuildConfig, DebugConfig, RunConfig, TestConfig, }, }; @@ -33,6 +33,10 @@ pub fn main() { let run_config = RunConfig::parse(run_args); execute_run_command(&run_config); } + OsdkSubcommand::Debug(debug_args) => { + let debug_config = DebugConfig::parse(debug_args); + execute_debug_command(&debug_config); + } OsdkSubcommand::Test(test_args) => { let test_config = TestConfig::parse(test_args); execute_test_command(&test_config); @@ -67,6 +71,8 @@ pub enum OsdkSubcommand { Build(BuildArgs), #[command(about = "Run the kernel with a VMM")] Run(RunArgs), + #[command(about = "Debug the kernel with a VMM as a client or a server")] + Debug(DebugArgs), #[command(about = "Execute kernel mode unit test by starting a VMM")] Test(TestArgs), #[command(about = "Check a local package and all of its dependencies for errors")] @@ -109,6 +115,24 @@ pub struct RunArgs { pub cargo_args: CargoArgs, #[command(flatten)] pub osdk_args: OsdkArgs, + #[arg(long, short = 'G', help = "Enable QEMU GDB server for debugging")] + pub gdb_server: bool, + #[arg( + long = "vsc", + help = "Generate a '.vscode/launch.json' for debugging with Visual Studio Code \ + (only works when '--gdb-server' is enabled)" + )] + pub vsc_launch_file: bool, +} + +#[derive(Debug, Parser)] +pub struct DebugArgs { + #[command(flatten)] + pub cargo_args: CargoArgs, + #[command(flatten)] + pub osdk_args: OsdkArgs, + #[arg(name = "ROLE", help = "'client' or 'server'", default_value = "server")] + pub role: String, } #[derive(Debug, Parser)] diff --git a/osdk/src/commands/debug.rs b/osdk/src/commands/debug.rs new file mode 100644 index 000000000..9b197fc75 --- /dev/null +++ b/osdk/src/commands/debug.rs @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MPL-2.0 + +use crate::commands::util::{bin_file_name, profile_adapter}; +use crate::config_manager::DebugConfig; + +use crate::util::get_target_directory; +use std::process::Command; + +pub fn execute_debug_command(config: &DebugConfig) { + let DebugConfig { + manifest, + cargo_args, + } = config; + + let profile = profile_adapter(&cargo_args.profile); + let file_path = get_target_directory() + .join("x86_64-unknown-none") + .join(profile) + .join(bin_file_name()); + println!("Debugging {:?}", file_path); + + let mut gdb = Command::new("gdb"); + gdb.args([ + "-ex", + "target remote :1234", + "-ex", + format!("file {}", file_path.display()).as_str(), + ]); + gdb.status().unwrap(); +} diff --git a/osdk/src/commands/launch.json.template b/osdk/src/commands/launch.json.template new file mode 100644 index 000000000..be93c6774 --- /dev/null +++ b/osdk/src/commands/launch.json.template @@ -0,0 +1,12 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug Asterinas(#PROFILE#)", + "type": "lldb", + "request": "custom", + "targetCreateCommands": ["target create ${workspaceFolder}/target/x86_64-unknown-none/#PROFILE#/#BIN_NAME#"], + "processCreateCommands": ["gdb-remote 1234"] + } + ] +} diff --git a/osdk/src/commands/mod.rs b/osdk/src/commands/mod.rs index 426e29040..ab3470b57 100644 --- a/osdk/src/commands/mod.rs +++ b/osdk/src/commands/mod.rs @@ -3,14 +3,15 @@ //! This module contains subcommands of cargo-osdk. mod build; +mod debug; mod new; mod run; mod test; mod util; pub use self::{ - build::execute_build_command, new::execute_new_command, run::execute_run_command, - test::execute_test_command, + build::execute_build_command, debug::execute_debug_command, new::execute_new_command, + run::execute_run_command, test::execute_test_command, }; /// Execute the forwarded cargo command with args containing the subcommand and its arguments. diff --git a/osdk/src/commands/run.rs b/osdk/src/commands/run.rs index 7973469f5..c4ba2304f 100644 --- a/osdk/src/commands/run.rs +++ b/osdk/src/commands/run.rs @@ -9,10 +9,20 @@ use super::{build::create_base_and_build, util::DEFAULT_TARGET_RELPATH}; use crate::{ bundle::Bundle, config_manager::{BuildConfig, RunConfig}, + error::Errno, + error_msg, util::{get_cargo_metadata, get_current_crate_info, get_target_directory}, }; pub fn execute_run_command(config: &RunConfig) { + if config.gdb_server { + use std::env; + env::set_var( + "RUSTFLAGS", + env::var("RUSTFLAGS").unwrap_or_default() + " -g", + ); + } + let ws_target_directory = get_target_directory(); let osdk_target_directory = ws_target_directory.join(DEFAULT_TARGET_RELPATH); let target_name = get_current_crate_info().name; @@ -40,8 +50,21 @@ pub fn execute_run_command(config: &RunConfig) { } } + let manifest = if config.gdb_server { + let qemu_dbg_args: Vec<_> = vec!["-s", "-S"] + .into_iter() + .filter(|arg| !config.manifest.qemu.args.iter().any(|x| x == arg)) + .map(|x| x.to_string()) + .collect(); + let mut manifest = config.manifest.clone(); + manifest.qemu.args.extend(qemu_dbg_args); + manifest + } else { + config.manifest.clone() + }; + let required_build_config = BuildConfig { - manifest: config.manifest.clone(), + manifest: manifest.clone(), cargo_args: config.cargo_args.clone(), }; @@ -53,7 +76,21 @@ pub fn execute_run_command(config: &RunConfig) { &[], ); - bundle.run(config); + let _vsc_launch_file = config.vsc_launch_file.then(|| { + if !config.gdb_server { + error_msg!( + "No need for a VSCode launch file without launching GDB server,\ + pass '-h' for help" + ); + std::process::exit(Errno::ExecuteCommand as _); + } + let profile = super::util::profile_adapter(&config.cargo_args.profile); + vsc::VscLaunchConfig::new(profile) + }); + bundle.run(&RunConfig { + manifest, + ..config.clone() + }); } fn get_last_modified_time(path: impl AsRef) -> SystemTime { @@ -73,3 +110,147 @@ fn get_last_modified_time(path: impl AsRef) -> SystemTime { } last_modified } + +mod vsc { + use crate::{commands::util::bin_file_name, util::get_cargo_metadata}; + + use serde_json::{from_str, Value}; + + use std::{ + fs::{read_to_string, write as write_file}, + path::Path, + }; + + const VSC_DIR: &str = ".vscode"; + + #[derive(Debug)] + struct Existence { + vsc_dir: bool, + launch_file: bool, + } + + impl Default for Existence { + fn default() -> Self { + Existence { + vsc_dir: false, + launch_file: false, + } + } + } + + fn workspace_root() -> String { + get_cargo_metadata(None::<&str>, None::<&[&str]>).unwrap()["workspace_root"] + .as_str() + .unwrap() + .to_owned() + } + #[derive(Debug, Default)] + pub struct VscLaunchConfig { + existence: Existence, + backup_launch_path: Option, + } + impl VscLaunchConfig { + pub fn new(profile: &str) -> Self { + let workspace = workspace_root(); + let workspace = Path::new(&workspace); + let launch_file_path = workspace.join(VSC_DIR).join("launch.json"); + let existence = Existence { + vsc_dir: workspace.join(VSC_DIR).exists(), + launch_file: launch_file_path.exists(), + }; + + let backup_launch_path = existence.launch_file.then(|| { + let backup_launch_path = launch_file_path.with_extension("bak"); + std::fs::copy(&launch_file_path, &backup_launch_path).unwrap(); + backup_launch_path.to_string_lossy().to_string() + }); + + if !existence.vsc_dir { + std::fs::create_dir(workspace.join(VSC_DIR)).unwrap(); + } + generate_vsc_launch_file(&workspace, profile).unwrap(); + + VscLaunchConfig { + existence, + backup_launch_path, + } + } + } + + impl Drop for VscLaunchConfig { + fn drop(&mut self) { + // remove generated files + if !self.existence.vsc_dir { + std::fs::remove_dir_all(Path::new(&workspace_root()).join(VSC_DIR)).unwrap(); + return; + } + if !self.existence.launch_file { + std::fs::remove_file( + Path::new(&workspace_root()) + .join(VSC_DIR) + .join("launch.json"), + ) + .unwrap(); + return; + } + // restore backup launch file + if let Some(backup_launch_path) = &self.backup_launch_path { + std::fs::copy( + backup_launch_path, + Path::new(&workspace_root()) + .join(VSC_DIR) + .join("launch.json"), + ) + .unwrap(); + std::fs::remove_file(backup_launch_path).unwrap(); + } + } + } + + fn generate_vsc_launch_file( + workspace: impl AsRef, + profile: &str, + ) -> Result<(), std::io::Error> { + let contents = include_str!("launch.json.template") + .replace("#PROFILE#", profile) + .replace("#BIN_NAME#", &bin_file_name()); + + let original_items: Option = { + let launch_file_path = workspace.as_ref().join(VSC_DIR).join("launch.json"); + let src_path = if launch_file_path.exists() { + launch_file_path + } else { + launch_file_path.with_extension("bak") + }; + src_path + .exists() + .then(|| from_str(&read_to_string(&src_path).unwrap()).unwrap()) + }; + + let contents = if let Some(mut original_items) = original_items { + let items: Value = from_str(&contents)?; + for item in items["configurations"].as_array().unwrap() { + if original_items["configurations"] + .as_array() + .unwrap() + .iter() + .any(|x| x["name"].as_str() == item["name"].as_str()) + { + println!("[{}/launch.json]{} already exists", VSC_DIR, item["name"]); + // no override for configurations with the same name + continue; + } + original_items["configurations"] + .as_array_mut() + .unwrap() + .push(item.clone()); + } + serde_json::to_string_pretty(&original_items).unwrap() + } else { + contents + }; + + let launch_file_path = workspace.as_ref().join(VSC_DIR).join("launch.json"); + write_file(launch_file_path, contents) + } +} diff --git a/osdk/src/commands/test.rs b/osdk/src/commands/test.rs index 3cc1ee698..e45954bd6 100644 --- a/osdk/src/commands/test.rs +++ b/osdk/src/commands/test.rs @@ -73,6 +73,8 @@ pub static KTEST_CRATE_WHITELIST: Option<&[&str]> = Some(&{:#?}); let required_run_config = RunConfig { manifest: required_build_config.manifest.clone(), cargo_args: required_build_config.cargo_args.clone(), + gdb_server: false, + vsc_launch_file: false, }; bundle.run(&required_run_config); diff --git a/osdk/src/commands/util.rs b/osdk/src/commands/util.rs index 19538ba13..d6f2647ff 100644 --- a/osdk/src/commands/util.rs +++ b/osdk/src/commands/util.rs @@ -2,6 +2,8 @@ use std::process::Command; +use crate::util::get_current_crate_info; + pub const COMMON_CARGO_ARGS: &[&str] = &[ "-Zbuild-std=core,alloc,compiler_builtins", "-Zbuild-std-features=compiler-builtins-mem", @@ -12,3 +14,14 @@ pub const DEFAULT_TARGET_RELPATH: &str = "osdk"; pub fn cargo() -> Command { Command::new("cargo") } + +pub fn profile_adapter(profile: &str) -> &str { + match profile { + "dev" => "debug", + _ => profile, + } +} + +pub fn bin_file_name() -> String { + get_current_crate_info().name + "-osdk-bin" +} diff --git a/osdk/src/config_manager/mod.rs b/osdk/src/config_manager/mod.rs index e411ebae2..67c5d3df0 100644 --- a/osdk/src/config_manager/mod.rs +++ b/osdk/src/config_manager/mod.rs @@ -22,13 +22,20 @@ use self::{ manifest::{OsdkManifest, TomlManifest}, }; use crate::{ - cli::{BuildArgs, CargoArgs, OsdkArgs, RunArgs, TestArgs}, + cli::{BuildArgs, CargoArgs, DebugArgs, OsdkArgs, RunArgs, TestArgs}, error::Errno, error_msg, util::get_cargo_metadata, warn_msg, }; +fn get_final_manifest(cargo_args: &CargoArgs, osdk_args: &OsdkArgs) -> OsdkManifest { + let mut manifest = load_osdk_manifest(cargo_args, osdk_args.select.as_ref()); + apply_cli_args(&mut manifest, &osdk_args); + try_fill_system_configs(&mut manifest); + manifest +} + /// Configurations for build subcommand #[derive(Debug)] pub struct BuildConfig { @@ -39,31 +46,45 @@ pub struct BuildConfig { impl BuildConfig { pub fn parse(args: &BuildArgs) -> Self { let cargo_args = parse_cargo_args(&args.cargo_args); - let mut manifest = load_osdk_manifest(&cargo_args, args.osdk_args.select.as_ref()); - apply_cli_args(&mut manifest, &args.osdk_args); - try_fill_system_configs(&mut manifest); Self { - manifest, + manifest: get_final_manifest(&cargo_args, &args.osdk_args), cargo_args, } } } /// Configurations for run subcommand -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct RunConfig { pub manifest: OsdkManifest, pub cargo_args: CargoArgs, + pub gdb_server: bool, + pub vsc_launch_file: bool, } impl RunConfig { pub fn parse(args: &RunArgs) -> Self { let cargo_args = parse_cargo_args(&args.cargo_args); - let mut manifest = load_osdk_manifest(&cargo_args, args.osdk_args.select.as_ref()); - apply_cli_args(&mut manifest, &args.osdk_args); - try_fill_system_configs(&mut manifest); Self { - manifest, + manifest: get_final_manifest(&cargo_args, &args.osdk_args), + cargo_args, + gdb_server: args.gdb_server, + vsc_launch_file: args.vsc_launch_file, + } + } +} + +#[derive(Debug)] +pub struct DebugConfig { + pub manifest: OsdkManifest, + pub cargo_args: CargoArgs, +} + +impl DebugConfig { + pub fn parse(args: &DebugArgs) -> Self { + let cargo_args = split_features(&args.cargo_args); + Self { + manifest: get_final_manifest(&cargo_args, &args.osdk_args), cargo_args, } } @@ -80,11 +101,8 @@ pub struct TestConfig { impl TestConfig { pub fn parse(args: &TestArgs) -> Self { let cargo_args = parse_cargo_args(&args.cargo_args); - let mut manifest = load_osdk_manifest(&cargo_args, args.osdk_args.select.as_ref()); - apply_cli_args(&mut manifest, &args.osdk_args); - try_fill_system_configs(&mut manifest); Self { - manifest, + manifest: get_final_manifest(&cargo_args, &args.osdk_args), cargo_args, test_name: args.test_name.clone(), }