Skip to content

Commit 4ad7d93

Browse files
[feat] Backtrace 现在显示了函数参数的实际值:
----------------- [BACKTRACE] #1 fn=...bt_depth3(msg="hello_os", flag=true) at src/main.rs:48 [BACKTRACE] #2 fn=...bt_depth2(count=42, label="hello_os") at src/main.rs:55 [BACKTRACE] #3 fn=...bt_depth1(id=42, name="hello_os", value=-1) at src/main.rs:63 实现原理 整个方案分编译期和运行时两阶段: 编译期(build.rs) 从上一次构建的 ELF DWARF 信息中提取每个函数形参的三项关键数据: 栈偏移 — 解析 DW_AT_location 中的 DW_OP_fbreg(N) 表达式(SLEB128 编码),得到参数相对于帧指针 s0 的偏移量 字节大小 — 通过 DW_AT_type 引用找到类型 DIE,读取 DW_AT_byte_size 类型标签 — 根据 DW_AT_encoding(signed/unsigned/boolean)和结构体名称(&str)分类为五种 kind 这些信息生成为 const FUNC_PARAM_LOCS: &[(u64, &str, i16, u8, u8)] 数组。 运行时(symtab_resolve.rs) 对每一帧: stackwalk.rs 将 frame.fp(被标识函数自身的 s0 值)传给 print_fn_for_ra 在 FUNC_PARAM_LOCS 中查找该函数的所有参数 通过 fp + fbreg_offset 计算参数在栈上的地址,直接读取原始字节 按类型格式化:整数显示十进制,bool 显示 true/false,&str 解引用后显示字符串内容
1 parent ecd7ced commit 4ad7d93

4 files changed

Lines changed: 380 additions & 66 deletions

File tree

tg-rcore-tutorial-ch1-show/build.rs

Lines changed: 172 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
//!
33
//! 1. ELF `.symtab` 函数符号(地址 + mangled name)
44
//! 2. DWARF 行号表(每条指令地址 → 源文件 + 行号)
5-
//! 3. 函数形参名(`DW_TAG_formal_parameter` / `DW_AT_name`)
5+
//! 3. 函数形参的栈偏移 + 类型(`DW_AT_location` + `DW_AT_type`)
66
//!
77
//! 结果嵌入为 `const` 数组,供运行时栈回溯逐帧显示
8-
//! `fn=name(params) at file:line`。
8+
//! `fn=name(param=value, ...) at file:line`。
99
1010
fn main() {
1111
use std::{env, fs, path::PathBuf};
@@ -132,10 +132,19 @@ fn extract_func_symbols(data: &[u8]) -> Vec<(u64, u64, String)> {
132132
const TEXT_BASE: u64 = 0x80200000;
133133
const TEXT_LIMIT: u64 = 0x81000000;
134134

135+
struct ParamLoc {
136+
func_addr: u64,
137+
name: String,
138+
fbreg_offset: i16,
139+
byte_size: u8,
140+
/// 0=unsigned, 1=signed, 2=bool, 3=str_ref, 4=raw_hex
141+
kind: u8,
142+
}
143+
135144
struct DwarfInfo {
136145
line_table: Vec<(u32, u16, u32)>,
137146
file_paths: Vec<String>,
138-
func_params: std::collections::HashMap<u64, Vec<String>>,
147+
param_locs: Vec<ParamLoc>,
139148
}
140149

141150
fn extract_dwarf_info(data: &[u8]) -> DwarfInfo {
@@ -145,7 +154,7 @@ fn extract_dwarf_info(data: &[u8]) -> DwarfInfo {
145154
let empty = DwarfInfo {
146155
line_table: Vec::new(),
147156
file_paths: Vec::new(),
148-
func_params: std::collections::HashMap::new(),
157+
param_locs: Vec::new(),
149158
};
150159
if data.len() < 64 {
151160
return empty;
@@ -173,15 +182,13 @@ fn extract_dwarf_info(data: &[u8]) -> DwarfInfo {
173182
Err(_) => return empty,
174183
};
175184

176-
// Extract function parameters first (borrows dwarf)
177-
let func_params = extract_func_params(&dwarf);
185+
let param_locs = extract_param_locs(&dwarf);
178186

179-
// Create addr2line Context (takes ownership of dwarf)
180187
let ctx = match addr2line::Context::from_dwarf(dwarf) {
181188
Ok(c) => c,
182189
Err(_) => {
183190
return DwarfInfo {
184-
func_params,
191+
param_locs,
185192
..empty
186193
}
187194
}
@@ -226,18 +233,18 @@ fn extract_dwarf_info(data: &[u8]) -> DwarfInfo {
226233
DwarfInfo {
227234
line_table: line_entries,
228235
file_paths,
229-
func_params,
236+
param_locs,
230237
}
231238
}
232239

233-
fn extract_func_params(
234-
dwarf: &addr2line::gimli::Dwarf<
235-
addr2line::gimli::EndianSlice<'_, addr2line::gimli::RunTimeEndian>,
236-
>,
237-
) -> std::collections::HashMap<u64, Vec<String>> {
240+
// ===== DWARF parameter location + type extraction =====
241+
242+
type DwarfSlice<'a> = addr2line::gimli::EndianSlice<'a, addr2line::gimli::RunTimeEndian>;
243+
244+
fn extract_param_locs(dwarf: &addr2line::gimli::Dwarf<DwarfSlice<'_>>) -> Vec<ParamLoc> {
238245
use addr2line::gimli;
239246

240-
let mut result = std::collections::HashMap::new();
247+
let mut result = Vec::new();
241248

242249
let mut units = dwarf.units();
243250
while let Ok(Some(header)) = units.next() {
@@ -248,19 +255,14 @@ fn extract_func_params(
248255

249256
let mut entries = unit.entries();
250257
let mut current_func: Option<u64> = None;
251-
let mut current_params: Vec<String> = Vec::new();
252258
let mut depth: isize = 0;
253259
let mut func_depth: isize = -1;
254260

255261
while let Ok(Some((delta, entry))) = entries.next_dfs() {
256262
depth += delta;
257263

258264
if current_func.is_some() && depth <= func_depth {
259-
if let Some(addr) = current_func.take() {
260-
if !current_params.is_empty() {
261-
result.insert(addr, std::mem::take(&mut current_params));
262-
}
263-
}
265+
current_func = None;
264266
func_depth = -1;
265267
}
266268

@@ -277,35 +279,156 @@ fn extract_func_params(
277279
if let Some(a) = addr {
278280
if a >= TEXT_BASE && a < TEXT_LIMIT {
279281
current_func = Some(a);
280-
current_params.clear();
281282
func_depth = depth;
282283
}
283284
}
284285
}
285286
gimli::DW_TAG_formal_parameter
286287
if current_func.is_some() && depth == func_depth + 1 =>
287288
{
288-
if let Ok(Some(attr)) = entry.attr(gimli::DW_AT_name) {
289-
if let Ok(name) = dwarf.attr_string(&unit, attr.value()) {
290-
if let Ok(s) = name.to_string() {
291-
let s = s.to_string();
292-
if !s.is_empty() && !s.starts_with("__") && s != "self" {
293-
current_params.push(s);
294-
}
289+
let func_addr = current_func.unwrap();
290+
291+
let name = entry
292+
.attr(gimli::DW_AT_name)
293+
.ok()
294+
.flatten()
295+
.and_then(|a| dwarf.attr_string(&unit, a.value()).ok())
296+
.and_then(|s| s.to_string().ok().map(|x| x.to_string()))
297+
.unwrap_or_default();
298+
if name.is_empty() || name.starts_with("__") || name == "self" {
299+
continue;
300+
}
301+
302+
let fbreg_offset = entry
303+
.attr_value(gimli::DW_AT_location)
304+
.ok()
305+
.flatten()
306+
.and_then(|v| match v {
307+
gimli::AttributeValue::Exprloc(expr) => {
308+
parse_fbreg_offset(expr.0.slice())
295309
}
310+
_ => None,
311+
});
312+
let fbreg_offset = match fbreg_offset {
313+
Some(off) if off >= i16::MIN as i64 && off <= i16::MAX as i64 => {
314+
off as i16
296315
}
297-
}
316+
_ => continue,
317+
};
318+
319+
let (byte_size, kind) = entry
320+
.attr_value(gimli::DW_AT_type)
321+
.ok()
322+
.flatten()
323+
.and_then(|v| match v {
324+
gimli::AttributeValue::UnitRef(offset) => {
325+
resolve_type(&unit, dwarf, offset)
326+
}
327+
_ => None,
328+
})
329+
.unwrap_or((8, 4));
330+
331+
result.push(ParamLoc {
332+
func_addr,
333+
name,
334+
fbreg_offset,
335+
byte_size,
336+
kind,
337+
});
298338
}
299339
_ => {}
300340
}
301341
}
302-
if let Some(addr) = current_func {
303-
if !current_params.is_empty() {
304-
result.insert(addr, current_params);
342+
}
343+
result.sort_by(|a, b| a.func_addr.cmp(&b.func_addr).then(a.fbreg_offset.cmp(&b.fbreg_offset)));
344+
result
345+
}
346+
347+
/// Parse a `DW_OP_fbreg(N)` DWARF expression. Returns the SLEB128-decoded offset N.
348+
fn parse_fbreg_offset(bytes: &[u8]) -> Option<i64> {
349+
if bytes.is_empty() || bytes[0] != 0x91 {
350+
return None;
351+
}
352+
let mut result: i64 = 0;
353+
let mut shift: u32 = 0;
354+
for &byte in &bytes[1..] {
355+
result |= ((byte & 0x7f) as i64) << shift;
356+
shift += 7;
357+
if byte & 0x80 == 0 {
358+
if shift < 64 && (byte & 0x40) != 0 {
359+
result |= !0i64 << shift;
305360
}
361+
return Some(result);
306362
}
307363
}
308-
result
364+
None
365+
}
366+
367+
/// Resolve a DWARF type DIE to (byte_size, kind).
368+
fn resolve_type(
369+
unit: &addr2line::gimli::Unit<DwarfSlice<'_>>,
370+
dwarf: &addr2line::gimli::Dwarf<DwarfSlice<'_>>,
371+
offset: addr2line::gimli::UnitOffset<usize>,
372+
) -> Option<(u8, u8)> {
373+
use addr2line::gimli;
374+
375+
let mut cursor = match unit.entries_at_offset(offset) {
376+
Ok(c) => c,
377+
Err(_) => return None,
378+
};
379+
match cursor.next_entry() {
380+
Ok(Some(())) => {}
381+
_ => return None,
382+
}
383+
let entry = cursor.current()?;
384+
385+
match entry.tag() {
386+
gimli::DW_TAG_base_type => {
387+
let byte_size = entry
388+
.attr_value(gimli::DW_AT_byte_size)
389+
.ok()
390+
.flatten()
391+
.and_then(|v: gimli::AttributeValue<DwarfSlice<'_>>| v.udata_value())
392+
.unwrap_or(8) as u8;
393+
let kind = entry
394+
.attr_value(gimli::DW_AT_encoding)
395+
.ok()
396+
.flatten()
397+
.and_then(|v: gimli::AttributeValue<DwarfSlice<'_>>| match v {
398+
gimli::AttributeValue::Encoding(e) => Some(e),
399+
_ => None,
400+
})
401+
.map(|e| match e {
402+
gimli::DW_ATE_boolean => 2u8,
403+
gimli::DW_ATE_signed | gimli::DW_ATE_signed_char => 1,
404+
_ => 0,
405+
})
406+
.unwrap_or(0);
407+
Some((byte_size, kind))
408+
}
409+
gimli::DW_TAG_structure_type => {
410+
let name = entry
411+
.attr(gimli::DW_AT_name)
412+
.ok()
413+
.flatten()
414+
.and_then(|a: gimli::Attribute<DwarfSlice<'_>>| {
415+
dwarf.attr_string(unit, a.value()).ok()
416+
})
417+
.and_then(|s: DwarfSlice<'_>| s.to_string().ok().map(|x| x.to_string()));
418+
if name.as_deref() == Some("&str") {
419+
Some((16, 3))
420+
} else {
421+
let byte_size = entry
422+
.attr_value(gimli::DW_AT_byte_size)
423+
.ok()
424+
.flatten()
425+
.and_then(|v: gimli::AttributeValue<DwarfSlice<'_>>| v.udata_value())
426+
.unwrap_or(8) as u8;
427+
Some((byte_size, 4))
428+
}
429+
}
430+
_ => Some((8, 4)),
431+
}
309432
}
310433

311434
/// Strip build-dir prefixes to produce short display paths like `src/main.rs`.
@@ -370,14 +493,21 @@ fn write_generated_rs(
370493
}
371494
writeln!(c, "];").unwrap();
372495

373-
// --- FUNC_PARAMS: (func_addr, "p1,p2,...") ---
374-
writeln!(c, "const FUNC_PARAMS: &[(u64, &str)] = &[").unwrap();
375-
let mut param_entries: Vec<_> = dwarf.func_params.iter().collect();
376-
param_entries.sort_by_key(|(a, _)| *a);
377-
for (addr, params) in &param_entries {
378-
let joined = params.join(",");
379-
let esc = joined.replace('\\', "\\\\").replace('"', "\\\"");
380-
writeln!(c, " (0x{addr:x}, \"{esc}\"),").unwrap();
496+
// --- FUNC_PARAM_LOCS: (func_addr, name, fbreg_offset, byte_size, kind) ---
497+
// kind: 0=unsigned 1=signed 2=bool 3=str_ref 4=raw_hex
498+
writeln!(
499+
c,
500+
"const FUNC_PARAM_LOCS: &[(u64, &str, i16, u8, u8)] = &["
501+
)
502+
.unwrap();
503+
for p in &dwarf.param_locs {
504+
let esc = p.name.replace('\\', "\\\\").replace('"', "\\\"");
505+
writeln!(
506+
c,
507+
" (0x{:x}, \"{esc}\", {}, {}, {}),",
508+
p.func_addr, p.fbreg_offset, p.byte_size, p.kind
509+
)
510+
.unwrap();
381511
}
382512
writeln!(c, "];").unwrap();
383513

tg-rcore-tutorial-ch1-show/src/stackwalk.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ pub fn print_backtrace() {
117117
put_usize_hex(frame.ip);
118118
put_bytes(b"\n");
119119

120-
crate::symtab_resolve::print_fn_for_ra(frame.ip);
120+
crate::symtab_resolve::print_fn_for_ra(frame.ip, frame.fp);
121121

122122
if let Some(limit) = fp.checked_add(8 * 1024 * 1024)
123123
&& frame.fp >= limit

0 commit comments

Comments
 (0)