use ghidra::{
Address, AddressRange, AnalysisMode, AnalysisOptions, AnalysisReport, BasicBlockInfo,
CallGraphEdge, CallGraphNode, ControlFlowEdgeInfo, ControlFlowGraph, DataFlowEdge,
DataFlowGraph, DataFlowOp, DataFlowVariable, DataFlowVarnode, DataInfo, DecompileOptions,
DecompileResult, DecompileStatus, Decompiler, Error, ExportInfo, ExportKind,
FunctionControlFlowGraph, FunctionDescriptor, FunctionPrototype, FunctionSummary, Ghidra,
GhidraRuntime, HighPcodeBlock, HighPcodeGraph, HighSymbol, ImportInfo, ImportKind,
InstructionInfo, LaunchOptions, LoadedProgram, LoaderArgument, MemoryBlockInfo, NamedCount,
OperandInfo, PcodeOpInfo, PcodeSummary, Program, ProgramCallGraph, ProgramEnumValue,
ProgramFunction, ProgramFunctionSignature, ProgramLoadInfo, ProgramLoadOptions,
ProgramMetadata, ProgramOpenOptions, ProgramParameter, ProgramPath, ProgramSymbol,
ProgramTransaction, ProgramType, ProgramTypeComponent, ProgramTypeDetails,
ProgramTypeIndexError, Project, ReferenceInfo, Result, RuntimeError, SourceMapInfo, SymbolInfo,
SymbolKind, SymbolSource, TaskMonitorOptions, TaskMonitorReport, VarnodeInfo, start, started,
};
#[test]
fn public_surface_exports_lifecycle_handles_and_models() {
let address = Address {
space: "ram".to_string(),
offset: 0x1000,
text: "ram:00001000".to_string(),
};
let range = AddressRange {
start: address.clone(),
end: address.clone(),
};
let reference = ReferenceInfo {
from: address.clone(),
to: address.clone(),
reference_type: "UNCONDITIONAL_CALL".to_string(),
operand_index: 0,
primary: true,
external: false,
call: true,
data: false,
read: false,
write: false,
};
let varnode = VarnodeInfo {
space: "register".to_string(),
offset: 0,
size: 8,
text: "(register, 0, 8)".to_string(),
address: Some(address.clone()),
constant: false,
register: true,
unique: false,
address_tied: false,
input: false,
persistent: false,
};
let pcode_op = PcodeOpInfo {
sequence_number: "ram:00001000:0".to_string(),
sequence_target: address.clone(),
sequence_time: 0,
sequence_order: 0,
mnemonic: "COPY".to_string(),
opcode: 1,
output: Some(varnode.clone()),
inputs: vec![varnode.clone()],
};
let instruction = InstructionInfo {
address: address.clone(),
mnemonic: "CALL".to_string(),
pcode: vec![pcode_op.clone()],
operands: vec![OperandInfo {
index: 0,
text: "ram:00001000".to_string(),
objects: vec!["ram:00001000".to_string()],
references: vec![reference.clone()],
}],
length: 5,
bytes: vec![0xe8, 0, 0, 0, 0],
fallthrough: Some(address.clone()),
flow_type: "CALL".to_string(),
};
let data = DataInfo {
address: address.clone(),
range: range.clone(),
data_type: "string".to_string(),
length: 4,
value: Some("test".to_string()),
display: "\"test\"".to_string(),
};
let symbol = SymbolInfo {
name: "main".to_string(),
address: address.clone(),
kind: SymbolKind::Function,
source: SymbolSource::Analysis,
namespace: "Global".to_string(),
primary: true,
external: false,
};
let block = MemoryBlockInfo {
name: ".text".to_string(),
range: range.clone(),
size: 16,
initialized: true,
read: true,
write: false,
execute: true,
volatile: false,
overlay: false,
};
let decompile = DecompileResult {
status: DecompileStatus::Completed,
error_message: None,
c: Some("int main(void) { return 0; }".to_string()),
signature: Some("int main(void)".to_string()),
prototype: Some(FunctionPrototype {
return_type: "int".to_string(),
return_type_id: Some("type:int".to_string()),
calling_convention: Some("__stdcall".to_string()),
parameter_count: 0,
varargs: false,
inline: false,
no_return: false,
has_this_pointer: false,
}),
pcode: Some(PcodeSummary {
op_count: 1,
op_counts: vec![NamedCount {
name: "RETURN".to_string(),
count: 1,
}],
basic_block_count: 1,
varnode_count: 1,
varnode_space_counts: vec![NamedCount {
name: "register".to_string(),
count: 1,
}],
}),
high_pcode: Some(HighPcodeGraph {
blocks: vec![HighPcodeBlock {
id: "high_block_0".to_string(),
index: 0,
block_type: "basic".to_string(),
range: Some(range.clone()),
op_sequence_numbers: vec![pcode_op.sequence_number.clone()],
}],
edges: Vec::new(),
ops: vec![pcode_op.clone()],
}),
data_flow: Some(DataFlowGraph {
ops: vec![DataFlowOp {
id: "op_0".to_string(),
sequence_number: pcode_op.sequence_number.clone(),
mnemonic: pcode_op.mnemonic.clone(),
opcode: pcode_op.opcode,
block_id: Some("high_block_0".to_string()),
}],
varnodes: vec![DataFlowVarnode {
id: "varnode_0".to_string(),
kind: "register".to_string(),
variable_id: Some("variable_0".to_string()),
varnode: varnode.clone(),
}],
variables: vec![DataFlowVariable {
id: "variable_0".to_string(),
name: "argc".to_string(),
data_type: "int".to_string(),
type_id: Some("type:int".to_string()),
size: 4,
storage: "register".to_string(),
kind: "parameter".to_string(),
symbol_id: Some(1),
symbol_address: Some(address.clone()),
symbol_type: Some("PARAMETER".to_string()),
namespace: Some("main".to_string()),
}],
edges: vec![
DataFlowEdge {
source_id: "varnode_0".to_string(),
destination_id: "op_0".to_string(),
kind: "input".to_string(),
slot: Some(0),
},
DataFlowEdge {
source_id: "op_0".to_string(),
destination_id: "varnode_0".to_string(),
kind: "output".to_string(),
slot: None,
},
],
}),
parameters: vec![HighSymbol {
name: "argc".to_string(),
data_type: "int".to_string(),
type_id: Some("type:int".to_string()),
size: 4,
storage: "register".to_string(),
}],
local_symbols: vec![HighSymbol {
name: "local_0".to_string(),
data_type: "int".to_string(),
type_id: Some("type:int".to_string()),
size: 4,
storage: "stack[-0x4]".to_string(),
}],
};
let descriptor = FunctionDescriptor {
entry: address.clone(),
name: "main".to_string(),
};
let call_graph = ProgramCallGraph {
nodes: vec![
CallGraphNode {
id: "function:ram:00001000".to_string(),
kind: "function".to_string(),
name: "main".to_string(),
address: Some(address.clone()),
namespace: Some("Global".to_string()),
library: None,
function_id: Some("function:ram:00001000".to_string()),
symbol_id: Some("symbol:Global%1fmain%1ffunction".to_string()),
},
CallGraphNode {
id: "external:libc:puts:".to_string(),
kind: "external".to_string(),
name: "puts".to_string(),
address: None,
namespace: None,
library: Some("libc".to_string()),
function_id: None,
symbol_id: None,
},
],
edges: vec![CallGraphEdge {
id: "call:function:ram:00001000:ram:00001000:0:external:libc:puts:".to_string(),
source_id: "function:ram:00001000".to_string(),
destination_id: "external:libc:puts:".to_string(),
callsite: address.clone(),
reference_type: "UNCONDITIONAL_CALL".to_string(),
operand_index: 0,
computed: false,
thunk_resolved: false,
}],
};
let type_ref = ProgramType {
id: "type:int%1fint%1f4%1f/%1fbuiltin".to_string(),
name: "int".to_string(),
display_name: "int".to_string(),
size: 4,
alignment: 4,
category_path: Some("/".to_string()),
details: ProgramTypeDetails::Builtin,
};
let metadata = ProgramMetadata {
symbols: vec![ProgramSymbol {
id: "symbol:Global%1fmain%1ffunction".to_string(),
name: "main".to_string(),
kind: "function".to_string(),
source: "analysis".to_string(),
address: address.clone(),
namespace: "Global".to_string(),
primary: true,
external: false,
type_id: None,
}],
functions: vec![ProgramFunction {
id: "function:ram:00001000".to_string(),
name: "main".to_string(),
entry: address.clone(),
namespace: "Global".to_string(),
external: false,
thunk: false,
thunked_function_id: None,
symbol_id: Some("symbol:Global%1fmain%1ffunction".to_string()),
signature: ProgramFunctionSignature {
display: "int main(void)".to_string(),
calling_convention: "__stdcall".to_string(),
return_type_id: Some(type_ref.id.clone()),
parameters: vec![ProgramParameter {
ordinal: 0,
name: "argc".to_string(),
type_id: Some(type_ref.id.clone()),
storage: "register".to_string(),
}],
varargs: false,
no_return: false,
},
}],
types: vec![type_ref.clone()],
};
let control_flow = ControlFlowGraph {
blocks: vec![BasicBlockInfo {
id: "block_0".to_string(),
name: "main".to_string(),
range: range.clone(),
flow_type: "CALL".to_string(),
instruction_addresses: vec![address.clone()],
}],
edges: vec![ControlFlowEdgeInfo {
source_block_id: "block_0".to_string(),
destination_block_id: "block_0".to_string(),
flow_type: "FALL_THROUGH".to_string(),
source_address: Some(address.clone()),
destination_address: Some(address.clone()),
reference_address: Some(address.clone()),
}],
};
let import = ImportInfo {
name: "puts".to_string(),
library: Some("<EXTERNAL>".to_string()),
kind: ImportKind::Function,
address: Some(address.clone()),
reference_types: vec!["UNCONDITIONAL_CALL".to_string()],
};
let export = ExportInfo {
name: "main".to_string(),
address: address.clone(),
kind: ExportKind::EntryPoint,
};
let source_map = SourceMapInfo {
file_path: "sample.c".to_string(),
function_name: "main".to_string(),
line_start: Some(1),
line_end: Some(3),
};
let summary = FunctionSummary {
function: descriptor.clone(),
basic_block_count: 1,
instruction_count: 1,
callers: vec![address.clone()],
callees: vec![address.clone()],
strings: vec!["test".to_string()],
constants: vec!["0x0".to_string()],
data_refs: vec![address.clone()],
imports: vec![import.clone()],
exports: vec![export.clone()],
source_map: Some(source_map.clone()),
};
let function_control_flow = FunctionControlFlowGraph {
function: descriptor.clone(),
graph: control_flow.clone(),
};
assert_eq!(address.as_str(), "ram:00001000");
assert_eq!(instruction.operands[0].references, vec![reference]);
assert_eq!(data.range, range);
assert_eq!(symbol.kind, SymbolKind::Function);
assert!(block.execute);
assert_eq!(descriptor.name, "main");
assert_eq!(control_flow.blocks[0].instruction_addresses[0], address);
assert_eq!(import.kind, ImportKind::Function);
assert_eq!(export.kind, ExportKind::EntryPoint);
assert_eq!(source_map.function_name, "main");
assert_eq!(summary.function, descriptor);
assert_eq!(summary.imports[0].name, "puts");
assert_eq!(function_control_flow.graph, control_flow);
assert_eq!(decompile.status, DecompileStatus::Completed);
assert_eq!(call_graph.edges[0].destination_id, call_graph.nodes[1].id);
assert_eq!(metadata.functions[0].signature.parameters[0].name, "argc");
assert_eq!(metadata.types[0].details, ProgramTypeDetails::Builtin);
let type_index = metadata.type_index().expect("type index builds");
assert_eq!(
type_index
.signature_return_type(&metadata.functions[0].signature)
.expect("return type resolves")
.expect("return type is present")
.id,
type_ref.id
);
let _index_error = ProgramTypeIndexError::MissingType {
id: "type:missing".to_string(),
};
let _type_component = ProgramTypeComponent {
ordinal: 0,
name: "value".to_string(),
offset: 0,
length: 4,
type_id: Some(type_ref.id.clone()),
bit_size: None,
bit_offset: None,
comment: None,
};
let _enum_value = ProgramEnumValue {
name: "One".to_string(),
value: "1".to_string(),
comment: None,
};
let _start: fn(LaunchOptions) -> Result<GhidraRuntime> = start;
let _started: fn() -> bool = started;
let _ghidra_start: fn(LaunchOptions) -> Result<Ghidra> = Ghidra::start;
let _program_path = ProgramPath::root("sample").expect("valid path");
let _open_options = ProgramOpenOptions::new(ProgramPath::root("sample").expect("valid path"));
let _load_options = ProgramLoadOptions::new("sample")
.expect("valid load options")
.with_loader("ghidra.app.util.opinion.ElfLoader")
.with_language("x86:LE:64:default")
.with_compiler("gcc")
.with_loader_arg("key", "value");
let _loader_arg = LoaderArgument {
name: "key".to_string(),
value: "value".to_string(),
};
let _load_info = ProgramLoadInfo {
opened_existing: false,
project_path: "/sample".to_string(),
folder: "/".to_string(),
name: "sample".to_string(),
loader: None,
language: "x86:LE:64:default".to_string(),
compiler: "gcc".to_string(),
monitor: TaskMonitorReport {
timeout_seconds: None,
cancelled: false,
message: String::new(),
progress: 0,
maximum: 0,
},
};
let _analysis_options = AnalysisOptions {
mode: AnalysisMode::IfNeeded,
monitor: TaskMonitorOptions::none(),
};
let _analysis_report = AnalysisReport {
mode: AnalysisMode::IfNeeded,
analyzed: false,
already_analyzed: true,
message_log: String::new(),
monitor: TaskMonitorReport {
timeout_seconds: Some(5),
cancelled: false,
message: String::new(),
progress: 0,
maximum: 0,
},
};
let _decompile_options = DecompileOptions::new(60)
.with_monitor(TaskMonitorOptions::timeout(std::time::Duration::from_secs(5)).unwrap());
let _result: Result<()> = Ok(());
let _error: Option<Error> = None;
let _runtime_error: Option<RuntimeError> = None;
let _project: Option<Project> = None;
let _program: Option<Program<'_>> = None;
let _loaded_program: Option<LoadedProgram<'_>> = None;
let _transaction: Option<ProgramTransaction<'_>> = None;
let _decompiler: Option<Decompiler<'_>> = None;
}