Skip to content

Commit 4b8f2be

Browse files
authored
Include source column numbers, where available (#367)
* Provide access to source column numbers of symbol locations, in addition to filename and line numbers (where available - currently only gimli). * Print column numbers in backtraces, if available. * cargo fmt * Fixed breaking-change to public API, by delegating to new print_raw_with_column (as suggested by @alexcrichton in review #367 (comment)) * Added tests for column numbers * Reflect actual conditional compilation logic in smoke test guards
1 parent 7938ddc commit 4b8f2be

File tree

8 files changed

+144
-11
lines changed

8 files changed

+144
-11
lines changed

src/capture.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ pub struct BacktraceSymbol {
9494
addr: Option<usize>,
9595
filename: Option<PathBuf>,
9696
lineno: Option<u32>,
97+
colno: Option<u32>,
9798
}
9899

99100
impl Backtrace {
@@ -213,6 +214,7 @@ impl Backtrace {
213214
addr: symbol.addr().map(|a| a as usize),
214215
filename: symbol.filename().map(|m| m.to_owned()),
215216
lineno: symbol.lineno(),
217+
colno: symbol.colno(),
216218
});
217219
};
218220
match frame.frame {
@@ -322,6 +324,16 @@ impl BacktraceSymbol {
322324
pub fn lineno(&self) -> Option<u32> {
323325
self.lineno
324326
}
327+
328+
/// Same as `Symbol::colno`
329+
///
330+
/// # Required features
331+
///
332+
/// This function requires the `std` feature of the `backtrace` crate to be
333+
/// enabled, and the `std` feature is enabled by default.
334+
pub fn colno(&self) -> Option<u32> {
335+
self.colno
336+
}
325337
}
326338

327339
impl fmt::Debug for Backtrace {
@@ -383,6 +395,7 @@ impl fmt::Debug for BacktraceSymbol {
383395
.field("addr", &self.addr())
384396
.field("filename", &self.filename())
385397
.field("lineno", &self.lineno())
398+
.field("colno", &self.colno())
386399
.finish()
387400
}
388401
}

src/print.rs

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ impl BacktraceFrameFmt<'_, '_, '_> {
130130
frame: &BacktraceFrame,
131131
symbol: &BacktraceSymbol,
132132
) -> fmt::Result {
133-
self.print_raw(
133+
self.print_raw_with_column(
134134
frame.ip(),
135135
symbol.name(),
136136
// TODO: this isn't great that we don't end up printing anything
@@ -140,18 +140,20 @@ impl BacktraceFrameFmt<'_, '_, '_> {
140140
.filename()
141141
.and_then(|p| Some(BytesOrWideString::Bytes(p.to_str()?.as_bytes()))),
142142
symbol.lineno(),
143+
symbol.colno(),
143144
)?;
144145
Ok(())
145146
}
146147

147148
/// Prints a raw traced `Frame` and `Symbol`, typically from within the raw
148149
/// callbacks of this crate.
149150
pub fn symbol(&mut self, frame: &Frame, symbol: &super::Symbol) -> fmt::Result {
150-
self.print_raw(
151+
self.print_raw_with_column(
151152
frame.ip(),
152153
symbol.name(),
153154
symbol.filename_raw(),
154155
symbol.lineno(),
156+
symbol.colno(),
155157
)?;
156158
Ok(())
157159
}
@@ -174,7 +176,32 @@ impl BacktraceFrameFmt<'_, '_, '_> {
174176
if cfg!(target_os = "fuchsia") {
175177
self.print_raw_fuchsia(frame_ip)?;
176178
} else {
177-
self.print_raw_generic(frame_ip, symbol_name, filename, lineno)?;
179+
self.print_raw_generic(frame_ip, symbol_name, filename, lineno, None)?;
180+
}
181+
self.symbol_index += 1;
182+
Ok(())
183+
}
184+
185+
/// Adds a raw frame to the backtrace output, including column information.
186+
///
187+
/// This method, like the previous, takes the raw arguments in case
188+
/// they're being source from different locations. Note that this may be
189+
/// called multiple times for one frame.
190+
pub fn print_raw_with_column(
191+
&mut self,
192+
frame_ip: *mut c_void,
193+
symbol_name: Option<SymbolName<'_>>,
194+
filename: Option<BytesOrWideString<'_>>,
195+
lineno: Option<u32>,
196+
colno: Option<u32>,
197+
) -> fmt::Result {
198+
// Fuchsia is unable to symbolize within a process so it has a special
199+
// format which can be used to symbolize later. Print that instead of
200+
// printing addresses in our own format here.
201+
if cfg!(target_os = "fuchsia") {
202+
self.print_raw_fuchsia(frame_ip)?;
203+
} else {
204+
self.print_raw_generic(frame_ip, symbol_name, filename, lineno, colno)?;
178205
}
179206
self.symbol_index += 1;
180207
Ok(())
@@ -187,6 +214,7 @@ impl BacktraceFrameFmt<'_, '_, '_> {
187214
symbol_name: Option<SymbolName<'_>>,
188215
filename: Option<BytesOrWideString<'_>>,
189216
lineno: Option<u32>,
217+
colno: Option<u32>,
190218
) -> fmt::Result {
191219
// No need to print "null" frames, it basically just means that the
192220
// system backtrace was a bit eager to trace back super far.
@@ -232,13 +260,18 @@ impl BacktraceFrameFmt<'_, '_, '_> {
232260

233261
// And last up, print out the filename/line number if they're available.
234262
if let (Some(file), Some(line)) = (filename, lineno) {
235-
self.print_fileline(file, line)?;
263+
self.print_fileline(file, line, colno)?;
236264
}
237265

238266
Ok(())
239267
}
240268

241-
fn print_fileline(&mut self, file: BytesOrWideString<'_>, line: u32) -> fmt::Result {
269+
fn print_fileline(
270+
&mut self,
271+
file: BytesOrWideString<'_>,
272+
line: u32,
273+
colno: Option<u32>,
274+
) -> fmt::Result {
242275
// Filename/line are printed on lines under the symbol name, so print
243276
// some appropriate whitespace to sort of right-align ourselves.
244277
if let PrintFmt::Full = self.fmt.format {
@@ -249,7 +282,14 @@ impl BacktraceFrameFmt<'_, '_, '_> {
249282
// Delegate to our internal callback to print the filename and then
250283
// print out the line number.
251284
(self.fmt.print_path)(self.fmt.fmt, file)?;
252-
write!(self.fmt.fmt, ":{}\n", line)?;
285+
write!(self.fmt.fmt, ":{}", line)?;
286+
287+
// Add column number, if available.
288+
if let Some(colno) = colno {
289+
write!(self.fmt.fmt, ":{}", colno)?;
290+
}
291+
292+
write!(self.fmt.fmt, "\n")?;
253293
Ok(())
254294
}
255295

src/symbolize/dbghelp.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ impl Symbol<'_> {
6262
.map(|slice| unsafe { BytesOrWideString::Wide(&*slice) })
6363
}
6464

65+
pub fn colno(&self) -> Option<u32> {
66+
None
67+
}
68+
6569
pub fn lineno(&self) -> Option<u32> {
6670
self.line
6771
}

src/symbolize/gimli.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -682,4 +682,11 @@ impl Symbol<'_> {
682682
Symbol::Symtab { .. } => None,
683683
}
684684
}
685+
686+
pub fn colno(&self) -> Option<u32> {
687+
match self {
688+
Symbol::Frame { location, .. } => location.as_ref()?.column,
689+
Symbol::Symtab { .. } => None,
690+
}
691+
}
685692
}

src/symbolize/libbacktrace.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,10 @@ impl Symbol<'_> {
149149
Symbol::Pcinfo { lineno, .. } => Some(lineno as u32),
150150
}
151151
}
152+
153+
pub fn colno(&self) -> Option<u32> {
154+
None
155+
}
152156
}
153157

154158
extern "C" fn error_cb(_data: *mut c_void, _msg: *const c_char, _errnum: c_int) {

src/symbolize/mod.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,14 @@ impl Symbol {
219219
self.inner.filename_raw()
220220
}
221221

222+
/// Returns the column number for where this symbol is currently executing.
223+
///
224+
/// Only gimli currently provides a value here and even then only if `filename`
225+
/// returns `Some`, and so it is then consequently subject to similar caveats.
226+
pub fn colno(&self) -> Option<u32> {
227+
self.inner.colno()
228+
}
229+
222230
/// Returns the line number for where this symbol is currently executing.
223231
///
224232
/// This return value is typically `Some` if `filename` returns `Some`, and
@@ -229,8 +237,8 @@ impl Symbol {
229237

230238
/// Returns the file name where this function was defined.
231239
///
232-
/// This is currently only available when libbacktrace is being used (e.g.
233-
/// unix platforms other than OSX) and when a binary is compiled with
240+
/// This is currently only available when libbacktrace or gimli is being
241+
/// used (e.g. unix platforms other) and when a binary is compiled with
234242
/// debuginfo. If neither of these conditions is met then this will likely
235243
/// return `None`.
236244
///

src/symbolize/noop.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,8 @@ impl Symbol<'_> {
3232
pub fn lineno(&self) -> Option<u32> {
3333
None
3434
}
35+
36+
pub fn colno(&self) -> Option<u32> {
37+
None
38+
}
3539
}

tests/smoke.rs

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,36 @@
11
use backtrace::Frame;
22
use std::thread;
33

4-
static LIBBACKTRACE: bool = cfg!(feature = "libbacktrace") && !cfg!(target_os = "fuchsia");
5-
static GIMLI_SYMBOLIZE: bool = cfg!(all(feature = "gimli-symbolize", unix, target_os = "linux"));
4+
// Reflects the conditional compilation logic at end of src/symbolize/mod.rs
5+
static NOOP: bool = cfg!(miri);
6+
static DBGHELP: bool = !NOOP
7+
&& cfg!(all(
8+
windows,
9+
target_env = "msvc",
10+
not(target_vendor = "uwp")
11+
));
12+
static LIBBACKTRACE: bool = !NOOP
13+
&& !DBGHELP
14+
&& cfg!(all(
15+
feature = "libbacktrace",
16+
any(
17+
unix,
18+
all(windows, not(target_vendor = "uwp"), target_env = "gnu")
19+
),
20+
not(target_os = "fuchsia"),
21+
not(target_os = "emscripten"),
22+
not(target_env = "uclibc"),
23+
not(target_env = "libnx"),
24+
));
25+
static GIMLI_SYMBOLIZE: bool = !NOOP
26+
&& !DBGHELP
27+
&& !LIBBACKTRACE
28+
&& cfg!(all(
29+
feature = "gimli-symbolize",
30+
any(unix, windows),
31+
not(target_vendor = "uwp"),
32+
not(target_os = "emscripten"),
33+
));
634

735
#[test]
836
// FIXME: shouldn't ignore this test on i686-msvc, unsure why it's failing
@@ -46,34 +74,39 @@ fn smoke_test_frames() {
4674
"frame_4",
4775
"tests/smoke.rs",
4876
start_line + 6,
77+
9,
4978
);
5079
assert_frame(
5180
frames.next().unwrap(),
5281
frame_3 as usize,
5382
"frame_3",
5483
"tests/smoke.rs",
5584
start_line + 3,
85+
52,
5686
);
5787
assert_frame(
5888
frames.next().unwrap(),
5989
frame_2 as usize,
6090
"frame_2",
6191
"tests/smoke.rs",
6292
start_line + 2,
93+
52,
6394
);
6495
assert_frame(
6596
frames.next().unwrap(),
6697
frame_1 as usize,
6798
"frame_1",
6899
"tests/smoke.rs",
69100
start_line + 1,
101+
52,
70102
);
71103
assert_frame(
72104
frames.next().unwrap(),
73105
smoke_test_frames as usize,
74106
"smoke_test_frames",
75107
"",
76108
0,
109+
0,
77110
);
78111
}
79112

@@ -83,6 +116,7 @@ fn smoke_test_frames() {
83116
expected_name: &str,
84117
expected_file: &str,
85118
expected_line: u32,
119+
expected_col: u32,
86120
) {
87121
backtrace::resolve_frame(frame, |sym| {
88122
print!("symbol ip:{:?} address:{:?} ", frame.ip(), frame.symbol_address());
@@ -95,6 +129,9 @@ fn smoke_test_frames() {
95129
if let Some(lineno) = sym.lineno() {
96130
print!("lineno:{} ", lineno);
97131
}
132+
if let Some(colno) = sym.colno() {
133+
print!("colno:{} ", colno);
134+
}
98135
println!();
99136
});
100137

@@ -103,12 +140,13 @@ fn smoke_test_frames() {
103140
assert!(ip >= sym);
104141
assert!(
105142
sym >= actual_fn_pointer,
106-
"{:?} < {:?} ({} {}:{})",
143+
"{:?} < {:?} ({} {}:{}:{})",
107144
sym as *const usize,
108145
actual_fn_pointer as *const usize,
109146
expected_name,
110147
expected_file,
111148
expected_line,
149+
expected_col,
112150
);
113151

114152
// windows dbghelp is *quite* liberal (and wrong) in many of its reports
@@ -121,15 +159,18 @@ fn smoke_test_frames() {
121159

122160
let mut resolved = 0;
123161
let can_resolve = LIBBACKTRACE || GIMLI_SYMBOLIZE;
162+
let can_resolve_cols = GIMLI_SYMBOLIZE;
124163

125164
let mut name = None;
126165
let mut addr = None;
166+
let mut col = None;
127167
let mut line = None;
128168
let mut file = None;
129169
backtrace::resolve_frame(frame, |sym| {
130170
resolved += 1;
131171
name = sym.name().map(|v| v.to_string());
132172
addr = sym.addr();
173+
col = sym.colno();
133174
line = sym.lineno();
134175
file = sym.filename().map(|v| v.to_path_buf());
135176
});
@@ -179,6 +220,18 @@ fn smoke_test_frames() {
179220
expected_line
180221
);
181222
}
223+
if can_resolve_cols {
224+
let col = col.expect("didn't find a column number");
225+
if expected_col != 0 {
226+
assert!(
227+
col == expected_col,
228+
"bad column number on frame for `{}`: {} != {}",
229+
expected_name,
230+
col,
231+
expected_col
232+
);
233+
}
234+
}
182235
}
183236
}
184237
}

0 commit comments

Comments
 (0)