mirror of
https://github.com/ApfelTeeSaft/Yaroze.NET.git
synced 2026-08-26 19:43:27 +00:00
367 lines
12 KiB
C#
367 lines
12 KiB
C#
using Xunit;
|
|
using Yaroze.Core.Analysis;
|
|
using Yaroze.Core.CPU;
|
|
|
|
namespace Yaroze.Tests.Analysis;
|
|
|
|
public class FunctionAnalyzerTests
|
|
{
|
|
[Fact]
|
|
public void FunctionAnalyzer_DiscoversSingleFunction()
|
|
{
|
|
// Arrange - Simple function with just a return
|
|
byte[] memory = new byte[4096];
|
|
uint baseAddress = 0x80000000;
|
|
|
|
// main:
|
|
// jr $ra
|
|
// nop
|
|
WriteInstruction(memory, 0, 0x03E00008); // JR $ra
|
|
WriteInstruction(memory, 4, 0x00000000); // NOP
|
|
|
|
var analyzer = new FunctionAnalyzer(memory);
|
|
|
|
// Act
|
|
analyzer.AnalyzeFromEntryPoint(baseAddress);
|
|
|
|
// Assert
|
|
Assert.Single(analyzer.Functions);
|
|
Assert.True(analyzer.Functions.ContainsKey(baseAddress));
|
|
}
|
|
|
|
[Fact]
|
|
public void FunctionAnalyzer_DiscoversCalledFunction()
|
|
{
|
|
// Arrange
|
|
byte[] memory = new byte[4096];
|
|
uint baseAddress = 0x80000000;
|
|
uint subAddress = baseAddress + 0x10;
|
|
|
|
// main:
|
|
// jal sub
|
|
// nop
|
|
// jr $ra
|
|
// nop
|
|
// sub:
|
|
// jr $ra
|
|
// nop
|
|
WriteInstruction(memory, 0, 0x0C000004); // JAL 0x80000010
|
|
WriteInstruction(memory, 4, 0x00000000); // NOP
|
|
WriteInstruction(memory, 8, 0x03E00008); // JR $ra
|
|
WriteInstruction(memory, 12, 0x00000000); // NOP
|
|
WriteInstruction(memory, 16, 0x03E00008); // JR $ra (sub)
|
|
WriteInstruction(memory, 20, 0x00000000); // NOP
|
|
|
|
var analyzer = new FunctionAnalyzer(memory);
|
|
|
|
// Act
|
|
analyzer.AnalyzeFromEntryPoint(baseAddress);
|
|
|
|
// Assert
|
|
Assert.Equal(2, analyzer.Functions.Count);
|
|
Assert.True(analyzer.Functions.ContainsKey(baseAddress));
|
|
Assert.True(analyzer.Functions.ContainsKey(subAddress));
|
|
}
|
|
|
|
[Fact]
|
|
public void FunctionAnalyzer_BuildsCallGraph()
|
|
{
|
|
// Arrange
|
|
byte[] memory = new byte[4096];
|
|
uint baseAddress = 0x80000000;
|
|
uint subAddress = baseAddress + 0x10;
|
|
|
|
// main calls sub
|
|
WriteInstruction(memory, 0, 0x0C000004); // JAL 0x80000010
|
|
WriteInstruction(memory, 4, 0x00000000); // NOP
|
|
WriteInstruction(memory, 8, 0x03E00008); // JR $ra
|
|
WriteInstruction(memory, 12, 0x00000000); // NOP
|
|
WriteInstruction(memory, 16, 0x03E00008); // JR $ra (sub)
|
|
WriteInstruction(memory, 20, 0x00000000); // NOP
|
|
|
|
var analyzer = new FunctionAnalyzer(memory);
|
|
|
|
// Act
|
|
analyzer.AnalyzeFromEntryPoint(baseAddress);
|
|
|
|
// Assert
|
|
var mainFunc = analyzer.Functions[baseAddress];
|
|
Assert.Contains(subAddress, mainFunc.CallsTo);
|
|
|
|
var subFunc = analyzer.Functions[subAddress];
|
|
Assert.Contains(baseAddress, subFunc.CalledFrom);
|
|
}
|
|
|
|
[Fact]
|
|
public void FunctionAnalyzer_HandlesConditionalBranches()
|
|
{
|
|
// Arrange - Function with branch
|
|
byte[] memory = new byte[4096];
|
|
uint baseAddress = 0x80000000;
|
|
|
|
// main:
|
|
// beq $t0, $zero, +2
|
|
// nop
|
|
// addiu $v0, $zero, 1
|
|
// jr $ra
|
|
// nop
|
|
WriteInstruction(memory, 0, 0x11000002); // BEQ $8, $0, +2
|
|
WriteInstruction(memory, 4, 0x00000000); // NOP
|
|
WriteInstruction(memory, 8, 0x24020001); // ADDIU $v0, $zero, 1
|
|
WriteInstruction(memory, 12, 0x03E00008); // JR $ra
|
|
WriteInstruction(memory, 16, 0x00000000); // NOP
|
|
|
|
var analyzer = new FunctionAnalyzer(memory);
|
|
|
|
// Act
|
|
analyzer.AnalyzeFromEntryPoint(baseAddress);
|
|
|
|
// Assert - Should have one function with all instructions
|
|
Assert.Single(analyzer.Functions);
|
|
var func = analyzer.Functions[baseAddress];
|
|
Assert.Contains(baseAddress + 0, func.Instructions); // BEQ
|
|
Assert.Contains(baseAddress + 4, func.Instructions); // NOP
|
|
Assert.Contains(baseAddress + 8, func.Instructions); // ADDIU
|
|
Assert.Contains(baseAddress + 12, func.Instructions); // JR
|
|
}
|
|
|
|
[Fact]
|
|
public void FunctionAnalyzer_AssignsDefaultNames()
|
|
{
|
|
// Arrange
|
|
byte[] memory = new byte[4096];
|
|
uint baseAddress = 0x80000000;
|
|
|
|
WriteInstruction(memory, 0, 0x03E00008); // JR $ra
|
|
WriteInstruction(memory, 4, 0x00000000); // NOP
|
|
|
|
var analyzer = new FunctionAnalyzer(memory);
|
|
|
|
// Act
|
|
analyzer.AnalyzeFromEntryPoint(baseAddress);
|
|
|
|
// Assert
|
|
var func = analyzer.Functions[baseAddress];
|
|
Assert.Equal("func_80000000", func.Name);
|
|
}
|
|
|
|
[Fact(Skip = "ExportCallGraphToDot method removed")]
|
|
public void FunctionAnalyzer_ExportsToDotFormat()
|
|
{
|
|
// Arrange
|
|
byte[] memory = new byte[4096];
|
|
uint baseAddress = 0x80000000;
|
|
uint subAddress = baseAddress + 0x10;
|
|
|
|
// main calls sub
|
|
WriteInstruction(memory, 0, 0x0C000004); // JAL 0x80000010
|
|
WriteInstruction(memory, 4, 0x00000000); // NOP
|
|
WriteInstruction(memory, 8, 0x03E00008); // JR $ra
|
|
WriteInstruction(memory, 12, 0x00000000); // NOP
|
|
WriteInstruction(memory, 16, 0x03E00008); // JR $ra (sub)
|
|
WriteInstruction(memory, 20, 0x00000000); // NOP
|
|
|
|
var analyzer = new FunctionAnalyzer(memory);
|
|
analyzer.AnalyzeFromEntryPoint(baseAddress);
|
|
|
|
// Act
|
|
// string dot = analyzer.ExportCallGraphToDot();
|
|
|
|
// Assert
|
|
// Assert.Contains("digraph", dot);
|
|
// Assert.Contains("func_80000000", dot);
|
|
// Assert.Contains("func_80000010", dot);
|
|
// Assert.Contains("->", dot);
|
|
}
|
|
|
|
[Fact]
|
|
public void FunctionAnalyzer_HandlesUnconditionalJump()
|
|
{
|
|
// Arrange - Function with unconditional jump
|
|
byte[] memory = new byte[4096];
|
|
uint baseAddress = 0x80000000;
|
|
|
|
// main:
|
|
// j 0x80000010
|
|
// nop
|
|
// ...
|
|
// addiu $v0, $zero, 1
|
|
// jr $ra
|
|
// nop
|
|
WriteInstruction(memory, 0, 0x08000004); // J 0x80000010
|
|
WriteInstruction(memory, 4, 0x00000000); // NOP
|
|
WriteInstruction(memory, 8, 0x00000000); // NOP (unreachable)
|
|
WriteInstruction(memory, 12, 0x00000000); // NOP (unreachable)
|
|
WriteInstruction(memory, 16, 0x24020001); // ADDIU $v0, $zero, 1
|
|
WriteInstruction(memory, 20, 0x03E00008); // JR $ra
|
|
WriteInstruction(memory, 24, 0x00000000); // NOP
|
|
|
|
var analyzer = new FunctionAnalyzer(memory);
|
|
|
|
// Act
|
|
analyzer.AnalyzeFromEntryPoint(baseAddress);
|
|
|
|
// Assert
|
|
var func = analyzer.Functions[baseAddress];
|
|
Assert.Contains(baseAddress + 0, func.Instructions); // J
|
|
Assert.Contains(baseAddress + 16, func.Instructions); // ADDIU
|
|
Assert.Contains(baseAddress + 20, func.Instructions); // JR
|
|
Assert.DoesNotContain(baseAddress + 8, func.Instructions); // Unreachable
|
|
}
|
|
|
|
[Fact]
|
|
public void FunctionAnalyzer_HandlesRecursiveCalls()
|
|
{
|
|
// Arrange - Recursive factorial function
|
|
byte[] memory = new byte[4096];
|
|
uint baseAddress = 0x80000000;
|
|
|
|
// factorial:
|
|
// beq $a0, $zero, done
|
|
// nop
|
|
// addiu $a0, $a0, -1
|
|
// jal factorial (recursive)
|
|
// nop
|
|
// done:
|
|
// jr $ra
|
|
// nop
|
|
WriteInstruction(memory, 0, 0x10800003); // BEQ $a0, $zero, +3
|
|
WriteInstruction(memory, 4, 0x00000000); // NOP
|
|
WriteInstruction(memory, 8, 0x2484FFFF); // ADDIU $a0, $a0, -1
|
|
WriteInstruction(memory, 12, 0x0C000000); // JAL 0x80000000 (self)
|
|
WriteInstruction(memory, 16, 0x00000000); // NOP
|
|
WriteInstruction(memory, 20, 0x03E00008); // JR $ra
|
|
WriteInstruction(memory, 24, 0x00000000); // NOP
|
|
|
|
var analyzer = new FunctionAnalyzer(memory);
|
|
|
|
// Act
|
|
analyzer.AnalyzeFromEntryPoint(baseAddress);
|
|
|
|
// Assert - Should have one function that calls itself
|
|
Assert.Single(analyzer.Functions);
|
|
var func = analyzer.Functions[baseAddress];
|
|
Assert.Contains(baseAddress, func.CallsTo); // Calls itself
|
|
Assert.Contains(baseAddress, func.CalledFrom); // Called by itself
|
|
}
|
|
|
|
[Fact(Skip = "GetOrCreateFunction method removed")]
|
|
public void FunctionAnalyzer_GetOrCreateFunction_CreatesNewFunction()
|
|
{
|
|
// Arrange
|
|
byte[] memory = new byte[4096];
|
|
uint baseAddress = 0x80000000;
|
|
var analyzer = new FunctionAnalyzer(memory);
|
|
|
|
// Act
|
|
// var func1 = analyzer.GetOrCreateFunction(baseAddress);
|
|
// var func2 = analyzer.GetOrCreateFunction(baseAddress);
|
|
|
|
// Assert - Should return same instance
|
|
// Assert.Same(func1, func2);
|
|
// Assert.Single(analyzer.Functions);
|
|
}
|
|
|
|
[Fact]
|
|
public void FunctionAnalyzer_HandlesMultipleFunctions()
|
|
{
|
|
// Arrange - Program with 3 functions
|
|
byte[] memory = new byte[4096];
|
|
uint baseAddress = 0x80000000;
|
|
uint func1 = baseAddress + 0x20;
|
|
uint func2 = baseAddress + 0x40;
|
|
|
|
// main:
|
|
// jal func1
|
|
// nop
|
|
// jal func2
|
|
// nop
|
|
// jr $ra
|
|
// nop
|
|
WriteInstruction(memory, 0, 0x0C000008); // JAL func1
|
|
WriteInstruction(memory, 4, 0x00000000); // NOP
|
|
WriteInstruction(memory, 8, 0x0C000010); // JAL func2
|
|
WriteInstruction(memory, 12, 0x00000000); // NOP
|
|
WriteInstruction(memory, 16, 0x03E00008); // JR $ra
|
|
WriteInstruction(memory, 20, 0x00000000); // NOP
|
|
|
|
// func1:
|
|
// jr $ra
|
|
// nop
|
|
WriteInstruction(memory, 32, 0x03E00008); // JR $ra
|
|
WriteInstruction(memory, 36, 0x00000000); // NOP
|
|
|
|
// func2:
|
|
// jr $ra
|
|
// nop
|
|
WriteInstruction(memory, 64, 0x03E00008); // JR $ra
|
|
WriteInstruction(memory, 68, 0x00000000); // NOP
|
|
|
|
var analyzer = new FunctionAnalyzer(memory);
|
|
|
|
// Act
|
|
analyzer.AnalyzeFromEntryPoint(baseAddress);
|
|
|
|
// Assert
|
|
Assert.Equal(3, analyzer.Functions.Count);
|
|
Assert.True(analyzer.Functions.ContainsKey(baseAddress));
|
|
Assert.True(analyzer.Functions.ContainsKey(func1));
|
|
Assert.True(analyzer.Functions.ContainsKey(func2));
|
|
|
|
var mainFunc = analyzer.Functions[baseAddress];
|
|
Assert.Contains(func1, mainFunc.CallsTo);
|
|
Assert.Contains(func2, mainFunc.CallsTo);
|
|
}
|
|
|
|
[Fact]
|
|
public void Function_ToString_FormatsCorrectly()
|
|
{
|
|
// Arrange
|
|
var func = new Function
|
|
{
|
|
Address = 0x80000000,
|
|
Name = "main"
|
|
};
|
|
func.Instructions.Add(0x80000000);
|
|
func.Instructions.Add(0x80000004);
|
|
func.CallsTo.Add(0x80000100);
|
|
|
|
// Act
|
|
string result = func.ToString();
|
|
|
|
// Assert
|
|
Assert.Contains("main", result);
|
|
Assert.Contains("0x80000000", result);
|
|
Assert.Contains("2", result); // instruction count
|
|
Assert.Contains("1", result); // calls count
|
|
}
|
|
|
|
[Fact]
|
|
public void FunctionAnalyzer_StopsAtSafetyLimit()
|
|
{
|
|
// Arrange - Create a scenario that could loop infinitely
|
|
byte[] memory = new byte[4096];
|
|
uint baseAddress = 0x80000000;
|
|
|
|
// Infinite loop: j 0x80000000
|
|
for (int i = 0; i < 1000; i += 4)
|
|
{
|
|
WriteInstruction(memory, i, 0x08000000); // J 0x80000000
|
|
}
|
|
|
|
var analyzer = new FunctionAnalyzer(memory);
|
|
|
|
// Act - Should not hang or throw
|
|
analyzer.AnalyzeFromEntryPoint(baseAddress);
|
|
|
|
// Assert - Should have analyzed something without hanging
|
|
Assert.NotEmpty(analyzer.Functions);
|
|
}
|
|
|
|
private void WriteInstruction(byte[] memory, int offset, uint instruction)
|
|
{
|
|
byte[] bytes = BitConverter.GetBytes(instruction);
|
|
Array.Copy(bytes, 0, memory, offset, 4);
|
|
}
|
|
}
|