private void _renderPanel_MouseMove(object sender, MouseEventArgs e) { // Update rendering offset if left mouse button is pressed if (e.LeftButton == MouseButtonState.Pressed) { // Calculate new scroll offset Point mousePosition = e.GetPosition(_renderPanelContainer); _renderPanelContainer.ScrollToHorizontalOffset(_renderPanelContainer.HorizontalOffset - (mousePosition.X - _mouseClickLocation.X)); _renderPanelContainer.ScrollToVerticalOffset(_renderPanelContainer.VerticalOffset - (mousePosition.Y - _mouseClickLocation.Y)); _mouseClickLocation = mousePosition; // Update render data UpdateRenderedElements(); } else if (_entryArrows != null) { // Get mouse position in render panel coordinate system Point mousePosition = e.GetPosition(_renderPanel); // Run through entry arrow list and test whether one was hit double minimumYValueForMatch = mousePosition.Y - EntryArrow.ArrowTipSideLength; // Lower bound for Y filtering; offset should correspond to the vertical size of one arrow EntryArrow newlyHoveredElement = null; foreach (EntryArrow entryArrow in _entryArrows) { // Check Y coordinate for fast filtering if (entryArrow.PositionY < minimumYValueForMatch) { continue; } // The list is sorted, so we can stop if the desired Y value is exceeded if (entryArrow.PositionY > mousePosition.Y) { break; } // The entry is a candidate, now check X coordinate double left; double right; if (entryArrow.From == entryArrow.To) { // == left = entryArrow.From.CenterXPosition - EntryArrow.ArrowTipSideLength; right = entryArrow.From.CenterXPosition + EntryArrow.ArrowTipSideLength; } else if (entryArrow.From.CenterXPosition < entryArrow.To.CenterXPosition) { // --> left = entryArrow.From.CenterXPosition; right = entryArrow.To.CenterXPosition; } else { // <-- left = entryArrow.To.CenterXPosition; right = entryArrow.From.CenterXPosition; } if (mousePosition.X < left || right < mousePosition.X) { continue; } // Hit! newlyHoveredElement = entryArrow; break; } // Deselect currently hovered element if (_hoveredEntryArrow != null && _hoveredEntryArrow != newlyHoveredElement) { // Deselect currently hovered element _hoveredEntryArrow.Selected = false; _renderPanel.Children.Remove(_hoveredEntryArrow); _renderPanel.Children.Add(_hoveredEntryArrow); _hoveredEntryArrow = null; _debugLabel.Content = ""; } // Show annotation if (newlyHoveredElement != _hoveredEntryArrow) { // Select element _hoveredEntryArrow = newlyHoveredElement; _hoveredEntryArrow.Selected = true; _renderPanel.Children.Remove(_hoveredEntryArrow); _renderPanel.Children.Add(_hoveredEntryArrow); _debugLabel.Content = $"[{newlyHoveredElement.TraceFileEntryIndex}] {newlyHoveredElement.TraceFileEntry.ToString()}"; } // Try to find diff area DiffArea hoveredDiffArea = null; foreach (DiffArea diffArea in _diffAreas) { // Check Y coordinate if (diffArea.PositionY <= mousePosition.Y && mousePosition.Y <= diffArea.PositionY + diffArea.Height) { // Hit hoveredDiffArea = diffArea; break; } } // Show hover text if a diff area was found if (hoveredDiffArea == null) { _resultLabel.Content = ""; } else { _resultLabel.Content = hoveredDiffArea.Description; } } }
private void _loadTraceFileButton_Click(object sender, RoutedEventArgs e) { // Check input first string mapFileName = Properties.Settings.Default.MapFileName; string trace1FileName = Properties.Settings.Default.Trace1FileName; string trace2FileName = Properties.Settings.Default.Trace2FileName; if (string.IsNullOrWhiteSpace(trace2FileName)) { trace2FileName = trace1FileName; } if (!File.Exists(mapFileName) || !File.Exists(trace1FileName) || !File.Exists(trace2FileName)) { MessageBox.Show("File(s) not found.", "Error", MessageBoxButton.OK, MessageBoxImage.Error); return; } // Reset lists possibly containing data from previous loads _imageFileNames.Clear(); _mapFiles.Clear(); // Delete all drawable elements _functionNodes = new List <FunctionNode>(); _functionNodeNameAnnotations = new List <AnnotationElement>(); _entryArrows = new List <EntryArrow>(); _diffAreas = new List <DiffArea>(); _renderPanel.Children.Clear(); // Local function to create function nodes (called in several places) double nextNodeX = 20; FunctionNode CreateFunctionNode(string functionName) { // Create new node FunctionNode node = new FunctionNode(functionName, new Point(nextNodeX, 20)); nextNodeX += node.Width; _functionNodes.Add(node); return(node); }; // Regex for removing extensions from image names Regex imageNameRegex = new Regex("\\.(dll|exe)(\\..*)?", RegexOptions.Compiled | RegexOptions.IgnoreCase); // Load map files string mapFileImageName = System.IO.Path.GetFileNameWithoutExtension(mapFileName).ToLower(); _mapFiles.Add(imageNameRegex.Replace(mapFileImageName, ""), new MapFile(mapFileName)); // Load trace files _traceFile1 = new TraceFile(trace1FileName, _imageFileNames, 1); _traceFile1.CacheEntries(); _traceFile2 = new TraceFile(trace2FileName, _imageFileNames, 1); _traceFile2.CacheEntries(); // Local function to retrieve function metadata for a given address Dictionary <int, string> imageIdMapFileNameMapping = new Dictionary <int, string>(); Dictionary <int, Dictionary <ulong, FunctionNode> > nodes = new Dictionary <int, Dictionary <ulong, FunctionNode> >(); FunctionNode GetFunctionNodeForBranchEntryOperand(int imageId, string imageName, ulong instructionAddress) { // Try to retrieve name of function string cleanedImageName = imageNameRegex.Replace(imageName, ""); if (!imageIdMapFileNameMapping.ContainsKey(imageId)) { // Check whether map file exists if (_mapFiles.ContainsKey(cleanedImageName.ToLower())) { imageIdMapFileNameMapping.Add(imageId, cleanedImageName.ToLower()); } else { imageIdMapFileNameMapping.Add(imageId, null); } nodes.Add(imageId, new Dictionary <ulong, FunctionNode>()); nodes[imageId].Add(0, CreateFunctionNode(cleanedImageName)); } string sourceMapFileName = imageIdMapFileNameMapping[imageId]; ulong sourceInstructionAddress = instructionAddress - 0x1000; // TODO hardcoded -> correct? var(sourceFunctionAddress, sourceFunctionName) = (sourceMapFileName == null ? (0, "Unknown") : _mapFiles[sourceMapFileName].GetSymbolNameByAddress(sourceInstructionAddress)); // Retrieve source function node if (!nodes[imageId].ContainsKey(sourceFunctionAddress)) { nodes[imageId].Add(sourceFunctionAddress, CreateFunctionNode(cleanedImageName + " ! " + sourceFunctionName)); } return(nodes[imageId][sourceFunctionAddress]); }; double CreateEntryArrow(IEnumerator <TraceEntry> traceFileEnumerator, double y, int index, int traceFileId) { // Retrieve entry and check its type traceFileEnumerator.MoveNext(); TraceEntry entry = traceFileEnumerator.Current; if (entry.EntryType != TraceEntryTypes.Branch) { return(0); } // Draw if taken BranchEntry branchEntry = (BranchEntry)entry; if (branchEntry.Taken) { // Get function nodes of first entry FunctionNode sourceFunctionNode1 = GetFunctionNodeForBranchEntryOperand(branchEntry.SourceImageId, branchEntry.SourceImageName, branchEntry.SourceInstructionAddress); FunctionNode destinationFunctionNode1 = GetFunctionNodeForBranchEntryOperand(branchEntry.DestinationImageId, branchEntry.DestinationImageName, branchEntry.DestinationInstructionAddress); // Add line _entryArrows.Add(new EntryArrow(sourceFunctionNode1, destinationFunctionNode1, y, branchEntry, index, traceFileId)); return(EntryArrow.ArrowTipSideLength + 4); } return(0); }; // Compare trace files and create diff TraceFileDiff diff = new TraceFileDiff(_traceFile1, _traceFile2); double nextEntryY = 60; var traceFile1Enumerator = _traceFile1.Entries.GetEnumerator(); var traceFile2Enumerator = _traceFile2.Entries.GetEnumerator(); List <Tuple <double, double, Brush, string> > requestedDiffAreas = new List <Tuple <double, double, Brush, string> >(); foreach (var diffEntry in diff.RunDiff()) { // Save Y offset of first entry double baseY = nextEntryY; // Draw branch entries for trace int diffEntryCount1 = diffEntry.EndLine1 - diffEntry.StartLine1; int diffEntryCount2 = diffEntry.EndLine2 - diffEntry.StartLine2; int commonDiffEntryCount = Math.Min(diffEntryCount1, diffEntryCount2); for (int i = 0; i < commonDiffEntryCount; ++i) { // Draw main entry nextEntryY += CreateEntryArrow(traceFile1Enumerator, nextEntryY, diffEntry.StartLine1 + i, 1); // If there are differences, draw corresponding other entry; move the enumerator anyway if (!diffEntry.Equal) { nextEntryY += CreateEntryArrow(traceFile2Enumerator, nextEntryY, diffEntry.StartLine2 + i, 2); } else { traceFile2Enumerator.MoveNext(); } } // Draw remaining entries from longer sequence if (diffEntryCount1 > commonDiffEntryCount) { for (int i = commonDiffEntryCount; i < diffEntryCount1; ++i) { nextEntryY += CreateEntryArrow(traceFile1Enumerator, nextEntryY, diffEntry.StartLine1 + i, 1); } } else if (diffEntryCount2 > commonDiffEntryCount) { for (int i = commonDiffEntryCount; i < diffEntryCount2; ++i) { nextEntryY += CreateEntryArrow(traceFile2Enumerator, nextEntryY, diffEntry.StartLine2 + i, 2); } } // Differences? => Schedule if (!diffEntry.Equal) { // Make sure this diff area is visible even if it does not contain branch entries requestedDiffAreas.Add(new Tuple <double, double, Brush, string>(baseY - 2, nextEntryY + 2, Brushes.LightPink, $"DIFF A: {diffEntry.StartLine1}-{diffEntry.EndLine1 - 1} vs. B: {diffEntry.StartLine2}-{diffEntry.EndLine2 - 1}")); nextEntryY += 6; } } // Show message if files match if (!requestedDiffAreas.Any()) { MessageBox.Show("The compared files match.", "Trace diff", MessageBoxButton.OK, MessageBoxImage.Information); } // Set draw panel dimensions _renderPanel.Width = nextNodeX; _renderPanel.Height = nextEntryY; // Draw diff areas in the background FunctionNode lastFunctionNode = _functionNodes.LastOrDefault(); if (lastFunctionNode != null) { foreach (var reqDiffArea in requestedDiffAreas) { // Create diff area DiffArea diffArea = new DiffArea(lastFunctionNode.Position.X + lastFunctionNode.Width, reqDiffArea.Item2 - reqDiffArea.Item1, reqDiffArea.Item3, reqDiffArea.Item4, reqDiffArea.Item1); Canvas.SetLeft(diffArea, 0); Canvas.SetTop(diffArea, reqDiffArea.Item1); _diffAreas.Add(diffArea); _renderPanel.Children.Add(diffArea); } } // Draw function nodes above the diff areas foreach (var node in _functionNodes) { // Insert function node node.GenerateVisual(nextEntryY); _renderPanel.Children.Add(node); Canvas.SetLeft(node, node.Position.X); Canvas.SetTop(node, node.Position.Y); // Create annotation node to display function name independent of scrolling // The vertical position will be updated in another place AnnotationElement annotationElement = new AnnotationElement(node.FunctionName); Canvas.SetLeft(annotationElement, node.CenterXPosition - annotationElement.TopLeft.X + 2); _functionNodeNameAnnotations.Add(annotationElement); _renderPanel.Children.Add(annotationElement); } // Finally draw the entry arrows in the foreground foreach (var entry in _entryArrows) { // Prepare coordinates Canvas.SetLeft(entry, entry.From.CenterXPosition); Canvas.SetTop(entry, entry.PositionY); } // Update render data _renderPanel.LayoutTransform = new ScaleTransform(_zoom, _zoom); UpdateRenderedElements(); }