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 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 InstructionFlowBehavior GenerateOutgoingCall(ProgramLine programLine, IDictionary<string, NumberExpression> programVariables) { if (programLine.Type == ProgramLineType.OpCodeInstruction) { // Analyse instruction effect on the program flow InstructionFlowBehavior instructionFlowBehavior = ProgramFlowAnalyzer.GetInstructionFlowBehavior(programLine.InstructionCode.InstructionType); // - check if the program flow continues to the next line programLine.ContinueToNextLine = (instructionFlowBehavior & InstructionFlowBehavior.ContinueToNextInstruction) > 0; // - check if the current program line can jump elswhere (either always or depending on a condition) if ((instructionFlowBehavior & InstructionFlowBehavior.JumpToKnownLocation) > 0) { // Register the outgoing call address in the program line CallSourceType callInstructionType; InstructionLineParam targetAddressParameter; AddressingMode targetAddressParameterType; switch (programLine.InstructionType.Index) { // -- Call and jump instructions -- // Instruction_18_CALL_Address PC => stack, PC = Param1 (ODh|ODl) case 18: callInstructionType = CallSourceType.CallInstruction; targetAddressParameter = programLine.OpCodeParameters[0]; targetAddressParameterType = programLine.InstructionType.Param1Type.Value; break; // Instruction_19_CALL_FlagCondition_Address (PC += instruction size) OR (PC => stack, PC = Param2 (ODh|ODl)) case 19: callInstructionType = CallSourceType.CallInstruction; targetAddressParameter = programLine.OpCodeParameters[1]; targetAddressParameterType = programLine.InstructionType.Param2Type.Value; break; // Instruction_36_DJNZ_RelativeDisplacement (PC += instruction size) OR (PC += Param1 (OD)) case 36: callInstructionType = CallSourceType.JumpInstruction; targetAddressParameter = programLine.OpCodeParameters[0]; targetAddressParameterType = programLine.InstructionType.Param1Type.Value; break; // Instruction_54_JP_Address PC = Param1 (ODh|ODl) case 54: callInstructionType = CallSourceType.JumpInstruction; targetAddressParameter = programLine.OpCodeParameters[0]; targetAddressParameterType = programLine.InstructionType.Param1Type.Value; break; // Instruction_56_JP_FlagCondition_Address (PC += instruction size) OR (PC = Param2 (ODh|ODl)) case 56: callInstructionType = CallSourceType.JumpInstruction; targetAddressParameter = programLine.OpCodeParameters[1]; targetAddressParameterType = programLine.InstructionType.Param2Type.Value; break; // Instruction_57_JR_RelativeDisplacement PC += Param1 (OD) case 57: callInstructionType = CallSourceType.JumpInstruction; targetAddressParameter = programLine.OpCodeParameters[0]; targetAddressParameterType = programLine.InstructionType.Param1Type.Value; break; // Instruction_58_JR_FlagCondition_RelativeDisplacement (PC += instruction size) OR (PC += Param2 (OD)) case 58: callInstructionType = CallSourceType.JumpInstruction; targetAddressParameter = programLine.OpCodeParameters[1]; targetAddressParameterType = programLine.InstructionType.Param2Type.Value; break; // Instruction_122_RST_ResetAddress PC => stack, PC = (AddressModifiedPageZero)Param1 case 122: callInstructionType = CallSourceType.CallInstruction; targetAddressParameter = programLine.OpCodeParameters[0]; targetAddressParameterType = programLine.InstructionType.Param1Type.Value; break; default: throw new NotImplementedException("Unexpected instruction type " + programLine.InstructionType.Index); } // Compute target address int targetAddress; if (targetAddressParameterType == AddressingMode.Relative && !(targetAddressParameter.NumberExpression is SymbolOperand)) { targetAddress = programLine.LineAddress + targetAddressParameter.NumberExpression.GetValue(programVariables, programLine) /* + 2 not necessary because the assembler already adjusts when parsing the expression */; } else { targetAddress = targetAddressParameter.NumberExpression.GetValue(programVariables, programLine); } // Register call source and call target CallSource callSource = new CallSource(callInstructionType, programLine.LineAddress, programLine); CallTarget callTarget = new CallTarget(callSource, targetAddress); programLine.OutgoingCall = callTarget; } return instructionFlowBehavior; } else { return 0; } }
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 InstructionFlowBehavior GenerateOutgoingCall(ProgramLine programLine, IDictionary <string, NumberExpression> programVariables) { if (programLine.Type == ProgramLineType.OpCodeInstruction) { // Analyse instruction effect on the program flow InstructionFlowBehavior instructionFlowBehavior = ProgramFlowAnalyzer.GetInstructionFlowBehavior(programLine.InstructionCode.InstructionType); // - check if the program flow continues to the next line programLine.ContinueToNextLine = (instructionFlowBehavior & InstructionFlowBehavior.ContinueToNextInstruction) > 0; // - check if the current program line can jump elswhere (either always or depending on a condition) if ((instructionFlowBehavior & InstructionFlowBehavior.JumpToKnownLocation) > 0) { // Register the outgoing call address in the program line CallSourceType callInstructionType; InstructionLineParam targetAddressParameter; AddressingMode targetAddressParameterType; switch (programLine.InstructionType.Index) { // -- Call and jump instructions -- // Instruction_18_CALL_Address PC => stack, PC = Param1 (ODh|ODl) case 18: callInstructionType = CallSourceType.CallInstruction; targetAddressParameter = programLine.OpCodeParameters[0]; targetAddressParameterType = programLine.InstructionType.Param1Type.Value; break; // Instruction_19_CALL_FlagCondition_Address (PC += instruction size) OR (PC => stack, PC = Param2 (ODh|ODl)) case 19: callInstructionType = CallSourceType.CallInstruction; targetAddressParameter = programLine.OpCodeParameters[1]; targetAddressParameterType = programLine.InstructionType.Param2Type.Value; break; // Instruction_36_DJNZ_RelativeDisplacement (PC += instruction size) OR (PC += Param1 (OD)) case 36: callInstructionType = CallSourceType.JumpInstruction; targetAddressParameter = programLine.OpCodeParameters[0]; targetAddressParameterType = programLine.InstructionType.Param1Type.Value; break; // Instruction_54_JP_Address PC = Param1 (ODh|ODl) case 54: callInstructionType = CallSourceType.JumpInstruction; targetAddressParameter = programLine.OpCodeParameters[0]; targetAddressParameterType = programLine.InstructionType.Param1Type.Value; break; // Instruction_56_JP_FlagCondition_Address (PC += instruction size) OR (PC = Param2 (ODh|ODl)) case 56: callInstructionType = CallSourceType.JumpInstruction; targetAddressParameter = programLine.OpCodeParameters[1]; targetAddressParameterType = programLine.InstructionType.Param2Type.Value; break; // Instruction_57_JR_RelativeDisplacement PC += Param1 (OD) case 57: callInstructionType = CallSourceType.JumpInstruction; targetAddressParameter = programLine.OpCodeParameters[0]; targetAddressParameterType = programLine.InstructionType.Param1Type.Value; break; // Instruction_58_JR_FlagCondition_RelativeDisplacement (PC += instruction size) OR (PC += Param2 (OD)) case 58: callInstructionType = CallSourceType.JumpInstruction; targetAddressParameter = programLine.OpCodeParameters[1]; targetAddressParameterType = programLine.InstructionType.Param2Type.Value; break; // Instruction_122_RST_ResetAddress PC => stack, PC = (AddressModifiedPageZero)Param1 case 122: callInstructionType = CallSourceType.CallInstruction; targetAddressParameter = programLine.OpCodeParameters[0]; targetAddressParameterType = programLine.InstructionType.Param1Type.Value; break; default: throw new NotImplementedException("Unexpected instruction type " + programLine.InstructionType.Index); } // Compute target address int targetAddress; if (targetAddressParameterType == AddressingMode.Relative && !(targetAddressParameter.NumberExpression is SymbolOperand)) { targetAddress = programLine.LineAddress + targetAddressParameter.NumberExpression.GetValue(programVariables, programLine) /* + 2 not necessary because the assembler already adjusts when parsing the expression */; } else { targetAddress = targetAddressParameter.NumberExpression.GetValue(programVariables, programLine); } // Register call source and call target CallSource callSource = new CallSource(callInstructionType, programLine.LineAddress, programLine); CallTarget callTarget = new CallTarget(callSource, targetAddress); programLine.OutgoingCall = callTarget; } return(instructionFlowBehavior); } else { return(0); } }