Example #1
0
        /// <summary>
        /// Writes the execution data of the given trace file into the given output file.
        /// </summary>
        /// <param name="traceFile">The trace file to be converted.</param>
        /// <param name="outputFileName">The output file name.</param>
        /// <param name="callChainInstructionAddress">The address of the instruction for which a call chain shall be printed (else 0).</param>
        /// <param name="relativeMemoryAddresses">Determines whether allocation block accesses should be converted to their relative offsets.</param>
        public void Dump(TraceFile traceFile, string outputFileName, uint callChainInstructionAddress, bool relativeMemoryAddresses)
        {
            // Make sure that all entries are loaded (the trace file is iterated two times simultaneously)
            traceFile.CacheEntries();

            // Print call chain?
            if (callChainInstructionAddress != 0)
            {
                Program.Log($"Call chain instruction address: 0x{callChainInstructionAddress.ToString("X8")}\n", Program.LogLevel.Debug);
            }

            // Open output file for writing
            TextWriter outputWriter = null;

            using (!string.IsNullOrWhiteSpace(outputFileName) ? outputWriter = new StreamWriter(File.Open(outputFileName, FileMode.Create)) : outputWriter = Console.Out)
            {
                // Run through entries
                Stack <string> callStack       = new Stack <string>();
                int            callLevel       = 0;
                int            entryIndexWidth = (int)Math.Ceiling(Math.Log10(traceFile.EntryCount));
                int            i = 0;
                var            traceFileEntryEncoder = TraceFileDiff.EncodeTraceEntries(traceFile).GetEnumerator();
                foreach (TraceEntry entry in traceFile.Entries)
                {
                    // Get encoding for current entry
                    traceFileEntryEncoder.MoveNext();
                    long encodedEntry = traceFileEntryEncoder.Current;

                    // Print entry index
                    outputWriter.Write($"[{i.ToString().PadLeft(entryIndexWidth, ' ')}] \t");

                    // Add indentation depending on call level
                    outputWriter.Write(new string('\t', callLevel));

                    // Print entry depending on type
                    switch (entry.EntryType)
                    {
                    case TraceEntryTypes.Allocation:
                    {
                        // Print entry
                        AllocationEntry allocationEntry = (AllocationEntry)entry;
                        outputWriter.WriteLine($"Alloc {allocationEntry.Address.ToString("X16")} :: {(allocationEntry.Address + allocationEntry.Size).ToString("X16")}, {allocationEntry.Size} bytes");

                        break;
                    }

                    case TraceEntryTypes.Free:
                    {
                        // Print entry
                        FreeEntry freeEntry = (FreeEntry)entry;
                        outputWriter.WriteLine($"Free {freeEntry.Address}");

                        break;
                    }

                    case TraceEntryTypes.AllocMemoryRead:
                    case TraceEntryTypes.AllocMemoryWrite:
                    case TraceEntryTypes.ImageMemoryRead:
                    case TraceEntryTypes.ImageMemoryWrite:
                    {
                        uint instructionAddr = 0;
                        switch (entry.EntryType)
                        {
                        case TraceEntryTypes.AllocMemoryRead:
                        {
                            // Retrieve function name of executed instruction
                            AllocMemoryReadEntry allocMemoryReadEntry = (AllocMemoryReadEntry)entry;
                            instructionAddr = allocMemoryReadEntry.InstructionAddress;
                            string formattedInstructionAddress = FormatAddressWithSymbolNames(allocMemoryReadEntry.InstructionAddress, allocMemoryReadEntry.ImageId, allocMemoryReadEntry.ImageName);

                            // Get allocation block relative offset from encoded entry
                            uint   addressPart            = (uint)(encodedEntry >> 32);
                            string formattedMemoryAddress = allocMemoryReadEntry.MemoryAddress.ToString("X16");
                            if (relativeMemoryAddresses && addressPart != 0)
                            {
                                formattedMemoryAddress = $"rel:{addressPart.ToString("X")}";
                            }

                            // Print entry
                            outputWriter.WriteLine($"MemRead <0x{allocMemoryReadEntry.InstructionAddress:X8} {formattedInstructionAddress}>  {formattedMemoryAddress}");

                            break;
                        }

                        case TraceEntryTypes.AllocMemoryWrite:
                        {
                            // Retrieve function name of executed instruction
                            AllocMemoryWriteEntry allocMemoryWriteEntry = (AllocMemoryWriteEntry)entry;
                            instructionAddr = allocMemoryWriteEntry.InstructionAddress;
                            string formattedInstructionAddress = FormatAddressWithSymbolNames(allocMemoryWriteEntry.InstructionAddress, allocMemoryWriteEntry.ImageId, allocMemoryWriteEntry.ImageName);

                            // Get allocation block relative offset from encoded entry
                            uint   addressPart            = (uint)(encodedEntry >> 32);
                            string formattedMemoryAddress = allocMemoryWriteEntry.MemoryAddress.ToString("X16");
                            if (relativeMemoryAddresses && addressPart != 0)
                            {
                                formattedMemoryAddress = $"rel:{addressPart.ToString("X")}";
                            }

                            // Print entry
                            outputWriter.WriteLine($"MemWrite <0x{allocMemoryWriteEntry.InstructionAddress:X8} {formattedInstructionAddress}>  {formattedMemoryAddress}");

                            break;
                        }

                        case TraceEntryTypes.ImageMemoryRead:
                        {
                            // Retrieve function name of executed instruction
                            ImageMemoryReadEntry imageMemoryReadEntry = (ImageMemoryReadEntry)entry;
                            instructionAddr = imageMemoryReadEntry.InstructionAddress;
                            string formattedInstructionAddress = FormatAddressWithSymbolNames(imageMemoryReadEntry.InstructionAddress, imageMemoryReadEntry.InstructionImageId, imageMemoryReadEntry.InstructionImageName);

                            // Print entry
                            outputWriter.WriteLine($"ImgRead <0x{imageMemoryReadEntry.InstructionAddress:X8} {formattedInstructionAddress}>  {imageMemoryReadEntry.MemoryImageName}+{imageMemoryReadEntry.MemoryAddress.ToString("X8")}");

                            break;
                        }

                        case TraceEntryTypes.ImageMemoryWrite:
                        {
                            // Retrieve function name of executed instruction
                            ImageMemoryWriteEntry imageMemoryWriteEntry = (ImageMemoryWriteEntry)entry;
                            instructionAddr = imageMemoryWriteEntry.InstructionAddress;
                            string formattedInstructionAddress = FormatAddressWithSymbolNames(imageMemoryWriteEntry.InstructionAddress, imageMemoryWriteEntry.InstructionImageId, imageMemoryWriteEntry.InstructionImageName);

                            // Print entry
                            outputWriter.WriteLine($"ImgWrite <0x{imageMemoryWriteEntry.InstructionAddress:X8} {formattedInstructionAddress}>  {imageMemoryWriteEntry.MemoryImageName}+{imageMemoryWriteEntry.MemoryAddress.ToString("X8")}");

                            break;
                        }
                        }

                        // Output call chain of this instruction?
                        if (instructionAddr == callChainInstructionAddress)
                        {
                            outputWriter.WriteLine($"[{new string('-', entryIndexWidth)}] CALL CHAIN:");
                            foreach (string call in callStack.Reverse())
                            {
                                outputWriter.Write($"{new string(' ', 1 + entryIndexWidth + 1 + 1 + 4)}");
                                outputWriter.WriteLine(call);
                            }
                        }
                        break;
                    }

                    case TraceEntryTypes.Branch:
                    {
                        // Retrieve function names of instructions
                        BranchEntry branchEntry = (BranchEntry)entry;
                        string      formattedSourceInstructionAddress      = FormatAddressWithSymbolNames(branchEntry.SourceInstructionAddress, branchEntry.SourceImageId, branchEntry.SourceImageName);
                        string      formattedDestinationInstructionAddress = FormatAddressWithSymbolNames(branchEntry.DestinationInstructionAddress, branchEntry.DestinationImageId, branchEntry.DestinationImageName);

                        // Output entry and update call level
                        if (branchEntry.BranchType == BranchTypes.Call)
                        {
                            string lg = $"Call <0x{branchEntry.SourceInstructionAddress:X8} {formattedSourceInstructionAddress}>    ->    <0x{branchEntry.DestinationInstructionAddress:X8} {formattedDestinationInstructionAddress}>";
                            outputWriter.WriteLine(lg);
                            callStack.Push(lg);
                            ++callLevel;
                        }
                        else if (branchEntry.BranchType == BranchTypes.Ret)
                        {
                            outputWriter.WriteLine($"Return <0x{branchEntry.SourceInstructionAddress:X8} {formattedSourceInstructionAddress}>    ->    <0x{branchEntry.DestinationInstructionAddress:X8} {formattedDestinationInstructionAddress}>");
                            if (callStack.Count() > 0)
                            {
                                callStack.Pop();
                            }
                            --callLevel;

                            // Check indentation
                            if (callLevel < 0)
                            {
                                // Just output a warning, this was probably caused by trampoline functions and similar constructions
                                callLevel = 0;
                                Program.Log($"Warning: Indentation error in line {i}\n", Program.LogLevel.Warning);
                            }
                        }
                        else if (branchEntry.BranchType == BranchTypes.Jump)
                        {
                            outputWriter.WriteLine($"Jump <0x{branchEntry.SourceInstructionAddress:X8} {formattedSourceInstructionAddress}>    ->    <0x{branchEntry.DestinationInstructionAddress:X8} {formattedDestinationInstructionAddress}> {(branchEntry.Taken ? "" : "not ")}taken");
                        }

                        break;
                    }
                    }

                    // Next entry
                    ++i;
                }
            }
        }
Example #2
0
        /// <summary>
        /// Encodes all entries of the given trace file as 64-bit integers. This implies a loss of information.
        /// The least significant 4 bits of each entry will contain the entry type (directly casted from <see cref="TraceEntryTypes"/>).
        /// </summary>
        /// <param name="traceFile">The trace file to be encoded.</param>
        /// <returns>A list of encoded trace entries.</returns>
        public static IEnumerable <long> EncodeTraceEntries(TraceFile traceFile)
        {
            // Initialize stack memory allocation block
            AllocationData stackMemory = new AllocationData
            {
                AllocationLineNumber = 0,
                FreeLineNumber       = traceFile.EntryCount, // Never freed
                StartAddress         = traceFile.StackPointerMin,
                EndAddress           = traceFile.StackPointerMax
            };

            // Run through entries
            int line = 0;
            SortedList <int, AllocationData> allocs = new SortedList <int, AllocationData>();

            foreach (TraceEntry entry in traceFile.Entries)
            {
                // Encode type right away
                ulong enc = (ulong)entry.EntryType;
                switch (entry.EntryType)
                {
                case TraceEntryTypes.Allocation:
                {
                    // Save allocation data
                    AllocationEntry allocationEntry = (AllocationEntry)entry;
                    allocs[line] = new AllocationData()
                    {
                        AllocationLineNumber = line,
                        FreeLineNumber       = traceFile.EntryCount, // First assume as never freed, is overwritten as soon as a matching free is encountered
                        StartAddress         = allocationEntry.Address,
                        EndAddress           = allocationEntry.Address + allocationEntry.Size
                    };

                    // Encode allocation size
                    enc |= (ulong)allocationEntry.Size << 4;
                    break;
                }

                case TraceEntryTypes.Free:
                {
                    // Find corresponding allocation block to mark as freed
                    FreeEntry freeEntry       = (FreeEntry)entry;
                    var       allocCandidates = allocs.Where(a => a.Key <= line && a.Value.FreeLineNumber >= line && a.Value.StartAddress == freeEntry.Address);
                    if (!allocCandidates.Any())
                    {
                        // TODO Error handling (same problem as in TraceFileComparer)
                        //Debug.WriteLine($"Warning! Processing line {line}: Skipped free() without any matching allocation block.");
                    }
                    else
                    {
                        // Mark block as freed
                        AllocationData allocData = allocCandidates.First().Value;
                        allocData.FreeLineNumber = line;
                    }

                    // Not encoded
                    break;
                }

                case TraceEntryTypes.ImageMemoryRead:
                {
                    // Encode instruction and address offset
                    ImageMemoryReadEntry imageMemoryReadEntry = (ImageMemoryReadEntry)entry;
                    enc |= imageMemoryReadEntry.InstructionAddress << 4;
                    enc |= (ulong)imageMemoryReadEntry.MemoryAddress << 32;
                    break;
                }

                case TraceEntryTypes.ImageMemoryWrite:
                {
                    // Encode instruction and address offset
                    ImageMemoryWriteEntry imageMemoryWriteEntry = (ImageMemoryWriteEntry)entry;
                    enc |= imageMemoryWriteEntry.InstructionAddress << 4;
                    enc |= (ulong)imageMemoryWriteEntry.MemoryAddress << 32;
                    break;
                }

                case TraceEntryTypes.AllocMemoryRead:
                {
                    // Find related allocation block
                    // Stack or heap?
                    AllocMemoryReadEntry allocMemoryReadEntry = (AllocMemoryReadEntry)entry;
                    AllocationData       allocData            = stackMemory;
                    if (allocMemoryReadEntry.MemoryAddress < stackMemory.StartAddress || stackMemory.EndAddress < allocMemoryReadEntry.MemoryAddress)
                    {
                        // Heap?
                        var allocCandidates = allocs.Where(a => a.Key <= line && a.Value.FreeLineNumber >= line && a.Value.StartAddress <= allocMemoryReadEntry.MemoryAddress && allocMemoryReadEntry.MemoryAddress <= a.Value.EndAddress);
                        if (!allocCandidates.Any())
                        {
                            // TODO error handling - caused by missed malloc()
                            //Program.Log($"Error: Could not find allocation block for [{line}] {allocMemoryReadEntry.ToString()}\n", Program.LogLevel.Error);
                            Debug.WriteLine($"Error: Could not find allocation block for [{line}] {allocMemoryReadEntry.ToString()}");
                            break;
                        }
                        else
                        {
                            allocData = allocCandidates.Last().Value;
                        }
                    }

                    // Encode instruction address, the size of the allocation block, and the offset
                    enc |= allocMemoryReadEntry.InstructionAddress << 4;
                    enc |= (allocMemoryReadEntry.MemoryAddress - allocData.StartAddress) << 32;
                    //enc |= (allocData.EndAddress - allocData.StartAddress) << 48; // Only catches sizes less than 64 KB (16 bit address space)
                    break;
                }

                case TraceEntryTypes.AllocMemoryWrite:
                {
                    // Find related allocation block
                    // Stack or heap?
                    AllocMemoryWriteEntry allocMemoryWriteEntry = (AllocMemoryWriteEntry)entry;
                    AllocationData        allocData             = stackMemory;
                    if (allocMemoryWriteEntry.MemoryAddress < stackMemory.StartAddress || stackMemory.EndAddress < allocMemoryWriteEntry.MemoryAddress)
                    {
                        // Heap?
                        var allocCandidates = allocs.Where(a => a.Key <= line && a.Value.FreeLineNumber >= line && a.Value.StartAddress <= allocMemoryWriteEntry.MemoryAddress && allocMemoryWriteEntry.MemoryAddress <= a.Value.EndAddress);
                        if (!allocCandidates.Any())
                        {
                            // TODO error handling - caused by missed malloc()
                            //Program.Log($"Error: Could not find allocation block for [{line}] {allocMemoryWriteEntry.ToString()}\n", Program.LogLevel.Error);
                            Debug.WriteLine($"Error: Could not find allocation block for [{line}] {allocMemoryWriteEntry.ToString()}");
                            break;
                        }
                        else
                        {
                            allocData = allocCandidates.Last().Value;
                        }
                    }

                    // Encode instruction address, the size of the allocation block, and the offset
                    enc |= allocMemoryWriteEntry.InstructionAddress << 4;
                    enc |= (allocMemoryWriteEntry.MemoryAddress - allocData.StartAddress) << 32;
                    //enc |= (allocData.EndAddress - allocData.StartAddress) << 48; // Only catches sizes less than 64 KB (16 bit address space)
                    break;
                }

                case TraceEntryTypes.Branch:
                {
                    // Encode branch source and destination image offsets, and the "taken" flag
                    BranchEntry branchEntry = (BranchEntry)entry;
                    enc |= branchEntry.SourceInstructionAddress << 4;
                    enc |= (ulong)branchEntry.DestinationInstructionAddress << 32;
                    enc |= (ulong)(branchEntry.Taken ? 1 : 0) << 63;
                    break;
                }
                }
                yield return(unchecked ((long)enc));

                ++line;
            }
        }
Example #3
0
        /// <summary>
        /// Reads all entries from the trace file using deferred execution.
        /// </summary>
        /// <returns></returns>
        private IEnumerable <TraceEntry> ReadEntriesLazily()
        {
            // Read trace entries
            _traceFileReader.Position = _headerEndOffset;
            for (int i = 0; i < _traceEntryCount; ++i)
            {
                // Read depending on type
                TraceEntryTypes entryType = (TraceEntryTypes)_traceFileReader.ReadByte();
                switch (entryType)
                {
                case TraceEntryTypes.Allocation:
                {
                    AllocationEntry entry = new AllocationEntry();
                    entry.Size    = _traceFileReader.ReadUInt32();
                    entry.Address = _traceFileReader.ReadUInt64();

                    yield return(entry);

                    break;
                }

                case TraceEntryTypes.Free:
                {
                    FreeEntry entry = new FreeEntry();
                    entry.Address = _traceFileReader.ReadUInt64();

                    yield return(entry);

                    break;
                }

                case TraceEntryTypes.ImageMemoryRead:
                {
                    ImageMemoryReadEntry entry = new ImageMemoryReadEntry();
                    entry.InstructionImageId   = _imageIdLookup[_traceFileReader.ReadByte()];
                    entry.InstructionImageName = _knownImages[entry.InstructionImageId];
                    entry.InstructionAddress   = _traceFileReader.ReadUInt32();
                    entry.MemoryImageId        = _imageIdLookup[_traceFileReader.ReadByte()];
                    entry.MemoryImageName      = _knownImages[entry.MemoryImageId];
                    entry.MemoryAddress        = (uint)(_traceFileReader.ReadUInt32() & _alignmentOperand);

                    yield return(entry);

                    break;
                }

                case TraceEntryTypes.ImageMemoryWrite:
                {
                    ImageMemoryWriteEntry entry = new ImageMemoryWriteEntry();
                    entry.InstructionImageId   = _imageIdLookup[_traceFileReader.ReadByte()];
                    entry.InstructionImageName = _knownImages[entry.InstructionImageId];
                    entry.InstructionAddress   = _traceFileReader.ReadUInt32();
                    entry.MemoryImageId        = _imageIdLookup[_traceFileReader.ReadByte()];
                    entry.MemoryImageName      = _knownImages[entry.MemoryImageId];
                    entry.MemoryAddress        = (uint)(_traceFileReader.ReadUInt32() & _alignmentOperand);

                    yield return(entry);

                    break;
                }

                case TraceEntryTypes.AllocMemoryRead:
                {
                    AllocMemoryReadEntry entry = new AllocMemoryReadEntry();
                    entry.ImageId            = _imageIdLookup[_traceFileReader.ReadByte()];
                    entry.ImageName          = _knownImages[entry.ImageId];
                    entry.InstructionAddress = _traceFileReader.ReadUInt32();
                    entry.MemoryAddress      = _traceFileReader.ReadUInt64() & _alignmentOperand;

                    yield return(entry);

                    break;
                }

                case TraceEntryTypes.AllocMemoryWrite:
                {
                    AllocMemoryWriteEntry entry = new AllocMemoryWriteEntry();
                    entry.ImageId            = _imageIdLookup[_traceFileReader.ReadByte()];
                    entry.ImageName          = _knownImages[entry.ImageId];
                    entry.InstructionAddress = _traceFileReader.ReadUInt32();
                    entry.MemoryAddress      = _traceFileReader.ReadUInt64() & _alignmentOperand;

                    yield return(entry);

                    break;
                }

                case TraceEntryTypes.Branch:
                {
                    BranchEntry entry = new BranchEntry();
                    entry.SourceImageId                 = _imageIdLookup[_traceFileReader.ReadByte()];
                    entry.SourceImageName               = _knownImages[entry.SourceImageId];
                    entry.SourceInstructionAddress      = _traceFileReader.ReadUInt32();
                    entry.DestinationImageId            = _imageIdLookup[_traceFileReader.ReadByte()];
                    entry.DestinationImageName          = _knownImages[entry.DestinationImageId];
                    entry.DestinationInstructionAddress = _traceFileReader.ReadUInt32();
                    entry.Taken      = _traceFileReader.ReadBoolean();
                    entry.BranchType = (BranchTypes)_traceFileReader.ReadByte();

                    yield return(entry);

                    break;
                }
                }
            }
        }
Example #4
0
        /// <summary>
        /// Executes the comparison and returns whether the two traces have identical memory access patterns.
        /// </summary>
        /// <returns></returns>
        public bool Compare()
        {
            // Run linearily through trace entries and compare them
            // If the traces have different length, use the shorter one as reference
            // This should not lead to wrong results: The shorter trace won't just randomly end, instead there has to be some kind of branch mismatch before
            // Keep track of currently allocated datablocks
            AllocationData stackMemory = new AllocationData() // StartAddress = Top of stack, EndAddress = Bottom of stack
            {
                StartAddress1 = TraceFile1.StackPointerMin,
                StartAddress2 = TraceFile2.StackPointerMin,
                EndAddress1   = TraceFile1.StackPointerMax,
                EndAddress2   = TraceFile2.StackPointerMax
            };
            int totalEntries         = Math.Min(TraceFile1.EntryCount, TraceFile2.EntryCount);
            var traceFile1Enumerator = TraceFile1.Entries.GetEnumerator();
            var traceFile2Enumerator = TraceFile2.Entries.GetEnumerator();

            for (int i = 0; i < totalEntries; ++i)
            {
                // Retrieve entries
                traceFile1Enumerator.MoveNext();
                traceFile2Enumerator.MoveNext();
                TraceEntry e1 = traceFile1Enumerator.Current;
                TraceEntry e2 = traceFile2Enumerator.Current;

                // The entries should have the same type
                // If not, something must have gone very wrong: Differing branches are detected, so we should not run into differing instructions
                if (e1.EntryType != e2.EntryType)
                {
                    Result = ComparisonResults.ExecutionFlow_DifferentType;
                    ComparisonErrorLine  = i;
                    ComparisonErrorItems = new Tuple <TraceEntry, TraceEntry>(e1, e2);
                    if (PrintResults)
                    {
                        PrintComparisonResult($"Entries #{i} have different type (probably some bigger error occured?).", false,
                                              $"Trace 1: {e1.ToString()}",
                                              $"Trace 2: {e2.ToString()}");
                    }
                    return(false);
                }

                // Act depending on entry types
                switch (e1.EntryType)
                {
                // Possible leakages:
                // - Different branch targets
                // - In one execution the branch is taken, in the other one not
                case TraceEntryTypes.Branch:
                {
                    // Cast entries
                    BranchEntry branch1 = (BranchEntry)e1;
                    BranchEntry branch2 = (BranchEntry)e2;

                    // Compare targets
                    if (branch1.DestinationImageId != branch2.DestinationImageId || branch1.DestinationInstructionAddress != branch2.DestinationInstructionAddress)
                    {
                        Result = ComparisonResults.ExecutionFlow_DifferentBranchTarget;
                        ComparisonErrorLine  = i;
                        ComparisonErrorItems = new Tuple <TraceEntry, TraceEntry>(e1, e2);
                        if (PrintResults)
                        {
                            PrintComparisonResult($"Entries #{i}: Different branch target: <{branch1.DestinationImageName}+{branch1.DestinationInstructionAddress.ToString("X8")}> vs. <{branch2.DestinationImageName}+{branch2.DestinationInstructionAddress.ToString("X8")}>", false,
                                                  $"Trace 1: {e1.ToString()}",
                                                  $"Trace 2: {e2.ToString()}");
                        }
                        return(false);
                    }

                    // Compare "taken"
                    if (branch1.Taken != branch2.Taken)
                    {
                        if (branch1.Taken)
                        {
                            Result = ComparisonResults.ExecutionFlow_BranchTakenIn1;
                            ComparisonErrorLine  = i;
                            ComparisonErrorItems = new Tuple <TraceEntry, TraceEntry>(e1, e2);
                            if (PrintResults)
                            {
                                PrintComparisonResult($"Entries #{i}: Branch to <{branch1.DestinationImageName}+{branch1.DestinationInstructionAddress.ToString("X8")}> taken in trace 1, but not in trace 2.", false,
                                                      $"Trace 1: {e1.ToString()}",
                                                      $"Trace 2: {e2.ToString()}");
                            }
                        }
                        else
                        {
                            Result = ComparisonResults.ExecutionFlow_BranchTakenIn2;
                            ComparisonErrorLine  = i;
                            ComparisonErrorItems = new Tuple <TraceEntry, TraceEntry>(e1, e2);
                            if (PrintResults)
                            {
                                PrintComparisonResult($"Entries #{i}: Branch to <{branch1.DestinationImageName}+{branch1.DestinationInstructionAddress.ToString("X8")}> not taken in trace 1, but in trace 2.", false,
                                                      $"Trace 1: {e1.ToString()}",
                                                      $"Trace 2: {e2.ToString()}");
                            }
                        }
                        return(false);
                    }

                    // OK
                    break;
                }

                // Store allocation metadata
                // Possible leakages:
                // - The allocation size might differ
                case TraceEntryTypes.Allocation:
                {
                    // Cast entries
                    AllocationEntry alloc1 = (AllocationEntry)e1;
                    AllocationEntry alloc2 = (AllocationEntry)e2;

                    // Compare sizes
                    if (alloc1.Size != alloc2.Size)
                    {
                        Result = ComparisonResults.MemoryAccess_DifferentAllocationSize;
                        ComparisonErrorLine  = i;
                        ComparisonErrorItems = new Tuple <TraceEntry, TraceEntry>(e1, e2);
                        if (PrintResults)
                        {
                            PrintComparisonResult($"Entries #{i}: Allocation size differs: {alloc1.Size} vs. {alloc2.Size}", false,
                                                  $"Trace 1: {e1.ToString()}",
                                                  $"Trace 2: {e2.ToString()}");
                        }
                        return(false);
                    }

                    // Save allocation data
                    _allocs[alloc1.Address] = new AllocationData()
                    {
                        StartAddress1 = alloc1.Address,
                        EndAddress1   = alloc1.Address + alloc1.Size,
                        StartAddress2 = alloc2.Address,
                        EndAddress2   = alloc2.Address + alloc2.Size
                    };

                    // OK
                    break;
                }

                // Remove allocation metadata from list
                case TraceEntryTypes.Free:
                {
                    // Cast entries
                    FreeEntry free1 = (FreeEntry)e1;
                    FreeEntry free2 = (FreeEntry)e2;

                    // Make sure the frees apply to the same datablock
                    if (!_allocs.TryGetValue(free1.Address, out AllocationData freeCandidate))
                    {
                        /*Result = ComparisonResults.MemoryAccess_FreedBlockNotFound;
                         * ComparisonErrorLine = i;
                         * PrintComparisonResult($"Entries #{i}: Cannot find freed allocation block for execution trace 1.", false,
                         *  $"Trace 1: {e1.ToString()}",
                         *  $"Trace 2: {e2.ToString()}");
                         * // TODO This line is triggered in the Alloc/Free prefix, when one malloc() call was missed
                         * return false;*/
                    }
                    else if (freeCandidate.StartAddress2 != free2.Address)
                    {
                        Result = ComparisonResults.MemoryAccess_FreedBlockNotMatching;
                        ComparisonErrorLine  = i;
                        ComparisonErrorItems = new Tuple <TraceEntry, TraceEntry>(e1, e2);
                        if (PrintResults)
                        {
                            PrintComparisonResult($"Entries #{i}: Freed allocation block of execution trace 1 does not match freed block of execution trace 2.", false,
                                                  $"Trace 1: {e1.ToString()}",
                                                  $"Trace 2: {e2.ToString()}");
                        }
                        return(false);
                    }
                    else
                    {
                        // OK, remove allocation block
                        _allocs.Remove(free1.Address);
                    }
                    break;
                }

                // Possible leakages:
                // -> Different access offset in image
                case TraceEntryTypes.ImageMemoryRead:
                {
                    // Cast entries
                    ImageMemoryReadEntry read1 = (ImageMemoryReadEntry)e1;
                    ImageMemoryReadEntry read2 = (ImageMemoryReadEntry)e2;

                    // Compare relative access offsets
                    if (read1.MemoryAddress != read2.MemoryAddress)
                    {
                        Result = ComparisonResults.MemoryAccess_DifferentImageMemoryReadOffset;
                        ComparisonErrorLine  = i;
                        ComparisonErrorItems = new Tuple <TraceEntry, TraceEntry>(e1, e2);
                        if (PrintResults)
                        {
                            PrintComparisonResult($"Entries #{i}: Memory read offsets in image differ: {read1.MemoryImageName}+{read1.MemoryAddress.ToString("X")} vs. {read2.MemoryImageName}+{read2.MemoryAddress.ToString("X")}", false,
                                                  $"Trace 1: {e1.ToString()}",
                                                  $"Trace 2: {e2.ToString()}");
                        }
                        return(false);
                    }

                    // OK
                    break;
                }

                // Possible leakages:
                // -> Different access offset in image
                case TraceEntryTypes.ImageMemoryWrite:
                {
                    // Cast entries
                    ImageMemoryWriteEntry write1 = (ImageMemoryWriteEntry)e1;
                    ImageMemoryWriteEntry write2 = (ImageMemoryWriteEntry)e2;

                    // Compare relative access offsets
                    if (write1.MemoryAddress != write2.MemoryAddress)
                    {
                        Result = ComparisonResults.MemoryAccess_DifferentImageMemoryWriteOffset;
                        ComparisonErrorLine  = i;
                        ComparisonErrorItems = new Tuple <TraceEntry, TraceEntry>(e1, e2);
                        if (PrintResults)
                        {
                            PrintComparisonResult($"Entries #{i}: Memory write offsets in image differ: {write1.MemoryImageName}+{write1.MemoryAddress.ToString("X")} vs. {write2.MemoryImageName}+{write2.MemoryAddress.ToString("X")}", false,
                                                  $"Trace 1: {e1.ToString()}",
                                                  $"Trace 2: {e2.ToString()}");
                        }
                        return(false);
                    }

                    // OK
                    break;
                }

                // Possible leakages:
                // -> Different access offset (after subtracting allocation base addresses)
                case TraceEntryTypes.AllocMemoryRead:
                {
                    // Cast entries
                    AllocMemoryReadEntry read1 = (AllocMemoryReadEntry)e1;
                    AllocMemoryReadEntry read2 = (AllocMemoryReadEntry)e2;

                    // Find related allocation block
                    AllocationData allocCandidate = FindAllocationBlock(read1.MemoryAddress, read2.MemoryAddress);
                    if (allocCandidate == null)
                    {
                        // Stack access?
                        if (stackMemory.StartAddress1 <= read1.MemoryAddress && read1.MemoryAddress <= stackMemory.EndAddress1 &&
                            stackMemory.StartAddress2 <= read2.MemoryAddress && read2.MemoryAddress <= stackMemory.EndAddress2)
                        {
                            allocCandidate = stackMemory;
                        }
                        else
                        {
                            // TODO remove warning, better include the fs/gs segment bounds in the trace
                            if (PrintResults)
                            {
                                PrintComparisonResult($"Entries #{i}: Cannot find common accessed allocation block. Maybe there are segment registers involved?", true,
                                                      $"Trace 1: {read1.ToString()}",
                                                      $"Trace 2: {read2.ToString()}",
                                                      $"Stack: {stackMemory.ToString()}");
                            }
                            break;
                        }
                    }

                    // Calculate and compare relative access offsets
                    ulong offset1 = read1.MemoryAddress - allocCandidate.StartAddress1;
                    ulong offset2 = read2.MemoryAddress - allocCandidate.StartAddress2;
                    if (offset1 != offset2)
                    {
                        Result = ComparisonResults.MemoryAccess_DifferentAllocMemoryReadOffset;
                        ComparisonErrorLine  = i;
                        ComparisonErrorItems = new Tuple <TraceEntry, TraceEntry>(e1, e2);
                        if (PrintResults)
                        {
                            PrintComparisonResult($"Entries #{i}: Memory read offsets in common allocation block differ: +{offset1.ToString("X8")} vs. +{offset2.ToString("X8")}", false,
                                                  $"Trace 1: {e1.ToString()}",
                                                  $"Trace 2: {e2.ToString()}");
                        }
                        return(false);
                    }

                    // OK
                    break;
                }

                // Possible leakages:
                // -> Different access offset (after subtracting allocation/stack pointer base addresses)
                case TraceEntryTypes.AllocMemoryWrite:
                {
                    // Cast entries
                    AllocMemoryWriteEntry write1 = (AllocMemoryWriteEntry)e1;
                    AllocMemoryWriteEntry write2 = (AllocMemoryWriteEntry)e2;

                    // Find related allocation block
                    AllocationData allocCandidate = FindAllocationBlock(write1.MemoryAddress, write2.MemoryAddress);
                    if (allocCandidate == null)
                    {
                        // Stack access?
                        if (stackMemory.StartAddress1 <= write1.MemoryAddress && write1.MemoryAddress <= stackMemory.EndAddress1 &&
                            stackMemory.StartAddress2 <= write2.MemoryAddress && write2.MemoryAddress <= stackMemory.EndAddress2)
                        {
                            allocCandidate = stackMemory;
                        }
                        else
                        {
                            // TODO remove warning, better include the fs/gs segment bounds in the trace
                            if (PrintResults)
                            {
                                PrintComparisonResult($"Entries #{i}: Cannot find common accessed allocation block. Maybe there are segment registers involved?", true,
                                                      $"Trace 1: {write1.ToString()}",
                                                      $"Trace 2: {write2.ToString()}",
                                                      $"Stack: {stackMemory.ToString()}");
                            }
                            break;
                        }
                    }

                    // Calculate and compare relative access offsets
                    ulong offset1 = write1.MemoryAddress - allocCandidate.StartAddress1;
                    ulong offset2 = write2.MemoryAddress - allocCandidate.StartAddress2;
                    if (offset1 != offset2)
                    {
                        Result = ComparisonResults.MemoryAccess_DifferentAllocMemoryWriteOffset;
                        ComparisonErrorLine  = i;
                        ComparisonErrorItems = new Tuple <TraceEntry, TraceEntry>(e1, e2);
                        if (PrintResults)
                        {
                            PrintComparisonResult($"Entries #{i}: Memory write offsets in common allocation block differ: +{offset1.ToString("X8")} vs. +{offset2.ToString("X8")}", false,
                                                  $"Trace 1: {e1.ToString()}",
                                                  $"Trace 2: {e2.ToString()}");
                        }
                        return(false);
                    }

                    // OK
                    break;
                }
                }
            }

            // No differences found
            Result = ComparisonResults.Match;
            return(true);
        }