Use official minicov and add unit test for coverage feature

This commit is contained in:
Marsman1996 2025-07-22 10:14:33 +08:00 committed by Tate, Hongliang Tian
parent 79335b272f
commit b04d62ae71
8 changed files with 90 additions and 30 deletions

3
Cargo.lock generated
View File

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

View File

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

View File

@ -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 <https://github.com/Amanieu/minicov/issues/29>,
// 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());

View File

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

View File

@ -134,6 +134,44 @@ pub(crate) fn depends_on_local_ostd(manifest_path: impl AsRef<Path>) {
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<Path>, osdk_path: impl AsRef<Path>) {
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<Path>) -> std::io::Result<()> {
let template_path = Path::new(file!())
.parent()

View File

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

View File

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

View File

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