675 lines
23 KiB
C#
675 lines
23 KiB
C#
//
|
|
// Author:
|
|
// Jb Evain (jbevain@gmail.com)
|
|
//
|
|
// Copyright (c) 2008 - 2015 Jb Evain
|
|
// Copyright (c) 2008 - 2011 Novell, Inc.
|
|
//
|
|
// Licensed under the MIT/X11 license.
|
|
//
|
|
|
|
using MonoFN.Cecil.PE;
|
|
using MonoFN.Collections.Generic;
|
|
using System;
|
|
|
|
namespace MonoFN.Cecil.Cil
|
|
{
|
|
internal sealed class CodeReader : BinaryStreamReader
|
|
{
|
|
internal readonly MetadataReader reader;
|
|
private int start;
|
|
private MethodDefinition method;
|
|
private MethodBody body;
|
|
private int Offset
|
|
{
|
|
get { return Position - start; }
|
|
}
|
|
|
|
public CodeReader(MetadataReader reader) : base(reader.image.Stream.value)
|
|
{
|
|
this.reader = reader;
|
|
}
|
|
|
|
public int MoveTo(MethodDefinition method)
|
|
{
|
|
this.method = method;
|
|
reader.context = method;
|
|
int position = Position;
|
|
Position = (int)reader.image.ResolveVirtualAddress((uint)method.RVA);
|
|
return position;
|
|
}
|
|
|
|
public void MoveBackTo(int position)
|
|
{
|
|
reader.context = null;
|
|
Position = position;
|
|
}
|
|
|
|
public MethodBody ReadMethodBody(MethodDefinition method)
|
|
{
|
|
int position = MoveTo(method);
|
|
body = new(method);
|
|
|
|
ReadMethodBody();
|
|
|
|
MoveBackTo(position);
|
|
return body;
|
|
}
|
|
|
|
public int ReadCodeSize(MethodDefinition method)
|
|
{
|
|
int position = MoveTo(method);
|
|
|
|
int code_size = ReadCodeSize();
|
|
|
|
MoveBackTo(position);
|
|
return code_size;
|
|
}
|
|
|
|
private int ReadCodeSize()
|
|
{
|
|
byte flags = ReadByte();
|
|
switch (flags & 0x3)
|
|
{
|
|
case 0x2: // tiny
|
|
return flags >> 2;
|
|
case 0x3: // fat
|
|
Advance(-1 + 2 + 2); // go back, 2 bytes flags, 2 bytes stack size
|
|
return (int)ReadUInt32();
|
|
default:
|
|
throw new InvalidOperationException();
|
|
}
|
|
}
|
|
|
|
private void ReadMethodBody()
|
|
{
|
|
byte flags = ReadByte();
|
|
switch (flags & 0x3)
|
|
{
|
|
case 0x2: // tiny
|
|
body.code_size = flags >> 2;
|
|
body.MaxStackSize = 8;
|
|
ReadCode();
|
|
break;
|
|
case 0x3: // fat
|
|
Advance(-1);
|
|
ReadFatMethod();
|
|
break;
|
|
default:
|
|
throw new InvalidOperationException();
|
|
}
|
|
|
|
ISymbolReader symbol_reader = reader.module.symbol_reader;
|
|
|
|
if (symbol_reader != null && method.debug_info == null)
|
|
method.debug_info = symbol_reader.Read(method);
|
|
|
|
if (method.debug_info != null)
|
|
ReadDebugInfo();
|
|
}
|
|
|
|
private void ReadFatMethod()
|
|
{
|
|
ushort flags = ReadUInt16();
|
|
body.max_stack_size = ReadUInt16();
|
|
body.code_size = (int)ReadUInt32();
|
|
body.local_var_token = new(ReadUInt32());
|
|
body.init_locals = (flags & 0x10) != 0;
|
|
|
|
if (body.local_var_token.RID != 0)
|
|
body.variables = ReadVariables(body.local_var_token);
|
|
|
|
ReadCode();
|
|
|
|
if ((flags & 0x8) != 0)
|
|
ReadSection();
|
|
}
|
|
|
|
public VariableDefinitionCollection ReadVariables(MetadataToken local_var_token)
|
|
{
|
|
int position = reader.position;
|
|
VariableDefinitionCollection variables = reader.ReadVariables(local_var_token, method);
|
|
reader.position = position;
|
|
|
|
return variables;
|
|
}
|
|
|
|
private void ReadCode()
|
|
{
|
|
start = Position;
|
|
int code_size = body.code_size;
|
|
|
|
if (code_size < 0 || Length <= (uint)(code_size + Position))
|
|
code_size = 0;
|
|
|
|
int end = start + code_size;
|
|
Collection<Instruction> instructions = body.instructions = new InstructionCollection(method, (code_size + 1) / 2);
|
|
|
|
while (Position < end)
|
|
{
|
|
int offset = Position - start;
|
|
OpCode opcode = ReadOpCode();
|
|
Instruction current = new(offset, opcode);
|
|
|
|
if (opcode.OperandType != OperandType.InlineNone)
|
|
current.operand = ReadOperand(current);
|
|
|
|
instructions.Add(current);
|
|
}
|
|
|
|
ResolveBranches(instructions);
|
|
}
|
|
|
|
private OpCode ReadOpCode()
|
|
{
|
|
byte il_opcode = ReadByte();
|
|
return il_opcode != 0xfe ? OpCodes.OneByteOpCode[il_opcode] : OpCodes.TwoBytesOpCode[ReadByte()];
|
|
}
|
|
|
|
private object ReadOperand(Instruction instruction)
|
|
{
|
|
switch (instruction.opcode.OperandType)
|
|
{
|
|
case OperandType.InlineSwitch:
|
|
int length = ReadInt32();
|
|
int base_offset = Offset + 4 * length;
|
|
int[] branches = new int [length];
|
|
for (int i = 0; i < length; i++)
|
|
branches[i] = base_offset + ReadInt32();
|
|
return branches;
|
|
case OperandType.ShortInlineBrTarget:
|
|
return ReadSByte() + Offset;
|
|
case OperandType.InlineBrTarget:
|
|
return ReadInt32() + Offset;
|
|
case OperandType.ShortInlineI:
|
|
if (instruction.opcode == OpCodes.Ldc_I4_S)
|
|
return ReadSByte();
|
|
|
|
return ReadByte();
|
|
case OperandType.InlineI:
|
|
return ReadInt32();
|
|
case OperandType.ShortInlineR:
|
|
return ReadSingle();
|
|
case OperandType.InlineR:
|
|
return ReadDouble();
|
|
case OperandType.InlineI8:
|
|
return ReadInt64();
|
|
case OperandType.ShortInlineVar:
|
|
return GetVariable(ReadByte());
|
|
case OperandType.InlineVar:
|
|
return GetVariable(ReadUInt16());
|
|
case OperandType.ShortInlineArg:
|
|
return GetParameter(ReadByte());
|
|
case OperandType.InlineArg:
|
|
return GetParameter(ReadUInt16());
|
|
case OperandType.InlineSig:
|
|
return GetCallSite(ReadToken());
|
|
case OperandType.InlineString:
|
|
return GetString(ReadToken());
|
|
case OperandType.InlineTok:
|
|
case OperandType.InlineType:
|
|
case OperandType.InlineMethod:
|
|
case OperandType.InlineField:
|
|
return reader.LookupToken(ReadToken());
|
|
default:
|
|
throw new NotSupportedException();
|
|
}
|
|
}
|
|
|
|
public string GetString(MetadataToken token)
|
|
{
|
|
return reader.image.UserStringHeap.Read(token.RID);
|
|
}
|
|
|
|
public ParameterDefinition GetParameter(int index)
|
|
{
|
|
return body.GetParameter(index);
|
|
}
|
|
|
|
public VariableDefinition GetVariable(int index)
|
|
{
|
|
return body.GetVariable(index);
|
|
}
|
|
|
|
public CallSite GetCallSite(MetadataToken token)
|
|
{
|
|
return reader.ReadCallSite(token);
|
|
}
|
|
|
|
private void ResolveBranches(Collection<Instruction> instructions)
|
|
{
|
|
Instruction[] items = instructions.items;
|
|
int size = instructions.size;
|
|
|
|
for (int i = 0; i < size; i++)
|
|
{
|
|
Instruction instruction = items[i];
|
|
switch (instruction.opcode.OperandType)
|
|
{
|
|
case OperandType.ShortInlineBrTarget:
|
|
case OperandType.InlineBrTarget:
|
|
instruction.operand = GetInstruction((int)instruction.operand);
|
|
break;
|
|
case OperandType.InlineSwitch:
|
|
int[] offsets = (int[])instruction.operand;
|
|
Instruction[] branches = new Instruction [offsets.Length];
|
|
for (int j = 0; j < offsets.Length; j++)
|
|
branches[j] = GetInstruction(offsets[j]);
|
|
|
|
instruction.operand = branches;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private Instruction GetInstruction(int offset)
|
|
{
|
|
return GetInstruction(body.Instructions, offset);
|
|
}
|
|
|
|
private static Instruction GetInstruction(Collection<Instruction> instructions, int offset)
|
|
{
|
|
int size = instructions.size;
|
|
Instruction[] items = instructions.items;
|
|
if (offset < 0 || offset > items[size - 1].offset)
|
|
return null;
|
|
|
|
int min = 0;
|
|
int max = size - 1;
|
|
while (min <= max)
|
|
{
|
|
int mid = min + (max - min) / 2;
|
|
Instruction instruction = items[mid];
|
|
int instruction_offset = instruction.offset;
|
|
|
|
if (offset == instruction_offset)
|
|
return instruction;
|
|
|
|
if (offset < instruction_offset)
|
|
max = mid - 1;
|
|
else
|
|
min = mid + 1;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private void ReadSection()
|
|
{
|
|
Align(4);
|
|
|
|
const byte fat_format = 0x40;
|
|
const byte more_sects = 0x80;
|
|
|
|
byte flags = ReadByte();
|
|
if ((flags & fat_format) == 0)
|
|
ReadSmallSection();
|
|
else
|
|
ReadFatSection();
|
|
|
|
if ((flags & more_sects) != 0)
|
|
ReadSection();
|
|
}
|
|
|
|
private void ReadSmallSection()
|
|
{
|
|
int count = ReadByte() / 12;
|
|
Advance(2);
|
|
|
|
ReadExceptionHandlers(count, () => (int)ReadUInt16(), () => (int)ReadByte());
|
|
}
|
|
|
|
private void ReadFatSection()
|
|
{
|
|
Advance(-1);
|
|
int count = (ReadInt32() >> 8) / 24;
|
|
|
|
ReadExceptionHandlers(count, ReadInt32, ReadInt32);
|
|
}
|
|
|
|
// inline ?
|
|
private void ReadExceptionHandlers(int count, Func<int> read_entry, Func<int> read_length)
|
|
{
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
ExceptionHandler handler = new((ExceptionHandlerType)(read_entry() & 0x7));
|
|
|
|
handler.TryStart = GetInstruction(read_entry());
|
|
handler.TryEnd = GetInstruction(handler.TryStart.Offset + read_length());
|
|
|
|
handler.HandlerStart = GetInstruction(read_entry());
|
|
handler.HandlerEnd = GetInstruction(handler.HandlerStart.Offset + read_length());
|
|
|
|
ReadExceptionHandlerSpecific(handler);
|
|
|
|
body.ExceptionHandlers.Add(handler);
|
|
}
|
|
}
|
|
|
|
private void ReadExceptionHandlerSpecific(ExceptionHandler handler)
|
|
{
|
|
switch (handler.HandlerType)
|
|
{
|
|
case ExceptionHandlerType.Catch:
|
|
handler.CatchType = (TypeReference)reader.LookupToken(ReadToken());
|
|
break;
|
|
case ExceptionHandlerType.Filter:
|
|
handler.FilterStart = GetInstruction(ReadInt32());
|
|
break;
|
|
default:
|
|
Advance(4);
|
|
break;
|
|
}
|
|
}
|
|
|
|
public MetadataToken ReadToken()
|
|
{
|
|
return new(ReadUInt32());
|
|
}
|
|
|
|
private void ReadDebugInfo()
|
|
{
|
|
if (method.debug_info.sequence_points != null)
|
|
ReadSequencePoints();
|
|
|
|
if (method.debug_info.scope != null)
|
|
ReadScope(method.debug_info.scope);
|
|
|
|
if (method.custom_infos != null)
|
|
ReadCustomDebugInformations(method);
|
|
}
|
|
|
|
private void ReadCustomDebugInformations(MethodDefinition method)
|
|
{
|
|
Collection<CustomDebugInformation> custom_infos = method.custom_infos;
|
|
|
|
for (int i = 0; i < custom_infos.Count; i++)
|
|
{
|
|
StateMachineScopeDebugInformation state_machine_scope = custom_infos[i] as StateMachineScopeDebugInformation;
|
|
if (state_machine_scope != null)
|
|
ReadStateMachineScope(state_machine_scope);
|
|
|
|
AsyncMethodBodyDebugInformation async_method = custom_infos[i] as AsyncMethodBodyDebugInformation;
|
|
if (async_method != null)
|
|
ReadAsyncMethodBody(async_method);
|
|
}
|
|
}
|
|
|
|
private void ReadAsyncMethodBody(AsyncMethodBodyDebugInformation async_method)
|
|
{
|
|
if (async_method.catch_handler.Offset > -1)
|
|
async_method.catch_handler = new(GetInstruction(async_method.catch_handler.Offset));
|
|
|
|
if (!async_method.yields.IsNullOrEmpty())
|
|
for (int i = 0; i < async_method.yields.Count; i++)
|
|
async_method.yields[i] = new(GetInstruction(async_method.yields[i].Offset));
|
|
|
|
if (!async_method.resumes.IsNullOrEmpty())
|
|
for (int i = 0; i < async_method.resumes.Count; i++)
|
|
async_method.resumes[i] = new(GetInstruction(async_method.resumes[i].Offset));
|
|
}
|
|
|
|
private void ReadStateMachineScope(StateMachineScopeDebugInformation state_machine_scope)
|
|
{
|
|
if (state_machine_scope.scopes.IsNullOrEmpty())
|
|
return;
|
|
|
|
foreach (StateMachineScope scope in state_machine_scope.scopes)
|
|
{
|
|
scope.start = new(GetInstruction(scope.start.Offset));
|
|
|
|
Instruction end_instruction = GetInstruction(scope.end.Offset);
|
|
scope.end = end_instruction == null ? new() : new InstructionOffset(end_instruction);
|
|
}
|
|
}
|
|
|
|
private void ReadSequencePoints()
|
|
{
|
|
MethodDebugInformation symbol = method.debug_info;
|
|
|
|
for (int i = 0; i < symbol.sequence_points.Count; i++)
|
|
{
|
|
SequencePoint sequence_point = symbol.sequence_points[i];
|
|
Instruction instruction = GetInstruction(sequence_point.Offset);
|
|
if (instruction != null)
|
|
sequence_point.offset = new(instruction);
|
|
}
|
|
}
|
|
|
|
private void ReadScopes(Collection<ScopeDebugInformation> scopes)
|
|
{
|
|
for (int i = 0; i < scopes.Count; i++)
|
|
ReadScope(scopes[i]);
|
|
}
|
|
|
|
private void ReadScope(ScopeDebugInformation scope)
|
|
{
|
|
Instruction start_instruction = GetInstruction(scope.Start.Offset);
|
|
if (start_instruction != null)
|
|
scope.Start = new(start_instruction);
|
|
|
|
Instruction end_instruction = GetInstruction(scope.End.Offset);
|
|
scope.End = end_instruction != null ? new(end_instruction) : new InstructionOffset();
|
|
|
|
if (!scope.variables.IsNullOrEmpty())
|
|
{
|
|
for (int i = 0; i < scope.variables.Count; i++)
|
|
{
|
|
VariableDebugInformation variable_info = scope.variables[i];
|
|
VariableDefinition variable = GetVariable(variable_info.Index);
|
|
if (variable != null)
|
|
variable_info.index = new(variable);
|
|
}
|
|
}
|
|
|
|
if (!scope.scopes.IsNullOrEmpty())
|
|
ReadScopes(scope.scopes);
|
|
}
|
|
|
|
public ByteBuffer PatchRawMethodBody(MethodDefinition method, CodeWriter writer, out int code_size, out MetadataToken local_var_token)
|
|
{
|
|
int position = MoveTo(method);
|
|
|
|
ByteBuffer buffer = new();
|
|
|
|
byte flags = ReadByte();
|
|
|
|
switch (flags & 0x3)
|
|
{
|
|
case 0x2: // tiny
|
|
buffer.WriteByte(flags);
|
|
local_var_token = MetadataToken.Zero;
|
|
code_size = flags >> 2;
|
|
PatchRawCode(buffer, code_size, writer);
|
|
break;
|
|
case 0x3: // fat
|
|
Advance(-1);
|
|
PatchRawFatMethod(buffer, writer, out code_size, out local_var_token);
|
|
break;
|
|
default:
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
MoveBackTo(position);
|
|
|
|
return buffer;
|
|
}
|
|
|
|
private void PatchRawFatMethod(ByteBuffer buffer, CodeWriter writer, out int code_size, out MetadataToken local_var_token)
|
|
{
|
|
ushort flags = ReadUInt16();
|
|
buffer.WriteUInt16(flags);
|
|
buffer.WriteUInt16(ReadUInt16());
|
|
code_size = ReadInt32();
|
|
buffer.WriteInt32(code_size);
|
|
local_var_token = ReadToken();
|
|
|
|
if (local_var_token.RID > 0)
|
|
{
|
|
VariableDefinitionCollection variables = ReadVariables(local_var_token);
|
|
buffer.WriteUInt32(variables != null ? writer.GetStandAloneSignature(variables).ToUInt32() : 0);
|
|
}
|
|
else
|
|
{
|
|
buffer.WriteUInt32(0);
|
|
}
|
|
|
|
PatchRawCode(buffer, code_size, writer);
|
|
|
|
if ((flags & 0x8) != 0)
|
|
PatchRawSection(buffer, writer.metadata);
|
|
}
|
|
|
|
private void PatchRawCode(ByteBuffer buffer, int code_size, CodeWriter writer)
|
|
{
|
|
MetadataBuilder metadata = writer.metadata;
|
|
buffer.WriteBytes(ReadBytes(code_size));
|
|
int end = buffer.position;
|
|
buffer.position -= code_size;
|
|
|
|
while (buffer.position < end)
|
|
{
|
|
OpCode opcode;
|
|
byte il_opcode = buffer.ReadByte();
|
|
if (il_opcode != 0xfe)
|
|
{
|
|
opcode = OpCodes.OneByteOpCode[il_opcode];
|
|
}
|
|
else
|
|
{
|
|
byte il_opcode2 = buffer.ReadByte();
|
|
opcode = OpCodes.TwoBytesOpCode[il_opcode2];
|
|
}
|
|
|
|
switch (opcode.OperandType)
|
|
{
|
|
case OperandType.ShortInlineI:
|
|
case OperandType.ShortInlineBrTarget:
|
|
case OperandType.ShortInlineVar:
|
|
case OperandType.ShortInlineArg:
|
|
buffer.position += 1;
|
|
break;
|
|
case OperandType.InlineVar:
|
|
case OperandType.InlineArg:
|
|
buffer.position += 2;
|
|
break;
|
|
case OperandType.InlineBrTarget:
|
|
case OperandType.ShortInlineR:
|
|
case OperandType.InlineI:
|
|
buffer.position += 4;
|
|
break;
|
|
case OperandType.InlineI8:
|
|
case OperandType.InlineR:
|
|
buffer.position += 8;
|
|
break;
|
|
case OperandType.InlineSwitch:
|
|
int length = buffer.ReadInt32();
|
|
buffer.position += length * 4;
|
|
break;
|
|
case OperandType.InlineString:
|
|
string @string = GetString(new(buffer.ReadUInt32()));
|
|
buffer.position -= 4;
|
|
buffer.WriteUInt32(new MetadataToken(TokenType.String, metadata.user_string_heap.GetStringIndex(@string)).ToUInt32());
|
|
break;
|
|
case OperandType.InlineSig:
|
|
CallSite call_site = GetCallSite(new(buffer.ReadUInt32()));
|
|
buffer.position -= 4;
|
|
buffer.WriteUInt32(writer.GetStandAloneSignature(call_site).ToUInt32());
|
|
break;
|
|
case OperandType.InlineTok:
|
|
case OperandType.InlineType:
|
|
case OperandType.InlineMethod:
|
|
case OperandType.InlineField:
|
|
IMetadataTokenProvider provider = reader.LookupToken(new(buffer.ReadUInt32()));
|
|
buffer.position -= 4;
|
|
buffer.WriteUInt32(metadata.LookupToken(provider).ToUInt32());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void PatchRawSection(ByteBuffer buffer, MetadataBuilder metadata)
|
|
{
|
|
int position = Position;
|
|
Align(4);
|
|
buffer.WriteBytes(Position - position);
|
|
|
|
const byte fat_format = 0x40;
|
|
const byte more_sects = 0x80;
|
|
|
|
byte flags = ReadByte();
|
|
if ((flags & fat_format) == 0)
|
|
{
|
|
buffer.WriteByte(flags);
|
|
PatchRawSmallSection(buffer, metadata);
|
|
}
|
|
else
|
|
{
|
|
PatchRawFatSection(buffer, metadata);
|
|
}
|
|
|
|
if ((flags & more_sects) != 0)
|
|
PatchRawSection(buffer, metadata);
|
|
}
|
|
|
|
private void PatchRawSmallSection(ByteBuffer buffer, MetadataBuilder metadata)
|
|
{
|
|
byte length = ReadByte();
|
|
buffer.WriteByte(length);
|
|
Advance(2);
|
|
|
|
buffer.WriteUInt16(0);
|
|
|
|
int count = length / 12;
|
|
|
|
PatchRawExceptionHandlers(buffer, metadata, count, false);
|
|
}
|
|
|
|
private void PatchRawFatSection(ByteBuffer buffer, MetadataBuilder metadata)
|
|
{
|
|
Advance(-1);
|
|
int length = ReadInt32();
|
|
buffer.WriteInt32(length);
|
|
|
|
int count = (length >> 8) / 24;
|
|
|
|
PatchRawExceptionHandlers(buffer, metadata, count, true);
|
|
}
|
|
|
|
private void PatchRawExceptionHandlers(ByteBuffer buffer, MetadataBuilder metadata, int count, bool fat_entry)
|
|
{
|
|
const int fat_entry_size = 16;
|
|
const int small_entry_size = 6;
|
|
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
ExceptionHandlerType handler_type;
|
|
if (fat_entry)
|
|
{
|
|
uint type = ReadUInt32();
|
|
handler_type = (ExceptionHandlerType)(type & 0x7);
|
|
buffer.WriteUInt32(type);
|
|
}
|
|
else
|
|
{
|
|
ushort type = ReadUInt16();
|
|
handler_type = (ExceptionHandlerType)(type & 0x7);
|
|
buffer.WriteUInt16(type);
|
|
}
|
|
|
|
buffer.WriteBytes(ReadBytes(fat_entry ? fat_entry_size : small_entry_size));
|
|
|
|
switch (handler_type)
|
|
{
|
|
case ExceptionHandlerType.Catch:
|
|
IMetadataTokenProvider exception = reader.LookupToken(ReadToken());
|
|
buffer.WriteUInt32(metadata.LookupToken(exception).ToUInt32());
|
|
break;
|
|
default:
|
|
buffer.WriteUInt32(ReadUInt32());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |