using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Shapes; namespace BlackbirdInterface { public partial class ParallelStacksWindow : Window { private readonly int _pid; private readonly List _threads; private readonly Func>? _historyProvider; private readonly Func? _observationTimeUtcProvider; private readonly Func? _liveCaptureAvailableProvider; private readonly Func>? _threadSnapshotProvider; private readonly Action? _onSnapshotCaptured; private readonly List _rows = new(); private bool _refreshInFlight; private Border? _selectedCard; public ParallelStacksWindow( int pid, IReadOnlyList threads, Func>? historyProvider = null, Func? observationTimeUtcProvider = null, Func? liveCaptureAvailableProvider = null, Func>? threadSnapshotProvider = null, Action? onSnapshotCaptured = null) { InitializeComponent(); WindowThemeHelper.WireThemeAwareTitleBar(this); _pid = pid; _threads = threads.Select(CloneThread).ToList(); _historyProvider = historyProvider; _observationTimeUtcProvider = observationTimeUtcProvider; _liveCaptureAvailableProvider = liveCaptureAvailableProvider; _threadSnapshotProvider = threadSnapshotProvider; _onSnapshotCaptured = onSnapshotCaptured; SubtitleBlock.Text = $"PID {_pid} | {_threads.Count} thread(s)"; Loaded += async (_, __) => await RefreshStacksAsync(); } private async Task RefreshStacksAsync() { if (_refreshInFlight) { return; } _refreshInFlight = true; try { RefreshThreadRowsFromProvider(); SummaryBlock.Text = $"Resolving {_threads.Count} thread stack(s)..."; /* Capture UI-thread values before entering Task.Run — the providers access WPF DependencyProperty getters which must run on the UI thread. */ DateTime capturedObservedUtc = _observationTimeUtcProvider?.Invoke() ?? DateTime.UtcNow; bool capturedLiveCapture = _liveCaptureAvailableProvider?.Invoke() ?? true; List resolved = await Task.Run(() => ResolveRows(capturedObservedUtc, capturedLiveCapture)); _rows.Clear(); _rows.AddRange(resolved); SummaryBlock.Text = $"Resolved {_rows.Count} thread stack(s)"; if (_rows.Count > 0) { RenderStackMap(); SelectRow(_rows[0], _selectedCard); } else { StacksCanvas.Children.Clear(); SelectedTitleBlock.Text = "No thread data"; SelectedMetaBlock.Text = "No stack snapshots could be resolved."; DetailBox.Text = string.Empty; } } finally { _refreshInFlight = false; } } private List ResolveRows(DateTime observedUtc, bool liveCaptureAvailable) { var results = new ParallelStackRow?[_threads.Count]; using var gate = new SemaphoreSlim(4); var tasks = new List(_threads.Count); for (int i = 0; i < _threads.Count; i += 1) { int index = i; tasks.Add(Task.Run( async () => { await gate.WaitAsync().ConfigureAwait(false); try { ThreadUsageRow thread = _threads[index]; IReadOnlyList history = _historyProvider?.Invoke(_pid, thread.Tid, thread.State) ?? Array.Empty(); ThreadStackSessionSnapshot? historical = history.Where(x => x.CapturedAtUtc <= observedUtc) .OrderByDescending(x => x.CapturedAtUtc) .FirstOrDefault(); if (historical != null && (!liveCaptureAvailable || observedUtc < historical.CapturedAtUtc.AddMilliseconds(750))) { results[index] = BuildRow(thread, historical.Clone()); } else { ThreadStackResolveResult result = ThreadStackResolver.Resolve(_pid, thread.Tid, thread.State); if (HasResolvedStackData(result)) { ThreadStackSessionSnapshot snapshot = CreateSnapshot(DateTime.UtcNow, result); _onSnapshotCaptured?.Invoke(_pid, thread.Tid, thread.State, snapshot.Clone()); results[index] = BuildRow(thread, snapshot); } else if (historical != null) { results[index] = BuildRow(thread, historical.Clone()); } else { results[index] = BuildRow(thread, result); } } } finally { gate.Release(); } })); } Task.WaitAll(tasks.ToArray()); return results.OfType().ToList(); } private static bool HasResolvedStackData(ThreadStackResolveResult result) { return result.Frames.Count > 0 || result.StackPointer != 0 || result.TebAddress != 0 || result.ContextSnapshot != null; } private void RefreshThreadRowsFromProvider() { IReadOnlyList? currentRows = _threadSnapshotProvider?.Invoke(); if (currentRows == null || currentRows.Count == 0) { return; } _threads.Clear(); _threads.AddRange(currentRows.Select(CloneThread)); SubtitleBlock.Text = $"PID {_pid} | {_threads.Count} thread(s)"; } private static ThreadStackSessionSnapshot CreateSnapshot(DateTime capturedAtUtc, ThreadStackResolveResult result) { return new ThreadStackSessionSnapshot { CapturedAtUtc = capturedAtUtc, TebAddress = result.TebAddress, StackBase = result.StackBase, StackTop = result.StackTop, TebFlags = result.TebFlags, StackPointer = result.StackPointer, ContextSnapshot = result.ContextSnapshot == null ? null : new ThreadStackSessionSnapshot { ContextSnapshot = result.ContextSnapshot } .Clone() .ContextSnapshot, Frames = result.Frames.Select(CloneFrame).ToList() }; } private static StackFrameRow CloneFrame(StackFrameRow frame) { return new StackFrameRow { Index = frame.Index, Address = frame.Address, Module = frame.Module, Symbol = frame.Symbol, InstructionPointerRaw = frame.InstructionPointerRaw, FramePointerRaw = frame.FramePointerRaw, FrameSpanBytes = frame.FrameSpanBytes, IsCurrent = frame.IsCurrent }; } private void RenderStackMap() { StacksCanvas.Children.Clear(); _selectedCard = null; const double cardWidth = 280; const double cardHeight = 220; const double spacingX = 318; const double spacingY = 248; int columns = Math.Max(1, (int)Math.Ceiling(Math.Sqrt(Math.Max(1, _rows.Count)))); ParallelStackRow? mainThread = _rows.OrderBy(row => row.StartTimeUtc ?? DateTime.MaxValue).ThenBy(row => row.Tid).FirstOrDefault(); for (int i = 0; i < _rows.Count; i += 1) { ParallelStackRow row = _rows[i]; int col = i % columns; int currentRow = i / columns; double x = 26 + (col * spacingX) + ((currentRow % 2 == 0) ? 0 : 18); double y = 24 + (currentRow * spacingY) + ((col % 2 == 0) ? 0 : 14); row.IsMainThread = ReferenceEquals(row, mainThread); Border card = BuildCard(row, cardWidth, cardHeight); Canvas.SetLeft(card, x); Canvas.SetTop(card, y); row.Card = card; row.Center = new Point(x + (cardWidth / 2.0), y + (cardHeight / 2.0)); } DrawLooseLinks(mainThread); for (int i = 0; i < _rows.Count; i += 1) { StacksCanvas.Children.Add(_rows[i].Card!); } double width = 52 + (Math.Min(columns, Math.Max(1, _rows.Count)) * spacingX); double height = 48 + (((_rows.Count + columns - 1) / columns) * spacingY); StacksCanvas.Width = Math.Max(920, width); StacksCanvas.Height = Math.Max(620, height); } private void DrawLooseLinks(ParallelStackRow? mainThread) { if (_rows.Count <= 1) { return; } for (int i = 1; i < _rows.Count; i += 1) { ParallelStackRow from = _rows[i - 1]; ParallelStackRow to = _rows[i]; AddLooseLink(from.Center, to.Center, highlighted: false); } if (mainThread == null) { return; } for (int i = 0; i < _rows.Count; i += 1) { ParallelStackRow row = _rows[i]; if (ReferenceEquals(row, mainThread)) { continue; } AddLooseLink(mainThread.Center, row.Center, highlighted: true); } } private void AddLooseLink(Point from, Point to, bool highlighted) { double dx = Math.Abs(to.X - from.X); double controlOffset = Math.Max(42, dx * 0.22); var figure = new PathFigure { StartPoint = from, IsClosed = false, IsFilled = false }; figure.Segments.Add(new BezierSegment(new Point(from.X + controlOffset, from.Y), new Point(to.X - controlOffset, to.Y), to, true)); var geometry = new PathGeometry(); geometry.Figures.Add(figure); StacksCanvas.Children.Add( new Path { Data = geometry, Stroke = highlighted ? new SolidColorBrush(Color.FromArgb(0x48, 0xD8, 0xD8, 0xD8)) : new SolidColorBrush(Color.FromArgb(0x22, 0x9A, 0x9A, 0x9A)), StrokeThickness = highlighted ? 1.6 : 1.0, StrokeDashArray = new DoubleCollection { 7, 9 }, SnapsToDevicePixels = true }); } private Border BuildCard(ParallelStackRow row, double width, double height) { var border = new Border { Width = width, Height = height, Padding = new Thickness(10, 8, 10, 8), CornerRadius = new CornerRadius(10), BorderThickness = row.IsMainThread ? new Thickness(2) : new Thickness(1), BorderBrush = row.IsMainThread ? new SolidColorBrush(Color.FromRgb(0xC8, 0xC8, 0xC8)) : new SolidColorBrush(Color.FromRgb(0x46, 0x46, 0x46)), Background = new SolidColorBrush(Color.FromRgb(0x1A, 0x1A, 0x1A)), Cursor = Cursors.Hand, Tag = row, Effect = new System.Windows.Media.Effects.DropShadowEffect { BlurRadius = 18, ShadowDepth = 0, Opacity = 0.32, Color = Color.FromRgb(0x00, 0x00, 0x00) } }; var stack = new StackPanel(); stack.Children.Add(new TextBlock { Text = row.IsMainThread ? $"TID {row.Tid} MAIN" : $"TID {row.Tid}", FontWeight = FontWeights.SemiBold, FontSize = 13, Foreground = Brushes.White }); stack.Children.Add(new TextBlock { Text = row.State, Margin = new Thickness(0, 2, 0, 0), Foreground = new SolidColorBrush(Color.FromRgb(0xB9, 0xC2, 0xCC)) }); stack.Children.Add(new TextBlock { Text = row.Rip, Margin = new Thickness(0, 6, 0, 0), FontFamily = new FontFamily("Cascadia Mono, Cascadia Code, Consolas"), Foreground = new SolidColorBrush(Color.FromRgb(0xD0, 0xD0, 0xD0)) }); stack.Children.Add(new TextBlock { Text = $"{row.FrameCount} frame(s)", Margin = new Thickness(0, 2, 0, 8), Foreground = new SolidColorBrush(Color.FromRgb(0x90, 0x90, 0x90)) }); for (int i = 0; i < row.FrameLines.Count; i += 1) { stack.Children.Add( new TextBlock { Text = row.FrameLines[i], Margin = new Thickness(0, i == 0 ? 0 : 4, 0, 0), TextWrapping = TextWrapping.Wrap, TextTrimming = TextTrimming.CharacterEllipsis, FontFamily = new FontFamily("Cascadia Mono, Cascadia Code, Consolas"), Foreground = i == 0 ? new SolidColorBrush(Color.FromRgb(0xF3, 0xF3, 0xF3)) : new SolidColorBrush(Color.FromRgb(0xB7, 0xB7, 0xB7)) }); } border.Child = stack; border.MouseLeftButtonUp += Card_MouseLeftButtonUp; return border; } private static ParallelStackRow BuildRow(ThreadUsageRow thread, ThreadStackResolveResult result) { StackFrameRow? top = result.Frames.FirstOrDefault(); string rip = top?.Address ?? (result.ContextSnapshot?.Rip is ulong value && value != 0 ? $"0x{value:X}" : "N/A"); string topFrame = top == null ? (string.IsNullOrWhiteSpace(result.Note) ? "No stack frames" : result.Note) : $"{top.Module}!{top.Symbol}"; var detail = new StringBuilder(2048); detail.Append("TID: ").AppendLine(thread.Tid.ToString()); detail.Append("State: ").AppendLine(thread.State); detail.Append("RIP: ").AppendLine(rip); detail.Append("Frames: ").AppendLine(result.Frames.Count.ToString()); if (result.TebAddress != 0) { detail.Append("TEB: 0x").Append(result.TebAddress.ToString("X")).AppendLine(); } if (result.StackBase != 0 || result.StackTop != 0) { detail.Append("Stack: 0x") .Append(result.StackTop.ToString("X")) .Append(" -> 0x") .Append(result.StackBase.ToString("X")) .AppendLine(); } if (!string.IsNullOrWhiteSpace(result.Note)) { detail.Append("Note: ").AppendLine(result.Note); } detail.AppendLine(); if (result.Frames.Count == 0) { detail.AppendLine("No frames resolved."); } else { for (int i = 0; i < result.Frames.Count; i += 1) { StackFrameRow frame = result.Frames[i]; detail.Append('[') .Append(i) .Append("] ") .Append(frame.Address) .Append(" ") .Append(frame.Module) .Append(" ") .AppendLine(frame.Symbol); } } return new ParallelStackRow { Tid = thread.Tid, State = thread.State, StartTimeUtc = thread.StartTimeUtc, Rip = rip, FrameCount = result.Frames.Count, TopFrame = topFrame, FrameLines = result.Frames.Take(6).Select(frame => $"{frame.Module}!{frame.Symbol}").ToList(), DetailText = detail.ToString().TrimEnd() }; } private static ParallelStackRow BuildRow(ThreadUsageRow thread, ThreadStackSessionSnapshot snapshot) { StackFrameRow? top = snapshot.Frames.FirstOrDefault(); string rip = top?.Address ?? (snapshot.ContextSnapshot?.Rip is ulong value && value != 0 ? $"0x{value:X}" : "N/A"); string topFrame = top == null ? "No stack frames" : $"{top.Module}!{top.Symbol}"; var detail = new StringBuilder(2048); detail.Append("TID: ").AppendLine(thread.Tid.ToString()); detail.Append("State: ").AppendLine(thread.State); detail.Append("Captured: ").AppendLine(snapshot.CapturedAtUtc.ToString("HH:mm:ss.fff")); detail.Append("RIP: ").AppendLine(rip); detail.Append("Frames: ").AppendLine(snapshot.Frames.Count.ToString()); if (snapshot.TebAddress != 0) { detail.Append("TEB: 0x").Append(snapshot.TebAddress.ToString("X")).AppendLine(); } if (snapshot.StackBase != 0 || snapshot.StackTop != 0) { detail.Append("Stack: 0x") .Append(snapshot.StackTop.ToString("X")) .Append(" -> 0x") .Append(snapshot.StackBase.ToString("X")) .AppendLine(); } detail.AppendLine(); if (snapshot.Frames.Count == 0) { detail.AppendLine("No frames resolved."); } else { for (int i = 0; i < snapshot.Frames.Count; i += 1) { StackFrameRow frame = snapshot.Frames[i]; detail.Append('[') .Append(i) .Append("] ") .Append(frame.Address) .Append(" ") .Append(frame.Module) .Append(" ") .AppendLine(frame.Symbol); } } return new ParallelStackRow { Tid = thread.Tid, State = thread.State, StartTimeUtc = thread.StartTimeUtc, Rip = rip, FrameCount = snapshot.Frames.Count, TopFrame = topFrame, FrameLines = snapshot.Frames.Take(6).Select(frame => $"{frame.Module}!{frame.Symbol}").ToList(), DetailText = detail.ToString().TrimEnd() }; } private static ThreadUsageRow CloneThread(ThreadUsageRow row) => new(new ThreadUsageSample { Tid = row.Tid, CpuMsDelta = row.CpuMs, State = row.State, WaitReason = row.IsSuspended ? "Suspended" : string.Empty, Kind = row.ThreadKind, StartTimeUtc = row.StartTimeUtc, TargetSuspended = row.IsSuspended }); private async void Refresh_Click(object sender, RoutedEventArgs e) { _ = sender; _ = e; await RefreshStacksAsync(); } private void Close_Click(object sender, RoutedEventArgs e) => Close(); private void Card_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) { _ = sender; _ = e; if (sender is not Border border || border.Tag is not ParallelStackRow row) { return; } SelectRow(row, border); } private void SelectRow(ParallelStackRow row, Border? border) { if (_selectedCard != null) { _selectedCard.BorderBrush = new SolidColorBrush(Color.FromRgb(0x46, 0x46, 0x46)); _selectedCard.BorderThickness = new Thickness(1); } _selectedCard = border ?? row.Card; if (_selectedCard != null) { _selectedCard.BorderBrush = new SolidColorBrush(Color.FromRgb(0x7C, 0xD5, 0xFF)); _selectedCard.BorderThickness = new Thickness(2); } SelectedTitleBlock.Text = $"Thread {row.Tid}"; SelectedMetaBlock.Text = $"{row.State} | RIP {row.Rip} | {row.FrameCount} frame(s)"; DetailBox.Text = row.DetailText; } private sealed class ParallelStackRow { public int Tid { get; init; } public string State { get; init; } = string.Empty; public DateTime? StartTimeUtc { get; init; } public string Rip { get; init; } = string.Empty; public int FrameCount { get; init; } public string TopFrame { get; init; } = string.Empty; public List FrameLines { get; init; } = new(); public string DetailText { get; init; } = string.Empty; public bool IsMainThread { get; set; } public Point Center { get; set; } public Border? Card { get; set; } } } }