2024-04-02 02:30:36 +11:00
|
|
|
use std::{
|
|
|
|
borrow::Cow,
|
|
|
|
fs::{self, File},
|
|
|
|
io::Read,
|
|
|
|
path::PathBuf,
|
|
|
|
};
|
2024-04-01 23:26:48 +11:00
|
|
|
|
2024-04-02 00:05:22 +11:00
|
|
|
use addr2line::{gimli, object};
|
2024-04-01 23:26:48 +11:00
|
|
|
use clap::Parser;
|
2024-04-02 00:10:23 +11:00
|
|
|
use colored::Colorize;
|
2024-04-01 23:26:48 +11:00
|
|
|
|
|
|
|
#[derive(Parser, Debug)]
|
|
|
|
#[command(version, about, long_about = None)]
|
|
|
|
struct Args {
|
|
|
|
/// The filename of the elf file
|
|
|
|
elf_path: PathBuf,
|
|
|
|
|
|
|
|
/// The output of agb's dump
|
|
|
|
dump: String,
|
2024-04-02 02:30:36 +11:00
|
|
|
|
|
|
|
/// Whether the actual code should be shown
|
|
|
|
#[arg(short, long)]
|
|
|
|
code: bool,
|
2024-04-01 23:26:48 +11:00
|
|
|
}
|
|
|
|
|
2024-04-01 23:43:08 +11:00
|
|
|
struct Location {
|
|
|
|
filename: String,
|
|
|
|
line: u32,
|
2024-04-02 02:30:36 +11:00
|
|
|
col: u32,
|
2024-04-01 23:43:08 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Location {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
filename: "??".to_string(),
|
|
|
|
line: 0,
|
2024-04-02 02:30:36 +11:00
|
|
|
col: 0,
|
2024-04-01 23:43:08 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-01 23:26:48 +11:00
|
|
|
fn main() -> anyhow::Result<()> {
|
|
|
|
let cli = Args::parse();
|
|
|
|
|
|
|
|
let file = fs::read(cli.elf_path)?;
|
|
|
|
let object = object::File::parse(file.as_slice())?;
|
|
|
|
|
|
|
|
let ctx = addr2line::Context::new(&object)?;
|
|
|
|
|
2024-04-02 00:16:13 +11:00
|
|
|
for (i, address) in cli.dump.split('-').enumerate() {
|
2024-04-02 00:05:22 +11:00
|
|
|
let mut address = u64::from_str_radix(address, 16)?;
|
|
|
|
if address <= 0xFFFF {
|
|
|
|
address += 0x0800_0000;
|
|
|
|
}
|
|
|
|
|
2024-04-02 02:30:36 +11:00
|
|
|
print_address(&ctx, i, address, cli.code)?;
|
2024-04-02 00:05:22 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-04-02 00:16:13 +11:00
|
|
|
fn print_address(
|
|
|
|
ctx: &addr2line::Context<impl gimli::Reader>,
|
|
|
|
index: usize,
|
|
|
|
address: u64,
|
2024-04-02 02:30:36 +11:00
|
|
|
include_code: bool,
|
2024-04-02 00:16:13 +11:00
|
|
|
) -> anyhow::Result<()> {
|
2024-04-02 00:05:22 +11:00
|
|
|
let mut frames = ctx.find_frames(address).skip_all_loads()?;
|
2024-04-01 23:43:08 +11:00
|
|
|
|
2024-04-02 00:16:13 +11:00
|
|
|
let mut is_first = true;
|
|
|
|
|
2024-04-01 23:43:08 +11:00
|
|
|
while let Some(frame) = frames.next()? {
|
2024-04-02 02:30:36 +11:00
|
|
|
let function_name = if let Some(ref func) = frame.function {
|
2024-04-01 23:43:08 +11:00
|
|
|
func.demangle()?.into_owned()
|
|
|
|
} else {
|
|
|
|
"unknown function".to_string()
|
|
|
|
};
|
|
|
|
|
|
|
|
let location = frame
|
|
|
|
.location
|
2024-04-02 02:30:36 +11:00
|
|
|
.as_ref()
|
2024-04-01 23:43:08 +11:00
|
|
|
.map(|location| Location {
|
|
|
|
filename: location.file.unwrap_or("??").to_owned(),
|
|
|
|
line: location.line.unwrap_or(0),
|
2024-04-02 02:30:36 +11:00
|
|
|
col: location.column.unwrap_or(0),
|
2024-04-01 23:43:08 +11:00
|
|
|
})
|
|
|
|
.unwrap_or_default();
|
2024-04-01 23:26:48 +11:00
|
|
|
|
2024-04-02 02:36:05 +11:00
|
|
|
let is_interesting = is_interesting_function(&function_name, &location.filename);
|
|
|
|
let function_name_to_print = if is_interesting {
|
|
|
|
function_name.bold()
|
|
|
|
} else {
|
|
|
|
function_name.normal()
|
|
|
|
};
|
|
|
|
|
2024-04-02 00:16:13 +11:00
|
|
|
if is_first {
|
2024-04-02 02:36:05 +11:00
|
|
|
print!("{index}:\t{function_name_to_print}");
|
2024-04-02 00:16:13 +11:00
|
|
|
} else {
|
2024-04-02 02:36:05 +11:00
|
|
|
print!("\t(inlined by) {function_name_to_print}");
|
2024-04-02 00:16:13 +11:00
|
|
|
}
|
|
|
|
|
2024-04-02 00:10:23 +11:00
|
|
|
println!(
|
2024-04-02 00:22:41 +11:00
|
|
|
" {}:{}",
|
|
|
|
prettify_path(&location.filename).green(),
|
2024-04-02 00:10:23 +11:00
|
|
|
location.line.to_string().green()
|
|
|
|
);
|
2024-04-02 00:16:13 +11:00
|
|
|
|
2024-04-02 02:36:05 +11:00
|
|
|
if include_code && location.line != 0 && is_interesting {
|
2024-04-02 02:30:36 +11:00
|
|
|
print_line_of_code(&frame, location)?;
|
|
|
|
}
|
|
|
|
|
2024-04-02 00:16:13 +11:00
|
|
|
is_first = false;
|
2024-04-01 23:26:48 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2024-04-02 00:22:41 +11:00
|
|
|
|
2024-04-02 02:30:36 +11:00
|
|
|
fn print_line_of_code(
|
|
|
|
frame: &addr2line::Frame<'_, impl gimli::Reader>,
|
|
|
|
location: Location,
|
|
|
|
) -> anyhow::Result<()> {
|
|
|
|
let Some(filename) = frame.location.as_ref().and_then(|location| location.file) else {
|
|
|
|
return Ok(());
|
|
|
|
};
|
|
|
|
|
|
|
|
let Ok(mut file) = File::open(filename) else {
|
|
|
|
return Ok(());
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut content = String::new();
|
|
|
|
file.read_to_string(&mut content)?;
|
|
|
|
|
|
|
|
let Some(line_of_code) = content.split('\n').nth(location.line as usize - 1) else {
|
|
|
|
eprintln!("File {filename} does not have line {}", location.line);
|
|
|
|
return Ok(());
|
|
|
|
};
|
|
|
|
|
|
|
|
let trimmed = line_of_code.trim_start();
|
|
|
|
let trimmed_len = line_of_code.len() - trimmed.len();
|
|
|
|
println!("\t\t{}", trimmed);
|
|
|
|
|
|
|
|
if location.col != 0 {
|
|
|
|
println!(
|
|
|
|
"\t\t{}{}",
|
|
|
|
" ".repeat(location.col as usize - trimmed_len - 1),
|
|
|
|
"^".bright_blue()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-04-02 00:22:41 +11:00
|
|
|
fn prettify_path(path: &str) -> Cow<'_, str> {
|
|
|
|
if let Some(src_index) = path.rfind("/src/") {
|
|
|
|
let crate_name_start = path[0..src_index].rfind('/');
|
|
|
|
let crate_name = crate_name_start
|
|
|
|
.map(|crate_name_start| &path[crate_name_start + 1..src_index])
|
|
|
|
.unwrap_or("<crate>");
|
|
|
|
|
|
|
|
Cow::Owned(format!("<{crate_name}>/{}", &path[src_index + 5..]))
|
|
|
|
} else {
|
|
|
|
Cow::Borrowed(path)
|
|
|
|
}
|
|
|
|
}
|
2024-04-02 02:36:05 +11:00
|
|
|
|
|
|
|
fn is_interesting_function(function_name: &str, path: &str) -> bool {
|
|
|
|
if function_name == "rust_begin_unwind" {
|
|
|
|
return false; // this is the unwind exception call
|
|
|
|
}
|
|
|
|
|
|
|
|
if path.ends_with("panicking.rs") {
|
|
|
|
return false; // probably part of rust's internal panic mechanisms
|
|
|
|
}
|
|
|
|
|
|
|
|
true
|
|
|
|
}
|