Skip to content

Commit

Permalink
Merge pull request #15 from mkroening/decouple-rust
Browse files Browse the repository at this point in the history
feat: decouple Rust app's toolchain from rftrace's toolchain
  • Loading branch information
mkroening authored Mar 25, 2024
2 parents 627ef47 + c11c909 commit d6b3faa
Show file tree
Hide file tree
Showing 10 changed files with 155 additions and 54 deletions.
10 changes: 9 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ jobs:
sudo apt-get update
sudo apt-get install uftrace
- uses: mkroening/rust-toolchain-toml@main
- uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2023-11-01
components: llvm-tools
- uses: Swatinem/rust-cache@v2
- name: Build
run: make
Expand All @@ -53,9 +57,13 @@ jobs:
sudo apt-get update
sudo apt-get install uftrace
- uses: mkroening/rust-toolchain-toml@main
- uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2023-11-01
components: llvm-tools
- uses: Swatinem/rust-cache@v2
- name: Build
run: cargo rustc -- -Zinstrument-mcount -C passes="ee-instrument post-inline-ee-instrument"
run: cargo +nightly-2023-11-01 rustc -- -Zinstrument-mcount -C passes="ee-instrument<post-inline>"
- name: Run
run: |
mkdir tracedir
Expand Down
9 changes: 2 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,13 +124,8 @@ rftrace = "0.1"
```

#### Any other kernel
Unfortunately, there is no way to communicate a fixed, different compilation-target to the backend. There is an open cargo issue for allowing arbitrary environment variables to be set: [Passing environment variables from down-stream to up-stream library](https://github.com/rust-lang/cargo/issues/4121)

For RustyHermit there is a workaround with the `autokernel` feature, which can easily be extended to other targets. Outside of this, you can also set a custom target by setting the environment variable `RFTRACE_TARGET_TRIPLE` to your wanted triple.

Other backend features which might be of interest are:
- `buildcore` - needed for no-std targets. Will build the core library when building the backend.
- `interruptsafe` - enabled by default. Will safe and restore more registers on function exits, to ensure interrupts do not clobber them. Probably only needed when interrupts are instrumented. Can be disabled for performance reasons.
Backend features which might be of interest are:
- `interruptsafe` - will safe and restore more registers on function exits, to ensure interrupts do not clobber them. Probably only needed when interrupts are instrumented. Can be disabled for performance reasons.


### Output Format
Expand Down
4 changes: 2 additions & 2 deletions examples/c/makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ default: out/debug/librftrace.a out/debug/librftrace_frontend_ffi.a
gcc main.c -p -pthread -ldl -lrftrace -lrftrace_frontend_ffi -Lout/debug/ -o test

out/debug/librftrace.a:
cargo build --manifest-path ../../rftrace/Cargo.toml --target-dir out
cargo +nightly-2023-11-01 build --manifest-path ../../rftrace/Cargo.toml --target-dir out

out/debug/librftrace_frontend_ffi.a:
cargo build --manifest-path ../../rftrace-frontend-ffi/Cargo.toml --target-dir out
cargo +nightly-2023-11-01 build --manifest-path ../../rftrace-frontend-ffi/Cargo.toml --target-dir out

clean:
rm -r out
Expand Down
2 changes: 1 addition & 1 deletion examples/hermitc/makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ $(HERMIT_PATH)/libhermit.a:
RUSTFLAGS=$(RUST_FLAGS) cargo build --manifest-path $(LIBHERMIT_SRC)/Cargo.toml -Z build-std=core,alloc --target $(TARGET)-kernel --features newlib --release --target-dir out

$(LIBRARY_PATH)/librftrace.a:
cargo build --manifest-path ../../rftrace/Cargo.toml --target $(TARGET) --features autokernel,buildcore --target-dir out -Z build-std=std,panic_abort
cargo build --manifest-path ../../rftrace/Cargo.toml --target $(TARGET) --target-dir out -Z build-std=std,panic_abort

$(LIBRARY_PATH)/librftrace_frontend_ffi.a:
RUSTFLAGS=$(RUST_FLAGS) cargo build --manifest-path ../../rftrace-frontend-ffi/Cargo.toml --target $(TARGET) --target-dir out -Z build-std=std,panic_abort
Expand Down
2 changes: 1 addition & 1 deletion examples/hermitrust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rftrace = { version = "0.2.0", path="../../rftrace", features=["buildcore","autokernel"] }
rftrace = { version = "0.2.0", path="../../rftrace" }
rftrace-frontend = { version = "0.1.0", path="../../rftrace-frontend" }


Expand Down
8 changes: 4 additions & 4 deletions examples/rust/out.snap
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# TID FUNCTION
[ 1] | core::fmt::Arguments::new_v1();
[ 1] | core::fmt::Arguments::new_const();
[ 1] | rftrace_rs_test::test1() {
[ 1] | core::fmt::Arguments::new_v1();
[ 1] | core::fmt::Arguments::new_const();
[ 1] | rftrace_rs_test::test2() {
[ 1] | core::fmt::Arguments::new_v1();
[ 1] | core::fmt::Arguments::new_const();
[ 1] | rftrace_rs_test::test3() {
[ 1] | core::fmt::Arguments::new_v1();
[ 1] | core::fmt::Arguments::new_const();
[ 1] | } /* rftrace_rs_test::test3 */
[ 1] | } /* rftrace_rs_test::test2 */
[ 1] | } /* rftrace_rs_test::test1 */
5 changes: 3 additions & 2 deletions rftrace/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,16 @@ include = [


[features]
buildcore = [] # Build core, needed when compiling against a kernel-target, such as x86_64-unknown-none-hermitkernel.
autokernel = [] # convenience flag, which specifies compilation target as `x86_64-unknown-none-hermitkernel` when orignal target is `x86_64-unknown-hermit`
interruptsafe = [] # backup and restore all scratch registers in the mcount_return trampoline. Needed if we instrument interrupt routines

default = []

[lib]
crate-type = ['staticlib', 'rlib']

[build-dependencies]
llvm-tools = "0.1"

#[profile.dev]
#panic = "abort"
# # we have to build with at least opt-level 1. Might aswell do always 3, since mcount() is in the hotpath!
Expand Down
157 changes: 129 additions & 28 deletions rftrace/build.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::collections::HashSet;
use std::env;
use std::fs::{self, File};
use std::io::prelude::*;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};

fn prepare_staticlib_toml(out_dir: &str) -> std::io::Result<String> {
Expand Down Expand Up @@ -31,29 +33,12 @@ fn build_backend() {
let full_target_dir = format!("{}/target_static", out_dir);
let profile = env::var("PROFILE").expect("PROFILE was not set");

// Set the target. Can be overwritten via env-var.
// If feature autokernel is enabled, automatically 'convert' hermit to hermit-kernel target.
let target = {
println!("cargo:rerun-if-env-changed=RFTRACE_TARGET_TRIPLE");
env::var("RFTRACE_TARGET_TRIPLE").unwrap_or_else(|_| {
let default = env::var("TARGET").unwrap();
#[cfg(not(feature = "autokernel"))]
return default;
#[cfg(feature = "autokernel")]
if default == "x86_64-unknown-hermit" {
"x86_64-unknown-none-hermitkernel".to_owned()
} else {
default
}
})
};
println!("Compiling for target {}", target);
let target = "x86_64-unknown-none";

let mut cmd = Command::new("cargo");
let mut cmd = cargo();
cmd.arg("build");

// Compile for the same target as the parent-lib
cmd.args(&["--target", &target]);
cmd.args(&["--target", target]);

// Output all build artifacts in output dir of parent-lib
cmd.args(&["--target-dir", &full_target_dir]);
Expand Down Expand Up @@ -81,10 +66,10 @@ fn build_backend() {
cmd.stdout(Stdio::inherit());
cmd.stderr(Stdio::inherit());

// Build core, needed when compiling against a kernel-target, such as x86_64-unknown-none-hermitkernel.
// parent's cargo does NOT expose -Z flags as envvar, we therefore use a feature flag for this
#[cfg(feature = "buildcore")]
cmd.args(&["-Z", "build-std=core"]); // should be build std,alloc?
cmd.args(&[
"-Zbuild-std=core",
"-Zbuild-std-features=compiler-builtins-mem",
]);

// Compile staticlib as release if included in release build.
if profile == "release" {
Expand All @@ -111,11 +96,22 @@ fn build_backend() {
assert!(status.success(), "Unable to build tracer's static lib!");
println!("Sub-cargo successful!");

// Link parent-lib against this staticlib
println!(
"cargo:rustc-link-search=native={}/{}/{}/",
&full_target_dir, &target, &profile
let dist_dir = format!("{}/{}/{}", &full_target_dir, &target, &profile);

retain_symbols(
Path::new(&format!("{}/librftrace_backend.a", &dist_dir)),
HashSet::from([
"mcount",
"rftrace_backend_disable",
"rftrace_backend_enable",
"rftrace_backend_get_events",
"rftrace_backend_get_events_index",
"rftrace_backend_init",
]),
);

// Link parent-lib against this staticlib
println!("cargo:rustc-link-search=native={}", &dist_dir);
println!("cargo:rustc-link-lib=static=rftrace_backend");

println!("cargo:rerun-if-changed=staticlib/Cargo.toml");
Expand All @@ -127,3 +123,108 @@ fn build_backend() {
fn main() {
build_backend();
}

/// Returns the Rustup proxy for Cargo.
// Adapted from Hermit.
fn cargo() -> Command {
let cargo = {
let exe = format!("cargo{}", env::consts::EXE_SUFFIX);
// On windows, the userspace toolchain ends up in front of the rustup proxy in $PATH.
// To reach the rustup proxy nonetheless, we explicitly query $CARGO_HOME.
let mut cargo_home = PathBuf::from(env::var_os("CARGO_HOME").unwrap());
cargo_home.push("bin");
cargo_home.push(&exe);
if cargo_home.exists() {
cargo_home
} else {
PathBuf::from(exe)
}
};

let mut cargo = Command::new(cargo);

// Remove rust-toolchain-specific environment variables from kernel cargo
cargo.env_remove("LD_LIBRARY_PATH");
env::vars()
.filter(|(key, _value)| key.starts_with("CARGO") || key.starts_with("RUST"))
.for_each(|(key, _value)| {
cargo.env_remove(&key);
});

cargo
}

/// Makes all internal symbols private to avoid duplicated symbols.
///
/// This allows us to have rftrace's copy of `core` alongside other potential copies in the final binary.
/// This is important when combining different versions of `core`.
/// Newer versions of `rustc` will throw an error on duplicated symbols.
// Adapted from Hermit.
pub fn retain_symbols(archive: &Path, mut exported_symbols: HashSet<&str>) {
use std::fmt::Write;

let prefix = "rftrace";

let all_symbols = {
let objcopy = binutil("nm").unwrap();
let output = Command::new(&objcopy)
.arg("--export-symbols")
.arg(archive)
.output()
.unwrap();
assert!(output.status.success());
String::from_utf8(output.stdout).unwrap()
};

let symbol_renames = all_symbols
.lines()
.fold(String::new(), |mut output, symbol| {
if exported_symbols.remove(symbol) {
return output;
}

if let Some(symbol) = symbol.strip_prefix("_ZN") {
let prefix_len = prefix.len();
let _ = writeln!(output, "_ZN{symbol} _ZN{prefix_len}{prefix}{symbol}",);
} else {
let _ = writeln!(output, "{symbol} {prefix}_{symbol}");
}
output
});
assert!(exported_symbols.is_empty());

let rename_path = archive.with_extension("redefine_syms");
fs::write(&rename_path, symbol_renames).unwrap();

let objcopy = binutil("objcopy").unwrap();
let status = Command::new(&objcopy)
.arg("--redefine-syms")
.arg(&rename_path)
.arg(archive)
.status()
.unwrap();
assert!(status.success());

fs::remove_file(&rename_path).unwrap();
}

/// Returns the path to the requested binutil from the llvm-tools component.
// Adapted from Hermit.
fn binutil(name: &str) -> Result<PathBuf, String> {
let exe_suffix = env::consts::EXE_SUFFIX;
let exe = format!("llvm-{name}{exe_suffix}");

let path = llvm_tools::LlvmTools::new()
.map_err(|err| match err {
llvm_tools::Error::NotFound =>
"Could not find llvm-tools component\n\
\n\
Maybe the rustup component `llvm-tools` is missing? Install it through: `rustup component add llvm-tools`".to_string()
,
err => format!("{err:?}"),
})?
.tool(&exe)
.ok_or_else(|| format!("could not find {exe}"))?;

Ok(path)
}
5 changes: 1 addition & 4 deletions rftrace/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,8 @@ static mut TID: Option<core::num::NonZeroU64> = None;
// Everytime we see a new thread (with emtpy thread-locals), we alloc out own TID
static mut TID_NEXT: AtomicU64 = AtomicU64::new(1);

// Need to define own panic handler, since we are no_std
use core::panic::PanicInfo;
#[linkage = "weak"]
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}

Expand Down
7 changes: 3 additions & 4 deletions rftrace/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@
//! Provides an `mcount` implementation, which does nothing by default but can be enabled via frontend.
//! A lot of documentation can be found in the parent workspaces [readme](https://github.com/tlambertz/rftrace).

#![feature(naked_functions)]
#![feature(llvm_asm)]
#![feature(thread_local)]
#![feature(linkage)]
#![cfg_attr(feature = "staticlib", feature(naked_functions))]
#![cfg_attr(feature = "staticlib", feature(llvm_asm))]
#![cfg_attr(feature = "staticlib", feature(thread_local))]
#![cfg_attr(feature = "staticlib", no_std)]

mod interface;
Expand Down

0 comments on commit d6b3faa

Please sign in to comment.