Files
ZuneUIXTools/libs/UIX.Asm/Disassembler.cs
T

266 lines
9.6 KiB
C#
Raw Normal View History

2024-01-30 23:30:42 -06:00
using Humanizer;
2024-01-31 09:21:30 -06:00
using Microsoft.Iris.Asm.Models;
2024-01-30 23:30:42 -06:00
using Microsoft.Iris.Markup;
using System;
using System.Collections.Generic;
2024-02-08 13:32:44 -06:00
using System.Linq;
2024-01-30 23:30:42 -06:00
namespace Microsoft.Iris.Asm;
public class Disassembler
{
private readonly MarkupLoadResult _loadResult;
2024-02-08 13:32:44 -06:00
private readonly Dictionary<string, string> _importedUris;
private readonly Dictionary<uint, List<Label>> _offsetLabelMap = new();
2024-01-30 23:30:42 -06:00
private Disassembler(MarkupLoadResult loadResult)
{
_loadResult = loadResult;
2024-02-08 13:32:44 -06:00
_importedUris = new()
{
[_loadResult.Uri] = "me",
["http://schemas.microsoft.com/2007/uix"] = null
};
2024-01-30 23:30:42 -06:00
}
public static Disassembler Load(MarkupLoadResult loadResult) => new(loadResult);
2024-02-08 13:32:44 -06:00
public IEnumerable<Directive> GetExports()
{
foreach (var typeSchema in _loadResult.ExportTable)
{
var labelPrefix = typeSchema.Name;
if (typeSchema is not MarkupTypeSchema markupTypeSchema)
throw new Exception($"Disassembler failed to disassemble export {typeSchema}, '{typeSchema.GetType()}' is not supported.");
2024-02-09 09:18:50 -06:00
var baseName = markupTypeSchema.MarkupType.ToString();
yield return new ExportDirective(labelPrefix, markupTypeSchema.ListenerCount, baseName);
2024-02-08 13:32:44 -06:00
var propOffset = markupTypeSchema.InitializePropertiesOffset;
if (propOffset != uint.MaxValue)
InsertLabel(propOffset, ExportDirective.GetInitializePropertiesLabel(labelPrefix));
2024-02-08 13:32:44 -06:00
var loclOffset = markupTypeSchema.InitializeLocalsInputOffset;
if (loclOffset != uint.MaxValue)
InsertLabel(loclOffset, ExportDirective.GetInitializeLocalsInputLabel(labelPrefix));
2024-02-08 13:32:44 -06:00
var contOffset = markupTypeSchema.InitializeContentOffset;
if (contOffset != uint.MaxValue)
InsertLabel(contOffset, ExportDirective.GetInitializeContentLabel(labelPrefix));
2024-02-08 13:32:44 -06:00
if (markupTypeSchema.InitialEvaluateOffsets != null)
{
for (int i = 0; i < markupTypeSchema.InitialEvaluateOffsets.Length; i++)
{
uint offset = markupTypeSchema.InitialEvaluateOffsets[i];
var labelName = $"{ExportDirective.GetInitializeContentLabel(labelPrefix)}{i:D}";
2024-02-08 13:32:44 -06:00
InsertLabel(offset, labelName);
}
}
if (markupTypeSchema.FinalEvaluateOffsets != null)
{
for (int i = 0; i < markupTypeSchema.FinalEvaluateOffsets.Length; i++)
{
uint offset = markupTypeSchema.FinalEvaluateOffsets[i];
var labelName = $"{ExportDirective.GetFinalEvaluateOffsetsLabelPrefix(labelPrefix)}{i:D}";
2024-02-08 13:32:44 -06:00
InsertLabel(offset, labelName);
}
}
if (markupTypeSchema.RefreshGroupOffsets != null)
{
for (int i = 0; i < markupTypeSchema.RefreshGroupOffsets.Length; i++)
{
uint offset = markupTypeSchema.RefreshGroupOffsets[i];
var labelName = $"{ExportDirective.GetRefreshGroupOffsetsLabelPrefix(labelPrefix)}{i:D}";
2024-02-08 13:32:44 -06:00
InsertLabel(offset, labelName);
}
}
}
}
2024-02-10 23:03:57 -06:00
public IEnumerable<IImportDirective> GetImports()
2024-01-30 23:30:42 -06:00
{
2024-02-08 13:32:44 -06:00
// Ues _importedUris to keep track of what has already been imported.
// Skip self and default UIX namespace.
2024-02-07 09:42:18 -06:00
2024-01-30 23:30:42 -06:00
foreach (var typeImport in _loadResult.ImportTables.TypeImports)
{
var uri = typeImport.Owner.Uri;
2024-02-08 13:32:44 -06:00
if (!_importedUris.TryGetValue(uri, out var namespacePrefix))
2024-02-08 10:52:01 -06:00
{
namespacePrefix = uri;
2024-02-08 13:32:44 -06:00
var schemeLength = uri.IndexOf("://");
if (schemeLength > 0)
2024-01-30 23:30:42 -06:00
{
2024-02-08 13:32:44 -06:00
var scheme = uri[..schemeLength];
if (scheme == "assembly")
{
// Assume 'host' is an assembly name and path represents a C# namespace
var assemblyUriParts = uri.Split('/');
var importedNamespace = assemblyUriParts[^1];
namespacePrefix = importedNamespace.Split('.', '/', '\\', '!')[^1];
System.Reflection.AssemblyName assemblyName = new(assemblyUriParts[^2]);
uri = $"{scheme}://{assemblyName.Name}/{importedNamespace}";
2024-02-08 13:32:44 -06:00
}
else
{
// Assume the URI represents a file,
// skip the extension
2024-02-08 10:52:01 -06:00
namespacePrefix = uri.Split('.', '/', '\\', '!')[^2];
}
2024-01-30 23:30:42 -06:00
}
2024-02-08 10:52:01 -06:00
2024-02-10 23:03:57 -06:00
// Some imports, such as assembly imports, require additional parsing
// and might change the URI that actually gets imported.
if (_importedUris.ContainsKey(uri))
continue;
2024-02-08 10:52:01 -06:00
namespacePrefix = namespacePrefix.Camelize();
2024-02-08 13:32:44 -06:00
_importedUris.Add(uri, namespacePrefix);
2024-02-08 10:52:01 -06:00
yield return new NamespaceImport(uri, namespacePrefix);
2024-01-30 23:30:42 -06:00
}
2024-02-11 20:39:07 -06:00
yield return new TypeImport(new(namespacePrefix, typeImport.Name));
2024-01-30 23:30:42 -06:00
};
}
2024-02-11 20:39:07 -06:00
public IEnumerable<ConstantDirective> GetConstants()
{
var constantsTable = _loadResult.ConstantsTable;
bool canUsePersistList = constantsTable.PersistList is not null;
if (canUsePersistList)
{
var constants = _loadResult.ConstantsTable.PersistList;
for (int c = 0; c < constants.Length; c++)
{
var persistedConstant = constants[c];
var typeSchema = persistedConstant.Type;
var qualifiedTypeName = GetQualifiedName(typeSchema);
yield return new ConstantDirective($"const{c:D}", qualifiedTypeName, persistedConstant.Data.ToString());
}
}
else
{
// UIB doesn't persist constants, so we have to use an alternate, slower method
for (int c = 0; ; c++)
{
object constantValue;
try
{
constantValue = constantsTable.Get(c);
}
catch
{
break;
}
var runtimeType = constantValue.GetType();
var typeSchema = _loadResult.ImportTables.TypeImports.FirstOrDefault(t => t.RuntimeType == runtimeType);
QualifiedTypeName qualifiedTypeName = GetQualifiedName(typeSchema);
yield return new ConstantDirective($"const{c:D}", qualifiedTypeName, constantValue.ToString());
}
}
}
2024-02-10 23:03:57 -06:00
public IEnumerable<IBodyItem> GetCode()
2024-01-30 23:30:42 -06:00
{
var reader = _loadResult.ObjectSection;
2024-01-31 19:44:23 -06:00
// Insert a label to mark the start of the object section.
yield return new SectionDirective("object");
2024-01-31 19:44:23 -06:00
2024-01-30 23:30:42 -06:00
while (reader.CurrentOffset < reader.Size)
{
2024-02-08 13:32:44 -06:00
if (_offsetLabelMap.TryGetValue(reader.CurrentOffset, out var labels))
foreach (var label in labels)
yield return label;
2024-01-30 23:30:42 -06:00
var opCode = (OpCode)reader.ReadByte();
2024-02-11 20:39:07 -06:00
var instSchema = InstructionSet.InstructionSchema[opCode];
Operand[] operands = new Operand[instSchema.Length];
for (int i = 0; i < instSchema.Length; i++)
2024-01-30 23:30:42 -06:00
{
2024-02-11 20:39:07 -06:00
var operandDataType = instSchema[i];
Operand operand;
2024-01-31 18:34:45 -06:00
2024-02-11 20:39:07 -06:00
if (operandDataType == LiteralDataType.ConstantIndex)
{
// Refer to the constant by name rather than index
var constantIndex = reader.ReadUInt16();
2024-01-30 23:30:42 -06:00
2024-02-11 20:39:07 -06:00
operand = new OperandReference($"const{constantIndex}");
}
else
{
object literalValue = OperandLiteral.ReduceDataType(operandDataType) switch
{
LiteralDataType.Byte => reader.ReadByte(),
LiteralDataType.UInt16 => reader.ReadUInt16(),
LiteralDataType.UInt32 => reader.ReadUInt32(),
LiteralDataType.Int32 => reader.ReadInt32(),
_ => throw new InvalidOperationException($"Unexpected operand data type '{operandDataType}'")
};
2024-01-30 23:30:42 -06:00
2024-02-11 20:39:07 -06:00
operand = new OperandLiteral(literalValue, operandDataType);
}
2024-01-31 18:34:45 -06:00
2024-02-11 20:39:07 -06:00
operands[i] = operand;
2024-01-30 23:30:42 -06:00
}
2024-02-11 20:39:07 -06:00
yield return new Instruction(opCode, operands);
2024-01-30 23:30:42 -06:00
}
yield break;
}
public string Write()
{
_loadResult.Load(LoadPass.DeclareTypes);
_loadResult.Load(LoadPass.PopulatePublicModel);
_loadResult.Load(LoadPass.Full);
_loadResult.Load(LoadPass.Done);
2024-02-11 20:39:07 -06:00
List<IEnumerable<IBodyItem>> segments = [
2024-02-10 23:03:57 -06:00
GetExports(),
GetImports(),
2024-02-11 20:39:07 -06:00
GetConstants(),
2024-02-10 23:03:57 -06:00
GetCode(),
];
2024-01-30 23:30:42 -06:00
2024-02-11 20:39:07 -06:00
List<IBodyItem> body = [];
foreach (var segment in segments)
body.AddRange(segment);
Program asmProgram = new(body);
2024-01-30 23:30:42 -06:00
return asmProgram.ToString();
}
2024-02-08 13:32:44 -06:00
2024-02-11 20:39:07 -06:00
private QualifiedTypeName GetQualifiedName(TypeSchema schema)
2024-02-08 13:32:44 -06:00
{
_importedUris.TryGetValue(schema.Owner.Uri, out string prefix);
2024-02-11 20:39:07 -06:00
return new(prefix, schema.Name);
2024-02-08 13:32:44 -06:00
}
private void InsertLabel(uint offset, string labelName)
{
if (!_offsetLabelMap.TryGetValue(offset, out var labels))
labels = _offsetLabelMap[offset] = new(1);
labels.Add(new(labelName));
}
2024-01-30 23:30:42 -06:00
}