Skip to content

Commit

Permalink
Add disassembler detection for floating and signed operands
Browse files Browse the repository at this point in the history
  • Loading branch information
TollyH committed May 12, 2024
1 parent 7256d23 commit 9872df0
Show file tree
Hide file tree
Showing 12 changed files with 110 additions and 30 deletions.
7 changes: 6 additions & 1 deletion Debugger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ public class Debugger
private readonly Dictionary<string, ulong> 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)
{
Expand Down Expand Up @@ -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();
Expand Down
51 changes: 43 additions & 8 deletions Disassembler.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Disassemble a program to AssEmbly code from it's assembled bytecode.
/// </summary>
public static string DisassembleProgram(byte[] program, bool detectStrings, bool detectPads, bool allowFullyQualifiedBaseOpcodes)
public static string DisassembleProgram(byte[] program, DisassemblerOptions options)
{
ulong offset = 0;
List<string> result = new();
Expand All @@ -25,20 +33,21 @@ public static string DisassembleProgram(byte[] program, bool detectStrings, bool
while (offset < (ulong)program.LongLength)
{
(string line, ulong additionalOffset, List<ulong> referencedAddresses, bool datFallback) =
DisassembleInstruction(program.AsSpan()[(int)offset..], allowFullyQualifiedBaseOpcodes, true);
DisassembleInstruction(program.AsSpan()[(int)offset..], options, true);

foreach (ulong address in referencedAddresses)
{
_ = addressReferences.TryAdd(address, new List<ulong>());
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++;
Expand Down Expand Up @@ -89,7 +98,7 @@ public static string DisassembleProgram(byte[] program, bool detectStrings, bool
/// </param>
/// <returns>(Disassembled line, Number of bytes instruction was, Referenced addresses [if present], Used %DAT directive)</returns>
public static (string Line, ulong AdditionalOffset, List<ulong> References, bool DatFallback) DisassembleInstruction(
Span<byte> instruction, bool allowFullyQualifiedBaseOpcodes, bool useLabelNames)
Span<byte> instruction, DisassemblerOptions options, bool useLabelNames)
{
if (instruction.Length == 0)
{
Expand All @@ -113,7 +122,8 @@ public static (string Line, ulong AdditionalOffset, List<ulong> 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.
Expand Down Expand Up @@ -151,7 +161,32 @@ public static (string Line, ulong AdditionalOffset, List<ulong> 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:
Expand All @@ -161,7 +196,7 @@ public static (string Line, ulong AdditionalOffset, List<ulong> 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:
Expand Down
11 changes: 8 additions & 3 deletions Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
2 changes: 2 additions & 0 deletions Resources/Localization/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,8 @@ disassemble - Generate an AssEmbly program listing from an already assembled exe
Usage: 'AssEmbly disassemble &lt;file-path&gt; [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.
Expand Down
36 changes: 32 additions & 4 deletions Test/DisassemblerTests/FullPrograms.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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");
Expand All @@ -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");
Expand All @@ -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");
Expand Down
21 changes: 13 additions & 8 deletions Test/DisassemblerTests/SpecificConditions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<byte>(), true, true, false);
string result = Disassembler.DisassembleProgram(Array.Empty<byte>(), disassemblerOptions);
Assert.AreEqual(string.Empty, result, "Providing empty program did not produce empty result");

(string line, ulong additionalOffset, List<ulong> references, bool datFallback) = Disassembler.DisassembleInstruction(
Span<byte>.Empty, false, false);
Span<byte>.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");
Expand All @@ -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<ulong> 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");
Expand All @@ -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
Expand All @@ -49,7 +54,7 @@ public void TruncatedLiteralOperand()
""", result, "Providing opcode with truncated literal operand did not produce correct program");

(string line, ulong additionalOffset, List<ulong> 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");
Expand All @@ -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
Expand All @@ -74,7 +79,7 @@ public void TruncatedAddressOperand()
""", result, "Providing opcode with truncated literal operand did not produce correct program");

(string line, ulong additionalOffset, List<ulong> 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");
Expand Down
2 changes: 1 addition & 1 deletion Test/KitchenSink.Disassembled.FullBase.asm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Test/KitchenSink.Disassembled.NoPads.asm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Test/KitchenSink.Disassembled.NoStrings.asm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions Test/KitchenSink.Disassembled.asm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Test/KitchenSink.asm
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ hlt

:MORE_CODE
mvQ RG0, :0x123456789
MVq :0X123456789, 696969420
MVq :0X123456789, 6969.69420
HLT

%NUM '🍭'
Expand Down
Binary file modified Test/KitchenSink.bin
Binary file not shown.

0 comments on commit 9872df0

Please sign in to comment.