Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upImprove backtrace formating while panicking. #38165
Conversation
rust-highfive
assigned
brson
Dec 4, 2016
This comment has been minimized.
This comment has been minimized.
|
Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @brson (or someone else) soon. If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. Due to the way GitHub handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes. Please see the contribution instructions for more information. |
This comment has been minimized.
This comment has been minimized.
|
Any idea about how I could get access to the build root path? |
This comment has been minimized.
This comment has been minimized.
|
Result formatting with
and with
I plan to mask 1-8 and 12-16 with |
This comment has been minimized.
This comment has been minimized.
|
Those appear to be the same, did you copy the wrong thing in? |
This comment has been minimized.
This comment has been minimized.
|
For now they are nearly the same, except the address which is not printed with |
This comment has been minimized.
This comment has been minimized.
|
In fact, that may not be necessary. We can simply recognized the mangled form of |
Yamakaky
force-pushed the
Yamakaky:better-backtrace
branch
from
e94d312
to
ebc1ce9
Dec 5, 2016
This comment has been minimized.
This comment has been minimized.
|
With
|
estebank
reviewed
Dec 5, 2016
| @@ -173,7 +178,7 @@ pub fn print(w: &mut Write, idx: isize, addr: *mut libc::c_void, | |||
| for (i, &(file, line)) in fileline_buf[..fileline_count].iter().enumerate() { | |||
| if file.is_null() { continue; } // just to be sure | |||
| let file = unsafe { CStr::from_ptr(file).to_bytes() }; | |||
| output_fileline(w, file, line, i == FILELINE_SIZE - 1)?; | |||
| output_fileline(w, file, line, i == FILELINE_SIZE - 1, format)?; | |||
This comment has been minimized.
This comment has been minimized.
estebank
Dec 5, 2016
Contributor
I believe you should be able to accomplish item 4 in #37783 by doing something like
let mut print_line = if format == backtrace::PrintFormat::Short { false } else { true };
for (i, &(file, line)) in fileline_buf[..fileline_count].iter().enumerate() {
if file.is_null() { continue; } // just to be sure
let file = unsafe { CStr::from_ptr(file).to_bytes() };
if format == backtrace::PrintFormat::Short && !print_line {
let magic_str = "core::panicking::panic_fmt";
if file[..magic_str.len()] == magic_str.as_bytes() {
print_line = true
}
continue;
}
output_fileline(w, file, line, i == FILELINE_SIZE - 1, format)?;
}
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
It's getting better!
compared to
|
This comment has been minimized.
This comment has been minimized.
|
Added removal of path prefix. It prints
|
This comment has been minimized.
This comment has been minimized.
|
Can someone test on windows? |
Yamakaky
force-pushed the
Yamakaky:better-backtrace
branch
to
1aa97b5
Dec 6, 2016
This comment has been minimized.
This comment has been minimized.
|
Updated first post. |
rust-highfive
assigned
alexcrichton
and unassigned
brson
Dec 10, 2016
alexcrichton
reviewed
Dec 12, 2016
|
Thanks for the PR! I like the look and feel of the new output. Could most of the changes be moved to the common implementation of backtraces? I don't see many changes to the Windows side of things so it'd be helpful to just define all the skipping and such logic in one location rather than in multiple. |
| @@ -1877,6 +1877,8 @@ fi | |||
|
|
|||
| msg | |||
| copy_if_changed ${CFG_SRC_DIR}${INPUT_MAKEFILE} ./Makefile | |||
| # Export CFG_BUILD_DIR for use in backtrace unwinding. | |||
| sed -i -re 's/^(CFG_BUILD_DIR)/export \1/' config.tmp | |||
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
| } | ||
|
|
||
| let val = match env::var_os("RUST_BACKTRACE") { | ||
| Some(x) => if &x == "0" { 1 } else { 2 }, | ||
| None => 1, | ||
| Some(x) => if ["no", "0"].iter().any(|val| &x == *val) { |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Dec 12, 2016
Member
In the interest of being as conservative as possible here, could we leave this as just recognizing 0?
This comment has been minimized.
This comment has been minimized.
| None | ||
| } else if &x == "full" { | ||
| Some(PrintFormat::Full) | ||
| } else if ["short", "yes", "1"].iter().any(|val| &x == *val) { |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Yamakaky
Dec 13, 2016
Author
Contributor
Are you sure? I find it cleaner with the "catch anything" separated.
| @@ -25,45 +27,107 @@ pub const HEX_WIDTH: usize = 18; | |||
| #[cfg(target_pointer_width = "32")] | |||
| pub const HEX_WIDTH: usize = 10; | |||
|
|
|||
| #[derive(Debug, Copy, Clone, Eq, PartialEq)] | |||
| pub enum PrintFormat { | |||
| Full = 2, | |||
This comment has been minimized.
This comment has been minimized.
alexcrichton
Dec 12, 2016
Member
These values seem to be quite subtle, could you be sure to comment as to their purpose here?
This comment has been minimized.
This comment has been minimized.
Yamakaky
Dec 13, 2016
Author
Contributor
OK. In fact, it controls if the unwinding should use the short or long backtrace format.
| let mut already_printed = false; | ||
| if format == PrintFormat::Short && file_path.is_absolute() { | ||
| if let Ok(cwd) = env::current_dir() { | ||
| let buildroot = Path::new(env!("CFG_BUILD_DIR")); |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Dec 12, 2016
Member
Could this use option_env and handle the None case to ensure that we can build the standard library separately if need be?
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
| let file_path = Path::new(file); | ||
| let mut already_printed = false; | ||
| if format == PrintFormat::Short && file_path.is_absolute() { | ||
| if let Ok(cwd) = env::current_dir() { |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
| if format == PrintFormat::Short && file_path.is_absolute() { | ||
| if let Ok(cwd) = env::current_dir() { | ||
| let buildroot = Path::new(env!("CFG_BUILD_DIR")); | ||
| let libpath = Path::new("/tmp/libpath"); |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Yamakaky
Dec 13, 2016
Author
Contributor
It's a dummy value, because the library directory is defined at compilation of the crate, not of stdlib. I still have to find a way to get it.
| if let Some(mangled_symbol_name) = resolve_symname(symaddr) { | ||
| let magics_begin = [ | ||
| "_ZN3std3sys3imp9backtrace7tracing3imp5write", | ||
| "_ZN3std9panicking", |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Dec 12, 2016
Member
This logic seems like we'd want to do this across all platforms, right? Perhaps this could go into the common module?
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
The |
This comment has been minimized.
This comment has been minimized.
|
Is it possible to also have the appveyor build? It would help me supporting windows. |
This comment has been minimized.
This comment has been minimized.
|
@alexcrichton A solution for cleaner code reuse would be that the unwinding function calls a platform-dependent function that generates some sort of Vec, then prints it. Not sure if it's an option, though, since it would allocate while unwinding. |
This comment has been minimized.
This comment has been minimized.
|
The platform specific option could potentially return an iterator instead of a vec to avoid the allocation (through impl Trait, or just explicitly naming it). |
This comment has been minimized.
This comment has been minimized.
|
I thought about that, but it may be complicated since the system specific code gives a callback to a C library that's invoked for each frame. |
This comment has been minimized.
This comment has been minimized.
What I propose is to add the structs |
Yamakaky
force-pushed the
Yamakaky:better-backtrace
branch
from
8e563c1
to
4447538
Dec 15, 2016
This comment has been minimized.
This comment has been minimized.
|
@alexcrichton I think I'm ready for a final review. I'm only on linux, so I was not able to test on windows and such. As I moved code around, there is probably at least some unused imports. |
alexcrichton
reviewed
Dec 16, 2016
| // See libunwind:src/unwind/Backtrace.c for the return values. | ||
| // No, there is no doc. | ||
| let ret = match result_unwind { | ||
| uw::_URC_END_OF_STACK => { |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Dec 16, 2016
Member
I may have missed it, but where did these changes come from? How come we're handling different return codes now?
This comment has been minimized.
This comment has been minimized.
Yamakaky
Dec 16, 2016
Author
Contributor
http://git.savannah.gnu.org/gitweb/?p=libunwind.git;a=blob;f=src/unwind/Backtrace.c;hb=bc8698fd7ed13a629a8ec3cb2a89bd74f9d8b5c0
_Unwind_Backtrace only return some values.
This comment has been minimized.
This comment has been minimized.
alexcrichton
Dec 16, 2016
Member
Sure yeah I can imagine that this function returns a number of values, but we weren't checking any of them before, only for errors. What prompted the change in logic?
This comment has been minimized.
This comment has been minimized.
Yamakaky
Dec 16, 2016
Author
Contributor
I thought it was better to handle error codes. Was I wrong?
This comment has been minimized.
This comment has been minimized.
alexcrichton
Dec 18, 2016
Member
Oh no that's fine, I'm just trying to understand why. Did this come up during testing? Was this just done on a whim? Have these code paths been executed? etc.
I'm wary of these sorts of changes because libunwind's cross-platform behavior is notoriously finnicky, so I just wanted to dig into what prompted this change. My guess is that it's fine to land as such, but I just want to make sure.
This comment has been minimized.
This comment has been minimized.
Yamakaky
Dec 18, 2016
Author
Contributor
No, I just found it strange to ignore the error case (https://github.com/rust-lang/rust/blob/master/src/libstd/panicking.rs#L349, I'm watching ^^)
This comment has been minimized.
This comment has been minimized.
alexcrichton
Dec 19, 2016
Member
Hm I feel like we're still not getting to an answer to my question. The code you linked to ignores errors because it has nowhere to pass the errors to and the information is just advisory, so it's fine to just ignore errors.
My question is why did this change happen? Is it purely being proactive? Was it reactive to some problem?
This comment has been minimized.
This comment has been minimized.
Yamakaky
Dec 19, 2016
Author
Contributor
No, I added it for the reasons explained above, without detected problems
| let addr = frame.AddrPC.Offset; | ||
| if addr == frame.AddrReturn.Offset || addr == 0 || | ||
| frame.AddrReturn.Offset == 0 { break } | ||
|
|
||
| frames[i] = addr; |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Yamakaky
Dec 16, 2016
Author
Contributor
Yeah, not sure why there is this -1, but it was probably important XD
|
|
||
| let name = if ret == c::TRUE { | ||
| if ret == c::TRUE { | ||
| let ptr = info.Name.as_ptr() as *const c_char; | ||
| Some(CStr::from_ptr(ptr).to_bytes()) | ||
| } else { | ||
| None | ||
| }; |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Dec 16, 2016
Member
This semicolon seems like it'd cause a compile failure? And the to_bytes doesn't seem to match the &'static str return type?
This comment has been minimized.
This comment has been minimized.
| CStr::from_ptr(info.dli_sname).to_bytes() | ||
| })) | ||
| Some(unsafe { | ||
| CStr::from_ptr(info.dli_sname).to_str().ok() |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Dec 16, 2016
Member
This type of Option<Option<&str>> seems to disagree with the tyep on the return of the function?
This comment has been minimized.
This comment has been minimized.
| fn _foreach_symbol_fileline<F>(symbol_addr: Frame, | ||
| mut f: F, | ||
| context: &BacktraceContext) -> io::Result<bool> | ||
| where F: FnMut(&[u8], libc::c_int) -> io::Result<()> |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Dec 16, 2016
Member
Stylistically we typically indent where clauses like these over one
This comment has been minimized.
This comment has been minimized.
| output(w, index, *frame, resolve_symname(*frame, &context), format)?; | ||
| if foreach_symbol_fileline(*frame, |file, line| { | ||
| output_fileline(w, file, line, format) | ||
| }, &context)? { |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Dec 16, 2016
Member
The indentation here was a little confusing for me at first, perhaps the result of this could be bound to a let?
This comment has been minimized.
This comment has been minimized.
| } else if &x == "full" { | ||
| Some(PrintFormat::Full) | ||
| } else if ["short", "yes", "1"].iter().any(|val| &x == *val) { | ||
| Some(PrintFormat::Short) |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Dec 16, 2016
Member
I believe we can just remove this, right? (always fall through below)
alexcrichton
reviewed
Dec 19, 2016
| match cx.last_error { | ||
| Some(err) => panic!("Fatal error while unwinding: {}", | ||
| err), | ||
| None => panic!("Fatal error while unwinding"), |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Dec 19, 2016
Member
Er so to clarify, we definitely can't panic while producing a backtrace. This is called during a panic, which would be a recursive panic, which we don't want to abort on. If we do end up handling this then we need to punt the error upwards and let the caller deal with it.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Yamakaky
Dec 20, 2016
Author
Contributor
In fact, if I replace it with a bool we will loose the io::Error on https://github.com/Yamakaky/rust/blob/better-backtrace/src/libstd/sys/windows/backtrace/mod.rs#L76. Is that what we want?
This comment has been minimized.
This comment has been minimized.
Yamakaky
Dec 20, 2016
Author
Contributor
An alternative would be to return the already-decoded frames on error. Are you OK with that?
This comment has been minimized.
This comment has been minimized.
alexcrichton
reviewed
Dec 19, 2016
| match print(cx.writer, cx.idx, ip, symaddr) { | ||
| Ok(()) => {} | ||
| Err(e) => { cx.last_error = Some(e); } | ||
| if cx.last_error.is_some() { |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Dec 19, 2016
Member
I think this field is no longer necessary, right? It was only used when we could generate an error during a backtrace by calling write!, but with that no longer happening the body of this function is infallible.
alexcrichton
reviewed
Dec 21, 2016
| } | ||
| _ => Ok(()), | ||
| _ => unreachable!("\"{:?}\" should not be triggered", | ||
| result_unwind), |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Yamakaky
Dec 21, 2016
Author
Contributor
I see a few possibilities:
- same handling than for
_URC_FATAL_PHASE1_ERROR - above + print an error message
- create a new error type and return it using io::ErrorKind::Other
This comment has been minimized.
This comment has been minimized.
alexcrichton
Dec 26, 2016
Member
Er sorry, what I mean is that this must not panic. It should return an error of some kind (Err(()) is fine) so the caller can deal with the failure.
Yamakaky
force-pushed the
Yamakaky:better-backtrace
branch
from
86a862d
to
52bed53
Feb 27, 2017
This comment has been minimized.
This comment has been minimized.
|
@bors r+ |
This comment has been minimized.
This comment has been minimized.
|
|
This comment has been minimized.
This comment has been minimized.
bors
added a commit
that referenced
this pull request
Feb 27, 2017
This comment has been minimized.
This comment has been minimized.
|
|
This comment has been minimized.
This comment has been minimized.
|
Both Travis and Appveyor failed.
Appveyor on the "classic" one (WTF, I tested this locally in this exact configuration)
Time to drop both |
This comment has been minimized.
This comment has been minimized.
|
I'm thinking: what if we captured a stacktrace juste before the main, then substract it to the backtrace on panic? That way, at least the bottom of the trace would be correct all the time. Maybe we could do the same for the top, in Not sure how it would play with inlining. |
petrochenkov
reviewed
Feb 27, 2017
| assert!(!s.contains(symbol), | ||
| "{} should be removed from the backtrace\n{}", | ||
| symbol, s); | ||
| } | ||
| assert!(s.contains(" 0:"), "the frame number should start at 0"); | ||
|
|
||
| // Only on linux for _start and __libc_start_main |
This comment has been minimized.
This comment has been minimized.
Yamakaky
force-pushed the
Yamakaky:better-backtrace
branch
from
9cfd356
to
6398b20
Feb 27, 2017
This comment has been minimized.
This comment has been minimized.
|
@Yamakaky |
This comment has been minimized.
This comment has been minimized.
|
@bors r+ |
This comment has been minimized.
This comment has been minimized.
|
|
This comment has been minimized.
This comment has been minimized.
bors
added a commit
that referenced
this pull request
Feb 27, 2017
This comment has been minimized.
This comment has been minimized.
|
|
bors
merged commit 6398b20
into
rust-lang:master
Feb 27, 2017
This comment has been minimized.
This comment has been minimized.
|
\o/ |
Yamakaky
deleted the
Yamakaky:better-backtrace
branch
Feb 27, 2017
This comment has been minimized.
This comment has been minimized.
|
Does this also affect the output of https://crates.io/crates/backtrace or should we file a separate issue in that repository? |
This comment has been minimized.
This comment has been minimized.
|
You should open an issue. |
Yamakaky commentedDec 4, 2016
•
edited
Fixes #37783.
Done:
RUST_BACKTRACE=fullprints all the informations (current behaviour)RUST_BACKTRACE=(short|yes)is the default and does:::hfabe6541873at the end of the symbolsRUST_BACKTRACE=(0|no)disables the backtrace.RUST_BACKTRACE=<everything else>is equivalent toshortforbackward compatibility.
Removed, TODO in a new PR:
Example of short backtrace:
Short:
Full: