diff --git a/Cargo.lock b/Cargo.lock index f3aa0d781..164dd1964 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1148,7 +1148,8 @@ dependencies = [ [[package]] name = "minicov" version = "0.3.7" -source = "git+https://github.com/asterinas/minicov?rev=bd5454a#bd5454a58f5e64ef67519df9fb2025c96b47f8be" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b" dependencies = [ "cc", "walkdir", diff --git a/docs/src/osdk/reference/commands/run.md b/docs/src/osdk/reference/commands/run.md index 263c4ca17..8aa8eade2 100644 --- a/docs/src/osdk/reference/commands/run.md +++ b/docs/src/osdk/reference/commands/run.md @@ -27,26 +27,6 @@ comma separated configuration list: Besides, to collect coverage data, we can use option `--coverage`. This option enables the coverage feature and collect coverage data to `coverage.profraw` when exit. -It actually does several things: - - - It adds `-Cinstrument-coverage -Zno-profiler-runtime` to `RUSTFLAGS` so LLVM will - generate coverage instrument. And `coverage` features will be enabled for `ostd`, - then before `exit_qemu` actually quit QEMU, it will call `minicov` to collect - the coverage data to guest's own memory, and print its address and size, so that - OSDK can dump it out of guest. - - Next, `--no-shutdown` will be enabled for QEMU, and OSDK will setup a monitor - connection to QEMU to monitor its status. Once exit, it dumps the coverage data - from guest's memory to `coverage.profraw`. - -**Note.** The code coverage feature of OSDK requires a non-default OSTD feature called -`coverage`, which relies on dependencies that are not published on crates.io. -To utilize this feature, projects must specify OSTD as a dependency using a Git -repository or a local filesystem path in their `Cargo.toml`. For example: - -```toml -[dependencies] -ostd = { git = "https://github.com/asterinas/asterinas", rev = "v0.11.0", features = ["coverage"] } -``` See [Debug Command](debug.md) to interact with the GDB server in terminal. diff --git a/osdk/src/commands/build/mod.rs b/osdk/src/commands/build/mod.rs index cb2c7786c..65ebcd2ed 100644 --- a/osdk/src/commands/build/mod.rs +++ b/osdk/src/commands/build/mod.rs @@ -245,8 +245,16 @@ fn build_kernel_elf( } const CFLAGS: &str = "CFLAGS_x86_64-unknown-none"; - let env_cflags = std::env::var(CFLAGS).unwrap_or_default(); - command.env(CFLAGS, env_cflags + " -fPIC"); + let mut env_cflags = std::env::var(CFLAGS).unwrap_or_default(); + env_cflags += " -fPIC"; + + if features.contains(&"coverage".to_string()) { + // This is a workaround for minicov , + // makes coverage work on x86_64-unknown-none. + env_cflags += " -D__linux__"; + } + + command.env(CFLAGS, env_cflags); info!("Building kernel ELF using command: {:#?}", command); info!("Building directory: {:?}", std::env::current_dir().unwrap()); diff --git a/osdk/tests/commands/run.rs b/osdk/tests/commands/run.rs index 716286abf..93f0a5fbf 100644 --- a/osdk/tests/commands/run.rs +++ b/osdk/tests/commands/run.rs @@ -61,6 +61,33 @@ mod workspace { } } +mod coverage_feature { + use super::*; + use crate::util::{cargo_osdk, depends_on_coverage}; + use assert_cmd::Command; + use std::path::Path; + + #[test] + fn basic_coverage() { + // Test skipped because TDX is enabled. + if is_tdx_enabled() { + return; + } + let workspace = workspace::WorkSpace::new(WORKSPACE, "basic_coverage"); + let manifest_path = Path::new(&workspace.os_dir()).join("Cargo.toml"); + let osdk_path = Path::new(&workspace.os_dir()).join("OSDK.toml"); + depends_on_coverage(&manifest_path, &osdk_path); + let mut instance = cargo_osdk(["run", "--coverage"]); + instance.current_dir(&workspace.os_dir()); + + let _output = instance + .output() + .expect("Failed to wait for QEMU coverage instance"); + let coverage_file = Path::new(&workspace.os_dir()).join("coverage.profraw"); + assert!(coverage_file.exists(), "Coverage file not found"); + } +} + mod qemu_gdb_feature { use super::*; use crate::util::cargo_osdk; diff --git a/osdk/tests/util/mod.rs b/osdk/tests/util/mod.rs index a80aa9526..96a7f31ca 100644 --- a/osdk/tests/util/mod.rs +++ b/osdk/tests/util/mod.rs @@ -134,6 +134,44 @@ pub(crate) fn depends_on_local_ostd(manifest_path: impl AsRef) { fs::write(manifest_path, manifest.to_string().as_bytes()).unwrap(); } +/// Makes crates created by `cargo ostd new` enable coverage features, +/// adding features = ["coverage"] to ostd dependency and creating a [features] section. +/// +/// This transforms the manifest to enable coverage support. +pub(crate) fn depends_on_coverage(manifest_path: impl AsRef, osdk_path: impl AsRef) { + let manifest_content = fs::read_to_string(&manifest_path).unwrap(); + let mut manifest: Table = toml::from_str(&manifest_content).unwrap(); + + // Add features = ["coverage"] to ostd dependency + let dep = manifest + .get_mut("dependencies") + .map(Value::as_table_mut) + .flatten() + .unwrap(); + + if let Some(ostd_dep) = dep.get_mut("ostd").map(Value::as_table_mut).flatten() { + let features = vec![Value::String("coverage".to_string())]; + ostd_dep.insert("features".to_string(), Value::Array(features)); + } + + // Add [features] section with coverage = [] + let mut features_table = Table::new(); + features_table.insert("coverage".to_string(), Value::Array(vec![])); + manifest.insert("features".to_string(), Value::Table(features_table)); + + fs::write(manifest_path, manifest.to_string().as_bytes()).unwrap(); + + // Modify OSDK.toml to add logfile=qemu.log to chardev line + if osdk_path.as_ref().exists() { + let osdk_content = fs::read_to_string(&osdk_path).unwrap(); + let modified_content = osdk_content.replace( + "-chardev stdio,id=mux,mux=on,signal=off \\", + "-chardev stdio,id=mux,mux=on,signal=off,logfile=qemu.log \\", + ); + fs::write(osdk_path, modified_content).unwrap(); + } +} + pub(crate) fn add_tdx_scheme(osdk_path: impl AsRef) -> std::io::Result<()> { let template_path = Path::new(file!()) .parent() diff --git a/ostd/Cargo.toml b/ostd/Cargo.toml index 079d91806..c2c81468d 100644 --- a/ostd/Cargo.toml +++ b/ostd/Cargo.toml @@ -38,7 +38,7 @@ unwinding = { version = "=0.2.5", default-features = false, features = ["fde-gnu volatile = "0.6.1" bitvec = { version = "1.0", default-features = false, features = ["alloc"] } -minicov = { git = "https://github.com/asterinas/minicov", rev = "bd5454a", version = "0.3", optional = true } +minicov = { version = "0.3", optional = true } [target.x86_64-unknown-none.dependencies] x86_64 = "0.14.13" diff --git a/ostd/src/arch/x86/qemu.rs b/ostd/src/arch/x86/qemu.rs index c27cc5535..f4345c8bd 100644 --- a/ostd/src/arch/x86/qemu.rs +++ b/ostd/src/arch/x86/qemu.rs @@ -25,7 +25,7 @@ pub enum QemuExitCode { /// `-device isa-debug-exit,iobase=0xf4,iosize=0x04`. pub fn exit_qemu(exit_code: QemuExitCode) -> ! { #[cfg(feature = "coverage")] - crate::coverage::dump_profraw(); + crate::coverage::on_qemu_exit(); use x86_64::instructions::port::Port; let mut port = Port::new(0xf4); diff --git a/ostd/src/coverage.rs b/ostd/src/coverage.rs index 09cd326e3..8bf398e46 100644 --- a/ostd/src/coverage.rs +++ b/ostd/src/coverage.rs @@ -1,13 +1,19 @@ // SPDX-License-Identifier: MPL-2.0 -use alloc::vec::Vec; +//! Support for the code coverage feature of OSDK. +//! +//! For more information about the code coverage feature (`cargo osdk run --coverage`), +//! check out the OSDK reference manual. -pub fn dump_profraw() { - let mut coverage = Vec::new(); +use alloc::vec::Vec; +use core::mem::ManuallyDrop; + +/// A hook to be invoked on QEMU exit for dumping the code coverage data. +pub(crate) fn on_qemu_exit() { + let mut coverage = ManuallyDrop::new(Vec::new()); unsafe { - minicov::capture_coverage(&mut coverage).unwrap(); + minicov::capture_coverage(&mut *coverage).unwrap(); } - let coverage = coverage.leak(); crate::early_println!("#### Coverage: {:p} {}", coverage.as_ptr(), coverage.len()); }