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