Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions Cpp2IL.Core/InstructionSets/NewArmV8InstructionSet.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Disarm;
using Cpp2IL.Core.Api;
Expand Down Expand Up @@ -189,6 +191,34 @@ public override List<Instruction> GetIsilFromMethod(MethodAnalysisContext contex
return instructions;
}

/// <summary>
/// Reads the single-precision value held at a constant address, when that address is in a segment
/// the runtime cannot write. A writable segment only holds its final value once the runtime has
/// initialised it, so the bytes there are not a constant.
/// </summary>
private static bool TryReadSingleLiteral(MethodAnalysisContext context, long address,
[NotNullWhen(true)] out FloatLiteral? literal)
{
literal = null;

if (address <= 0)
return false;

var binary = context.AppContext.Binary;

if (!binary.IsVirtualAddressReadOnly((ulong)address)
|| !binary.TryMapVirtualAddressToRaw((ulong)address, out var raw) || raw < 0)
return false;

var content = binary.GetRawBinaryContent();

if (raw + sizeof(float) > content.Length)
return false;

literal = new FloatLiteral(BinaryPrimitives.ReadSingleLittleEndian(content.Slice((int)raw, sizeof(float))));
return true;
}

private void ConvertInstructionStatement(Arm64Instruction instruction, List<Instruction> instructions, List<ulong> addresses, MethodAnalysisContext context)
{
var address = instruction.Address;
Expand Down Expand Up @@ -472,6 +502,15 @@ void EmitConditionalAssign(Arm64ConditionCode condition, Action emitTrueValue, A
? new MemoryOperand(addend: (long)address + instruction.Op1Imm)
: MemOperand();

// A single-precision load from a constant address in a segment the runtime cannot
// write is a literal pool entry, so its value is known right here. Restricted to Sn
// because the width is then unambiguous - an LDR Dn may hold two .2S lanes rather
// than one double, and reading it as a double would invent a value.
if (instruction.Op0Reg is >= Arm64Register.S0 and <= Arm64Register.S31
&& source is MemoryOperand { IsConstant: true } poolEntry
&& TryReadSingleLiteral(context, poolEntry.Addend, out var poolValue))
source = poolValue;

if (instruction.Op0Kind == Arm64OperandKind.Register && IsReg31(instruction.Op0Reg))
Add(address, OpCode.Nop); // load to xzr = prefetch, discard
else
Expand Down
13 changes: 13 additions & 0 deletions LibCpp2IL/Elf/ElfFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,19 @@ public override (ulong pCodeRegistration, ulong pMetadataRegistration) FindCodeA

public override long RawLength => _raw.Length;

public override bool IsVirtualAddressReadOnly(ulong addr)
{
var segment = _elfProgramHeaderEntries.FirstOrDefault(x => x.Type == ElfProgramEntryType.PT_LOAD
&& addr >= x.VirtualAddress
&& addr < x.VirtualAddress + x.VirtualSize);

//Bytes past RawSize are zero-filled at load time (.bss) rather than read from the file, so only
//the file-backed part of a non-writable segment holds a constant.
return segment != null
&& (segment.Flags & ElfProgramHeaderFlags.PF_W) == 0
&& addr < segment.VirtualAddress + segment.RawSize;
}

public override long MapVirtualAddressToRaw(ulong addr, bool throwOnError = true)
{
var section = _elfProgramHeaderEntries.FirstOrDefault(x => addr >= x.VirtualAddress && addr < x.VirtualAddress + x.VirtualSize);
Expand Down
7 changes: 7 additions & 0 deletions LibCpp2IL/Il2CppBinary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,13 @@ public ulong GetMethodPointer(int methodIndex, Il2CppVariableWidthIndex<Il2CppMe
public abstract ulong GetVirtualAddressOfExportedFunctionByName(string toFind);
public virtual bool IsExportedFunction(ulong addr) => false;

/// <summary>
/// Whether the bytes at the given virtual address cannot change once the binary is loaded, which
/// makes them a compile-time constant. Defaults to false, so a caller only gets a true answer from
/// a binary format that actually reports segment permissions.
/// </summary>
public virtual bool IsVirtualAddressReadOnly(ulong addr) => false;

public virtual bool TryGetExportedFunctionName(ulong addr, [NotNullWhen(true)] out string? name)
{
name = null;
Expand Down