diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ab26768..b93aa75 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -23,8 +23,10 @@ jobs:
with:
toolchain: stable
components: rustfmt, clippy
+ - run: rustup toolchain install nightly # we need nightly for some tests which try to detect rustflags
- run: cargo clippy --all-targets --all-features -- -D warnings
- run: cargo fmt -- --check
+ - run: cargo test
msrv:
runs-on: ubuntu-latest
diff --git a/Cargo.lock b/Cargo.lock
index fa91551..f44b0b9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -343,6 +343,12 @@ version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+[[package]]
+name = "current_platform"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a74858bcfe44b22016cb49337d7b6f04618c58e5dbfdef61b06b8c434324a0bc"
+
[[package]]
name = "dashmap"
version = "6.2.1"
@@ -450,11 +456,13 @@ dependencies = [
"cargo_metadata",
"clap",
"clap_complete",
+ "current_platform",
"indicatif",
"inferno",
"opener",
"quick-xml 0.40.1",
"rustc-demangle",
+ "serde_json",
"shlex",
"signal-hook",
]
diff --git a/Cargo.toml b/Cargo.toml
index 4e96246..13de0aa 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -29,8 +29,12 @@ opener = "0.8.1"
shlex = "2.0.1"
rustc-demangle = { version = "0.1", features = ["std"] }
+[dev-dependencies]
+current_platform = "0.2"
+
[target.'cfg(unix)'.dependencies]
signal-hook = "0.4.1"
+serde_json = "1"
[target.'cfg(windows)'.dependencies]
blondie = "0.5.2"
diff --git a/README.md b/README.md
index 8efb9cb..6d334d3 100644
--- a/README.md
+++ b/README.md
@@ -39,7 +39,9 @@ your cargo binary directory. On most systems this is something like `~/.cargo/bi
## Linux
-**Note**: If you're using lld (which is the default since Rust 1.90.0) or mold on Linux, you must use the `--no-rosegment` flag. Otherwise perf will not be able to generate accurate stack traces ([explanation](https://crbug.com/919499#c16)).
+**Note**: If you're using lld (which is the default since Rust 1.90.0) or mold on Linux, you may need to use the `--no-rosegment` flag. Otherwise perf will not be able to generate accurate stack traces ([explanation](https://crbug.com/919499#c16)). `cargo-flamegraph` tries to add this flag automatically but your setup may require extra configuration.
+
+
For example, Rust 1.90.0 and later:
@@ -64,6 +66,8 @@ linker = "clang"
rustflags = ["-Clink-arg=-fuse-ld=/usr/local/bin/mold", "-Clink-arg=-Wl,--no-rosegment"]
```
+
+
#### Debian (x86 and aarch)
**Note**: Debian bullseye packages an outdated version of Rust which does not meet flamegraph's requirements. You should use [rustup](https://rustup.rs/) to install an up-to-date version of Rust, or upgrade to Debian bookworm or newer.
diff --git a/src/bin/cargo-flamegraph.rs b/src/bin/cargo-flamegraph.rs
index ccf8c9e..89f3e82 100644
--- a/src/bin/cargo-flamegraph.rs
+++ b/src/bin/cargo-flamegraph.rs
@@ -1,4 +1,7 @@
-use std::path::{Path, PathBuf};
+use std::{
+ path::{Path, PathBuf},
+ process::{Command, Output, Stdio},
+};
use anyhow::{anyhow, Context};
use cargo_metadata::{Artifact, ArtifactDebuginfo, Message, MetadataCommand, Package, TargetKind};
@@ -97,7 +100,6 @@ enum Cli {
}
fn build(opt: &Opt, kind: Vec) -> anyhow::Result> {
- use std::process::{Command, Output, Stdio};
let mut cmd = Command::new("cargo");
// This will build benchmarks with the `bench` profile. This is needed
@@ -187,6 +189,24 @@ fn build(opt: &Opt, kind: Vec) -> anyhow::Result> {
println!("build command: {:?}", cmd);
}
+ #[cfg(any(target_os = "linux", target_os = "android"))]
+ {
+ let rustflags_env_var = std::env::var("RUSTFLAGS").ok();
+ if rosegment_detection::should_add_no_rosegment_flag(
+ "+nightly",
+ rustflags_env_var.as_deref(),
+ )? {
+ cmd.env(
+ "RUSTFLAGS",
+ format!(
+ "{} -C{}",
+ rustflags_env_var.as_ref().map_or("", String::as_str),
+ rosegment_detection::NO_ROSEGMENT_LINK_ARG
+ ),
+ );
+ }
+ }
+
let Output { status, stdout, .. } = cmd
.stderr(Stdio::inherit())
.output()
@@ -205,6 +225,205 @@ fn build(opt: &Opt, kind: Vec) -> anyhow::Result> {
.collect()
}
+#[cfg(any(target_os = "linux", target_os = "android"))]
+mod rosegment_detection {
+ use super::*;
+ use cargo_metadata::semver;
+
+ pub static NO_ROSEGMENT_LINK_ARG: &str = "link-arg=-Wl,--no-rosegment";
+
+ pub fn should_add_no_rosegment_flag(
+ toolchain_specifier: &'static str,
+ rustflags_env_var: Option<&str>,
+ ) -> anyhow::Result {
+ // `cargo metadata` doesn't provide this, so the `cargo_metadata` crate isn't a help here.
+ let cargo_version_stdout = Command::new("cargo")
+ .arg("version")
+ .stdout(Stdio::piped())
+ .spawn()
+ // .spawn and .wait_with_output don't have distinct enough fail conditions for us to
+ // provide special error messages for each one
+ .and_then(|c| c.wait_with_output())
+ .context("`cargo version` failed to run")?
+ .stdout;
+
+ let cargo_version_utf8 = std::str::from_utf8(&cargo_version_stdout)
+ .context("`cargo version`'s output was not valid utf8")?;
+
+ let cargo_version = cargo_version_utf8
+ .split(' ')
+ .nth(1)
+ .ok_or_else(|| anyhow!("`cargo version` provided an answer in a format unlike the expected 'cargo ( )' (got {cargo_version_utf8:?})"))?;
+
+ let cargo_semver = semver::Version::parse(cargo_version)
+ .context("cargo's version was not a valid semver")?;
+
+ let at_least_1_90 = cargo_semver >= semver::Version::new(1, 90, 0);
+ let mut using_gold = false;
+ let mut specified_no_rosegment = false;
+ let mut using_linker_that_needs_flag = false;
+
+ if let Some(flags) = rustflags_env_var {
+ detect_linker_settings(
+ flags.split(' '),
+ &mut using_gold,
+ &mut specified_no_rosegment,
+ &mut using_linker_that_needs_flag,
+ );
+ }
+
+ if let Some(rustflags) = get_rustc_target_from_printing_spec(toolchain_specifier)?
+ .and_then(|target| get_rustflags_from_cargo(&target, toolchain_specifier))
+ {
+ detect_linker_settings(
+ rustflags.iter().map(String::as_str),
+ &mut using_gold,
+ &mut specified_no_rosegment,
+ &mut using_linker_that_needs_flag,
+ );
+ }
+
+ let should_add = ((at_least_1_90 && !using_gold) || using_linker_that_needs_flag)
+ && !specified_no_rosegment;
+ Ok(should_add)
+ }
+
+ pub fn get_rustc_target_from_printing_spec(
+ toolchain_specifier: &'static str,
+ ) -> anyhow::Result