Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable unwinding in userspace #134

Merged
merged 4 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,27 +63,24 @@ jobs:

- uses: dtolnay/rust-toolchain@nightly
with:
components: rust-src,rustfmt
components: clippy,rust-src,rustfmt

- uses: dtolnay/rust-toolchain@master
if: runner.os == 'macOS' && runner.arch == 'X64'
with:
toolchain: ${{ matrix.rust }}
targets: x86_64-unknown-linux-musl
components: clippy

- uses: dtolnay/rust-toolchain@master
if: runner.os == 'macOS' && runner.arch == 'ARM64'
with:
toolchain: ${{ matrix.rust }}
targets: aarch64-unknown-linux-musl
components: clippy

- uses: dtolnay/rust-toolchain@master
if: runner.os == 'Linux'
with:
toolchain: ${{ matrix.rust }}
components: clippy

- uses: Swatinem/rust-cache@v2

Expand Down
8 changes: 1 addition & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ default-members = ["{{project-name}}", "{{project-name}}-common"]

[workspace.dependencies]
aya = { version = "0.13.1", default-features = false }
aya-build = { version = "0.1.2", default-features = false }
aya-ebpf = { version = "0.1.1", default-features = false }
aya-log = { version = "0.2.1", default-features = false }
aya-log-ebpf = { version = "0.1.1", default-features = false }

anyhow = { version = "1", default-features = false }
cargo_metadata = { version = "0.18.0", default-features = false }
# `std` feature is currently required to build `clap`.
#
# See https://github.com/clap-rs/clap/blob/61f5ee5/clap_builder/src/lib.rs#L15.
Expand All @@ -21,12 +21,6 @@ log = { version = "0.4.22", default-features = false }
tokio = { version = "1.40.0", default-features = false }
which = { version = "6.0.0", default-features = false }

[profile.dev]
panic = "abort"

[profile.release]
panic = "abort"

[profile.release.package.{{project-name}}-ebpf]
debug = 2
codegen-units = 1
38 changes: 19 additions & 19 deletions cargo-generate.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,25 @@ ignore = [".github", "test.sh"]
type = "string"
prompt = "Which type of eBPF program?"
choices = [
"cgroup_skb",
"cgroup_sockopt",
"cgroup_sysctl",
"classifier",
"fentry",
"fexit",
"kprobe",
"kretprobe",
"lsm",
"perf_event",
"raw_tracepoint",
"sk_msg",
"sock_ops",
"socket_filter",
"tp_btf",
"tracepoint",
"uprobe",
"uretprobe",
"xdp",
"cgroup_skb",
"cgroup_sockopt",
"cgroup_sysctl",
"classifier",
"fentry",
"fexit",
"kprobe",
"kretprobe",
"lsm",
"perf_event",
"raw_tracepoint",
"sk_msg",
"sock_ops",
"socket_filter",
"tp_btf",
"tracepoint",
"uprobe",
"uretprobe",
"xdp",
]
default = "xdp"

Expand Down
14 changes: 12 additions & 2 deletions test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,18 @@ case $OS in
# ${CRATE_NAME} and ${CRATE_NAME}-ebpf depend on ${CRATE_NAME}-common and ${CRATE_NAME} activates
# ${CRATE_NAME}-common's aya dependency, we end up trying to compile the panic handler twice: once
# from the bpf program, and again from std via aya.
cargo clippy --exclude "${CRATE_NAME}-ebpf" --all-targets --workspace -- --deny warnings
cargo clippy --package "${CRATE_NAME}-ebpf" --all-targets -- --deny warnings
#
# `-C panic=abort` because "unwinding panics are not supported without std";
# integration-ebpf contains `#[no_std]` binaries.
#
# `-Zpanic_abort_tests` because "building tests with panic=abort is not supported without
# `-Zpanic_abort_tests`"; Cargo does this automatically when panic=abort is set via profile
# but we want to preserve unwinding at runtime - here we are just running clippy so we don't
# care about unwinding behavior.
#
# `+nightly` because "the option `Z` is only accepted on the nightly compiler".
cargo +nightly clippy --exclude "${CRATE_NAME}-ebpf" --all-targets --workspace -- --deny warnings
cargo +nightly clippy --package "${CRATE_NAME}-ebpf" --all-targets -- --deny warnings -C panic=abort -Zpanic_abort_tests

expect <<EOF
set timeout 30 ;# Increase timeout if necessary
Expand Down
12 changes: 0 additions & 12 deletions {{project-name}}-ebpf/.cargo/config.toml

This file was deleted.

3 changes: 0 additions & 3 deletions {{project-name}}-ebpf/rust-toolchain.toml

This file was deleted.

3 changes: 2 additions & 1 deletion {{project-name}}/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ clap = { workspace = true, features = ["derive"] }
{% endif -%}

[build-dependencies]
cargo_metadata = { workspace = true }
anyhow = { workspace = true }
aya-build = { workspace = true }
# TODO(https://github.com/rust-lang/cargo/issues/12375): this should be an artifact dependency, but
# it's not possible to tell cargo to use `-Z build-std` to build it. We cargo-in-cargo in the build
# script to build this, but we want to teach cargo about the dependecy so that cache invalidation
Expand Down
158 changes: 11 additions & 147 deletions {{project-name}}/build.rs
Original file line number Diff line number Diff line change
@@ -1,150 +1,14 @@
use std::{
env, fs,
io::{BufRead as _, BufReader},
path::PathBuf,
process::{Child, Command, Stdio},
};

use cargo_metadata::{
Artifact, CompilerMessage, Message, Metadata, MetadataCommand, Package, Target,
};

/// This crate has a runtime dependency on artifacts produced by the `{{project-name}}-ebpf` crate.
/// This would be better expressed as one or more [artifact-dependencies][bindeps] but issues such
/// as:
///
/// * https://github.com/rust-lang/cargo/issues/12374
/// * https://github.com/rust-lang/cargo/issues/12375
/// * https://github.com/rust-lang/cargo/issues/12385
///
/// prevent their use for the time being.
///
/// [bindeps]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html?highlight=feature#artifact-dependencies
fn main() {
let Metadata { packages, .. } = MetadataCommand::new().no_deps().exec().unwrap();
use anyhow::{anyhow, Context as _};
use aya_build::cargo_metadata;

fn main() -> anyhow::Result<()> {
let cargo_metadata::Metadata { packages, .. } = cargo_metadata::MetadataCommand::new()
.no_deps()
.exec()
.context("MetadataCommand::exec")?;
let ebpf_package = packages
.into_iter()
.find(|Package { name, .. }| name == "{{project-name}}-ebpf")
.unwrap();

let out_dir = env::var_os("OUT_DIR").unwrap();
let out_dir = PathBuf::from(out_dir);

let endian = env::var_os("CARGO_CFG_TARGET_ENDIAN").unwrap();
let target = if endian == "big" {
"bpfeb"
} else if endian == "little" {
"bpfel"
} else {
panic!("unsupported endian={:?}", endian)
};

// TODO(https://github.com/rust-lang/cargo/issues/4001): Make this `false` if we can determine
// we're in a check build.
let build_ebpf = true;
if build_ebpf {
let arch = env::var_os("CARGO_CFG_TARGET_ARCH").unwrap();

let target = format!("{target}-unknown-none");

let Package { manifest_path, .. } = ebpf_package;
let ebpf_dir = manifest_path.parent().unwrap();

// We have a build-dependency on `{{project-name}}-ebpf`, so cargo will automatically rebuild us
// if `{{project-name}}-ebpf`'s *library* target or any of its dependencies change. Since we
// depend on `{{project-name}}-ebpf`'s *binary* targets, that only gets us half of the way. This
// stanza ensures cargo will rebuild us on changes to the binaries too, which gets us the
// rest of the way.
println!("cargo:rerun-if-changed={}", ebpf_dir.as_str());

let mut cmd = Command::new("cargo");
cmd.args([
"build",
"-Z",
"build-std=core",
"--bins",
"--message-format=json",
"--release",
"--target",
&target,
]);

cmd.env("CARGO_CFG_BPF_TARGET_ARCH", arch);

// Workaround to make sure that the rust-toolchain.toml is respected.
for key in ["RUSTUP_TOOLCHAIN", "RUSTC", "RUSTC_WORKSPACE_WRAPPER"] {
cmd.env_remove(key);
}
cmd.current_dir(ebpf_dir);

// Workaround for https://github.com/rust-lang/cargo/issues/6412 where cargo flocks itself.
let ebpf_target_dir = out_dir.join("{{project-name}}-ebpf");
cmd.arg("--target-dir").arg(&ebpf_target_dir);

let mut child = cmd
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap_or_else(|err| panic!("failed to spawn {cmd:?}: {err}"));
let Child { stdout, stderr, .. } = &mut child;

// Trampoline stdout to cargo warnings.
let stderr = stderr.take().unwrap();
let stderr = BufReader::new(stderr);
let stderr = std::thread::spawn(move || {
for line in stderr.lines() {
let line = line.unwrap();
println!("cargo:warning={line}");
}
});

let stdout = stdout.take().unwrap();
let stdout = BufReader::new(stdout);
let mut executables = Vec::new();
for message in Message::parse_stream(stdout) {
#[allow(clippy::collapsible_match)]
match message.expect("valid JSON") {
Message::CompilerArtifact(Artifact {
executable,
target: Target { name, .. },
..
}) => {
if let Some(executable) = executable {
executables.push((name, executable.into_std_path_buf()));
}
}
Message::CompilerMessage(CompilerMessage { message, .. }) => {
for line in message.rendered.unwrap_or_default().split('\n') {
println!("cargo:warning={line}");
}
}
Message::TextLine(line) => {
println!("cargo:warning={line}");
}
_ => {}
}
}

let status = child
.wait()
.unwrap_or_else(|err| panic!("failed to wait for {cmd:?}: {err}"));
assert_eq!(status.code(), Some(0), "{cmd:?} failed: {status:?}");

stderr.join().map_err(std::panic::resume_unwind).unwrap();

for (name, binary) in executables {
let dst = out_dir.join(name);
let _: u64 = fs::copy(&binary, &dst)
.unwrap_or_else(|err| panic!("failed to copy {binary:?} to {dst:?}: {err}"));
}
} else {
let Package { targets, .. } = ebpf_package;
for Target { name, kind, .. } in targets {
if *kind != ["bin"] {
continue;
}
let dst = out_dir.join(name);
fs::write(&dst, []).unwrap_or_else(|err| panic!("failed to create {dst:?}: {err}"));
}
}
.find(|cargo_metadata::Package { name, .. }| name == "{{project-name}}-ebpf")
.ok_or_else(|| anyhow!("{{project-name}}-ebpf package not found"))?;
aya_build::build_ebpf([ebpf_package])
}