using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
namespace Keystone
{
///
/// Represents a Keystone engine.
///
public sealed class Engine : IDisposable
{
private IntPtr engine = IntPtr.Zero;
private bool addedResolveSymbol;
private readonly ResolverInternal internalImpl;
private readonly List resolvers = new List();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate bool ResolverInternal(IntPtr symbol, ref ulong value);
///
/// Gets or sets a value that represents whether a
/// should be thrown on error.
///
public bool ThrowOnError { get; set; }
///
/// Delegate for defining symbol resolvers.
///
/// Symbol to resolve.
/// Address of taid symbol, if found.
/// Whether the symbol was recognized.
public delegate bool Resolver(string symbol, ref ulong value);
///
/// Event raised when keystone is resolving a symbol.
///
/// This event is only available on Keystone 0.9.2 or higher.
public event Resolver ResolveSymbol
{
add
{
if (!addedResolveSymbol)
{
KeystoneError err = NativeInterop.SetOption(engine, (int)OptionType.SYM_RESOLVER, Marshal.GetFunctionPointerForDelegate(internalImpl));
if (err == KeystoneError.KS_ERR_OK)
addedResolveSymbol = true;
else
throw new KeystoneException("Could not add symbol resolver", err);
}
resolvers.Add(value);
}
remove
{
if (addedResolveSymbol && resolvers.Count == 0)
{
KeystoneError err = NativeInterop.SetOption(engine, (int)OptionType.SYM_RESOLVER, IntPtr.Zero);
if (err == KeystoneError.KS_ERR_OK)
addedResolveSymbol = false;
else
throw new KeystoneException("Could not remove symbol resolver", err);
}
resolvers.Remove(value);
}
}
///
/// Method used for symbol resolving.
///
/// Pointer to the name of the symbol.
/// Address of the symbol, if found.
/// Whether the symbol could be recognized.
private bool ResolveSymbolInternal(IntPtr symbolPtr, ref ulong value)
{
string symbol = Marshal.PtrToStringAnsi(symbolPtr);
foreach (Resolver item in resolvers)
{
bool result = item(symbol, ref value);
if (result)
return true;
}
return false;
}
///
/// Constructs the engine with a given architecture and a given mode.
///
/// The target architecture.
/// The mode, i.e. endianness, word size etc.
///
/// Some architectures are not supported.
/// Check with if the engine
/// supports the target architecture.
///
public Engine(Architecture architecture, Mode mode)
{
internalImpl = ResolveSymbolInternal;
var result = NativeInterop.Open(architecture, (int)mode, ref engine);
if (result != KeystoneError.KS_ERR_OK)
throw new KeystoneException("Error while initializing keystone", result);
}
///
/// Sets an option in the engine.
///
/// Type of the option.
/// Value it the option.
/// Whether the option was correctly set.
/// An error encountered when setting the option.
public bool SetOption(OptionType type, uint value)
{
var result = NativeInterop.SetOption(engine, (int)type, (IntPtr)value);
if (result != KeystoneError.KS_ERR_OK)
{
if (ThrowOnError)
throw new KeystoneException("Error while setting option", result);
return false;
}
return true;
}
///
/// Encodes the given statement(s).
///
/// String that contains the statements to encode.
/// Address of the first instruction to encode.
/// Size of the buffer produced by the operation.
/// Number of statements found and encoded.
/// Result of the operation, or null if it failed and is false.
/// A null argument was given.
/// An error encountered when encoding the instructions.
public byte[] Assemble(string toEncode, ulong address, out int size, out int statementCount)
{
if (toEncode == null)
throw new ArgumentNullException(nameof(toEncode));
int result = NativeInterop.Assemble(engine,
toEncode,
address,
out IntPtr encoding,
out uint size_,
out uint statementCount_);
if (result != 0)
{
if (ThrowOnError)
throw new KeystoneException("Error while assembling instructions", GetLastKeystoneError());
size = statementCount = 0;
return null;
}
size = (int)size_;
statementCount = (int)statementCount_;
byte[] buffer = new byte[size];
Marshal.Copy(encoding, buffer, 0, size);
NativeInterop.Free(encoding);
return buffer;
}
///
/// Encodes the given statement(s).
///
/// String that contains the statements to encode.
/// Address of the first instruction to encode.
/// Result of the operation, or null if it failed and is false.
/// A null argument was given.
/// An error encountered when encoding the instructions.
public EncodedData Assemble(string toEncode, ulong address)
{
byte[] buffer = Assemble(toEncode, address, out int size, out int statementCount);
if (buffer == null)
return null;
return new EncodedData(buffer, statementCount, address);
}
///
/// Encodes the given statement(s) into the given buffer.
///
/// String that contains the statements to encode.
/// Address of the first instruction to encode.
/// Buffer into which the data shall be written.
/// Index into the buffer after which the data shall be written.
/// Number of statements found and encoded.
/// Size of the data writen by the operation., or 0 if it failed and is false.
/// A null argument was given.
/// The provided index is invalid.
/// An error encountered when encoding the instructions.
public int Assemble(string toEncode, ulong address, byte[] buffer, int index, out int statementCount)
{
if (toEncode == null)
throw new ArgumentNullException(nameof(toEncode));
if (buffer == null)
throw new ArgumentNullException(nameof(buffer));
if (index < 0 || index >= buffer.Length)
throw new ArgumentOutOfRangeException(nameof(buffer));
int result = NativeInterop.Assemble(engine,
toEncode,
address,
out IntPtr encoding,
out uint size_,
out uint statementCount_);
int size = (int)size_;
statementCount = (int)statementCount_;
if (result != 0)
{
if (ThrowOnError)
throw new KeystoneException("Error while assembling instructions", GetLastKeystoneError());
return 0;
}
Marshal.Copy(encoding, buffer, index, size);
NativeInterop.Free(encoding);
return size;
}
///
/// Encodes the given statement(s) into the given buffer.
///
/// String that contains the statements to encode.
/// Address of the first instruction to encode.
/// Buffer into which the data shall be written.
/// Index into the buffer after which the data shall be written.
/// Size of the data writen by the operation., or 0 if it failed and is false.
/// A null argument was given.
/// The provided index is invalid.
/// An error encountered when encoding the instructions.
public int Assemble(string toEncode, ulong address, byte[] buffer, int index)
{
return Assemble(toEncode, address, buffer, index, out _);
}
///
/// Encodes the given statement(s) into the given stream.
///
/// String that contains the statements to encode.
/// Address of the first instruction to encode.
/// Buffer into which the data shall be written.
/// Size of the buffer produced by the operation.
/// Number of statements found and encoded.
/// true on success, or false if it failed and is false.
/// A null argument was given.
/// An error encountered when encoding the instructions.
public bool Assemble(string toEncode, ulong address, Stream stream, out int size, out int statementCount)
{
if (stream == null)
throw new ArgumentNullException(nameof(stream));
byte[] enc = Assemble(toEncode, address, out size, out statementCount);
if (enc == null)
return false;
stream.Write(enc, 0, size);
return true;
}
///
/// Encodes the given statement(s) into the given stream.
///
/// String that contains the statements to encode.
/// Address of the first instruction to encode.
/// Buffer into which the data shall be written.
/// Size of the buffer produced by the operation.
/// true on success, or false if it failed and is false.
/// A null argument was given.
/// An error encountered when encoding the instructions.
public bool Assemble(string toEncode, ulong address, Stream stream, out int size)
{
return Assemble(toEncode, address, stream, out size, out _);
}
///
/// Encodes the given statement(s) into the given stream.
///
/// String that contains the statements to encode.
/// Address of the first instruction to encode.
/// Buffer into which the data shall be written.
/// true on success, or false if it failed and is false.
/// A null argument was given.
/// An error encountered when encoding the instructions.
public bool Assemble(string toEncode, ulong address, Stream stream)
{
return Assemble(toEncode, address, stream, out _, out _);
}
///
/// Gets the last error for this instance.
///
/// The last error code.
///
/// It might not retain its old error once accessed.
///
public KeystoneError GetLastKeystoneError()
{
return NativeInterop.GetLastKeystoneError(engine);
}
///
/// Returns the string associated with a given error code.
///
public static string ErrorToString(KeystoneError code)
{
IntPtr error = NativeInterop.ErrorToString(code);
if (error != IntPtr.Zero)
return Marshal.PtrToStringAnsi(error);
return string.Empty;
}
///
/// Checks if the given architecture is supported.
///
public static bool IsArchitectureSupported(Architecture architecture)
{
return NativeInterop.IsArchitectureSupported(architecture);
}
///
/// Gets the version of the engine.
///
/// Major version number.
/// Minor version number.
/// Unique identifier for this version.
public static uint GetKeystoneVersion(ref uint major, ref uint minor)
{
return NativeInterop.Version(ref major, ref minor);
}
///
/// Releases the engine.
///
public void Dispose()
{
IntPtr currentEngine = Interlocked.Exchange(ref engine, IntPtr.Zero);
if (currentEngine != IntPtr.Zero)
NativeInterop.Close(currentEngine);
}
}
}