Major `dwarf dump` rework

- Supports games with C++ DWARF info
- Syntax highlighting when printing to console (disable with `--no-color`)
- Overall improvements to parsing and output
This commit is contained in:
Luke Street 2023-11-18 13:56:58 -05:00
parent 4935708b61
commit 5e13998e93
4 changed files with 1444 additions and 485 deletions

216
Cargo.lock generated
View File

@ -310,7 +310,8 @@ dependencies = [
"flate2",
"glob",
"hex",
"indexmap",
"indent",
"indexmap 2.0.2",
"itertools",
"log",
"memchr",
@ -334,12 +335,22 @@ dependencies = [
"sha-1",
"smallvec",
"supports-color 2.1.0",
"syntect",
"tracing",
"tracing-attributes",
"tracing-subscriber",
"xxhash-rust",
]
[[package]]
name = "deranged"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3"
dependencies = [
"powerfmt",
]
[[package]]
name = "digest"
version = "0.10.6"
@ -428,6 +439,12 @@ dependencies = [
"miniz_oxide 0.7.1",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "generic-array"
version = "0.14.6"
@ -459,6 +476,12 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hashbrown"
version = "0.14.1"
@ -489,6 +512,22 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "indent"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9f1a0777d972970f204fdf8ef319f1f4f8459131636d7e3c96c5d59570d0fa6"
[[package]]
name = "indexmap"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
"hashbrown 0.12.3",
]
[[package]]
name = "indexmap"
version = "2.0.2"
@ -496,7 +535,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897"
dependencies = [
"equivalent",
"hashbrown",
"hashbrown 0.14.1",
]
[[package]]
@ -543,6 +582,21 @@ version = "0.2.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
[[package]]
name = "line-wrap"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9"
dependencies = [
"safemem",
]
[[package]]
name = "linked-hash-map"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "linux-raw-sys"
version = "0.4.10"
@ -681,8 +735,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0"
dependencies = [
"crc32fast",
"hashbrown",
"indexmap",
"hashbrown 0.14.1",
"indexmap 2.0.2",
"memchr",
]
@ -692,6 +746,28 @@ version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "onig"
version = "6.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f"
dependencies = [
"bitflags 1.3.2",
"libc",
"once_cell",
"onig_sys",
]
[[package]]
name = "onig_sys"
version = "69.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7"
dependencies = [
"cc",
"pkg-config",
]
[[package]]
name = "overload"
version = "0.1.1"
@ -720,7 +796,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9"
dependencies = [
"fixedbitset",
"indexmap",
"indexmap 2.0.2",
]
[[package]]
@ -729,6 +805,32 @@ version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05"
[[package]]
name = "pkg-config"
version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
[[package]]
name = "plist"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a4a0cfc5fb21a09dc6af4bf834cf10d4a32fccd9e2ea468c4b1751a097487aa"
dependencies = [
"base64",
"indexmap 1.9.3",
"line-wrap",
"quick-xml",
"serde",
"time",
]
[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "ppc750cl"
version = "0.2.0"
@ -770,6 +872,15 @@ dependencies = [
"unicase",
]
[[package]]
name = "quick-xml"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956"
dependencies = [
"memchr",
]
[[package]]
name = "quote"
version = "1.0.33"
@ -883,6 +994,21 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
[[package]]
name = "safemem"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
@ -937,7 +1063,7 @@ version = "0.9.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574"
dependencies = [
"indexmap",
"indexmap 2.0.2",
"itoa",
"ryu",
"serde",
@ -1018,6 +1144,27 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "syntect"
version = "5.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e02b4b303bf8d08bfeb0445cba5068a3d306b6baece1d5582171a9bf49188f91"
dependencies = [
"bincode",
"bitflags 1.3.2",
"flate2",
"fnv",
"once_cell",
"onig",
"plist",
"regex-syntax 0.7.5",
"serde",
"serde_json",
"thiserror",
"walkdir",
"yaml-rust",
]
[[package]]
name = "textwrap"
version = "0.11.0"
@ -1057,6 +1204,35 @@ dependencies = [
"once_cell",
]
[[package]]
name = "time"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5"
dependencies = [
"deranged",
"itoa",
"powerfmt",
"serde",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "time-macros"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20"
dependencies = [
"time-core",
]
[[package]]
name = "toml"
version = "0.5.11"
@ -1179,6 +1355,16 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "walkdir"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "winapi"
version = "0.3.9"
@ -1195,6 +1381,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
@ -1329,3 +1524,12 @@ name = "xxhash-rust"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9828b178da53440fa9c766a3d2f73f7cf5d0ac1fe3980c1e5018d899fd19e07b"
[[package]]
name = "yaml-rust"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [
"linked-hash-map",
]

View File

@ -35,6 +35,7 @@ flagset = { version = "0.4.4", features = ["serde"] }
flate2 = "1.0.27"
glob = "0.3.1"
hex = "0.4.3"
indent = "0.1.1"
indexmap = "2.0.2"
itertools = "0.11.0"
log = "0.4.20"
@ -59,6 +60,7 @@ serde_yaml = "0.9.25"
sha-1 = "0.10.1"
smallvec = "1.11.1"
supports-color = "2.1.0"
syntect = "5.1.0"
tracing = "0.1.37"
tracing-attributes = "0.1.26"
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }

View File

@ -2,16 +2,21 @@ use std::{
collections::{btree_map, BTreeMap},
io::{stdout, Cursor, Read, Write},
path::PathBuf,
str::from_utf8,
};
use anyhow::{anyhow, bail, Result};
use anyhow::{anyhow, bail, Context, Result};
use argp::FromArgs;
use object::{elf, Object, ObjectSection, ObjectSymbol, RelocationKind, RelocationTarget, Section};
use syntect::{
highlighting::{Color, HighlightIterator, HighlightState, Highlighter, Theme, ThemeSet},
parsing::{ParseState, ScopeStack, SyntaxReference, SyntaxSet},
};
use crate::util::{
dwarf::{
process_address, process_type, process_variable_location, read_debug_section, type_string,
ud_type, ud_type_def, ud_type_string, AttributeKind, TagKind,
process_root_tag, read_debug_section, should_skip_tag, tag_type_string, AttributeKind,
TagKind,
},
file::{buf_writer, map_file},
};
@ -40,6 +45,9 @@ pub struct DumpArgs {
#[argp(option, short = 'o')]
/// Output file. (Or directory, for archive)
out: Option<PathBuf>,
#[argp(switch)]
/// Disable color output.
no_color: bool,
}
pub fn run(args: Args) -> Result<()> {
@ -49,6 +57,12 @@ pub fn run(args: Args) -> Result<()> {
}
fn dump(args: DumpArgs) -> Result<()> {
let theme_set = ThemeSet::load_defaults();
let theme = theme_set.themes.get("Solarized (dark)").context("Failed to load theme")?;
let syntax_set = SyntaxSet::load_defaults_newlines();
let syntax =
syntax_set.find_syntax_by_extension("cpp").context("Failed to find syntax")?.clone();
let file = map_file(&args.in_file)?;
let buf = file.as_slice();
if buf.starts_with(b"!<arch>\n") {
@ -80,9 +94,13 @@ fn dump(args: DumpArgs) -> Result<()> {
let mut file = buf_writer(file_path)?;
dump_debug_section(&mut file, &obj_file, debug_section)?;
file.flush()?;
} else {
println!("\nFile {}:", name);
} else if args.no_color {
println!("\n// File {}:", name);
dump_debug_section(&mut stdout(), &obj_file, debug_section)?;
} else {
let mut writer = HighlightWriter::new(syntax_set.clone(), syntax.clone(), theme);
writeln!(writer, "\n// File {}:", name)?;
dump_debug_section(&mut writer, &obj_file, debug_section)?;
}
}
} else {
@ -94,8 +112,11 @@ fn dump(args: DumpArgs) -> Result<()> {
let mut file = buf_writer(out_path)?;
dump_debug_section(&mut file, &obj_file, debug_section)?;
file.flush()?;
} else {
} else if args.no_color {
dump_debug_section(&mut stdout(), &obj_file, debug_section)?;
} else {
let mut writer = HighlightWriter::new(syntax_set, syntax, theme);
dump_debug_section(&mut writer, &obj_file, debug_section)?;
}
}
Ok(())
@ -145,140 +166,37 @@ where
.string_attribute(AttributeKind::Name)
.ok_or_else(|| anyhow!("CompileUnit without name {:?}", tag))?;
if units.contains(unit) {
log::warn!("Duplicate unit '{}'", unit);
// log::warn!("Duplicate unit '{}'", unit);
} else {
units.push(unit.clone());
}
writeln!(w, "\n// Compile unit: {}", unit)?;
let children = tag.children(&tags);
let mut typedefs = BTreeMap::<u32, Vec<u32>>::new();
for child in children {
match child.kind {
TagKind::GlobalSubroutine | TagKind::Subroutine => {
let _is_prototyped =
child.string_attribute(AttributeKind::Prototyped).is_some();
if let (Some(_hi), Some(_lo)) = (
child.address_attribute(AttributeKind::HighPc),
child.address_attribute(AttributeKind::LowPc),
) {}
let name = child
.string_attribute(AttributeKind::Name)
.ok_or_else(|| anyhow!("Subroutine without name"))?;
let udt = ud_type(&tags, child)?;
let ts = ud_type_string(&tags, &typedefs, &udt)?;
writeln!(w, "{} {}{} {{", ts.prefix, name, ts.suffix)?;
for tag in child.children(&tags) {
match tag.kind {
TagKind::LocalVariable => {}
_ => continue,
}
let type_attr = tag.type_attribute().ok_or_else(|| {
anyhow!("LocalVariable without type attr")
})?;
let var_type = process_type(type_attr)?;
let ts = type_string(&tags, &typedefs, &var_type)?;
let name = tag
.string_attribute(AttributeKind::Name)
.ok_or_else(|| anyhow!("LocalVariable without name"))?;
write!(w, "\t{} {}{};", ts.prefix, name, ts.suffix)?;
if let Some(location) =
tag.block_attribute(AttributeKind::Location)
{
if !location.is_empty() {
write!(
w,
" // {}",
process_variable_location(location)?
)?;
}
}
writeln!(w)?;
}
writeln!(w, "}}")?;
}
TagKind::Typedef => {
let name = child
.string_attribute(AttributeKind::Name)
.ok_or_else(|| anyhow!("Typedef without name"))?;
let attr = child
.type_attribute()
.ok_or_else(|| anyhow!("Typedef without type attribute"))?;
let t = process_type(attr)?;
let ts = type_string(&tags, &typedefs, &t)?;
writeln!(w, "typedef {} {}{};", ts.prefix, name, ts.suffix)?;
let tag_type = process_root_tag(&tags, child)?;
if should_skip_tag(&tag_type) {
continue;
}
writeln!(w, "{}", tag_type_string(&tags, &typedefs, &tag_type)?)?;
// TODO fundamental typedefs?
if let Some(ud_type_ref) =
child.reference_attribute(AttributeKind::UserDefType)
{
match typedefs.entry(ud_type_ref) {
btree_map::Entry::Vacant(e) => {
e.insert(vec![child.key]);
}
btree_map::Entry::Occupied(e) => {
e.into_mut().push(child.key);
}
if let TagKind::Typedef = child.kind {
// TODO fundamental typedefs?
if let Some(ud_type_ref) =
child.reference_attribute(AttributeKind::UserDefType)
{
match typedefs.entry(ud_type_ref) {
btree_map::Entry::Vacant(e) => {
e.insert(vec![child.key]);
}
btree_map::Entry::Occupied(e) => {
e.into_mut().push(child.key);
}
}
}
TagKind::GlobalVariable | TagKind::LocalVariable => {
let name = child
.string_attribute(AttributeKind::Name)
.ok_or_else(|| anyhow!("Variable without name"))?;
let address = if let Some(location) =
child.block_attribute(AttributeKind::Location)
{
Some(process_address(location)?)
} else {
None
};
if let Some(type_attr) = child.type_attribute() {
let var_type = process_type(type_attr)?;
// log::info!("{:?}", var_type);
// if let TypeKind::UserDefined(key) = var_type.kind {
// let ud_tag = tags
// .get(&key)
// .ok_or_else(|| anyhow!("Invalid UD type ref"))?;
// let ud_type = ud_type(&tags, ud_tag)?;
// log::info!("{:?}", ud_type);
// }
let ts = type_string(&tags, &typedefs, &var_type)?;
let st = if child.kind == TagKind::LocalVariable {
"static "
} else {
""
};
let address_str = match address {
Some(addr) => format!(" : {:#010X}", addr),
None => String::new(),
};
let size = var_type.size(&tags)?;
writeln!(
w,
"{}{} {}{}{}; // size: {:#X}",
st, ts.prefix, name, ts.suffix, address_str, size,
)?;
}
}
TagKind::StructureType
| TagKind::ArrayType
| TagKind::EnumerationType
| TagKind::UnionType
| TagKind::ClassType
| TagKind::SubroutineType => {
let udt = ud_type(&tags, child)?;
if child.string_attribute(AttributeKind::Name).is_some() {
writeln!(w, "{}", ud_type_def(&tags, &typedefs, &udt)?)?;
} else {
// log::warn!("No name for tag: {:?}", child);
}
}
_ => {
log::warn!("Unhandled CompileUnit child {:?}", child.kind);
}
}
}
// println!("Children: {:?}", children.iter().map(|c| c.kind).collect::<Vec<TagKind>>());
}
_ => {
log::warn!("Expected CompileUnit, got {:?}", tag.kind);
@ -298,3 +216,81 @@ where
// }
Ok(())
}
struct HighlightWriter<'a> {
line: String,
highlighter: Highlighter<'a>,
parse_state: ParseState,
highlight_state: HighlightState,
syntax_set: SyntaxSet,
}
impl<'a> HighlightWriter<'a> {
pub fn new(
syntax_set: SyntaxSet,
syntax: SyntaxReference,
theme: &'a Theme,
) -> HighlightWriter<'a> {
let highlighter = Highlighter::new(theme);
let highlight_state = HighlightState::new(&highlighter, ScopeStack::new());
HighlightWriter {
line: String::new(),
highlighter,
syntax_set,
parse_state: ParseState::new(&syntax),
highlight_state,
}
}
}
#[inline]
fn blend_fg_color(fg: Color, bg: Color) -> Color {
if fg.a == 0xff {
return fg;
}
let ratio = fg.a as u32;
let r = (fg.r as u32 * ratio + bg.r as u32 * (255 - ratio)) / 255;
let g = (fg.g as u32 * ratio + bg.g as u32 * (255 - ratio)) / 255;
let b = (fg.b as u32 * ratio + bg.b as u32 * (255 - ratio)) / 255;
Color { r: r as u8, g: g as u8, b: b as u8, a: 255 }
}
impl Write for HighlightWriter<'_> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let str = from_utf8(buf).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
for s in str.split_inclusive('\n') {
self.line.push_str(s);
if self.line.ends_with('\n') {
self.flush()?;
}
}
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
if self.line.is_empty() {
return Ok(());
}
let ops = self
.parse_state
.parse_line(&self.line, &self.syntax_set)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
let iter = HighlightIterator::new(
&mut self.highlight_state,
&ops[..],
&self.line,
&self.highlighter,
);
for (style, text) in iter {
print!(
"\x1b[48;2;{};{};{}m",
style.background.r, style.background.g, style.background.b
);
let fg = blend_fg_color(style.foreground, style.background);
print!("\x1b[38;2;{};{};{}m{}", fg.r, fg.g, fg.b, text);
}
print!("\x1b[0m");
self.line.clear();
Ok(())
}
}

File diff suppressed because it is too large Load Diff