209 lines
6.3 KiB
C#
209 lines
6.3 KiB
C#
// Copyright (c) StellaOps. All rights reserved.
|
|
// Licensed under AGPL-3.0-or-later. See LICENSE in the project root.
|
|
|
|
using System.Collections.Immutable;
|
|
using FluentAssertions;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using StellaOps.BinaryIndex.Disassembly;
|
|
using Xunit;
|
|
|
|
namespace StellaOps.BinaryIndex.Semantic.Tests;
|
|
|
|
[Trait("Category", "Unit")]
|
|
public class IrLiftingServiceTests
|
|
{
|
|
private readonly IrLiftingService _sut;
|
|
|
|
public IrLiftingServiceTests()
|
|
{
|
|
_sut = new IrLiftingService(NullLogger<IrLiftingService>.Instance);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(CpuArchitecture.X86)]
|
|
[InlineData(CpuArchitecture.X86_64)]
|
|
[InlineData(CpuArchitecture.ARM32)]
|
|
[InlineData(CpuArchitecture.ARM64)]
|
|
public void SupportsArchitecture_ShouldReturnTrue_ForSupportedArchitectures(CpuArchitecture arch)
|
|
{
|
|
// Act
|
|
var result = _sut.SupportsArchitecture(arch);
|
|
|
|
// Assert
|
|
result.Should().BeTrue();
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(CpuArchitecture.MIPS32)]
|
|
[InlineData(CpuArchitecture.RISCV64)]
|
|
[InlineData(CpuArchitecture.Unknown)]
|
|
public void SupportsArchitecture_ShouldReturnFalse_ForUnsupportedArchitectures(CpuArchitecture arch)
|
|
{
|
|
// Act
|
|
var result = _sut.SupportsArchitecture(arch);
|
|
|
|
// Assert
|
|
result.Should().BeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task LiftToIrAsync_ShouldLiftSimpleInstructions()
|
|
{
|
|
// Arrange
|
|
var instructions = new List<DisassembledInstruction>
|
|
{
|
|
CreateInstruction(0x1000, "MOV", InstructionKind.Move, "RAX", "RBX"),
|
|
CreateInstruction(0x1004, "ADD", InstructionKind.Arithmetic, "RAX", "RCX"),
|
|
CreateInstruction(0x1008, "RET", InstructionKind.Return)
|
|
};
|
|
|
|
// Act
|
|
var result = await _sut.LiftToIrAsync(
|
|
instructions,
|
|
"test_func",
|
|
0x1000,
|
|
CpuArchitecture.X86_64);
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
result.Name.Should().Be("test_func");
|
|
result.Address.Should().Be(0x1000);
|
|
result.Statements.Should().HaveCount(3);
|
|
result.BasicBlocks.Should().NotBeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task LiftToIrAsync_ShouldCreateBasicBlocksOnBranches()
|
|
{
|
|
// Arrange
|
|
var instructions = new List<DisassembledInstruction>
|
|
{
|
|
CreateInstruction(0x1000, "MOV", InstructionKind.Move, "RAX", "0"),
|
|
CreateInstruction(0x1004, "CMP", InstructionKind.Compare, "RAX", "10"),
|
|
CreateInstruction(0x1008, "JE", InstructionKind.ConditionalBranch, "0x1020"),
|
|
CreateInstruction(0x100C, "ADD", InstructionKind.Arithmetic, "RAX", "1"),
|
|
CreateInstruction(0x1010, "RET", InstructionKind.Return)
|
|
};
|
|
|
|
// Act
|
|
var result = await _sut.LiftToIrAsync(
|
|
instructions,
|
|
"branch_func",
|
|
0x1000,
|
|
CpuArchitecture.X86_64);
|
|
|
|
// Assert
|
|
result.BasicBlocks.Should().HaveCountGreaterThan(1);
|
|
result.Cfg.Edges.Should().NotBeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task LiftToIrAsync_ShouldThrow_ForUnsupportedArchitecture()
|
|
{
|
|
// Arrange
|
|
var instructions = new List<DisassembledInstruction>
|
|
{
|
|
CreateInstruction(0x1000, "NOP", InstructionKind.Nop)
|
|
};
|
|
|
|
// Act
|
|
var act = () => _sut.LiftToIrAsync(
|
|
instructions,
|
|
"test",
|
|
0x1000,
|
|
CpuArchitecture.MIPS32);
|
|
|
|
// Assert
|
|
await act.Should().ThrowAsync<NotSupportedException>();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TransformToSsaAsync_ShouldVersionVariables()
|
|
{
|
|
// Arrange
|
|
var instructions = new List<DisassembledInstruction>
|
|
{
|
|
CreateInstruction(0x1000, "MOV", InstructionKind.Move, "RAX", "0"),
|
|
CreateInstruction(0x1004, "ADD", InstructionKind.Arithmetic, "RAX", "1"),
|
|
CreateInstruction(0x1008, "ADD", InstructionKind.Arithmetic, "RAX", "2"),
|
|
CreateInstruction(0x100C, "RET", InstructionKind.Return)
|
|
};
|
|
|
|
var lifted = await _sut.LiftToIrAsync(
|
|
instructions,
|
|
"ssa_test",
|
|
0x1000,
|
|
CpuArchitecture.X86_64);
|
|
|
|
// Act
|
|
var ssa = await _sut.TransformToSsaAsync(lifted);
|
|
|
|
// Assert
|
|
ssa.Should().NotBeNull();
|
|
ssa.Name.Should().Be("ssa_test");
|
|
ssa.Statements.Should().HaveCount(4);
|
|
|
|
// RAX should have multiple versions
|
|
var raxVersions = ssa.Statements
|
|
.Where(s => s.Destination?.BaseName == "RAX")
|
|
.Select(s => s.Destination!.Version)
|
|
.Distinct()
|
|
.ToList();
|
|
|
|
raxVersions.Should().HaveCountGreaterThan(1);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TransformToSsaAsync_ShouldBuildDefUseChains()
|
|
{
|
|
// Arrange
|
|
var instructions = new List<DisassembledInstruction>
|
|
{
|
|
CreateInstruction(0x1000, "MOV", InstructionKind.Move, "RAX", "0"),
|
|
CreateInstruction(0x1004, "ADD", InstructionKind.Arithmetic, "RBX", "RAX"),
|
|
CreateInstruction(0x1008, "RET", InstructionKind.Return)
|
|
};
|
|
|
|
var lifted = await _sut.LiftToIrAsync(
|
|
instructions,
|
|
"defuse_test",
|
|
0x1000,
|
|
CpuArchitecture.X86_64);
|
|
|
|
// Act
|
|
var ssa = await _sut.TransformToSsaAsync(lifted);
|
|
|
|
// Assert
|
|
ssa.DefUse.Should().NotBeNull();
|
|
ssa.DefUse.Definitions.Should().NotBeEmpty();
|
|
}
|
|
|
|
private static DisassembledInstruction CreateInstruction(
|
|
ulong address,
|
|
string mnemonic,
|
|
InstructionKind kind,
|
|
params string[] operands)
|
|
{
|
|
var ops = operands.Select((o, i) =>
|
|
{
|
|
if (long.TryParse(o, out var val))
|
|
{
|
|
return new Operand(OperandType.Immediate, o, val);
|
|
}
|
|
if (o.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return new Operand(OperandType.Address, o);
|
|
}
|
|
return new Operand(OperandType.Register, o, Register: o);
|
|
}).ToImmutableArray();
|
|
|
|
return new DisassembledInstruction(
|
|
address,
|
|
[0x90], // NOP placeholder
|
|
mnemonic,
|
|
string.Join(", ", operands),
|
|
kind,
|
|
ops);
|
|
}
|
|
}
|