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

ACP: core::arch::breakpoint #491

Closed
joshtriplett opened this issue Nov 20, 2024 · 13 comments
Closed

ACP: core::arch::breakpoint #491

joshtriplett opened this issue Nov 20, 2024 · 13 comments
Labels
ACP-accepted API Change Proposal is accepted (seconded with no objections) api-change-proposal A proposal to add or alter unstable APIs in the standard libraries T-libs-api

Comments

@joshtriplett
Copy link
Member

joshtriplett commented Nov 20, 2024

Proposal

Problem statement

Sometimes, for debugging, users want to have a software breakpoint instruction to use with their debugger, or to generate a core dump for subsequent analysis.

core::intrinsics::breakpoint() exists, but intrinsics are perma-unstable.

Users can manually emit a breakpoint instruction using inline assembly, such as core::arch::asm!("int3") on x86, or core::arch::asm!("brk #0xf000") on ARM. However, this isn't portable.

Solution sketch

In core::arch:

/// Compiles to a target-specific software breakpoint instruction or equivalent.
///
/// This will typically abort the program. It may result in a core dump, and/or the system logging
/// debug information. Additional target-specific capabilities may be possible depending on
/// debuggers or other tooling; in particular, a debugger may be able to resume execution.
///
/// If possible, this will produce an instruction sequence that allows a debugger to resume *after*
/// the breakpoint, rather than resuming *at* the breakpoint; however, the exact behavior is
/// target-specific and debugger-specific, and not guaranteed.
///
/// If the target platform does not have any kind of debug breakpoint instruction, this may compile
/// to a trapping instruction (e.g. an undefined instruction) instead, or to some other form of
/// target-specific abort that may or may not support convenient resumption.
///
/// The precise behavior and the precise instruction generated are not guaranteed, except that in
/// normal execution with no debug tooling involved this will not continue executing.
///
/// - On x86 targets, this produces an `int3` instruction.
/// - On aarch64 targets, this produces a `brk #0xf000` instruction.
#[inline(always)]
pub fn breakpoint() {
    unsafe {
        core::intrinsics::breakpoint();
    }
}

Note that this should not be noreturn (-> !), because on some targets and environments, the user may be able to continue execution from the breakpoint in a debugger.

Links and related work

The unbug crate provides macros that emit breakpoints (e.g. for assertions), but it depends on nightly Rust.

What happens now?

This issue contains an API change proposal (or ACP) and is part of the libs-api team feature lifecycle. Once this issue is filed, the libs-api team will review open proposals as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.

Possible responses

The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):

  • We think this problem seems worth solving, and the standard library might be the right place to solve it.
  • We think that this probably doesn't belong in the standard library.

Second, if there's a concrete solution:

  • We think this specific solution looks roughly right, approved, you or someone else should implement this. (Further review will still happen on the subsequent implementation PR.)
  • We're not sure this is the right solution, and the alternatives or other materials don't give us enough information to be sure about that. Here are some questions we have that aren't answered, or rough ideas about alternatives we'd want to see discussed.
@joshtriplett joshtriplett added api-change-proposal A proposal to add or alter unstable APIs in the standard libraries T-libs-api labels Nov 20, 2024
@joshtriplett joshtriplett changed the title core::arch::breakpoint ACP: core::arch::breakpoint Nov 20, 2024
@programmerjake
Copy link
Member

what happens if an ISA doesn't have a breakpoint instruction? (e.g. wasm iirc)

@Amanieu
Copy link
Member

Amanieu commented Nov 20, 2024

I'm concerned about portability: this works on x86 because, after handling an int3, the program counter will end up pointing to the instruction after the breakpoint. However this is not the case for other architectures, for example an AArch64 BRK will keep the program counter pointing at the BRK after it is handled, and you have to manually skip the instruction.

@joshtriplett
Copy link
Member Author

joshtriplett commented Nov 20, 2024

@Amanieu wrote:

I'm concerned about portability: this works on x86 because, after handling an int3, the program counter will end up pointing to the instruction after the breakpoint. However this is not the case for other architectures, for example an AArch64 BRK will keep the program counter pointing at the BRK after it is handled, and you have to manually skip the instruction.

This is entirely the problem of the debugger to deal with; it doesn't make the Rust program non-portable. (Also, I've recently learned that one standard workaround for that in x86 is to use int3; nop.)

@joshtriplett
Copy link
Member Author

joshtriplett commented Nov 20, 2024

@programmerjake wrote:

what happens if an ISA doesn't have a breakpoint instruction? (e.g. wasm iirc)

WebAssembly appears to implement the LLVM llvm.debugtrap intrinsic, and maps it to the instruction unreachable.

More generally: I would expect that there's always some way to trap, or failing that to abort, and that worst case it'll map to whatever unreachable! or assert! uses to bail out. If an architecture truly didn't have anything for that, the Rust target for it would have bigger problems.

@Amanieu
Copy link
Member

Amanieu commented Nov 20, 2024

@joshtriplett I think you misunderstand, if you try to continue after a breakpoint instruction:

  • on x86 this will continue execution after the int3 instruction, as you expect.
  • on other architectures this will resume execution before the breakpoint instruction, which will simply trigger the breakpoint again. You have to manually modify the PC in the debugging to skip past the instruction.
    • Effectively, the breakpoint instruction is treated like any other faulting instruction, such as a faulting load.

Given that this only produces the expected behavior on x86, I don't think we can reasonably expose this as a platform-independent intrinsic.

@joshtriplett
Copy link
Member Author

joshtriplett commented Nov 21, 2024

You have to manually modify the PC in the debugging to skip past the instruction.

I understood that; my point is, dealing with that kind of variation is the job of a debugger. Some debuggers do recognize, for instance, the specific aarch64 brk produced by LLVM's __builtin_debugtrap() and automatically skip over it when hit.

Given that this only produces the expected behavior on x86, I don't think we can reasonably expose this as a platform-independent intrinsic.

LLVM and C++ both have a platform-independent intrinsic for this.

The platform-independent behavior is "this will trap, stopping execution; it may result in a core dump; a debugger may treat this as a breakpoint".

@BrainBacon
Copy link

BrainBacon commented Nov 21, 2024

From my experiments in the Unbug crate running in VSCode I've noticed that brk #1 is insufficient on Apple silicon. That resulted in getting stuck on the breakpoint. However, it looks like __builtin_debugtrap() in LLVM uses brk #0xF000 which will allow the debugger to continue. I've also noticed that a similar nop trick was necessary to get the debugger to land on the correct statement, in my case brk #0xF000 \n nop. The newline (not a semicolon) was necessary using core::arch::asm!.

@programmerjake
Copy link
Member

@programmerjake wrote:

what happens if an ISA doesn't have a breakpoint instruction? (e.g. wasm iirc)

WebAssembly appears to implement the LLVM llvm.debugtrap intrinsic, and maps it to the instruction unreachable.

ok, I had assumed unreachable wasn't usable since I don't expect a debugger to be able to continue after hitting it...

More generally: I would expect that there's always some way to trap, or failing that to abort,

Yeah I assumed the desired semantics were that a debugger would always be able to continue after hitting the breakpoint, however lowering it to an abort-like thing means that isn't really possible.

@joshtriplett
Copy link
Member Author

Yeah I assumed the desired semantics were that a debugger would always be able to continue after hitting the breakpoint, however lowering it to an abort-like thing means that isn't really possible.

The desired semantics are that it traps, in a target-specific way, which might dump core and might be possible to continue if you have a debugger attached, but the details will be target-specific and debugger-specific.

@Amanieu
Copy link
Member

Amanieu commented Nov 22, 2024

The desired semantics are that it traps, in a target-specific way, which might dump core and might be possible to continue if you have a debugger attached, but the details will be target-specific and debugger-specific.

You can achieve this in a portable way (at least UNIX) and which works with resuming execution in the debugger by calling raise(SIGTRAP).

@joshtriplett
Copy link
Member Author

@Amanieu That's not portable, though, and you can't do it using exclusively core.

@Amanieu
Copy link
Member

Amanieu commented Nov 26, 2024

Accepted pending to documentation changes that were discussed in the meeting.

@Amanieu Amanieu added the ACP-accepted API Change Proposal is accepted (seconded with no objections) label Nov 26, 2024
@joshtriplett
Copy link
Member Author

I've updated the documentation.

@Amanieu Amanieu closed this as completed Nov 28, 2024
joshtriplett added a commit to joshtriplett/rust that referenced this issue Dec 2, 2024
joshtriplett added a commit to joshtriplett/rust that referenced this issue Dec 2, 2024
joshtriplett added a commit to joshtriplett/rust that referenced this issue Dec 2, 2024
Approved in [ACP 491](rust-lang/libs-team#491).

Remove the `unsafe` on `core::intrinsics::breakpoint()`, since it's a
safe intrinsic to call and has no prerequisites.
joshtriplett added a commit to joshtriplett/rust that referenced this issue Dec 2, 2024
Approved in [ACP 491](rust-lang/libs-team#491).

Remove the `unsafe` on `core::intrinsics::breakpoint()`, since it's a
safe intrinsic to call and has no prerequisites.
joshtriplett added a commit to joshtriplett/rust that referenced this issue Dec 2, 2024
Approved in [ACP 491](rust-lang/libs-team#491).

Remove the `unsafe` on `core::intrinsics::breakpoint()`, since it's a
safe intrinsic to call and has no prerequisites.

(Thanks to @zachs18 for figuring out the `bootstrap`/`not(bootstrap)`
logic.)
joshtriplett added a commit to joshtriplett/rust that referenced this issue Dec 2, 2024
Approved in [ACP 491](rust-lang/libs-team#491).

Remove the `unsafe` on `core::intrinsics::breakpoint()`, since it's a
safe intrinsic to call and has no prerequisites.

(Thanks to @zachs18 for figuring out the `bootstrap`/`not(bootstrap)`
logic.)
joshtriplett added a commit to joshtriplett/rust that referenced this issue Dec 2, 2024
Approved in [ACP 491](rust-lang/libs-team#491).

Remove the `unsafe` on `core::intrinsics::breakpoint()`, since it's a
safe intrinsic to call and has no prerequisites.

(Thanks to @zachs18 for figuring out the `bootstrap`/`not(bootstrap)`
logic.)
joshtriplett added a commit to joshtriplett/rust that referenced this issue Dec 3, 2024
Approved in [ACP 491](rust-lang/libs-team#491).

Remove the `unsafe` on `core::intrinsics::breakpoint()`, since it's a
safe intrinsic to call and has no prerequisites.

(Thanks to @zachs18 for figuring out the `bootstrap`/`not(bootstrap)`
logic.)
joshtriplett added a commit to joshtriplett/rust that referenced this issue Dec 3, 2024
Approved in [ACP 491](rust-lang/libs-team#491).

Remove the `unsafe` on `core::intrinsics::breakpoint()`, since it's a
safe intrinsic to call and has no prerequisites.

(Thanks to @zachs18 for figuring out the `bootstrap`/`not(bootstrap)`
logic.)
joshtriplett added a commit to joshtriplett/rust that referenced this issue Dec 3, 2024
Approved in [ACP 491](rust-lang/libs-team#491).

Remove the `unsafe` on `core::intrinsics::breakpoint()`, since it's a
safe intrinsic to call and has no prerequisites.

(Thanks to @zachs18 for figuring out the `bootstrap`/`not(bootstrap)`
logic.)
joshtriplett added a commit to joshtriplett/rust that referenced this issue Dec 3, 2024
Approved in [ACP 491](rust-lang/libs-team#491).

Remove the `unsafe` on `core::intrinsics::breakpoint()`, since it's a
safe intrinsic to call and has no prerequisites.

(Thanks to @zachs18 for figuring out the `bootstrap`/`not(bootstrap)`
logic.)
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Dec 3, 2024
Add `core::arch::breakpoint` and test

Approved in [ACP 491](rust-lang/libs-team#491).
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Dec 3, 2024
Add `core::arch::breakpoint` and test

Approved in [ACP 491](rust-lang/libs-team#491).
rust-timer added a commit to rust-lang-ci/rust that referenced this issue Dec 4, 2024
Rollup merge of rust-lang#133726 - joshtriplett:breakpoint, r=oli-obk

Add `core::arch::breakpoint` and test

Approved in [ACP 491](rust-lang/libs-team#491).
github-actions bot pushed a commit to rust-lang/miri that referenced this issue Dec 4, 2024
Add `core::arch::breakpoint` and test

Approved in [ACP 491](rust-lang/libs-team#491).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ACP-accepted API Change Proposal is accepted (seconded with no objections) api-change-proposal A proposal to add or alter unstable APIs in the standard libraries T-libs-api
Projects
None yet
Development

No branches or pull requests

4 participants