Skip to content

Tracking Issue for breakpoint feature (core::arch::breakpoint) #133724

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

Open
3 of 4 tasks
joshtriplett opened this issue Dec 2, 2024 · 50 comments
Open
3 of 4 tasks

Tracking Issue for breakpoint feature (core::arch::breakpoint) #133724

joshtriplett opened this issue Dec 2, 2024 · 50 comments
Labels
C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this PR / Issue. I-lang-radar Items that are on lang's radar and will need eventual work or consideration. T-lang Relevant to the language team T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.

Comments

@joshtriplett
Copy link
Member

joshtriplett commented Dec 2, 2024

Feature gate: #![feature(breakpoint)]

This is a tracking issue for the breakpoint feature, which gates the core::arch::breakpoint function. This feature was approved in ACP 491.

Public API

// core::arch

/// Compiles to a target-specific software breakpoint instruction or equivalent.
///
/// (Long description elided.)
#[inline(always)]
pub fn breakpoint() {
    unsafe {
        core::intrinsics::breakpoint();
    }
}

Steps / History

Unresolved Questions

  • None yet.

Footnotes

  1. https://std-dev-guide.rust-lang.org/feature-lifecycle/stabilization.html

@joshtriplett joshtriplett added C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. labels Dec 2, 2024
@clarfonthey
Copy link
Contributor

Is there a reason why this is in core::arch instead of core::hint? I feel like it would be logical to say that a perfectly acceptable implementation of this would be to do nothing, and I'd imagine this is what a lot of embedded targets will do.

Plus, core::arch is generally reserved for arch-specific stuff, and this is specifically independent of architecture.

@Scripter17
Copy link
Contributor

Does it really fit in std::hint? Everything else in there is, well, hints to the compiler. Either for lints or for optimization

It fits better in there but it doesn't feel like the obvious place to look for a breakpoint function

@joshtriplett
Copy link
Member Author

@clarfonthey I don't think "do nothing" should be a valid implementation of this. "abort" is a valid implementation. Given that, I don't think this belongs in hint.

That said, that doesn't necessarily make core::arch the best place. It seemed like a good fit at the time, in part because its behavior is arch-specific.

I don't see any existing module that this logically fits in other than arch.

We could put it in a new core::debug or similar, but I'm not sure it's worth a new module for this, and debug could be confused as being related to Debug.

@clarfonthey
Copy link
Contributor

I mean, sure, but we have other things in hint which emit special instructions, like spin_loop. Just because the definition of a "trivial" implementation changes, or that the result is architecture-specific, doesn't mean it doesn't fit into that module.

@joshtriplett
Copy link
Member Author

@clarfonthey AFAICT, every function in core::hint can be replaced by a no-op/identity for a correct (if suboptimal) implementation. I think that's a property worth not losing.

A breakpoint isn't a "hint"; it can't be replaced with a no-op.

Is there any other module in core that you think this could fit into? Or, do you think it's worth creating a new module for this and potential related items?

@kpreid
Copy link
Contributor

kpreid commented Dec 16, 2024

JavaScript has the debugger statement which causes the debugger to pause the program if a debugger is available (in current browsers, this means “if the developer tools are open”), and otherwise continues execution. This is a useful behavior (it allows executing an identical program with and without pauses for debugging, without needing to remove the breakpoint and recompile) and if Rust offered it, it would fit in core::hint.

@joshtriplett
Copy link
Member Author

@kpreid I'd love to have that operation, but unfortunately, that's a much more complex operation that isn't as simple as emitting an instruction, it'd be more error-prone (it can erroneously detect a debugger), it'd be less portable (as it's OS-specific rather than CPU-specific), and it wouldn't be available on all targets.

@BrainBacon
Copy link

@joshtriplett Maybe an ACP for debugger presence detection? C++ 26 is getting that.

I recently added debugger presence detection to Unbug using the dbg_breakpoint crate. The bulk of that crate was previously accepted as a panic hook in the Rust standard library, but then reverted later.

@joshtriplett
Copy link
Member Author

joshtriplett commented May 20, 2025

Stabilization Report

Implementation History

Approved in ACP rust-lang/libs-team#491 , implemented in #133726 , and not modified since. Includes tests and documentation.

Some discussion about what the correct module for this function should be, but no conclusion about a specific better place than core::arch.

API Summary

In core::arch (and thus also std::arch):

pub fn breakpoint();

Experience Report

The ACP was originally inspired by the unbug crate, which was promptly able to switch to this (and will be able to drop some architecture-specific code when this becomes stable, and run on stable Rust on more architectures). https://github.com/microsoft/edit is also using this.

@joshtriplett joshtriplett added the I-libs-api-nominated Nominated for discussion during a libs-api team meeting. label May 20, 2025
@Amanieu
Copy link
Member

Amanieu commented May 20, 2025

@rfcbot merge

@rfcbot
Copy link
Collaborator

rfcbot commented May 20, 2025

Team member @Amanieu has proposed to merge this. The next step is review by the rest of the tagged team members:

No concerns currently listed.

Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

See this document for info about what commands tagged team members can give me.

@rfcbot rfcbot added proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. labels May 20, 2025
@Amanieu Amanieu removed the I-libs-api-nominated Nominated for discussion during a libs-api team meeting. label May 20, 2025
@traviscross
Copy link
Contributor

traviscross commented May 20, 2025

@rustbot labels +I-lang-nominated +P-lang-drag-1

Generally we lang FCP the first stable use of an intrinsic. This is an intrinsic, and this is the first stable use. At the same time, as we discussed in the libs-api meeting today, perhaps what we're meaning to lang FCP are new capabilities of the language, and this one could be seen as equivalent to some inline assembly. So I don't know. It's worth us having a look in any case, so let's nominate.

cc @RalfJung @workingjubilee @rust-lang/lang

@rustbot rustbot added I-lang-nominated Nominated for discussion during a lang team meeting. P-lang-drag-1 Lang team prioritization drag level 1. https://rust-lang.zulipchat.com/#narrow/channel/410516-t-lang labels May 20, 2025
@RalfJung
Copy link
Member

This doesn't really have semantics so I don't think there's much to say here from the opsem side.

@Amanieu
Copy link
Member

Amanieu commented May 20, 2025

The semantics are essentially:

if debugger is attached and chooses to skip over the breakpoint {
    continue execution
} else {
    trap
}

@hanna-kruppe
Copy link
Contributor

hanna-kruppe commented May 20, 2025

The "trap" part seems like a somewhat "new capability" for no_std programs. There's lots of ways to diverge, of course, but all the ones I can think of right now are either std-exclusive (std::process::abort), platform-specific (e.g., inline assembly or functions like core::arch::wasm32::unreachable), diverge by entering an infinite loop, or end up in the #[panic_handler] which has to use one of the aforementioned options to diverge. The kind of "portable trap" that breakpoint does is more like core::intrinsics::abort, which is unstable and has no stable equivalent as far as I know.

@clarfonthey
Copy link
Contributor

clarfonthey commented May 20, 2025

I'm still not 100% convinced on this being in core::arch, since again, it's not an architecture-specific extension. (Inline assembly is technically callable by all architectures, but still inherently architecture-specific.)

That said, the closest analogue that seems to exist is std::process, and I'm not sure whether we'd want this to be core::process::breakpoint. It would go along with std::process::abort and std::process::exit.

Perhaps std::process::breakpoint could be the advanced version of breakpoint checking that truly does nothing when a debugger is not attached, and core::process::trap could be this version which is explicitly simpler and has a more descriptive name.

@hanna-kruppe
Copy link
Contributor

hanna-kruppe commented May 20, 2025

Also, regarding these arguments for core::arch over core::hint from last December:

AFAICT, every function in core::hint can be replaced by a no-op/identity for a correct (if suboptimal) implementation. I think that's a property worth not losing.

A breakpoint isn't a "hint"; it can't be replaced with a no-op.

Now that core::hint::select_unpredictable has been stabilized in beta, it's no longer true that every "hint" function is just a no-op or identity (though the correct-but-suboptimal implementation of select_unpredictable is pretty trivial). The quality of implementation lever to tune here is "whether a debugger can detect it and resume execution" and so the "suboptimal but valid" implementation would be unconditionally aborting. Indeed that's the implementation you get on targets where there is no standard mechanism that debuggers recognize as breakpoint distinct from any other program abort (e.g., I think this is the case for wasm).

@rfcbot rfcbot added final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. and removed proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. labels May 20, 2025
@rfcbot
Copy link
Collaborator

rfcbot commented May 20, 2025

🔔 This is now entering its final comment period, as per the review above. 🔔

@clarfonthey
Copy link
Contributor

Actually, that being the case now means that it probably is best to put this in core::hint for those reasons.

@joshtriplett
Copy link
Member Author

I'm still not 100% convinced on this being in core::arch, since again, it's not an architecture-specific extension. (Inline assembly is technically callable by all architectures, but still inherently architecture-specific.)

That said, the closest analogue that seems to exist is std::process, and I'm not sure whether we'd want this to be core::process::breakpoint. It would go along with std::process::abort and std::process::exit.

We did discuss core::process in the meeting, and we weren't sure either. It would be the only thing in core::process if we did that.

Perhaps std::process::breakpoint could be the advanced version of breakpoint checking that truly does nothing when a debugger is not attached, and core::process::trap could be this version which is explicitly simpler and has a more descriptive name.

trap is a different operation; this would be debugtrap. But breakpoint seems more descriptive.

@Jules-Bertholet
Copy link
Contributor

Jules-Bertholet commented May 22, 2025

Yes, that is a good argument for a different name (execution?). But either way, breakpoint() should be grouped together with immediate_abort().

(A third option is to put it directly in the root of core.)

@Jules-Bertholet
Copy link
Contributor

One argument in favor of core::process is that it would be a good place for an an abort() function that is equivalent to std::process::abort() where available, and intrinsics::abort() otherwise.

@Jules-Bertholet
Copy link
Contributor

Jules-Bertholet commented May 22, 2025

A fourth function that might be useful in this hypothetical module, is fn stop(), that does loop { std::thread::park() } where available, and loop { core::hint::spin_loop() } otherwise.

@joshtriplett joshtriplett added the I-libs-api-nominated Nominated for discussion during a libs-api team meeting. label May 22, 2025
@joshtriplett
Copy link
Member Author

Renominating so a subsequent meeting can do further bikeshedding.

@rfcbot rfcbot added finished-final-comment-period The final comment period is finished for this PR / Issue. to-announce Announce this issue on triage meeting and removed final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. labels May 30, 2025
@rfcbot
Copy link
Collaborator

rfcbot commented May 30, 2025

The final comment period, with a disposition to merge, as per the review above, is now complete.

As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed.

This will be merged soon.

@clarfonthey
Copy link
Contributor

I think that the FCP should be cancelled and restarted, considering how there was a lot of questioning about where this should be located. Especially considering how it was libs nominated a week ago.

@joshtriplett
Copy link
Member Author

We discussed this at length in last week's @rust-lang/libs-api meeting, and brought it up again in this week's meeting. Summary of that discussion:

  • We felt that the current proposed item has the correct semantics: unconditionally emit a target-specific debug breakpoint instruction.
  • We'd be open to a different item that has different semantics (e.g. a no-op unless a debugger is attached), but that would have to be in std, not core, and should have a different name.
  • We discussed both the module name and the item name pretty extensively.
    • core::arch seemed like a reasonable module choice, and given the desired semantics we couldn't come up with any better module location.
    • breakpoint seemed like as good a name as any. None of the alternative names made it any more self-explanatory (e.g. breakpoint instruction vs only break if debugger attached), and we didn't feel like a name could capture 100% of the semantics; the documentation should explain the semantics.

Given all of the above, we were fine going ahead with the stabilization. We would also be open to ACPs for additional items, such as a hint that produces a breakpoint if and only if a debugger is attached.

@traviscross
Copy link
Contributor

traviscross commented Jun 3, 2025

As a reminder for whoever puts up the stabilization PR, please nominate it for lang to review stable use of the intrinsic.

@Jules-Bertholet
Copy link
Contributor

we couldn't come up with any better module location.

Was core::execution discussed?

@clarfonthey
Copy link
Contributor

Although I personally dislike the decision, I think that the libs team isn't going to be particularly swayed by further bikeshedding, and so we shouldn't really comment anything else here unless it pertains to stabilising what's proposed as-is.

@apiraino apiraino removed the to-announce Announce this issue on triage meeting label Jun 5, 2025
@Amanieu Amanieu removed the I-libs-api-nominated Nominated for discussion during a libs-api team meeting. label Jun 10, 2025
joshtriplett added a commit to joshtriplett/rust that referenced this issue Jun 10, 2025
Stabilization report and FCP in
rust-lang#133724.
@RalfJung
Copy link
Member

RalfJung commented Jun 11, 2025

Are users expected to rely on this “by default” aborting the program, and if so, how?

I'd still love to see an answer to this, I don't think there has been one?

But yeah it seems too late... this feels a bit rushed given the amount of concerns and questions raised here.

given the desired semantics

Is there a writeup somewhere of why these semantics are desired? "stop if debugger is attached, ignore otherwise" seems like a much more desirable semantics to me.

@ChrisDenton
Copy link
Member

A higher level if debugger_present wrapper would need to build on top of the lower level SIGTRAP behaviour.

And the debug or abort behaviour is useful even if we had a function with different semantics.

@clarfonthey
Copy link
Contributor

The primary motivation, at least according to the ACP, is to be able to have this available on core, and while you're right that "stop if debugger is attached" behaviour is more desirable, this is the best that can be done without relying on OS help, AFAIK.

I've mentioned my arguments for why I think such a method should not be given a friendly name like breakpoint given how its undesirable semantics are mostly only suited for targets which are forced to use them, however, libs disagrees and I don't think that I'm going to change their mind.

@RalfJung
Copy link
Member

A higher level if debugger_present wrapper would need to build on top of the lower level SIGTRAP behaviour.

So the argument is "because that's what the system API provides, we don't have a choice here"?

And the debug or abort behaviour is useful even if we had a function with different semantics.

That's an assertion, not an argument.

@ChrisDenton
Copy link
Member

That's an assertion, not an argument.

Sure? You asserted that "stop if debugger is attached, ignore otherwise" is "more desirable". I did not take from that that you were requiring justification for any other behaviours being available in the standard library.

@joshtriplett
Copy link
Member Author

Are users expected to rely on this “by default” aborting the program, and if so, how?

I'd still love to see an answer to this, I don't think there has been one?

It emits a debug breakpoint instruction, or a trapping instruction if the target doesn't have a breakpoint instruction. Either way, that will have the behavior of aborting the program if not handled. On Linux, for instance, it'll result in the program receiving a SIGTRAP signal. The description of the function as aborting the program by default if not handled is very much part of the definition, and intended to be normative. Users can rely on it. Does that answer your question?

@joshtriplett
Copy link
Member Author

So the argument is "because that's what the system API provides, we don't have a choice here"?

No, some of the arguments are:

  • It's helpful, at the lowest level, to have the underlying mechanism to emit a breakpoint, which can then be inserted along a path the user wants to debug or get a core dump from. It can also be wrapped in any kind of conditional the user wants (e.g. if weird_error_condition { breakpoint(); }). This mechanism is always available, and doesn't require any OS services, so it can live in core.
  • We could also consider adding a function to detect if there's a debugger attached. That is a complex and target-specific operation, which may not be possible on all targets or in all environments, and which has some potential side effects. For instance, on Linux, this might require having /proc mounted and making several system calls to look for TracerPid, or attempting to attach a debugger to your own process and assuming that a failure indicates that you're being debugged. Also, this would need to live in std since it uses the OS.
  • The "break if debugger attached" operation still isn't something you'd want to leave in the program when not debugging, without some other conditional attached to it. Otherwise, for instance, it would trigger any time you run the program under strace, and it would also interfere with other unrelated debugging. So, typically I'd expect these operations to either be added when debugging and removed when done, or to be added underneath some conditional that has to be enabled. Once you have such a conditional, that reduces the value of also checking if a debugger is attached.

All that aside, it's a matter of debugging style and preference whether you want the "breakpoint if debugger attached" operation or the "breakpoint unconditionally" operation. The former can be built atop the latter, but the latter can't be built atop the former, and the latter is a useful operation to have.

@hanna-kruppe
Copy link
Contributor

hanna-kruppe commented Jun 11, 2025

You’ve stated what the function’s definition is and that “users can rely on it” but that doesn’t get to the heart of the question. Why is the definition like that, why and how is this definition useful to users? Would users complain or write their programs differently if breakpoint was changed to, e.g.,

  • Describe the “trap if not under a debugger” as best effort quality of implementation aspect (QoI)? Since we agree you wouldn’t leave this in a binary shipped to customers/production, does it matter if it occasionally doesn’t trap when you’re not actively debugging it?
  • Describe the “this is not a normal trap because a debugger can continue execution” aspect as QoI? (Indeed there are target platforms which don’t have such a concept, like wasm)
  • Give more or less detail about what “under a debugger” means? (i.e., if a user files a bug report “I used breakpoint and ran under the foo debugger but the debugger doesn’t recognize it as a breakpoint” would this be considered a bug in Rust if the foo debugger only recognizes a different instruction than the one Rust chose?)
  • Give more or less detail about how it aborts execution? For example, is the SIGTRAP on Linux guaranteed? Is entering an infinite loop (which counts as ! in Rust’s type system) a legal implementation?
  • Not exist at all, replaced with the advice “just panic and read these docs about how to place a breakpoint on panic“
  • Not exist at all, replaced with the advice “just define an inline(never) no_mangle function, call it where you want a breakpoint, and instruct your program to set a breakpoint on it”

Note that these questions are not coming from a preference for a different API (like “breakpoint under debugger, nop otherwise”) but from a language spec perspective: how and why is this “guarantee” you’re describing different from what the functions in std::hint get? Why is it important to users that this function is defined in a way that attracts T-lang attention? How are miri and formal verification tools supposed to model it?

@joshtriplett
Copy link
Member Author

  • Since we agree you wouldn’t leave this in a binary shipped to customers/production

That's not what I said. "breakpoint if debugger attached" isn't sufficient to leave the operation in place in production without some other conditional attached to it. In some production environments, it might make sense to deploy a binary that does have a breakpoint with a conditional wrapped around it, controlled by (for instance) a special option you can pass. But in the absence of such an additional conditional, a binary with a "breakpoint if debugger attached" such a binary would break if you tried to strace it, which people also need to do in production sometimes.

does it matter if it occasionally doesn’t trap when you’re not actively debugging it?

Yes, that would break user expectations. As one of many potential examples, you may want to run it without a debugger, expecting it to produce a coredump when it hits the breakpoint.

Describe the “this is not a normal trap because a debugger can continue execution” aspect as QoI?

That part of the behavior is target-specific, and if a target didn't have any mechanism for resuming execution, that wouldn't prevent aborting.

Give more or less detail about what “under a debugger” means? (i.e., if a user files a bug report “I used breakpoint and ran under the foo debugger but the debugger doesn’t recognize it as a breakpoint” would this be considered a bug in Rust if the foo debugger only recognizes a different instruction than the one Rust chose?)

That would depend on the nature of the issue, the debugger, and what the standard conventions on the target are. If it's the standard breakpoint of the platform and the debugger doesn't recognize it, that sounds like an issue in the debugger. But if there's some convention we failed to follow, and a proposed modification still works with other debuggers, it might be something we'd want to fix.

Not exist at all, replaced with the advice “just panic and read these docs about how to place a breakpoint on panic“

That would be less useful, for multiple reasons. Among them: you might not want to panic, because that's harder to step "around" than a breakpoint instruction. Also, a panic errors in a different way,

Not exist at all, replaced with the advice “just define an inline(never) no_mangle function, call it where you want a breakpoint, and instruct your program to set a breakpoint on it”

That would be less convenient, and additionally would not abort the program if hit without a debugger attached, and additionally

@RalfJung
Copy link
Member

RalfJung commented Jun 11, 2025

@ChrisDenton

Sure? You asserted that "stop if debugger is attached, ignore otherwise" is "more desirable". I did not take from that that you were requiring justification for any other behaviours being available in the standard library.

Josh claimed that what core does is the "desired semantics", implying that other semantics are not desired. I asked for justification, since I can totally see other semantics being "more desirable". I'm not the one trying to add something to our stable API surface here, so it is not me who has the burden of justification. If it was just about one gut feeling for what is desirable vs another, surely we should wait a bit longer and see whether we can find any actual arguments.

@joshtriplett

This mechanism is always available, and doesn't require any OS services, so it can live in core.

We could also consider adding a function to detect if there's a debugger attached. That is a complex and target-specific operation, which may not be possible on all targets or in all environments, and which has some potential side effects.

That very much sounds like "because that's what the system API provides, we don't have a choice here" to me. (I infer that the semantics of "only break under debugger" cannot live in core.)

To be clear, that's a reasonable argument as far as I am concerned.

I think it's just the name that is confusing for people not familiar with the details of assembly instructions, people that only ever used breakpoints in an IDE.

@hanna-kruppe
Copy link
Contributor

hanna-kruppe commented Jun 11, 2025

@joshtriplett Answering specific illustrative examples unfortunately doesn’t tell me directly what you think about the nature of this “guarantee users rely on” and I don’t want to assume. It feels unproductive to repeat it several times but I still haven’t gotten a straight answer how the points you made are not just QoI concerns. Is the (lack of) “guarantee” about what breakpoint does more like “your programs’ correctness can rely on <[T]>::sort actually sorting the slice, provided the comparison is well-behaved” or more like “you can use select_unpredictable to ask for branch-free codegen but it’s best effort, so don’t rely on it for trying to prevent timing side channels”? The concrete answers you’ve given (focusing on what’s practically useful) sound more like the latter, but again, I’d rather get a straight answer than having to guess at what you’re thinking.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this PR / Issue. I-lang-radar Items that are on lang's radar and will need eventual work or consideration. T-lang Relevant to the language team T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests