Implementation of basic data flow analysis for PowerPC (#212)

* WIP implementation

* * Move flow analysis to dedicated file
* Show string constants inline
* Handle calls to MWCC "sled" helpers which otherwise disrupt flow analysis

* Run cargo insta review

* Apply clippy feedback

* Update more tests.

* Remove std use from ppc flow analysis

* Try to make wasm build work again

* More test changes

* Probably last wasm fix

* Formatting

* Fix WASM

* One more clippy thing

* Fixed display of float constants in a LFS or LFD instruction in case where there is a branch to the subsequent instruction with a different register value.

* On lines with a reloc, only hide Symbol type data flow values rather than all data flow values.

* Formatting
This commit is contained in:
Mark Langen
2025-06-17 12:59:04 -06:00
committed by GitHub
parent f58616b6dd
commit e638d0b17a
25 changed files with 870 additions and 66 deletions
+1
View File
@@ -570,6 +570,7 @@ impl FunctionDiffUi {
DiffTextColor::Normal => Color::Gray,
DiffTextColor::Dim => Color::DarkGray,
DiffTextColor::Bright => Color::White,
DiffTextColor::DataFlow => Color::LightCyan,
DiffTextColor::Replace => Color::Cyan,
DiffTextColor::Delete => Color::Red,
DiffTextColor::Insert => Color::Green,
+1 -1
View File
@@ -175,7 +175,7 @@ time = { version = "0.3", optional = true }
encoding_rs = { version = "0.8.35", optional = true }
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", optional = true }
winapi = { version = "0.3", optional = true, features = ["winbase"] }
# For Linux static binaries, use rustls
[target.'cfg(target_os = "linux")'.dependencies]
+16 -1
View File
@@ -25,6 +25,20 @@
}
]
},
{
"id": "analyzeDataFlow",
"type": "boolean",
"default": false,
"name": "(Experimental) Perform data flow analysis",
"description": "Use data flow analysis to display known information about register contents where possible"
},
{
"id": "showDataFlow",
"type": "boolean",
"default": true,
"name": "Show data flow",
"description": "Show data flow analysis results in place of register name where present"
},
{
"id": "spaceBetweenArgs",
"type": "boolean",
@@ -264,7 +278,8 @@
"id": "ppc",
"name": "PowerPC",
"properties": [
"ppc.calculatePoolRelocations"
"ppc.calculatePoolRelocations",
"analyzeDataFlow"
]
},
{
+19 -3
View File
@@ -1,5 +1,8 @@
use alloc::{borrow::Cow, boxed::Box, format, string::String, vec::Vec};
use core::{ffi::CStr, fmt, fmt::Debug};
use core::{
ffi::CStr,
fmt::{self, Debug},
};
use anyhow::{Result, bail};
use encoding_rs::SHIFT_JIS;
@@ -11,8 +14,9 @@ use crate::{
display::{ContextItem, HoverItem, InstructionPart},
},
obj::{
InstructionArg, InstructionRef, Object, ParsedInstruction, Relocation, RelocationFlags,
ResolvedInstructionRef, ResolvedSymbol, Section, Symbol, SymbolFlagSet, SymbolKind,
FlowAnalysisResult, InstructionArg, InstructionRef, Object, ParsedInstruction, Relocation,
RelocationFlags, ResolvedInstructionRef, ResolvedSymbol, Section, Symbol, SymbolFlagSet,
SymbolKind,
},
util::ReallySigned,
};
@@ -31,6 +35,7 @@ pub mod superh;
pub mod x86;
/// Represents the type of data associated with an instruction
#[derive(PartialEq)]
pub enum DataType {
Int8,
Int16,
@@ -335,6 +340,17 @@ pub trait Arch: Send + Sync + Debug {
Vec::new()
}
// Perform detailed data flow analysis
fn data_flow_analysis(
&self,
_obj: &Object,
_symbol: &Symbol,
_code: &[u8],
_relocations: &[Relocation],
) -> Option<Box<dyn FlowAnalysisResult>> {
None
}
fn implcit_addend(
&self,
file: &object::File<'_>,
File diff suppressed because it is too large Load Diff
@@ -1,4 +1,5 @@
use alloc::{
boxed::Box,
collections::{BTreeMap, BTreeSet},
string::{String, ToString},
vec,
@@ -18,11 +19,13 @@ use crate::{
display::{ContextItem, HoverItem, HoverItemColor, InstructionPart, SymbolNavigationKind},
},
obj::{
InstructionRef, Object, Relocation, RelocationFlags, ResolvedInstructionRef,
ResolvedRelocation, Symbol, SymbolFlag, SymbolFlagSet,
FlowAnalysisResult, InstructionRef, Object, Relocation, RelocationFlags,
ResolvedInstructionRef, ResolvedRelocation, Symbol, SymbolFlag, SymbolFlagSet,
},
};
mod flow_analysis;
// Relative relocation, can be Simm, Offset or BranchDest
fn is_relative_arg(arg: &ppc750cl::Argument) -> bool {
matches!(
@@ -157,6 +160,7 @@ impl Arch for ArchPpc {
Ok(())
}
// Could be replaced by data_flow_analysis once that feature stabilizes
fn generate_pooled_relocations(
&self,
address: u64,
@@ -167,6 +171,16 @@ impl Arch for ArchPpc {
generate_fake_pool_relocations_for_function(address, code, relocations, symbols)
}
fn data_flow_analysis(
&self,
obj: &Object,
symbol: &Symbol,
code: &[u8],
relocations: &[Relocation],
) -> Option<Box<dyn FlowAnalysisResult>> {
Some(flow_analysis::ppc_data_flow_analysis(obj, symbol, code, relocations))
}
fn implcit_addend(
&self,
_file: &object::File<'_>,
@@ -225,7 +239,7 @@ impl Arch for ArchPpc {
return Some(DataType::String);
}
let opcode = ppc750cl::Opcode::from(resolved.ins_ref.opcode as u8);
if let Some(ty) = guess_data_type_from_load_store_inst_op(opcode) {
if let Some(ty) = flow_analysis::guess_data_type_from_load_store_inst_op(opcode) {
// Numeric type.
return Some(ty);
}
@@ -501,25 +515,6 @@ fn make_symbol_ref(symbol: &object::Symbol) -> Result<ExtabSymbolRef> {
Ok(ExtabSymbolRef { original_index: symbol.index().0 - 1, name, demangled_name })
}
fn guess_data_type_from_load_store_inst_op(inst_op: ppc750cl::Opcode) -> Option<DataType> {
use ppc750cl::Opcode;
match inst_op {
Opcode::Lbz | Opcode::Lbzu | Opcode::Lbzux | Opcode::Lbzx => Some(DataType::Int8),
Opcode::Lhz | Opcode::Lhzu | Opcode::Lhzux | Opcode::Lhzx => Some(DataType::Int16),
Opcode::Lha | Opcode::Lhau | Opcode::Lhaux | Opcode::Lhax => Some(DataType::Int16),
Opcode::Lwz | Opcode::Lwzu | Opcode::Lwzux | Opcode::Lwzx => Some(DataType::Int32),
Opcode::Lfs | Opcode::Lfsu | Opcode::Lfsux | Opcode::Lfsx => Some(DataType::Float),
Opcode::Lfd | Opcode::Lfdu | Opcode::Lfdux | Opcode::Lfdx => Some(DataType::Double),
Opcode::Stb | Opcode::Stbu | Opcode::Stbux | Opcode::Stbx => Some(DataType::Int8),
Opcode::Sth | Opcode::Sthu | Opcode::Sthux | Opcode::Sthx => Some(DataType::Int16),
Opcode::Stw | Opcode::Stwu | Opcode::Stwux | Opcode::Stwx => Some(DataType::Int32),
Opcode::Stfs | Opcode::Stfsu | Opcode::Stfsux | Opcode::Stfsx => Some(DataType::Float),
Opcode::Stfd | Opcode::Stfdu | Opcode::Stfdux | Opcode::Stfdx => Some(DataType::Double),
_ => None,
}
}
#[derive(Debug)]
struct PoolReference {
addr_src_gpr: ppc750cl::GPR,
@@ -536,7 +531,7 @@ fn get_pool_reference_for_inst(
) -> Option<PoolReference> {
use ppc750cl::{Argument, Opcode};
let args = &simplified.args;
if guess_data_type_from_load_store_inst_op(opcode).is_some() {
if flow_analysis::guess_data_type_from_load_store_inst_op(opcode).is_some() {
match (args[1], args[2]) {
(Argument::Offset(offset), Argument::GPR(addr_src_gpr)) => {
// e.g. lwz. Immediate offset.
@@ -668,7 +663,7 @@ fn make_fake_pool_reloc(
// and returns a Vec of "fake pool relocations" that simulate what a relocation for that instruction
// would look like if data hadn't been pooled.
// This method tries to follow the function's proper control flow. It keeps track of a queue of
// states it hasn't traversed yet, where each state holds an instruction address and a HashMap of
// states it hasn't traversed yet, where each state holds an instruction address and a map of
// which registers hold which pool relocations at that point.
// When a conditional or unconditional branch is encountered, the destination of the branch is added
// to the queue. Conditional branches will traverse both the path where the branch is taken and the
+39 -17
View File
@@ -14,8 +14,9 @@ use regex::Regex;
use crate::{
diff::{DiffObjConfig, InstructionDiffKind, InstructionDiffRow, ObjectDiff, SymbolDiff},
obj::{
InstructionArg, InstructionArgValue, Object, ParsedInstruction, ResolvedInstructionRef,
ResolvedRelocation, SectionFlag, SectionKind, Symbol, SymbolFlag, SymbolKind,
FlowAnalysisValue, InstructionArg, InstructionArgValue, Object, ParsedInstruction,
ResolvedInstructionRef, ResolvedRelocation, SectionFlag, SectionKind, Symbol, SymbolFlag,
SymbolKind,
},
};
@@ -47,11 +48,12 @@ pub enum DiffText<'a> {
pub enum DiffTextColor {
#[default]
Normal, // Grey
Dim, // Dark grey
Bright, // White
Replace, // Blue
Delete, // Red
Insert, // Green
Dim, // Dark grey
Bright, // White
DataFlow, // Light blue
Replace, // Blue
Delete, // Red
Insert, // Green
Rotating(u8),
}
@@ -186,6 +188,11 @@ pub fn display_row(
}
let mut arg_idx = 0;
let mut displayed_relocation = false;
let analysis_result = if diff_config.show_data_flow {
obj.flow_analysis_results.get(&resolved.symbol.address)
} else {
None
};
obj.arch.display_instruction(resolved, diff_config, &mut |part| match part {
InstructionPart::Basic(text) => {
if text.chars().all(|c| c == ' ') {
@@ -208,15 +215,30 @@ pub fn display_row(
if arg == InstructionArg::Reloc {
displayed_relocation = true;
}
match (arg, resolved.ins_ref.branch_dest) {
(InstructionArg::Value(value), _) => cb(DiffTextSegment {
text: DiffText::Argument(value),
color: diff_index
let data_flow_value =
analysis_result.and_then(|result|
result.as_ref().get_argument_value_at_address(
ins_ref.address, (arg_idx - 1) as u8));
match (arg, data_flow_value, resolved.ins_ref.branch_dest) {
// If we have a flow analysis result, always use that over anything else.
(InstructionArg::Value(_) | InstructionArg::Reloc, Some(FlowAnalysisValue::Text(text)), _) => {
cb(DiffTextSegment {
text: DiffText::Argument(InstructionArgValue::Opaque(Cow::Borrowed(text))),
color: DiffTextColor::DataFlow,
pad_to: 0,
})
},
(InstructionArg::Value(value), None, _) => {
let color = diff_index
.get()
.map_or(base_color, |i| DiffTextColor::Rotating(i as u8)),
pad_to: 0,
}),
(InstructionArg::Reloc, None) => {
.map_or(base_color, |i| DiffTextColor::Rotating(i as u8));
cb(DiffTextSegment {
text: DiffText::Argument(value),
color,
pad_to: 0,
})
},
(InstructionArg::Reloc, _, None) => {
let resolved = resolved.relocation.unwrap();
let color = diff_index
.get()
@@ -235,9 +257,9 @@ pub fn display_row(
}
Ok(())
}
(InstructionArg::BranchDest(dest), _) |
(InstructionArg::BranchDest(dest), _, _) |
// If the relocation was resolved to a branch destination, emit that instead.
(InstructionArg::Reloc, Some(dest)) => {
(InstructionArg::Reloc, _, Some(dest)) => {
if let Some(addr) = dest.checked_sub(resolved.symbol.address) {
cb(DiffTextSegment {
text: DiffText::BranchDest(addr),
+17
View File
@@ -233,6 +233,19 @@ pub enum SymbolKind {
Section,
}
#[derive(Debug)]
pub enum FlowAnalysisValue {
Text(String),
}
pub trait FlowAnalysisResult: core::fmt::Debug + Send {
fn get_argument_value_at_address(
&self,
address: u64,
argument: u8,
) -> Option<&FlowAnalysisValue>;
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
pub struct Symbol {
pub name: String,
@@ -260,6 +273,7 @@ pub struct Object {
pub path: Option<std::path::PathBuf>,
#[cfg(feature = "std")]
pub timestamp: Option<filetime::FileTime>,
pub flow_analysis_results: BTreeMap<u64, Box<dyn FlowAnalysisResult>>,
}
impl Default for Object {
@@ -274,6 +288,7 @@ impl Default for Object {
path: None,
#[cfg(feature = "std")]
timestamp: None,
flow_analysis_results: BTreeMap::<u64, Box<dyn FlowAnalysisResult>>::new(),
}
}
}
@@ -312,6 +327,8 @@ impl Object {
pub fn symbol_by_name(&self, name: &str) -> Option<usize> {
self.symbols.iter().position(|symbol| symbol.section.is_some() && symbol.name == name)
}
pub fn has_flow_analysis_result(&self) -> bool { !self.flow_analysis_results.is_empty() }
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
+43 -20
View File
@@ -432,17 +432,18 @@ fn map_relocations(
Ok(())
}
fn calculate_pooled_relocations(
arch: &dyn Arch,
sections: &mut [Section],
symbols: &[Symbol],
) -> Result<()> {
for (section_index, section) in sections.iter_mut().enumerate() {
fn perform_data_flow_analysis(obj: &mut Object, config: &DiffObjConfig) -> Result<()> {
// If neither of these settings are on, no flow analysis to perform
if !config.analyze_data_flow && !config.ppc_calculate_pool_relocations {
return Ok(());
}
let mut generated_relocations = Vec::<(usize, Vec<Relocation>)>::new();
for (section_index, section) in obj.sections.iter().enumerate() {
if section.kind != SectionKind::Code {
continue;
}
let mut fake_pool_relocs = Vec::new();
for symbol in symbols {
for symbol in obj.symbols.iter() {
if symbol.section != Some(section_index) {
continue;
}
@@ -457,14 +458,32 @@ fn calculate_pooled_relocations(
symbol.address + symbol.size
)
})?;
fake_pool_relocs.append(&mut arch.generate_pooled_relocations(
symbol.address,
code,
&section.relocations,
symbols,
));
// Optional pooled relocation computation
// Long view: This could be replaced by the full data flow analysis
// once that feature has stabilized.
if config.ppc_calculate_pool_relocations {
let relocations = obj.arch.generate_pooled_relocations(
symbol.address,
code,
&section.relocations,
&obj.symbols,
);
generated_relocations.push((section_index, relocations));
}
// Optional full data flow analysis
if config.analyze_data_flow {
obj.arch.data_flow_analysis(obj, symbol, code, &section.relocations).and_then(
|flow_result| obj.flow_analysis_results.insert(symbol.address, flow_result),
);
}
}
section.relocations.append(&mut fake_pool_relocs);
}
for (section_index, mut relocations) in generated_relocations {
obj.sections[section_index].relocations.append(&mut relocations);
}
for section in obj.sections.iter_mut() {
section.relocations.sort_by_key(|r| r.address);
}
Ok(())
@@ -865,15 +884,12 @@ pub fn parse(data: &[u8], config: &DiffObjConfig) -> Result<Object> {
let (mut symbols, symbol_indices) =
map_symbols(arch.as_ref(), &obj_file, &sections, &section_indices, split_meta.as_ref())?;
map_relocations(arch.as_ref(), &obj_file, &mut sections, &section_indices, &symbol_indices)?;
if config.ppc_calculate_pool_relocations {
calculate_pooled_relocations(arch.as_ref(), &mut sections, &symbols)?;
}
parse_line_info(&obj_file, &mut sections, &section_indices, data)?;
if config.combine_data_sections || config.combine_text_sections {
combine_sections(&mut sections, &mut symbols, config)?;
}
arch.post_init(&sections, &symbols);
Ok(Object {
let mut obj = Object {
arch,
endianness: obj_file.endianness(),
symbols,
@@ -883,7 +899,14 @@ pub fn parse(data: &[u8], config: &DiffObjConfig) -> Result<Object> {
path: None,
#[cfg(feature = "std")]
timestamp: None,
})
flow_analysis_results: Default::default(),
};
// Need to construct the obj first so that we have a convinient package to
// pass to flow analysis. Then the flow analysis will mutate obj adding
// additional data to it.
perform_data_flow_analysis(&mut obj, config)?;
Ok(obj)
}
#[cfg(feature = "std")]
+30
View File
@@ -59,3 +59,33 @@ pub fn align_u64_to(len: u64, align: u64) -> u64 { len + ((align - (len % align)
pub fn align_data_slice_to(data: &mut Vec<u8>, align: u64) {
data.resize(align_u64_to(data.len() as u64, align) as usize, 0);
}
// Float where we specifically care about comparing the raw bits rather than
// caring about IEEE semantics.
#[derive(Copy, Clone, Debug)]
pub struct RawFloat(pub f32);
impl PartialEq for RawFloat {
fn eq(&self, other: &Self) -> bool { self.0.to_bits() == other.0.to_bits() }
}
impl Eq for RawFloat {}
impl Ord for RawFloat {
fn cmp(&self, other: &Self) -> core::cmp::Ordering { self.0.to_bits().cmp(&other.0.to_bits()) }
}
impl PartialOrd for RawFloat {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> { Some(self.cmp(other)) }
}
// Double where we specifically care about comparing the raw bits rather than
// caring about IEEE semantics.
#[derive(Copy, Clone, Debug)]
pub struct RawDouble(pub f64);
impl PartialEq for RawDouble {
fn eq(&self, other: &Self) -> bool { self.0.to_bits() == other.0.to_bits() }
}
impl Eq for RawDouble {}
impl Ord for RawDouble {
fn cmp(&self, other: &Self) -> core::cmp::Ordering { self.0.to_bits().cmp(&other.0.to_bits()) }
}
impl PartialOrd for RawDouble {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> { Some(self.cmp(other)) }
}
@@ -1954,4 +1954,5 @@ Object {
split_meta: None,
path: None,
timestamp: None,
flow_analysis_results: {},
}
@@ -3826,4 +3826,5 @@ Object {
split_meta: None,
path: None,
timestamp: None,
flow_analysis_results: {},
}
@@ -1490,4 +1490,5 @@ Object {
split_meta: None,
path: None,
timestamp: None,
flow_analysis_results: {},
}
@@ -548,4 +548,5 @@ Object {
split_meta: None,
path: None,
timestamp: None,
flow_analysis_results: {},
}
@@ -581,4 +581,5 @@ Object {
),
path: None,
timestamp: None,
flow_analysis_results: {},
}
@@ -207,4 +207,5 @@ Object {
split_meta: None,
path: None,
timestamp: None,
flow_analysis_results: {},
}
@@ -1574,4 +1574,5 @@ Object {
split_meta: None,
path: None,
timestamp: None,
flow_analysis_results: {},
}
@@ -311,4 +311,5 @@ Object {
split_meta: None,
path: None,
timestamp: None,
flow_analysis_results: {},
}
@@ -160,4 +160,5 @@ Object {
split_meta: None,
path: None,
timestamp: None,
flow_analysis_results: {},
}
+5
View File
@@ -23,6 +23,8 @@ pub struct Appearance {
#[serde(skip)]
pub highlight_color: Color32, // WHITE
#[serde(skip)]
pub dataflow_color: Color32, //
#[serde(skip)]
pub replace_color: Color32, // LIGHT_BLUE
#[serde(skip)]
pub insert_color: Color32, // GREEN
@@ -61,6 +63,7 @@ impl Default for Appearance {
emphasized_text_color: Color32::LIGHT_GRAY,
deemphasized_text_color: Color32::DARK_GRAY,
highlight_color: Color32::WHITE,
dataflow_color: Color32::from_rgb(0, 128, 128),
replace_color: Color32::LIGHT_BLUE,
insert_color: Color32::GREEN,
delete_color: Color32::from_rgb(200, 40, 41),
@@ -104,6 +107,7 @@ impl Appearance {
self.emphasized_text_color = Color32::LIGHT_GRAY;
self.deemphasized_text_color = Color32::DARK_GRAY;
self.highlight_color = Color32::WHITE;
self.dataflow_color = Color32::from_rgb(0, 128, 128);
self.replace_color = Color32::LIGHT_BLUE;
self.insert_color = Color32::GREEN;
self.delete_color = Color32::from_rgb(200, 40, 41);
@@ -114,6 +118,7 @@ impl Appearance {
self.emphasized_text_color = Color32::DARK_GRAY;
self.deemphasized_text_color = Color32::LIGHT_GRAY;
self.highlight_color = Color32::BLACK;
self.dataflow_color = Color32::from_rgb(0, 128, 128);
self.replace_color = Color32::DARK_BLUE;
self.insert_color = Color32::DARK_GREEN;
self.delete_color = Color32::from_rgb(200, 40, 41);

Some files were not shown because too many files have changed in this diff Show More