-
Notifications
You must be signed in to change notification settings - Fork 13.4k
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
Comments
Is there a reason why this is in Plus, |
Does it really fit in It fits better in there but it doesn't feel like the obvious place to look for a breakpoint function |
@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 That said, that doesn't necessarily make I don't see any existing module that this logically fits in other than We could put it in a new |
I mean, sure, but we have other things in |
@clarfonthey AFAICT, every function in A breakpoint isn't a "hint"; it can't be replaced with a no-op. Is there any other module in |
JavaScript has the |
@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. |
@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. |
Stabilization ReportImplementation HistoryApproved 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 API SummaryIn pub fn breakpoint(); Experience ReportThe ACP was originally inspired by the |
@rfcbot merge |
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. |
@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 |
This doesn't really have semantics so I don't think there's much to say here from the opsem side. |
The semantics are essentially:
|
The "trap" part seems like a somewhat "new capability" for |
I'm still not 100% convinced on this being in That said, the closest analogue that seems to exist is Perhaps |
Also, regarding these arguments for
Now that |
🔔 This is now entering its final comment period, as per the review above. 🔔 |
Actually, that being the case now means that it probably is best to put this in |
We did discuss
|
Stabilization report and FCP in rust-lang#133724.
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.
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. |
A higher level And the debug or abort behaviour is useful even if we had a function with different semantics. |
The primary motivation, at least according to the ACP, is to be able to have this available on I've mentioned my arguments for why I think such a method should not be given a friendly name like |
So the argument is "because that's what the system API provides, we don't have a choice here"?
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. |
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? |
No, some of the arguments are:
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. |
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
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 |
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
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.
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.
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.
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,
That would be less convenient, and additionally would not abort the program if hit without a debugger attached, and additionally |
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.
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. |
@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 |
@RalfJung wrote:
It seems like you're starting from the baseline assumption that if we had a choice, we'd necessarily choose to have the "breakpoint only if debugging", and thus it must be that "we don't have a choice". Even leaving aside that the "if debugging" check isn't as portable, and adds complexity that has side effects that might interfere with debugging: if you acknowledge that the underlying breakpoint operation low-level operation and high-level operation are both useful, the intention here was to expose the low-level operation, and if someone wants to propose building the high-level operation on top of that, they can. |
I wasn't trying to do that, though maybe I wasn't separating my opinions sufficiently from the argument-gathering. |
Anyone interested in discussing the hypothetical high-level operation of "trigger the (architectural-specific breakpoint handling) if a debugger is detached" might be entertained by reading the story of our attempt to implement exactly that as an internal detail of std, to make debugging more convenient. It is actually surprisingly possible to implement, but it has unintended consequences: #142325 (comment) |
So, to attempt to more precisely call out the documented answer to the question of what the language semantics are: The doc comment on the
The world in which you're using debugging tooling, much like the world in which you open However, I don't think the entirety of the specification should be "breakpoint is implementation defined". Based on conversations with @nikomatsakis, I'm suggesting that it should instead be something like this: If no debugging tool or other implementation-defined breakpoint-handling mechanism (e.g. signal handler) is in use, execution will not continue; the manner in which it does not continue is target-defined (e.g. abort, trapping instruction, infinite loop). If a debugging tool or other implementation-defined breakpoint-handling mechanism is in use, the behavior is implementation-defined, and may be able to continue execution. Users of @hanna-kruppe, @RalfJung, does that answer the question you were trying to answer? Also, we might want to consolidate the discussion into one place rather than two. I'd suggest here, rather than on the stabilization PR. |
This seems to contradict the later part that even in the absence of debugging tools, execution may sometimes continue through target-specific means? |
I think resuming from a breakpoint is in a weird space somewhere between implementation-defined behavior and erroneous behavior. A normal program execution should not return from it, which means implementations also should not do this. But optimizations may not assume that this never happens.
Instead of volatile we could also model it as a shared atomic that is initialized to zero at program startup and may not be written to by the program itself, but since it's shared the environment may write to it. |
I've expanded the text to consistently use "debugging tool or other implementation-defined breakpoint-handling mechanism". Does the new text look reasonable to you? |
I think it would be a category mistake to bring notions like "if a debugging tool is used" or "normally, unless impl-defined mechanisms are used" into normative language spec contents. Everything written there may need to be reflected in (for example) a pen-and-paper mathematical proof, some code in Miri, or a verification tool like Kani. At least for those purposes, this intrinsics has to be boiled down into (at most) a conditional abort depending on some non-deterministic choice or some escape hatch into "interaction with outside environment we're not modeling here" (as for volatile loads and stores). This is already saying more than just " Consequently, there's not much wiggle room left, certainly no way to normatively nail down things like "normally, if there's no debugger attached [...]" because none of that has any meaning in the terms of the AM. I think all that intent about how this intrinsic should be useful to flesh and blood programmers dealing with silicon belong in non-normative notes and/or rustc's documentation of how it implements the language spec. The spec can be far removed from that, it just has to be compatible with any reasonable implementation strategy. To me this implies:
Coming back to the physical realm for a minute: I don't think rustc should document/support any other "breakpoint handling mechanisms" like catching SIGTRAP from within the program as a means of automatically "handling" breakpoints. To me that's in the same category as "handling" SIGFPE to let the program proceed after a division by zero. Technically the physical machine and OS let you do it, but practically speaking you can't know if the program is running off a cliff and hoping for the signal to stop it. The intended use case for this feature is interactive debugging; other ways of continuing execution should be unsupported so we don't have to worry about weird hacks people come up for it while evolving the implementation strategy to best fit the intended use case. |
Maybe we should flip the question around. @joshtriplett If an architecture, by default, masks most faults/interrupts unless you do something equivalent to "attach a debugger", perhaps because the architecture is oriented around parallel execution (like a GPU, say) and does not have abundant reserve state to both execute quickly and relatively-easily halt forward flow of the program upon hitting a condition, then should this intrinsic not also emit an instruction that would, by default, be masked by the architecture's default interrupt-handling of "I would prefer not to"? I believe most such architectures do support "unconditional" faults that happen even if debuggers are not attached, as they still have erroneous conditions like memory violations. But for a parallel execution of shaders, that might not even kill the entire "program", just that wave's execution. Meanwhile, this intrinsic seems to clearly want to emit the instruction that the architecture and attendant tools would recognize as part of the debugging protocol, not "definitely kill forward momentum". I understand that you're trying to produce a guarantee that on x86 and x86_64, an actual I know there's the problem of hypothetical psycho-compilers, but defending against them seems to introduce more complications than it solves. Programmers can and do rely, for instance, on most of the |
They can and will be used, and they should not be undefined behavior. They could be implementation-defined behavior, for instance, but the compiler still isn't allowed to eat your laundry if you do it (as it could if the function were defined as |
That sounds really painful to work with, but that's a reasonable point. If the conventional behavior of breakpoints on the target is not to fault, and the target doesn't have a mechanism for "breakpoint unconditionally" that is still useful with a debugger attached (e.g. if the only two options are "halt in an unrecoverable way" or "breakpoint in a resumable way but only if a debugger is attached"), then it's worth at least considering whether The thing I primarily want to avoid is, given a target that has a conventional "breakpoint unconditionally" operation and a conventional "breakpoint if debugger attached" operation, mapping
In part because I'm trying to guard against any target mapping this to a "do something if debugger is attached" primitive or a no-op, for reasons such as those demonstrated in your story about panics in the standard library.
That much seems fine, but I wouldn't want to specify a SIMD intrinsic as "this does an implementation-defined thing", to the point that it'd be a correct implementation to not do the operation. e.g. a SIMD add intrinsic doesn't necessarily have to use one specific add instruction, but it should behave as-if it did, and in particular, should perform an addition. |
I worry we're continually in danger of mixing libs concerns in with lang concerns. What matters for lang is the abstract machine, right? And in that sense is Of course on the libs side (and perhaps compiler side) there are some interesting things but lang doesn't need to worry about any of that because it doesn't concretely affect the AM, no? |
It’s probably useful for reasoning about programs if |
I would disagree since lang still cares about those: FFI is a landmine of undefined behaviour and we still would like to put up guard rails to define what is and isn't allowed. The abort path is self-explanatory because the program behaviour after is explicitly nothing: no program, no behaviour. But it's desirable to say that the continue path hasn't affected the rest of the program in a way that would cause UB, assuming that the program hasn't been altered in any way. Since it's intended for debugging, it's highly undesirable if the state of the program is affected at all by introducing the breakpoint, but at the same time, you'd also expect the breakpoint to act similarly to an atomic fence, since shouldn't everything before the breakpoint be executed before the breakpoint occurs? These are competing concerns: one says the breakpoint shouldn't affect anything, but that is an example of something it should affect. And these are very language-domain decisions to be made. |
Giving |
Uh oh!
There was an error while loading. Please reload this page.
Feature gate:
#![feature(breakpoint)]
This is a tracking issue for the
breakpoint
feature, which gates thecore::arch::breakpoint
function. This feature was approved in ACP 491.Public API
Steps / History
core::arch::breakpoint
libs-team#491core::arch::breakpoint
and test #133726Unresolved Questions
Footnotes
https://std-dev-guide.rust-lang.org/feature-lifecycle/stabilization.html ↩
The text was updated successfully, but these errors were encountered: