mirror of
https://github.com/sampletext32/ParkanPlayground.git
synced 2025-07-01 12:40:25 +03:00
decompiler iter1
This commit is contained in:
125
X86Disassembler/Analysers/AnalyzerContext.cs
Normal file
125
X86Disassembler/Analysers/AnalyzerContext.cs
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
namespace X86Disassembler.Analysers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Central context for all analysis data related to a disassembled function
|
||||||
|
/// </summary>
|
||||||
|
public class AnalyzerContext
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The function being analyzed
|
||||||
|
/// </summary>
|
||||||
|
public AsmFunction Function { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dictionary mapping block addresses to instruction blocks
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<ulong, InstructionBlock> BlocksByAddress { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dictionary mapping loop header addresses to loops
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<ulong, Loop> LoopsByHeaderAddress { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dictionary mapping block addresses to the loops that contain them
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<ulong, List<Loop>> LoopsByBlockAddress { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dictionary for storing arbitrary analysis data by address
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<ulong, Dictionary<string, object>> AnalysisDataByAddress { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new analyzer context for the given function
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="function">The function to analyze</param>
|
||||||
|
public AnalyzerContext(AsmFunction function)
|
||||||
|
{
|
||||||
|
Function = function;
|
||||||
|
|
||||||
|
// Initialize the block dictionary
|
||||||
|
foreach (var block in function.Blocks)
|
||||||
|
{
|
||||||
|
BlocksByAddress[block.Address] = block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a loop in the control flow graph
|
||||||
|
/// </summary>
|
||||||
|
public class Loop
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The header block of the loop (the entry point into the loop)
|
||||||
|
/// </summary>
|
||||||
|
public InstructionBlock Header { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The blocks that are part of this loop
|
||||||
|
/// </summary>
|
||||||
|
public List<InstructionBlock> Blocks { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The back edge that completes the loop (from a block back to the header)
|
||||||
|
/// </summary>
|
||||||
|
public (InstructionBlock From, InstructionBlock To) BackEdge { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The exit blocks of the loop (blocks that have successors outside the loop)
|
||||||
|
/// </summary>
|
||||||
|
public List<InstructionBlock> ExitBlocks { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores analysis data for a specific address
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">The address to store data for</param>
|
||||||
|
/// <param name="key">The key for the data</param>
|
||||||
|
/// <param name="value">The data to store</param>
|
||||||
|
public void StoreAnalysisData(ulong address, string key, object value)
|
||||||
|
{
|
||||||
|
if (!AnalysisDataByAddress.TryGetValue(address, out var dataDict))
|
||||||
|
{
|
||||||
|
dataDict = [];
|
||||||
|
AnalysisDataByAddress[address] = dataDict;
|
||||||
|
}
|
||||||
|
|
||||||
|
dataDict[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves analysis data for a specific address
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">The address to retrieve data for</param>
|
||||||
|
/// <param name="key">The key for the data</param>
|
||||||
|
/// <returns>The stored data, or null if not found</returns>
|
||||||
|
public object? GetAnalysisData(ulong address, string key)
|
||||||
|
{
|
||||||
|
if (AnalysisDataByAddress.TryGetValue(address, out var dataDict) &&
|
||||||
|
dataDict.TryGetValue(key, out var value))
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves typed analysis data for a specific address
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of data to retrieve</typeparam>
|
||||||
|
/// <param name="address">The address to retrieve data for</param>
|
||||||
|
/// <param name="key">The key for the data</param>
|
||||||
|
/// <returns>The stored data, or default(T) if not found or wrong type</returns>
|
||||||
|
public T? GetAnalysisData<T>(ulong address, string key)
|
||||||
|
{
|
||||||
|
var data = GetAnalysisData(address, key);
|
||||||
|
if (data is T typedData)
|
||||||
|
{
|
||||||
|
return typedData;
|
||||||
|
}
|
||||||
|
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}
|
@ -28,14 +28,60 @@ public class AsmFunction
|
|||||||
b.Instructions[^1].Type.IsRet()).ToList();
|
b.Instructions[^1].Type.IsRet()).ToList();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns a string representation of the function, including its address and blocks
|
/// The analyzer context for this function
|
||||||
|
/// </summary>
|
||||||
|
public AnalyzerContext Context { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new AsmFunction instance
|
||||||
|
/// </summary>
|
||||||
|
public AsmFunction()
|
||||||
|
{
|
||||||
|
Context = new AnalyzerContext(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Analyzes the function using various analyzers
|
||||||
|
/// </summary>
|
||||||
|
public void Analyze()
|
||||||
|
{
|
||||||
|
// Analyze loops
|
||||||
|
var loopAnalyzer = new LoopAnalyzer();
|
||||||
|
loopAnalyzer.AnalyzeLoops(Context);
|
||||||
|
|
||||||
|
// Analyze data flow
|
||||||
|
var dataFlowAnalyzer = new DataFlowAnalyzer();
|
||||||
|
dataFlowAnalyzer.AnalyzeDataFlow(Context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a string representation of the function, including its address, blocks, and analysis results
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
|
string loopsInfo = "";
|
||||||
|
if (Context.LoopsByHeaderAddress.Count > 0)
|
||||||
|
{
|
||||||
|
loopsInfo = $"Loops: {Context.LoopsByHeaderAddress.Count}\n";
|
||||||
|
int i = 0;
|
||||||
|
foreach (var loop in Context.LoopsByHeaderAddress.Values)
|
||||||
|
{
|
||||||
|
loopsInfo += $" Loop {i++}: Header=0x{loop.Header.Address:X8}, " +
|
||||||
|
$"Blocks={loop.Blocks.Count}, " +
|
||||||
|
$"Back Edge=(0x{loop.BackEdge.From.Address:X8} -> 0x{loop.BackEdge.To.Address:X8}), " +
|
||||||
|
$"Exits={loop.ExitBlocks.Count}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
loopsInfo = "Loops: None\n";
|
||||||
|
}
|
||||||
|
|
||||||
return $"Function at 0x{Address:X8}\n" +
|
return $"Function at 0x{Address:X8}\n" +
|
||||||
$"Entry Block: 0x{EntryBlock?.Address.ToString("X8") ?? "None"}\n" +
|
$"Entry Block: 0x{EntryBlock?.Address.ToString("X8") ?? "None"}\n" +
|
||||||
$"Exit Blocks: {(ExitBlocks.Count > 0 ? string.Join(", ", ExitBlocks.Select(b => $"0x{b.Address:X8}")) : "None")}\n" +
|
$"Exit Blocks: {(ExitBlocks.Count > 0 ? string.Join(", ", ExitBlocks.Select(b => $"0x{b.Address:X8}")) : "None")}\n" +
|
||||||
$"Total Blocks: {Blocks.Count}\n" +
|
$"Total Blocks: {Blocks.Count}\n" +
|
||||||
|
loopsInfo +
|
||||||
$"{string.Join("\n", Blocks.Select(x => $"\t{x}"))}";
|
$"{string.Join("\n", Blocks.Select(x => $"\t{x}"))}";
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,5 @@
|
|||||||
using X86Disassembler.X86;
|
using X86Disassembler.X86;
|
||||||
|
using X86Disassembler.X86.Operands;
|
||||||
|
|
||||||
namespace X86Disassembler.Analysers;
|
namespace X86Disassembler.Analysers;
|
||||||
|
|
||||||
@ -138,11 +139,45 @@ public class BlockDisassembler
|
|||||||
var newBlock = RegisterBlock(blocks, address, instructions, currentBlock, false, false);
|
var newBlock = RegisterBlock(blocks, address, instructions, currentBlock, false, false);
|
||||||
blocksByAddress[address] = newBlock;
|
blocksByAddress[address] = newBlock;
|
||||||
|
|
||||||
// Queue the jump target address for processing
|
// Register the target block if it doesn't exist yet
|
||||||
|
InstructionBlock? jumpTargetBlock = null;
|
||||||
|
if (blocksByAddress.TryGetValue(jumpTargetAddress, out var existingTargetBlock))
|
||||||
|
{
|
||||||
|
jumpTargetBlock = existingTargetBlock;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We'll create this block later when we process the queue
|
||||||
|
// For now, just queue it for processing
|
||||||
addressQueue.Enqueue(jumpTargetAddress);
|
addressQueue.Enqueue(jumpTargetAddress);
|
||||||
|
}
|
||||||
|
|
||||||
// Queue the fall-through address (next instruction after this jump)
|
// Register the fall-through block if it doesn't exist yet
|
||||||
|
InstructionBlock? fallThroughBlock = null;
|
||||||
|
if (blocksByAddress.TryGetValue(fallThroughAddress, out var existingFallThroughBlock))
|
||||||
|
{
|
||||||
|
fallThroughBlock = existingFallThroughBlock;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We'll create this block later when we process the queue
|
||||||
|
// For now, just queue it for processing
|
||||||
addressQueue.Enqueue(fallThroughAddress);
|
addressQueue.Enqueue(fallThroughAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the jump target block exists, add it as a successor to the current block
|
||||||
|
if (jumpTargetBlock != null)
|
||||||
|
{
|
||||||
|
newBlock.Successors.Add(jumpTargetBlock);
|
||||||
|
jumpTargetBlock.Predecessors.Add(newBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the fall-through block exists, add it as a successor to the current block
|
||||||
|
if (fallThroughBlock != null)
|
||||||
|
{
|
||||||
|
newBlock.Successors.Add(fallThroughBlock);
|
||||||
|
fallThroughBlock.Predecessors.Add(newBlock);
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -158,8 +193,25 @@ public class BlockDisassembler
|
|||||||
var newBlock = RegisterBlock(blocks, address, instructions, currentBlock, false, false);
|
var newBlock = RegisterBlock(blocks, address, instructions, currentBlock, false, false);
|
||||||
blocksByAddress[address] = newBlock;
|
blocksByAddress[address] = newBlock;
|
||||||
|
|
||||||
// Queue the jump target address for processing
|
// Register the target block if it doesn't exist yet
|
||||||
|
InstructionBlock? jumpTargetBlock = null;
|
||||||
|
if (blocksByAddress.TryGetValue(jumpTargetAddress, out var existingTargetBlock))
|
||||||
|
{
|
||||||
|
jumpTargetBlock = existingTargetBlock;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We'll create this block later when we process the queue
|
||||||
|
// For now, just queue it for processing
|
||||||
addressQueue.Enqueue(jumpTargetAddress);
|
addressQueue.Enqueue(jumpTargetAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the jump target block exists, add it as a successor to the current block
|
||||||
|
if (jumpTargetBlock != null)
|
||||||
|
{
|
||||||
|
newBlock.Successors.Add(jumpTargetBlock);
|
||||||
|
jumpTargetBlock.Predecessors.Add(newBlock);
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -181,11 +233,201 @@ public class BlockDisassembler
|
|||||||
// we need to sort the blocks ourselves
|
// we need to sort the blocks ourselves
|
||||||
blocks.Sort((b1, b2) => b1.Address.CompareTo(b2.Address));
|
blocks.Sort((b1, b2) => b1.Address.CompareTo(b2.Address));
|
||||||
|
|
||||||
// Convert all block addresses from file offsets to RVA
|
// First, establish the successor and predecessor relationships based on file offsets
|
||||||
|
// This is done by analyzing the last instruction of each block
|
||||||
foreach (var block in blocks)
|
foreach (var block in blocks)
|
||||||
{
|
{
|
||||||
// Convert from file offset to RVA by adding the base address
|
if (block.Instructions.Count == 0) continue;
|
||||||
block.Address += _baseAddress;
|
|
||||||
|
var lastInstruction = block.Instructions[^1];
|
||||||
|
|
||||||
|
// Check if the last instruction is a conditional jump
|
||||||
|
if (lastInstruction.Type.IsConditionalJump())
|
||||||
|
{
|
||||||
|
// Get the jump target address (file offset)
|
||||||
|
ulong targetAddress = 0;
|
||||||
|
if (lastInstruction.StructuredOperands.Count > 0 && lastInstruction.StructuredOperands[0] is RelativeOffsetOperand relOp)
|
||||||
|
{
|
||||||
|
targetAddress = relOp.TargetAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the target block
|
||||||
|
var targetBlock = blocks.FirstOrDefault(b => b.Address == targetAddress);
|
||||||
|
if (targetBlock != null)
|
||||||
|
{
|
||||||
|
// Add the target block as a successor to this block
|
||||||
|
if (!block.Successors.Contains(targetBlock))
|
||||||
|
{
|
||||||
|
block.Successors.Add(targetBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add this block as a predecessor to the target block
|
||||||
|
if (!targetBlock.Predecessors.Contains(block))
|
||||||
|
{
|
||||||
|
targetBlock.Predecessors.Add(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For conditional jumps, also add the fall-through block as a successor
|
||||||
|
// The fall-through block is the one that immediately follows this block in memory
|
||||||
|
// Find the next block in address order
|
||||||
|
var nextBlock = blocks.OrderBy(b => b.Address).FirstOrDefault(b => b.Address > block.Address);
|
||||||
|
if (nextBlock != null)
|
||||||
|
{
|
||||||
|
// The fall-through block is the one that immediately follows this block in memory
|
||||||
|
var fallThroughBlock = nextBlock;
|
||||||
|
|
||||||
|
// Add the fall-through block as a successor to this block
|
||||||
|
if (!block.Successors.Contains(fallThroughBlock))
|
||||||
|
{
|
||||||
|
block.Successors.Add(fallThroughBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add this block as a predecessor to the fall-through block
|
||||||
|
if (!fallThroughBlock.Predecessors.Contains(block))
|
||||||
|
{
|
||||||
|
fallThroughBlock.Predecessors.Add(block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check if the last instruction is an unconditional jump
|
||||||
|
else if (lastInstruction.Type == InstructionType.Jmp)
|
||||||
|
{
|
||||||
|
// Get the jump target address (file offset)
|
||||||
|
ulong targetAddress = 0;
|
||||||
|
if (lastInstruction.StructuredOperands.Count > 0 && lastInstruction.StructuredOperands[0] is RelativeOffsetOperand relOp)
|
||||||
|
{
|
||||||
|
targetAddress = relOp.TargetAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the target block
|
||||||
|
var targetBlock = blocks.FirstOrDefault(b => b.Address == targetAddress);
|
||||||
|
if (targetBlock != null)
|
||||||
|
{
|
||||||
|
// Add the target block as a successor to this block
|
||||||
|
if (!block.Successors.Contains(targetBlock))
|
||||||
|
{
|
||||||
|
block.Successors.Add(targetBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add this block as a predecessor to the target block
|
||||||
|
if (!targetBlock.Predecessors.Contains(block))
|
||||||
|
{
|
||||||
|
targetBlock.Predecessors.Add(block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// For non-jump instructions that don't end the function (like Ret), add the fall-through block
|
||||||
|
else if (!lastInstruction.Type.IsRet())
|
||||||
|
{
|
||||||
|
// The fall-through block is the one that immediately follows this block in memory
|
||||||
|
// Find the next block in address order
|
||||||
|
var nextBlock = blocks.OrderBy(b => b.Address).FirstOrDefault(b => b.Address > block.Address);
|
||||||
|
if (nextBlock != null)
|
||||||
|
{
|
||||||
|
// The fall-through block is the one that immediately follows this block in memory
|
||||||
|
var fallThroughBlock = nextBlock;
|
||||||
|
|
||||||
|
// Add the fall-through block as a successor to this block
|
||||||
|
if (!block.Successors.Contains(fallThroughBlock))
|
||||||
|
{
|
||||||
|
block.Successors.Add(fallThroughBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add this block as a predecessor to the fall-through block
|
||||||
|
if (!fallThroughBlock.Predecessors.Contains(block))
|
||||||
|
{
|
||||||
|
fallThroughBlock.Predecessors.Add(block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the original file offset for each block in a dictionary
|
||||||
|
Dictionary<InstructionBlock, ulong> blockToFileOffset = new Dictionary<InstructionBlock, ulong>();
|
||||||
|
foreach (var block in blocks)
|
||||||
|
{
|
||||||
|
blockToFileOffset[block] = block.Address;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert all block addresses from file offsets to RVA
|
||||||
|
// and update the block dictionary for quick lookup
|
||||||
|
Dictionary<ulong, InstructionBlock> rvaBlocksByAddress = new Dictionary<ulong, InstructionBlock>();
|
||||||
|
Dictionary<ulong, ulong> fileOffsetToRvaMap = new Dictionary<ulong, ulong>();
|
||||||
|
|
||||||
|
// First pass: create a mapping from file offset to RVA for each block
|
||||||
|
foreach (var block in blocks)
|
||||||
|
{
|
||||||
|
// Get the original file offset address
|
||||||
|
ulong blockFileOffset = block.Address;
|
||||||
|
|
||||||
|
// Calculate the RVA address
|
||||||
|
ulong blockRvaAddress = blockFileOffset + _baseAddress;
|
||||||
|
|
||||||
|
// Store the mapping
|
||||||
|
fileOffsetToRvaMap[blockFileOffset] = blockRvaAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second pass: update all blocks to use RVA addresses
|
||||||
|
foreach (var block in blocks)
|
||||||
|
{
|
||||||
|
// Get the original file offset address
|
||||||
|
ulong blockFileOffset = block.Address;
|
||||||
|
|
||||||
|
// Update the block's address to RVA
|
||||||
|
ulong blockRvaAddress = fileOffsetToRvaMap[blockFileOffset];
|
||||||
|
block.Address = blockRvaAddress;
|
||||||
|
|
||||||
|
// Add to the dictionary for quick lookup
|
||||||
|
rvaBlocksByAddress[blockRvaAddress] = block;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now update all successors and predecessors to use the correct RVA addresses
|
||||||
|
foreach (var block in blocks)
|
||||||
|
{
|
||||||
|
// Create new lists for successors and predecessors with the correct RVA addresses
|
||||||
|
List<InstructionBlock> updatedSuccessors = new List<InstructionBlock>();
|
||||||
|
List<InstructionBlock> updatedPredecessors = new List<InstructionBlock>();
|
||||||
|
|
||||||
|
// Update successors
|
||||||
|
foreach (var successor in block.Successors)
|
||||||
|
{
|
||||||
|
// Get the original file offset of the successor
|
||||||
|
if (blockToFileOffset.TryGetValue(successor, out ulong successorFileOffset))
|
||||||
|
{
|
||||||
|
// Look up the RVA address in our mapping
|
||||||
|
if (fileOffsetToRvaMap.TryGetValue(successorFileOffset, out ulong successorRvaAddress))
|
||||||
|
{
|
||||||
|
// Find the block with this RVA address
|
||||||
|
if (rvaBlocksByAddress.TryGetValue(successorRvaAddress, out var rvaSuccessor))
|
||||||
|
{
|
||||||
|
updatedSuccessors.Add(rvaSuccessor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update predecessors
|
||||||
|
foreach (var predecessor in block.Predecessors)
|
||||||
|
{
|
||||||
|
// Get the original file offset of the predecessor
|
||||||
|
if (blockToFileOffset.TryGetValue(predecessor, out ulong predecessorFileOffset))
|
||||||
|
{
|
||||||
|
// Look up the RVA address in our mapping
|
||||||
|
if (fileOffsetToRvaMap.TryGetValue(predecessorFileOffset, out ulong predecessorRvaAddress))
|
||||||
|
{
|
||||||
|
// Find the block with this RVA address
|
||||||
|
if (rvaBlocksByAddress.TryGetValue(predecessorRvaAddress, out var rvaPredecessor))
|
||||||
|
{
|
||||||
|
updatedPredecessors.Add(rvaPredecessor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace the old lists with the updated ones
|
||||||
|
block.Successors = updatedSuccessors;
|
||||||
|
block.Predecessors = updatedPredecessors;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new AsmFunction with the RVA address
|
// Create a new AsmFunction with the RVA address
|
||||||
@ -246,7 +488,7 @@ public class BlockDisassembler
|
|||||||
var block = new InstructionBlock()
|
var block = new InstructionBlock()
|
||||||
{
|
{
|
||||||
Address = address,
|
Address = address,
|
||||||
Instructions = instructions
|
Instructions = new List<Instruction>(instructions) // Create a copy of the instructions list
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add the block to the collection
|
// Add the block to the collection
|
||||||
@ -262,8 +504,6 @@ public class BlockDisassembler
|
|||||||
block.Predecessors.Add(currentBlock);
|
block.Predecessors.Add(currentBlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Block created successfully
|
|
||||||
|
|
||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,89 +42,61 @@ public class ControlFlowAnalyzer
|
|||||||
/// <param name="function">The function to analyze</param>
|
/// <param name="function">The function to analyze</param>
|
||||||
private void IdentifyIfElseStructures(Function function)
|
private void IdentifyIfElseStructures(Function function)
|
||||||
{
|
{
|
||||||
// First pass: identify basic if-else structures
|
// Now analyze each block for conditional jumps
|
||||||
foreach (var block in function.AsmFunction.Blocks)
|
foreach (var block in function.AsmFunction.Blocks)
|
||||||
{
|
{
|
||||||
// Skip blocks that don't end with a conditional jump
|
// Get the last instruction in the block
|
||||||
if (block.Instructions.Count == 0)
|
var lastInstruction = block.Instructions.LastOrDefault();
|
||||||
|
if (lastInstruction == null) continue;
|
||||||
|
|
||||||
|
// Check if the last instruction is a conditional jump
|
||||||
|
if (lastInstruction.Type.IsConditionalJump())
|
||||||
{
|
{
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var lastInstruction = block.Instructions[^1];
|
|
||||||
|
|
||||||
// Look for conditional jumps (Jcc instructions)
|
|
||||||
if (IsConditionalJump(lastInstruction.Type))
|
|
||||||
{
|
|
||||||
// This is a potential if-then-else structure
|
|
||||||
// The true branch is the target of the jump
|
|
||||||
// The false branch is the fallthrough block
|
|
||||||
|
|
||||||
// Get the jump target address
|
// Get the jump target address
|
||||||
ulong targetAddress = GetJumpTargetAddress(lastInstruction);
|
ulong targetAddress = GetJumpTargetAddress(lastInstruction);
|
||||||
|
|
||||||
// Find the target block
|
// Find the target block
|
||||||
if (_context.BlocksByAddress.TryGetValue(targetAddress, out var targetBlock))
|
InstructionBlock? targetBlock = null;
|
||||||
|
foreach (var b in function.AsmFunction.Blocks)
|
||||||
{
|
{
|
||||||
// Find the fallthrough block (the block that follows this one in memory)
|
if (b.Address == targetAddress)
|
||||||
var fallthroughBlock = FindFallthroughBlock(block);
|
|
||||||
|
|
||||||
if (fallthroughBlock != null)
|
|
||||||
{
|
{
|
||||||
// Check if the fallthrough block ends with an unconditional jump
|
targetBlock = b;
|
||||||
// This could indicate an if-else structure where the 'else' branch jumps to a common merge point
|
break;
|
||||||
InstructionBlock? mergeBlock = null;
|
|
||||||
bool hasElseBlock = true;
|
|
||||||
|
|
||||||
if (fallthroughBlock.Instructions.Count > 0 &&
|
|
||||||
fallthroughBlock.Instructions[^1].Type == InstructionType.Jmp)
|
|
||||||
{
|
|
||||||
// Get the jump target address
|
|
||||||
ulong mergeAddress = GetJumpTargetAddress(fallthroughBlock.Instructions[^1]);
|
|
||||||
|
|
||||||
// Find the merge block
|
|
||||||
if (_context.BlocksByAddress.TryGetValue(mergeAddress, out var potentialMergeBlock))
|
|
||||||
{
|
|
||||||
mergeBlock = potentialMergeBlock;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the 'then' block also jumps to the same merge point
|
if (targetBlock == null)
|
||||||
if (mergeBlock != null && targetBlock.Instructions.Count > 0 &&
|
|
||||||
targetBlock.Instructions[^1].Type == InstructionType.Jmp)
|
|
||||||
{
|
{
|
||||||
ulong thenJumpAddress = GetJumpTargetAddress(targetBlock.Instructions[^1]);
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (thenJumpAddress == mergeBlock.Address)
|
// Find the fall-through block (should be in the successors)
|
||||||
|
InstructionBlock? fallThroughBlock = null;
|
||||||
|
foreach (var successor in block.Successors)
|
||||||
{
|
{
|
||||||
// We have a classic if-else structure with a merge point
|
if (successor != targetBlock)
|
||||||
// Store the if-else structure in the context
|
{
|
||||||
|
fallThroughBlock = successor;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fallThroughBlock == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an if-else structure
|
||||||
var ifElseStructure = new IfElseStructure
|
var ifElseStructure = new IfElseStructure
|
||||||
{
|
{
|
||||||
ConditionBlock = block,
|
ConditionBlock = block,
|
||||||
ThenBlock = targetBlock,
|
ThenBlock = targetBlock,
|
||||||
ElseBlock = fallthroughBlock,
|
ElseBlock = fallThroughBlock
|
||||||
MergeBlock = mergeBlock,
|
|
||||||
IsComplete = true // Both branches merge back
|
|
||||||
};
|
};
|
||||||
|
|
||||||
_context.StoreAnalysisData(block.Address, "IfElseStructure", ifElseStructure);
|
// Store the if-else structure in the analysis context
|
||||||
continue;
|
function.AsmFunction.Context.StoreAnalysisData(block.Address, "IfElseStructure", ifElseStructure);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we get here, we have a simple if-then or if-then-else without a clear merge point
|
|
||||||
var simpleIfStructure = new IfElseStructure
|
|
||||||
{
|
|
||||||
ConditionBlock = block,
|
|
||||||
ThenBlock = targetBlock,
|
|
||||||
ElseBlock = hasElseBlock ? fallthroughBlock : null,
|
|
||||||
IsComplete = false // No clear merge point
|
|
||||||
};
|
|
||||||
|
|
||||||
_context.StoreAnalysisData(block.Address, "IfElseStructure", simpleIfStructure);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,32 +167,6 @@ public class ControlFlowAnalyzer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if the given instruction type is a conditional jump
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="type">The instruction type</param>
|
|
||||||
/// <returns>True if the instruction is a conditional jump, false otherwise</returns>
|
|
||||||
private bool IsConditionalJump(InstructionType type)
|
|
||||||
{
|
|
||||||
// Check for common conditional jumps
|
|
||||||
return type == InstructionType.Jz ||
|
|
||||||
type == InstructionType.Jnz ||
|
|
||||||
type == InstructionType.Jg ||
|
|
||||||
type == InstructionType.Jge ||
|
|
||||||
type == InstructionType.Jl ||
|
|
||||||
type == InstructionType.Jle ||
|
|
||||||
type == InstructionType.Ja ||
|
|
||||||
type == InstructionType.Jae ||
|
|
||||||
type == InstructionType.Jb ||
|
|
||||||
type == InstructionType.Jbe ||
|
|
||||||
type == InstructionType.Jo ||
|
|
||||||
type == InstructionType.Jno ||
|
|
||||||
type == InstructionType.Js ||
|
|
||||||
type == InstructionType.Jns ||
|
|
||||||
type == InstructionType.Jp ||
|
|
||||||
type == InstructionType.Jnp;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the target address of a jump instruction
|
/// Gets the target address of a jump instruction
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -228,39 +174,31 @@ public class ControlFlowAnalyzer
|
|||||||
/// <returns>The target address of the jump</returns>
|
/// <returns>The target address of the jump</returns>
|
||||||
private ulong GetJumpTargetAddress(Instruction instruction)
|
private ulong GetJumpTargetAddress(Instruction instruction)
|
||||||
{
|
{
|
||||||
// The target address is usually the first operand of the jump instruction
|
// Add debug output to see the instruction and its operands
|
||||||
if (instruction.StructuredOperands.Count > 0 &&
|
|
||||||
instruction.StructuredOperands[0] is ImmediateOperand immOp)
|
// For conditional jumps, the target address is the first operand
|
||||||
|
if (instruction.StructuredOperands.Count > 0)
|
||||||
|
{
|
||||||
|
var operand = instruction.StructuredOperands[0];
|
||||||
|
|
||||||
|
if (operand is ImmediateOperand immOp)
|
||||||
{
|
{
|
||||||
return (ulong)immOp.Value;
|
return (ulong)immOp.Value;
|
||||||
}
|
}
|
||||||
|
else if (operand is RelativeOffsetOperand relOp)
|
||||||
|
{
|
||||||
|
// For relative jumps, the target address is directly available in the operand
|
||||||
|
// We need to convert from file offset to RVA by adding 0x1000 (the section offset)
|
||||||
|
// This matches how the blocks are converted in BlockDisassembler.cs
|
||||||
|
ulong rvaTargetAddress = relOp.TargetAddress + 0x1000;
|
||||||
|
return rvaTargetAddress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If we can't determine the target, return 0
|
// If we can't determine the target, return 0
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Finds the fallthrough block for a given block
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="block">The block to find the fallthrough for</param>
|
|
||||||
/// <returns>The fallthrough block, or null if none found</returns>
|
|
||||||
private InstructionBlock? FindFallthroughBlock(InstructionBlock block)
|
|
||||||
{
|
|
||||||
// The fallthrough block is the one that follows this one in memory
|
|
||||||
// It should be a successor of this block
|
|
||||||
foreach (var successor in block.Successors)
|
|
||||||
{
|
|
||||||
// Check if this successor is the fallthrough block
|
|
||||||
// (its address should be immediately after this block)
|
|
||||||
if (successor.Address > block.Address)
|
|
||||||
{
|
|
||||||
return successor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if the given block is a potential switch statement header
|
/// Checks if the given block is a potential switch statement header
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
384
X86Disassembler/Analysers/DataFlowAnalyzer.cs
Normal file
384
X86Disassembler/Analysers/DataFlowAnalyzer.cs
Normal file
@ -0,0 +1,384 @@
|
|||||||
|
using X86Disassembler.X86;
|
||||||
|
using X86Disassembler.X86.Operands;
|
||||||
|
|
||||||
|
namespace X86Disassembler.Analysers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Analyzes data flow through instructions to track register values
|
||||||
|
/// </summary>
|
||||||
|
public class DataFlowAnalyzer
|
||||||
|
{
|
||||||
|
// Constants for analysis data keys
|
||||||
|
private const string REGISTER_VALUE_KEY = "RegisterValue";
|
||||||
|
private const string MEMORY_VALUE_KEY = "MemoryValue";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a known value for a register or memory location
|
||||||
|
/// </summary>
|
||||||
|
public class ValueInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The type of value (constant, register, memory, unknown)
|
||||||
|
/// </summary>
|
||||||
|
public enum ValueType
|
||||||
|
{
|
||||||
|
Unknown,
|
||||||
|
Constant,
|
||||||
|
Register,
|
||||||
|
Memory
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The type of this value
|
||||||
|
/// </summary>
|
||||||
|
public ValueType Type { get; set; } = ValueType.Unknown;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The constant value (if Type is Constant)
|
||||||
|
/// </summary>
|
||||||
|
public ulong? ConstantValue { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The source register (if Type is Register)
|
||||||
|
/// </summary>
|
||||||
|
public RegisterIndex? SourceRegister { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The memory address or expression (if Type is Memory)
|
||||||
|
/// </summary>
|
||||||
|
public string? MemoryExpression { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The instruction that defined this value
|
||||||
|
/// </summary>
|
||||||
|
public Instruction? DefiningInstruction { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a string representation of the value
|
||||||
|
/// </summary>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Type switch
|
||||||
|
{
|
||||||
|
ValueType.Constant => $"0x{ConstantValue:X8}",
|
||||||
|
ValueType.Register => $"{SourceRegister}",
|
||||||
|
ValueType.Memory => $"[{MemoryExpression}]",
|
||||||
|
_ => "unknown"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Analyzes data flow in the function and stores results in the analyzer context
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The analyzer context to store results in</param>
|
||||||
|
public void AnalyzeDataFlow(AnalyzerContext context)
|
||||||
|
{
|
||||||
|
// Process each block in order
|
||||||
|
foreach (var block in context.Function.Blocks)
|
||||||
|
{
|
||||||
|
// Dictionary to track register values within this block
|
||||||
|
Dictionary<RegisterIndex, ValueInfo> registerValues = new();
|
||||||
|
|
||||||
|
// Process each instruction in the block
|
||||||
|
foreach (var instruction in block.Instructions)
|
||||||
|
{
|
||||||
|
// Process the instruction based on its type
|
||||||
|
ProcessInstruction(instruction, registerValues, context);
|
||||||
|
|
||||||
|
// Store the current register state at this instruction's address
|
||||||
|
StoreRegisterState(instruction.Address, registerValues, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes an instruction to update register values
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="instruction">The instruction to process</param>
|
||||||
|
/// <param name="registerValues">The current register values</param>
|
||||||
|
/// <param name="context">The analyzer context</param>
|
||||||
|
private void ProcessInstruction(Instruction instruction, Dictionary<RegisterIndex, ValueInfo> registerValues, AnalyzerContext context)
|
||||||
|
{
|
||||||
|
// Handle different instruction types
|
||||||
|
switch (instruction.Type)
|
||||||
|
{
|
||||||
|
// MOV instructions
|
||||||
|
case InstructionType.Mov:
|
||||||
|
ProcessMovInstruction(instruction, registerValues);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// XOR instructions
|
||||||
|
case InstructionType.Xor:
|
||||||
|
ProcessXorInstruction(instruction, registerValues);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// ADD instructions
|
||||||
|
case InstructionType.Add:
|
||||||
|
ProcessAddInstruction(instruction, registerValues);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// SUB instructions
|
||||||
|
case InstructionType.Sub:
|
||||||
|
ProcessSubInstruction(instruction, registerValues);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// PUSH/POP instructions can affect register values
|
||||||
|
case InstructionType.Pop:
|
||||||
|
ProcessPopInstruction(instruction, registerValues);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Call instructions typically clobber certain registers
|
||||||
|
case InstructionType.Call:
|
||||||
|
ProcessCallInstruction(instruction, registerValues);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Other instructions that modify registers
|
||||||
|
default:
|
||||||
|
// For now, mark destination registers as unknown for unsupported instructions
|
||||||
|
if (instruction.StructuredOperands.Count > 0 &&
|
||||||
|
instruction.StructuredOperands[0] is RegisterOperand regOp)
|
||||||
|
{
|
||||||
|
registerValues[regOp.Register] = new ValueInfo
|
||||||
|
{
|
||||||
|
Type = ValueInfo.ValueType.Unknown,
|
||||||
|
DefiningInstruction = instruction
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes a MOV instruction to update register values
|
||||||
|
/// </summary>
|
||||||
|
private void ProcessMovInstruction(Instruction instruction, Dictionary<RegisterIndex, ValueInfo> registerValues)
|
||||||
|
{
|
||||||
|
// Handle different MOV variants
|
||||||
|
if (instruction.StructuredOperands.Count >= 2)
|
||||||
|
{
|
||||||
|
var dest = instruction.StructuredOperands[0];
|
||||||
|
var src = instruction.StructuredOperands[1];
|
||||||
|
|
||||||
|
// MOV reg, imm
|
||||||
|
if (dest is RegisterOperand destReg && src is ImmediateOperand immSrc)
|
||||||
|
{
|
||||||
|
registerValues[destReg.Register] = new ValueInfo
|
||||||
|
{
|
||||||
|
Type = ValueInfo.ValueType.Constant,
|
||||||
|
ConstantValue = immSrc.Value,
|
||||||
|
DefiningInstruction = instruction
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// MOV reg, reg
|
||||||
|
else if (dest is RegisterOperand destReg2 && src is RegisterOperand srcReg)
|
||||||
|
{
|
||||||
|
if (registerValues.TryGetValue(srcReg.Register, out var srcValue))
|
||||||
|
{
|
||||||
|
// Copy the source value
|
||||||
|
registerValues[destReg2.Register] = new ValueInfo
|
||||||
|
{
|
||||||
|
Type = srcValue.Type,
|
||||||
|
ConstantValue = srcValue.ConstantValue,
|
||||||
|
SourceRegister = srcValue.SourceRegister,
|
||||||
|
MemoryExpression = srcValue.MemoryExpression,
|
||||||
|
DefiningInstruction = instruction
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Source register value is unknown
|
||||||
|
registerValues[destReg2.Register] = new ValueInfo
|
||||||
|
{
|
||||||
|
Type = ValueInfo.ValueType.Register,
|
||||||
|
SourceRegister = srcReg.Register,
|
||||||
|
DefiningInstruction = instruction
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// MOV reg, [mem]
|
||||||
|
else if (dest is RegisterOperand destReg3 && src is MemoryOperand memSrc)
|
||||||
|
{
|
||||||
|
registerValues[destReg3.Register] = new ValueInfo
|
||||||
|
{
|
||||||
|
Type = ValueInfo.ValueType.Memory,
|
||||||
|
MemoryExpression = memSrc.ToString(),
|
||||||
|
DefiningInstruction = instruction
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// MOV [mem], reg or MOV [mem], imm
|
||||||
|
// These don't update register values, so we don't need to handle them here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes an XOR instruction to update register values
|
||||||
|
/// </summary>
|
||||||
|
private void ProcessXorInstruction(Instruction instruction, Dictionary<RegisterIndex, ValueInfo> registerValues)
|
||||||
|
{
|
||||||
|
// Handle XOR reg, reg (often used for zeroing a register)
|
||||||
|
if (instruction.StructuredOperands.Count >= 2)
|
||||||
|
{
|
||||||
|
var dest = instruction.StructuredOperands[0];
|
||||||
|
var src = instruction.StructuredOperands[1];
|
||||||
|
|
||||||
|
// XOR reg, same_reg (zeroing idiom)
|
||||||
|
if (dest is RegisterOperand destReg && src is RegisterOperand srcReg &&
|
||||||
|
destReg.Register == srcReg.Register)
|
||||||
|
{
|
||||||
|
registerValues[destReg.Register] = new ValueInfo
|
||||||
|
{
|
||||||
|
Type = ValueInfo.ValueType.Constant,
|
||||||
|
ConstantValue = 0,
|
||||||
|
DefiningInstruction = instruction
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Other XOR operations make the result unknown
|
||||||
|
else if (dest is RegisterOperand destReg2)
|
||||||
|
{
|
||||||
|
registerValues[destReg2.Register] = new ValueInfo
|
||||||
|
{
|
||||||
|
Type = ValueInfo.ValueType.Unknown,
|
||||||
|
DefiningInstruction = instruction
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes an ADD instruction to update register values
|
||||||
|
/// </summary>
|
||||||
|
private void ProcessAddInstruction(Instruction instruction, Dictionary<RegisterIndex, ValueInfo> registerValues)
|
||||||
|
{
|
||||||
|
// Handle ADD reg, imm where we know the register value
|
||||||
|
if (instruction.StructuredOperands.Count >= 2)
|
||||||
|
{
|
||||||
|
var dest = instruction.StructuredOperands[0];
|
||||||
|
var src = instruction.StructuredOperands[1];
|
||||||
|
|
||||||
|
// ADD reg, imm where reg is a known constant
|
||||||
|
if (dest is RegisterOperand destReg && src is ImmediateOperand immSrc &&
|
||||||
|
registerValues.TryGetValue(destReg.Register, out var destValue) &&
|
||||||
|
destValue.Type == ValueInfo.ValueType.Constant &&
|
||||||
|
destValue.ConstantValue.HasValue)
|
||||||
|
{
|
||||||
|
// Calculate the new constant value
|
||||||
|
registerValues[destReg.Register] = new ValueInfo
|
||||||
|
{
|
||||||
|
Type = ValueInfo.ValueType.Constant,
|
||||||
|
ConstantValue = (uint?) (destValue.ConstantValue.Value + immSrc.Value),
|
||||||
|
DefiningInstruction = instruction
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Other ADD operations make the result unknown
|
||||||
|
else if (dest is RegisterOperand destReg2)
|
||||||
|
{
|
||||||
|
registerValues[destReg2.Register] = new ValueInfo
|
||||||
|
{
|
||||||
|
Type = ValueInfo.ValueType.Unknown,
|
||||||
|
DefiningInstruction = instruction
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes a SUB instruction to update register values
|
||||||
|
/// </summary>
|
||||||
|
private void ProcessSubInstruction(Instruction instruction, Dictionary<RegisterIndex, ValueInfo> registerValues)
|
||||||
|
{
|
||||||
|
// Handle SUB reg, imm where we know the register value
|
||||||
|
if (instruction.StructuredOperands.Count >= 2)
|
||||||
|
{
|
||||||
|
var dest = instruction.StructuredOperands[0];
|
||||||
|
var src = instruction.StructuredOperands[1];
|
||||||
|
|
||||||
|
// SUB reg, imm where reg is a known constant
|
||||||
|
if (dest is RegisterOperand destReg && src is ImmediateOperand immSrc &&
|
||||||
|
registerValues.TryGetValue(destReg.Register, out var destValue) &&
|
||||||
|
destValue.Type == ValueInfo.ValueType.Constant &&
|
||||||
|
destValue.ConstantValue.HasValue)
|
||||||
|
{
|
||||||
|
// Calculate the new constant value
|
||||||
|
registerValues[destReg.Register] = new ValueInfo
|
||||||
|
{
|
||||||
|
Type = ValueInfo.ValueType.Constant,
|
||||||
|
ConstantValue = (uint?) (destValue.ConstantValue.Value - immSrc.Value),
|
||||||
|
DefiningInstruction = instruction
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Other SUB operations make the result unknown
|
||||||
|
else if (dest is RegisterOperand destReg2)
|
||||||
|
{
|
||||||
|
registerValues[destReg2.Register] = new ValueInfo
|
||||||
|
{
|
||||||
|
Type = ValueInfo.ValueType.Unknown,
|
||||||
|
DefiningInstruction = instruction
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes a POP instruction to update register values
|
||||||
|
/// </summary>
|
||||||
|
private void ProcessPopInstruction(Instruction instruction, Dictionary<RegisterIndex, ValueInfo> registerValues)
|
||||||
|
{
|
||||||
|
// POP reg makes the register value unknown (comes from stack)
|
||||||
|
if (instruction.StructuredOperands.Count >= 1 &&
|
||||||
|
instruction.StructuredOperands[0] is RegisterOperand destReg)
|
||||||
|
{
|
||||||
|
registerValues[destReg.Register] = new ValueInfo
|
||||||
|
{
|
||||||
|
Type = ValueInfo.ValueType.Unknown,
|
||||||
|
DefiningInstruction = instruction
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes a CALL instruction to update register values
|
||||||
|
/// </summary>
|
||||||
|
private void ProcessCallInstruction(Instruction instruction, Dictionary<RegisterIndex, ValueInfo> registerValues)
|
||||||
|
{
|
||||||
|
// CALL instructions typically clobber EAX, ECX, and EDX in x86 calling conventions
|
||||||
|
registerValues[RegisterIndex.A] = new ValueInfo
|
||||||
|
{
|
||||||
|
Type = ValueInfo.ValueType.Unknown,
|
||||||
|
DefiningInstruction = instruction
|
||||||
|
};
|
||||||
|
|
||||||
|
registerValues[RegisterIndex.C] = new ValueInfo
|
||||||
|
{
|
||||||
|
Type = ValueInfo.ValueType.Unknown,
|
||||||
|
DefiningInstruction = instruction
|
||||||
|
};
|
||||||
|
|
||||||
|
registerValues[RegisterIndex.D] = new ValueInfo
|
||||||
|
{
|
||||||
|
Type = ValueInfo.ValueType.Unknown,
|
||||||
|
DefiningInstruction = instruction
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores the current register state at the given address
|
||||||
|
/// </summary>
|
||||||
|
private void StoreRegisterState(ulong address, Dictionary<RegisterIndex, ValueInfo> registerValues, AnalyzerContext context)
|
||||||
|
{
|
||||||
|
// Create a copy of the register values to store
|
||||||
|
var registerValuesCopy = new Dictionary<RegisterIndex, ValueInfo>(registerValues);
|
||||||
|
|
||||||
|
// Store in the context
|
||||||
|
context.StoreAnalysisData(address, REGISTER_VALUE_KEY, registerValuesCopy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the register values at the given address
|
||||||
|
/// </summary>
|
||||||
|
public static Dictionary<string, ValueInfo>? GetRegisterValues(ulong address, AnalyzerContext context)
|
||||||
|
{
|
||||||
|
return context.GetAnalysisData<Dictionary<string, ValueInfo>>(address, REGISTER_VALUE_KEY);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
namespace X86Disassembler.Analysers.DecompilerTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a calling convention used by a function
|
||||||
|
/// </summary>
|
||||||
|
public enum CallingConvention
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// C declaration calling convention (caller cleans the stack)
|
||||||
|
/// Parameters are pushed right-to-left
|
||||||
|
/// EAX, ECX, EDX are caller-saved
|
||||||
|
/// EBX, ESI, EDI, EBP are callee-saved
|
||||||
|
/// Return value in EAX (or EDX:EAX for 64-bit values)
|
||||||
|
/// </summary>
|
||||||
|
Cdecl,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Standard calling convention (callee cleans the stack)
|
||||||
|
/// Parameters are pushed right-to-left
|
||||||
|
/// EAX, ECX, EDX are caller-saved
|
||||||
|
/// EBX, ESI, EDI, EBP are callee-saved
|
||||||
|
/// Return value in EAX (or EDX:EAX for 64-bit values)
|
||||||
|
/// </summary>
|
||||||
|
Stdcall,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fast calling convention
|
||||||
|
/// First two parameters in ECX and EDX, rest on stack right-to-left
|
||||||
|
/// EAX, ECX, EDX are caller-saved
|
||||||
|
/// EBX, ESI, EDI, EBP are callee-saved
|
||||||
|
/// Return value in EAX
|
||||||
|
/// Callee cleans the stack
|
||||||
|
/// </summary>
|
||||||
|
Fastcall,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This calling convention (C++ member functions)
|
||||||
|
/// 'this' pointer in ECX, other parameters pushed right-to-left
|
||||||
|
/// EAX, ECX, EDX are caller-saved
|
||||||
|
/// EBX, ESI, EDI, EBP are callee-saved
|
||||||
|
/// Return value in EAX
|
||||||
|
/// Caller cleans the stack
|
||||||
|
/// </summary>
|
||||||
|
Thiscall,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Microsoft vectorcall convention
|
||||||
|
/// First six parameters in registers (XMM0-XMM5 for floating point, ECX, EDX, R8, R9 for integers)
|
||||||
|
/// Additional parameters pushed right-to-left
|
||||||
|
/// Return value in EAX or XMM0
|
||||||
|
/// </summary>
|
||||||
|
Vectorcall,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unknown calling convention
|
||||||
|
/// </summary>
|
||||||
|
Unknown
|
||||||
|
}
|
190
X86Disassembler/Analysers/DecompilerTypes/DataType.cs
Normal file
190
X86Disassembler/Analysers/DecompilerTypes/DataType.cs
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
namespace X86Disassembler.Analysers.DecompilerTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a data type in decompiled code
|
||||||
|
/// </summary>
|
||||||
|
public class DataType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The category of the data type
|
||||||
|
/// </summary>
|
||||||
|
public enum TypeCategory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Unknown type
|
||||||
|
/// </summary>
|
||||||
|
Unknown,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Void type (no value)
|
||||||
|
/// </summary>
|
||||||
|
Void,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Integer type
|
||||||
|
/// </summary>
|
||||||
|
Integer,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Floating point type
|
||||||
|
/// </summary>
|
||||||
|
Float,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pointer type
|
||||||
|
/// </summary>
|
||||||
|
Pointer,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Structure type
|
||||||
|
/// </summary>
|
||||||
|
Struct,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Array type
|
||||||
|
/// </summary>
|
||||||
|
Array,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Function type
|
||||||
|
/// </summary>
|
||||||
|
Function
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the type
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The category of the type
|
||||||
|
/// </summary>
|
||||||
|
public TypeCategory Category { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The size of the type in bytes
|
||||||
|
/// </summary>
|
||||||
|
public int Size { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the type is signed (for integer types)
|
||||||
|
/// </summary>
|
||||||
|
public bool IsSigned { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The pointed-to type (for pointer types)
|
||||||
|
/// </summary>
|
||||||
|
public DataType? PointedType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The element type (for array types)
|
||||||
|
/// </summary>
|
||||||
|
public DataType? ElementType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of elements (for array types)
|
||||||
|
/// </summary>
|
||||||
|
public int? ElementCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The fields of the structure (for struct types)
|
||||||
|
/// </summary>
|
||||||
|
public List<StructField> Fields { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new data type with the specified name and category
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name of the type</param>
|
||||||
|
/// <param name="category">The category of the type</param>
|
||||||
|
/// <param name="size">The size of the type in bytes</param>
|
||||||
|
public DataType(string name, TypeCategory category, int size)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Category = category;
|
||||||
|
Size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a string representation of the type
|
||||||
|
/// </summary>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a pointer type to the specified type
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pointedType">The type being pointed to</param>
|
||||||
|
/// <returns>A new pointer type</returns>
|
||||||
|
public static DataType CreatePointerType(DataType pointedType)
|
||||||
|
{
|
||||||
|
return new DataType($"{pointedType.Name}*", TypeCategory.Pointer, 4)
|
||||||
|
{
|
||||||
|
PointedType = pointedType
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an array type of the specified element type and count
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="elementType">The type of the array elements</param>
|
||||||
|
/// <param name="count">The number of elements in the array</param>
|
||||||
|
/// <returns>A new array type</returns>
|
||||||
|
public static DataType CreateArrayType(DataType elementType, int count)
|
||||||
|
{
|
||||||
|
return new DataType($"{elementType.Name}[{count}]", TypeCategory.Array, elementType.Size * count)
|
||||||
|
{
|
||||||
|
ElementType = elementType,
|
||||||
|
ElementCount = count
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Common predefined types
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DataType Unknown = new DataType("unknown", TypeCategory.Unknown, 0);
|
||||||
|
public static readonly DataType Void = new DataType("void", TypeCategory.Void, 0);
|
||||||
|
public static readonly DataType Char = new DataType("char", TypeCategory.Integer, 1) { IsSigned = true };
|
||||||
|
public static readonly DataType UChar = new DataType("unsigned char", TypeCategory.Integer, 1);
|
||||||
|
public static readonly DataType Short = new DataType("short", TypeCategory.Integer, 2) { IsSigned = true };
|
||||||
|
public static readonly DataType UShort = new DataType("unsigned short", TypeCategory.Integer, 2);
|
||||||
|
public static readonly DataType Int = new DataType("int", TypeCategory.Integer, 4) { IsSigned = true };
|
||||||
|
public static readonly DataType UInt = new DataType("unsigned int", TypeCategory.Integer, 4);
|
||||||
|
public static readonly DataType Float = new DataType("float", TypeCategory.Float, 4);
|
||||||
|
public static readonly DataType Double = new DataType("double", TypeCategory.Float, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a field in a structure
|
||||||
|
/// </summary>
|
||||||
|
public class StructField
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the field
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The type of the field
|
||||||
|
/// </summary>
|
||||||
|
public DataType Type { get; set; } = DataType.Unknown;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The offset of the field within the structure
|
||||||
|
/// </summary>
|
||||||
|
public int Offset { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new structure field
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name of the field</param>
|
||||||
|
/// <param name="type">The type of the field</param>
|
||||||
|
/// <param name="offset">The offset of the field within the structure</param>
|
||||||
|
public StructField(string name, DataType type, int offset)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Type = type;
|
||||||
|
Offset = offset;
|
||||||
|
}
|
||||||
|
}
|
98
X86Disassembler/Analysers/DecompilerTypes/Function.cs
Normal file
98
X86Disassembler/Analysers/DecompilerTypes/Function.cs
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
using X86Disassembler.X86;
|
||||||
|
using X86Disassembler.X86.Operands;
|
||||||
|
|
||||||
|
namespace X86Disassembler.Analysers.DecompilerTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a function in decompiled code
|
||||||
|
/// </summary>
|
||||||
|
public class Function
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the function
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The address of the function
|
||||||
|
/// </summary>
|
||||||
|
public ulong Address { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The return type of the function
|
||||||
|
/// </summary>
|
||||||
|
public DataType ReturnType { get; set; } = DataType.Void;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The parameters of the function
|
||||||
|
/// </summary>
|
||||||
|
public List<Variable> Parameters { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Local variables in this function
|
||||||
|
/// </summary>
|
||||||
|
public List<Variable> LocalVariables { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Variables stored in registers
|
||||||
|
/// </summary>
|
||||||
|
public List<Variable> RegisterVariables { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The calling convention used by the function
|
||||||
|
/// </summary>
|
||||||
|
public CallingConvention CallingConvention { get; set; } = CallingConvention.Cdecl;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The assembly function representation
|
||||||
|
/// </summary>
|
||||||
|
public AsmFunction AsmFunction { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new function with the specified name and address
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name of the function</param>
|
||||||
|
/// <param name="address">The address of the function</param>
|
||||||
|
/// <param name="asmFunction">The assembly function representation</param>
|
||||||
|
public Function(string name, ulong address, AsmFunction asmFunction)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Address = address;
|
||||||
|
AsmFunction = asmFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Analyzes the function to identify variables
|
||||||
|
/// </summary>
|
||||||
|
public void AnalyzeVariables()
|
||||||
|
{
|
||||||
|
// Create a variable analyzer
|
||||||
|
var variableAnalyzer = new VariableAnalyzer(AsmFunction.Context);
|
||||||
|
|
||||||
|
// Analyze stack variables
|
||||||
|
variableAnalyzer.AnalyzeStackVariables(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a string representation of the function signature
|
||||||
|
/// </summary>
|
||||||
|
public string GetSignature()
|
||||||
|
{
|
||||||
|
string paramList = string.Join(", ", Parameters.Select(p => $"{p.Type} {p.Name}"));
|
||||||
|
return $"{ReturnType} {Name}({paramList})";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a string representation of the function
|
||||||
|
/// </summary>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return GetSignature();
|
||||||
|
}
|
||||||
|
}
|
102
X86Disassembler/Analysers/DecompilerTypes/Variable.cs
Normal file
102
X86Disassembler/Analysers/DecompilerTypes/Variable.cs
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
namespace X86Disassembler.Analysers.DecompilerTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a variable in decompiled code
|
||||||
|
/// </summary>
|
||||||
|
public class Variable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The type of storage for a variable
|
||||||
|
/// </summary>
|
||||||
|
public enum StorageType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Variable stored on the stack (local variable)
|
||||||
|
/// </summary>
|
||||||
|
Stack,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Variable stored in a register
|
||||||
|
/// </summary>
|
||||||
|
Register,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Variable stored in global memory
|
||||||
|
/// </summary>
|
||||||
|
Global,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Function parameter passed on the stack
|
||||||
|
/// </summary>
|
||||||
|
Parameter,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Function parameter passed in a register
|
||||||
|
/// </summary>
|
||||||
|
RegisterParameter
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the variable
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The type of the variable
|
||||||
|
/// </summary>
|
||||||
|
public DataType Type { get; set; } = DataType.Unknown;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The storage location of the variable
|
||||||
|
/// </summary>
|
||||||
|
public StorageType Storage { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The offset from the base pointer (for stack variables)
|
||||||
|
/// </summary>
|
||||||
|
public int? StackOffset { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The register that holds this variable (for register variables)
|
||||||
|
/// </summary>
|
||||||
|
public X86.RegisterIndex? Register { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The memory address (for global variables)
|
||||||
|
/// </summary>
|
||||||
|
public ulong? Address { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The size of the variable in bytes
|
||||||
|
/// </summary>
|
||||||
|
public int Size { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this variable is a function parameter
|
||||||
|
/// </summary>
|
||||||
|
public bool IsParameter { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The parameter index (if this is a parameter)
|
||||||
|
/// </summary>
|
||||||
|
public int? ParameterIndex { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new variable with the specified name and type
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name of the variable</param>
|
||||||
|
/// <param name="type">The type of the variable</param>
|
||||||
|
public Variable(string name, DataType type)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a string representation of the variable
|
||||||
|
/// </summary>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{Type} {Name}";
|
||||||
|
}
|
||||||
|
}
|
120
X86Disassembler/Analysers/LoopAnalyzer.cs
Normal file
120
X86Disassembler/Analysers/LoopAnalyzer.cs
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
namespace X86Disassembler.Analysers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Analyzes the control flow graph to identify loops
|
||||||
|
/// </summary>
|
||||||
|
public class LoopAnalyzer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Identifies loops in the given function and stores them in the analyzer context
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The analyzer context to store results in</param>
|
||||||
|
public void AnalyzeLoops(AnalyzerContext context)
|
||||||
|
{
|
||||||
|
// A back edge is an edge from a node to one of its dominators
|
||||||
|
// For our simplified approach, we'll identify back edges as edges that point to blocks
|
||||||
|
// with a lower address (potential loop headers)
|
||||||
|
foreach (var block in context.Function.Blocks)
|
||||||
|
{
|
||||||
|
foreach (var successor in block.Successors)
|
||||||
|
{
|
||||||
|
// If the successor has a lower address than the current block,
|
||||||
|
// it's potentially a back edge forming a loop
|
||||||
|
if (successor.Address < block.Address)
|
||||||
|
{
|
||||||
|
// Create a new loop with the identified back edge
|
||||||
|
var loop = new AnalyzerContext.Loop
|
||||||
|
{
|
||||||
|
Header = successor,
|
||||||
|
BackEdge = (block, successor)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Find all blocks in the loop using a breadth-first search
|
||||||
|
FindLoopBlocks(loop);
|
||||||
|
|
||||||
|
// Find the exit blocks of the loop
|
||||||
|
FindLoopExits(loop);
|
||||||
|
|
||||||
|
// Store the loop in the context
|
||||||
|
context.LoopsByHeaderAddress[successor.Address] = loop;
|
||||||
|
|
||||||
|
// Update the blocks-to-loops mapping
|
||||||
|
foreach (var loopBlock in loop.Blocks)
|
||||||
|
{
|
||||||
|
if (!context.LoopsByBlockAddress.TryGetValue(loopBlock.Address, out var loops))
|
||||||
|
{
|
||||||
|
loops = [];
|
||||||
|
context.LoopsByBlockAddress[loopBlock.Address] = loops;
|
||||||
|
}
|
||||||
|
|
||||||
|
loops.Add(loop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds all blocks that are part of the loop
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="loop">The loop to analyze</param>
|
||||||
|
private void FindLoopBlocks(AnalyzerContext.Loop loop)
|
||||||
|
{
|
||||||
|
// Start with the header block
|
||||||
|
loop.Blocks.Add(loop.Header);
|
||||||
|
|
||||||
|
// Use a queue for breadth-first search
|
||||||
|
Queue<InstructionBlock> queue = new Queue<InstructionBlock>();
|
||||||
|
queue.Enqueue(loop.BackEdge.From); // Start from the back edge source
|
||||||
|
|
||||||
|
// Keep track of visited blocks to avoid cycles
|
||||||
|
HashSet<ulong> visited = new HashSet<ulong> { loop.Header.Address };
|
||||||
|
|
||||||
|
while (queue.Count > 0)
|
||||||
|
{
|
||||||
|
var block = queue.Dequeue();
|
||||||
|
|
||||||
|
// If we've already processed this block, skip it
|
||||||
|
if (!visited.Add(block.Address))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the block to the loop
|
||||||
|
loop.Blocks.Add(block);
|
||||||
|
|
||||||
|
// Add all predecessors to the queue (except those that would take us outside the loop)
|
||||||
|
foreach (var predecessor in block.Predecessors)
|
||||||
|
{
|
||||||
|
// Skip the header's predecessors that aren't in the loop already
|
||||||
|
// (to avoid including blocks outside the loop)
|
||||||
|
if (block == loop.Header && !loop.Blocks.Contains(predecessor) && predecessor != loop.BackEdge.From)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.Enqueue(predecessor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds all exit blocks of the loop (blocks that have successors outside the loop)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="loop">The loop to analyze</param>
|
||||||
|
private void FindLoopExits(AnalyzerContext.Loop loop)
|
||||||
|
{
|
||||||
|
foreach (var block in loop.Blocks)
|
||||||
|
{
|
||||||
|
foreach (var successor in block.Successors)
|
||||||
|
{
|
||||||
|
// If the successor is not part of the loop, this block is an exit
|
||||||
|
if (!loop.Blocks.Contains(successor))
|
||||||
|
{
|
||||||
|
loop.ExitBlocks.Add(block);
|
||||||
|
break; // Once we've identified this block as an exit, we can stop checking its successors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -159,6 +159,38 @@ public class PseudocodeGenerator
|
|||||||
bool hasConditionalJump = block.Instructions.Count > 0 &&
|
bool hasConditionalJump = block.Instructions.Count > 0 &&
|
||||||
IsConditionalJump(block.Instructions[^1].Type);
|
IsConditionalJump(block.Instructions[^1].Type);
|
||||||
|
|
||||||
|
// Add debug info about conditional jumps
|
||||||
|
if (hasConditionalJump)
|
||||||
|
{
|
||||||
|
var jumpInstruction = block.Instructions[^1];
|
||||||
|
result.AppendLine($"{new string(' ', indentLevel * 4)}// DEBUG: Conditional jump {jumpInstruction} detected");
|
||||||
|
|
||||||
|
// Get the jump target address
|
||||||
|
ulong targetAddress = GetJumpTargetAddress(jumpInstruction);
|
||||||
|
result.AppendLine($"{new string(' ', indentLevel * 4)}// DEBUG: Jump target: 0x{targetAddress:X8}");
|
||||||
|
|
||||||
|
// Check if we can find a comparison instruction before the jump
|
||||||
|
Instruction? comparisonInstruction = null;
|
||||||
|
for (int i = block.Instructions.Count - 2; i >= 0 && i >= block.Instructions.Count - 5; i--)
|
||||||
|
{
|
||||||
|
var instruction = block.Instructions[i];
|
||||||
|
if (instruction.Type == InstructionType.Cmp || instruction.Type == InstructionType.Test)
|
||||||
|
{
|
||||||
|
comparisonInstruction = instruction;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comparisonInstruction != null)
|
||||||
|
{
|
||||||
|
result.AppendLine($"{new string(' ', indentLevel * 4)}// DEBUG: Found comparison: {comparisonInstruction}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result.AppendLine($"{new string(' ', indentLevel * 4)}// DEBUG: No comparison instruction found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If this block has a conditional jump but wasn't detected as an if-else structure,
|
// If this block has a conditional jump but wasn't detected as an if-else structure,
|
||||||
// we'll create an inline if statement for better readability
|
// we'll create an inline if statement for better readability
|
||||||
if (hasConditionalJump && block.Successors.Count == 2)
|
if (hasConditionalJump && block.Successors.Count == 2)
|
||||||
|
@ -94,12 +94,25 @@ public class Program
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// Find a suitable exported function to decompile
|
||||||
|
// Let's try to find a function that might have more complex control flow
|
||||||
|
var exportedFunctions = peFile.ExportedFunctions;
|
||||||
|
|
||||||
|
// Print all exported functions to help us choose a better one
|
||||||
|
Console.WriteLine("Available exported functions:");
|
||||||
|
foreach (var func in exportedFunctions)
|
||||||
|
{
|
||||||
|
Console.WriteLine($" - {func.Name} (RVA=0x{func.AddressRva:X8})");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decompile the entry point function
|
||||||
|
Console.WriteLine($"\nDecompiling entry point function at address 0x{peFile.OptionalHeader.AddressOfEntryPoint:X8}\n");
|
||||||
|
|
||||||
// Decompile the entry point function
|
// Decompile the entry point function
|
||||||
var function = decompiler.DecompileFunction(peFile.OptionalHeader.AddressOfEntryPoint);
|
var function = decompiler.DecompileFunction(peFile.OptionalHeader.AddressOfEntryPoint);
|
||||||
|
|
||||||
// Generate pseudocode
|
// Generate pseudocode
|
||||||
string pseudocode = decompiler.GeneratePseudocode(function);
|
var pseudocode = decompiler.GeneratePseudocode(function);
|
||||||
|
|
||||||
Console.WriteLine("\nGenerated Pseudocode:\n");
|
Console.WriteLine("\nGenerated Pseudocode:\n");
|
||||||
Console.WriteLine(pseudocode);
|
Console.WriteLine(pseudocode);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user