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

Add API for capturing backtrace #1559

Merged
merged 12 commits into from
Sep 28, 2020
Merged

Conversation

Aaron1011
Copy link
Member

This PR adds two new Miri-defined extern functions:
miri_get_backtrace and miri_resolve_frame, which are documented in
the README. Together, they allow obtaining a backtrace for the currently
executing program.

I've added a test showing how these APIs are used. I've also prepared a
companion PR backtrace-rs, which will allow
backtrace::Backtrace::new() to work automatically under Miri.

Once these two PRs are merged, we will be able to print backtraces from
the normal Rust panic hook (since libstd is now using backtrace-rs).

A few notes:

  • Resolving the backtrace frames is very slow - you can actually see
    each line being printed out one at a time. Some local testing showed
    that this is not (primrary) caused by resolving a Span - it seems
    to be just Miri being slow.
  • For the first time, we now interact directly with a user-defined
    struct (instead of just executing the user-provided MIR that
    manipulates the struct). To allow for future changes, I've added
    a 'version' parameter (currently required to be 0). This should allow
    us to change the MiriFrame struct should the need ever arise.
  • I used the approach suggested by @oli-obk - a returned backtrace
    pointer consists of a base function allocation, with the 'offset'
    used to encode the Span.lo. This allows losslessly reconstructing
    the location information in miri_resolve_frame.
  • There are a few quirks on the backtrace-rs side:
    • backtrace-rs calls getcwd() by default to try to simplify
      the filename. This results in an isolation error by default,
      which could be annoying when printing a backtrace from libstd.
    • backtrace-rs tries to remove 'internal' frames (everything between
      the call to Backtrace::new() and the internal API call made by
      backtrace-rs) by comparing the returned frame pointer value to
      a Rust function pointer. This doesn't work due to the way we
      construct the frame pointers passed to the caller. We could
      attempt to support this kind of comparison, or just add a
      #[cfg(miri)] and ignore the frames ourselves.

Aaron1011 added a commit to Aaron1011/backtrace-rs that referenced this pull request Sep 22, 2020
@Aaron1011
Copy link
Member Author

The backtrace-rs PR is up at rust-lang/backtrace-rs#372

src/shims/foreign_items.rs Outdated Show resolved Hide resolved
@RalfJung
Copy link
Member

Resolving the backtrace frames is very slow - you can actually see
each line being printed out one at a time. Some local testing showed
that this is not (primrary) caused by resolving a Span - it seems
to be just Miri being slow.

I have seen string formatting being very slow before, so this is likely another case of that.

@RalfJung
Copy link
Member

backtrace-rs calls getcwd() by default to try to simplify
the filename. This results in an isolation error by default,
which could be annoying when printing a backtrace from libstd.

This is not a new problem, it already leads to isolation error (instead of an empty backtrace) right now.
So this is just an instance of #1034.

/// and `MiriFrame` should be declared as follows:
///
/// ```rust
/// struct MiriFrame {
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this be repr(C)?

Copy link
Member Author

Choose a reason for hiding this comment

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

We use mplace_field to write to fields, which ends up using FieldsShape to map the definition-order fields to their actual offsets in memory.

Copy link
Member

Choose a reason for hiding this comment

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

Oh so we exploit that we actually have access to the user-defined type... sneaky.^^

README.md Outdated
///
/// The fields must be declared in exactly the same order as they appear in `MiriFrame` above.
/// This function can be called on any thread (not just the one which obtained `frame`)
fn miri_resolve_frame(version: u8, frame: *mut ()) -> MiriFrame;
Copy link
Member

Choose a reason for hiding this comment

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

What we are effectively doing here is define something like a syscall interface. I have not seen version numbers in a syscall interface before, but I have seen flag fields -- so maybe it would make sense to instead make this flags: u64? That seems to be a more common way of future-proofing an API.

Also, if we ever need to change the number of arguments, we should probably add a miri_resolve_frame_2 or so. Overall I think things will be least confusing if we can make this work and evolve like a normal C API, not exploiting the fact that Miri can do fancy things like determine how many arguments the caller actually passed (without varargs).

Copy link
Member Author

Choose a reason for hiding this comment

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

I've added a flags field instead. Currently, I just throw an error if it's not 0. We could return a Result instead, but I don't know if there would ever be a need for code to support multiple Miri versions.

README.md Outdated Show resolved Hide resolved
@Aaron1011
Copy link
Member Author

@RalfJung I've addressed your comments

@Aaron1011
Copy link
Member Author

@RalfJung: I've addressed your latest round of comments

@RalfJung
Copy link
Member

@Aaron1011 yeah I know, I just didn't have time this week-end to look at your PR.

@RalfJung
Copy link
Member

But I was actually looking at it when you wrote your comment.^^
@bors r+

Thanks for the PR :)

@bors
Copy link
Contributor

bors commented Sep 28, 2020

📌 Commit 2a2a93d has been approved by RalfJung

@bors
Copy link
Contributor

bors commented Sep 28, 2020

⌛ Testing commit 2a2a93d with merge 2c9afae...

bors added a commit that referenced this pull request Sep 28, 2020
Add API for capturing backtrace

This PR adds two new Miri-defined extern functions:
`miri_get_backtrace` and `miri_resolve_frame`, which are documented in
the README. Together, they allow obtaining a backtrace for the currently
executing program.

I've added a test showing how these APIs are used. I've also prepared a
companion PR `backtrace-rs`, which will allow
`backtrace::Backtrace::new()` to work automatically under Miri.

Once these two PRs are merged, we will be able to print backtraces from
the normal Rust panic hook (since libstd is now using backtrace-rs).

A few notes:
* Resolving the backtrace frames is *very* slow - you can actually see
  each line being printed out one at a time. Some local testing showed
  that this is not (primrary) caused by resolving a `Span` - it seems
  to be just Miri being slow.
* For the first time, we now interact directly with a user-defined
  struct (instead of just executing the user-provided MIR that
  manipulates the struct). To allow for future changes, I've added
  a 'version' parameter (currently required to be 0). This should allow
  us to change the `MiriFrame` struct should the need ever arise.
* I used the approach suggested by `@oli-obk` - a returned backtrace
  pointer consists of a base function allocation, with the 'offset'
  used to encode the `Span.lo`. This allows losslessly reconstructing
  the location information in `miri_resolve_frame`.
* There are a few quirks on the `backtrace-rs` side:
  * `backtrace-rs` calls `getcwd()` by default to try to simplify
    the filename. This results in an isolation error by default,
    which could be annoying when printing a backtrace from libstd.
  * `backtrace-rs` tries to remove 'internal' frames (everything between
     the call to `Backtrace::new()` and the internal API call made by
     backtrace-rs) by comparing the returned frame pointer value to
     a Rust function pointer. This doesn't work due to the way we
     construct the frame pointers passed to the caller. We could
     attempt to support this kind of comparison, or just add a
    `#[cfg(miri)]` and ignore the frames ourselves.
README.md Outdated Show resolved Hide resolved
@RalfJung
Copy link
Member

@bors r+ retry

@bors
Copy link
Contributor

bors commented Sep 28, 2020

📌 Commit 1670f8c has been approved by RalfJung

@bors
Copy link
Contributor

bors commented Sep 28, 2020

⌛ Testing commit 1670f8c with merge e550540...

bors added a commit that referenced this pull request Sep 28, 2020
Add API for capturing backtrace

This PR adds two new Miri-defined extern functions:
`miri_get_backtrace` and `miri_resolve_frame`, which are documented in
the README. Together, they allow obtaining a backtrace for the currently
executing program.

I've added a test showing how these APIs are used. I've also prepared a
companion PR `backtrace-rs`, which will allow
`backtrace::Backtrace::new()` to work automatically under Miri.

Once these two PRs are merged, we will be able to print backtraces from
the normal Rust panic hook (since libstd is now using backtrace-rs).

A few notes:
* Resolving the backtrace frames is *very* slow - you can actually see
  each line being printed out one at a time. Some local testing showed
  that this is not (primrary) caused by resolving a `Span` - it seems
  to be just Miri being slow.
* For the first time, we now interact directly with a user-defined
  struct (instead of just executing the user-provided MIR that
  manipulates the struct). To allow for future changes, I've added
  a 'version' parameter (currently required to be 0). This should allow
  us to change the `MiriFrame` struct should the need ever arise.
* I used the approach suggested by `@oli-obk` - a returned backtrace
  pointer consists of a base function allocation, with the 'offset'
  used to encode the `Span.lo`. This allows losslessly reconstructing
  the location information in `miri_resolve_frame`.
* There are a few quirks on the `backtrace-rs` side:
  * `backtrace-rs` calls `getcwd()` by default to try to simplify
    the filename. This results in an isolation error by default,
    which could be annoying when printing a backtrace from libstd.
  * `backtrace-rs` tries to remove 'internal' frames (everything between
     the call to `Backtrace::new()` and the internal API call made by
     backtrace-rs) by comparing the returned frame pointer value to
     a Rust function pointer. This doesn't work due to the way we
     construct the frame pointers passed to the caller. We could
     attempt to support this kind of comparison, or just add a
    `#[cfg(miri)]` and ignore the frames ourselves.
This PR adds two new Miri-defined extern functions:
`miri_get_backtrace` and `miri_resolve_frame`, which are documented in
the README. Together, they allow obtaining a backtrace for the currently
executing program.

I've added a test showing how these APIs are used. I've also prepared a
companion PR `backtrace-rs`, which will allow
`backtrace::Backtrace::new()` to work automatically under Miri.

Once these two PRs are merged, we will be able to print backtraces from
the normal Rust panic hook (since libstd is now using backtrace-rs).

A few notes:
* Resolving the backtrace frames is *very* slow - you can actually see
  each line being printed out one at a time. Some local testing showed
  that this is not (primrary) caused by resolving a `Span` - it seems
  to be just Miri being slow.
* For the first time, we now interact directly with a user-defined
  struct (instead of just executing the user-provided MIR that
  manipulates the struct). To allow for future changes, I've added
  a 'version' parameter (currently required to be 0). This should allow
  us to change the `MiriFrame` struct should the need ever arise.
* I used the approach suggested by @oli-obk - a returned backtrace
  pointer consists of a base function allocation, with the 'offset'
  used to encode the `Span.lo`. This allows losslessly reconstructing
  the location information in `miri_resolve_frame`.
* There are a few quirks on the `backtrace-rs` side:
  * `backtrace-rs` calls `getcwd()` by default to try to simplify
    the filename. This results in an isolation error by default,
    which could be annoying when printing a backtrace from libstd.
  * `backtrace-rs` tries to remove 'internal' frames (everything between
     the call to `Backtrace::new()` and the internal API call made by
     backtrace-rs) by comparing the returned frame pointer value to
     a Rust function pointer. This doesn't work due to the way we
     construct the frame pointers passed to the caller. We could
     attempt to support this kind of comparison, or just add a
    `#[cfg(miri)]` and ignore the frames ourselves.
@RalfJung
Copy link
Member

RalfJung commented Sep 28, 2020

Looks like I was right to be worried about test stability... we already have a change in the output:

-RUSTLIB/src/rust/library/std/src/rt.rs:LL:COL (std::rt::lang_start::<()>::{{closure}}#0)
+RUSTLIB/src/rust/library/std/src/rt.rs:LL:COL (std::rt::lang_start::<()>::{closure#0})

Can you adjust normalization to remove more things? We might actually just want to cut off the function name after the first ::<.

@bors
Copy link
Contributor

bors commented Sep 28, 2020

💔 Test failed - checks-travis

@Aaron1011
Copy link
Member Author

This looks like it was caused by https://github.com/rust-lang/rust/pull/76176/files. That PR also caused a lot of changes in rustc UI tests, so I don't think this issue is specific to the way we're printing backtrace frames.

However, I'm fine with normalizing more of the output.

@Aaron1011
Copy link
Member Author

@RalfJung: I've normalized out all turbofished generic arguments (::<>) from the stderr.

Aaron1011 added a commit to Aaron1011/backtrace-rs that referenced this pull request Sep 28, 2020
@RalfJung
Copy link
Member

Thanks!
@bors r+

@bors
Copy link
Contributor

bors commented Sep 28, 2020

📌 Commit 7fba3c2 has been approved by RalfJung

@bors
Copy link
Contributor

bors commented Sep 28, 2020

⌛ Testing commit 7fba3c2 with merge 510e62c...

@bors
Copy link
Contributor

bors commented Sep 28, 2020

☀️ Test successful - checks-travis, status-appveyor
Approved by: RalfJung
Pushing 510e62c to master...

@bors bors merged commit 510e62c into rust-lang:master Sep 28, 2020
alexcrichton pushed a commit to rust-lang/backtrace-rs that referenced this pull request Sep 29, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants