using Humanizer; using Microsoft.Iris.Asm.Models; using Microsoft.Iris.Markup; using System; using System.Collections.Generic; using System.Linq; namespace Microsoft.Iris.Asm; public class Disassembler { private readonly MarkupLoadResult _loadResult; private readonly Dictionary _importedUris; private readonly Dictionary> _offsetLabelMap = new(); private Disassembler(MarkupLoadResult loadResult) { _loadResult = loadResult; _importedUris = new() { [_loadResult.Uri] = "me", ["http://schemas.microsoft.com/2007/uix"] = null }; } public static Disassembler Load(MarkupLoadResult loadResult) => new(loadResult); public IEnumerable 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."); var baseName = markupTypeSchema.MarkupType.ToString(); yield return new ExportDirective(labelPrefix, markupTypeSchema.ListenerCount, baseName); var propOffset = markupTypeSchema.InitializePropertiesOffset; if (propOffset != uint.MaxValue) InsertLabel(propOffset, ExportDirective.GetInitializePropertiesLabel(labelPrefix)); var loclOffset = markupTypeSchema.InitializeLocalsInputOffset; if (loclOffset != uint.MaxValue) InsertLabel(loclOffset, ExportDirective.GetInitializeLocalsInputLabel(labelPrefix)); var contOffset = markupTypeSchema.InitializeContentOffset; if (contOffset != uint.MaxValue) InsertLabel(contOffset, ExportDirective.GetInitializeContentLabel(labelPrefix)); if (markupTypeSchema.InitialEvaluateOffsets != null) { for (int i = 0; i < markupTypeSchema.InitialEvaluateOffsets.Length; i++) { uint offset = markupTypeSchema.InitialEvaluateOffsets[i]; var labelName = $"{ExportDirective.GetInitializeContentLabel(labelPrefix)}{i:N}"; 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:N}"; 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:N}"; InsertLabel(offset, labelName); } } } } public IEnumerable GetImports() { // Ues _importedUris to keep track of what has already been imported. // Skip self and default UIX namespace. foreach (var typeImport in _loadResult.ImportTables.TypeImports) { var uri = typeImport.Owner.Uri; if (!_importedUris.TryGetValue(uri, out var namespacePrefix)) { namespacePrefix = uri; var schemeLength = uri.IndexOf("://"); if (schemeLength > 0) { 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}"; } else { // Assume the URI represents a file, // skip the extension namespacePrefix = uri.Split('.', '/', '\\', '!')[^2]; } } namespacePrefix = namespacePrefix.Camelize(); _importedUris.Add(uri, namespacePrefix); yield return new NamespaceImport(uri, namespacePrefix); } yield return new TypeImport(namespacePrefix, typeImport.Name); }; } public IEnumerable GetBody() { var reader = _loadResult.ObjectSection; // Insert a label to mark the start of the object section. yield return new SectionDirective("object"); while (reader.CurrentOffset < reader.Size) { if (_offsetLabelMap.TryGetValue(reader.CurrentOffset, out var labels)) foreach (var label in labels) yield return label; var opCode = (OpCode)reader.ReadByte(); switch (opCode) { // CMD (No operands) case OpCode.InitializeInstanceIndirect: // INITI case OpCode.PushNull: // PSHN case OpCode.PushThis: // PSHT case OpCode.DiscardValue: // DIS case OpCode.ReturnValue: // RET case OpCode.ReturnVoid: // RETV yield return Instruction.CreateWithSchema(opCode); break; // CMD case OpCode.ConstructObject: // COBJ case OpCode.ConstructObjectIndirect: // COBJI case OpCode.InitializeInstance: // INIT case OpCode.LookupSymbol: // LSYM case OpCode.WriteSymbol: // WSYM case OpCode.WriteSymbolPeek: // WSYMP case OpCode.ClearSymbol: // CSYM case OpCode.PropertyInitialize: // PINI case OpCode.PropertyInitializeIndirect: // PINII case OpCode.PropertyListAdd: // PLAD case OpCode.PropertyAssign: // PASS case OpCode.PropertyAssignStatic: // PASST case OpCode.PropertyGet: // PGET case OpCode.PropertyGetPeek: // PGETP case OpCode.PropertyGetStatic: // PGETT case OpCode.MethodInvoke: // MINV case OpCode.MethodInvokePeek: // MINVP case OpCode.MethodInvokeStatic: // MINVT case OpCode.MethodInvokePushLastParam: // MINVA case OpCode.MethodInvokeStaticPushLastParam: // MINVAT case OpCode.VerifyTypeCast: // VTC case OpCode.IsCheck: // ISC case OpCode.As: // ASC case OpCode.TypeOf: // TYP case OpCode.PushConstant: // PSHC case OpCode.ConstructListenerStorage: // CLIS yield return Instruction.CreateWithSchema(opCode, reader.ReadUInt16()); break; // CMD case OpCode.JumpIfFalse: // JMPF case OpCode.JumpIfFalsePeek: // JMPFP case OpCode.JumpIfTruePeek: // JMPT case OpCode.JumpIfNullPeek: // JMPNP case OpCode.Jump: // JMP yield return Instruction.CreateWithSchema(opCode, reader.ReadUInt32()); break; // CMD case OpCode.EnterDebugState: // DBG yield return Instruction.CreateWithSchema(opCode, reader.ReadInt32()); break; // CMD case OpCode.ConstructObjectParam: // COBP case OpCode.ConstructFromString: // CSTR case OpCode.PropertyDictionaryAdd: // PDAD case OpCode.ConvertType: // CON yield return Instruction.CreateWithSchema(opCode, reader.ReadUInt16(), reader.ReadUInt16()); break; case OpCode.JumpIfDictionaryContains: // JMPD yield return Instruction.CreateWithSchema(opCode, reader.ReadUInt16(), reader.ReadUInt16(), reader.ReadUInt32()); break; case OpCode.ConstructFromBinary: // CBIN var cbinTypeIndex = reader.ReadUInt16(); TypeSchema cbinTypeSchema = _loadResult.ImportTables.TypeImports[cbinTypeIndex]; object cbinObject = cbinTypeSchema.DecodeBinary(reader); yield return Instruction.CreateWithSchema(opCode, cbinTypeIndex, cbinObject); break; case OpCode.Operation: // OPR var opHostIndex = reader.ReadUInt16(); var op = (OperationType)reader.ReadByte(); yield return Instruction.CreateWithSchema(opCode, opHostIndex, op); break; case OpCode.Listen: // LIS case OpCode.DestructiveListen: // LISD var listenerIndex = reader.ReadUInt16(); var listenerType = reader.ReadByte(); var watchIndex = reader.ReadUInt16(); var handlerOffset = reader.ReadUInt32(); if (opCode == OpCode.DestructiveListen) yield return Instruction.CreateWithSchema(opCode, listenerIndex, listenerType, watchIndex, handlerOffset, reader.ReadUInt32()); else yield return Instruction.CreateWithSchema(opCode, listenerIndex, listenerType, watchIndex, handlerOffset); break; default: throw new NotImplementedException($"The {opCode} instruction has not been implemented yet."); } } yield break; } public string Write() { _loadResult.Load(LoadPass.DeclareTypes); _loadResult.Load(LoadPass.PopulatePublicModel); _loadResult.Load(LoadPass.Full); _loadResult.Load(LoadPass.Done); List directives = GetImports().Cast() .Concat(GetExports()) .ToList(); List body = new(GetBody()); Program asmProgram = new(directives, body); return asmProgram.ToString(); } private string GetQualifiedName(TypeSchema schema) { _importedUris.TryGetValue(schema.Owner.Uri, out string prefix); return prefix != null ? $"{prefix}:{schema.Name}" : schema.Name; } private void InsertLabel(uint offset, string labelName) { if (!_offsetLabelMap.TryGetValue(offset, out var labels)) labels = _offsetLabelMap[offset] = new(1); labels.Add(new(labelName)); } }