public static void GenerateIncomingCalls(IEnumerable<ProgramLine> programLines, MemoryMap memoryMap) { foreach (ProgramLine instructionLine in programLines) { if (instructionLine.OutgoingCall != null) { // Find the memory description of the call target address int targetAddress = instructionLine.OutgoingCall.Address; MemoryCellDescription memDescription = memoryMap.MemoryCellDescriptions[targetAddress]; // This description should be a instruction line starting at the same address if (memDescription == null || memDescription.Type != MemoryDescriptionType.ProgramLine) { throw new Exception("Outgoing call at address " + targetAddress + " points to an unknown area in memory"); } ProgramLine targetLine = (ProgramLine)memDescription.Description; if (targetLine.Type != ProgramLineType.OpCodeInstruction || targetLine.LineAddress != targetAddress) { throw new Exception("Outgoing call from program line " + instructionLine.Text + " at address " + instructionLine.LineAddress + " points in the middle of the instruction " + targetLine.Text + " starting at address " + targetLine.LineAddress + " in memory"); } else { // Connect the source and the target instructionLine.OutgoingCall.Line = targetLine; if (targetLine.IncomingCalls == null) { targetLine.IncomingCalls = new List<CallSource>(); } targetLine.IncomingCalls.Add(instructionLine.OutgoingCall.Source); } } } }
public ROM() { cells = new byte[Memory.BYTES_16K]; Address = new BusConnector<ushort>(); Data = new BusConnector<byte>(); string sourcePath = "zxspectrum.asm"; Stream romProgramFileStream = PlatformSpecific.GetStreamForProjectFile(sourcePath); Program = Assembler.ParseProgram(sourcePath, romProgramFileStream, Encoding.UTF8, false); MemoryMap programBytes = new MemoryMap(cells.Length); Assembler.CompileProgram(Program, 0, programBytes); cells = programBytes.MemoryCells; }
public static void CheckMemoryDump(string filename) { string objFilepath = String.Format("Assembly/Samples/{0}.obj", filename); Stream objStream = PlatformSpecific.GetStreamForProjectFile(objFilepath); string asmFilepath = String.Format("Assembly/DisassemblerResults/{0}.disasm", filename); Stream asmStream = PlatformSpecific.GetStreamForProjectFile(asmFilepath); string referenceAsmText = null; using (StreamReader sr = new StreamReader(asmStream)) { referenceAsmText = sr.ReadToEnd(); } MemoryMap memoryMap = new MemoryMap(65536); CallTarget entryPoint = new CallTarget(new CallSource(CallSourceType.Boot, 0, null), 0); Program program = Disassembler.GenerateProgram(objFilepath, objStream, 0, new CallTarget[] { entryPoint }, memoryMap); if (program.ToString() != referenceAsmText) { throw new Exception(String.Format("Disassembly of program {0} does not match reference text", filename)); } }
public static void CheckProgram(string filename) { string objFilePath = String.Format("Assembly/Samples/{0}.obj", filename); Stream objStream = PlatformSpecific.GetStreamForProjectFile(objFilePath); string asmFilePath = String.Format("Assembly/Samples/{0}.asm", filename); Stream asmStream = PlatformSpecific.GetStreamForProjectFile(asmFilePath); byte[] referenceMemory = ReadToEnd(objStream); Program program = Assembler.ParseProgram(filename, asmStream, Encoding.UTF8, false); MemoryMap compiledMemory = new MemoryMap(referenceMemory.Length); Assembler.CompileProgram(program, 0, compiledMemory); for (int i = 0; i < referenceMemory.Length; i++) { if (compiledMemory.MemoryCells[i] != referenceMemory[i]) { ProgramLine errorLine = null; foreach (ProgramLine prgLine in program.Lines) { if (prgLine.LineAddress > i) break; else if (prgLine.Type != ProgramLineType.CommentOrWhitespace) errorLine = prgLine; } if (errorLine.Type == ProgramLineType.OpCodeInstruction) { throw new Exception(String.Format("Compilation result different from reference at address {0} - compiled {1:X} / expected {2:X} - line {3} starting at address {4}, {5}+{6} bytes : {7}", i, compiledMemory.MemoryCells[i], referenceMemory[i], errorLine.LineNumber, errorLine.LineAddress, errorLine.InstructionCode.OpCodeByteCount, errorLine.InstructionCode.OperandsByteCount, errorLine.Text)); } else { throw new Exception(String.Format("Compilation result different from reference at address {0} - compiled {1:X} / expected {2:X} - line {3} starting at address {4}, {5}+{6} bytes : {7}", i, compiledMemory.MemoryCells[i], referenceMemory[i], errorLine.LineNumber, errorLine.LineAddress, errorLine.Text)); } } } }
private static InstructionCode ReadOneInstructionCode(MemoryMap memoryMap, ref int currentAddress, out byte displacement) { byte currentOpCodeTable = 0; displacement = 0; InstructionCode instrCode = null; do { // Read one code byte in memory and look for a coresponding instruction code in the instructions table byte codeByte = memoryMap.MemoryCells[currentAddress++]; instrCode = Z80OpCodes.Tables[currentOpCodeTable, codeByte]; // If more code bytes are expected, switch to another instruction decoding table if (instrCode.SwitchToTableNumber.HasValue) { currentOpCodeTable = instrCode.SwitchToTableNumber.Value; if (currentOpCodeTable >= 5) { // 4 bytes opcode, next byte is displacement value displacement = memoryMap.MemoryCells[currentAddress++]; } } } // Continue to read code bytes until a complete instruction has been recognized while(instrCode.FetchMoreBytes > 0); // Special case : NONI[DDH] and NONI[FDH] if (instrCode.SwitchToTableNumber.HasValue) { // A second byte was read, which did not yield a valid instruction code => backtrack currentAddress--; } return(instrCode); }
private static void CompileInstructionLine(Program program, MemoryMap memoryMap, ref int address, IList<Operand> operands, ProgramLine programLine) { if (!String.IsNullOrEmpty(programLine.Label)) { string label = programLine.Label; if (!program.Variables.ContainsKey(label)) { program.Variables.Add(label, new LabelAddress(address)); } } // Find instruction code and instruction type string instructionText = GenerateInstructionText(programLine); InstructionCode instructionCode = null; if (!Z80OpCodes.Dictionary.TryGetValue(instructionText, out instructionCode)) { throw new Exception(String.Format("Line {0} : invalid instruction type {1}", programLine.LineNumber, instructionText)); } InstructionType instructionType = Z80InstructionTypes.Table[instructionCode.InstructionTypeIndex]; // Register binary representation of program line programLine.InstructionCode = instructionCode; programLine.InstructionType = instructionType; MemoryCellDescription programLineDescription = new MemoryCellDescription(MemoryDescriptionType.ProgramLine, programLine); // One or two byte opcodes if (instructionCode.OpCodeByteCount <= 2) { // Optional one byte prefix if (instructionCode.Prefix != null) { memoryMap.MemoryCellDescriptions[address] = programLineDescription; memoryMap.MemoryCells[address++] = instructionCode.Prefix[0]; } // Opcode memoryMap.MemoryCellDescriptions[address] = programLineDescription; memoryMap.MemoryCells[address++] = instructionCode.OpCode; // Parameters AddressingMode? paramType1 = instructionType.Param1Type; if (paramType1.HasValue) { InstructionLineParam lineParam1 = programLine.OpCodeParameters[0]; AddOperandForLineParam(ref address, operands, programLine, paramType1, lineParam1); } AddressingMode? paramType2 = instructionType.Param2Type; if (paramType2.HasValue) { InstructionLineParam lineParam2 = programLine.OpCodeParameters[1]; AddOperandForLineParam(ref address, operands, programLine, paramType2, lineParam2); } } // Four bytes opcodes else { // Two bytes prefix memoryMap.MemoryCellDescriptions[address] = programLineDescription; memoryMap.MemoryCells[address++] = instructionCode.Prefix[0]; memoryMap.MemoryCellDescriptions[address] = programLineDescription; memoryMap.MemoryCells[address++] = instructionCode.Prefix[1]; // Displacement AddressingMode? paramType1 = instructionType.Param1Type; if (paramType1.HasValue) { InstructionLineParam lineParam1 = programLine.OpCodeParameters[0]; AddOperandForLineParam(ref address, operands, programLine, paramType1, lineParam1); } AddressingMode? paramType2 = instructionType.Param2Type; if (paramType2.HasValue) { InstructionLineParam lineParam2 = programLine.OpCodeParameters[1]; AddOperandForLineParam(ref address, operands, programLine, paramType2, lineParam2); } // Opcode memoryMap.MemoryCellDescriptions[address] = programLineDescription; memoryMap.MemoryCells[address++] = instructionCode.OpCode; } }
public static void CompileProgram(Program program, int baseAddress, MemoryMap memoryMap) { // 1. First pass : // - execute assembler directives // - recognize and generate opcodes // => compute all adresses for labels // - collect all operands and their adresses, for delayed evaluation during the second pass int address = baseAddress; IList<Operand> operands = new List<Operand>(); foreach(ProgramLine programLine in program.Lines) { // Skip lines with parsing errors if(programLine.HasError) { continue; } // Set line address programLine.LineAddress = address; try { // -------------------------------------- // Assembler directives // -------------------------------------- if (programLine.Type == ProgramLineType.AssemblerDirective) { bool isEndDirective = CompileDirectiveLine(program, memoryMap, ref address, operands, programLine); if (isEndDirective) { // END : indicates the end of the program. Any other statements following it will be ignored. break; } } // -------------------------------------- // Instructions opcodes // -------------------------------------- else if (programLine.Type == ProgramLineType.OpCodeInstruction) { CompileInstructionLine(program, memoryMap, ref address, operands, programLine); } // -------------------------------------- // Comments // -------------------------------------- else if (programLine.Type == ProgramLineType.CommentOrWhitespace) { CompileCommentLine(programLine); } } catch (Exception e) { programLine.Error = new ProgramLineError() { ErrorMessage = e.Message, StartColumnIndex = 0, EndColumnIndex = programLine.Text.Length - 1 }; program.ErrorCount++; program.LinesWithErrors.Add(programLine); } } // 2. Second pass // - compute and generate values for all operands try { ComputeAndGenerateOperands(program, memoryMap, operands); } catch (Exception e) { ProgramLine firstProgramLine = program.Lines[0]; firstProgramLine.Error = new ProgramLineError() { ErrorMessage = "Failed to compute values for all operands : " + e.Message, StartColumnIndex = 0, EndColumnIndex = firstProgramLine.Text.Length - 1 }; program.ErrorCount++; program.LinesWithErrors.Add(firstProgramLine); } }
private static bool CompileDirectiveLine(Program program, MemoryMap memoryMap, ref int address, IList<Operand> operands, ProgramLine programLine) { switch (programLine.DirectiveName) { case "EQU": // label EQU nn : this directive is used to assign a value to a label. if (String.IsNullOrEmpty(programLine.Label)) { throw new Exception(String.Format("Line {0} : EQU directive needs a label", programLine.LineNumber)); } else if (programLine.DirectiveParameters.Count != 1) { throw new Exception(String.Format("Line {0} : EQU directive needs exactly one parameter", programLine.LineNumber)); } else { DirectiveLineParam param = programLine.DirectiveParameters[0]; string label = programLine.Label; if (program.Variables.ContainsKey(label)) { throw new Exception(String.Format("Line {0} : EQU directive - this label can not be defined twice", programLine.LineNumber)); } program.Variables.Add(label, param.NumberExpression); } break; case "DEFL": // label DEFL nn : this directive also assigns a value nn to a label, // but may be repeated whithin the program with different values for // the same label, whereas EQU may be used only once. // ==> Symbol table is much more complicated to handle in incremental parsing scenarios if it does not contain constants only throw new Exception(String.Format("Line {0} : DEFL directive is not supported", programLine.LineNumber)); case "ORG": // ORG nn : this directive will set the address counter to the value nn. // In other words, the first executable instruction encountered after // this directive will reside at the value nn. It can be used to locate // different segments of a program at different memory locations. if (!String.IsNullOrEmpty(programLine.Label)) { throw new Exception(String.Format("Line {0} : ORG directive does not allow a label", programLine.LineNumber)); } else if (programLine.DirectiveParameters.Count != 1) { throw new Exception(String.Format("Line {0} : ORG directive needs exactly one parameter", programLine.LineNumber)); } else { DirectiveLineParam param = programLine.DirectiveParameters[0]; address = param.NumberExpression.GetValue(program.Variables, programLine); } break; case "DEFS": // DEFS nn : reserves a bloc of memory size nn bytes, starting at the // current value of the reference counter. case "RESERVE": // label RESERVE nn : using the RESERVE pseudo-operation, you assign a // name to the memory area and declare the number of locations to be assigned. if (String.IsNullOrEmpty(programLine.Label)) { string label = programLine.Label; program.Variables.Add(label, new LabelAddress(address)); } if (programLine.DirectiveParameters.Count != 1) { throw new Exception(String.Format("Line {0} : DEFS or RESERVE directive needs exactly one parameter", programLine.LineNumber)); } else { string label = programLine.Label; program.Variables.Add(label, new NumberOperand(address)); DirectiveLineParam param = programLine.DirectiveParameters[0]; int increment = param.NumberExpression.GetValue(program.Variables, programLine); address += increment; } break; case "DEFB": // DEFB n,n,... : this directive assigns eight-bit contents to a byte // residing at the current reference counter. // DEFB 'S',... : assigns the ASCII value of 'S' to the byte. if (!String.IsNullOrEmpty(programLine.Label)) { string label = programLine.Label; program.Variables.Add(label, new LabelAddress(address)); } if (programLine.DirectiveParameters == null) { throw new Exception(String.Format("Line {0} : DEFB directive needs at least one parameter", programLine.LineNumber)); } else { foreach (DirectiveLineParam param in programLine.DirectiveParameters) { Operand operand = new Operand() { Type = OperandType.Unsigned8, Address = address, Expression = param.NumberExpression, Line = programLine }; address += 1; operands.Add(operand); } } break; case "DEFW": // DEFW nn,nn,... : this assigns the value nn to the two-byte word residing at // the current reference counter and the following location. if (!String.IsNullOrEmpty(programLine.Label)) { string label = programLine.Label; program.Variables.Add(label, new LabelAddress(address)); } if (programLine.DirectiveParameters == null) { throw new Exception(String.Format("Line {0} : DEFW directive at least one parameter", programLine.LineNumber)); } else { foreach (DirectiveLineParam param in programLine.DirectiveParameters) { Operand operand = new Operand() { Type = OperandType.Unsigned16, Address = address, Expression = param.NumberExpression, Line = programLine }; address += 2; operands.Add(operand); } } break; case "DEFM": // DEFM "S" : stores into memory the string "S" starting at the current // reference counter. It must less than 63 in length. if (!String.IsNullOrEmpty(programLine.Label)) { string label = programLine.Label; program.Variables.Add(label, new LabelAddress(address)); } if (programLine.DirectiveParameters.Count != 1) { throw new Exception(String.Format("Line {0} : DEFM directive needs exactly one parameter", programLine.LineNumber)); } else { DirectiveLineParam param = programLine.DirectiveParameters[0]; string stringValue = param.StringValue; MemoryCellDescription programLineDescription = new MemoryCellDescription(MemoryDescriptionType.ProgramLine, programLine); foreach (char chr in stringValue.ToCharArray()) { memoryMap.MemoryCellDescriptions[address] = programLineDescription; memoryMap.MemoryCells[address] = (byte)chr; address += 1; } } break; case "DATA": // label DATA n,nn,'S' : the DATA pseudo-operation allows the programmer // to enter fixed data into memory. Most assemblers allow more elaborate // DATA instructions that handle a large amount of data at one time. // => Not useful since we already have DEFB / DEFW / DEFM, and one type of value per line is better for readability throw new Exception(String.Format("Line {0} : DATA directive is not supported", programLine.LineNumber)); case "END": // END : indicates the end of the program. Any other statements following // it will be ignored. if (!String.IsNullOrEmpty(programLine.Label)) { throw new Exception(String.Format("Line {0} : END directive does not allow a label", programLine.LineNumber)); } else { return true; } // ==> Not supported yet, spectrum programs remain short by nature case "MACRO": // MACRO P0 P1 .. Pn : is used to define a label as a macro, and to define // its formal parameter list. throw new Exception(String.Format("Line {0} : MACRO directive is not supported", programLine.LineNumber)); case "ENDM": // ENDM : is used to mark the end of macro definition. throw new Exception(String.Format("Line {0} : ENDM directive is not supported", programLine.LineNumber)); } return false; }
private static InstructionCode ReadOneInstructionCode(MemoryMap memoryMap, ref int currentAddress, out byte displacement) { byte currentOpCodeTable = 0; displacement = 0; InstructionCode instrCode = null; do { // Read one code byte in memory and look for a coresponding instruction code in the instructions table byte codeByte = memoryMap.MemoryCells[currentAddress++]; instrCode = Z80OpCodes.Tables[currentOpCodeTable, codeByte]; // If more code bytes are expected, switch to another instruction decoding table if(instrCode.SwitchToTableNumber.HasValue) { currentOpCodeTable = instrCode.SwitchToTableNumber.Value; if (currentOpCodeTable >= 5) { // 4 bytes opcode, next byte is displacement value displacement = memoryMap.MemoryCells[currentAddress++]; } } } // Continue to read code bytes until a complete instruction has been recognized while(instrCode.FetchMoreBytes > 0); // Special case : NONI[DDH] and NONI[FDH] if (instrCode.SwitchToTableNumber.HasValue) { // A second byte was read, which did not yield a valid instruction code => backtrack currentAddress--; } return instrCode; }
public static void AnalyzeProgramFlow(Program program, MemoryMap memoryMap) { // Generate all outgoing calls foreach (ProgramLine programLine in program.Lines) { ProgramFlowAnalyzer.GenerateOutgoingCall(programLine, program.Variables); } // Generate all incoming calls ProgramFlowAnalyzer.GenerateIncomingCalls(program.Lines, memoryMap); }
public static void DecompileZ80tests() { string fileName = "z80tests"; TapFile tapFile = TapFileReader.ReadTapFile(PlatformSpecific.GetStreamForProjectFile("TestTapes/" + fileName + ".tap"), fileName); ProgramHeader loaderHeader = (ProgramHeader)tapFile.DataBlocks[0]; int programLength = loaderHeader.ProgramLength; int variablesLength = loaderHeader.FollowingDataLength - loaderHeader.ProgramLength; TapDataBlock loaderBlock = tapFile.DataBlocks[1]; MemoryStream binaryMemoryStream = new MemoryStream(loaderBlock.TapeData); /* Read flag */ binaryMemoryStream.ReadByte(); BasicProgram basicLoader = BasicReader.ReadMemoryFormat(fileName, binaryMemoryStream, programLength, variablesLength); ByteArrayHeader codeHeader = (ByteArrayHeader)tapFile.DataBlocks[2]; int dataLength = codeHeader.DataLength; TapDataBlock codeBlock = tapFile.DataBlocks[3]; binaryMemoryStream = new MemoryStream(codeBlock.TapeData); /* Read flag */ binaryMemoryStream.ReadByte(); int baseAddress = codeHeader.StartAddress; int startAddress = codeHeader.StartAddress; SpectrumMemoryMap spectrumMemory = new SpectrumMemoryMap(); // Load machine code in memory to generate entry points BEFORE dissassembly int b = -1; int currentAddress = baseAddress; long streamPositionBefore = binaryMemoryStream.Position; while ((b = binaryMemoryStream.ReadByte()) >= 0) { spectrumMemory.MemoryCells[currentAddress] = (byte)b; spectrumMemory.MemoryCellDescriptions[currentAddress] = null; // Reset previous cell descriptions currentAddress++; } binaryMemoryStream.Position = streamPositionBefore; List<CallTarget> entryPoints = new List<CallTarget>(); entryPoints.Add(new CallTarget(new CallSource(CallSourceType.ExternalEntryPoint, 0, null), startAddress)); entryPoints.Add(new CallTarget(new CallSource(CallSourceType.CallInstruction, 0, null), 0x80B8)); entryPoints.Add(new CallTarget(new CallSource(CallSourceType.CallInstruction, 0, null), 0x80C0)); entryPoints.Add(new CallTarget(new CallSource(CallSourceType.JumpInstruction, 0x94CE, null), 0x94D1)); ReadEntryPointsInTestsParametersTable(0x822B, spectrumMemory, entryPoints); ReadEntryPointsInTestsParametersTable(0x8407, spectrumMemory, entryPoints); Program program = Disassembler.GenerateProgram(fileName, binaryMemoryStream, baseAddress, entryPoints.ToArray(), spectrumMemory); foreach (ProgramLine line in program.Lines) { if (line.Type == ProgramLineType.OpCodeInstruction) { TryReplaceAddressWithSystemVariables(spectrumMemory, program, line, 0); TryReplaceAddressWithSystemVariables(spectrumMemory, program, line, 1); } } program.Lines.Insert(5, Assembler.ParseProgramLine("VAR_MEMPTR_CHECKSUM EQU FFFFH")); program.Lines.Insert(5, Assembler.ParseProgramLine("LAST_K EQU 5C08H")); program.Lines.Insert(5, Assembler.ParseProgramLine("CHAN_OPEN EQU 1601H")); program.Lines.Insert(5, Assembler.ParseProgramLine("CLS EQU 0D6BH")); program.Lines.Insert(5, Assembler.ParseProgramLine("START EQU 0000H")); program.Lines.Insert(5, Assembler.ParseProgramLine("")); program.RenumberLinesAfterLineNumber(0); program.PrefixLabelToProgramLine(0x8000, "MAIN"); program.RenameSymbolInProgram("L8003", "EXEC_LDIR"); program.RenameSymbolInProgram("L800A", "EXEC_LDDR"); program.RenameSymbolInProgram("L8011", "EXEC_CPIR"); program.RenameSymbolInProgram("L801A", "EXEC_CPDR"); program.RenameSymbolInProgram("L8023", "EXEC_DJNZ_FF"); program.RenameSymbolInProgram("L802B", "EXEC_DJNZ_FF_LOOP"); program.RenameSymbolInProgram("L802E", "EXEC_DJNZ_01"); program.RenameSymbolInProgram("L8036", "EXEC_DJNZ_01_LOOP"); program.RenameSymbolInProgram("L8039", "MENU_SCREEN"); program.RenameSymbolInProgram("L8042", "MENU_SCREEN_INPUT"); program.RenameSymbolInProgram("L80DA", "INIT_SCREEN"); program.AppendCommentToProgramLine(0x803C, "Menu screen string start address"); program.InsertCommentAboveProgramLine(0x8114, "--- Menu screen string ---"); CharDecoder decoder = new CharDecoder(); program.CommentStringChars(0x8114, decoder.DecodeChar, Program.StringTerminationType.SpecialChar, 0, 0xFF); program.AppendCommentToProgramLine(0x8045, "If Key 1 was pressed"); program.RenameSymbolInProgram("L8057", "FLAGS_TESTS"); program.AppendCommentToProgramLine(0x8049, "If Key 2 was pressed"); program.RenameSymbolInProgram("L805F", "MEMPTR_TESTS"); program.AppendCommentToProgramLine(0x804D, "If Key 3 was pressed"); program.InsertCommentAboveProgramLine(0x8057, "=> exit to BASIC"); program.AppendCommentToProgramLine(0x80DA, "Clear the display"); program.AppendCommentToProgramLine(0x80DF, "Open a channel : user stream 02"); program.InsertCommentAboveProgramLine(0x80E2, "=> return to MENU_SCREEN"); program.RenameSymbolInProgram("L80E2", "WAIT_KEYPRESS"); program.RenameSymbolInProgram("L80E6", "WAIT_KEYPRESS_LOOP"); program.RenameSymbolInProgram("L80ED", "DISPLAY_STRING"); program.RenameSymbolInProgram("L80D2", "SETIY_DISPLAY_STRING"); program.RenameSymbolInProgram("L80F5", "DISPLAY_TWO_HEXBYTES"); program.RenameSymbolInProgram("L80FE", "DISPLAY_TWO_HEXCHARS"); program.RenameSymbolInProgram("L810B", "DISPLAY_ONE_HEXCHAR"); program.InsertCommentAboveProgramLine(0x80ED, "Display all chars starting from address DE upwards, until char FF"); program.RenameSymbolInProgram("L8072", "TEST_SCREEN"); program.RenameSymbolInProgram("L807D", "TEST_SCREEN_LAUNCH_TEST"); program.RenameSymbolInProgram("L80AF", "TEST_SCREEN_NEXT_TEST"); program.PrefixLabelToProgramLine(0x80B8, "TEST_SCREEN_TEST_PASSED", true); Program.ReplaceAddressWithSymbolInProgramLine(program.GetLineFromAddress(0x80A6), 1, false, "TEST_SCREEN_TEST_PASSED"); program.PrefixLabelToProgramLine(0x80C0, "TEST_SCREEN_TEST_FAILED", true); Program.ReplaceAddressWithSymbolInProgramLine(program.GetLineFromAddress(0x80AA), 1, false, "TEST_SCREEN_TEST_FAILED"); program.AppendCommentToProgramLine(0x805A, "Flags test screen string start address"); program.InsertCommentAboveProgramLine(0x81CD, "--- Flags test screen string ---"); program.CommentStringChars(0x81CD, decoder.DecodeChar, Program.StringTerminationType.SpecialChar, 0, 0xFF); program.AppendCommentToProgramLine(0x8057, "Flags tests parameter table start address"); program.InsertCommentAboveProgramLine(0x822B, "--- Flags tests parameter table ---"); program.AppendCommentToProgramLine(0x8062, "MEMPTR test screen string start address"); program.InsertCommentAboveProgramLine(0x81EA, "--- MEMPTR test screen string ---"); program.CommentStringChars(0x81EA, decoder.DecodeChar, Program.StringTerminationType.SpecialChar, 0, 0xFF); program.AppendCommentToProgramLine(0x805F, "MEMPTR tests parameter table start address"); program.InsertCommentAboveProgramLine(0x8407, "--- MEMPTR tests parameter table ---"); program.RenameSymbolInProgram("L8067", "END_OF_TESTS"); program.AppendCommentToProgramLine(0x8067, "End of tests string start address"); program.InsertCommentAboveProgramLine(0x81AB, "--- End of tests string ---"); program.CommentStringChars(0x81AB, decoder.DecodeChar, Program.StringTerminationType.SpecialChar, 0, 0xFF); program.AppendCommentToProgramLine(0x8226, "Space reserved to save HL in routine TEST_SCREEN"); program.AppendCommentToProgramLine(0x8227, "Space reserved to save HL in routine TEST_SCREEN"); program.AppendCommentToProgramLine(0x8228, "Space reserved to save SP in routine TEST_SCREEN"); program.AppendCommentToProgramLine(0x8229, "Space reserved to save SP in routine TEST_SCREEN"); program.InsertCommentAboveProgramLine(0x8085, "Here HL points to an entry in the tests parameters table"); program.InsertCommentAboveProgramLine(0x8088, "Test params structure 0 - 1 : Pointer to an executable machine code function : the test launcher. 00H|00H marks the end of the parameters table."); program.InsertCommentAboveProgramLine(0x8088, "Test params structure 2 -> FFH : starting with the third byte, the parameter table entry contains a string describing the test"); program.AppendCommentToProgramLine(0x8091, "Display tested opcode name"); program.AppendCommentToProgramLine(0x8094, "TAB"); program.AppendCommentToProgramLine(0x8097, "BRIGHT ..."); program.AppendCommentToProgramLine(0x809A, "... 0"); program.AppendCommentToProgramLine(0x809C, ":"); program.AppendCommentToProgramLine(0x809F, "space"); program.InsertCommentAboveProgramLine(0x80AF, "=> jump to test launcher, start address found at the beginning the current parameter table entry"); program.RenameSymbolInProgram("L892D", "FLAGS_TEST_COMMON_EXEC"); program.RenameSymbolInProgram("L9423", "MEMPTR_TEST_COMMON_EXEC"); program.RenameSymbolInProgram("L89B9", "FLAGS_TEST_REPEAT_EXEC"); program.RenameSymbolInProgram("L8A0B", "FLAGS_TEST_DDCB_EXEC"); program.RenameSymbolInProgram("L8A6D", "FLAGS_TEST_FDCB_EXEC"); program.RenameSymbolInProgram("L8B03", "FLAGS_TEST_CB_EXEC"); program.CommentStringChars(0x8208, decoder.DecodeChar, Program.StringTerminationType.SpecialChar, 0, 0xFF); program.CommentStringChars(0x820F, decoder.DecodeChar, Program.StringTerminationType.SpecialChar, 0, 0xFF); program.AppendCommentToProgramLine(0x8083, "Set white border around the screen"); program.AppendCommentToProgramLine(0x808C, "Here : HL points to the address of the test laucher routine, DE points to the string describing the test"); program.AppendCommentToProgramLine(0x808F, "If HL = 00 : end of the tests table, else :"); program.AppendCommentToProgramLine(0x80A2, "After the end of the string, save the address of the next entry in tests table for next iteration"); program.InsertCommentAboveProgramLine(0x8208, "--- Passed test string ---"); program.InsertCommentAboveProgramLine(0x820F, "--- Failed test string ---"); program.InsertCommentAboveProgramLine(0x892D, "Generic execution environment for flags test"); program.RenameSymbolInProgram("L9520", "ADD_FLAGS_TO_CHECKSUM"); program.AppendCommentToProgramLine(0x954A, "Flags Checksum value"); program.AppendCommentToProgramLine(0x892F, "Replaces instruction CALL START below with CALL (test EXEC address)"); program.InsertCommentAboveProgramLine(0x894E, "==> call to one of the FLAGS_TEST_nn_EXEC routine"); program.AppendCommentToProgramLine(0x895C, "Display Flags checksum value"); program.AppendCommentToProgramLine(0x8964, "Compare Flags checksum with expected value"); program.AppendCommentToProgramLine(0x8966, "==> return to TEST_SCREEN_TEST_FAILED"); program.AppendCommentToProgramLine(0x8968, "==> return to TEST_SCREEN_TEST_PASSED"); program.RenameSymbolInProgram("L8949", "FLAGS_TEST_CMN_EXEC_AF_VAL"); program.AppendCommentToProgramLine(0x89BA, "Replaces instruction LDI below with one of the repeated instructions from the test table"); program.InsertCommentAboveProgramLine(0x89D1, "==> executes one of the repeated instructions from the test table"); program.RenameSymbolInProgram("L89C3", "FLAGS_TEST_REPEAT_EXEC_EXT_LOOP"); program.RenameSymbolInProgram("L89CD", "FLAGS_TEST_REPEAT_EXEC_INN_LOOP"); program.AppendCommentToProgramLine(0x89E1, "Display Flags checksum value"); program.AppendCommentToProgramLine(0x89E9, "Compare Flags checksum with expected value"); program.AppendCommentToProgramLine(0x89EB, "==> return to TEST_SCREEN_TEST_FAILED"); program.AppendCommentToProgramLine(0x89ED, "==> return to TEST_SCREEN_TEST_PASSED"); program.RenameSymbolInProgram("L8A1C", "FLAGS_TEST_DDCB_EXEC_LOOP"); program.AppendCommentToProgramLine(0x8A1F, "<= third byte of opcode replaced with all values between 00 and FF by the line above"); program.AppendCommentToProgramLine(0x8A45, "Display Flags checksum value"); program.AppendCommentToProgramLine(0x8A4D, "Compare Flags checksum with expected value"); program.AppendCommentToProgramLine(0x8A4F, "==> return to TEST_SCREEN_TEST_FAILED"); program.AppendCommentToProgramLine(0x8A51, "==> return to TEST_SCREEN_TEST_PASSED"); program.RenameSymbolInProgram("L8A81", "FLAGS_TEST_FDCB_EXEC_LOOP"); program.AppendCommentToProgramLine(0x8A84, "<= third byte of opcode replaced with all values between 00 and FF by the line above"); program.AppendCommentToProgramLine(0x8AAA, "Display Flags checksum value"); program.AppendCommentToProgramLine(0x8AB2, "Compare Flags checksum with expected value"); program.AppendCommentToProgramLine(0x8AB4, "==> return to TEST_SCREEN_TEST_FAILED"); program.AppendCommentToProgramLine(0x8AB6, "==> return to TEST_SCREEN_TEST_PASSED"); program.RenameSymbolInProgram("L8B14", "FLAGS_TEST_CB_EXEC_LOOP"); program.RenameSymbolInProgram("L8B2A", "FLAGS_TEST_CB_EXEC_CASE1"); program.RenameSymbolInProgram("L8B2C", "FLAGS_TEST_CB_EXEC_CASE2"); program.RenameSymbolInProgram("L8B3A", "FLAGS_TEST_CB_EXEC_CASE3"); program.AppendCommentToProgramLine(0x8B24, "<= third byte of opcode replaced with all values between 00 and FF by the line above at 8B17"); program.AppendCommentToProgramLine(0x8B2A, "<= third byte of opcode replaced with all values between 00 and FF by the line above at 8B14"); program.AppendCommentToProgramLine(0x8B51, "Display Flags checksum value"); program.AppendCommentToProgramLine(0x8B59, "Compare Flags checksum with expected value"); program.AppendCommentToProgramLine(0x8B5B, "==> return to TEST_SCREEN_TEST_FAILED"); program.AppendCommentToProgramLine(0x8B5D, "==> return to TEST_SCREEN_TEST_PASSED"); program.RenameSymbolInProgram("L942E", "MEMPTR_CMN_EXEC_START"); program.AppendCommentToProgramLine(0x94CF, "==> loop back to MEMPTR_CMN_EXEC_LOOP"); program.AppendCommentToProgramLine(0x94CE, "==> jump to MEMPTR_CMN_EXEC_CHECKSUM"); program.PrefixLabelToProgramLine(0x94D1, "MEMPTR_CMN_EXEC_CHECKSUM", true); program.RenameSymbolInProgram("L94E1", "MEMPTR_CMN_EXEC_RET"); program.AppendCommentToProgramLine(0x9429, "Compare Flags checksum with expected value"); program.AppendCommentToProgramLine(0x942B, "==> return to TEST_SCREEN_TEST_FAILED"); program.AppendCommentToProgramLine(0x942D, "==> return to TEST_SCREEN_TEST_PASSED"); program.InsertCommentAboveProgramLine(0x9445, "--- NOPs below will be replaced by MEMPTR_TEST_nn_EXEC (instruction LDIR at address 9435 above) ---"); program.InsertCommentAboveProgramLine(0x9459, "--- End of replaced code ---"); program.AppendCommentToProgramLine(0x94DB, "Display Flags checksum value"); program.RenameSymbolInProgram("L94E2", "MEMPTR_CHECKSUM"); program.RenameSymbolInProgram("L94EE", "MEMPTR_CHECKSUM_LOOP1"); program.RenameSymbolInProgram("L94F0", "MEMPTR_CHECKSUM_LOOP2"); program.RenameSymbolInProgram("L94FB", "MEMPTR_CHECKSUM_CASE1"); program.RenameSymbolInProgram("L94FE", "MEMPTR_CHECKSUM_CASE2"); program.RenameSymbolInProgram("L9500", "MEMPTR_CHECKSUM_LOOP3"); program.RenameSymbolInProgram("L950B", "MEMPTR_CHECKSUM_CASE3"); program.RenameSymbolInProgram("L950C", "MEMPTR_CHECKSUM_CASE4"); program.RenameSymbolInProgram("L9515", "MEMPTR_CHECKSUM_CASE5"); program.RenameSymbolInProgram("L9517", "RESET_FLAGS_CHECKSUM"); program.AppendCommentToProgramLine(0x8B5E, "Used to save and restore accumulator in FLAGS_TEST_CB_EXEC"); program.AppendCommentToProgramLine(0x822A, "Used to restore accumulator in MEMPTR_TEST_COMMON_EXEC"); program.AppendCommentToProgramLine(0x954B, "Not used ?"); program.AppendCommentToProgramLine(0x954C, "Not used ?"); program.PrefixLabelToProgramLine(0x954A, "VAR_FLAGS_CHECKSUM", true); program.ReplaceAddressWithSymbol(0xFFFF, "VAR_MEMPTR_CHECKSUM", true); program.PrefixLabelToProgramLine(0x8226, "VAR_SAVE_HL", true); program.PrefixLabelToProgramLine(0x8228, "VAR_SAVE_SP", true); program.PrefixLabelToProgramLine(0x822A, "VAR_SAVE_A", true); program.PrefixLabelToProgramLine(0x8114, "STRING_MENU_SCREEN", true); program.PrefixLabelToProgramLine(0x81AB, "STRING_END_OF_TESTS", true); program.PrefixLabelToProgramLine(0x81CD, "STRING_FLAGS_TESTS_SCREEN", true); program.PrefixLabelToProgramLine(0x81EA, "STRING_MEMPTR_TESTS_SCREEN", true); program.PrefixLabelToProgramLine(0x8208, "STRING_PASSED_TEST", true); program.PrefixLabelToProgramLine(0x820F, "STRING_FAILED_TEST", true); program.PrefixLabelToProgramLine(0x822B, "TABLE_FLAGS_TESTS", true); program.PrefixLabelToProgramLine(0x8407, "TABLE_MEMPTR_TESTS", true); program.PrefixLabelToProgramLine(0x8B5E, "VAR_SAVE_A2", true); program.PrefixLabelToProgramLine(0x9445, "MEMPTR_CMN_EXEC_LOOP", true); program.PrefixLabelToProgramLine(0x945C, "AREA_MEMPTR_TEST_INSERT", true); AddCommentsInTestsParametersTable(0x822B, spectrumMemory, decoder, program); AddCommentsInTestsParametersTable(0x8407, spectrumMemory, decoder, program); MemoryMap testMemoryMap = new MemoryMap(65536); program.Variables.Clear(); Assembler.CompileProgram(program, 0x8000, testMemoryMap); for (int programAddress = program.BaseAddress; programAddress <= program.MaxAddress; programAddress++) { if (testMemoryMap.MemoryCells[programAddress] != spectrumMemory.MemoryCells[programAddress]) { throw new Exception("Error in decompiled program at address " + programAddress + " : source byte = " + spectrumMemory.MemoryCells[programAddress] + ", decompiled program line + " + ((ProgramLine)spectrumMemory.MemoryCellDescriptions[programAddress].Description).Text + " produced byte = " + testMemoryMap.MemoryCells[programAddress]); } } string htmlProgram = AsmHtmlFormatter.BeautifyAsmCode(fileName, program); }
public static Program GenerateProgram(string sourcePath, Stream machinecodeStream, int programStartAddress, CallTarget[] entryPoints, MemoryMap memoryMap) { // Load machine code in memory int b = -1; int currentAddress = programStartAddress; while((b = machinecodeStream.ReadByte()) >= 0) { memoryMap.MemoryCells[currentAddress] = (byte)b; memoryMap.MemoryCellDescriptions[currentAddress] = null; // Reset previous cell descriptions currentAddress++; } int programEndAddress = currentAddress - 1; // Check entry points if (entryPoints == null || entryPoints.Length == 0) { throw new Exception("Entry point adresses are mandatory to try to decompile machine code"); } foreach (CallTarget entryPoint in entryPoints) { if (entryPoint.Address < programStartAddress || entryPoint.Address > programEndAddress) { throw new Exception("Entry point adrress : " + entryPoint.Address + " is out of the range of the program"); } } // Initialize program Program program = new Program(sourcePath, ProgramSource.ObjectCodeBinary); program.BaseAddress = programStartAddress; program.MaxAddress = programEndAddress; memoryMap.Programs.Add(program); program.Lines.Add( Assembler.ParseProgramLine("; **************************************************************************") ); program.Lines.Add( Assembler.ParseProgramLine("; * Decompiled assembly program for : " + sourcePath) ); program.Lines.Add( Assembler.ParseProgramLine("; **************************************************************************") ); program.Lines.Add( Assembler.ParseProgramLine("" ) ); program.Lines.Add( Assembler.ParseProgramLine("ORG " + FormatHexAddress(programStartAddress)) ); program.Lines.Add( Assembler.ParseProgramLine("") ); // Initialize code addresses to explore with entry points received as parameters Queue<CallTarget> callTargetsToExplore = new Queue<CallTarget>(); foreach(CallTarget entryPoint in entryPoints) { callTargetsToExplore.Enqueue(entryPoint); } // The tracing decompiler will follow all the code paths // and it will generate the program lines completely out of order. // We can't add them to the program in the ascending // order of the addresses during the first pass. // So we store them in a temporary map for the second pass. IDictionary<int, ProgramLine> generatedProgramLines = new SortedDictionary<int, ProgramLine>(); // Start from each call target and follow code path while(callTargetsToExplore.Count > 0) { // Dequeue one code address to explore CallTarget entryPoint = callTargetsToExplore.Dequeue(); currentAddress = entryPoint.Address; // If this code address was already explored, do nothing if (memoryMap.MemoryCellDescriptions[currentAddress] != null) { continue; } // Check if there is a limit in the number of opcodes to disassemble bool stopDisassemblyAfterMaxNumberOfBytes = entryPoint.Source.CodeRelocationBytesCount > 0; int maxNumberOfBytes = entryPoint.Source.CodeRelocationBytesCount; InstructionFlowBehavior instructionFlowBehavior = 0; do { // Check if instruction start address stays in the boundaries of the current program if (currentAddress < programStartAddress || currentAddress > programEndAddress) { throw new Exception("Entry point address : " + currentAddress + " is out of the range of the program"); } // Check if current address was already explored if (memoryMap.MemoryCellDescriptions[currentAddress] != null) { break; } // Check if the maximum number of bytes to disassemble after the current entry point was reached if (stopDisassemblyAfterMaxNumberOfBytes && (currentAddress - entryPoint.Address) >= maxNumberOfBytes) { break; } // Read one instruction code int instructionStartAddress = currentAddress; byte displacement; InstructionCode instructionCode = ReadOneInstructionCode(memoryMap, ref currentAddress, out displacement); // Check that instruction end address stays in the boundaries of the current program int instructionEndAddress = instructionStartAddress + instructionCode.OpCodeByteCount + instructionCode.OperandsByteCount - 1; if (instructionEndAddress > programEndAddress) { throw new Exception("End of instruction : " + instructionCode.InstructionType.OpCodeName + ", at address : " + instructionStartAddress + " is out of the range of the program"); } // Read instruction code operands byte operandByte1 = 0; byte operandByte2 = 0; if (instructionCode.OpCodeByteCount == 3) { operandByte1 = displacement; } else { if (instructionCode.OperandsByteCount >= 1) { operandByte1 = memoryMap.MemoryCells[currentAddress++]; } if (instructionCode.OperandsByteCount == 2) { operandByte2 = memoryMap.MemoryCells[currentAddress++]; } } // Generate assembly text for the current instruction string instructionLineText = GenerateInstructionLineText(instructionCode, operandByte1, operandByte2); // Create a new program line from its textual representation ProgramLine programLine = Assembler.ParseProgramLine(instructionLineText); // Register the binary representation of the program line programLine.LineAddress = instructionStartAddress; programLine.InstructionCode = instructionCode; programLine.InstructionType= instructionCode.InstructionType; programLine.OperandByte1 = operandByte1; programLine.OperandByte2 = operandByte2; // Store the generated program line in the temporay map generatedProgramLines.Add(instructionStartAddress, programLine); // Register the signification of all the bytes of the instruction in the memory map MemoryCellDescription instructionDescription = new MemoryCellDescription(MemoryDescriptionType.ProgramLine, programLine); for (int instructionAddress = instructionStartAddress; instructionAddress <= instructionEndAddress; instructionAddress++) { memoryMap.MemoryCellDescriptions[instructionAddress] = instructionDescription; } // Analyse instruction effect on the program flow // - check if the program flow continues to the next line // - check if the current program line can jump elswhere (either always or depending on a condition) // => register the outgoing call address in the program line instructionFlowBehavior = ProgramFlowAnalyzer.GenerateOutgoingCall(programLine, null); if(programLine.OutgoingCall != null) { // If target address was not explored before, add a new entry point to the list if (memoryMap.MemoryCellDescriptions[programLine.OutgoingCall.Address] == null) { callTargetsToExplore.Enqueue(programLine.OutgoingCall); } } } // Continue to next instruction if the current instruction enables it while ((instructionFlowBehavior & InstructionFlowBehavior.ContinueToNextInstruction) > 0); } // Find all the outgoing calls and register them as incoming calls on their target lines ProgramFlowAnalyzer.GenerateIncomingCalls(generatedProgramLines.Values, memoryMap); // To enhance the readability of the generated program // Find all the incoming calls : // - generate labels for the target lines // Find all the outgoing calls // - replace address references with labels in the source lines Assembler.GenerateLabelsForIncomingAndOutgoingCalls(generatedProgramLines.Values, program.Variables); // All the memory cells which were not hit during the exploration // of all the code paths must represent data MemoryCellDescription dataDescription = new MemoryCellDescription(MemoryDescriptionType.Data, null); for (int programAddress = programStartAddress; programAddress <= programEndAddress; programAddress++) { if (memoryMap.MemoryCellDescriptions[programAddress] == null) { // Register in the memory map that this address contains data memoryMap.MemoryCellDescriptions[programAddress] = dataDescription; // Generate a program line to insert this byte of data ProgramLine dataDirectiveLine = Assembler.ParseProgramLine("DEFB " + String.Format("{0:X2}H", memoryMap.MemoryCells[programAddress])); // Register the binary representation of the program line dataDirectiveLine.LineAddress = programAddress; dataDirectiveLine.MemoryBytes = new byte[] { memoryMap.MemoryCells[programAddress] }; // Store the generated program line in the temporay map generatedProgramLines.Add(programAddress, dataDirectiveLine); } } // Add all generated program lines in the ascending order of their addresses foreach (ProgramLine programLine in generatedProgramLines.Values) { programLine.LineNumber = program.Lines.Count + 1; program.Lines.Add(programLine); } // Try to compile the generated program and check that it produces the right machine code output // NB : !! remove this step when the decompiler is well tested and mature !! MemoryMap generatedProgramMachineCode = new MemoryMap(memoryMap.MemoryCells.Length); Assembler.CompileProgram(program, programStartAddress, generatedProgramMachineCode); for (int programAddress = programStartAddress; programAddress <= programEndAddress; programAddress++) { if (generatedProgramMachineCode.MemoryCells[programAddress] != memoryMap.MemoryCells[programAddress]) { throw new Exception("Error in decompiled program at address " + programAddress + " : source byte = " + memoryMap.MemoryCells[programAddress] + ", decompiled program line + " + ((ProgramLine)memoryMap.MemoryCellDescriptions[programAddress].Description).Text + " produced byte = " + generatedProgramMachineCode.MemoryCells[programAddress]); } } return program; }
public static void DecompileZexall2() { string fileName = "zexall2"; TapFile tapFile = TapFileReader.ReadTapFile(PlatformSpecific.GetStreamForProjectFile("TestTapes/" + fileName + ".tap"), fileName); ProgramHeader loaderHeader = (ProgramHeader)tapFile.DataBlocks[0]; int programLength = loaderHeader.ProgramLength; int variablesLength = loaderHeader.FollowingDataLength - loaderHeader.ProgramLength; TapDataBlock loaderBlock = tapFile.DataBlocks[1]; MemoryStream binaryMemoryStream = new MemoryStream(loaderBlock.TapeData); /* Read flag */ binaryMemoryStream.ReadByte(); BasicProgram basicLoader = BasicReader.ReadMemoryFormat(fileName, binaryMemoryStream, programLength, variablesLength); ByteArrayHeader codeHeader = (ByteArrayHeader)tapFile.DataBlocks[2]; int dataLength = codeHeader.DataLength; TapDataBlock codeBlock = tapFile.DataBlocks[3]; binaryMemoryStream = new MemoryStream(codeBlock.TapeData); /* Read flag */ binaryMemoryStream.ReadByte(); int baseAddress = codeHeader.StartAddress; int startAddress = codeHeader.StartAddress; SpectrumMemoryMap spectrumMemory = new SpectrumMemoryMap(); // Load machine code in memory to generate entry points BEFORE dissassembly int b = -1; int currentAddress = baseAddress; long streamPositionBefore = binaryMemoryStream.Position; while ((b = binaryMemoryStream.ReadByte()) >= 0) { spectrumMemory.MemoryCells[currentAddress] = (byte)b; spectrumMemory.MemoryCellDescriptions[currentAddress] = null; // Reset previous cell descriptions currentAddress++; } binaryMemoryStream.Position = streamPositionBefore; List<CallTarget> entryPoints = new List<CallTarget>(); entryPoints.Add(new CallTarget(new CallSource(CallSourceType.ExternalEntryPoint, 0, null), startAddress)); Program program = Disassembler.GenerateProgram(fileName, binaryMemoryStream, baseAddress, entryPoints.ToArray(), spectrumMemory); program.Lines.Insert(5, Assembler.ParseProgramLine("PRINT_A EQU 0010H")); program.Lines.Insert(5, Assembler.ParseProgramLine("CHAN_OPEN EQU 1601H")); program.Lines.Insert(5, Assembler.ParseProgramLine("")); program.RenumberLinesAfterLineNumber(0); program.PrefixLabelToProgramLine(0x8000, "MAIN"); program.RenameSymbolInProgram("L8013", "START"); program.PrefixLabelToProgramLine(0x9F69, "MSG1"); program.RenameSymbolInProgram("L9F41", "BDOS"); program.PrefixLabelToProgramLine(0x8043, "TESTS"); program.RenameSymbolInProgram("L802B", "LOOP"); program.RenameSymbolInProgram("L8038", "DONE"); program.RenameSymbolInProgram("L9C4F", "STT"); program.RenameSymbolInProgram("L8040", "STOP"); program.PrefixLabelToProgramLine(0x9F8B, "MSG2"); program.PrefixLabelToProgramLine(0x9ED7, "FLGMSK"); program.PrefixLabelToProgramLine(0x9E49, "COUNTER"); program.RenameSymbolInProgram("L9DB8", "INITMASK"); program.PrefixLabelToProgramLine(0x9E71, "SHIFTER"); program.PrefixLabelToProgramLine(0x9EB3, "IUT"); program.PrefixLabelToProgramLine(0x8003, "MSBT"); program.RenameSymbolInProgram("L9FF4", "INITCRC"); program.RenameSymbolInProgram("L9C94", "TLP"); program.RenameSymbolInProgram("L9CA8", "TLP1"); program.RenameSymbolInProgram("L9CAB", "TLP2"); program.RenameSymbolInProgram("L9E99", "TEST"); program.RenameSymbolInProgram("L9DF8", "COUNT"); program.RenameSymbolInProgram("L9E1C", "SHIFT"); program.RenameSymbolInProgram("L9CE9", "TLP3"); program.RenameSymbolInProgram("L9FB5", "CMPCRC"); program.PrefixLabelToProgramLine(0x9F9A, "OKMSG"); program.RenameSymbolInProgram("L9CE0", "TLPOK"); program.PrefixLabelToProgramLine(0x9F9F, "ERMSG1"); program.RenameSymbolInProgram("L9F0C", "PHEX8"); program.PrefixLabelToProgramLine(0x9FA7, "ERMSG2"); program.PrefixLabelToProgramLine(0x9FB2, "CRLF"); program.PrefixLabelToProgramLine(0x9D5F, "CNBIT"); program.PrefixLabelToProgramLine(0x9D83, "SHFBIT"); program.PrefixLabelToProgramLine(0x9D60, "CNTBYT"); program.PrefixLabelToProgramLine(0x9D84, "SHBYT"); program.RenameSymbolInProgram("L9D13", "SETUP"); program.RenameSymbolInProgram("L9D1C", "SUBYTE"); program.RenameSymbolInProgram("L9D3D", "SUBSHF"); program.RenameSymbolInProgram("L9D2C", "SUBCLP"); program.RenameSymbolInProgram("L9D62", "NXTCBIT"); program.RenameSymbolInProgram("L9D58", "SUBSTR"); program.RenameSymbolInProgram("L9D49", "SBSHF1"); program.RenameSymbolInProgram("L9D86", "NXTSBIT"); program.RenameSymbolInProgram("L9D7B", "NCB1"); program.RenameSymbolInProgram("L9D9F", "NSB1"); program.RenameSymbolInProgram("L9DA7", "CLRMEM"); program.RenameSymbolInProgram("L9DC7", "IMLP"); program.RenameSymbolInProgram("L9DC8", "IMLP1"); program.RenameSymbolInProgram("L9DCE", "IMLP2"); program.RenameSymbolInProgram("L9DEB", "IMLP3"); program.RenameSymbolInProgram("L9E04", "CNTLP"); program.RenameSymbolInProgram("L9E13", "CNTEND"); program.RenameSymbolInProgram("L9E17", "CNTLP1"); program.RenameSymbolInProgram("L9E28", "SHFLP"); program.RenameSymbolInProgram("L9E3E", "SHFLP2"); program.RenameSymbolInProgram("L9E40", "SHFLPE"); program.RenameSymbolInProgram("L9E44", "SHFLP1"); program.PrefixLabelToProgramLine(0x9F00, "SPSAV"); program.PrefixLabelToProgramLine(0x8011, "SPBT"); program.PrefixLabelToProgramLine(0x9EFE, "SPAT"); program.PrefixLabelToProgramLine(0x9EF0, "MSAT"); program.PrefixLabelToProgramLine(0x9EFC, "FLGSAT"); program.PrefixLabelToProgramLine(0xA008, "CRCVAL"); program.RenameSymbolInProgram("L9EE2", "TCRC"); program.RenameSymbolInProgram("L9FCC", "UPDCRC"); program.PrefixLabelToProgramLine(0x9F04, "HEXSTR"); program.RenameSymbolInProgram("L9F11", "PH8LP"); program.RenameSymbolInProgram("L9F1E", "PHEX2"); program.RenameSymbolInProgram("L9F27", "PHEX1"); program.RenameSymbolInProgram("L9F34", "PH11"); program.RenameSymbolInProgram("L9F54", "PRCHAR"); program.RenameSymbolInProgram("L9F5C", "PRSTRING"); program.RenameSymbolInProgram("L9F4F", "BDOSDONE"); program.RenameSymbolInProgram("L9FBD", "CCLP"); program.RenameSymbolInProgram("L9FC8", "CCE"); program.PrefixLabelToProgramLine(0xA00C, "CRCTAB"); program.RenameSymbolInProgram("L9FE5", "CRCLP"); program.RenameSymbolInProgram("L9FFE", "ICRCLP"); string[] testNames = new string[] { "SCFOP","CCFOP","SCFCCF","CCFSCF","BITA","BITHL","BITX","BITZ80","DAAOP","CPLOP","ADC16","ADD16","ADD16X", "ADD16Y","ALU8I","ALU8R","ALU8RX","ALU8X","CPD1","CPI1","INCA","INCB","INCBC","INCC","INCD","INCDE","INCE","INCH","INCHL","INCIX","INCIY", "INCL","INCM","INCSP","INCX","INCXH","INCXL","INCYH","INCYL","LD161","LD162","LD163","LD164","LD165","LD166","LD167","LD168","LD16IM","LD16IX", "LD8BD","LD8IM","LD8IMX","LD8IX1","LD8IX2","LD8IX3","LD8IXY","LD8RR","LD8RRX","LDA","LDD1","LDD2","LDI1","LDI2","NEGOP","RLDOP","ROT8080", "ROTXY","ROTZ80","SRZ80","SRZX","ST8IX1","ST8IX2","ST8IX3","STABD" }; int testTableAddress = 0x8043; foreach(string testName in testNames) { int testAddress = spectrumMemory.MemoryCells[testTableAddress++] + 256 * spectrumMemory.MemoryCells[testTableAddress++]; program.PrefixLabelToProgramLine(testAddress, testName); } MemoryMap testMemoryMap = new MemoryMap(65536); program.Variables.Clear(); Assembler.CompileProgram(program, 0x8000, testMemoryMap); for (int programAddress = program.BaseAddress; programAddress <= program.MaxAddress; programAddress++) { if (testMemoryMap.MemoryCells[programAddress] != spectrumMemory.MemoryCells[programAddress]) { throw new Exception("Error in decompiled program at address " + programAddress + " : source byte = " + spectrumMemory.MemoryCells[programAddress] + ", decompiled program line + " + ((ProgramLine)spectrumMemory.MemoryCellDescriptions[programAddress].Description).Text + " produced byte = " + testMemoryMap.MemoryCells[programAddress]); } } string htmlProgram = AsmHtmlFormatter.BeautifyAsmCode(fileName, program); }
private static void ComputeAndGenerateOperands(Program program, MemoryMap memoryMap, IList<Operand> operands) { foreach (Operand operand in operands) { switch (operand.Type) { // 8 bit unsigned operand case OperandType.Unsigned8: byte uOperand = (byte)operand.Expression.GetValue(program.Variables, operand.Line); if (operand.Line.Type == ProgramLineType.OpCodeInstruction) { memoryMap.MemoryCellDescriptions[operand.Address] = memoryMap.MemoryCellDescriptions[operand.Address - 1]; } memoryMap.MemoryCells[operand.Address] = uOperand; break; // 8 bit signed operand case OperandType.Signed8: sbyte sOperand = (sbyte)operand.Expression.GetValue(program.Variables, operand.Line); if (operand.Line.Type == ProgramLineType.OpCodeInstruction) { memoryMap.MemoryCellDescriptions[operand.Address] = memoryMap.MemoryCellDescriptions[operand.Address - 1]; } memoryMap.MemoryCells[operand.Address] = unchecked((byte)sOperand); break; // 16 bit unsigned operand case OperandType.Unsigned16: ushort lOperand = (ushort)operand.Expression.GetValue(program.Variables, operand.Line); if (operand.Line.Type == ProgramLineType.OpCodeInstruction) { memoryMap.MemoryCellDescriptions[operand.Address] = memoryMap.MemoryCellDescriptions[operand.Address - 1]; } memoryMap.MemoryCells[operand.Address] = (byte)(lOperand & 0xFF); if (operand.Line.Type == ProgramLineType.OpCodeInstruction) { memoryMap.MemoryCellDescriptions[operand.Address + 1] = memoryMap.MemoryCellDescriptions[operand.Address - 1]; } memoryMap.MemoryCells[operand.Address + 1] = (byte)(lOperand >> 8); break; } } }
public static Program GenerateProgram(string sourcePath, Stream machinecodeStream, int programStartAddress, CallTarget[] entryPoints, MemoryMap memoryMap) { // Load machine code in memory int b = -1; int currentAddress = programStartAddress; while ((b = machinecodeStream.ReadByte()) >= 0) { memoryMap.MemoryCells[currentAddress] = (byte)b; memoryMap.MemoryCellDescriptions[currentAddress] = null; // Reset previous cell descriptions currentAddress++; } int programEndAddress = currentAddress - 1; // Check entry points if (entryPoints == null || entryPoints.Length == 0) { throw new Exception("Entry point adresses are mandatory to try to decompile machine code"); } foreach (CallTarget entryPoint in entryPoints) { if (entryPoint.Address < programStartAddress || entryPoint.Address > programEndAddress) { throw new Exception("Entry point adrress : " + entryPoint.Address + " is out of the range of the program"); } } // Initialize program Program program = new Program(sourcePath, ProgramSource.ObjectCodeBinary); program.BaseAddress = programStartAddress; program.MaxAddress = programEndAddress; memoryMap.Programs.Add(program); program.Lines.Add(Assembler.ParseProgramLine("; **************************************************************************")); program.Lines.Add(Assembler.ParseProgramLine("; * Decompiled assembly program for : " + sourcePath)); program.Lines.Add(Assembler.ParseProgramLine("; **************************************************************************")); program.Lines.Add(Assembler.ParseProgramLine("")); program.Lines.Add(Assembler.ParseProgramLine("ORG " + FormatHexAddress(programStartAddress))); program.Lines.Add(Assembler.ParseProgramLine("")); // Initialize code addresses to explore with entry points received as parameters Queue <CallTarget> callTargetsToExplore = new Queue <CallTarget>(); foreach (CallTarget entryPoint in entryPoints) { callTargetsToExplore.Enqueue(entryPoint); } // The tracing decompiler will follow all the code paths // and it will generate the program lines completely out of order. // We can't add them to the program in the ascending // order of the addresses during the first pass. // So we store them in a temporary map for the second pass. IDictionary <int, ProgramLine> generatedProgramLines = new SortedDictionary <int, ProgramLine>(); // Start from each call target and follow code path while (callTargetsToExplore.Count > 0) { // Dequeue one code address to explore CallTarget entryPoint = callTargetsToExplore.Dequeue(); currentAddress = entryPoint.Address; // If this code address was already explored, do nothing if (memoryMap.MemoryCellDescriptions[currentAddress] != null) { continue; } // Check if there is a limit in the number of opcodes to disassemble bool stopDisassemblyAfterMaxNumberOfBytes = entryPoint.Source.CodeRelocationBytesCount > 0; int maxNumberOfBytes = entryPoint.Source.CodeRelocationBytesCount; InstructionFlowBehavior instructionFlowBehavior = 0; do { // Check if instruction start address stays in the boundaries of the current program if (currentAddress < programStartAddress || currentAddress > programEndAddress) { throw new Exception("Entry point address : " + currentAddress + " is out of the range of the program"); } // Check if current address was already explored if (memoryMap.MemoryCellDescriptions[currentAddress] != null) { break; } // Check if the maximum number of bytes to disassemble after the current entry point was reached if (stopDisassemblyAfterMaxNumberOfBytes && (currentAddress - entryPoint.Address) >= maxNumberOfBytes) { break; } // Read one instruction code int instructionStartAddress = currentAddress; byte displacement; InstructionCode instructionCode = ReadOneInstructionCode(memoryMap, ref currentAddress, out displacement); // Check that instruction end address stays in the boundaries of the current program int instructionEndAddress = instructionStartAddress + instructionCode.OpCodeByteCount + instructionCode.OperandsByteCount - 1; if (instructionEndAddress > programEndAddress) { throw new Exception("End of instruction : " + instructionCode.InstructionType.OpCodeName + ", at address : " + instructionStartAddress + " is out of the range of the program"); } // Read instruction code operands byte operandByte1 = 0; byte operandByte2 = 0; if (instructionCode.OpCodeByteCount == 3) { operandByte1 = displacement; } else { if (instructionCode.OperandsByteCount >= 1) { operandByte1 = memoryMap.MemoryCells[currentAddress++]; } if (instructionCode.OperandsByteCount == 2) { operandByte2 = memoryMap.MemoryCells[currentAddress++]; } } // Generate assembly text for the current instruction string instructionLineText = GenerateInstructionLineText(instructionCode, operandByte1, operandByte2); // Create a new program line from its textual representation ProgramLine programLine = Assembler.ParseProgramLine(instructionLineText); // Register the binary representation of the program line programLine.LineAddress = instructionStartAddress; programLine.InstructionCode = instructionCode; programLine.InstructionType = instructionCode.InstructionType; programLine.OperandByte1 = operandByte1; programLine.OperandByte2 = operandByte2; // Store the generated program line in the temporay map generatedProgramLines.Add(instructionStartAddress, programLine); // Register the signification of all the bytes of the instruction in the memory map MemoryCellDescription instructionDescription = new MemoryCellDescription(MemoryDescriptionType.ProgramLine, programLine); for (int instructionAddress = instructionStartAddress; instructionAddress <= instructionEndAddress; instructionAddress++) { memoryMap.MemoryCellDescriptions[instructionAddress] = instructionDescription; } // Analyse instruction effect on the program flow // - check if the program flow continues to the next line // - check if the current program line can jump elswhere (either always or depending on a condition) // => register the outgoing call address in the program line instructionFlowBehavior = ProgramFlowAnalyzer.GenerateOutgoingCall(programLine, null); if (programLine.OutgoingCall != null) { // If target address was not explored before, add a new entry point to the list if (memoryMap.MemoryCellDescriptions[programLine.OutgoingCall.Address] == null) { callTargetsToExplore.Enqueue(programLine.OutgoingCall); } } } // Continue to next instruction if the current instruction enables it while ((instructionFlowBehavior & InstructionFlowBehavior.ContinueToNextInstruction) > 0); } // Find all the outgoing calls and register them as incoming calls on their target lines ProgramFlowAnalyzer.GenerateIncomingCalls(generatedProgramLines.Values, memoryMap); // To enhance the readability of the generated program // Find all the incoming calls : // - generate labels for the target lines // Find all the outgoing calls // - replace address references with labels in the source lines Assembler.GenerateLabelsForIncomingAndOutgoingCalls(generatedProgramLines.Values, program.Variables); // All the memory cells which were not hit during the exploration // of all the code paths must represent data MemoryCellDescription dataDescription = new MemoryCellDescription(MemoryDescriptionType.Data, null); for (int programAddress = programStartAddress; programAddress <= programEndAddress; programAddress++) { if (memoryMap.MemoryCellDescriptions[programAddress] == null) { // Register in the memory map that this address contains data memoryMap.MemoryCellDescriptions[programAddress] = dataDescription; // Generate a program line to insert this byte of data ProgramLine dataDirectiveLine = Assembler.ParseProgramLine("DEFB " + String.Format("{0:X2}H", memoryMap.MemoryCells[programAddress])); // Register the binary representation of the program line dataDirectiveLine.LineAddress = programAddress; dataDirectiveLine.MemoryBytes = new byte[] { memoryMap.MemoryCells[programAddress] }; // Store the generated program line in the temporay map generatedProgramLines.Add(programAddress, dataDirectiveLine); } } // Add all generated program lines in the ascending order of their addresses foreach (ProgramLine programLine in generatedProgramLines.Values) { programLine.LineNumber = program.Lines.Count + 1; program.Lines.Add(programLine); } // Try to compile the generated program and check that it produces the right machine code output // NB : !! remove this step when the decompiler is well tested and mature !! MemoryMap generatedProgramMachineCode = new MemoryMap(memoryMap.MemoryCells.Length); Assembler.CompileProgram(program, programStartAddress, generatedProgramMachineCode); for (int programAddress = programStartAddress; programAddress <= programEndAddress; programAddress++) { if (generatedProgramMachineCode.MemoryCells[programAddress] != memoryMap.MemoryCells[programAddress]) { throw new Exception("Error in decompiled program at address " + programAddress + " : source byte = " + memoryMap.MemoryCells[programAddress] + ", decompiled program line + " + ((ProgramLine)memoryMap.MemoryCellDescriptions[programAddress].Description).Text + " produced byte = " + generatedProgramMachineCode.MemoryCells[programAddress]); } } return(program); }
public Program LoadProgramInMemory(string sourcePath, Stream inputStream, Encoding encoding, bool ignoreCase) { Program program = Assembler.ParseProgram(sourcePath, inputStream, encoding, ignoreCase); MemoryMap programBytes = new MemoryMap(memory.Size); Assembler.CompileProgram(program, 0, programBytes); if (program.ErrorCount > 0) { throw new Exception("Program error at line " + program.LinesWithErrors[0].LineNumber + " : " + program.LinesWithErrors[0].Text); } memory.LoadBytes(programBytes.MemoryCells); programInMemory = program; return program; }
public static void GenerateIncomingCalls(IEnumerable <ProgramLine> programLines, MemoryMap memoryMap) { foreach (ProgramLine instructionLine in programLines) { if (instructionLine.OutgoingCall != null) { // Find the memory description of the call target address int targetAddress = instructionLine.OutgoingCall.Address; MemoryCellDescription memDescription = memoryMap.MemoryCellDescriptions[targetAddress]; // This description should be a instruction line starting at the same address if (memDescription == null || memDescription.Type != MemoryDescriptionType.ProgramLine) { throw new Exception("Outgoing call at address " + targetAddress + " points to an unknown area in memory"); } ProgramLine targetLine = (ProgramLine)memDescription.Description; if (targetLine.Type != ProgramLineType.OpCodeInstruction || targetLine.LineAddress != targetAddress) { throw new Exception("Outgoing call from program line " + instructionLine.Text + " at address " + instructionLine.LineAddress + " points in the middle of the instruction " + targetLine.Text + " starting at address " + targetLine.LineAddress + " in memory"); } else { // Connect the source and the target instructionLine.OutgoingCall.Line = targetLine; if (targetLine.IncomingCalls == null) { targetLine.IncomingCalls = new List <CallSource>(); } targetLine.IncomingCalls.Add(instructionLine.OutgoingCall.Source); } } } }
public ProgramInMemory LoadProgramInMemory(string sourcePath, Stream inputStream, Encoding encoding, bool ignoreCase, ushort baseAddress, int memorySize) { Program program = Assembler.ParseProgram(sourcePath, inputStream, encoding, ignoreCase); MemoryMap programBytes = new MemoryMap(memorySize); Assembler.CompileProgram(program, 0, programBytes); LoadDataInMemory(programBytes.MemoryCells, baseAddress); return new ProgramInMemory() { Program = program, BaseAddress = baseAddress, MemorySize = memorySize }; }
public void Compile(object sender, ExecutedRoutedEventArgs e) { CurrentProgram = Assembler.ParseProgram(currentFileName, new MemoryStream(Encoding.Default.GetBytes(textEditor.Document.Text)), Encoding.Default, true); MemoryMap memoryMap = new MemoryMap(65536); Assembler.CompileProgram(CurrentProgram, 0, memoryMap); DisplayErrors(CurrentProgram.LinesWithErrors); }