rust-lang / backtrace-rs

Backtraces in Rust

Home Page:https://docs.rs/backtrace

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

resolve is off by one

DzenanJupic opened this issue · comments

It appears, that resolve doesn't work correctly on Windows. When resolving function pointer addresses, the resulting Symbol is incorrect. Everything works fine when calling ptr.add(1) first.

Environment

Windows 10 Pro (10.0.19042 Build 19042)
backtrace = "0.3.64"
rustc 1.61.0-nightly (4ce374923 2022-02-28)
binary: rustc
commit-hash: 4ce3749235fc31d15ebd444b038a9877e8c700d7
commit-date: 2022-02-28
host: x86_64-pc-windows-msvc
release: 1.61.0-nightly
LLVM version: 14.0.0

Reproduce

# Cargo.toml
[package]
name = "temp"
version = "0.1.0"
edition = "2021"


[dependencies]
backtrace = "0.3.64"
// src/main.rs
fn main() {
    backtrace::resolve(test as *mut std::os::raw::c_void, |symbol| {
        println!("{symbol:?}");
    });
    backtrace::resolve(unsafe { (test as *mut std::os::raw::c_void).add(1) }, |symbol| {
        println!("{symbol:?}");
    });
}

fn test() {}

cargo run:

Symbol { name: "temp::main::closure$1", addr: 0x7ff7ad8d1310 }
Symbol { name: "temp::test", addr: 0x7ff7ad8d13b0, filename: "C:\\Users\\info\\Code\\Rust\\temp\\src\\main.rs", lineno: 11 }

See

// IP values from stack frames are typically (always?) the instruction
// *after* the call that's the actual stack trace. Symbolizing this on
// causes the filename/line number to be one ahead and perhaps into
// the void if it's near the end of the function.
//
// This appears to basically always be the case on all platforms, so we always
// subtract one from a resolved ip to resolve it to the previous call
// instruction instead of the instruction being returned to.
//
// Ideally we would not do this. Ideally we would require callers of the
// `resolve` APIs here to manually do the -1 and account that they want location
// information for the *previous* instruction, not the current. Ideally we'd
// also expose on `Frame` if we are indeed the address of the next instruction
// or the current.
//
// For now though this is a pretty niche concern so we just internally always
// subtract one. Consumers should keep working and getting pretty good results,
// so we should be good enough.
fn adjust_ip(a: *mut c_void) -> *mut c_void {
if a.is_null() {
a
} else {
(a as usize - 1) as *mut c_void
}
}
1 is substracted from the address as the return address of a call frame is just after the call instruction. We want the information of the call instruction rather than the next instruction.

I see, thank you. It would be great if that could be documented!

Though, when this only applies to the IP of a Frame, why is this adjustment also used for addresses? Wouldn't it make sense to only apply this in resolve_frame?

This isn't something that should be worked around, if the ips coming out of this crate don't resolve to the right function that's a bug in this crate. It may be the case that Windows is already doing the subtraction for us to have the reported ip land on the call instruction instead of the instruction after the call instruction, unlike libunwind, which would mean that the -1 logic isn't required on Windows. I don't know dbghelp on Windows enough though to know if this is the case.

It would, nevertheless, be great if that could be documented.