/// <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; } }
/// <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); }