Support GDB server for cargo osdk run

Feature: generate launch config for debugging via VS Code.
This commit is contained in:
fgh1999 2024-03-20 17:53:52 +00:00 committed by Tate, Hongliang Tian
parent a18fc9eb25
commit 5fd3b1bc2f
9 changed files with 305 additions and 21 deletions

3
.gitignore vendored
View File

@ -17,3 +17,6 @@ qemu.log
# packet dump file
virtio-net.pcap
# vscode launch config file
.vscode/launch.json

View File

@ -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)]

View File

@ -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();
}

View File

@ -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"]
}
]
}

View File

@ -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.

View File

@ -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<Path>) -> SystemTime {
@ -73,3 +110,147 @@ fn get_last_modified_time(path: impl AsRef<Path>) -> 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<String>,
}
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<Path>,
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<Value> = {
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)
}
}

View File

@ -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);

View File

@ -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"
}

View File

@ -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(),
}