Now generating a .dot graph of all 'blocks' of the msd.

This commit is contained in:
Filipe Rodrigues 2021-08-07 17:04:14 +01:00
parent 37ba47e20f
commit dd24925fd1
5 changed files with 108 additions and 9 deletions

View File

@ -20,6 +20,7 @@ anyhow = "1.0.38"
# Utility
byteorder = "1.4.3"
dbg_hex = {git = "https://github.com/Zenithsiz/dbg_hex"}
itertools = "0.10.0"
# Strings

View File

@ -9,7 +9,8 @@
exact_size_is_empty,
iter_advance_by,
try_blocks,
cow_is_borrowed
cow_is_borrowed,
map_first_last
)]
// Modules
@ -26,7 +27,9 @@ use std::{
collections::{BTreeMap, HashMap},
convert::TryInto,
fs,
io::Write,
};
use zutil::Void;
fn main() -> Result<(), anyhow::Error> {
@ -118,12 +121,99 @@ fn main() -> Result<(), anyhow::Error> {
.map(|(idx, addr)| (addr, format!("jump_{idx}")));
labels.extend(heuristic_labels);
#[derive(Clone, Debug)]
struct Block {
start: u32,
end: u32,
calls: BTreeMap<u32, u32>,
}
let blocks = labels
.keys()
.map(|&pos| {
let (end, label_at_end, unconditional_jump) = commands
.range(pos..)
.tuple_windows()
.find_map(|((&cur_pos, cur), (&next_pos, next))| {
// If the first instruction is a jump, it's unconditional
if pos == cur_pos {
if let Command::Jump { addr, .. } = *cur {
return Some((next_pos, labels.contains_key(&next_pos), Some((cur_pos, addr))));
}
}
// Else check if the next instruction is a label
if labels.contains_key(&next_pos) {
// Note: Here the unconditional must be `None` or we would have
// returned on the previous iteration or above.
return Some((next_pos, true, None));
}
// Else check for a non-test + jump combo.
match (cur, next) {
// Ignore test-jumps
(Command::Test { .. }, Command::Jump { .. }) => None,
// Else a non-test jump should be unconditional
// Note: `unconditional_jump` should be false here always I believe, else it's dead code?
(_, Command::Jump { .. }) => Some((next_pos + next.size() as u32, false, match *cur {
Command::Jump { addr, .. } => Some((cur_pos, addr)),
_ => None,
})),
_ => None,
}
})
.unwrap_or_else(|| (commands.last_key_value().map_or(0, |(&pos, _)| pos), false, None));
let mut calls = commands
.range(pos..end)
.filter_map(|(&pos, command)| match *command {
Command::Jump { addr, .. } => Some((pos, addr)),
_ => None,
})
.collect::<BTreeMap<_, _>>();
// Check if we need to add any extra calls
match (label_at_end, unconditional_jump) {
// If we ended on a label without diverging, add a call the label
(true, None) => calls.insert(end, end).void(),
// If we ended by diverging, insert a call to it.
(_, Some((pos, addr))) => calls.insert(pos, addr).void(),
// Else no extra calls
_ => (),
}
(pos, Block { start: pos, end, calls })
})
.collect::<BTreeMap<u32, Block>>();
let dot_file_path = format!("{}.dot", cli_data.input_file.display());
let mut dot_file = std::fs::File::create(dot_file_path).context("Unable to create dot file")?;
writeln!(dot_file, "digraph \"G\" {{").context("Unable to write to dot file")?;
for block in blocks.values() {
let label = labels.get(&block.start).expect("Block had no label");
writeln!(dot_file, "\t{label};").context("Unable to write to dot file")?;
for call_pos in block.calls.values().unique() {
let call_label = match labels.get(call_pos) {
Some(label) => label.to_owned(),
None => format!("\"{call_pos:#x}\""),
};
writeln!(dot_file, "\t{label} -> {call_label};").context("Unable to write to dot file")?;
}
}
writeln!(dot_file, "}}").context("Unable to write to dot file")?;
let mut state = State::Start;
for (pos, command) in commands {
if let Some(label) = labels.get(&pos) {
println!("{label}:");
};
print!("{pos:#010x}: ");
/*

View File

@ -1,7 +1,9 @@
---
0x00000100: "intro"
0x000002ac: "city_start"
0x0000b090: "arena_1_closed"
0x0000b090: "arenas_check_open"
0x0000b0cc: "arena_1_check_open"
0x0000b0e0: "arena_1_closed"
0x0000b144: "arena_1_enter_menu"
0x0000b1bc: "arena_1_enter_menu_no"
0x0000b1c4: "arena_1_enter_menu_yes"
@ -28,7 +30,8 @@
0x0000cc00: "arena_1_leave_menu"
0x0000cc74: "arena_1_leave_menu_no"
0x0000cc7c: "arena_1_leave_menu_yes"
0x0000cc90: "arena_2_closed"
0x0000cc90: "arena_2_check_open"
0x0000cca4: "arena_2_closed"
0x0000cd08: "arena_2_enter_menu"
0x0000cd80: "arena_2_enter_menu_no"
0x0000cd88: "arena_2_enter_menu_yes"
@ -63,7 +66,8 @@
0x0000e35c: "arena_2_leave_menu"
0x0000e3d0: "arena_2_leave_menu_no"
0x0000e3d8: "arena_2_leave_menu_yes"
0x0000e3ec: "arena_3_closed"
0x0000e3ec: "arena_3_check_open"
0x0000e400: "arena_3_closed"
0x0000e464: "arena_3_enter_menu"
0x0000e4dc: "arena_3_enter_menu_no"
0x0000e4e4: "arena_3_enter_menu_yes"
@ -97,6 +101,7 @@
0x0000fb60: "arena_3_leave_menu"
0x0000fbd4: "arena_3_leave_menu_no"
0x0000fbdc: "arena_3_leave_menu_yes"
0x0000fc04: "arena_4_check_open"
0x0000fbf0: "arena_4_closed"
0x0000fc68: "arena_4_enter_menu"
0x0000fce0: "arena_4_enter_menu_no"

View File

@ -1,4 +1,5 @@
---
0x0000a9e8: "arena_1_check_open"
0x0000a9fc: "arena_1_closed"
0x0000aa60: "arena_1_enter_menu"
0x0000aaec: "arena_1_enter_menu_no"
@ -35,7 +36,9 @@
0x0000cb5c: "arena_1_leave_menu"
0x0000cbd0: "arena_1_leave_menu_no"
0x0000cbd8: "arena_1_leave_menu_yes"
0x0000cbec: "arena_2_closed"
0x0000cbec: "arenas_2_3_4_5_check_open"
0x0000cc28: "arena_2_check_open"
0x0000cc3c: "arena_2_closed"
0x0000cca0: "arena_2_enter_menu"
0x0000cd18: "arena_2_enter_menu_no"
0x0000cd20: "arena_2_enter_menu_yes"

View File

@ -22,9 +22,9 @@
0x0018: "city00_rosemon_favor_dark_city_accepted"
0x0019: "city00_rosemon_favor_dark_city_finished"
0x001a: "city00_battle_cafe_tai_won"
0x001b: "city00_arena_2_unlocked"
0x001c: "city00_arena_3_unlocked"
0x001d: "city00_arena_4_unlocked"
0x001b: "city00_arena_2_open"
0x001c: "city00_arena_3_open"
0x001d: "city00_arena_4_open"
0x001e: "done_intro"
0x001f: "city00_battle_cafe_rosemon_won"
0x0020: "city01_arena_1_won"
@ -42,7 +42,7 @@
0x002d: "city01_arena_4_magnamon_introduction_done"
0x002e: "city01_arena_5_imperialdramon_introduction_done"
0x0033: "city00_talked_to_babamon_early"
0x0036: "city00_arena_open"
0x0036: "city00_arena_1_open"
0x008c: "rosemon_favor_dark_city_battled"
0x010c: "arena_won_first"
0x010d: "arena_won_second"