From 9872df09b153d09b698220eeb75e28e1174e6621 Mon Sep 17 00:00:00 2001 From: TollyH Date: Sun, 12 May 2024 16:34:39 +0100 Subject: [PATCH] Add disassembler detection for floating and signed operands --- Debugger.cs | 7 ++- Disassembler.cs | 51 ++++++++++++++++--- Program.cs | 11 ++-- Resources/Localization/Strings.resx | 2 + Test/DisassemblerTests/FullPrograms.cs | 36 +++++++++++-- Test/DisassemblerTests/SpecificConditions.cs | 21 +++++--- Test/KitchenSink.Disassembled.FullBase.asm | 2 +- Test/KitchenSink.Disassembled.NoPads.asm | 2 +- Test/KitchenSink.Disassembled.NoStrings.asm | 2 +- Test/KitchenSink.Disassembled.asm | 4 +- Test/KitchenSink.asm | 2 +- Test/KitchenSink.bin | Bin 656 -> 656 bytes 12 files changed, 110 insertions(+), 30 deletions(-) diff --git a/Debugger.cs b/Debugger.cs index c98c16b..87e3487 100644 --- a/Debugger.cs +++ b/Debugger.cs @@ -35,6 +35,11 @@ public class Debugger private readonly Dictionary replLabels = new() { { "START", 0 } }; private ulong nextFreeRpoAddress; + private static readonly DisassemblerOptions disassemblerOptions = new() + { + AllowFullyQualifiedBaseOpcodes = true + }; + public Debugger(bool inReplMode, ulong entryPoint = 0, bool useV1CallStack = false, bool mapStack = true, bool autoEcho = false) { @@ -85,7 +90,7 @@ public void DisplayDebugInfo() string lineDisassembly = LoadedDebugInfoFile is null || !LoadedDebugInfoFile.Value.AssembledInstructions.TryGetValue(currentAddress, out string? inst) ? Disassembler.DisassembleInstruction( - DebuggingProcessor.Memory.AsSpan()[(int)currentAddress..], true, false).Line + DebuggingProcessor.Memory.AsSpan()[(int)currentAddress..], disassemblerOptions, false).Line : inst; Console.WriteLine(); diff --git a/Disassembler.cs b/Disassembler.cs index b4187b0..401dab4 100644 --- a/Disassembler.cs +++ b/Disassembler.cs @@ -1,15 +1,23 @@ using System.Buffers.Binary; +using System.Globalization; using System.Runtime.InteropServices; using System.Text; namespace AssEmbly { + public record DisassemblerOptions( + bool DetectStrings = true, + bool DetectPads = true, + bool DetectFloats = true, + bool DetectSigned = true, + bool AllowFullyQualifiedBaseOpcodes = false); + public static class Disassembler { /// /// Disassemble a program to AssEmbly code from it's assembled bytecode. /// - public static string DisassembleProgram(byte[] program, bool detectStrings, bool detectPads, bool allowFullyQualifiedBaseOpcodes) + public static string DisassembleProgram(byte[] program, DisassemblerOptions options) { ulong offset = 0; List result = new(); @@ -25,7 +33,7 @@ public static string DisassembleProgram(byte[] program, bool detectStrings, bool while (offset < (ulong)program.LongLength) { (string line, ulong additionalOffset, List referencedAddresses, bool datFallback) = - DisassembleInstruction(program.AsSpan()[(int)offset..], allowFullyQualifiedBaseOpcodes, true); + DisassembleInstruction(program.AsSpan()[(int)offset..], options, true); foreach (ulong address in referencedAddresses) { @@ -33,12 +41,13 @@ public static string DisassembleProgram(byte[] program, bool detectStrings, bool addressReferences[address].Add(offset); } - if (detectStrings && char.IsAscii((char)program[offset]) && !char.IsControl((char)program[offset]) && datFallback) + if (options.DetectStrings + && char.IsAscii((char)program[offset]) && !char.IsControl((char)program[offset]) && datFallback) { DumpPendingZeroPad(ref pendingZeroPad, result, offset, offsetToLine); pendingStringCharacters.Add(program[offset]); } - else if (detectPads && program[offset] == 0 && additionalOffset == 1) + else if (options.DetectPads && program[offset] == 0 && additionalOffset == 1) { DumpPendingString(pendingStringCharacters, result, offset, offsetToLine); pendingZeroPad++; @@ -89,7 +98,7 @@ public static string DisassembleProgram(byte[] program, bool detectStrings, bool /// /// (Disassembled line, Number of bytes instruction was, Referenced addresses [if present], Used %DAT directive) public static (string Line, ulong AdditionalOffset, List References, bool DatFallback) DisassembleInstruction( - Span instruction, bool allowFullyQualifiedBaseOpcodes, bool useLabelNames) + Span instruction, DisassemblerOptions options, bool useLabelNames) { if (instruction.Length == 0) { @@ -113,7 +122,8 @@ public static (string Line, ulong AdditionalOffset, List References, bool opcode = Opcode.ParseBytes(instruction, ref totalBytes); totalBytes++; } - if (!fallbackToDat && !allowFullyQualifiedBaseOpcodes && instruction[0] == Opcode.FullyQualifiedMarker && instruction[1] == 0x00) + if (!fallbackToDat && !options.AllowFullyQualifiedBaseOpcodes + && instruction[0] == Opcode.FullyQualifiedMarker && instruction[1] == 0x00) { // Opcode is fully qualified but is for the base instruction set // - technically valid but never done by the assembler, so interpret as data. @@ -151,7 +161,32 @@ public static (string Line, ulong AdditionalOffset, List References, bool fallbackToDat = true; break; } - operandStrings.Add(BinaryPrimitives.ReadUInt64LittleEndian(instruction[(int)totalBytes..]).ToString()); + + ulong value; + if (options.DetectFloats) + { + double floatingValue = BinaryPrimitives.ReadDoubleLittleEndian(instruction[(int)totalBytes..]); + if (Math.Abs(floatingValue) is >= 0.0000000000000001 and <= ulong.MaxValue) + { + operandStrings.Add(floatingValue.ToString(CultureInfo.InvariantCulture)); + totalBytes += 8; + break; + } + value = BitConverter.DoubleToUInt64Bits(floatingValue); + } + else + { + value = BinaryPrimitives.ReadUInt64LittleEndian(instruction[(int)totalBytes..]); + } + + if (options.DetectSigned && value > long.MaxValue) + { + operandStrings.Add(unchecked((long)value).ToString()); + } + else + { + operandStrings.Add(value.ToString()); + } totalBytes += 8; break; case OperandType.Address: @@ -161,7 +196,7 @@ public static (string Line, ulong AdditionalOffset, List References, bool break; } referencedAddresses.Add(BinaryPrimitives.ReadUInt64LittleEndian(instruction[(int)totalBytes..])); - operandStrings.Add(useLabelNames ? $":ADDR_{referencedAddresses[^1]:X}" : $":{referencedAddresses[^1]:X}"); + operandStrings.Add(useLabelNames ? $":ADDR_{referencedAddresses[^1]:X}" : $":0x{referencedAddresses[^1]:X}"); totalBytes += 8; break; case OperandType.Pointer: diff --git a/Program.cs b/Program.cs index 8899e96..a10d8bc 100644 --- a/Program.cs +++ b/Program.cs @@ -533,9 +533,14 @@ private static void PerformDisassembly(CommandLineArgs args) try { disassembledProgram = Disassembler.DisassembleProgram( - program, !args.IsOptionGiven('S', "no-strings"), - !args.IsOptionGiven('P', "no-pads"), - args.IsOptionGiven('f', "allow-full-base-opcodes")); + program, new DisassemblerOptions() + { + DetectStrings = !args.IsOptionGiven('S', "no-strings"), + DetectPads = !args.IsOptionGiven('P', "no-pads"), + DetectFloats = !args.IsOptionGiven('F', "no-floats"), + DetectSigned = !args.IsOptionGiven('G', "no-signed"), + AllowFullyQualifiedBaseOpcodes = args.IsOptionGiven('f', "allow-full-base-opcodes") + }); } catch (Exception e) { diff --git a/Resources/Localization/Strings.resx b/Resources/Localization/Strings.resx index 1a9a38f..999194f 100644 --- a/Resources/Localization/Strings.resx +++ b/Resources/Localization/Strings.resx @@ -202,6 +202,8 @@ disassemble - Generate an AssEmbly program listing from an already assembled exe Usage: 'AssEmbly disassemble <file-path> [destination-path] [options]' -S, --no-strings - Don't attempt to locate and decode strings; keep them as raw bytes -P, --no-pads - Don't attempt to locate uses of the %PAD directive; keep them as chains of HLT + -F, --no-floats - Don't attempt to detect floating point literal operands; keep them as integers + -G, --no-signed - Don't attempt to detect signed negative operands; keep them as positive integers -f, --allow-full-base-opcodes - Allow data detected as fully qualified opcodes for the base instruction set to be interpreted as instructions instead of data. Re-assembly may not be byte-perfect. -1, --v1-format - Specifies that the given executable uses the v1.x.x header-less format. -i, --ignore-newer-version - Force an executable to be loaded even if the major version is greater than the current major version. May cause issues. diff --git a/Test/DisassemblerTests/FullPrograms.cs b/Test/DisassemblerTests/FullPrograms.cs index b86641e..d0561c9 100644 --- a/Test/DisassemblerTests/FullPrograms.cs +++ b/Test/DisassemblerTests/FullPrograms.cs @@ -7,7 +7,14 @@ public class FullPrograms public void StringsPadsNoFullBase() { string program = Disassembler.DisassembleProgram(File.ReadAllBytes("KitchenSink.bin"), - true, true, false); + new DisassemblerOptions() + { + DetectStrings = true, + DetectPads = true, + AllowFullyQualifiedBaseOpcodes = false, + DetectFloats = true, + DetectSigned = true + }); Assert.AreEqual(File.ReadAllText("KitchenSink.Disassembled.asm"), program, "The disassembly process produced unexpected output"); @@ -24,7 +31,14 @@ public void StringsPadsNoFullBase() public void NoStringsPadsNoFullBase() { string program = Disassembler.DisassembleProgram(File.ReadAllBytes("KitchenSink.bin"), - false, true, false); + new DisassemblerOptions() + { + DetectStrings = false, + DetectPads = true, + AllowFullyQualifiedBaseOpcodes = false, + DetectFloats = true, + DetectSigned = false + }); Assert.AreEqual(File.ReadAllText("KitchenSink.Disassembled.NoStrings.asm"), program, "The disassembly process produced unexpected output"); @@ -41,7 +55,14 @@ public void NoStringsPadsNoFullBase() public void StringsNoPadsNoFullBase() { string program = Disassembler.DisassembleProgram(File.ReadAllBytes("KitchenSink.bin"), - true, false, false); + new DisassemblerOptions() + { + DetectStrings = true, + DetectPads = false, + AllowFullyQualifiedBaseOpcodes = false, + DetectFloats = false, + DetectSigned = false + }); Assert.AreEqual(File.ReadAllText("KitchenSink.Disassembled.NoPads.asm"), program, "The disassembly process produced unexpected output"); @@ -58,7 +79,14 @@ public void StringsNoPadsNoFullBase() public void StringsPadsFullBase() { string program = Disassembler.DisassembleProgram(File.ReadAllBytes("KitchenSink.bin"), - true, true, true); + new DisassemblerOptions() + { + DetectStrings = true, + DetectPads = true, + AllowFullyQualifiedBaseOpcodes = true, + DetectFloats = false, + DetectSigned = false + }); Assert.AreEqual(File.ReadAllText("KitchenSink.Disassembled.FullBase.asm"), program, "The disassembly process produced unexpected output"); diff --git a/Test/DisassemblerTests/SpecificConditions.cs b/Test/DisassemblerTests/SpecificConditions.cs index d8e8736..f233f70 100644 --- a/Test/DisassemblerTests/SpecificConditions.cs +++ b/Test/DisassemblerTests/SpecificConditions.cs @@ -3,14 +3,19 @@ [TestClass] public class SpecificConditions { + private static readonly DisassemblerOptions disassemblerOptions = new() + { + DetectStrings = false + }; + [TestMethod] public void EmptyProgram() { - string result = Disassembler.DisassembleProgram(Array.Empty(), true, true, false); + string result = Disassembler.DisassembleProgram(Array.Empty(), disassemblerOptions); Assert.AreEqual(string.Empty, result, "Providing empty program did not produce empty result"); (string line, ulong additionalOffset, List references, bool datFallback) = Disassembler.DisassembleInstruction( - Span.Empty, false, false); + Span.Empty, disassemblerOptions, false); Assert.AreEqual(string.Empty, line, "Providing empty instruction did not produce empty line content"); Assert.AreEqual(0UL, additionalOffset, "Providing empty instruction did not produce 0 additional offset"); Assert.AreEqual(0, references.Count, "Providing empty instruction produced unexpected address references"); @@ -20,11 +25,11 @@ public void EmptyProgram() [TestMethod] public void MissingOperands() { - string result = Disassembler.DisassembleProgram(new byte[] { 0x10 }, true, true, false); + string result = Disassembler.DisassembleProgram(new byte[] { 0x10 }, disassemblerOptions); Assert.AreEqual("%DAT 16", result, "Providing opcode without operands did not produce correct program"); (string line, ulong additionalOffset, List references, bool datFallback) = Disassembler.DisassembleInstruction( - new byte[] { 0x12 }, false, false); + new byte[] { 0x12 }, disassemblerOptions, false); Assert.AreEqual("%DAT 18", line, "Providing opcode without operands did not produce correct line content"); Assert.AreEqual(1UL, additionalOffset, "Providing opcode without operands did not produce correct additional offset"); Assert.AreEqual(0, references.Count, "Providing opcode without operands produced unexpected address references"); @@ -35,7 +40,7 @@ public void MissingOperands() public void TruncatedLiteralOperand() { string result = Disassembler.DisassembleProgram( - new byte[] { 0x11, 0x06, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77 }, false, true, false); + new byte[] { 0x11, 0x06, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77 }, disassemblerOptions); Assert.AreEqual(""" %DAT 17 %DAT 6 @@ -49,7 +54,7 @@ public void TruncatedLiteralOperand() """, result, "Providing opcode with truncated literal operand did not produce correct program"); (string line, ulong additionalOffset, List references, bool datFallback) = Disassembler.DisassembleInstruction( - new byte[] { 0x11, 0x06, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77 }, false, false); + new byte[] { 0x11, 0x06, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77 }, disassemblerOptions, false); Assert.AreEqual("%DAT 17", line, "Providing opcode with truncated literal operand did not produce correct line content"); Assert.AreEqual(1UL, additionalOffset, "Providing opcode with truncated literal operand did not produce correct additional offset"); Assert.AreEqual(0, references.Count, "Providing opcode with truncated literal operand produced unexpected address references"); @@ -60,7 +65,7 @@ public void TruncatedLiteralOperand() public void TruncatedAddressOperand() { string result = Disassembler.DisassembleProgram( - new byte[] { 0x12, 0x06, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77 }, false, true, false); + new byte[] { 0x12, 0x06, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77 }, disassemblerOptions); Assert.AreEqual(""" %DAT 18 %DAT 6 @@ -74,7 +79,7 @@ public void TruncatedAddressOperand() """, result, "Providing opcode with truncated literal operand did not produce correct program"); (string line, ulong additionalOffset, List references, bool datFallback) = Disassembler.DisassembleInstruction( - new byte[] { 0x12, 0x06, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77 }, false, false); + new byte[] { 0x12, 0x06, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77 }, disassemblerOptions, false); Assert.AreEqual("%DAT 18", line, "Providing opcode with truncated literal operand did not produce correct line content"); Assert.AreEqual(1UL, additionalOffset, "Providing opcode with truncated literal operand did not produce correct additional offset"); Assert.AreEqual(0, references.Count, "Providing opcode with truncated literal operand produced unexpected address references"); diff --git a/Test/KitchenSink.Disassembled.FullBase.asm b/Test/KitchenSink.Disassembled.FullBase.asm index 996ba8d..488ca53 100644 --- a/Test/KitchenSink.Disassembled.FullBase.asm +++ b/Test/KitchenSink.Disassembled.FullBase.asm @@ -53,7 +53,7 @@ JLT :0x8ABEED000005292C ; Address does not align to a disassembled instruction %DAT 3 POP rpo MVQ rg0, :0x123456789 ; Address does not align to a disassembled instruction -MVQ :0x123456789, 696969420 ; Address does not align to a disassembled instruction +MVQ :0x123456789, 4664385274516035810 ; Address does not align to a disassembled instruction HLT %DAT 240 %DAT 159 diff --git a/Test/KitchenSink.Disassembled.NoPads.asm b/Test/KitchenSink.Disassembled.NoPads.asm index 3f6c5a7..a7d9661 100644 --- a/Test/KitchenSink.Disassembled.NoPads.asm +++ b/Test/KitchenSink.Disassembled.NoPads.asm @@ -72,7 +72,7 @@ JLT :0x8ABEED000005292C ; Address does not align to a disassembled instruction %DAT 3 POP rpo MVQ rg0, :0x123456789 ; Address does not align to a disassembled instruction -MVQ :0x123456789, 696969420 ; Address does not align to a disassembled instruction +MVQ :0x123456789, 4664385274516035810 ; Address does not align to a disassembled instruction HLT %DAT 240 %DAT 159 diff --git a/Test/KitchenSink.Disassembled.NoStrings.asm b/Test/KitchenSink.Disassembled.NoStrings.asm index 054a21b..f5fa3cd 100644 --- a/Test/KitchenSink.Disassembled.NoStrings.asm +++ b/Test/KitchenSink.Disassembled.NoStrings.asm @@ -150,7 +150,7 @@ JLT :0x8ABEED000005292C ; Address does not align to a disassembled instruction %DAT 3 POP rpo MVQ rg0, :0x123456789 ; Address does not align to a disassembled instruction -MVQ :0x123456789, 696969420 ; Address does not align to a disassembled instruction +MVQ :0x123456789, 6969.6942 ; Address does not align to a disassembled instruction HLT %DAT 240 %DAT 159 diff --git a/Test/KitchenSink.Disassembled.asm b/Test/KitchenSink.Disassembled.asm index a2b4a93..ba8eb23 100644 --- a/Test/KitchenSink.Disassembled.asm +++ b/Test/KitchenSink.Disassembled.asm @@ -53,11 +53,11 @@ JLT :0x8ABEED000005292C ; Address does not align to a disassembled instruction %DAT 3 POP rpo MVQ rg0, :0x123456789 ; Address does not align to a disassembled instruction -MVQ :0x123456789, 696969420 ; Address does not align to a disassembled instruction +MVQ :0x123456789, 6969.6942 ; Address does not align to a disassembled instruction HLT %DAT 240 %DAT 159 -MVW :0x8D9FF000000000AD, 9836955600246800557 ; Address does not align to a disassembled instruction +MVW :0x8D9FF000000000AD, -8609788473462751059 ; Address does not align to a disassembled instruction HLT %PAD 4 DFL :0x700000000008883 ; Address does not align to a disassembled instruction diff --git a/Test/KitchenSink.asm b/Test/KitchenSink.asm index 6bc60d1..dbc5305 100644 --- a/Test/KitchenSink.asm +++ b/Test/KitchenSink.asm @@ -210,7 +210,7 @@ hlt :MORE_CODE mvQ RG0, :0x123456789 -MVq :0X123456789, 696969420 +MVq :0X123456789, 6969.69420 HLT %NUM '🍭' diff --git a/Test/KitchenSink.bin b/Test/KitchenSink.bin index 2802784562cd88f885118dd4f83f2e204c990ae9..293e9a057cd407e8852914e5344bc6297135fa89 100644 GIT binary patch delta 21 dcmbQhI)QbAF(b#L2=VP3Eq6O?wq^`v1OQSd2T1?` delta 21 bcmbQhI)QbAF(b#B7hReRK(N`GF_aMiNRI`>