use dotscope::prelude::*;
use std::{env, path::Path};
fn format_impl_code_type(code_type: &MethodImplCodeType) -> String {
match code_type.bits() {
0x0000 => "IL".to_string(),
0x0001 => "Native".to_string(),
0x0002 => "OPTIL".to_string(),
0x0003 => "Runtime".to_string(),
_ => format!("Unknown (0x{:04x})", code_type.bits()),
}
}
fn format_impl_management(management: &MethodImplManagement) -> String {
if management.bits() & 0x0004 != 0 {
"Unmanaged".to_string()
} else {
"Managed".to_string()
}
}
fn format_impl_options(options: &MethodImplOptions) -> String {
let mut parts = Vec::new();
let bits = options.bits();
if bits & 0x0008 != 0 {
parts.push("NoInlining");
}
if bits & 0x0010 != 0 {
parts.push("ForwardRef");
}
if bits & 0x0020 != 0 {
parts.push("Synchronized");
}
if bits & 0x0080 != 0 {
parts.push("PreserveSig");
}
if bits & 0x1000 != 0 {
parts.push("InternalCall");
}
if parts.is_empty() {
"Standard".to_string()
} else {
parts.join(", ")
}
}
fn main() -> Result<()> {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
eprintln!("Usage: {} <path-to-dotnet-assembly>", args[0]);
eprintln!();
eprintln!("This example performs exhaustive analysis of a selected method:");
eprintln!(" • Complete method metadata examination");
eprintln!(" • Signature and parameter analysis");
eprintln!(" • IL instruction disassembly");
eprintln!(" • Control flow and exception analysis");
eprintln!();
eprintln!("The example will automatically select a suitable method with IL code.");
return Ok(());
}
let path = Path::new(&args[1]);
println!("🔍 Comprehensive Method Analysis of: {}", path.display());
let assembly = CilObject::from_path(path)?;
let target_method = find_suitable_method(&assembly)?;
println!();
println!("{}", "=".repeat(80));
println!("📋 COMPREHENSIVE METHOD ANALYSIS");
println!("{}", "=".repeat(80));
analyze_method_completely(&assembly, target_method)?;
println!("\n✅ Comprehensive method analysis completed!");
Ok(())
}
fn find_suitable_method(assembly: &CilObject) -> Result<MethodRc> {
let methods = assembly.methods();
for entry in methods {
let method = entry.value();
if method.name == ".ctor" || method.name == ".cctor" {
continue;
}
if let Some(body) = method.body.get() {
if body.size_code > 50 && body.size_code < 500 {
println!(
"🎯 Selected method: {} (Token: 0x{:08X})",
method.name,
method.token.value()
);
println!(
" IL Size: {} bytes, Max Stack: {}",
body.size_code, body.max_stack
);
return Ok(method.clone());
}
}
}
for entry in methods {
let method = entry.value();
if method.body.get().is_some() {
println!(
"🎯 Selected method: {} (Token: 0x{:08X})",
method.name,
method.token.value()
);
return Ok(method.clone());
}
}
Err(Error::Other(
"No suitable method found for analysis".to_string(),
))
}
fn analyze_method_completely(_assembly: &CilObject, method: MethodRc) -> Result<()> {
print_method_basic_info(&method);
print_method_flags(&method);
print_method_signature(&method);
print_method_parameters(&method);
print_local_variables(&method);
print_generic_information(&method);
if let Some(body) = method.body.get() {
print_method_body_details(&method, body);
print_il_instructions(&method, body)?;
print_exception_handlers(body);
} else {
println!("\n❌ NO METHOD BODY");
println!(" This method is abstract, extern, or P/Invoke");
}
print_pinvoke_info(&method);
print_security_info(&method);
print_additional_metadata(&method);
Ok(())
}
fn print_method_basic_info(method: &Method) {
println!("\n📝 BASIC METHOD INFORMATION");
println!("{}", "-".repeat(50));
println!(" Name: {}", method.name);
println!(" Token: 0x{:08X}", method.token.value());
println!(" RID: {}", method.rid);
println!(" Metadata Offset: 0x{:X}", method.meta_offset);
if let Some(rva) = method.rva {
println!(" RVA: 0x{rva:08X}");
} else {
println!(" RVA: None (abstract/extern method)");
}
}
fn print_method_flags(method: &Method) {
println!("\n🏷️ METHOD FLAGS AND ATTRIBUTES");
println!("{}", "-".repeat(50));
println!(
" Implementation Code Type: {}",
format_impl_code_type(&method.impl_code_type)
);
println!(
" Implementation Management: {}",
format_impl_management(&method.impl_management)
);
println!(
" Implementation Options: {}",
format_impl_options(&method.impl_options)
);
println!(" Access Flags: {:08b}", method.flags_access.bits());
println!(" VTable Flags: {:08b}", method.flags_vtable.bits());
println!(" Modifiers: {:08b}", method.flags_modifiers.bits());
let pinvoke_flags = method
.flags_pinvoke
.load(std::sync::atomic::Ordering::Relaxed);
if pinvoke_flags != 0 {
println!(" P/Invoke Flags: 0x{pinvoke_flags:08X}");
}
}
fn print_method_signature(method: &Method) {
println!("\n📋 METHOD SIGNATURE");
println!("{}", "-".repeat(50));
let sig = &method.signature;
println!(" Calling Convention:");
println!(" Has This: {}", sig.has_this);
println!(" Explicit This: {}", sig.explicit_this);
println!(" Default: {}", sig.default);
println!(" VarArg: {}", sig.vararg);
println!(" CDecl: {}", sig.cdecl);
println!(" StdCall: {}", sig.stdcall);
println!(" ThisCall: {}", sig.thiscall);
println!(" FastCall: {}", sig.fastcall);
println!(" Parameter Counts:");
println!(" Generic Parameter Count: {}", sig.param_count_generic);
println!(" Parameter Count: {}", sig.param_count);
println!(" Return Type:");
print_signature_parameter(&sig.return_type, " ");
}
fn print_signature_parameter(param: &SignatureParameter, indent: &str) {
println!("{indent}Type: String"); println!("{}By Reference: {}", indent, param.by_ref);
if !param.modifiers.is_empty() {
println!(
"{}Custom Modifiers: {} modifier(s)",
indent,
param.modifiers.len()
);
for (i, modifier) in param.modifiers.iter().enumerate() {
println!(
"{} [{}]: Token 0x{:08X} ({})",
indent,
i,
modifier.modifier_type.value(),
if modifier.is_required {
"required"
} else {
"optional"
}
);
}
}
}
fn print_method_parameters(method: &Method) {
println!("\n🔧 PARAMETERS");
println!("{}", "-".repeat(50));
let param_count = method.params.iter().count();
if param_count == 0 {
println!(" No parameters");
} else {
for (i, param) in method.params.iter() {
println!(" Parameter [{i}]:");
println!(
" Name: {}",
param.name.as_ref().unwrap_or(&"<unnamed>".to_string())
);
println!(" Sequence: {}", param.sequence);
println!(" Flags: 0x{:04X}", param.flags);
if let Some(default_value) = param.default.get() {
println!(" Default Value: {default_value:?}");
}
}
}
println!("\n Signature Parameters:");
for (i, param) in method.signature.params.iter().enumerate() {
println!(" Parameter [{i}] from signature:");
print_signature_parameter(param, " ");
}
let vararg_count = method.varargs.count();
if vararg_count > 0 {
println!("\n VarArg Parameters:");
for (i, vararg) in method.varargs.iter() {
println!(" VarArg [{i}]:");
println!(" Type: <VarArg Type>"); println!(" By Reference: {}", vararg.by_ref);
if vararg.modifiers.is_empty() {
println!(" Custom Modifiers: None");
} else {
println!(
" Custom Modifiers: {} modifier(s)",
vararg.modifiers.count()
);
for (j, modifier) in vararg.modifiers.iter() {
println!(
" [{}]: Token 0x{:08X}",
j,
modifier.token().unwrap().value()
);
}
}
}
}
}
fn print_local_variables(method: &Method) {
println!("\n🗂️ LOCAL VARIABLES");
println!("{}", "-".repeat(50));
let local_count = method.local_vars.count();
if local_count == 0 {
println!(" No local variables");
} else {
for (i, (_, local_var)) in method.local_vars.iter().enumerate() {
println!(" Local Variable [{i}]:");
println!(" Type: LocalVar");
println!(" Is ByRef: {}", local_var.is_byref);
println!(" Is Pinned: {}", local_var.is_pinned);
if !local_var.modifiers.is_empty() {
println!(
" Custom Modifiers: {} modifier(s)",
local_var.modifiers.len()
);
for (j, _modifier) in local_var.modifiers.iter().enumerate() {
println!(" [{j}]: Custom modifier");
}
}
}
}
}
fn print_generic_information(method: &Method) {
println!("\n🧬 GENERIC INFORMATION");
println!("{}", "-".repeat(50));
let generic_param_count = method.generic_params.count();
if generic_param_count == 0 {
println!(" No generic parameters");
} else {
for (i, (_, generic_param)) in method.generic_params.iter().enumerate() {
println!(" Generic Parameter [{i}]:");
println!(" Name: {}", generic_param.name);
println!(" Number: {}", generic_param.number);
println!(" Flags: 0x{:04X}", generic_param.flags);
if let Some(_owner) = generic_param.owner.get() {
println!(" Owner: <Referenced Type>");
}
}
}
let generic_arg_count = method.generic_args.count();
if generic_arg_count > 0 {
println!("\n Generic Arguments (Method Specifications):");
for (i, (_, method_spec)) in method.generic_args.iter().enumerate() {
println!(" MethodSpec [{i}]:");
println!(" Token: 0x{:08X}", method_spec.token.value());
println!(" RID: {}", method_spec.rid);
if method_spec.generic_args.count() > 0 {
println!(" Resolved Types:");
for (j, (_, resolved_type)) in method_spec.generic_args.iter().enumerate() {
if let Some(type_name) = resolved_type.name() {
println!(" [{j}]: {type_name}");
if let Some(namespace) = resolved_type.namespace() {
if !namespace.is_empty() {
println!(" Namespace: {namespace}");
}
}
if let Some(token) = resolved_type.token() {
println!(" Token: 0x{:08X}", token.value());
}
} else {
println!(" [{j}]: <Unknown Type>");
}
}
}
if !method_spec.instantiation.generic_args.is_empty() {
println!(
" Signature Arguments: {} type(s)",
method_spec.instantiation.generic_args.len()
);
for (j, sig_arg) in method_spec.instantiation.generic_args.iter().enumerate() {
println!(" [{j}]: {sig_arg:?}");
}
}
}
}
}
fn print_method_body_details(_method: &Method, body: &MethodBody) {
println!("\n🏗️ METHOD BODY DETAILS");
println!("{}", "-".repeat(50));
println!(" IL Code Size: {} bytes", body.size_code);
println!(" Header Size: {} bytes", body.size_header);
println!(" Maximum Stack Size: {}", body.max_stack);
println!(
" Local Variable Signature Token: 0x{:08X}",
body.local_var_sig_token
);
println!(" Flags:");
println!(" Is Fat Header: {}", body.is_fat);
println!(" Initialize Locals: {}", body.is_init_local);
println!(" Has Exception Data: {}", body.is_exception_data);
println!(
" Exception Handlers: {} handler(s)",
body.exception_handlers.len()
);
}
fn print_il_instructions(method: &Method, body: &MethodBody) -> Result<()> {
println!("\n🔍 COMPREHENSIVE IL INSTRUCTION ANALYSIS");
println!("{}", "=".repeat(70));
if body.size_code == 0 {
println!(" No IL code to analyze");
return Ok(());
}
print_basic_il_statistics(method, body);
print_basic_block_analysis(method);
print_instruction_stream_analysis(method)?;
print_control_flow_analysis(method);
Ok(())
}
fn print_basic_il_statistics(method: &Method, body: &MethodBody) {
println!("\n📊 BASIC IL STATISTICS");
println!("{}", "-".repeat(50));
let block_count = method.block_count();
let instruction_count = method.instruction_count();
println!(" IL Code Size: {} bytes", body.size_code);
println!(" Basic Blocks: {block_count}");
println!(" Total Instructions: {instruction_count}");
if block_count > 0 {
let avg_instructions_per_block = instruction_count as f64 / block_count as f64;
println!(" Average Instructions per Block: {avg_instructions_per_block:.1}");
}
if body.size_code > 0 {
let avg_instruction_size = body.size_code as f64 / instruction_count.max(1) as f64;
println!(" Average Instruction Size: {avg_instruction_size:.1} bytes");
}
}
fn print_basic_block_analysis(method: &Method) {
println!("\n🧱 BASIC BLOCK ANALYSIS");
println!("{}", "-".repeat(50));
let block_count = method.block_count();
if block_count == 0 {
println!(" No basic blocks found");
return;
}
println!(" Basic Block Details:");
for (block_id, block) in method.blocks().take(10) {
let predecessor_count = block.predecessors.len();
let successor_count = block.successors.len();
let exception_count = block.exceptions.len();
println!(" Block {} (RVA: 0x{:08X}):", block_id, block.rva);
println!(" Instructions: {}", block.instructions.len());
println!(" Size: {} bytes", block.size);
println!(" Predecessors: {predecessor_count}");
println!(" Successors: {successor_count}");
if exception_count > 0 {
println!(" Exception regions: {exception_count}");
}
if !block.predecessors.is_empty() {
println!(" ← From blocks: {:?}", block.predecessors);
}
if !block.successors.is_empty() {
println!(" → To blocks: {:?}", block.successors);
}
println!();
}
if block_count > 10 {
println!(" ... ({} more blocks)", block_count - 10);
}
}
fn print_instruction_stream_analysis(method: &Method) -> Result<()> {
println!("\n🔄 INSTRUCTION STREAM ANALYSIS");
println!("{}", "-".repeat(50));
let mut instruction_stats = std::collections::HashMap::new();
let mut category_stats = std::collections::HashMap::new();
let mut flow_type_stats = std::collections::HashMap::new();
let mut stack_effects = Vec::new();
let mut branch_targets = std::collections::HashSet::new();
let total_instructions = method.instruction_count();
println!(" Analyzing {total_instructions} instructions using InstructionIterator...");
for (i, instruction) in method.instructions().enumerate() {
*instruction_stats
.entry(instruction.mnemonic.to_string())
.or_insert(0) += 1;
let category_name = format!("{:?}", instruction.category);
*category_stats.entry(category_name).or_insert(0) += 1;
let flow_name = format!("{:?}", instruction.flow_type);
*flow_type_stats.entry(flow_name).or_insert(0) += 1;
stack_effects.push(instruction.stack_behavior.net_effect);
for target in &instruction.branch_targets {
branch_targets.insert(*target);
}
if i < 15 {
let operand_str = format_operand(&instruction.operand);
let operand_display = if !operand_str.is_empty() {
format!(" {operand_str}")
} else {
String::new()
};
println!(
" [{:3}] IL_{:04X}: {:<12} 0x{:02X} (stack: {:+}, flow: {:?}){}",
i,
instruction.rva,
instruction.mnemonic,
instruction.opcode,
instruction.stack_behavior.net_effect,
instruction.flow_type,
operand_display
);
} else if i == 15 {
println!(" ... ({} more instructions)", total_instructions - 15);
}
}
println!("\n 📈 INSTRUCTION STATISTICS:");
if !instruction_stats.is_empty() {
println!(" Most Common Instructions:");
let mut sorted_stats: Vec<_> = instruction_stats.iter().collect();
sorted_stats.sort_by(|a, b| b.1.cmp(a.1));
for (mnemonic, count) in sorted_stats.iter().take(8) {
let percentage = (**count as f64 / total_instructions as f64) * 100.0;
println!(" {mnemonic:<12}: {count:3} times ({percentage:.1}%)");
}
}
if !category_stats.is_empty() {
println!("\n Instruction Categories:");
let mut sorted_categories: Vec<_> = category_stats.iter().collect();
sorted_categories.sort_by(|a, b| b.1.cmp(a.1));
for (category, count) in sorted_categories.iter() {
let percentage = (**count as f64 / total_instructions as f64) * 100.0;
println!(" {category:<15}: {count:3} instructions ({percentage:.1}%)");
}
}
if !stack_effects.is_empty() {
let total_stack_effect: i32 = stack_effects.iter().map(|&x| x as i32).sum();
let max_stack_push = stack_effects.iter().max().unwrap_or(&0);
let max_stack_pop = stack_effects.iter().min().unwrap_or(&0);
println!("\n Stack Behavior Analysis:");
println!(" Net stack effect: {total_stack_effect:+}");
println!(" Maximum stack push: +{max_stack_push}");
println!(" Maximum stack pop: {max_stack_pop}");
}
if !branch_targets.is_empty() {
println!("\n Branch Target Analysis:");
println!(" Unique branch targets: {}", branch_targets.len());
let sorted_targets: Vec<_> = branch_targets.iter().collect();
if sorted_targets.len() <= 5 {
println!(" Targets: {sorted_targets:?}");
} else {
println!(" First 5 targets: {:?}...", &sorted_targets[0..5]);
}
}
Ok(())
}
fn print_control_flow_analysis(method: &Method) {
println!("\n🌊 CONTROL FLOW ANALYSIS");
println!("{}", "-".repeat(50));
let block_count = method.block_count();
if block_count == 0 {
println!(" No control flow to analyze");
return;
}
let mut entry_blocks = 0;
let mut exit_blocks = 0;
let mut branch_blocks = 0;
let mut simple_blocks = 0;
for (_, block) in method.blocks() {
match (block.predecessors.len(), block.successors.len()) {
(0, _) => entry_blocks += 1,
(_, 0) => exit_blocks += 1,
(_, n) if n > 1 => branch_blocks += 1,
_ => simple_blocks += 1,
}
}
println!(" Control Flow Characteristics:");
println!(" Entry blocks (no predecessors): {entry_blocks}");
println!(" Exit blocks (no successors): {exit_blocks}");
println!(" Branch blocks (multiple successors): {branch_blocks}");
println!(" Simple blocks (single flow): {simple_blocks}");
let cyclomatic_complexity = method
.blocks()
.map(|(_, block)| block.successors.len().saturating_sub(1))
.sum::<usize>()
+ 1;
println!("\n Complexity Metrics:");
println!(" Cyclomatic Complexity: {cyclomatic_complexity}");
if cyclomatic_complexity <= 5 {
println!(" Complexity Assessment: Low (simple method)");
} else if cyclomatic_complexity <= 10 {
println!(" Complexity Assessment: Moderate");
} else {
println!(" Complexity Assessment: High (complex method)");
}
}
fn format_operand(operand: &dotscope::assembly::Operand) -> String {
match operand {
dotscope::assembly::Operand::None => String::new(),
dotscope::assembly::Operand::Immediate(imm) => format!("{imm:?}"),
dotscope::assembly::Operand::Token(token) => format!("token:0x{:08X}", token.value()),
dotscope::assembly::Operand::Target(target) => format!("IL_{target:04X}"),
dotscope::assembly::Operand::Switch(targets) => {
format!("switch({} targets)", targets.len())
}
dotscope::assembly::Operand::Local(idx) => format!("local:{idx}"),
dotscope::assembly::Operand::Argument(idx) => format!("arg:{idx}"),
}
}
fn print_exception_handlers(body: &MethodBody) {
println!("\n🚨 EXCEPTION HANDLERS");
println!("{}", "-".repeat(50));
if body.exception_handlers.is_empty() {
println!(" No exception handlers");
} else {
for (i, handler) in body.exception_handlers.iter().enumerate() {
println!(" Exception Handler [{i}]:");
println!(" Flags: {:08b}", handler.flags.bits());
println!(" Try Block:");
println!(" Offset: 0x{:04X}", handler.try_offset);
println!(" Length: {} bytes", handler.try_length);
println!(" Handler Block:");
println!(" Offset: 0x{:04X}", handler.handler_offset);
println!(" Length: {} bytes", handler.handler_length);
if handler.flags.bits() & 0x01 != 0 {
println!(" Type: Finally/Fault handler");
} else {
println!(" Type: Catch handler");
if let Some(_handler_type) = &handler.handler {
println!(" Exception Type: <exception type>");
}
}
}
}
}
fn print_pinvoke_info(method: &Method) {
println!("\n🔗 P/INVOKE INFORMATION");
println!("{}", "-".repeat(50));
let pinvoke_flags = method
.flags_pinvoke
.load(std::sync::atomic::Ordering::Relaxed);
if pinvoke_flags != 0 {
println!(" P/Invoke Flags: 0x{pinvoke_flags:08X}");
println!(" This method is a P/Invoke method");
} else {
println!(" Not a P/Invoke method");
}
}
fn print_security_info(method: &Method) {
println!("\n🔒 SECURITY INFORMATION");
println!("{}", "-".repeat(50));
if method.security.get().is_some() {
println!(" Security attributes present");
} else {
println!(" No security attributes");
}
}
fn print_additional_metadata(method: &Method) {
println!("\n📊 ADDITIONAL METADATA");
println!("{}", "-".repeat(50));
println!(" Method Implementation:");
println!(" Is IL Code: {}", method.is_code_il());
if method.overrides.count() > 0 {
println!(" Method Overrides:");
println!(" Override count: {}", method.overrides.count());
}
let interface_impl_count = method.interface_impls.iter().count();
if interface_impl_count > 0 {
println!(" Interface Implementations:");
println!(" Interface methods: {interface_impl_count}");
}
println!(" Memory Layout:");
println!(
" Parameters List Size: {}",
method.params.iter().count()
);
println!(" VarArgs Size: {}", method.varargs.iter().count());
println!(
" Local Variables Size: {}",
method.local_vars.iter().count()
);
println!(
" Generic Parameters Size: {}",
method.generic_params.iter().count()
);
println!(
" Generic Arguments Size: {}",
method.generic_args.iter().count()
);
println!(" Basic Blocks Size: {}", method.block_count());
}