664 lines
17 KiB
C#
664 lines
17 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 {
|
|
|
|
sealed class CodeReader : BinaryStreamReader {
|
|
|
|
readonly internal MetadataReader reader;
|
|
|
|
int start;
|
|
|
|
MethodDefinition method;
|
|
MethodBody body;
|
|
|
|
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;
|
|
this.reader.context = method;
|
|
var position = this.Position;
|
|
this.Position = (int)reader.image.ResolveVirtualAddress ((uint)method.RVA);
|
|
return position;
|
|
}
|
|
|
|
public void MoveBackTo (int position)
|
|
{
|
|
this.reader.context = null;
|
|
this.Position = position;
|
|
}
|
|
|
|
public MethodBody ReadMethodBody (MethodDefinition method)
|
|
{
|
|
var position = MoveTo (method);
|
|
this.body = new MethodBody (method);
|
|
|
|
ReadMethodBody ();
|
|
|
|
MoveBackTo (position);
|
|
return this.body;
|
|
}
|
|
|
|
public int ReadCodeSize (MethodDefinition method)
|
|
{
|
|
var position = MoveTo (method);
|
|
|
|
var code_size = ReadCodeSize ();
|
|
|
|
MoveBackTo (position);
|
|
return code_size;
|
|
}
|
|
|
|
int ReadCodeSize ()
|
|
{
|
|
var 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 ();
|
|
}
|
|
}
|
|
|
|
void ReadMethodBody ()
|
|
{
|
|
var 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 ();
|
|
}
|
|
|
|
var 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 ();
|
|
}
|
|
|
|
void ReadFatMethod ()
|
|
{
|
|
var flags = ReadUInt16 ();
|
|
body.max_stack_size = ReadUInt16 ();
|
|
body.code_size = (int)ReadUInt32 ();
|
|
body.local_var_token = new MetadataToken (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)
|
|
{
|
|
var position = reader.position;
|
|
var variables = reader.ReadVariables (local_var_token, method);
|
|
reader.position = position;
|
|
|
|
return variables;
|
|
}
|
|
|
|
void ReadCode ()
|
|
{
|
|
start = Position;
|
|
var code_size = body.code_size;
|
|
|
|
if (code_size < 0 || Length <= (uint)(code_size + Position))
|
|
code_size = 0;
|
|
|
|
var end = start + code_size;
|
|
var instructions = body.instructions = new InstructionCollection (method, (code_size + 1) / 2);
|
|
|
|
while (Position < end) {
|
|
var offset = Position - start;
|
|
var opcode = ReadOpCode ();
|
|
var current = new Instruction (offset, opcode);
|
|
|
|
if (opcode.OperandType != OperandType.InlineNone)
|
|
current.operand = ReadOperand (current);
|
|
|
|
instructions.Add (current);
|
|
}
|
|
|
|
ResolveBranches (instructions);
|
|
}
|
|
|
|
OpCode ReadOpCode ()
|
|
{
|
|
var il_opcode = ReadByte ();
|
|
return il_opcode != 0xfe
|
|
? OpCodes.OneByteOpCode [il_opcode]
|
|
: OpCodes.TwoBytesOpCode [ReadByte ()];
|
|
}
|
|
|
|
object ReadOperand (Instruction instruction)
|
|
{
|
|
switch (instruction.opcode.OperandType) {
|
|
case OperandType.InlineSwitch:
|
|
var length = ReadInt32 ();
|
|
var base_offset = Offset + (4 * length);
|
|
var 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);
|
|
}
|
|
|
|
void ResolveBranches (Collection<Instruction> instructions)
|
|
{
|
|
var items = instructions.items;
|
|
var size = instructions.size;
|
|
|
|
for (int i = 0; i < size; i++) {
|
|
var instruction = items [i];
|
|
switch (instruction.opcode.OperandType) {
|
|
case OperandType.ShortInlineBrTarget:
|
|
case OperandType.InlineBrTarget:
|
|
instruction.operand = GetInstruction ((int)instruction.operand);
|
|
break;
|
|
case OperandType.InlineSwitch:
|
|
var offsets = (int [])instruction.operand;
|
|
var branches = new Instruction [offsets.Length];
|
|
for (int j = 0; j < offsets.Length; j++)
|
|
branches [j] = GetInstruction (offsets [j]);
|
|
|
|
instruction.operand = branches;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
Instruction GetInstruction (int offset)
|
|
{
|
|
return GetInstruction (body.Instructions, offset);
|
|
}
|
|
|
|
static Instruction GetInstruction (Collection<Instruction> instructions, int offset)
|
|
{
|
|
var size = instructions.size;
|
|
var 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);
|
|
var instruction = items [mid];
|
|
var instruction_offset = instruction.offset;
|
|
|
|
if (offset == instruction_offset)
|
|
return instruction;
|
|
|
|
if (offset < instruction_offset)
|
|
max = mid - 1;
|
|
else
|
|
min = mid + 1;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
void ReadSection ()
|
|
{
|
|
Align (4);
|
|
|
|
const byte fat_format = 0x40;
|
|
const byte more_sects = 0x80;
|
|
|
|
var flags = ReadByte ();
|
|
if ((flags & fat_format) == 0)
|
|
ReadSmallSection ();
|
|
else
|
|
ReadFatSection ();
|
|
|
|
if ((flags & more_sects) != 0)
|
|
ReadSection ();
|
|
}
|
|
|
|
void ReadSmallSection ()
|
|
{
|
|
var count = ReadByte () / 12;
|
|
Advance (2);
|
|
|
|
ReadExceptionHandlers (
|
|
count,
|
|
() => (int)ReadUInt16 (),
|
|
() => (int)ReadByte ());
|
|
}
|
|
|
|
void ReadFatSection ()
|
|
{
|
|
Advance (-1);
|
|
var count = (ReadInt32 () >> 8) / 24;
|
|
|
|
ReadExceptionHandlers (
|
|
count,
|
|
ReadInt32,
|
|
ReadInt32);
|
|
}
|
|
|
|
// inline ?
|
|
void ReadExceptionHandlers (int count, Func<int> read_entry, Func<int> read_length)
|
|
{
|
|
for (int i = 0; i < count; i++) {
|
|
var handler = new ExceptionHandler (
|
|
(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);
|
|
|
|
this.body.ExceptionHandlers.Add (handler);
|
|
}
|
|
}
|
|
|
|
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 MetadataToken (ReadUInt32 ());
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
void ReadCustomDebugInformations (MethodDefinition method)
|
|
{
|
|
var custom_infos = method.custom_infos;
|
|
|
|
for (int i = 0; i < custom_infos.Count; i++) {
|
|
var state_machine_scope = custom_infos [i] as StateMachineScopeDebugInformation;
|
|
if (state_machine_scope != null)
|
|
ReadStateMachineScope (state_machine_scope);
|
|
|
|
var async_method = custom_infos [i] as AsyncMethodBodyDebugInformation;
|
|
if (async_method != null)
|
|
ReadAsyncMethodBody (async_method);
|
|
}
|
|
}
|
|
|
|
void ReadAsyncMethodBody (AsyncMethodBodyDebugInformation async_method)
|
|
{
|
|
if (async_method.catch_handler.Offset > -1)
|
|
async_method.catch_handler = new InstructionOffset (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 InstructionOffset (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 InstructionOffset (GetInstruction (async_method.resumes [i].Offset));
|
|
}
|
|
|
|
void ReadStateMachineScope (StateMachineScopeDebugInformation state_machine_scope)
|
|
{
|
|
if (state_machine_scope.scopes.IsNullOrEmpty ())
|
|
return;
|
|
|
|
foreach (var scope in state_machine_scope.scopes) {
|
|
scope.start = new InstructionOffset (GetInstruction (scope.start.Offset));
|
|
|
|
var end_instruction = GetInstruction (scope.end.Offset);
|
|
scope.end = end_instruction == null
|
|
? new InstructionOffset ()
|
|
: new InstructionOffset (end_instruction);
|
|
}
|
|
}
|
|
|
|
void ReadSequencePoints ()
|
|
{
|
|
var symbol = method.debug_info;
|
|
|
|
for (int i = 0; i < symbol.sequence_points.Count; i++) {
|
|
var sequence_point = symbol.sequence_points [i];
|
|
var instruction = GetInstruction (sequence_point.Offset);
|
|
if (instruction != null)
|
|
sequence_point.offset = new InstructionOffset (instruction);
|
|
}
|
|
}
|
|
|
|
void ReadScopes (Collection<ScopeDebugInformation> scopes)
|
|
{
|
|
for (int i = 0; i < scopes.Count; i++)
|
|
ReadScope (scopes [i]);
|
|
}
|
|
|
|
void ReadScope (ScopeDebugInformation scope)
|
|
{
|
|
var start_instruction = GetInstruction (scope.Start.Offset);
|
|
if (start_instruction != null)
|
|
scope.Start = new InstructionOffset (start_instruction);
|
|
|
|
var end_instruction = GetInstruction (scope.End.Offset);
|
|
scope.End = end_instruction != null
|
|
? new InstructionOffset (end_instruction)
|
|
: new InstructionOffset ();
|
|
|
|
if (!scope.variables.IsNullOrEmpty ()) {
|
|
for (int i = 0; i < scope.variables.Count; i++) {
|
|
var variable_info = scope.variables [i];
|
|
var variable = GetVariable (variable_info.Index);
|
|
if (variable != null)
|
|
variable_info.index = new VariableIndex (variable);
|
|
}
|
|
}
|
|
|
|
if (!scope.scopes.IsNullOrEmpty ())
|
|
ReadScopes (scope.scopes);
|
|
}
|
|
|
|
public ByteBuffer PatchRawMethodBody (MethodDefinition method, CodeWriter writer, out int code_size, out MetadataToken local_var_token)
|
|
{
|
|
var position = MoveTo (method);
|
|
|
|
var buffer = new ByteBuffer ();
|
|
|
|
var 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;
|
|
}
|
|
|
|
void PatchRawFatMethod (ByteBuffer buffer, CodeWriter writer, out int code_size, out MetadataToken local_var_token)
|
|
{
|
|
var 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) {
|
|
var 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);
|
|
}
|
|
|
|
void PatchRawCode (ByteBuffer buffer, int code_size, CodeWriter writer)
|
|
{
|
|
var metadata = writer.metadata;
|
|
buffer.WriteBytes (ReadBytes (code_size));
|
|
var end = buffer.position;
|
|
buffer.position -= code_size;
|
|
|
|
while (buffer.position < end) {
|
|
OpCode opcode;
|
|
var il_opcode = buffer.ReadByte ();
|
|
if (il_opcode != 0xfe) {
|
|
opcode = OpCodes.OneByteOpCode [il_opcode];
|
|
} else {
|
|
var 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:
|
|
var length = buffer.ReadInt32 ();
|
|
buffer.position += length * 4;
|
|
break;
|
|
case OperandType.InlineString:
|
|
var @string = GetString (new MetadataToken (buffer.ReadUInt32 ()));
|
|
buffer.position -= 4;
|
|
buffer.WriteUInt32 (
|
|
new MetadataToken (
|
|
TokenType.String,
|
|
metadata.user_string_heap.GetStringIndex (@string)).ToUInt32 ());
|
|
break;
|
|
case OperandType.InlineSig:
|
|
var call_site = GetCallSite (new MetadataToken (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:
|
|
var provider = reader.LookupToken (new MetadataToken (buffer.ReadUInt32 ()));
|
|
buffer.position -= 4;
|
|
buffer.WriteUInt32 (metadata.LookupToken (provider).ToUInt32 ());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void PatchRawSection (ByteBuffer buffer, MetadataBuilder metadata)
|
|
{
|
|
var position = Position;
|
|
Align (4);
|
|
buffer.WriteBytes (Position - position);
|
|
|
|
const byte fat_format = 0x40;
|
|
const byte more_sects = 0x80;
|
|
|
|
var 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);
|
|
}
|
|
|
|
void PatchRawSmallSection (ByteBuffer buffer, MetadataBuilder metadata)
|
|
{
|
|
var length = ReadByte ();
|
|
buffer.WriteByte (length);
|
|
Advance (2);
|
|
|
|
buffer.WriteUInt16 (0);
|
|
|
|
var count = length / 12;
|
|
|
|
PatchRawExceptionHandlers (buffer, metadata, count, false);
|
|
}
|
|
|
|
void PatchRawFatSection (ByteBuffer buffer, MetadataBuilder metadata)
|
|
{
|
|
Advance (-1);
|
|
var length = ReadInt32 ();
|
|
buffer.WriteInt32 (length);
|
|
|
|
var count = (length >> 8) / 24;
|
|
|
|
PatchRawExceptionHandlers (buffer, metadata, count, true);
|
|
}
|
|
|
|
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) {
|
|
var type = ReadUInt32 ();
|
|
handler_type = (ExceptionHandlerType)(type & 0x7);
|
|
buffer.WriteUInt32 (type);
|
|
} else {
|
|
var 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:
|
|
var exception = reader.LookupToken (ReadToken ());
|
|
buffer.WriteUInt32 (metadata.LookupToken (exception).ToUInt32 ());
|
|
break;
|
|
default:
|
|
buffer.WriteUInt32 (ReadUInt32 ());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|