Skip to content

native-lib/trace: Intercept allocating calls, but do nothing - #4792

Open
nia-e wants to merge 2 commits into
rust-lang:masterfrom
nia-e:alloc-tracing-libc-part-1
Open

native-lib/trace: Intercept allocating calls, but do nothing#4792
nia-e wants to merge 2 commits into
rust-lang:masterfrom
nia-e:alloc-tracing-libc-part-1

Conversation

@nia-e

@nia-e nia-e commented Dec 27, 2025

Copy link
Copy Markdown
Member

This is part 1 of #4791; it adds the bits needed to intercept allocating/deallocating calls to libc, but silently ignores them for now. In a follow-up PR this will forward them to shims we control.

@rustbot

rustbot commented Dec 27, 2025

Copy link
Copy Markdown
Collaborator

Thank you for contributing to Miri! A reviewer will take a look at your PR, typically within a week or two.
Please remember to not force-push to the PR branch except when you need to rebase due to a conflict or when the reviewer asks you for it.

@rustbot rustbot added the S-waiting-on-review Status: Waiting for a review to complete label Dec 27, 2025
@nia-e
nia-e force-pushed the alloc-tracing-libc-part-1 branch from 428bee0 to 966775e Compare December 28, 2025 13:02
@rustbot

rustbot commented Jan 2, 2026

Copy link
Copy Markdown
Collaborator

☔ The latest upstream changes (possibly 4b59d7e) made this pull request unmergeable. Please resolve the merge conflicts.

@RalfJung RalfJung left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! I can already see I have to brace myself for some more abstraction-breaking shenanigans.^^ :)

Here's a first round of comments. As you said there's also conflicts to be resolved and CI failures to be looked into.

@rustbot author

View changes since this review

Comment thread Cargo.toml
nix = { version = "0.30.1", features = ["mman", "ptrace", "signal"], optional = true }
ipc-channel = { version = "0.20.0", optional = true }
capstone = { version = "0.13", optional = true }
proc-maps = { version = "0.4.0", optional = true }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a huge pile of new dependencies. :/ and some duplicated ones, like libloading.

How long does this build script take? We just managed to finally get rid of the capstone overhead for check builds, I don't want to add back any new slow build scripts.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it? It seems like the only dependency on linux is libc: https://github.com/rbspy/proc-maps/blob/master/Cargo.toml

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lockfile has a load of new stuff in it -- that has to come from somewhere?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lockfile has a load of new stuff in it -- that has to come from somewhere?

I believe the additional lockfile stuff comes from this windows dependency. While it's not compiled or downloaded in non-windows hosts, the lockfile tracks all the different targets I believe.

Since this is a Linux-only feature if I understand correctly, the procfs crate without any default features would work too. It pulls some other dependencies and it's slightly larger but if in the future miri needs other linux only data it could be useful

(nix:nix-shell-env) $ cargo tree 
proc-maps v0.1.0 (/home/fhonduvillac/src/miri-experiments/proc-maps)
└── proc-maps v0.5.0
    └── libc v0.2.189
(nix:nix-shell-env) $ ls -lah target/release/proc-maps
-rwxrwxr-x 2 fhonduvillac fhonduvillac 470K Jul 28 09:42 target/release/proc-maps
(nix:nix-shell-env) $ cargo tree
procfs v0.1.0 (/home/fhonduvillac/src/miri-experiments/procfs)
└── procfs v0.18.0
    ├── bitflags v2.13.1
    ├── procfs-core v0.18.0
    │   ├── bitflags v2.13.1
    │   └── hex v0.4.3
    └── rustix v1.1.4
        ├── bitflags v2.13.1
        └── linux-raw-sys v0.12.1
(nix:nix-shell-env) $ ls -lah target/release/procfs
-rwxrwxr-x 2 fhonduvillac fhonduvillac 519K Jul 28 09:42 target/release/procfs

let alloc = ();

trace::Supervisor::do_ffi(alloc, || {
let ty_is_sized = dest.layout.ty.is_sized(*this.tcx, this.typing_env());

@RalfJung RalfJung Jan 22, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not support unsized return types for FFI calls.^^ So what is this about?

EDIT: Ah, it is factoring out the RawPtr check below -- but this is incorrect, it checks sizedness of the wrong thing. Anyway this logic looks quite different on master so hopefully a rebase will make this all go away.

return interp_ok(ImmTy::uninit(dest.layout));
}
ty::RawPtr(ty, ..) if ty.is_sized(*this.tcx, this.typing_env()) => {
ty::RawPtr(ty, ..) if ty_is_sized => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes what is being checked. Previously it checked whether the pointee is sized, now you are checking of the pointer is sized (which obviously it always is).


if tracing {
this.tracing_apply_accesses(maybe_memevents.unwrap())?;
let mm = maybe_memevents.unwrap();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's mm for? After unwrapping they are not "maybe" any more.


// Save the machine pointer to a location where the libc interceptors can use it,
// since we can't pass in arguments.
super::parent::MACHINE_PTR.store(machine_ptr.cast(), std::sync::atomic::Ordering::Relaxed);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems very similar to native_lib_ecx_interchange which has been added by a concurrent PR -- we don't need both of those, do we?

Comment on lines +757 to +761
// TODO: This should probably only log writes & not reads, since reads
// in these functions will never expose provenance to the rest of the native
// code. However, these functions likely won't even do any reads, so...
wait_for_signal(Some(pid), signal::SIGTRAP, InitialCont::Yes, Some(catch_segfaults))
.unwrap();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are we even doing here? Who's logging stuff? What does that have to do with exposing anything? I am confused.

The commit message says "Handle accesses during libc fn calls also". Is this something we have to do for some reason, or something you thought would be better to do but it's not mandatory?

wait_for_signal(Some(pid), signal::SIGTRAP, InitialCont::Yes, Some(catch_segfaults))
.unwrap();

// Unset the breakpoint stuff and move the ip back an instruction to compensate.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As in, undo the ret_addr editing we did above? Right?

Comment on lines +351 to +356
ptrace::write(
pid,
libc::malloc as *mut _,
(libc::malloc as *mut libc::c_long).read_volatile(),
)
.unwrap();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lol you're just copying the bytes from the parent (where we didn't change them) back to the child? 😭 😂


/// Set up SIGTRAPs on the first few bytes of malloc/free/etc.
#[expect(clippy::as_conversions)]
fn trap_libc(pid: unistd::Pid) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be good to consistently use debugger terminology here: we are setting and unsetting breakpoints.

Comment on lines +749 to +750
// Override the return address to give us another sigtrap
// (but save the original bytes).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is another case of setting (and later unsetting) a breakpoint, right?

@rustbot rustbot added S-waiting-on-author Status: Waiting for the PR author to address review comments and removed S-waiting-on-review Status: Waiting for a review to complete labels Jan 22, 2026

@javierhonduco javierhonduco left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you so much for working on this, I am very interested in this PR! Left some drive-by comments, hope that's okay but feel free to ignore them.

I maintain a bunch of Rust libraries with a significant amount of ffi code, so very excited to have better support for native code in the future 🎉

View changes since this review

Comment thread Cargo.toml
nix = { version = "0.30.1", features = ["mman", "ptrace", "signal"], optional = true }
ipc-channel = { version = "0.20.0", optional = true }
capstone = { version = "0.13", optional = true }
proc-maps = { version = "0.4.0", optional = true }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lockfile has a load of new stuff in it -- that has to come from somewhere?

I believe the additional lockfile stuff comes from this windows dependency. While it's not compiled or downloaded in non-windows hosts, the lockfile tracks all the different targets I believe.

Since this is a Linux-only feature if I understand correctly, the procfs crate without any default features would work too. It pulls some other dependencies and it's slightly larger but if in the future miri needs other linux only data it could be useful

(nix:nix-shell-env) $ cargo tree 
proc-maps v0.1.0 (/home/fhonduvillac/src/miri-experiments/proc-maps)
└── proc-maps v0.5.0
    └── libc v0.2.189
(nix:nix-shell-env) $ ls -lah target/release/proc-maps
-rwxrwxr-x 2 fhonduvillac fhonduvillac 470K Jul 28 09:42 target/release/proc-maps
(nix:nix-shell-env) $ cargo tree
procfs v0.1.0 (/home/fhonduvillac/src/miri-experiments/procfs)
└── procfs v0.18.0
    ├── bitflags v2.13.1
    ├── procfs-core v0.18.0
    │   ├── bitflags v2.13.1
    │   └── hex v0.4.3
    └── rustix v1.1.4
        ├── bitflags v2.13.1
        └── linux-raw-sys v0.12.1
(nix:nix-shell-env) $ ls -lah target/release/procfs
-rwxrwxr-x 2 fhonduvillac fhonduvillac 519K Jul 28 09:42 target/release/procfs

// since we can't pass in arguments.
super::parent::MACHINE_PTR.store(machine_ptr.cast(), std::sync::atomic::Ordering::Relaxed);
// Give the libc interceptors the event channel.
let mut e_rx = super::parent::EVT_RX.lock().unwrap();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Is there a reason why this error is not being handled? Just curious!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-author Status: Waiting for the PR author to address review comments

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants