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;
            }
        }
Exemple #4
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);
            }
        }