iced/Iced/Intel/BlockEncoder.cs

326 lines
11 KiB
C#

/*
Copyright (C) 2018-2019 de4dot@gmail.com
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#if !NO_ENCODER
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Iced.Intel.BlockEncoderInternal;
namespace Iced.Intel {
/// <summary>
/// Relocation kind
/// </summary>
public enum RelocKind {
/// <summary>
/// 64-bit offset. Only used if it's 64-bit code.
/// </summary>
Offset64,
}
/// <summary>
/// Relocation info
/// </summary>
public readonly struct RelocInfo {
/// <summary>
/// Address
/// </summary>
public readonly ulong Address;
/// <summary>
/// Relocation kind
/// </summary>
public readonly RelocKind Kind;
/// <summary>
/// Constructor
/// </summary>
/// <param name="kind">Relocation kind</param>
/// <param name="address">Address</param>
public RelocInfo(RelocKind kind, ulong address) {
Kind = kind;
Address = address;
}
}
/// <summary>
/// Contains a list of instructions and a base IP
/// </summary>
public readonly struct InstructionBlock {
/// <summary>
/// Code writer
/// </summary>
public readonly CodeWriter CodeWriter;
/// <summary>
/// All instructions
/// </summary>
public readonly IList<Instruction> Instructions;
/// <summary>
/// Base IP of all encoded instructions
/// </summary>
public readonly ulong RIP;
/// <summary>
/// List that gets all reloc infos or null
/// </summary>
public readonly IList<RelocInfo> RelocInfos;
/// <summary>
/// Offsets of the new instructions relative to the base IP. If the instruction was rewritten to
/// a completely different instruction, the value <see cref="uint.MaxValue"/> is stored in that array element.
/// This array can be null.
/// </summary>
public readonly uint[] NewInstructionOffsets;
/// <summary>
/// Offsets of all constants in the new encoded instructions. If the instruction was rewritten,
/// the 'default' value is stored in the corresponding array element. This array can be null.
/// </summary>
public readonly ConstantOffsets[] ConstantOffsets;
/// <summary>
/// Constructor
/// </summary>
/// <param name="codeWriter">Code writer</param>
/// <param name="instructions">Instructions</param>
/// <param name="rip">Base IP of all encoded instructions</param>
/// <param name="relocInfos">List that gets all reloc infos or null</param>
/// <param name="newInstructionOffsets">Offsets of the new instructions relative to the base IP. If the instruction was rewritten to
/// a completely different instruction, the value <see cref="uint.MaxValue"/> is stored in that array element.
/// This array can be null.</param>
/// <param name="constantOffsets">Offsets of all constants in the new encoded instructions. If the instruction was rewritten,
/// the 'default' value is stored in the corresponding array element. This array can be null.</param>
public InstructionBlock(CodeWriter codeWriter, IList<Instruction> instructions, ulong rip, IList<RelocInfo> relocInfos = null, uint[] newInstructionOffsets = null, ConstantOffsets[] constantOffsets = null) {
CodeWriter = codeWriter ?? throw new ArgumentNullException(nameof(codeWriter));
Instructions = instructions ?? throw new ArgumentNullException(nameof(instructions));
RIP = rip;
RelocInfos = relocInfos;
NewInstructionOffsets = newInstructionOffsets;
ConstantOffsets = constantOffsets;
if (newInstructionOffsets != null && newInstructionOffsets.Length != instructions.Count)
throw new ArgumentException($"{nameof(newInstructionOffsets)} must have the same number of elements as {nameof(instructions)} or it must be null");
if (constantOffsets != null && constantOffsets.Length != instructions.Count)
throw new ArgumentException($"{nameof(constantOffsets)} must have the same number of elements as {nameof(instructions)} or it must be null");
}
}
/// <summary>
/// Encoder options
/// </summary>
[Flags]
public enum BlockEncoderOptions : uint {
/// <summary>
/// No option is set
/// </summary>
None = 0,
/// <summary>
/// By default, branches get updated if the target is too far away, eg. jcc short -> jcc near
/// or if 64-bit mode, jcc + jmp [rip+mem]. If this option is enabled, no branches are fixed.
/// </summary>
DontFixBranches = 0x00000001,
}
/// <summary>
/// Encodes instructions
/// </summary>
public sealed class BlockEncoder {
readonly int bitness;
readonly BlockEncoderOptions options;
readonly Block[] blocks;
readonly Encoder nullEncoder;
readonly Dictionary<ulong, Instr> toInstr;
internal int Bitness => bitness;
internal bool FixBranches => (options & BlockEncoderOptions.DontFixBranches) == 0;
internal Encoder NullEncoder => nullEncoder;
sealed class NullCodeWriter : CodeWriter {
public static readonly NullCodeWriter Instance = new NullCodeWriter();
NullCodeWriter() { }
public override void WriteByte(byte value) { }
}
BlockEncoder(int bitness, InstructionBlock[] instrBlocks, BlockEncoderOptions options) {
if (bitness != 16 && bitness != 32 && bitness != 64)
throw new ArgumentOutOfRangeException(nameof(bitness));
if (instrBlocks == null)
throw new ArgumentNullException(nameof(instrBlocks));
this.bitness = bitness;
nullEncoder = Encoder.Create(bitness, NullCodeWriter.Instance);
this.options = options;
blocks = new Block[instrBlocks.Length];
int instrCount = 0;
for (int i = 0; i < instrBlocks.Length; i++) {
var instructions = instrBlocks[i].Instructions;
if (instructions == null)
throw new ArgumentException();
var instrs = new Instr[instructions.Count];
ulong ip = instrBlocks[i].RIP;
for (int j = 0; j < instrs.Length; j++) {
var instruction = instructions[j];
var instr = Instr.Create(this, ref instruction);
instr.IP = ip;
instrs[j] = instr;
instrCount++;
Debug.Assert(instr.Size != 0);
ip += instr.Size;
}
blocks[i] = new Block(this, instrBlocks[i].CodeWriter, instrBlocks[i].RIP, instrBlocks[i].RelocInfos, instrBlocks[i].NewInstructionOffsets, instrBlocks[i].ConstantOffsets, instrs);
}
// Optimize from low to high addresses
Array.Sort(blocks, (a, b) => a.RIP.CompareTo(b.RIP));
// There must not be any instructions with the same IP, except if IP = 0 (default value)
var toInstr = new Dictionary<ulong, Instr>(instrCount);
this.toInstr = toInstr;
bool hasMultipleZeroIPInstrs = false;
foreach (var block in blocks) {
foreach (var instr in block.Instructions) {
ulong origIP = instr.OrigIP;
if (toInstr.TryGetValue(origIP, out var origInstr)) {
if (origIP != 0)
throw new ArgumentException($"Multiple instructions with the same IP: 0x{origIP:X}");
hasMultipleZeroIPInstrs = true;
}
else
toInstr[origIP] = instr;
}
}
if (hasMultipleZeroIPInstrs)
toInstr.Remove(0);
foreach (var block in blocks) {
ulong ip = block.RIP;
foreach (var instr in block.Instructions) {
instr.IP = ip;
var oldSize = instr.Size;
instr.Initialize();
if (instr.Size > oldSize)
throw new InvalidOperationException();
ip += instr.Size;
}
}
}
/// <summary>
/// Encodes instructions. Returns null or an error message
/// </summary>
/// <param name="bitness">16, 32 or 64</param>
/// <param name="block">All instructions</param>
/// <param name="errorMessage">Updated with an error message if the method failed</param>
/// <param name="options">Encoder options</param>
/// <returns></returns>
public static bool TryEncode(int bitness, InstructionBlock block, out string errorMessage, BlockEncoderOptions options = BlockEncoderOptions.None) =>
TryEncode(bitness, new[] { block }, out errorMessage, options);
/// <summary>
/// Encodes instructions. Returns null or an error message
/// </summary>
/// <param name="bitness">16, 32 or 64</param>
/// <param name="blocks">All instructions</param>
/// <param name="errorMessage">Updated with an error message if the method failed</param>
/// <param name="options">Encoder options</param>
/// <returns></returns>
public static bool TryEncode(int bitness, InstructionBlock[] blocks, out string errorMessage, BlockEncoderOptions options = BlockEncoderOptions.None) =>
new BlockEncoder(bitness, blocks, options).Encode(out errorMessage);
bool Encode(out string errorMessage) {
const int MAX_ITERS = 1000;
for (int iter = 0; iter < MAX_ITERS; iter++) {
bool updated = false;
foreach (var block in blocks) {
ulong ip = block.RIP;
foreach (var instr in block.Instructions) {
instr.IP = ip;
var oldSize = instr.Size;
if (instr.Optimize()) {
if (instr.Size > oldSize) {
errorMessage = "Internal error: new size > old size";
return false;
}
if (instr.Size < oldSize)
updated = true;
}
else if (instr.Size != oldSize) {
errorMessage = "Internal error: new size != old size";
return false;
}
ip += instr.Size;
}
}
if (!updated)
break;
}
foreach (var block in blocks)
block.InitializeData();
foreach (var block in blocks) {
var encoder = Encoder.Create(bitness, block.CodeWriter);
ulong ip = block.RIP;
var newInstructionOffsets = block.NewInstructionOffsets;
var constantOffsets = block.ConstantOffsets;
var instructions = block.Instructions;
for (int i = 0; i < instructions.Length; i++) {
var instr = instructions[i];
uint bytesWritten = block.CodeWriter.BytesWritten;
bool isOriginalInstruction;
if (constantOffsets != null)
errorMessage = instr.TryEncode(encoder, out constantOffsets[i], out isOriginalInstruction);
else
errorMessage = instr.TryEncode(encoder, out _, out isOriginalInstruction);
if (errorMessage != null)
return false;
uint size = block.CodeWriter.BytesWritten - bytesWritten;
if (size != instr.Size) {
errorMessage = "Internal error: didn't write all bytes";
return false;
}
if (newInstructionOffsets != null) {
if (isOriginalInstruction)
newInstructionOffsets[i] = (uint)(ip - block.RIP);
else
newInstructionOffsets[i] = uint.MaxValue;
}
ip += size;
}
block.WriteData();
}
errorMessage = null;
return true;
}
internal TargetInstr GetTarget(ulong address) {
if (toInstr.TryGetValue(address, out var instr))
return new TargetInstr(instr);
return new TargetInstr(address);
}
}
}
#endif