diff --git a/README.md b/README.md
index ac952cf..2bae1f2 100644
--- a/README.md
+++ b/README.md
@@ -29,7 +29,7 @@ The scripts accept these tracing commands: Start, Stop, View, Status, Cancel
Code ↓`
+* Download and unzip [a recent Release of MSO-Scripts](https://github.com/microsoft/MSO-Scripts/releases), or clone the Repository: `<> Code ↓`
* _MSO-Scripts_\\`TraceCPU Start`
_Exercise the application/scenario._
* _MSO-Scripts_\\`TraceCPU Stop`
diff --git a/src/BETA/GetSymbols.bat b/src/BETA/GetSymbols.bat
index bbc7085..e3c7e23 100644
--- a/src/BETA/GetSymbols.bat
+++ b/src/BETA/GetSymbols.bat
@@ -6,6 +6,7 @@ REM Licensed under the MIT License.
REM Download symbols based on those referenced in the given ETW log file (.etl).
REM Optionally limit downloads to the given modules (faster).
REM EnvVar OVerbose=1 : report commands executed
+REM EnvVar WPT_PATH : find XPerf here
setlocal
@@ -37,6 +38,9 @@ call :SetSymDir
if not defined _NT_SYMBOL_PATH set _NT_SYMBOL_PATH=%SYMPATH_DFT%
if not defined _NT_SYMCACHE_PATH set _NT_SYMCACHE_PATH=%SYMCACHE_DFT%
+if defined WPT_PATH set path=%WPT_PATH:"=%;%path%
+if defined OVerbose echo XPERF: & where xperf & echo:
+
REM Older versions of XPerf.exe copy PDB files to the current directory, which will be _SYMDIR.
pushd "%_SYMDIR%"
@@ -50,7 +54,7 @@ echo:
rem XPerf.exe is often adjacent to WPA.exe, or available in the ADK / Windows Performance Toolkit: https://aka.ms/adk
-set _filter=findstr /r "bytes.*SYMSRV.*RESULT..0x00000000"
+set _filter=findstr /r /c:"[0-9] bytes -"
set _filter1=2^>^&1 ^| %_filter%
set _filter2=2^^^>^^^&1 ^^^| %_filter%
@@ -230,4 +234,10 @@ echo Download all referenced modules, or (faster) the modules listed and depende
echo See: https://github.com/microsoft/MSO-Scripts/wiki/Advanced-Symbols#deeper
echo See: https://github.com/microsoft/MSO-Scripts/wiki/Symbol-Resolution#native
echo See: https://learn.microsoft.com/en-us/previous-versions/windows/desktop/xperf/symbols
+echo:
+echo Optional Environment Variables:
+echo _NT_SYMBOL_PATH=cache*%SystemDrive%\Symbols;srv*https://msdl.microsoft.com/download/symbols
+echo _NT_SYMCACHE_PATH=%SystemDrive%\SymCache
+echo WPT_PATH=^ (no quotes)
+echo OVerbose=1 (diagnostic output)
exit /b 1
diff --git a/src/BETA/TraceNetwork.ps1 b/src/BETA/TraceNetwork.ps1
index bad3403..9c21c0d 100644
--- a/src/BETA/TraceNetwork.ps1
+++ b/src/BETA/TraceNetwork.ps1
@@ -377,6 +377,7 @@ Param ( # $ViewerParams 'parameter splat'
# Case 2: Do not proceed.
Write-Err "Found an older version of the Windows Performance Analyzer (WPA): $Version"
+ Write-Err "`"$WpaPath`""
Write-Err "The minimum version for this analysis is: $VersionMinForAddin"
WriteWPTInstallMessage "WPA.exe"
exit 1
@@ -390,9 +391,9 @@ Param ( # $ViewerParams 'parameter splat'
$ExtraParams = $Null
$Processors = $Null
- if (!$Version -or ($Version -ge $VersionForProcessors) -or !$WpaPath.EndsWith('.exe'))
+ if (!$Version -or !(IsRealVersion $Version) -or ($Version -ge $VersionForProcessors))
{
- # Required for WPA.bat or WPA.exe v11.8+ to bypass the New WPA Launcher
+ # Required for WPA.bat or WindowsApps\WPA.exe (stub) or WPA.exe v11.8+ to bypass the New WPA Launcher.
$Processors = GetPluginsList $Version
$ExtraParams += GetArgs -processors $Processors
}
diff --git a/src/INCLUDE.WPA.ps1 b/src/INCLUDE.WPA.ps1
index 71fd993..5cb2c05 100644
--- a/src/INCLUDE.WPA.ps1
+++ b/src/INCLUDE.WPA.ps1
@@ -402,7 +402,7 @@ function LaunchAsStandardUser
$ArgList = $ArgList -replace '(? '2>&1 | findstr /r "bytes.*SYMSRV.*RESULT..0x00000000"'
+ $OutFilter = <# $XPerfCmd #> '2>&1 | findstr /r /c:"[0-9] bytes -"'
$XPerfFiltered = "$XPerfCmd $OutFilter"
$XPerfFilterEnv = "$XPerfCmdEnv $OutFilter"
diff --git a/src/INCLUDE.ps1 b/src/INCLUDE.ps1
index 972376e..ed10022 100644
--- a/src/INCLUDE.ps1
+++ b/src/INCLUDE.ps1
@@ -728,7 +728,9 @@ function HandleChangeThreadMode
{
Write-Err "Windows Performance Recorder (WPR): Internal COM Error"
Write-Warn "This issue usually resolves by restarting Windows."
- Write-Warn "If this occurs frequently, make sure that you have a very recent version of WPR:`n"
+ Write-Warn "If this occurs frequently, make sure that you have a very recent version of WPR."
+ if (!$script:WPR_PreWin10) { Write-Warn "The current version is: $script:WPR_Win10Ver" }
+ Write-Warn 'https://devblogs.microsoft.com/performance-diagnostics/wpr-start-and-stop-commands/#:~:text=0x80010106'
WriteWPTInstallMessage "WPR.exe"
}
@@ -872,6 +874,28 @@ Param (
}
+<#
+ 0x80071069: The instance name passed was not recognized as valid by a WMI data provider.
+#>
+function HandleAbortedTrace
+{
+Param (
+ $WprParams
+)
+ Write-Err (GetCmdVerbose $script:WPR_Path $WprParams)
+
+ Write-Err "`nWPR returned error 0x80071069: The instance name passed was not recognized as valid by a WMI data provider.`n"
+
+ Write-Warn "This sometimes means that there is not enough space on the disk for the .ETL log file."
+ Write-Warn "Intermediate trace files are stored here:"
+ Write-Warn "`"$env:TRACE_PATH`""
+ Write-Warn
+ Write-Warn "Try running 'cleanmgr' or clearing space on the disk."
+ Write-Warn "Or set the TRACE_PATH environment variable to a folder on another drive."
+ Write-Warn "Or simply try again to capture the trace using -Loop : TraceCPU Start -Loop ..."
+}
+
+
<#
Use WMI (deprecated after PSv2) to return the SID of the logged-on user for the current Windows Session.
Return: $Null if failure; $False if it's unchanged from the current context; [string]SID if success.
@@ -1865,17 +1889,19 @@ Param (
if ($TestRun)
{
+ # Skip stray, obviously old installations.
+ $fileVer = GetFileVersion $ExePath
+ if ($fileVer -and ($fileVer.Major -lt [Environment]::OSVersion.Version.Major))
+ {
+ Write-Status "Found old v$fileVer at: $ExePath"
+ return $False
+ }
+
try
{
$Null = & $ExePath -? 2>$Null | Out-Null # silent mode for PSv2+
- if ($?) { $TestRun = $False } # Success!
}
catch
- {
- # Do not flag success.
- }
-
- if ($TestRun)
{
Write-Status "Test run failed: $ExePath"
return $False
@@ -1906,23 +1932,26 @@ Param (
Get the full path of the given executable from the Windows Performance Toolkit: WPA.exe, WPR.exe, etc.
This path can be customized with the WPT_PATH environment variable. ($Env:WPT_PATH may get cleared if it is invalid.)
ISSUE Win10/11+: WPR.exe exists in: $Env:windir\system32\wpr.exe Should this script search for another (newer?) version?
+ Consider Win10 installed with: WPR v10.0.19041 This is subject to: Error 0x80010106
+ https://devblogs.microsoft.com/performance-diagnostics/wpr-start-and-stop-commands/#:~:text=0x80010106
#>
function GetWptExePath
{
Param (
[string]$Exe,
[switch]$Silent,
+ [switch]$Shallow,
[switch]$AltPath,
[switch]$TestRun
)
- # Test the optional environment variable
+ # Test the optional environment variable. (Use the binary found on this path if at all possible.)
# This is set by the user. All others are set by the system or by an installer.
if ($env:WPT_PATH)
{
$WptPath = $env:WPT_PATH.Trim('"').TrimEnd('\')
$WptPath = "$WptPath\$Exe"
- if (TestExePath $WptPath $TestRun ([ref]$AltPath)) { return $WptPath }
+ if (TestExePath $WptPath -TestRun:$False ([ref]$AltPath)) { return $WptPath }
# Sanity check: If $env:WPT_PATH does not exist then reset is to null for this script.
if (!(Test-Path -PathType container -Path $env:WPT_PATH -ErrorAction:SilentlyContinue)) { $env:WPT_PATH = $Null }
@@ -1931,9 +1960,9 @@ Param (
# Test Windows Apps (not executable stubs)
# $Exe should exist in the folder registered under WPA.exe
- $PathReg = $Null
+ $WptPath = $PathReg = $Null
$WpaOpenPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\App Paths\wpa.exe"
- $WptPath = GetRegistryValue $WpaOpenPath "Path"
+ if (!$Shallow) { $WptPath = GetRegistryValue $WpaOpenPath "Path" }
if ($WptPath)
{
$WptPath = $PathReg = "$WptPath\$Exe"
@@ -1970,10 +1999,14 @@ Param (
# App store paths require Admin privilege to walk, unless you land directly on the app folder.
# Look up the path of the app folder(s) via the registry.
+ # This may only work for WPA.
- $WildPath = 'Registry::HKEY_CLASSES_ROOT\Local Settings\Software\Microsoft\Windows\CurrentVersion\AppModel\Repository\Packages\Microsoft.WindowsPerformanceAnalyzer*'
- [string[]] $RegPaths = Get-Item -Path $WildPath -ErrorAction:SilentlyContinue
-
+ [string[]]$RegPaths = $Null
+ if (!$Shallow)
+ {
+ $WildPath = 'Registry::HKEY_CLASSES_ROOT\Local Settings\Software\Microsoft\Windows\CurrentVersion\AppModel\Repository\Packages\Microsoft.WindowsPerformanceAnalyzer*'
+ $RegPaths = Get-Item -Path $WildPath -ErrorAction:SilentlyContinue
+ }
foreach ($RegPath in $RegPaths)
{
$Path = GetRegistryValue "Registry::$RegPath" 'PackageRootFolder'
@@ -1989,11 +2022,10 @@ Param (
# Test the system path, which may include WindowsApps (executable stubs, NO VERSION)
- $PathApps= $Null
$WptCmd = Get-Command -TotalCount 1 -CommandType Application $Exe -ErrorAction:SilentlyContinue
- if ($WptCmd)
+ foreach ($Cmd in $WptCmd)
{
- $WptPath = $PathApps = $WptCmd.Path
+ $WptPath = $PathApps = $Cmd.Path
if (TestExePath $WptPath $TestRun ([ref]$AltPath)) { return $WptPath }
}
@@ -2002,7 +2034,7 @@ Param (
if ($Env:LOCALAPPDATA)
{
$WptPath = "$Env:LOCALAPPDATA\Microsoft\WindowsApps\$Exe"
- if (($WptPath -ne $PathApps) -and (TestExePath $WptPath $TestRun ([ref]$AltPath))) { return $WptPath }
+ if (!($WptCmd -contains $WptPath) -and (TestExePath $WptPath $TestRun ([ref]$AltPath))) { return $WptPath }
}
# Try the Side-by-Side path for WPR
@@ -3211,6 +3243,18 @@ Param (
return [ResultValue]::Error
}
+ 0x80071069 # The instance name passed was not recognized as valid by a WMI data provider.
+ {
+ HandleAbortedTrace $WprParams
+
+ Write-Msg "`nCanceling..."
+ $Null = InvokeWPR -Cancel -InstanceName $InstanceName
+
+ ListRunningProfiles
+
+ return [ResultValue]::Error
+ }
+
default
{
Write-Err $script:WPR_Path @WprParams
diff --git a/src/NetBlame/Auxiliary/CallStack.cs b/src/NetBlame/Auxiliary/CallStack.cs
index b1d6a03..7a51a20 100644
--- a/src/NetBlame/Auxiliary/CallStack.cs
+++ b/src/NetBlame/Auxiliary/CallStack.cs
@@ -1,6 +1,13 @@
// Copyright(c) Microsoft Corporation.
// Licensed under the MIT License.
+/*
+ It is often helpful to add a title to each call stack (even when missing).
+ However, WPA can't aggregate same call stacks when the titles have different PIDs, types, ...
+ Nonetheless, middle stacks have titles as separators.
+*/
+// #define StackTitle
+
using Microsoft.Windows.EventTracing;
using Microsoft.Windows.EventTracing.Processes;
using Microsoft.Windows.EventTracing.Symbols; // IStackDataSource, IStackSnapshot
@@ -48,6 +55,10 @@ Office Idle Manager
namespace NetBlameCustomDataSource.Stack
{
+ static class PID { public const IDVal Unknown = -1; } // Process ID
+ static class TID { public const IDVal Unknown = -1; } // Thrread ID
+ static class PROC { public const IDVal Unknown = -1; } // Processor ID
+
public class MyStackSnapshot : IStackSnapshot
{
public struct Attributes
@@ -62,11 +73,16 @@ public struct Attributes
public readonly IStackSnapshot[] rgStack;
public readonly Attributes[] rgAttrib;
+ // If there is exactly one Stack then rgStack[0] == rgStack[^1].
+ // But that Stack cannot be BOTH the First AND the Last Stack.
+ public bool IsOneStack => rgStack?.Length == 1 && rgStack[0] != null;
+
// The stack which first enqueued the work item, usually on the main thread.
- public IStackSnapshot stackFirst => rgStack?[0];
+ // If there is only one stack then let it be StackLast.
+ public IStackSnapshot StackFirst => IsOneStack ? null : rgStack?[0];
// The stack which actually dispatched the network request, usually via threadpool worker.
- public IStackSnapshot stackLast => rgStack?[^1];
+ public IStackSnapshot StackLast => rgStack?[^1];
public IStackSnapshot[] StackChainExport() => rgStack;
@@ -74,6 +90,14 @@ public struct Attributes
public MyStackSnapshot() { } // no stacks
+ public IDVal TidFirst
+ {
+ get
+ {
+ AssertImportant(this.StackFirst == null || this.StackFirst.ThreadId == this.rgAttrib[0].tidEnqueue);
+ return this.StackFirst?.ThreadId ?? this.rgAttrib?[0].tidEnqueue ?? TID.Unknown;
+ }
+ }
/*
Populate the stack chains and their attributes.
@@ -101,12 +125,12 @@ Each stack contains both the EXECUTE of the previous item and the chained ENQUEU
for (Tasks.TaskItem taskNext = xlink.taskLinkNext; taskNext != null; taskNext = taskNext.xlink.taskLinkNext)
{
depth = taskNext.xlink.depth;
- AssertImportant(depth == (taskNext.xlink.taskLinkNext?.xlink.depth+1 ?? 0)); // Sequential!
+ AssertImportant(depth == (taskNext.xlink.taskLinkNext?.xlink.depth + 1 ?? 0)); // Sequential!
this.rgStack[depth] = taskNext.stack;
this.rgAttrib[depth].tidEnqueue = taskNext.tidCreate;
this.rgAttrib[depth].tidExec = taskNext.tidExec;
- this.rgAttrib[depth+1].type = typeNext;
- this.rgAttrib[depth+1].strSubType = taskNext.Info.SubTypeName;
+ this.rgAttrib[depth + 1].type = typeNext;
+ this.rgAttrib[depth + 1].strSubType = taskNext.Info.SubTypeName;
typeNext = taskNext.xlink.typeNext;
}
AssertImportant(depth == 0);
@@ -114,17 +138,19 @@ Each stack contains both the EXECUTE of the previous item and the chained ENQUEU
AssertImportant(this.rgAttrib[0].type == XLinkType.None);
AssertImportant(this.rgAttrib[0].strSubType == null);
+ AssertImportant(this.rgAttrib[^1].cCut == 0);
AssertImportant(this.rgAttrib[^1].tidEnqueue == 0);
AssertImportant(this.rgAttrib[^1].tidExec == 0);
- AssertImportant(this.rgAttrib[^1].cCut == 0);
- AssertInfo(this.stackFirst != null);
- AssertCritical(this.stackLast == null);
+ this.rgAttrib[^1].tidEnqueue = TID.Unknown;
+ this.rgAttrib[^1].tidExec = TID.Unknown;
+
+ AssertCritical(this.StackLast == null);
// This is the final stack where the network action occurs.
this.rgStack[^1] = stack;
this.rgAttrib[^1].tidExec = tidStack;
- AssertImportant(FImplies(stack != null, tidStack != 0));
+ AssertImportant(FImplies(stack != null, tidStack != TID.Unknown));
// Mark and remove redundancies in the set of stacks.
if (OptimizeStack())
@@ -281,18 +307,18 @@ int DoOptimizeStack(int iFirst, int iLast)
} // OptimizeStack
- // Implement IStackSnapshot on stackLast
- public int ProcessId { get => this.stackLast?.ProcessId ?? 0; }
- public int ThreadId { get => this.stackLast?.ThreadId ?? 0; }
- public TraceTimestamp Timestamp { get => this.stackLast?.Timestamp ?? default; }
- public IProcess Process { get => this.stackLast?.Process ?? default; }
- public IThread Thread { get => this.stackLast?.Thread ?? default; }
- public int Processor { get => this.stackLast?.Processor ?? -1; }
- public IReadOnlyList GetStackFrameTags(IStackTagMapper mapper) => this.stackLast?.GetStackFrameTags(mapper);
- public string GetStackTag(IStackTagMapper mapper) => this.stackLast?.GetStackTag(mapper);
- public string GetStackTagPath(IStackTagMapper mapper) => this.stackLast?.GetStackTagPath(mapper);
- public bool IsIdle => this.stackLast?.IsIdle ?? false;
- public IReadOnlyList Frames => this.stackLast?.Frames;
+ // Implement IStackSnapshot on StackLast
+ public int ProcessId { get => this.StackLast?.ProcessId ?? PID.Unknown; }
+ public int ThreadId { get => this.StackLast?.ThreadId ?? TID.Unknown; }
+ public TraceTimestamp Timestamp { get => this.StackLast?.Timestamp ?? default; }
+ public IProcess Process { get => this.StackLast?.Process ?? default; }
+ public IThread Thread { get => this.StackLast?.Thread ?? default; }
+ public int Processor { get => this.StackLast?.Processor ?? PROC.Unknown; }
+ public IReadOnlyList GetStackFrameTags(IStackTagMapper mapper) => this.StackLast?.GetStackFrameTags(mapper);
+ public string GetStackTag(IStackTagMapper mapper) => this.StackLast?.GetStackTag(mapper);
+ public string GetStackTagPath(IStackTagMapper mapper) => this.StackLast?.GetStackTagPath(mapper);
+ public bool IsIdle => this.StackLast?.IsIdle ?? false;
+ public IReadOnlyList Frames => this.StackLast?.Frames;
}
@@ -305,26 +331,36 @@ public class StackSnapshotAccessProvider : IStackSnapshotAccessProvider
public StackSnapshotAccessProvider(SymLoadProgress _symLoadProgress) { this.symLoadProgress = _symLoadProgress; }
+ public int SymLoadProgress => symLoadProgress?.PctProcessed ?? 0; // 0 = disabled
+
public bool HasUniqueStart => false;
// This is what appears in an otherwise empty column.
public string PastEndValue => strNA; // "N/A"
- public bool IsNull(IStackSnapshot stack) => stack?.Frames == null;
+ protected static bool IsEmpty(MyStackSnapshot stack)
+ {
+ if (CountAll(stack) == 0) return true;
+ return Array.TrueForAll(stack.rgStack, s => s?.Frames == null);
+ }
+
+ public bool IsNull(IStackSnapshot stack) => IsEmpty((MyStackSnapshot)stack);
- public int GetFullCount(IStackSnapshot stack) => stack?.Frames?.Count ?? 0;
+ protected static int CountAll(MyStackSnapshot stack) => stack?.rgStack?.Length ?? 0;
+
+ static int GetFrameCount(IStackSnapshot stack) => stack?.Frames?.Count ?? 0;
// Return the count of stack frames.
// For brevity, trim the final ~three ETW stack frames in ntdll.dll
- public int GetCount(IStackSnapshot stack)
+ protected int _GetCount(IStackSnapshot stack)
{
// Empirically: EtwEventWrite, EtwpEventWriteFull, ZwTraceEvent
const int cFramesTrim = 3;
- int cFrames = GetFullCount(stack);
+ int cFrames = GetFrameCount(stack);
int cTrim = 0;
- int iFrameMax = Math.Min(cFrames - cFramesTrim + 1, cFramesTrim+1);
+ int iFrameMax = Math.Min(cFrames - cFramesTrim + 1, cFramesTrim + 1);
for (int iFrame = 0; iFrame < iFrameMax; ++iFrame)
{
if (!stack.Frames[iFrame].Image?.FileName?.Equals("ntdll.dll") ?? true)
@@ -339,18 +375,66 @@ public int GetCount(IStackSnapshot stack)
return cFrames;
}
- public int GetHashCode(IStackSnapshot stack) => stack?.Hash() ?? 0;
+ public string _GetValue(IStackSnapshot stack, in MyStackSnapshot.Attributes attrib, int index)
+ {
+#if !StackTitle
+ if (stack?.Frames != null)
+ ++index; // title + frames
+#endif
+ if (index > 0)
+ return GetStackFrame(stack, index - 1);
+
+#if StackTitle
+ return ThreadTitle(in attrib, stack?.Frames != null);
+#else
+ return "Missing Stackwalk";
+#endif
+ }
- public bool Equals(IStackSnapshot x, IStackSnapshot y)
+ // Invoked for Last Stack
+ public int GetCount(IStackSnapshot stack)
{
- return x.ThreadId == y.ThreadId && x.Timestamp.Nanoseconds == y.Timestamp.Nanoseconds;
+ MyStackSnapshot myStack = (MyStackSnapshot)stack;
+
+ AssertCritical(myStack?.StackLast == null || Equals(stack, myStack.StackLast));
+
+ if (IsEmpty(myStack)) return 0;
+
+ int count = _GetCount(stack);
+#if StackTitle
+ ++count; // title + frames
+#else
+ if (count == 0)
+ ++count; // title only
+#endif
+ return count;
}
- public int SymLoadProgress => symLoadProgress?.PctProcessed ?? 0;
+ // Invoked for Last Stack
+ public string GetValue(IStackSnapshot stack, int index)
+ {
+ MyStackSnapshot myStack = (MyStackSnapshot)stack;
+
+ AssertCritical(myStack?.StackLast == null || Equals(stack, myStack.StackLast));
+
+ if (IsEmpty(myStack)) return PastEndValue;
+
+ return _GetValue(stack, in ((MyStackSnapshot)stack).rgAttrib[^1], index);
+
+ }
+
+
+ //// vvv Not Invoked vvv
+ public int GetHashCode(IStackSnapshot stack) => stack?.Hash() ?? 0;
+
+ public bool Equals(IStackSnapshot x, IStackSnapshot y) => x.ThreadId == y.ThreadId && x.Timestamp.Nanoseconds == y.Timestamp.Nanoseconds;
+
+ public IStackSnapshot GetParent(IStackSnapshot collection) => throw new NotImplementedException();
+ //// ^^^ Not Invoked ^^^
protected string GetStackFrame(IStackSnapshot stack, int index)
{
- int iFwd = GetFullCount(stack) - index - 1;
+ int iFwd = GetFrameCount(stack) - index - 1;
if (iFwd < 0) { return PastEndValue; }
const string strUnknown = "?";
@@ -374,7 +458,12 @@ protected string GetStackFrame(IStackSnapshot stack, int index)
{
StringBuilder builderPct =
new StringBuilder(module.Length + "! ".Length);
- builderPct.AppendFormat("{0}!", module, pct);
+
+ if (pct != 0)
+ builderPct.AppendFormat("{0}!", module, pct);
+ else
+ builderPct.AppendFormat("{0}!", module, pct);
+
return builderPct.ToString();
}
}
@@ -391,11 +480,8 @@ protected string GetStackFrame(IStackSnapshot stack, int index)
return builder.ToString();
} // GetStackFrame
- public string GetValue(IStackSnapshot stack, int index) => GetStackFrame(stack, index);
- public IStackSnapshot GetParent(IStackSnapshot collection) => throw new NotImplementedException();
-
- protected string ThreadTitle(in MyStackSnapshot.Attributes attrib, TraceTimestamp? timeStamp)
+ protected static string ThreadTitle(in MyStackSnapshot.Attributes attrib, bool fPresent)
{
StringBuilder builder = new StringBuilder(160);
@@ -409,14 +495,18 @@ protected string ThreadTitle(in MyStackSnapshot.Attributes attrib, TraceTimestam
builder.AppendFormat("> Pool Thread: {0} {1}, ", attrib.type.ToString(), attrib.strSubType);
}
- if (attrib.tidEnqueue == 0)
+ string strShow = fPresent ? "below" : "missing stack";
+
+ if (attrib.tidEnqueue == TID.Unknown)
{
- if (attrib.tidExec != 0)
- builder.AppendFormat("TID Exec (below): {0}", attrib.tidExec);
+ if (attrib.tidExec != TID.Unknown)
+ builder.AppendFormat("TID Exec ({1}): {0}", attrib.tidExec, strShow);
+ else
+ builder.AppendFormat(strShow);
}
else
{
- builder.AppendFormat("TID Enqueue (below): {0}, TID Exec (next): {1}", attrib.tidEnqueue, attrib.tidExec);
+ builder.AppendFormat("TID Enqueue ({2}): {0}, TID Exec (next): {1}", attrib.tidEnqueue, attrib.tidExec, strShow);
}
if (attrib.cCut > 0)
@@ -432,7 +522,7 @@ protected string ThreadTitle(in MyStackSnapshot.Attributes attrib, TraceTimestam
*/
public class FirstStackSnapshotAccessProvider : StackSnapshotAccessProvider, IStackSnapshotAccessProvider
{
- public FirstStackSnapshotAccessProvider(SymLoadProgress _symLoadProgress) : base(_symLoadProgress) {}
+ public FirstStackSnapshotAccessProvider(SymLoadProgress _symLoadProgress) : base(_symLoadProgress) { }
#if DEBUG
public new int GetHashCode(IStackSnapshot stack) => throw new NotImplementedException();
@@ -440,33 +530,37 @@ public FirstStackSnapshotAccessProvider(SymLoadProgress _symLoadProgress) : base
public new bool Equals(IStackSnapshot x, IStackSnapshot y) => throw new NotImplementedException();
#endif // DEBUG
- public new bool IsNull(IStackSnapshot stack) => (((MyStackSnapshot)stack)?.rgStack?.Length ?? 0) == 0;
-
public new int GetCount(IStackSnapshot stack)
{
AssertImportant(stack != null);
- if (IsNull(stack)) return 0;
MyStackSnapshot myStack = (MyStackSnapshot)stack;
- if (myStack.stackFirst != null)
- return base.GetCount(myStack.stackFirst);
+ if (IsEmpty(myStack)) return 0;
+
+ if (myStack.IsOneStack)
+ return 1; // "One Stack >"
- return 1; // ThreadTitle
+ int count = _GetCount(myStack.StackFirst);
+#if StackTitle
+ ++count; // title + frames
+#else
+ if (count == 0)
+ ++count; // title only
+#endif
+ return count;
}
public new string GetValue(IStackSnapshot stack, int index)
{
- if (IsNull(stack)) return PastEndValue;
-
MyStackSnapshot myStack = (MyStackSnapshot)stack;
- if (myStack.stackFirst != null)
- return base.GetValue(myStack.stackFirst, index);
+ if (IsEmpty(myStack)) return PastEndValue;
- // Don't leave the First Stack column blank in this case. At least show some data for the thread.
+ if (index == 0 && myStack.IsOneStack)
+ return "One Stack >";
- return base.ThreadTitle(in myStack.rgAttrib[0], null);
+ return _GetValue(myStack.StackFirst, in myStack.rgAttrib[0], index);
}
}
@@ -485,21 +579,24 @@ public MiddleStackSnapshotAccessProvider(SymLoadProgress _symLoadProgress) : bas
#endif // DEBUG
// The middle stacks exclude the first and last, naturally.
- public new bool IsNull(IStackSnapshot stack) => (((MyStackSnapshot)stack)?.rgStack?.Length ?? 0) <= 2;
+ new static bool IsEmpty(MyStackSnapshot stack) => CountAll(stack) < 2 || StackSnapshotAccessProvider.IsEmpty(stack);
+
+ public new bool IsNull(IStackSnapshot stack) => IsEmpty((MyStackSnapshot)stack);
public new int GetCount(IStackSnapshot stack)
{
AssertImportant(stack != null);
- if (IsNull(stack)) return 0;
MyStackSnapshot myStack = (MyStackSnapshot)stack;
+ if (IsEmpty(myStack)) return 0;
+
int cFrames = 0;
- int iStackMax = myStack.rgStack.Length-1;
+ int iStackMax = myStack.rgStack.Length - 1;
for (int iStack = 1; iStack < iStackMax; ++iStack)
{
// Include a title on each stack.
- cFrames += base.GetCount(myStack.rgStack[iStack]) + 1;
+ cFrames += _GetCount(myStack.rgStack[iStack]) + 1;
}
return cFrames;
}
@@ -507,21 +604,22 @@ public MiddleStackSnapshotAccessProvider(SymLoadProgress _symLoadProgress) : bas
public new string GetValue(IStackSnapshot stack, int index)
{
AssertImportant(stack != null);
- if (IsNull(stack)) return PastEndValue;
MyStackSnapshot myStack = (MyStackSnapshot)stack;
- int iStackMax = myStack.rgStack.Length-1;
+ if (IsEmpty(myStack)) return PastEndValue;
+
+ int iStackMax = myStack.rgStack.Length - 1;
for (int iStack = 1; iStack < iStackMax; ++iStack)
{
IStackSnapshot stackM = myStack.rgStack[iStack];
- int cFrames = base.GetCount(stackM) + 1; // plus the "thread title" string
+ int cFrames = _GetCount(stackM) + 1; // plus the "thread title" string
if (index == 0)
- return ThreadTitle(in myStack.rgAttrib[iStack], stackM?.Timestamp);
+ return ThreadTitle(in myStack.rgAttrib[iStack], stackM?.Frames != null);
if (index < cFrames)
- return GetStackFrame(stackM, index-1);
+ return GetStackFrame(stackM, index - 1);
index -= cFrames;
}
@@ -544,20 +642,19 @@ public FullStackSnapshotAccessProvider(SymLoadProgress _symLoadProgress) : base(
public new bool Equals(IStackSnapshot x, IStackSnapshot y) => throw new NotImplementedException();
#endif // DEBUG
- public new bool IsNull(IStackSnapshot stack) => ((MyStackSnapshot)stack)?.rgStack == null;
-
public new int GetCount(IStackSnapshot stack)
{
AssertImportant(stack != null);
- if (IsNull(stack)) return 0;
MyStackSnapshot myStack = (MyStackSnapshot)stack;
+ if (IsEmpty(myStack)) return 0;
+
int cFrames = 0;
foreach (IStackSnapshot stackF in myStack.rgStack)
{
// Include a title on each stack.
- cFrames += base.GetCount(stackF) + 1;
+ cFrames += _GetCount(stackF) + 1;
}
// Omit the title on the top stack.
@@ -570,10 +667,11 @@ public FullStackSnapshotAccessProvider(SymLoadProgress _symLoadProgress) : base(
public new string GetValue(IStackSnapshot stack, int index)
{
AssertImportant(stack != null);
- if (IsNull(stack)) return PastEndValue;
MyStackSnapshot myStack = (MyStackSnapshot)stack;
+ if (IsEmpty(myStack)) return PastEndValue;
+
// Omit the title on the top stack.
if (myStack.rgStack.Length > 0 && myStack.rgStack[0] != null)
++index;
@@ -581,13 +679,13 @@ public FullStackSnapshotAccessProvider(SymLoadProgress _symLoadProgress) : base(
int iStack = 0;
foreach (IStackSnapshot stackF in myStack.rgStack)
{
- int cFrames = base.GetCount(stackF) + 1; // plus the "thread title" string
+ int cFrames = _GetCount(stackF) + 1; // plus the "thread title" string
if (index == 0)
- return ThreadTitle(in myStack.rgAttrib[iStack], stackF?.Timestamp);
+ return ThreadTitle(in myStack.rgAttrib[iStack], stackF?.Frames != null);
if (index < cFrames)
- return GetStackFrame(stackF, index-1);
+ return GetStackFrame(stackF, index - 1);
index -= cFrames;
++iStack;
@@ -595,4 +693,4 @@ public FullStackSnapshotAccessProvider(SymLoadProgress _symLoadProgress) : base(
return PastEndValue;
} // GetValue
} // FullStackSnapshotAccessProvider
-} // namespace NetBlameCustomDataSource.Stack
\ No newline at end of file
+} // namespace NetBlameCustomDataSource.Stack
diff --git a/src/NetBlame/Auxiliary/Extensions.cs b/src/NetBlame/Auxiliary/Extensions.cs
index 76164a0..8a91dab 100644
--- a/src/NetBlame/Auxiliary/Extensions.cs
+++ b/src/NetBlame/Auxiliary/Extensions.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Net;
using Microsoft.Windows.EventTracing.Events; // IGenericEvent
@@ -12,7 +13,7 @@
using TimestampUI = Microsoft.Performance.SDK.Timestamp;
using AddressETW = Microsoft.Windows.EventTracing.Address;
-using AddrVal = System.UInt64; // type of AddressUI.ToBytes
+using AddrVal = System.UInt64;
namespace NetBlameCustomDataSource
{
@@ -47,6 +48,43 @@ public static class Extensions
public static bool Empty(this SocketAddress socket) => (socket == null || socket[0]/*family*/ == 0);
+ // SocketAddress.Equals may give false negatives when the socket contains random padding bytes!
+ public static bool SafeEquals(this SocketAddress socket, SocketAddress comparand)
+ {
+ if (socket == null || comparand == null)
+ return false;
+
+ if (socket.Family != comparand.Family)
+ return false;
+
+ if (socket.Port() != comparand.Port())
+ return false;
+
+ if (socket.Equals(comparand))
+ return true;
+
+ // Expensive!
+ return Util.NewEndPoint(socket).Equals(Util.NewEndPoint(comparand));
+ }
+
+ public static bool IsAddrZero(this SocketAddress socket)
+ {
+#if DEBUG
+ if (socket.Empty()) return true;
+
+ switch (socket.Family)
+ {
+ case System.Net.Sockets.AddressFamily.InterNetwork: // IPv4
+ return Util.NewEndPoint(socket).Address.Equals(IPAddress.Any);
+ case System.Net.Sockets.AddressFamily.InterNetworkV6: // IPv6
+ return Util.NewEndPoint(socket).Address.Equals(IPAddress.IPv6Any);
+ }
+ return false;
+#else
+ throw new NotImplementedException("IsAddrZero: Not intended for release.");
+#endif // DEBUG
+ }
+
public static ushort Port(this SocketAddress socket) => (ushort)((socket[2] << 8) + socket[3]);
public static UInt16 GetUInt16(this IGenericEvent evt, string strField) => evt.Fields[strField].AsUInt16;
diff --git a/src/NetBlame/Auxiliary/GeoLocation.cs b/src/NetBlame/Auxiliary/GeoLocation.cs
index 9c13cd9..f7cd1ba 100644
--- a/src/NetBlame/Auxiliary/GeoLocation.cs
+++ b/src/NetBlame/Auxiliary/GeoLocation.cs
@@ -30,41 +30,6 @@ enum XmlName
};
- /*
- IPv4 CIDR (Classless Inter-Domain Routing)
- https://datatracker.ietf.org/doc/html/rfc1918
- 10.*.*.* // 24-bit block / class A
- 172.16-31.*.* // 20-bit block / class B
- 192.168.*.* // 16-bit block / class C
- */
- static bool IsIPv4CIDR(IPAddress ipAddr)
- {
- if (ipAddr.IsIPv4MappedToIPv6)
- ipAddr = ipAddr.MapToIPv4();
- else if (ipAddr.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork)
- return false;
-
- #pragma warning disable 0618 // obsolete: IPAddress.Address
- uint address = (uint)ipAddr.Address;
- #pragma warning restore 0618
-
- // 10.*.*.*
- if ((byte)address == 10)
- return true;
-
- // 192.168.*.* ie. C0.A8.*.*
- if ((ushort)address == 0xA8C0)
- return true;
-
- // 172.16-31.*.* ie. AC.10-1F.*.* ie. 0x10AC <= address <= 0x1FAC
- if ((byte)address == 0xAC && (ushort)(address - 0x10AC) <= 0x0F00)
- return true;
-
- // none of the above
- return false;
- }
-
-
/*
Return a string representing the geolocation of this IPAddress.
Check first for special ranges representing LocalHost or a Local/Private Network.
@@ -75,14 +40,12 @@ public static string GetGeoLocation(IPAddress ipAddr)
if (ipAddr == null)
return string.Empty;
- if (IPAddress.IsLoopback(ipAddr))
- return DNSClient.DNSTable.strLocalHost;
+ string strAddrType = Util.AddressType(ipAddr);
+ if (strAddrType != null)
+ return strAddrType;
- if (IsIPv4CIDR(ipAddr) || ipAddr.IsIPv6UniqueLocal || ipAddr.IsIPv6SiteLocal || ipAddr.IsIPv6LinkLocal)
- return "Local/Private Network";
-
- if (ipAddr.IsIPv6Multicast)
- return "Multicast";
+ if (ipAddr.IsIPv4MappedToIPv6)
+ ipAddr = ipAddr.MapToIPv4();
string strRequest = strGetXmlService + ipAddr.ToString();
diff --git a/src/NetBlame/Auxiliary/NetUtil.cs b/src/NetBlame/Auxiliary/NetUtil.cs
index 5cbe465..cc055a9 100644
--- a/src/NetBlame/Auxiliary/NetUtil.cs
+++ b/src/NetBlame/Auxiliary/NetUtil.cs
@@ -2,10 +2,11 @@
// Licensed under the MIT License.
using System;
+using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
+using System.Runtime.CompilerServices; // MethodImpl
-using System.Diagnostics;
using Microsoft.Windows.EventTracing.Symbols;
using QWord = System.UInt64;
@@ -79,6 +80,7 @@ public static void BreakWhen(bool c)
public static bool SUCCEEDED(UInt32 err) => (Int32)err >= 0;
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool FImplies(bool a, bool b) => !a || b;
public static uint BitFromI(uint i) => (uint)1 << ((int)i - 1); // i: 1-based
@@ -92,6 +94,9 @@ public static void BreakWhen(bool c)
public static IPEndPoint NewEndPoint(in SocketAddress socket)
{
+ if (socket.Empty())
+ return new IPEndPoint(0, 0);
+
// IPEndPoint.Create throws an exception if (this.AddressFamily != socket.Family)
if (socket.Family == AddressFamily.InterNetworkV6)
@@ -163,6 +168,14 @@ public static IPEndPoint NewEndPoint(in SocketAddress socket)
return new IPEndPoint((Int64)0x2A2A2A2A, (int)socket.Family);
} // NewEndPoint
+ public static IPEndPoint NewEndPoint(in Microsoft.Windows.EventTracing.Events.IGenericEvent evt)
+ {
+ if (evt.GetUInt32("AddressLen") == 0)
+ return new IPEndPoint(0, 0);
+
+ return NewEndPoint(evt.GetSocketAddress());
+ }
+
static public readonly char[] rgchURLSplit = new char[] { ':', '/' };
static public readonly char[] rgchEOLSplit = new char[] { '\r', '\n' };
@@ -286,6 +299,11 @@ static public string ServiceFromPort(int port)
return "AD/SMB";
case 465:
return "SMTPS";
+ case 546:
+ case 547:
+ return "DHCPv6";
+ case 554:
+ return "RTSP";
case 563:
return "NNTPS";
case 636:
@@ -299,7 +317,11 @@ static public string ServiceFromPort(int port)
return "POP3S";
case 1433:
return "MSSQL";
+ case 1900:
+ return "SSDP";
case 2555:
+ case 2869:
+ case 5000:
return "UPnP";
case 3268:
return "LDAP/AD";
@@ -308,7 +330,7 @@ static public string ServiceFromPort(int port)
case 3389:
return "TS/RDP";
case 5353:
- return "UDP";
+ return "mDNS";
case 5355:
return "LLMNR";
case 5985:
@@ -347,6 +369,67 @@ static public string ComposeMethod(this WinsockAFD.Connection cxn)
return service;
}
+ static public string AddressType(IPAddress addr)
+ {
+ if (addr == null)
+ return null;
+
+ if (addr.IsIPv4MappedToIPv6)
+ addr = addr.MapToIPv4();
+
+ if (addr.AddressFamily == AddressFamily.InterNetwork)
+ {
+ byte[] bytes = addr.GetAddressBytes();
+ switch (bytes[0])
+ {
+ case 10:
+ return "Private Network (A)";
+ case 127:
+ return "Loopback";
+ case 169:
+ if (bytes[1] == 254)
+ return "Link-Local";
+ break;
+ case 172:
+ if (bytes[1] >= 16 && bytes[1] < 32)
+ return "Private Network (B)";
+ break;
+ case 192:
+ if (bytes[1] == 168)
+ return "Private Network (C)";
+ break;
+ case 255:
+ if (bytes[1] == 255 && bytes[2] == 255 && bytes[3] == 255)
+ return "Broadcast";
+ break;
+ default:
+ if (bytes[0] >= 224 && bytes[0] < 240)
+ return "Multicast";
+ break;
+ }
+ return null;
+ }
+
+ if ((int)addr.AddressFamily == 34)
+ return "Hyper-V";
+
+ if (addr.AddressFamily != AddressFamily.InterNetworkV6)
+ return null;
+
+ if (addr.IsIPv6LinkLocal)
+ return "Link Local";
+
+ if (addr.IsIPv6Multicast)
+ return "Multicast";
+
+ if (addr.IsIPv6SiteLocal)
+ return "Site Local";
+
+ if (addr.IsIPv6UniqueLocal)
+ return "Unique Local";
+
+ return null;
+ }
/*
qw.RotateLeft()
diff --git a/src/NetBlame/Auxiliary/Tasks.cs b/src/NetBlame/Auxiliary/Tasks.cs
index fcf59cf..9f24400 100644
--- a/src/NetBlame/Auxiliary/Tasks.cs
+++ b/src/NetBlame/Auxiliary/Tasks.cs
@@ -74,11 +74,13 @@ public class TaskItem
public uint cRef; // Count of other events that happened between Start/EndExec
public EState state;
+ const IDVal tidUnknown = -1;
public TaskItem(IDVal pid, IDVal tid, in TimestampUI timeStamp)
{
this.pid = pid;
this.tidCreate = tid;
+ this.tidExec = tidUnknown;
this.timeCreate = timeStamp;
this.timeDestroy.SetMaxValue();
this.state = EState.Created;
diff --git a/src/NetBlame/GatherTables.cs b/src/NetBlame/GatherTables.cs
index 00da767..43a2763 100644
--- a/src/NetBlame/GatherTables.cs
+++ b/src/NetBlame/GatherTables.cs
@@ -200,7 +200,6 @@ void AddURLConnection(WebIO.Request req, WebIO.Connection cxn)
if (cxn.socket != null)
{
tcbR = this.allTables.tcpTable.TcbrFromI(cxn.socket.iTCB);
- AssertImportant((tcbR == null) == (cxn.socket.iDNS == 0)); // else may not be adequately correlated: RequestTable.CorrelateByAddress
AssertCritical(tcbR == null || tcbR.pid == req.pid);
}
@@ -215,6 +214,12 @@ void AddURLConnection(WebIO.Request req, WebIO.Connection cxn)
url.timeRef = req.timeRef;
url.xlink = req.xlink; // no AddRef
url.myStack = new MyStackSnapshot(in req.stack, in req.xlink, req.tidStack);
+ if (tcbR == null)
+ {
+ IPAddress addr = this.allTables.dnsTable.AddressFromI(cxn.socket?.iDNS ?? 0, cxn.socket?.iAddr ?? 0);
+ if (addr != null)
+ url.ipAddrPort = new IPEndPoint(addr, (int)cxn.socket.port);
+ }
}
static readonly WebIO.Connection s_cxnWebIO_Null = new WebIO.Connection(0, null);
@@ -306,7 +311,25 @@ void AddURL(WinsockAFD.Connection cxn)
void AddURL(TcbRecord tcb)
{
tcb.grbitProtocol |= (byte)(tcb.fUDP ? Protocol.UDP : Protocol.TCP);
- string strMethod = ((tcb.grbitProtocol & (byte)Protocol.Rundown) != 0) ? Protocol.Rundown.ToString() : null;
+
+ string strMethod = null;
+ if ((tcb.grbitProtocol & (byte)Protocol.Rundown) != 0)
+ {
+ strMethod = Protocol.Rundown.ToString();
+ }
+ else if (tcb.addrRemote != null)
+ {
+ strMethod = Util.ServiceFromPort(tcb.addrRemote.Port);
+ string strType = Util.AddressType(tcb.addrRemote.Address);
+ if (strType != null)
+ {
+ if (strMethod != null)
+ strMethod += " : " + strType;
+ else
+ strMethod = strType;
+ }
+ }
+
URL url = AddURL(null, strMethod, tcb.Pid, tcb.TidOpen, tcb.cbSend, tcb.cbRecv, in tcb, Prominent((Protocol)tcb.grbitProtocol));
url.strServer = this.allTables.dnsTable.GetServerNameAndAlt(tcb.addrRemote?.Address, null, strNA, out url.strServerAlt);
@@ -401,16 +424,21 @@ void GatherWinSock()
continue;
}
- AssertImportant(cxn.ipProtocol == WinsockAFD.IPPROTO.TCP || cxn.ipProtocol == WinsockAFD.IPPROTO.UDP); // else handle other protocol
-
- // Claim a TCB not otherwise claimed by WinINet or WinHTTP.
- // WARNING: This is a best-guess: the most recent TCB that mostly matches.
- if (cxn.iTCB == 0 && cxn.ipProtocol != WinsockAFD.IPPROTO.UDP)
- cxn.iTCB = this.allTables.tcpTable.CorrelateByAddress(cxn.addrRemote, cxn.pid, cxn.tidClose, cxn.socket, (Protocol)cxn.grbitType);
-
- // Try again! The Name<->Address mapping may have been added later.
- if (cxn.iDNS == 0)
- cxn.iDNS = this.allTables.dnsTable.IDNSFromAddress(cxn.addrRemote.Address);
+ if (cxn.ipProtocol == WinsockAFD.IPPROTO.TCP || cxn.ipProtocol == WinsockAFD.IPPROTO.UDP)
+ {
+#if DEBUG
+ // Confirm that we cannot still claim a TCB which is not otherwise claimed by WinINet or WinHTTP.
+ if (cxn.iTCB == 0 && cxn.ipProtocol != WinsockAFD.IPPROTO.UDP && cxn.socktype != WinsockAFD.SOCKTYPE.SOCK_RAW)
+ AssertImportant(0 == this.allTables.tcpTable.CorrelateByAddress(cxn.addrRemote, cxn.pid, cxn.tidClose, cxn.socket, (Protocol)cxn.grbitType));
+#endif // DEBUG
+ // Maybe we can finally claim a DNS entry which was added later.
+ if (cxn.iDNS == 0)
+ cxn.iDNS = this.allTables.dnsTable.IDNSFromAddress(cxn.addrRemote.Address);
+ }
+ else
+ {
+ AssertImportant(cxn.ipProtocol == WinsockAFD.IPPROTO.HyperV); // else do what?
+ }
AddURL(cxn);
}
diff --git a/src/NetBlame/NetBlameAddIn.csproj b/src/NetBlame/NetBlameAddIn.csproj
index c9268cf..f422085 100644
--- a/src/NetBlame/NetBlameAddIn.csproj
+++ b/src/NetBlame/NetBlameAddIn.csproj
@@ -1,9 +1,10 @@
-
+
net6.0
AnyCPU
true
+ 1.6.0
diff --git a/src/NetBlame/NetBlameDataProcessor.cs b/src/NetBlame/NetBlameDataProcessor.cs
index 9b065c9..1755754 100644
--- a/src/NetBlame/NetBlameDataProcessor.cs
+++ b/src/NetBlame/NetBlameDataProcessor.cs
@@ -102,6 +102,10 @@ public class SymLoadProgress : IProgress
public void Report(SymbolLoadingProgress slp)
{
pctProcessed = slp.ImagesProcessed * 100 / slp.ImagesTotal;
+
+ // 0% => Symbols Disabled
+ if (pctProcessed <= 0)
+ pctProcessed = 1;
}
}
@@ -243,10 +247,10 @@ Without at least a few of these there can be no meaningful final output.
*/
public int EventCount()
{
- return this.wsTable.Count()
- + this.tcpTable.Count()
- + this.webioTable.sessionTable.Count()
- + this.winetTable.Count();
+ return this.wsTable.Count
+ + this.tcpTable.Count
+ + this.webioTable.sessionTable.Count
+ + this.winetTable.Count;
}
@@ -390,9 +394,9 @@ public override DataSourceInfo GetDataSourceInfo()
bool FSymbolsEnabled()
{
- // If either of these are null/empty then LoadSymbolsAsync won't do anything anyway, it appears.
+ // If both of these are null/empty then LoadSymbolsAsync won't do anything anyway, it appears.
// But if _NT_SYMCACHE_PATH or _NT_SYMBOL_PATH were empty then default values will appear here.
- return !String.IsNullOrWhiteSpace(SymCachePath.Automatic.Value) && !String.IsNullOrWhiteSpace(SymbolPath.Automatic.Value);
+ return !String.IsNullOrWhiteSpace(SymCachePath.Automatic.Value) || !String.IsNullOrWhiteSpace(SymbolPath.Automatic.Value);
}
@@ -454,6 +458,7 @@ void MakeTargetProcessList()
AssertCritical(evt.ProcessId == (IDVal)evt.GetAddrValue("ProcessId"));
HProcToPid[evt.GetAddrValue("Process")] = evt.ProcessId;
break;
+ case (int)WinsockTable.AFD.BindWithAddress:
case (int)WinsockTable.AFD.ConnectWithAddress:
case (int)WinsockTable.AFD.ConnectExWithAddress:
// These events are reliable indicators.
@@ -462,10 +467,12 @@ void MakeTargetProcessList()
case (int)WinsockTable.AFD.AcceptExWithAddress:
case (int)WinsockTable.AFD.ReceiveFromWithAddress:
case (int)WinsockTable.AFD.ReceiveMessageWithAddress:
- case (int)WinsockTable.AFD.SendMessageWithAddress:
// These events have an unreliable ProcessId.
// If there was an AFD.Create event for this process, then add its ProcessId now.
- if (evt.GetUInt32("EnterExit") == 0) break;
+ if (evt.GetUInt32("EnterExit") == 1)
+ goto case (int)WinsockTable.AFD.SendMessageWithAddress;
+ break;
+ case (int)WinsockTable.AFD.SendMessageWithAddress:
if (HProcToPid.TryGetValue(evt.GetAddrValue("Process"), out IDVal pid))
PidTargetSet.Add(pid);
break;
@@ -499,7 +506,7 @@ Task LoadSymbolsAsync(IProgress slpLogger)
// WPA doesn't allow -symbols or -symcacheonly with -addsearchdir
// So simulate -symcacheonly with: !!_NT_SYMCACHE_PATH & !_NT_SYMBOL_PATH
- bool fSymCacheOnly = SymCachePath.FromEnvironment != null && SymbolPath.FromEnvironment == null;
+ bool fSymCacheOnly = !string.IsNullOrWhiteSpace(SymCachePath.FromEnvironment?.Value) && string.IsNullOrWhiteSpace(SymbolPath.FromEnvironment?.Value);
#if DEBUG
fSymCacheOnly = true; // fast symbols when debugging
#endif // DEBUG
diff --git a/src/NetBlame/Providers/DNSClient.cs b/src/NetBlame/Providers/DNSClient.cs
index d77520b..6fef872 100644
--- a/src/NetBlame/Providers/DNSClient.cs
+++ b/src/NetBlame/Providers/DNSClient.cs
@@ -58,6 +58,9 @@ public IPAddress AddressFromI(uint iDNS, uint iAddr)
*/
public uint IDNSFromAddress(IPAddress ipAddress)
{
+ if (ipAddress.Empty())
+ return 0;
+
if (ipAddress.IsIPv4MappedToIPv6)
ipAddress = ipAddress.MapToIPv4();
@@ -251,6 +254,9 @@ string GetServerNameAndAlt(string strURL /*opt*/, string strServer /*opt*/, stri
AssertInfo(!strServer.IsNA());
AssertInfo(!strServerAlt.IsNA());
+ if (string.IsNullOrEmpty(strServer))
+ strServer = Util.strNA;
+
return strServer;
}
@@ -378,7 +384,6 @@ static public bool TryParseEx(string strAddr, out IPAddress ipAddress)
strAddr = strAddr[..iPct];
}
- // TODO: Confirm that this works with AF_HYPERV
if (!IPAddress.TryParse(strAddr, out ipAddress))
return false;
@@ -496,10 +501,12 @@ public uint ParseDNSEntries(string hostName, string addrList, out uint iAddr)
int iSpace = strDNS.LastIndexOf(' ');
string strAddr = (iSpace >= 0) ? strDNS[(iSpace + 1)..] : strDNS;
- // TODO: Confirm that this works with AF_HYPERV
if (TryParseIgnorePort(strAddr, out IPAddress ipAddress))
{
- iAddr = AddDNSEntry(hostName, ipAddress, ref iDNSCache);
+ uint iAddrT = AddDNSEntry(hostName, ipAddress, ref iDNSCache);
+ // Return the index of the first address parsed.
+ if (iAddr == 0)
+ iAddr = iAddrT;
}
else if (strAddr.LastIndexOf('.') >= 0) // reality check
{
@@ -544,7 +551,7 @@ public string ConnectNameResolution(in IGenericEvent evt, string strServer)
string strServerName = evt.GetString("FQDN");
string strCanonicalName = evt.GetString("CanonicalName");
- if (strServer != null)
+ if (!string.IsNullOrWhiteSpace(strServer))
{
// The "canonical name" is added below, so use the server name from the URL.
if (strServerName.Equals(strCanonicalName, StringComparison.OrdinalIgnoreCase))
diff --git a/src/NetBlame/Providers/MsoIdleMan.cs b/src/NetBlame/Providers/MsoIdleMan.cs
index 872afed..1f9bb1d 100644
--- a/src/NetBlame/Providers/MsoIdleMan.cs
+++ b/src/NetBlame/Providers/MsoIdleMan.cs
@@ -256,7 +256,6 @@ public void Dispatch(in IGenericEvent evt)
case TaskID.IdleDeregisterTask:
dwID = evt.GetUInt32("dwID");
cookie = (int)evt.GetUInt64("cookie");
-// TODO: Sometimes the dwID is invalid here.
imTask = FindTask(dwID, cookie, evt.ProcessId);
if (imTask != null)
{
@@ -313,8 +312,10 @@ public void Dispatch(in IGenericEvent evt)
#if DEBUG
case IdleFlag.ChangeDeleteTask:
// This could fail when the trace missed the app launch.
+ // The cookie is occasionally invalid.
+ QWord cookieQ = evt.GetUInt64("cookie");
int cookiePrev = this.cookieDispenser.GetPrevCookie(evt.ProcessId);
- AssertImportant(cookiePrev >= cookie || cookiePrev == 0);
+ AssertImportant(cookiePrev >= cookie || cookiePrev == 0 || cookieQ != (QWord)cookie);
break;
#endif // DEBUG
}
diff --git a/src/NetBlame/Providers/TcpIp.cs b/src/NetBlame/Providers/TcpIp.cs
index a82810f..cece454 100644
--- a/src/NetBlame/Providers/TcpIp.cs
+++ b/src/NetBlame/Providers/TcpIp.cs
@@ -21,9 +21,9 @@ namespace NetBlameCustomDataSource.TcpIp
public class TcbRecord : IGraphableEntry
{
public const IDVal pidUnknown = -1; // process
+ public const IDVal tidUnknown = -1; // thread
public const IDVal pidIdle = 0;
public const IDVal pidSystem = 4;
- public const IDVal tidUnknown = 0; // thread
public enum MyStatus : byte
{
@@ -65,14 +65,17 @@ public enum MyStatus_Meta : byte
public IDVal tid; // thread id
public IDVal pid; // process id
public IDVal pidAlt; // alternate PID if pid==pidUnknown
+ public IDVal tidRecvCache; // thread of most recent UDP Receive event
public uint cbPost;
public uint cbSend;
public uint cbRecv;
+ public uint cbRecvCache; // data size of most recent UDP Receive event
public uint iNext; // UDP records with the same open endpoint and different addresses
public bool fUDP; // fUDP => tcb = endpoint identifier
+ public bool fPidSure;
public bool fGathered; // for GatherTcpIp
public bool fCorrelatedSendRecv; // optimization for CorrelateSendRecv
@@ -87,14 +90,13 @@ public enum MyStatus_Meta : byte
public ushort socket; // for correlation with WinINet and Winsock
-
- public MyStatus_Meta MetaStatus { get => (MyStatus_Meta)status; }
-
public TcbRecord(QWord tcb)
{
this.tcb = tcb;
this.pid = pidUnknown;
this.pidAlt = pidUnknown;
+ this.tid = tidUnknown;
+ this.tidRecvCache = tidUnknown;
this.timeShutdown.SetMaxValue();
this.timeClose.SetMaxValue();
}
@@ -104,10 +106,32 @@ public TcbRecord Clone(IPEndPoint ipAddrRemote)
TcbRecord tcbr = (TcbRecord)this.MemberwiseClone();
tcbr.addrRemote = ipAddrRemote;
tcbr.iNext = 0;
- tcbr.cbPost = tcbr.cbSend = tcbr.cbRecv = 0;
+ tcbr.grbitProtocol = 0;
+ tcbr.cbPost = tcbr.cbSend = tcbr.cbRecv = tcbr.cbRecvCache = 0;
+ tcbr.tidRecvCache = tidUnknown;
return tcbr;
}
+ /*
+ Confirm that the UDP Endpoint of the most recent UDP Receive event is the expected one.
+ */
+ public bool FMatch(IDVal pid, ushort socket, in IPEndPoint address)
+ {
+ AssertImportant(this.socket != 0);
+ if (!this.fUDP) return false;
+ if (!FImplies(pid != pidUnknown, this.pid == pid)) return false;
+ if (!FImplies(socket != 0, this.socket == socket)) return false;
+ if (this.addrRemote.Empty() || !this.addrRemote.Equals(address)) return false;
+ return true;
+ }
+
+ public bool FMatchCb(IDVal pid, uint cb, ushort socket, in IPEndPoint address)
+ {
+ if (this.cbRecvCache != cb) return false;
+ return FMatch(pid, socket, in address);
+ }
+
+
public bool CheckType(Protocol bitf)
{
return (this.grbitProtocol & (byte)bitf) != 0;
@@ -119,6 +143,23 @@ public void SetType(Protocol bitf)
}
+ /*
+ pidSure is the PID from the event's ProcessId/PID field via GetProcessId().
+ */
+ public void EnsurePID(IDVal pidSure)
+ {
+ AssertCritical(this != null);
+ if (pidSure != pidUnknown)
+ {
+ AssertCritical(FImplies(this.fPidSure, this.pid == pidSure));
+ AssertCritical(FImplies(this.pid != pidUnknown, this.pid == pidSure));
+ AssertImportant(FImplies(this.pid == pidUnknown && this.pidAlt != pidUnknown, this.pidAlt == pidSure));
+ this.pid = pidSure;
+ this.fPidSure = true;
+ }
+ }
+
+
public void HandleOpenRecord(MyStatus status, IDVal pid, IDVal tid, in TimestampETW timeStamp, in SocketAddress addrLocal, in SocketAddress addrRemote)
{
// All these are important.
@@ -127,47 +168,71 @@ public void HandleOpenRecord(MyStatus status, IDVal pid, IDVal tid, in Timestamp
AssertImportant(timeStamp.HasValue);
AssertImportant(!this.FClosed);
-
- if (status == TcbRecord.MyStatus.Connect_Request || status == TcbRecord.MyStatus.Rundown || status == TcbRecord.MyStatus.Inferred)
+#if DEBUG
+ switch (status)
{
+ case TcbRecord.MyStatus.Connect_Request:
+ case TcbRecord.MyStatus.Rundown:
+ case TcbRecord.MyStatus.Inferred:
// First logged record of a TCB.
AssertInfo(this.status == TcbRecord.MyStatus.Null || this.status == TcbRecord.MyStatus.Inferred);
AssertInfo(!this.timeOpen.HasValue);
- AssertImportant(this.addrRemote.Empty());
- }
- else
- {
+ // There may be duplicated rundown records.
+ AssertImportant(FImplies(status != TcbRecord.MyStatus.Rundown, this.addrRemote.Empty()));
+ break;
+ default:
// Subsequent logged records of a TCB.
AssertImportant(this.status < status || this.status == MyStatus.Rundown);
AssertImportant(this.timeOpen.HasValue);
AssertImportant(!this.addrRemote.Empty());
+ break;
}
-
+#endif // DEBUG
if (!this.timeOpen.HasValue)
this.timeOpen = timeStamp;
this.status = status;
- if (this.pid == pidUnknown)
- this.pid = pid;
+ if (pid != pidUnknown)
+ {
+ if (this.pid == pidUnknown)
+ {
+ this.pid = pid;
+ }
+ else if (this.pid != pid)
+ {
+ // Somehow we got an inconsistent ProcessID.
+ AssertImportant(this.pid == pidSystem); // OK, don't trust pidSystem
+ AssertImportant(tid == tidUnknown); // Got 'pid' from the ProcessId field.
+ AssertImportant(!this.fPidSure); // Inconsistency not unexpected?
+ if (!this.fPidSure)
+ this.pid = pid;
+ }
- if (this.tid == tidUnknown)
- this.tid = tid;
+ if (this.tid == tidUnknown)
+ this.tid = tid;
+ }
// The "socket" is the port of the local address.
- if (this.socket == 0 && addrLocal != null)
- this.socket = addrLocal.Port();
+ if (!addrLocal.Empty())
+ {
+ if (this.socket == 0)
+ this.socket = addrLocal.Port();
+ else
+ AssertImportant(this.socket == addrLocal.Port());
+ }
- if (addrRemote != null)
+ if (!addrRemote.Empty())
{
if (this.addrRemote.Empty())
+ {
this.addrRemote = NewEndPoint(addrRemote);
+ }
#if DEBUG
else
{
IPEndPoint addrT = NewEndPoint(addrRemote);
- if (!addrT.Empty())
- AssertImportant(addrT.Address.Equals(this.addrRemote.Address));
+ AssertImportant(addrT.Address.Equals(this.addrRemote.Address));
}
#endif // DEBUG
}
@@ -262,15 +327,27 @@ public class TcpTable : List
struct UDPEvent
{
public TcbRecord tcbr;
- public IDVal pid;
- public uint cb;
+ IDVal pid;
+ uint cb;
+
+ public void Set(IDVal pid, uint cb, TcbRecord tcbr)
+ {
+ this.pid = pid;
+ this.cb = cb;
+ this.tcbr = tcbr;
+ }
- public bool FMatch(IPEndPoint address, uint cb) => this.cb == cb && this.tcbr?.addrRemote != null && this.tcbr.addrRemote.Equals(address);
- public bool FMatch(IPEndPoint address, IDVal pid, uint cb) => this.pid == pid && this.FMatch(address, cb);
+ public void Reset()
+ {
+ this.pid = pidUnknown;
+ this.cb = 0;
+ this.tcbr = null;
+ }
+
+ public bool FMatch(IPEndPoint address, IDVal pid, uint cb) => this.pid == pid && this.cb == cb && this.tcbr?.addrRemote != null && this.tcbr.addrRemote.Equals(address);
}
// For correlating with adjacent Winsock events.
- UDPEvent udpSendCache;
UDPEvent udpRecvCache;
new void Add(TcbRecord tcbr)
@@ -287,26 +364,22 @@ struct UDPEvent
/*
Return the most recent, matching, open TCB record.
Shutdown might occur before the close and other events, so track it separately.
+ 'pid' is either reliable (GetProcessId(evt)), or it is pidUnknown.
+ If 'pid' is reliable, it might modify: TcbRecord.pid
HIGH TRAFFIC FUNCTION!
*/
TcbRecord FindTcbRecord(QWord tcb, IDVal pid)
{
- if (tcbRCache.tcb == tcb)
- return !tcbRCache.FClosed ? tcbRCache : null;
+ AssertCritical(pid != pidIdle);
- TcbRecord tcbR = this.FindLast(t => t.tcb == tcb);
+ TcbRecord tcbR = tcbRCache;
- if (tcbR == null || tcbR.FClosed) return null; // already shutdown/closed
+ if (tcbR.tcb != tcb)
+ tcbR = this.FindLast(t => t.tcb == tcb);
-#if DEBUG
- AssertCritical(pid != pidIdle);
+ if (tcbR == null || tcbR.FClosed) return null; // already closed?
- if (pid != TcbRecord.pidUnknown && pid != TcbRecord.pidSystem)
- {
- AssertCritical(tcbR.pid == pid || tcbR.pid == TcbRecord.pidUnknown);
- AssertCritical(FImplies(tcbR.pid == TcbRecord.pidUnknown, tcbR.pidAlt == pid || tcbR.pidAlt == TcbRecord.pidUnknown));
- }
-#endif // DEBUG
+ tcbR.EnsurePID(pid);
tcbRCache = tcbR;
@@ -316,23 +389,27 @@ TcbRecord FindTcbRecord(QWord tcb, IDVal pid)
/*
When a TCB Record is closed, we may still need to access it for Shutdown.
Or there may be lingering Receive events.
+ 'pid' is either reliable (GetProcessId(evt)), or it is pidUnknown.
+ If 'pid' is reliable, it might modify: TcbRecord.pid
*/
TcbRecord FindTcbRecordClosed(QWord tcb, IDVal pid)
{
AssertCritical(pid != pidIdle);
- if (tcbRCache.tcb == tcb)
- return !tcbRCache.FShutdown ? tcbRCache : null;
+ TcbRecord tcbR = tcbRCache;
- TcbRecord tcbR = this.FindLast(t => t.tcb == tcb);
- if (tcbR == null || tcbR.FShutdown) return null; // already shutdown/closed
-#if DEBUG
- if (pid != TcbRecord.pidUnknown && pid != TcbRecord.pidSystem)
+ if (tcbR.tcb != tcb)
{
- AssertCritical(tcbR.pid == pid || tcbR.pid == TcbRecord.pidUnknown);
- AssertCritical(FImplies(tcbR.pid == TcbRecord.pidUnknown, tcbR.pidAlt == pid || tcbR.pidAlt == TcbRecord.pidUnknown));
+ if (pid != pidUnknown)
+ tcbR = this.FindLast(t => t.tcb == tcb && FImplies(t.fPidSure, t.pid == pid));
+ else
+ tcbR = this.FindLast(t => t.tcb == tcb);
}
-#endif // DEBUG
+
+ if (tcbR == null || tcbR.FShutdown)
+ return null; // already shutdown?
+
+ tcbR.EnsurePID(pid);
tcbRCache = tcbR;
@@ -340,9 +417,13 @@ TcbRecord FindTcbRecordClosed(QWord tcb, IDVal pid)
}
+ /*
+ Do all the work (to close an outstanding TcbRecord and) to open a new TcbRecord.
+ pid is assumed to be trusted (via GetProcessId()) iff tid == tidUnknown
+ */
TcbRecord AddTcbRecord(QWord tcb, TcbRecord.MyStatus status, in TimestampETW timeStamp, IDVal pid, IDVal tid, in SocketAddress addrLocal, in SocketAddress addrRemote)
{
- TcbRecord tcbR = FindTcbRecord(tcb, pid);
+ TcbRecord tcbR = FindTcbRecord(tcb, (tid==tidUnknown) ? pid : pidUnknown);
if (FImplies(status == TcbRecord.MyStatus.Rundown, tcbR == null))
{
AssertImportant(tcbR == null);
@@ -394,51 +475,30 @@ TcbRecord RestoreTcbRecord(QWord tcb, TcbRecord.MyStatus status, in TimestampETW
/*
- Return the Process ID of the most recently created UDP record, if its IP Address matches.
- */
- public IDVal PidFromUDPEvent(IPEndPoint ipAddr, uint cb, bool fSend)
- {
- AssertCritical(ipAddr != null);
-
- if (fSend)
- {
- if (udpSendCache.FMatch(ipAddr, cb))
- return udpSendCache.pid;
- }
- else
- {
- if (udpRecvCache.FMatch(ipAddr, cb))
- return udpRecvCache.pid;
- }
-
- if (this.Count > 0 && this[^1].fUDP && this[^1].addrRemote != null && this[^1].addrRemote.Equals(ipAddr))
- return this[^1].pid;
-
- return pidUnknown;
- }
-
- /*
- Return the 1-based index if the most recent, open UDP record with the given IP Address and Process ID.
+ Return the 1-based index if the most recent, UDP receive event's TcbRecord with the given IP Address, cb, etc.
Mark it as correlated with Winsock.
*/
- public uint CorrelateUDPAddress(IPEndPoint ipAddr, uint cb, IDVal pid, bool fSend)
+ public uint CorrelateUDPRecvEvent(IDVal pid, IDVal tid, uint cb, ushort socket, IPEndPoint ipAddr)
{
TcbRecord tcbr = null;
- if (fSend)
- {
- if (udpSendCache.FMatch(ipAddr, pid, cb))
- tcbr = udpSendCache.tcbr;
- }
- else
+ if (udpRecvCache.FMatch(ipAddr, pid, cb))
{
- if (udpRecvCache.FMatch(ipAddr, pid, cb))
+ if (FImplies(tid != tidUnknown, udpRecvCache.tcbr.tidRecvCache == tid) && FImplies(socket != 0, udpRecvCache.tcbr.socket == socket))
+ {
tcbr = udpRecvCache.tcbr;
+ AssertImportant(tcbr.cbRecvCache == cb);
+
+ tcbr.tidRecvCache = tidUnknown;
+ tcbr.cbRecvCache = 0;
+
+ udpRecvCache.Reset();
+ }
}
int iTCB;
- // Search by most recent event or by address (less accurate).
+ // Search by most recent event.
if (tcbr != null)
{
@@ -448,11 +508,37 @@ public uint CorrelateUDPAddress(IPEndPoint ipAddr, uint cb, IDVal pid, bool fSen
}
else
{
- iTCB = this.FindLastIndex(t => t.fUDP && t.pid == pid && !t.FClosed && t.addrRemote.Equals(ipAddr));
- if (iTCB < 0) return 0;
- tcbr = this[iTCB];
+ if (tid != tidUnknown)
+ iTCB = this.FindLastIndex(t => t.tidRecvCache == tid && t.FMatchCb(pid, cb, socket, in ipAddr));
+ else
+ iTCB = this.FindLastIndex(t => t.FMatchCb(pid, cb, socket, in ipAddr));
+
+ if (iTCB >= 0)
+ {
+ tcbr = this[iTCB];
+ tcbr.cbRecvCache = 0; // match by size only once
+ }
+ else
+ {
+ // It may still be that other UDP.EndpointReceiveMessage events were emitted after the one that matches this event.
+ // But if the PID, socket, and address match, then we can still trust it.
+ AssertImportant(pid != pidUnknown);
+ AssertInfo(socket != 0); // zero near the start of a trace
+
+ if (tid != tidUnknown)
+ iTCB = this.FindLastIndex(t => t.tidRecvCache == tid && t.FMatch(pid, socket, in ipAddr));
+ else
+ iTCB = this.FindLastIndex(t => t.FMatch(pid, socket, in ipAddr));
+
+ if (iTCB < 0) return 0;
+
+ tcbr = this[iTCB];
+ }
}
+ AssertImportant(FImplies(socket != 0, tcbr.socket == socket));
+ AssertImportant(tcbr.cbRecv >= cb);
+
tcbr.SetType(Protocol.Winsock);
return (uint)(iTCB + 1); // 1-based
@@ -720,11 +806,12 @@ public void Dispatch(in IGenericEvent evt)
TcbRecord tcbr;
SocketAddress addrLocal, addrRemote;
TimestampUI timeStamp;
+ WinsockAFD.Connection cxn;
switch ((TCP)evt.Id)
{
case TCP.RequestConnect:
- // evt.ProcessId is reliable
+ // evt.ProcessId is usually reliable, unless it's pidSystem
tcb = GetTCB(evt);
addrLocal = evt.GetLocalAddress();
addrRemote = evt.GetRemoteAddress();
@@ -737,24 +824,27 @@ public void Dispatch(in IGenericEvent evt)
case TCP.ConnectTcbProceeding:
tid = tidUnknown;
pid = GetProcessId(in evt); // usually unknown
- if (pid == pidUnknown)
- {
- // evt.ProcessId is reliable
- pid = evt.ProcessId;
- tid = evt.ThreadId;
- }
+
AssertStatus(in evt);
+ tcbr = FindTcbRecord(GetTCB(evt), pid);
+
addrLocal = evt.GetLocalAddress();
addrRemote = evt.GetRemoteAddress();
- tcbr = FindTcbRecord(GetTCB(evt), pid);
+ if (pid == pidUnknown || pid == evt.ProcessId)
+ {
+ // evt.ProcessId is reliable?
+ pid = evt.ProcessId;
+ tid = evt.ThreadId;
+ }
+
CorrelateConnection(tcbr, in addrLocal, in addrRemote, pid, tid, evt.Timestamp);
break;
case TCP.ConnectTcbComplete:
// evt.ProcessId is usually unreliable
- pid = GetProcessId(in evt); // sometimes unknown
+ IDVal pidField = pid = GetProcessId(in evt); // sometimes unknown
tcb = GetTCB(evt);
AssertStatus(in evt);
@@ -766,20 +856,25 @@ public void Dispatch(in IGenericEvent evt)
addrRemote = evt.GetRemoteAddress();
tid = tidUnknown;
- if (pid == pidUnknown && tcbr.pid == evt.ProcessId)
+ if (pid == evt.ProcessId || (pid == pidUnknown && tcbr?.pid == evt.ProcessId))
{
// These are now assumed to be reliable.
pid = evt.ProcessId;
tid = evt.ThreadId;
}
- if (tcbr == null)
- tcbr = RestoreTcbRecord(tcb, TcbRecord.MyStatus.Inferred, evt.Timestamp, pid, addrLocal, addrRemote);
- else
+ if (tcbr != null)
+ {
tcbr.HandleOpenRecord(TcbRecord.MyStatus.Connect_Complete, pid, tid, evt.Timestamp, addrLocal, in addrRemote);
+ }
+ else
+ {
+ tcbr = RestoreTcbRecord(tcb, TcbRecord.MyStatus.Inferred, evt.Timestamp, pid, addrLocal, addrRemote);
+ tcbr.EnsurePID(pidField);
+ }
// ThreadId doesn't correlate here.
- CorrelateConnection(tcbr, in addrLocal, in addrRemote, pid, 0/*tid*/, evt.Timestamp);
+ CorrelateConnection(tcbr, in addrLocal, in addrRemote, pid, tidUnknown, evt.Timestamp);
break;
case TCP.ConnectTcbFailure:
@@ -789,7 +884,8 @@ public void Dispatch(in IGenericEvent evt)
// Status is usually non-zero
tcbr = FindTcbRecord(tcb, pid);
- AssertImportant(tcbr != null);
+ // The record may already be closed/shutdown.
+ AssertInfo(tcbr != null);
if (tcbr != null)
{
@@ -810,15 +906,18 @@ public void Dispatch(in IGenericEvent evt)
addrRemote = evt.GetRemoteAddress();
addrLocal = evt.GetLocalAddress();
- if (tcbr == null)
- tcbr = RestoreTcbRecord(tcb, TcbRecord.MyStatus.Inferred, evt.Timestamp, pid, addrLocal, addrRemote);
- else
+ if (tcbr != null)
+ {
tcbr.HandleOpenRecord(TcbRecord.MyStatus.Inferred, pid, tidUnknown, evt.Timestamp, in addrLocal, in addrRemote);
-
- timeStamp = evt.Timestamp.ToGraphable();
+ }
+ else
+ {
+ tcbr = RestoreTcbRecord(tcb, TcbRecord.MyStatus.Inferred, evt.Timestamp, pid, addrLocal, addrRemote);
+ tcbr.EnsurePID(pid);
+ }
// Here the actual ThreadId matches the ThreadId of WebIO.AFD.AcceptExWithAddress
- WinsockAFD.Connection cxn = this.allTables.wsTable.CorrelateListener(addrRemote, tcbr, pid, evt.ThreadId, in timeStamp);
+ cxn = this.allTables.wsTable.CorrelateListener(tcbr, pid, (pid==evt.ProcessId) ? evt.ThreadId : tidUnknown);
break;
case TCP.CloseTcbRequest:
@@ -867,7 +966,8 @@ public void Dispatch(in IGenericEvent evt)
addrLocal = evt.GetLocalAddress();
addrRemote = evt.GetRemoteAddress();
- AddTcbRecord(tcb, TcbRecord.MyStatus.Rundown, evt.Timestamp, pid, tidUnknown, in addrLocal, in addrRemote);
+ tcbr = AddTcbRecord(tcb, TcbRecord.MyStatus.Rundown, evt.Timestamp, pid, tidUnknown, in addrLocal, in addrRemote);
+ tcbr.EnsurePID(pid);
break;
case TCP.DataTransferReceive:
@@ -917,14 +1017,14 @@ public void Dispatch(in IGenericEvent evt)
pid = pidUnknown;
tcbr = FindTcbRecord(tcb, pidUnknown);
- if (tcbr == null)
+ if (tcbr != null)
{
- tcbr = RestoreTcbRecord(tcb, TcbRecord.MyStatus.Inferred, evt.Timestamp, pid, null, null);
+ if (tcbr.pidAlt == pidUnknown)
+ tcbr.pidAlt = pid;
}
else
{
- if (tcbr.pidAlt == pidUnknown)
- tcbr.pidAlt = pid;
+ tcbr = RestoreTcbRecord(tcb, TcbRecord.MyStatus.Inferred, evt.Timestamp, pid, null, null);
}
timeStamp = evt.Timestamp.ToGraphable();
@@ -946,14 +1046,14 @@ public void Dispatch(in IGenericEvent evt)
tcbr = FindTcbRecord(tcb, pidUnknown);
- if (tcbr == null)
+ if (tcbr != null)
{
- tcbr = RestoreTcbRecord(tcb, TcbRecord.MyStatus.Inferred, evt.Timestamp, pid, null, null);
+ if (tcbr.pidAlt == pidUnknown)
+ tcbr.pidAlt = pid;
}
else
{
- if (tcbr.pidAlt == pidUnknown)
- tcbr.pidAlt = pid;
+ tcbr = RestoreTcbRecord(tcb, TcbRecord.MyStatus.Inferred, evt.Timestamp, pid, null, null);
}
timeStamp = evt.Timestamp.ToGraphable();
@@ -964,7 +1064,7 @@ public void Dispatch(in IGenericEvent evt)
case TCP.InspectConnectWithNameResContext:
tcb = GetTCB(evt);
pid = evt.ProcessId; // reliable
- if (pid == 0)
+ if (pid <= TcbRecord.pidSystem)
pid = pidUnknown;
AssertStatus(in evt);
@@ -999,40 +1099,38 @@ public void Dispatch(in IGenericEvent evt)
tcb = evt.GetAddrValue("Endpoint"); // Not exactly a TCB
tcbr = FindTcbRecord(tcb/*pseudo-TCB*/, pid);
- SocketAddress addrSrc, addrDst;
- addrLocal = (evt.GetUInt32("LocalSockAddrLength") != 0) ? evt.GetSocketAddress("LocalSockAddr") : null;
addrRemote = (evt.GetUInt32("RemoteSockAddrLength") != 0) ? evt.GetSocketAddress("RemoteSockAddr") : null;
+ addrLocal = (evt.GetUInt32("LocalSockAddrLength") != 0) ? evt.GetSocketAddress("LocalSockAddr") : null;
+ socket = addrLocal?.Port() ?? 0;
- if ((UDP)evt.Id == UDP.EndpointSendMessages)
- {
- addrDst = addrRemote;// address of interest
- addrSrc = addrLocal; // 0.0.0.0:Port
- }
- else
+ if ((UDP)evt.Id == UDP.EndpointReceiveMessages)
{
- AssertCritical((UDP)evt.Id == UDP.EndpointReceiveMessages);
-
- addrDst = addrLocal; // address of interest
- addrSrc = addrRemote;// [LocalIPAddress]:Port
-
// There may be a lingering Receive message after the TCB was closed.
if (tcbr == null)
tcbr = FindTcbRecordClosed(tcb/*pseudo-TCB*/, pid);
+
+ // Sometimes the addresses are swapped.
+ if (tcbr != null && socket != tcbr.socket && addrRemote?.Port() == tcbr.socket && (NewEndPoint(addrLocal).Equals(tcbr.addrRemote)))
+ {
+ SocketAddress addrT = addrLocal;
+ addrLocal = addrRemote;
+ addrRemote = addrT;
+ socket = addrLocal?.Port() ?? 0;
+ }
}
+ IPEndPoint ipAddrRemote = NewEndPoint(addrRemote);
+
if (tcbr == null)
{
IDVal pidAlt = (evt.ProcessId > TcbRecord.pidSystem) ? evt.ProcessId : pidUnknown;
- tcbr = RestoreTcbRecord(tcb/*pseudo-TCB*/, TcbRecord.MyStatus.Inferred, evt.Timestamp, pidAlt, addrSrc, addrDst);
+ tcbr = RestoreTcbRecord(tcb/*pseudo-TCB*/, TcbRecord.MyStatus.Inferred, evt.Timestamp, pidAlt, addrLocal, addrRemote);
tcbr.pid = pid;
tcbr.tid = evt.ThreadId;
tcbr.fUDP = true;
}
else
{
- IPEndPoint ipAddrRemote = NewEndPoint(addrDst);
- socket = addrSrc?.Port() ?? 0;
-
// UDP can have different Remote Addresses on the same Endpoint.
// Find the record with matching address.
// If needed, create separate records and link them backward in time.
@@ -1058,24 +1156,46 @@ public void Dispatch(in IGenericEvent evt)
tcbr = tcbrFound;
}
+ tcbr.EnsurePID(pid);
AssertImportant(tcbr.fUDP);
cb = (uint)evt.GetUInt32("NumBytes");
if ((UDP)evt.Id == UDP.EndpointSendMessages)
{
- tcbr.cbSend += cb;
+ // Correlate this UDP Send event with the preceeding Winsock Send event.
+ // AFD.SendMessageWithAddress TID cb Address
+ // UDP.EndpointSendMessages TID cb RemoteSockAddr
+
+ cxn = this.allTables.wsTable.CorrelateUDPSendEvent(pid, evt.ThreadId, cb, socket, ipAddrRemote);
+
+ if (cxn != null)
+ {
+ if (cxn.iTCB == 0)
+ cxn.iTCB = this.IFromTcbr(tcbr);
+ else
+ AssertImportant(cxn.iTCB == this.IFromTcbr(tcbr));
+
+ if (cxn.socket == 0)
+ cxn.socket = socket;
+ else
+ AssertImportant(cxn.socket == socket);
+
+ AssertImportant(tcbr.addrRemote.Address.Equals(cxn.addrRemote.Address));
+
+ tcbr.SetType(Protocol.Winsock);
+ }
- this.udpSendCache.cb = cb;
- this.udpSendCache.pid = pid;
- this.udpSendCache.tcbr = tcbr;
+ tcbr.cbSend += cb;
}
else
{
+ // Note that this UDP Receive event with the preceeds the Winsock Receive event.
+
+ tcbr.tidRecvCache = evt.ThreadId;
+ tcbr.cbRecvCache = cb;
tcbr.cbRecv += cb;
- this.udpRecvCache.cb = cb;
- this.udpRecvCache.pid = pid;
- this.udpRecvCache.tcbr = tcbr;
+ this.udpRecvCache.Set(pid, cb, tcbr);
}
break;
@@ -1086,7 +1206,7 @@ public void Dispatch(in IGenericEvent evt)
socket = evt.GetLocalAddress()?.Port() ?? 0;
// For UDP there can be various TCB (really, Endpoint) records with different addresses.
- for (tcbr = FindTcbRecord(tcb/*pseudo-TCB*/, pid); tcbr != null; tcbr = TcbrFromI(tcbr.iNext))
+ for (tcbr = FindTcbRecord(tcb/*pseudo-TCB*/, pidUnknown); tcbr != null; tcbr = TcbrFromI(tcbr.iNext))
{
AssertCritical(tcbr.fUDP);
AssertCritical(tcbr.tcb == tcb);
@@ -1100,7 +1220,7 @@ public void Dispatch(in IGenericEvent evt)
// Correlate with WinSock Datagram / UDP record.
uint iTCB = IFromTcbr(tcbr);
- cxn = this.allTables.wsTable.CorrelateByAddress(tcbr.addrRemote, iTCB, tcbr.socket, pid, 0);
+ cxn = this.allTables.wsTable.CorrelateByAddress(tcbr.addrRemote, iTCB, tcbr.socket, pid, tidUnknown);
if (cxn != null)
{
AssertImportant(cxn.socktype == WinsockAFD.SOCKTYPE.SOCK_DGRAM);
diff --git a/src/NetBlame/Providers/Thread.Classic.cs b/src/NetBlame/Providers/Thread.Classic.cs
index c141d1d..ebc100d 100644
--- a/src/NetBlame/Providers/Thread.Classic.cs
+++ b/src/NetBlame/Providers/Thread.Classic.cs
@@ -67,6 +67,8 @@ public class THREAD_EVENT : ThreadClassicEvent
public readonly ThreadEventPayload ThreadEvt;
public readonly Addr64 ThreadProc;
+ const DWId tidUnknown = -1;
+
private Addr32 GetThreadProc32(in ClassicEvent evt)
{
if (evt.Data.Length < 2*sizeof(UInt32) + 6*sizeof(UInt32))
@@ -95,7 +97,7 @@ public THREAD_EVENT(in ClassicEvent evt) : base(in evt)
// If the thread "created itself" then the creator/initiator is unknown.
if (this.tidInitiator == this.ThreadEvt.ThreadId && (TEID)evt.Id != TEID.Exit)
- this.tidInitiator = 0;
+ this.tidInitiator = tidUnknown;
// Get the ThreadProc address if available. (See wmicore.mof: "Thread Create/Exit Event")
if (evt.Version > 0)
diff --git a/src/NetBlame/Providers/Thread.cs b/src/NetBlame/Providers/Thread.cs
index 528dfcb..9108348 100644
--- a/src/NetBlame/Providers/Thread.cs
+++ b/src/NetBlame/Providers/Thread.cs
@@ -380,8 +380,10 @@ public void Dispatch(in THREAD_EVENT evt, bool fTarget)
Then there will be a Thread Create event followed by a Rundown event.
But the Thread pre-dispatcher queues all of the Rundown events before the Create events.
If that happens here, ignore this thread's Rundown event/record.
+ Also, thread events can duplicate in a merged trace: xperf/wpr -merge ...
*/
- AssertImportant(!FTestThreadItem(evt.ThreadEvt.ThreadId) || IsRundownThread(evt.ThreadEvt.ThreadId));
+ if (FTestThreadItem(evt.ThreadEvt.ThreadId) && !IsRundownThread(evt.ThreadEvt.ThreadId))
+ break;
tItem = AddThread(in evt);
@@ -399,6 +401,7 @@ But the Thread pre-dispatcher queues all of the Rundown events before the Create
case TEID.Rundown:
// Feed the Thread Oracle with every thread we can find.
+ // (Thread events can duplicate in a merged trace: xperf/wpr -merge ...)
AssertImportant(!FTestThreadItem(evt.ThreadEvt.ThreadId));
AddThread(in evt);
break;
diff --git a/src/NetBlame/Providers/WThreadPool.Timer.cs b/src/NetBlame/Providers/WThreadPool.Timer.cs
index 58dcfd6..777e34e 100644
--- a/src/NetBlame/Providers/WThreadPool.Timer.cs
+++ b/src/NetBlame/Providers/WThreadPool.Timer.cs
@@ -60,12 +60,10 @@ public class WTPTimer : TaskItem, ITaskItemInfo
public WTP Status { get => (WTP)status; set { status = (byte)value; } }
- // TODO: Are these 8 functions needed?
- public TimestampUI TimeSet { get => timeCreate; set { timeCreate = value; } }
- public TimestampUI TimeExpire { get => timeStartExec; set { timeStartExec = value; } }
- public IDVal TidSet { get => tidCreate; set { tidCreate = value; } }
public IDVal TidExpire { get => tidExec; set { tidExec = value; } }
+ public const IDVal tidUnknown = -1;
+
public WTPTimer(/*in*/ TIMER_1 evt)
: base(evt.idProcess, evt.idThread/*Set*/, /*timeSet*/evt.timeStamp.ToGraphable())
{
@@ -127,7 +125,7 @@ public bool FEndExpiration(QWord qwSubQueue, IDVal tid)
if (timer.Recurring)
{
// TODO: How does this work with accumulating execution time, capturing references, etc?
- timer.TidExpire = 0;
+ timer.TidExpire = WTPTimer.tidUnknown;
timer.Status = WTP.TimerSet;
}
else
diff --git a/src/NetBlame/Providers/WebIO.Connection.cs b/src/NetBlame/Providers/WebIO.Connection.cs
index 5fba1ce..fa90454 100644
--- a/src/NetBlame/Providers/WebIO.Connection.cs
+++ b/src/NetBlame/Providers/WebIO.Connection.cs
@@ -5,6 +5,7 @@
using IDVal = System.Int32; // type of Event.pid/tid / ideally: System.UInt32
using QWord = System.UInt64;
+using static NetBlameCustomDataSource.Util;
namespace NetBlameCustomDataSource.WebIO
{
@@ -29,6 +30,12 @@ public class Connection
// Thread of most recent Send activity, between: ConnectionSocketSend_Start/Stop
public IDVal tidSend;
public uint cbSendTCB;
+
+ public QWord ctxSend;
+ public QWord ctxRecv;
+
+ // There's another one of these used by an earlier Request.
+ public bool fDuplicate;
#if DEBUG
public bool fOutdated;
public bool fTransferred;
@@ -51,6 +58,20 @@ public bool MatchTCB(uint iTCB, uint iDNS, uint iAddr, IDVal tid)
else
return false; // no TCB, no tid: very uncertain
}
- }
+ [Conditional("DEBUG")]
+ public void TestSocket(in Microsoft.Windows.EventTracing.Events.IGenericEvent evt, QWord qwCxn)
+ {
+ QWord qwSocket = evt.TryGetUInt64("SocketHandle"); // ConnectSocketStop, etc.
+ if (qwSocket == 0)
+ qwSocket = evt.TryGetUInt64("Socket"); // ConnectSocketClose
+#if DEBUG
+ AssertCritical(qwSocket != 0);
+ AssertCritical(this.socket != null);
+ AssertImportant(this.socket.qwSocket == qwSocket || (this.socket.FClosed && qwSocket == QWord.MaxValue));
+ AssertImportant(this.socket.qwConnection == qwCxn);
+ AssertImportant(this.qwConnection == qwCxn);
+#endif // DEBUG
+ }
+ }
}
\ No newline at end of file
diff --git a/src/NetBlame/Providers/WebIO.Request.cs b/src/NetBlame/Providers/WebIO.Request.cs
index 6d8d099..82f0030 100644
--- a/src/NetBlame/Providers/WebIO.Request.cs
+++ b/src/NetBlame/Providers/WebIO.Request.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using Microsoft.Windows.EventTracing.Events;
using Microsoft.Windows.EventTracing.Symbols;
@@ -72,6 +73,9 @@ public Request(in IGenericEvent evt, QWord hReq)
this.timeOpen = evt.Timestamp.ToGraphable();
this.timeClose.SetMaxValue();
this.pid = evt.ProcessId;
+ this.tidStack = RequestTable.tidUnknown;
+ this.strServer = string.Empty;
+ this.strMethod = string.Empty;
}
public bool FOpen => this.timeClose.HasMaxValue();
@@ -85,13 +89,69 @@ public Request(in IGenericEvent evt, QWord hReq)
public bool HasCurrentOpenConnection =>
this.HasCurrentConnection && this.FOpen;
+/*
+ The Connection value from RequestWaitingForConnection
+ is sometimes invalid (never occuring elsewhere in the trace).
+ It appears to be the address of a different heap object.
+ Empirically, the invalidity appears to be unique to a certain pattern.
+ It is these 5 events, all on the same thread, always occuring together:
+ WIO.RequestCreate T1 Request=R# Method=M$
+ WIO.RequestWaitingForConnection T1 Request=R# Connection=C1#
+ WIO.RequestHeader_Start T1 Request=R# Headers=H$
+ WIO.ConnectionSocketSend_Start T1 Connection=C2#
+ WIO.ConnectionSocketSend_End T1 Connection=C2#
+ ...
+ AND !H$.StartsWith(M$) OR exception thrown.
+ Also, the WIO.RequestHeader_Start event often has fields that throw when parsed. (!?)
+
+ Even so, C1#==C2# sometimes, unless we find the inconsistencies in the header event.
+ It appears that when we see those inconsistencies in the above pattern, then C1# != C2#.
+*/
+ public enum EValidity : int
+ {
+ Dubious = -1,
+ Unknown = 0, // default
+ Confirmed = 1
+ }
+
+ public EValidity Validity { get; private set; }
+
+ [Conditional("DEBUG")]
+ public void ConfirmValidity() => this.Validity = EValidity.Confirmed;
+
+ [Conditional("DEBUG")]
+ public void HandleValidity(in IGenericEvent evt, bool fTestHeader)
+ {
+ AssertImportant(this.strMethod != null);
+ AssertCritical(evt.Id == (int)WebIOTable.WIO.RequestHeader_Send);
+ // Test the GuessRequest mechanism for the RequestHeader_Send event.
+ AssertInfo(WebIOTable.GuessRequest(in evt, this.qwConnectionCur) == this.qwRequest);
+
+ if (this.Validity != EValidity.Unknown)
+ {
+ AssertImportant(this.Validity != EValidity.Dubious); // how?
+ return;
+ }
+
+ if (fTestHeader)
+ {
+ string strHeader = evt.GetString("Headers");
+ if (!strHeader.StartsWith(this.strMethod))
+ this.Validity = EValidity.Dubious; // rare!
+ }
+ else
+ {
+ this.Validity = EValidity.Dubious;
+ }
+ }
+
// Implement IGraphableEntry
#if DEBUG || AUX_TABLES
public IDVal Pid => this.pid;
public IDVal TidOpen => this.tidStack;
#else
- public IDVal Pid => 0;
- public IDVal TidOpen => 0;
+ public IDVal Pid => pidUnknown;
+ public IDVal TidOpen => tidUnknown;
#endif // DEBUG || AUX_TABLES
public TimestampETW TimeRef => this.timeRef;
public TimestampUI TimeOpen => this.timeOpen;
@@ -111,6 +171,8 @@ public RequestTable(int capacity) : base(capacity) { }
public uint IFromRequest(in Request request) => (uint)(this.LastIndexOf(request) + 1); // 1-based
+ public const IDVal tidUnknown = -1;
+
public uint IFindRequest(QWord qwRequest, TimestampUI timeStamp)
{
@@ -172,90 +234,16 @@ Find the Request and Connection with the given Connection Handle.
This can return a Connection which doesn't (yet) belong to the Request (but should).
A returned Connection is assumed to have a non-null Socket.
*/
- public Connection FindConnectionByHandle_OLD(in IGenericEvent evt, out Request req)
- {
- Connection cxn = null;
-
- req = null;
-
- IDVal pid = evt.ProcessId;
-
- QWord qwCxn = evt.TryGetAddrValue("Connection"); // not ConnectionSocketClose
-
- if (qwCxn != 0)
- {
- // In some cases (ConnectionSocketReceive) we're looking for a Request which can be technically closed.
-
- QWord hreq = WebIOTable.GetHReq(in evt);
-
- req = FindRequestByHReq(hreq, pid);
-
- // Check all Connections of the expected Request.
-
- if (req != null && req.HasCurrentConnection)
- cxn = req.rgConnection.FindLast(c => c.qwConnection == qwCxn);
- }
-
- if (cxn == null)
- {
- if (qwCxn == 0)
- qwCxn = evt.GetAddrValue("Endpoint"); // ConnectionSocketClose
-
- AssertCritical(qwCxn != 0);
-
- // Check all Requests for their current Connection.
- // The request may already be closed for ConnectionSocketClose.
-
- req = this.FindLast(r => r.qwConnectionCur == qwCxn && r.pid == pid);
-
- if (req != null)
- {
- if (req.HasCurrentConnection)
- cxn = req.rgConnection[^1]; // current connection
- }
- else
- {
- // Check all Connections of all Requests, most recent first.
-
- Request reqT = this.FindLast(r =>
- {
- if (r.pid != pid) return false;
- cxn = r.rgConnection?.FindLast(c => c.qwConnection == qwCxn);
- return cxn != null;
- });
- }
- }
-
-#if DEBUG
- // Verify that the Connection has the expected Socket.
-
- if (cxn != null)
- {
- QWord qwSocket = evt.TryGetUInt64("SocketHandle"); // ConnectSocketStop, etc.
- if (qwSocket == 0)
- qwSocket = evt.TryGetUInt64("Socket"); // ConnectSocketClose
-
- AssertCritical(qwSocket != 0);
- AssertCritical(cxn.socket != null);
-
- AssertImportant(cxn.socket.qwSocket == qwSocket || (cxn.socket.FClosed && qwSocket == QWord.MaxValue));
- AssertImportant(cxn.socket.qwConnection == qwCxn);
- AssertImportant(cxn.qwConnection == qwCxn);
- }
-#endif // DEBUG
-
- return cxn;
- }
-
-
public Connection FindConnectionByHandle(in IGenericEvent evt, out Request req)
{
- Connection cxn = null;
-
req = null;
-
+ Connection cxn = null;
IDVal pid = evt.ProcessId;
+ // Here we test Connection values AGAINST the one provided by RequestWaitingForConnection.
+ AssertCritical(evt.Id != (int)WebIOTable.WIO.RequestWaitingForConnection_Stop);
+ AssertCritical(evt.Id < (int)WebIOTable.WIO.ConnectionSocketSend_Start || evt.Id > (int)WebIOTable.WIO.ConnectionSocketReceive_Stop);
+
QWord qwCxn = evt.TryGetAddrValue("Connection"); // not ConnectionSocketClose
if (qwCxn != 0)
@@ -274,16 +262,13 @@ public Connection FindConnectionByHandle(in IGenericEvent evt, out Request req)
if (req.HasCurrentConnection)
cxn = req.rgConnection[^1];
// else later add a new Connection to this Request.
+
+ // The Request's Connection ID matches the event's (qwCxn).
+ req.ConfirmValidity();
}
else
{
- // SocketReceive events are allowed to operate on a non-current Connection.
-
- if (WebIOTable.IsSocketReceiveStopClose(in evt))
- cxn = req?.rgConnection.FindLast(c => c.qwConnection == qwCxn);
-
- if (cxn == null)
- req = null;
+ req = null;
}
}
@@ -301,9 +286,9 @@ public Connection FindConnectionByHandle(in IGenericEvent evt, out Request req)
{
req = null;
- // Find a Closed request, sometimes needed for ConnectionSocketReceive/Close.
+ // Find a Closed request, sometimes needed for ConnectionSocketClose.
- if (WebIOTable.IsSocketReceiveStopClose(in evt))
+ if (evt.Id == (int)WebIOTable.WIO.ConnectionSocketClose)
reqT = this.FindLast(r => r.HasCurrentConnection && r.qwConnectionCur == qwCxn && r.pid == pid);
}
@@ -311,161 +296,237 @@ public Connection FindConnectionByHandle(in IGenericEvent evt, out Request req)
{
req = reqT;
cxn = reqT.rgConnection[^1]; // current connection
+
+ // The Request's Connection ID matches the event's (qwCxn).
+ req.ConfirmValidity();
#if DEBUG
AssertImportant(reqT.qwConnectionCur == qwCxn);
AssertImportant(FImplies(!WebIOTable.IsSocketReceiveStopClose(in evt), !cxn.fOutdated));
#endif // DEBUG
}
}
-#if DEBUG
- // Verify that the Connection has the expected Socket.
-
- if (cxn != null)
- {
- QWord qwSocket = evt.TryGetUInt64("SocketHandle"); // ConnectSocketStop, etc.
- if (qwSocket == 0)
- qwSocket = evt.TryGetUInt64("Socket"); // ConnectSocketClose
-
- AssertCritical(qwSocket != 0);
- AssertCritical(cxn.socket != null);
-
- AssertImportant(cxn.socket.qwSocket == qwSocket || (cxn.socket.FClosed && qwSocket == QWord.MaxValue));
- AssertImportant(cxn.socket.qwConnection == qwCxn);
- AssertImportant(cxn.qwConnection == qwCxn);
- AssertImportant(!cxn.fOutdated || WebIOTable.IsSocketReceiveStopClose(in evt));
- }
-#endif // DEBUG
+ cxn?.TestSocket(in evt, qwCxn);
return cxn;
- }
+ } // FindConnectionByHandle
/*
- The Socket is probably there in the Request, created by ConnectionSocketConnect records.
- But the Request may be reusing a Socket, probably referenced by another Request.
- Or we may need to create a default Socket if we missed its creation.
+ Create a new Connection and add it to this Request.
+ Base it on an existing Connection from another Request, if possible.
*/
- public Connection GetConnection(in IGenericEvent evt)
+ public Connection SetRequestConnection(in IGenericEvent evt, Request req, QWord qwCxn, int iReq)
{
- Connection cxn = this.FindConnectionByHandle(in evt, out Request req);
+ AssertCritical(req.qwConnectionCur == qwCxn);
+ AssertCritical(req.Validity == Request.EValidity.Confirmed);
- QWord hReq = WebIOTable.GetHReq(in evt);
- if (req == null || (req.qwHandle != hReq && (!req.FOpen || req.FShared)))
- {
- Request reqT = null;
- QWord qwCxn = (cxn != null) ? cxn.qwConnection : evt.GetAddrValue("Connection");
+ // We didn't get a ConnectionSocketConnect event pair, apparently.
+ // We must be reusing a Connection/Socket previously opened (PATTERN 4B).
+ // Or this event occurs very near the beginning of the trace.
- switch ((WebIOTable.WIO)evt.Id)
- {
- case WebIOTable.WIO.ConnectionSocketSend_Start:
- case WebIOTable.WIO.ConnectionSocketReceive_Start:
- // PATTERN 4B: Sharing a Connection with a closed Request!?
- // Or this handle might be used to send data for: Request.Header
-
- AssertImportant(req == null || !req.FOpen || req.FShared);
- if (req != null && req.FOpen && req.qwConnectionCur == qwCxn)
- break;
-
- // Here we don't completely trust hReq, but we also don't completely trust reqT.qwConnectionCur.
- // We do see: Microsoft-Windows-WebIO.RequestWaitingForConnection | Connection =
- // So go back to using the request identified by the request handle, if it has no connections.
-
- reqT = this.FindOpenRequestByHReq(hReq, evt.ProcessId);
- AssertImportant(reqT == null || !reqT.FShared);
- if (reqT != null && reqT.rgConnection == null)
- {
- req = reqT;
- req.qwConnectionCur = qwCxn;
- }
- else if (req == null)
- {
- // fall through to find the Request and Connection
- goto case WebIOTable.WIO.ConnectionSocketSend_Stop;
- }
- break;
+ QWord qwSocket = evt.TryGetUInt64("SocketHandle");
+ AssertImportant(qwSocket != 0);
+ AssertImportant(qwSocket != QWord.MaxValue);
- case WebIOTable.WIO.ConnectionSocketSend_Stop:
- case WebIOTable.WIO.ConnectionSocketReceive_Stop:
- AssertImportant(req == null || req.FOpen);
- AssertImportant(req == null || req.FShared);
+ if (iReq < 0)
+ iReq = this.Count - 1;
- // Empirically we believe that this 'receive' (or even 'send') event is residual from a [soon to be] closed Request.
- // Find that one.
- // Or this handle might be used to receive data for: Request.Header
+ // Find the shareable Connection and Socket with the given ID/handle values.
- IDVal pid = evt.ProcessId;
- reqT = this.FindLast(r => r != req && r.qwConnectionCur == qwCxn && r.pid == pid);
- AssertImportant(reqT == null || reqT.qwConnectionCur == evt.GetAddrValue("Connection"));
- if (reqT != null && (req == null || (reqT.qwHandle == hReq && reqT.FOpen))) // Is it a better match?
- {
- AssertImportant(reqT.HasCurrentConnection);
- if (reqT.HasCurrentConnection)
- {
- req = reqT;
- cxn = req.rgConnection[^1];
-#if DEBUG
- AssertImportant(!cxn.fOutdated);
-#endif // DEBUG
- }
- }
- break;
+ Connection cxn = null;
+ while (cxn == null && --iReq >= 0)
+ {
+ Request reqT = this[iReq];
- default:
- AssertCritical(false);
- break;
- }
+ if (reqT.pid != evt.ProcessId)
+ continue;
+
+ cxn = reqT.rgConnection?.FindLast(
+ c => c.qwConnection == qwCxn && c.socket.qwSocket == qwSocket && !c.socket.FClosed
+ );
}
- if (req == null) return null;
+ Socket socket;
- // Hereafter must not return null;
+ if (cxn != null)
+ {
+ // This Connection and its Socket will get shared with this Request.
+ // Both Requests may be open at the same time but Send/Receive operations cannot overlap, respectively.
+#if DEBUG
+ bool fSend = ((evt.Id + 1) & -2) == (int)WebIOTable.WIO.ConnectionSocketSend_Stop; // ConnectionSocketSent_Start/Stop
+ AssertImportant((fSend ? cxn.ctxSend : cxn.ctxRecv) == 0); // no overlapping, cross-Request conflicts!
- if (req.HasCurrentConnection)
+ cxn.socket.AddRef();
+ cxn.fOutdated = true;
+#endif // DEBUG
+ req.FShared = true;
+ socket = cxn.socket;
+ }
+ else
{
- AssertImportant(cxn != null);
- AssertImportant(cxn == req.rgConnection?.Find(c => c == cxn)); // cxn is in req.rgConnection[]
- return cxn;
+ socket = new Socket(in evt);
}
- // We didn't get a ConnectionSocketConnect event pair, apparently.
- // We must be reusing a Connection/Socket previously opened (PATTERN 4B).
- // Or this event occurs very near the beginning of the trace.
+ Connection cxnNew = new Connection(qwCxn, socket);
- req.FShared = true;
+ cxnNew.fDuplicate = cxn != null;
+#if DEBUG
+ cxnNew.fTransferred = true;
+#endif // DEBUG
+ AssertImportant(!cxnNew.socket.FClosed);
+ AssertImportant(cxnNew.socket.timeStart.HasValue());
+ cxnNew.socket.timeStop.SetMaxValue(); // no longer stopped
if (req.rgConnection == null)
req.rgConnection = new List(1);
else
AssertCritical(!req.HasCurrentConnection); // No duplicates!
- if (cxn == null)
- {
- // The socket might have been created before the trace started. (Rare!)
+ req.qwConnectionCur = qwCxn;
+ req.rgConnection.Add(cxnNew);
+
+ AssertCritical(req.HasCurrentConnection);
+ req.ConfirmValidity(); // valid qwConnectionCur
- Socket socket = new Socket(in evt);
+ cxnNew.TestSocket(in evt, qwCxn);
+ return cxnNew;
+ } // SetRequestConnection
- cxn = new Connection(evt.GetAddrValue("Connection"), socket);
- }
- else
+
+ /*
+ Return the Connection which matches:
+ - Request Handle (hReq) - param hReq not trusted
+ - Connection Id (qwCxn) - Request.qwConnectionCur not always trusted
+ - Context (ctx) - ctx==0 if Start, ctx!=0 if Stop
+ - Send/Recv (fSend) - true = Send, false = Receive
+ A Stop event can arrive after the Request has closed.
+
+ Find the most recent Request and its current Connection (if it exists):
+ - Matching Request handle, where hReq is not always trusted, particularly for Receive.
+ - Matching current Connection ID, where Request.qwConnectionCur is not always trusted.
+ - Matching Context: Start context uses 0; Stop context = Start context
+
+ It mat create a new Connection/Socket via SetRequestConnection.
+ */
+ public Connection GetConnection(in IGenericEvent evt)
+ {
+ AssertCritical(evt.Id >= (int)WebIOTable.WIO.ConnectionSocketSend_Start && evt.Id <= (int)WebIOTable.WIO.ConnectionSocketReceive_Stop);
+
+ bool fSend = ((evt.Id + 1) & -2) == (int)WebIOTable.WIO.ConnectionSocketSend_Stop; // ConnectionSocketSent_Start/Stop
+ bool fStart = (evt.Id & 1) != 0;
+
+ QWord ctx = fStart ? 0 : evt.GetAddrValue("Context");
+
+ QWord qwCxn = evt.GetAddrValue("Connection");
+ AssertCritical(qwCxn != 0);
+
+ QWord hReq = WebIOTable.GetHReq(in evt);
+ AssertImportant(hReq != 0);
+
+ for (int iReq = this.Count-1; iReq >= 0; --iReq)
{
- // Reusing a Connection attached to another Request.
+ Request req = this[iReq];
- AssertCritical(cxn.socket != null);
- AssertImportant(!cxn.socket.FClosed);
- AssertImportant(cxn.socket.timeStart.HasValue());
+ if (req.pid != evt.ProcessId)
+ continue;
- if (cxn.socket.FStopped)
- cxn.socket.timeStop = cxn.socket.timeClose; // infinity
- }
+ if (req.qwConnectionCur == 0)
+ continue;
- req.qwConnectionCur = cxn.qwConnection;
- req.rgConnection.Add(cxn);
+ // Check for a current Connection and match the Context value.
- AssertCritical(req.HasCurrentConnection);
+ Connection cxn = null;
+ if (req.HasCurrentConnection)
+ {
+ cxn = req.rgConnection[^1];
- return cxn;
- }
+ AssertCritical(cxn != null);
+
+ // The Connection's Context value MUST match, even 0==0.
+ QWord ctxCxn = fSend ? cxn.ctxSend : cxn.ctxRecv;
+ if (ctx != ctxCxn)
+ {
+ if (fStart || ctxCxn != 0)
+ continue;
+
+ if (qwCxn != req.qwConnectionCur)
+ continue;
+
+ // There's another, earlier Request with the same Connection?
+ if (cxn.fDuplicate)
+ continue;
+
+ // There is no Start context for this Stop event.
+ // This is probably near the beginning of the trace.
+ AssertImportant(iReq == 0); // iReq == small number
+ }
+
+ // If this is a Stop event then we've found the match and are done.
+ if (!fStart)
+ {
+ AssertImportant(qwCxn == req.qwConnectionCur);
+ cxn.TestSocket(in evt, qwCxn);
+ return cxn;
+ }
+ }
+ else if (!fStart)
+ {
+ // Stop event with no current Connection OR Send_Stop event with closed Connection.
+ continue;
+ }
+ else if (!req.FOpen)
+ {
+ // Closed Request with no current Connection!?
+ continue;
+ }
+
+ // Here we have only ConnectionSocketSend/Receive_Start events.
+ // The Request is not closed.
+ // It may have a current Connection, in which case: cxn != null
+
+ AssertCritical(fStart); // only Start events
+
+ if (req.qwConnectionCur == qwCxn)
+ {
+ AssertImportant(req.Validity != Request.EValidity.Dubious);
+ req.ConfirmValidity();
+
+ // The critical parameters match!
+ if (cxn != null)
+ {
+ cxn.TestSocket(in evt, qwCxn);
+ return cxn;
+ }
+
+ // Here we got a RequestWaitingForConnection event (with valid Connection ID),
+ // but no Connection/Socket creation events.
+ // That means it's a shared Connection.
+ }
+ else if (cxn == null)
+ {
+ // Here we have no current Connection and not a matching Connection ID.
+ // It might not be a match, but the Connection ID is not always trustable.
+ // This could be the famous case 4B! (See: WebIO.Socket.cs)
+
+ if (hReq != req.qwHandle)
+ continue;
+
+ AssertImportant(req.Validity == Request.EValidity.Dubious); // part of condition?
+ req.qwConnectionCur = qwCxn;
+ req.ConfirmValidity();
+ }
+ else // Has current Connection, but not a matching Connection ID.
+ {
+ AssertImportant(req.Validity != Request.EValidity.Dubious);
+ continue;
+ }
+
+ return SetRequestConnection(in evt, req, qwCxn, iReq);
+ } // for iReq
+
+ return null;
+ } // GetConnection
/*
@@ -489,7 +550,7 @@ public Connection EnsureConnectionTCB(IDVal pid, IDVal tid, uint iTCB, in Timest
// Socket (of the most recently created Connection) appears near the beginning of the trace, lacking full context.
// If the match is nearly certain, apply the TCB context.
cxn = req.rgConnection[^1];
- if ((fSend ? cxn.tidSend : cxn.tidRecv) == tid && cxn.tidTCB == 0 && cxn.socket.tidConnect == tid && cxn.socket.iTCB == 0 && !cxn.socket.FClosed)
+ if ((fSend ? cxn.tidSend : cxn.tidRecv) == tid && cxn.tidTCB == tidUnknown && cxn.socket.tidConnect == tid && cxn.socket.iTCB == 0 && !cxn.socket.FClosed)
{
cxn.tidTCB = tid;
cxn.socket.iTCB = iTCB;
@@ -516,7 +577,7 @@ public Connection EnsureConnectionTCB(IDVal pid, IDVal tid, uint iTCB, in Timest
/*
- Find the unique Socket created on the given time during the given time interval.
+ Find the unique Socket created on the given thread during the given time interval.
*/
public Socket CorrelateByTimeThread(uint iTCB, IDVal pid, IDVal tid, TimestampUI timeCreate, TimestampUI timeConnect)
{
@@ -550,6 +611,9 @@ public Socket CorrelateByTimeThread(uint iTCB, IDVal pid, IDVal tid, TimestampUI
}
+ /*
+ Search all the Requests to find the most recent Connection which matches the given parameters.
+ */
public Socket CorrelateByAddress(uint iTCB, uint iDNS, uint iAddr, IDVal pid, IDVal tid)
{
AssertImportant(iTCB != 0);
@@ -572,7 +636,7 @@ public Socket CorrelateByAddress(uint iTCB, uint iDNS, uint iAddr, IDVal pid, ID
#if DEBUG
// Search all Connections/Sockets of each open Request.
// We shouldn't find a match.
- req = this.FindLast(r => r.FOpen && r.rgConnection?.FindLast(c => c.MatchTCB(iTCB, iDNS, iAddr, tid)) != null);
+ req = this.FindLast(r => r.pid == pid && r.FOpen && r.rgConnection?.FindLast(c => c.MatchTCB(iTCB, iDNS, iAddr, tid)) != null);
AssertImportant(req == null);
#endif // DEBUG
@@ -580,6 +644,10 @@ public Socket CorrelateByAddress(uint iTCB, uint iDNS, uint iAddr, IDVal pid, ID
} // CorrelateByAddress
+ /*
+ Handle the event: RequestHeader_Recv
+ Extrace the Header string and add it to the corresponding Connection.
+ */
public void AddHeader(in IGenericEvent evt)
{
Request req;
@@ -619,9 +687,13 @@ public void AddHeader(in IGenericEvent evt)
AssertInfo(cxn.strHeader == null);
if (cxn.strHeader == null || !cxn.strHeader.StartsWith("HTTP", StringComparison.OrdinalIgnoreCase) || strHeader.StartsWith("HTTP", StringComparison.OrdinalIgnoreCase))
cxn.strHeader = strHeader;
- }
+ } // AddHeader
+ /*
+ Handle the event: ConnectionSocketClose
+ Find the corresponding Connection(s) and close the attached Socket.
+ */
public void CloseSocket(in IGenericEvent evt)
{
// ConnectionSocketClose behaves like Stop in the case of (potentially) multiple Sockets. (See PATTERN 2, elsewhere.)
@@ -635,9 +707,9 @@ public void CloseSocket(in IGenericEvent evt)
if (cxn != null)
{
- cxn.tidSend = 0;
- cxn.tidRecv = 0;
- cxn.tidTCB = 0;
+ cxn.tidSend = tidUnknown;
+ cxn.tidRecv = tidUnknown;
+ cxn.tidTCB = tidUnknown;
socket = cxn.socket;
#if DEBUG
@@ -667,9 +739,9 @@ public void CloseSocket(in IGenericEvent evt)
if (cxn == null) continue;
// There may be some other Connections which share this Socket whose ThreadID values are not cleared here.
- cxn.tidTCB = 0;
- cxn.tidSend = 0;
- cxn.tidRecv = 0;
+ cxn.tidTCB = tidUnknown;
+ cxn.tidSend = tidUnknown;
+ cxn.tidRecv = tidUnknown;
socket = cxn.socket;
#if DEBUG
@@ -685,6 +757,10 @@ public void CloseSocket(in IGenericEvent evt)
} // CloseSocket
+ /*
+ Handle the event: RequestCreate
+ Create a new Request, attach it to a Session, and apply a Stackwalk.
+ */
public void AddRequest(in IGenericEvent evt, in AllTables allTables)
{
Request request = new Request(in evt);
@@ -743,6 +819,10 @@ public void AddRequest(in IGenericEvent evt, in AllTables allTables)
} // AddRequest
+ /*
+ Handle the event: RequestClose_Stop
+ Find the corresponding Request, tidy it up, and mark it as closed.
+ */
public Request CloseRequest(QWord qwRequest, QWord qwHandle, IDVal pid, in TimestampUI timeStamp)
{
Request request = FindOpenRequestByHReq(qwHandle, pid);
diff --git a/src/NetBlame/Providers/WebIO.Socket.cs b/src/NetBlame/Providers/WebIO.Socket.cs
index 7098e42..5ee7d52 100644
--- a/src/NetBlame/Providers/WebIO.Socket.cs
+++ b/src/NetBlame/Providers/WebIO.Socket.cs
@@ -7,7 +7,6 @@
using IDVal = System.Int32; // type of Event.pid/tid / ideally: System.UInt32
using QWord = System.UInt64;
-using System.Diagnostics;
/*
diff --git a/src/NetBlame/Providers/WebIO.cs b/src/NetBlame/Providers/WebIO.cs
index ab334f1..6b56e4d 100644
--- a/src/NetBlame/Providers/WebIO.cs
+++ b/src/NetBlame/Providers/WebIO.cs
@@ -48,7 +48,7 @@ public enum WIO
RequestClose_Start = 19, // unused
SessionClose_Stop = 29, // unused
RequestClose_Stop = 30,
- RequestHeader_Send = 100, // unused
+ RequestHeader_Send = 100,
RequestHeader_Recv = 101,
RequestWaitingForConnection_Stop = 104,
RequestSend_Start = 130, // unused
@@ -113,20 +113,25 @@ static public bool IsSocketReceiveStopClose(in IGenericEvent evt)
}
+ /*
+ Get a value which represents a Request handle, derived from the ActivityId.
+ This value is not always the handle of the true corresponding Request.
+ */
static public QWord GetHReq(in IGenericEvent evt)
{
- AssertCritical(evt.Id == (int)WIO.RequestWaitingForConnection_Stop
+ AssertCritical(evt.Id == (int)WIO.RequestWaitingForConnection_Stop // always valid?
|| evt.Id == (int)WIO.ConnectionSocketCreate
|| evt.Id == (int)WIO.ConnectionSocketConnect_Start
|| evt.Id == (int)WIO.ConnectionSocketConnect_Stop
|| evt.Id == (int)WIO.ConnectionSocketConnect_Stop2
|| evt.Id == (int)WIO.ConnectionSocketClose // valid in one case
- || evt.Id == (int)WIO.RequestHeader_Recv // sometimes invalid
- || evt.Id == (int)WIO.ConnectionSocketSend_Start // rarely invalid
- || evt.Id == (int)WIO.ConnectionSocketSend_Stop // rarely invalid
+ || evt.Id == (int)WIO.RequestHeader_Send
+ || evt.Id == (int)WIO.RequestHeader_Recv
+ || evt.Id == (int)WIO.ConnectionSocketSend_Start
+ || evt.Id == (int)WIO.ConnectionSocketSend_Stop
|| evt.Id == (int)WIO.ConnectionSocketReceive_Start
|| evt.Id == (int)WIO.ConnectionSocketReceive_Stop
- || evt.Id == (int)WIO.ConnectionNameResolutionRequest_Start
+ || evt.Id == (int)WIO.ConnectionNameResolutionRequest_Start // always valid?
|| evt.Id == (int)WIO.ConnectionNameResolutionRequest_Stop
|| evt.Id >= (int)ThreadAction.First && evt.Id <= (int)ThreadAction.Last);
@@ -134,6 +139,36 @@ static public QWord GetHReq(in IGenericEvent evt)
return BitConverter.ToUInt64(evt.ActivityId.ToByteArray());
}
+ /*
+ Get a value which is probably the Request Id.
+ The low 32 bits are in the ActivityId.
+ The high 32 bits are inferred from an adjacent allocation within the same heap.
+ */
+ static public QWord GuessRequest(in IGenericEvent evt, QWord qw)
+ {
+ uint dw = BitConverter.ToUInt32(evt.ActivityId.ToByteArray(), 8);
+ qw = (qw & 0xFFFFFFFF00000000) | dw;
+ return qw;
+ }
+
+ Request RestoreRequest(in IGenericEvent evt, QWord qwReq)
+ {
+ Request req = new Request(evt, GetHReq(in evt)); // GetHReq not always dependable here, bit it's the best we've got.
+ req.qwRequest = qwReq;
+ req.xlink.ReGetLink(evt.ThreadId, in req.timeOpen, in allTables.threadTable); // Useful??
+ this.requestTable.Add(req);
+ return req;
+ }
+
+ Request RestoreRequestCxn(in IGenericEvent evt, QWord qwCxn)
+ {
+ Request req = RestoreRequest(in evt, GuessRequest(in evt, qwCxn));
+ req.qwConnectionCur = qwCxn;
+ req.ConfirmValidity();
+ return req;
+ }
+
+
const uint S_OK = 0;
public void Dispatch(in IGenericEvent evt)
@@ -169,7 +204,26 @@ public void Dispatch(in IGenericEvent evt)
timeStamp = evt.Timestamp.ToGraphable();
req = requestTable.CloseRequest(evt.GetAddrValue("ApiObject")/*qwReq*/, evt.GetUInt64("ApiHandle")/*hReq*/, evt.ProcessId, in timeStamp);
break;
-
+#if DEBUG
+ case WIO.RequestHeader_Send:
+ try
+ {
+ // Parses ALL of this event's fields.
+ qw = evt.GetAddrValue("Request");
+ req = requestTable.FindRequest(qw, evt.ProcessId, evt.Timestamp.ToGraphable());
+ AssertImportant(req != null);
+ req?.HandleValidity(in evt, true);
+ }
+ catch
+ {
+ // Parsing the "Headers" field threw an exception.
+ hReq = GetHReq(in evt); // not a field
+ req = requestTable.FindOpenRequestByHReq(hReq, evt.ProcessId);
+ AssertImportant(req != null);
+ req?.HandleValidity(in evt, false);
+ }
+ break;
+#endif // DEBUG
case WIO.RequestHeader_Recv:
requestTable.AddHeader(in evt);
break;
@@ -182,12 +236,11 @@ public void Dispatch(in IGenericEvent evt)
AssertInfo(req != null);
if (req == null)
- {
- req = new Request(in evt, GetHReq(in evt)); // GetHReq not always dependable here, bit it's the best we've got.
- req.qwConnectionCur = qw;
- }
+ req = RestoreRequestCxn(in evt, qw);
+ AssertInfo(req.qwRequest == GuessRequest(in evt, qw));
AssertImportant(req.FOpen);
+ req.ConfirmValidity(); // valid qwConnectionCur
if (req.rgConnection == null)
req.rgConnection = new List((int)evt.GetUInt64("RemainingAddressCount"));
@@ -236,8 +289,15 @@ public void Dispatch(in IGenericEvent evt)
case WIO.ConnectionSocketSend_Start:
AssertImportant(evt.GetUInt32("Error") == S_OK);
AssertImportant(evt.GetUInt64("Information") == 0);
+
cxn = requestTable.GetConnection(in evt);
- if (cxn == null) break;
+ if (cxn == null)
+ {
+ qw = evt.GetAddrValue("Connection");
+ req = RestoreRequestCxn(in evt, qw);
+ cxn = requestTable.SetRequestConnection(in evt, req, qw, -1);
+ AssertCritical(req.rgConnection[^1] == cxn);
+ }
// Unfortunately we can't get a cbSend directly (in older versions of Windows), like we can for cbRecv.
// Attract a TCB correlation via TCP.SendPosted. See SendSocketTCB.
@@ -245,14 +305,13 @@ public void Dispatch(in IGenericEvent evt)
AssertImportant(cxn.cbSendTCB == 0);
AssertImportant(cxn.tidSend == 0);
cxn.tidSend = evt.ThreadId;
+ cxn.ctxSend = evt.GetAddrValue("Context");
break;
case WIO.ConnectionSocketSend_Stop:
cxn = requestTable.GetConnection(in evt);
if (cxn == null) break;
- AssertImportant(FImplies(cxn.tidSend != 0, cxn.tidSend == evt.ThreadId)); // Start & Stop are on the same thread!
-
AssertImportant(cxn.error == S_OK);
cxn.error = evt.GetUInt32("Error");
@@ -272,6 +331,7 @@ public void Dispatch(in IGenericEvent evt)
// Stop attracting a TCB correlation via TCP.SendPosted.
cxn.cbSendTCB = 0;
cxn.tidSend = 0;
+ cxn.ctxSend = 0;
break;
case WIO.ConnectionSocketReceive_Start:
@@ -279,9 +339,16 @@ public void Dispatch(in IGenericEvent evt)
AssertImportant(evt.GetUInt64("Information") == 0);
cxn = requestTable.GetConnection(in evt);
- if (cxn == null) break;
+ if (cxn == null)
+ {
+ qw = evt.GetAddrValue("Connection");
+ req = RestoreRequestCxn(in evt, qw);
+ cxn = requestTable.SetRequestConnection(in evt, req, qw, -1);
+ AssertCritical(req.rgConnection[^1] == cxn);
+ }
cxn.tidRecv = evt.ThreadId; // for correlation with WebIO, not necessarily with Stop
+ cxn.ctxRecv = evt.GetAddrValue("Context");
break;
case WIO.ConnectionSocketReceive_Stop:
@@ -291,10 +358,15 @@ public void Dispatch(in IGenericEvent evt)
if (cbRecv == 0) break;
cxn = requestTable.GetConnection(in evt);
- if (cxn == null) break;
+ if (cxn == null)
+ {
+ qw = evt.GetAddrValue("Connection");
+ req = RestoreRequestCxn(in evt, qw);
+ cxn = requestTable.SetRequestConnection(in evt, req, qw, -1);
+ AssertCritical(req.rgConnection[^1] == cxn);
+ }
// NOTE: The Socket can continue to receive data even after the Request is closed!
-
// Even if the server sent some more data, it doesn't count for a closed Socket.
if (cxn.socket.FClosed) break;
@@ -303,18 +375,17 @@ public void Dispatch(in IGenericEvent evt)
#if DEBUG
cxn.socket.cbRecv += (uint)cbRecv;
#endif // DEBUG
+ cxn.ctxRecv = 0;
break;
case WIO.RequestWaitingForConnection_Stop:
timeStamp = evt.Timestamp.ToGraphable();
qw = evt.GetAddrValue("Request");
req = requestTable.FindRequest(qw, evt.ProcessId, timeStamp);
- AssertImportant(req == requestTable.FindOpenRequestByHReq(GetHReq(in evt), evt.ProcessId));
if (req == null)
- {
- req = new Request(evt, GetHReq(in evt));
- req.qwRequest = qw;
- }
+ req = RestoreRequest(in evt, qw);
+
+ AssertImportant(req == requestTable.FindOpenRequestByHReq(GetHReq(in evt), evt.ProcessId));
// This Connection value may later be abandoned in an abbreviated Connection (PATTERN 4B).
// This Connection might already exist, and it (its Socket) needs to be transferred from another Request.
@@ -344,6 +415,7 @@ public void Dispatch(in IGenericEvent evt)
cxn.fOutdated = false;
cxn.fTransferred = true;
#endif // DEBUG
+ cxn.fDuplicate = true;
}
// else this Connection will be added later: ConnectionSocketConnect_Start
@@ -354,12 +426,8 @@ public void Dispatch(in IGenericEvent evt)
qw = evt.GetAddrValue("DnsQuery"); // DnsQuery = Connection
hReq = GetHReq(in evt);
req = requestTable.FindOpenRequestByHReq(hReq, evt.ProcessId);
- AssertInfo(req != null);
if (req == null)
- {
- req = new Request(in evt, hReq);
- req.qwConnectionCur = qw;
- }
+ req = RestoreRequestCxn(in evt, qw);
AssertImportant(req.FOpen);
AssertImportant(req.qwConnectionCur == qw);
@@ -380,12 +448,9 @@ public void Dispatch(in IGenericEvent evt)
case WIO.ConnectionNameResolutionRequest_Stop:
qw = evt.GetAddrValue("DnsQuery"); // DnsQuery = Connection
req = requestTable.FindOpenRequestByConnection(qw, evt.ProcessId);
- AssertInfo(req != null);
if (req == null)
- {
- req = new Request(in evt, GetHReq(in evt)); // GetHReq not always dependable here, but it's the best we've got.
- req.qwConnectionCur = qw;
- }
+ req = RestoreRequestCxn(in evt, qw);
+
AssertImportant(req.FOpen);
req.strServer = allTables.dnsTable.ConnectNameResolution(in evt, req.strServer);
@@ -395,7 +460,7 @@ public void Dispatch(in IGenericEvent evt)
if ((ThreadAction)evt.Id >= ThreadAction.First && (ThreadAction)evt.Id <= ThreadAction.Last)
{
// OverlappedIO records are useful. WorkItem records, too. Timer records are not.
-
+
ActionType actionType = (ActionType)evt.GetUInt32("EtwQueueActionType");
AssertImportant(actionType == ActionType.OverlappedIO ||
actionType == ActionType.Timer ||
@@ -408,4 +473,4 @@ public void Dispatch(in IGenericEvent evt)
} // switch
} // Dispatch
} // WebIOTable
-} // NetBlameCustomDataSource.WebIO
\ No newline at end of file
+} // NetBlameCustomDataSource.WebIO
diff --git a/src/NetBlame/Providers/WinHTTP.cs b/src/NetBlame/Providers/WinHTTP.cs
index 8bce395..c4abccb 100644
--- a/src/NetBlame/Providers/WinHTTP.cs
+++ b/src/NetBlame/Providers/WinHTTP.cs
@@ -11,7 +11,7 @@
using static NetBlameCustomDataSource.Util; // Assert*
using QWord = System.UInt64;
-using IDVal = System.Int32; // type of Event.pid/tid / ideally: System.UInt32
+using IDVal = System.Int32; // type of Event.pid/tid
using System.Collections.Generic;
@@ -125,8 +125,10 @@ public class CallbackEvent : TaskItem, ITaskItemInfo
public int actHash;
public ActionType actType;
- public CallbackEvent(QWord qwContext, int actHash, ActionType action, IDVal pid, IDVal tid, TimestampUI timeStamp)
- : base(pid, tid, timeStamp)
+ public const IDVal tidUnknown = -1;
+
+ public CallbackEvent(QWord qwContext, int actHash, ActionType action, IDVal pid, IDVal tid, in TimestampUI timeStamp)
+ : base(pid, tid, in timeStamp)
{
this.qwContext = qwContext;
this.actHash = actHash;
@@ -134,8 +136,8 @@ public CallbackEvent(QWord qwContext, int actHash, ActionType action, IDVal pid,
this.state = EState.Created;
}
- public bool FQueued => this.tidCreate != 0;
- public bool FStarted => this.tidExec != 0;
+ public bool FQueued => this.tidCreate != tidUnknown;
+ public bool FStarted => this.tidExec != tidUnknown;
public bool FOnlyCreated => this.state == EState.Created;
public bool FOnlyStarted => this.state == EState.StartExec;
public bool FStopped => this.state == EState.EndExec;
@@ -175,10 +177,6 @@ private void Add(in CallbackEvent cbt) // override
hashContext[cbt.qwContext] = (uint)this.Count; // 1-based index, single-threaded
}
-#if DEBUG
- int cTableCount;
-#endif // DEBUG
-
/*
Return the most recent worker object with the given context and time range.
*/
@@ -188,7 +186,7 @@ CallbackEvent FindCallback(QWord qwContext, int actHash, IDVal tid, ThreadAction
for (uint i = IFromContext(qwContext); i > 0; i = cbe.iNext)
{
- if (i == 0) break;
+ AssertCritical(i != 0);
cbe = EventFromI(i);
AssertCritical(cbe.qwContext == qwContext);
@@ -270,14 +268,15 @@ public void DoDispatchEvent(in IGenericEvent evt, ActionType action)
AssertCritical(cbe.actHash == actHash);
}
- TimestampUI timeStamp = evt.Timestamp.ToGraphable();
+ TimestampUI timeStamp;
switch ((ThreadAction)evt.Id)
{
case ThreadAction.Queue:
if (cbe == null)
{
- cbe = new CallbackEvent(qwContext, actHash, action, evt.ProcessId, evt.ThreadId, timeStamp);
+ timeStamp = evt.Timestamp.ToGraphable();
+ cbe = new CallbackEvent(qwContext, actHash, action, evt.ProcessId, evt.ThreadId, in timeStamp);
Add(cbe);
}
else
@@ -288,7 +287,7 @@ public void DoDispatchEvent(in IGenericEvent evt, ActionType action)
AssertImportant(!cbe.FOnlyCreated);
AssertImportant(cbe.FInverted);
// This is a thread inversion / race condition. There should be no long delays.
- AssertImportant(timeStamp.ToMilliseconds - cbe.timeCreate.ToMilliseconds < 100);
+ AssertImportant(evt.Timestamp.ToGraphable().ToMilliseconds - cbe.timeCreate.ToMilliseconds < 100);
cbe.tidCreate = evt.ThreadId; // cbe.FQueued = true
}
@@ -301,10 +300,11 @@ public void DoDispatchEvent(in IGenericEvent evt, ActionType action)
break;
case ThreadAction.Start:
+ timeStamp = evt.Timestamp.ToGraphable();
if (cbe == null)
{
// Case 6!? Or the Enqueue event happened before the start of the trace.
- cbe = new CallbackEvent(qwContext, actHash, action, evt.ProcessId, 0/*tidCreate*/, timeStamp);
+ cbe = new CallbackEvent(qwContext, actHash, action, evt.ProcessId, CallbackEvent.tidUnknown/*tidCreate*/, in timeStamp);
Add(cbe);
AssertImportant(!cbe.FQueued);
@@ -320,7 +320,7 @@ public void DoDispatchEvent(in IGenericEvent evt, ActionType action)
AssertImportant(cbe.timeDestroy.HasMaxValue());
AssertImportant(cbe.cRef == 0);
- cbe.StartExec(evt.ThreadId, timeStamp);
+ cbe.StartExec(evt.ThreadId, in timeStamp);
// Remember the most recent StartExec on this thread.
this.allTables.threadTable.StartExec(this, cbe);
@@ -339,7 +339,8 @@ public void DoDispatchEvent(in IGenericEvent evt, ActionType action)
AssertImportant(cbe.tidExec == evt.ThreadId);
AssertImportant(cbe.FOnlyStarted);
- cbe.EndExec(timeStamp);
+ timeStamp = evt.Timestamp.ToGraphable();
+ cbe.EndExec(in timeStamp);
// We must disable garbage collection:
// We're indexing into the table with the per-Context lists hashed and linked.
@@ -351,12 +352,12 @@ public void DoDispatchEvent(in IGenericEvent evt, ActionType action)
AssertInfo(cbe != null);
if (cbe == null) break;
- AssertImportant(cbe.FQueued);
+ AssertInfo(cbe.FQueued); // maybe near the trace start
AssertImportant(cbe.timeCreate.HasValue());
AssertImportant(cbe.timeDestroy.HasMaxValue());
if (cbe.FOnlyCreated)
- {
+ {
// case 2 - Canceling
AssertImportant(!cbe.timeStartExec.HasValue());
AssertImportant(!cbe.timeEndExec.HasValue());
@@ -364,23 +365,17 @@ public void DoDispatchEvent(in IGenericEvent evt, ActionType action)
Finish(cbe, false /*fGC*/);
cbe.state = EState.Canceled;
- }
+ }
else // FOnlyStarted
- {
+ {
// case 3 - Ignore this event.
AssertImportant(cbe.FOnlyStarted);
AssertImportant(cbe.timeStartExec.HasValue());
AssertImportant(cbe.timeEndExec.HasMaxValue());
AssertImportant(cbe.tidExec == evt.ThreadId);
- }
+ }
break;
} // switch
-
-#if DEBUG
- // No garbage collection for this table.
- AssertCritical(this.Count >= cTableCount);
- cTableCount = this.Count;
-#endif // DEBUG
} // DoDispatchEvent
public void Dispatch(IGenericEvent evt)
diff --git a/src/NetBlame/Providers/WinINet.cs b/src/NetBlame/Providers/WinINet.cs
index 92da7cf..2778377 100644
--- a/src/NetBlame/Providers/WinINet.cs
+++ b/src/NetBlame/Providers/WinINet.cs
@@ -15,7 +15,7 @@
using TimestampETW = Microsoft.Windows.EventTracing.TraceTimestamp;
using TimestampUI = Microsoft.Performance.SDK.Timestamp;
-using IDVal = System.Int32; // type of Event.pid/tid / ideally: System.UInt32
+using IDVal = System.Int32;
using QWord = System.UInt64;
@@ -158,7 +158,7 @@ public class Request : Gatherable, IGraphableEntry
public readonly IDVal pid; // for correlation
public IDVal tid1; // for correlation
- public IDVal tid2;
+ public IDVal tid2; // Wininet_Connect.Start
public uint iDNS;
public uint iAddr;
@@ -201,6 +201,7 @@ public Request(QWord hConnection, string strMethod, IDVal pid, in TimestampETW t
this.timeClose1.SetMaxValue();
this.timeClose2.SetMaxValue();
this.pid = pid;
+ this.tid1 = this.tid2 = WinINetTable.tidUnknown;
this.strMethod = strMethod;
}
@@ -221,6 +222,8 @@ public class WinINetTable : List
public WinINetTable(int capacity, in AllTables _allTables) : base(capacity) { this.allTables = _allTables; }
+ public const IDVal tidUnknown = -1;
+
/*
Find the request in the given Process with the given Connect handle,
and either the same Context or not yet initialized.
@@ -274,7 +277,7 @@ Close the Request by setting the various fields.
The remote address (sockAddr) usually has a port.
Redundant calls are fine.
*/
- public void StopRequest(in Request req, in SocketAddress sockAddr)
+ public void StopRequest(Request req, in SocketAddress sockAddr)
{
AssertInfo(req.qwConnect != 0);
AssertImportant(req.timeSend.HasValue());
@@ -285,9 +288,10 @@ public void StopRequest(in Request req, in SocketAddress sockAddr)
if (req.addrRemote.Empty())
{
+ int iTCB;
uint iDNSNew = 0; // none
uint iAddrNew = 1; // first
- if (sockAddr != null)
+ if (!sockAddr.Empty())
{
// This is the remote address, and it usually has a port.
// The other port value is that of the server request...not always the same.
@@ -297,6 +301,19 @@ public void StopRequest(in Request req, in SocketAddress sockAddr)
req.addrRemote = ipep;
}
+ else if ((iTCB = this.allTables.tcpTable.FindLastIndex(t => t.pid == req.pid && t.socket == req.socket && t.tid == req.tid2)) >= 0)
+ {
+ TcpIp.TcbRecord tcbT = this.allTables.tcpTable[iTCB];
+
+ req.addrRemote = tcbT.addrRemote;
+
+ AssertImportant(req.iTCB == 0);
+
+ if (req.iTCB == 0)
+ req.iTCB = (uint)(iTCB + 1);
+
+ tcbT.SetType(Protocol.WinINet);
+ }
else if (req.iDNS != 0)
{
iDNSNew = req.iDNS;
@@ -338,7 +355,10 @@ public void StopRequest(in Request req, in SocketAddress sockAddr)
// Without an IP address, there's nothing more that can be done!
if (req.addrRemote.Empty())
+ {
+ req.addrRemote = new IPEndPoint(0, 0);
return;
+ }
}
#if DEBUG
else if (sockAddr != null)
@@ -374,7 +394,7 @@ public void StopRequest(in Request req, in SocketAddress sockAddr)
if (req.iTCB == 0)
req.iTCB = this.allTables.tcpTable.CorrelateByAddress(in req.addrRemote, req.pid, req.tid2, req.socket, Protocol.WinINet);
- WinsockAFD.Connection cxn = this.allTables.wsTable.CorrelateByAddress(req.addrRemote, req.iTCB, req.socket, req.pid, 0/*tid*/);
+ WinsockAFD.Connection cxn = this.allTables.wsTable.CorrelateByAddress(req.addrRemote, req.iTCB, req.socket, req.pid, WinsockAFD.WinsockTable.tidUnknown);
if (cxn != null)
{
@@ -384,6 +404,8 @@ public void StopRequest(in Request req, in SocketAddress sockAddr)
#if DEBUG
if (req.cxnWinsock == null)
req.cxnWinsock = cxn;
+ else
+ AssertImportant(req.cxnWinsock == cxn);
#endif // DEBUG
}
}
@@ -503,7 +525,7 @@ public void Dispatch(in IGenericEvent evt)
// This may occur once or twice in the lifetime of a request, and if twice then its parameters will match the first.
// In fact the HTTPRequest_Start/Stop events can seem rather spurious.
- QWord qwContext = evt.GetAddrValue("Context"); // often 0!
+ QWord qwContext = evt.TryGetAddrValue("Context"); // often 0!
timeStamp = evt.Timestamp.ToGraphable();
req = FindRequestByCxn(evt.GetAddrValue("HINTERNET"), qwContext, evt.ProcessId, timeStamp);
AssertImportant(req != null);
@@ -516,7 +538,7 @@ public void Dispatch(in IGenericEvent evt)
if (req.qwContext == 0)
req.qwContext = qwContext;
- if (req.tid1 == 0)
+ if (req.tid1 == tidUnknown)
req.tid1 = evt.ThreadId;
break;
@@ -537,8 +559,7 @@ public void Dispatch(in IGenericEvent evt)
if (req.socket == 0)
req.socket = (ushort)socket;
- else
- AssertImportant(req.socket == socket);
+ // else see how a different socket is eventually handled in case WINET.Connect_Stop
break;
@@ -556,8 +577,8 @@ public void Dispatch(in IGenericEvent evt)
{
// Case 2 above: Keep Alive / Connection Reused
- AssertImportant(req.tid1 != 0);
- AssertImportant(req.tid2 != 0);
+ AssertImportant(req.tid1 != tidUnknown);
+ AssertImportant(req.tid2 != tidUnknown);
AssertImportant(req.strURL != null);
AssertImportant(req.fStopped);
AssertImportant(req.timeSend.HasValue());
@@ -581,7 +602,7 @@ public void Dispatch(in IGenericEvent evt)
req = reqNew;
}
- AssertImportant(req.tid2 == 0);
+ AssertImportant(req.tid2 == tidUnknown);
AssertCritical(req.tid1 == evt.ThreadId);
AssertCritical(req.qwRequest == 0); // Case2 should already be covered above!
AssertCritical(req.timeClose1.HasMaxValue());
@@ -620,7 +641,7 @@ public void Dispatch(in IGenericEvent evt)
AssertInfo(req.timeClose2.HasMaxValue()); // Sometimes WINET.HandleClosed comes early.
AssertImportant(req.strURL != null);
AssertImportant(req.strStatus == null);
- AssertInfo(req.tid2 != 0); // no Connect_Start
+ AssertInfo(req.tid2 != tidUnknown); // no Connect_Start
if (req.timeClose1.HasMaxValue())
req.timeClose1 = evt.Timestamp.ToGraphable();
@@ -653,7 +674,7 @@ public void Dispatch(in IGenericEvent evt)
AssertImportant(req.timeClose2.HasMaxValue());
AssertImportant(req.strURL != null);
AssertImportant(req.strStatus == null);
- AssertInfo(req.tid2 != 0); // no Connect_Start
+ AssertInfo(req.tid2 != tidUnknown); // no Connect_Start
timeStamp = evt.Timestamp.ToGraphable();
@@ -733,8 +754,8 @@ public void Dispatch(in IGenericEvent evt)
if (req.timeClose2.HasMaxValue())
req.timeClose2 = timeStamp;
- AssertImportant(req.tid1 != 0);
- // If req.tid2==0 then there was no Connect.Start event. Too bad.
+ AssertImportant(req.tid1 != tidUnknown);
+ // If req.tid2==tidUnknown then there was no Connect.Start event. Too bad.
StopRequest(req, null);
diff --git a/src/NetBlame/Providers/WinsockAFD.cs b/src/NetBlame/Providers/WinsockAFD.cs
index d2167b7..52eb327 100644
--- a/src/NetBlame/Providers/WinsockAFD.cs
+++ b/src/NetBlame/Providers/WinsockAFD.cs
@@ -4,6 +4,8 @@
using System;
using System.Collections.Generic; // List<>
using System.Net;
+using System.Net.Sockets;
+using System.Runtime.CompilerServices; // MethodImpl
using Microsoft.Windows.EventTracing.Events;
using Microsoft.Windows.EventTracing.Symbols;
@@ -20,7 +22,6 @@
using IDVal = System.Int32; // Process/ThreadID (ideally UInt32)
using ProcessHash = System.Collections.Generic.Dictionary;
-using System.Security.Cryptography;
namespace NetBlameCustomDataSource.WinsockAFD
@@ -81,6 +82,8 @@ public class Connection : IGraphableEntry
public TimestampUI timeClose;
public AddrVal qwEndpoint; // not unique
+ public AddrVal hProcess; // WinsockTable.hashProcess maps hProcess -> pid ... when available.
+
public IDVal pid;
public IDVal tidOpen;
public IDVal tidConnect; // Connect*WithAddress / AcceptExWithAddress
@@ -90,8 +93,10 @@ public class Connection : IGraphableEntry
public uint iTCB; // 1-based
#if DEBUG
public uint cbSendNested; // redundant
+ public SocketAddress addrLocal;
#endif
public uint cbSend;
+ public uint cbSendConnect; // Connect*WithAddress
public uint cbRecv;
public uint status;
@@ -124,23 +129,26 @@ public Connection(AddrVal qwEndpoint, IDVal pid, IDVal tid, in TimestampETW time
this.timeClose.SetMaxValue();
}
- public Connection()
- {
- this.qwEndpoint = AddrVal.MaxValue;
- }
-
public bool MatchAddr(uint iTCB, uint socket, IDVal pid, IDVal tid, IPEndPoint addrRemote)
{
if (this.socket != socket) return false;
- if (this.pid != pid) return false;
- if (tid != 0 && this.tidConnect != tid) return false;
+ if (this.pid != WinsockTable.pidUnknown && this.pid != pid) return false;
+ if (tid != WinsockTable.tidUnknown && this.tidConnect != tid) return false;
if (this.iTCB != 0)
return this.iTCB == iTCB;
else
return this.addrRemote?.Equals(addrRemote) ?? false;
}
+ public bool MatchUDPAddr(IDVal pid, ushort socket, IPEndPoint addr)
+ {
+ if (this.ipProtocol != IPPROTO.UDP) return false;
+ if (this.socktype == SOCKTYPE.SOCK_RAW) return false;
+ if (this.pid != pid) return false;
+ if (this.socket != 0 && this.socket != socket) return false;
+ return (this.addrRemote?.Equals(addr) ?? false);
+ }
public Connection Clone(in IPEndPoint ipAddr)
{
@@ -174,21 +182,9 @@ public class WinsockTable : List
public WinsockTable(int capacity, in AllTables _allTables) : base(capacity) { this.allTables = _allTables; }
- const IDVal pidUnknown = -1;
- const IDVal tidUnknown = -1;
-
-
- void TrySetProcess(in IGenericEvent evt, IDVal pid)
- {
- AssertImportant(pid != 0);
- AssertCritical(pid != pidUnknown);
-
- AddrVal hProcess = evt.GetAddrValue("Process");
- if (!this.hashProcess.TryGetValue(hProcess, out IDVal pidHash))
- this.hashProcess[hProcess] = pid;
- else
- AssertImportant(pidHash == pid);
- }
+ public const IDVal pidUnknown = -1;
+ public const IDVal tidUnknown = -1;
+ public const IDVal pidSystem = 4;
// This list/table can shrink (CloseConnection). Do not hold indices.
@@ -198,70 +194,129 @@ void TrySetProcess(in IGenericEvent evt, IDVal pid)
/*
Return the most recent, matching, open Connection record.
+ Used by AFD.BindWithAddress/.Connect*WithAddress/.AcceptExWithAddress
+ to match the most recent AFD.Create (not closed) with the given Endpoint value.
HIGH TRAFFIC FUNCTION!
*/
- Connection FindConnection(AddrVal qwEndpoint)
+ Connection FindConnection(AddrVal qwEndpoint, AddrVal hProc)
{
Connection cxn = this.FindLast(c => c.qwEndpoint == qwEndpoint);
- if (cxn == null || cxn.FClosed)
+ if (cxn == null)
return null;
- return cxn;
+ if (cxn.FClosed)
+ return null;
+
+ // Here we may have another chance to set the Process ID.
+ ConfirmProcess(cxn);
+
+ AssertCritical(cxn.hProcess == hProc);
+
+ return (cxn.hProcess == hProc) ? cxn : null;
}
/*
Try to find the connection, which may be closed.
- If it IS closed then do extra validation.
- Do this to complete an outstanding Send or Receive.
+ If it IS closed then do extra validation: confirm it's the same process.
+ Used with: AFD.Send/.Receive
Can return null!
*/
- Connection FindConnection2(AddrVal qwEndpoint, in IGenericEvent evt)
+ Connection FindConnection2(AddrVal qwEndpoint, AddrVal hProc)
{
Connection cxn = this.FindLast(c => c.qwEndpoint == qwEndpoint);
if (cxn == null)
return null;
- IDVal pid = pidUnknown;
+ // Here we may have another chance to set the Process ID.
+ ConfirmProcess(cxn);
// If this Connection is closed, then make sure it's not from another process.
// If this Connection is open, then it should NEVER be from another process.
- if (!cxn.FClosed)
+ AssertCritical(FImplies(!cxn.FClosed, cxn.hProcess == hProc));
+
+ return (cxn.hProcess == hProc) ? cxn : null;
+ }
+
+ /*
+ Find the most recent UDP Connection with the given Endpoint (and Process):
+ With the same address (.addrRemote), OR
+ With a null address, OR
+ The most recent with a different address,
+ Else null
+ HandleUDPAddress will take care of it from there.
+ */
+ Connection FindConnectionUDP(AddrVal qwEndpoint, AddrVal hProc, in IPEndPoint addr)
+ {
+ Connection cxn = FindConnection2(qwEndpoint, hProc);
+
+ // Found the most recent (possibly closed?) Connection with the given Endpoint and Process.
+ // Now find a duplicate Connection with the same IPEndPoint (.addrRemote), if any.
+ // Because that's what UDP does.
+
+ for (Connection cxnFound = cxn; cxnFound != null; cxnFound = cxnFound.cxnNext)
{
- AssertImportant(!this.hashProcess.TryGetValue(evt.GetAddrValue("Process"), out pid) || cxn.pid == pid);
- return cxn;
+ AssertCritical(cxnFound.ipProtocol == IPPROTO.UDP);
+ AssertImportant(FImplies(cxnFound.cxnNext != null, cxnFound.addrRemote != null));
+ if (cxnFound.addrRemote?.Equals(addr) ?? true)
+ return cxnFound;
}
- // If we can't be sure that the PIDs match, then assume that they do.
- if (!this.hashProcess.TryGetValue(evt.GetAddrValue("Process"), out pid))
- return cxn;
-
- return (cxn.pid == pid) ? cxn : null;
+ return cxn;
}
/*
Try to reconstruct the Connection from the current event.
- Can return null!
+ Perhaps AFD.Create happened before the beginning of the trace.
+ Cannot return null.
*/
- Connection RestoreConnection(AddrVal qwEndpoint, in IGenericEvent evt)
+ Connection RestoreConnection(AddrVal qwEndpoint, AddrVal hProc, in IGenericEvent evt)
{
- AssertImportant(FindConnection2(qwEndpoint, in evt) == null); // Should have called FindConnection2 instead of FindConnection?
-
- // evt.ProcessId is very likely unreliable, so if we can't look up the true PID then ignore it.
- if (!this.hashProcess.TryGetValue(evt.GetAddrValue("Process"), out IDVal pid))
- return null;
-
+ IDVal pid = GetProcessId(hProc, in evt);
IDVal tid = (pid == evt.ProcessId) ? evt.ThreadId : tidUnknown;
- // The corresponding TcpAcceptListenerComplete can provide the PID.
+ AssertImportant(FindConnection2(qwEndpoint, hProc) == null);
+
Connection cxn = new Connection(qwEndpoint, pid, tid, evt.Timestamp)
{
+ hProcess = hProc,
+ socktype = SOCKTYPE.SOCK_STREAM,
ipProtocol = IPPROTO.TCP
};
+
+ this.Add(cxn);
+
+ return cxn;
+ }
+
+ Connection RestoreConnection(AddrVal qwEndpoint, in IGenericEvent evt)
+ {
+ AddrVal hProc = evt.GetAddrValue("Process");
+ return RestoreConnection(qwEndpoint, hProc, in evt);
+ }
+
+ /*
+ Try to reconstruct the Connection from the current event.
+ UDP events don't get a Create event (too noisy!), so generate one from the first Send/Receive.
+ Cannot return null.
+ */
+ Connection RestoreConnectionUDP(AddrVal qwEndpoint, AddrVal hProc, in IPEndPoint addr, IDVal pid, IDVal tid, in TimestampETW timeStamp)
+ {
+ // Shouldn't find this UDP-Endpoint still open.
+ AssertImportant(FindConnection(qwEndpoint, hProc) == null);
+
+ Connection cxn = new Connection(qwEndpoint, pid, tid, timeStamp)
+ {
+ hProcess = hProc,
+ addrRemote = addr,
+ ipProtocol = IPPROTO.UDP,
+ socktype = SOCKTYPE.SOCK_DGRAM
+ };
+
this.Add(cxn);
return cxn;
@@ -269,99 +324,235 @@ Connection RestoreConnection(AddrVal qwEndpoint, in IGenericEvent evt)
/*
- Mark the type of the most recent Winsock Connection with the given PID, TCB, and time overlap.
- Can be 0: tid, iTCB (?)
+ Correlate a Connection with another recent event by process, socket and either iTCB or IPEndPoint.
+ If tid != tidUnknown (-1) then also match to: Connection.tidConnect
+ Invoked by:
+ TCP.RequestConnect/.ConnectTcbProceeding/.ConnectTcbComplete via CorrelateConnection
+ UDP.CloseEndpointBound directly
+ WINET.HandleClosed/.Connect_Stop via StopRequest
*/
public Connection CorrelateByAddress(IPEndPoint addrRemote, uint iTCB, uint socket, IDVal pid, IDVal tid)
{
- AssertImportant(iTCB != 0);
-
- // There either was no TCB or there's no way to correlate.
+ AssertCritical(pid != pidUnknown);
AssertCritical(socket != 0);
- AssertCritical(!addrRemote.Empty());
+ AssertImportant(iTCB != 0);
+ AssertImportant(!addrRemote.Empty());
Connection cxn = this.FindLast(c => c.MatchAddr(iTCB, socket, pid, tid, addrRemote));
if (cxn == null)
- return cxn;
+ {
+ // pid and tid from the TCP record are not always reliable.
+ // Look up the Winsock Connection via just the socket and address.
+
+ cxn = this.FindLast(c => c.socket == socket && (c.addrRemote?.Equals(addrRemote) ?? false));
- if (iTCB == 0)
- return cxn;
+ if (cxn == null || !FImplies(cxn.iTCB != 0, cxn.iTCB == iTCB))
+ return null;
+ }
- AssertImportant(cxn.iTCB == 0 || cxn.iTCB == iTCB);
+ AssertImportant(cxn.pid != pidUnknown); // possible!
if (cxn.iTCB == 0)
{
- this.allTables.tcpTable.TcbrFromI(iTCB).SetType(Protocol.Winsock);
- cxn.iTCB = iTCB;
+ if (iTCB != 0)
+ {
+ cxn.iTCB = iTCB;
+
+ TcpIp.TcbRecord tcbr = this.allTables.tcpTable.TcbrFromI(iTCB);
+ tcbr.SetType(Protocol.Winsock);
+
+ if (!tcbr.fPidSure && hashProcess.TryGetValue(cxn.hProcess, out pid))
+ tcbr.pid = cxn.pid;
+ }
+ }
+ else
+ {
+ AssertImportant(cxn.iTCB == iTCB);
}
+ if (cxn.pid == pidUnknown)
+ cxn.pid = pid;
+
return cxn;
}
/*
Mark the type of the most recent Winsock Connection with the given PID, TID, iTCB, etc.
+ Used by: TCP.AcceptListenerComplete
*/
- public Connection CorrelateListener(SocketAddress sockRemote, TcpIp.TcbRecord tcbR, IDVal pid, IDVal tid, in TimestampUI timeStamp)
- {
+ public Connection CorrelateListener(TcpIp.TcbRecord tcbR, IDVal pid, IDVal tid)
+ {
AssertImportant(tcbR != null);
- AssertImportant(!timeStamp.HasMaxValue());
+ AssertCritical(!tcbR.addrRemote.Empty());
+ if (tcbR.addrRemote.Empty())
+ return null;
- // There either was no TCB or there's no way to correlate.
- AssertCritical(!sockRemote.Empty());
- if (sockRemote.Empty())
+ if (this.Count == 0)
return null;
- IPEndPoint addrRemote = NewEndPoint(sockRemote);
+ Connection cxn;
+
+ if (tid != tidUnknown)
+ {
+ cxn = this.FindLast(c =>
+ c.tidConnect == tid &&
+ c.socket == tcbR.socket &&
+ (c.addrRemote.Empty() || c.addrRemote.Equals(tcbR.addrRemote))
+ );
+ }
+ else
+ {
+ cxn = this.FindLast(c =>
+ c.socket == tcbR.socket &&
+ (c.addrRemote.Empty() || c.addrRemote.Equals(tcbR.addrRemote))
+ );
+ }
- Connection cxn = this.FindLast(cxn =>
- cxn.tidConnect == tid &&
- !cxn.addrRemote.Empty() &&
- cxn.addrRemote.Equals(addrRemote));
+ // This could fire near the start of the trace.
+ AssertImportant(cxn != null || pid <= pidSystem);
if (cxn == null)
return null;
- AssertImportant(timeStamp.Between(cxn.timeCreate, cxn.timeClose));
-
- AssertImportant(FImplies(cxn.pid != pidUnknown, cxn.pid == pid));
-
- if (cxn.pid == pidUnknown)
- cxn.pid = pid;
-
- uint iTCB = allTables.tcpTable.IFromTcbr(tcbR);
+ if (pid != pidUnknown)
+ {
+ if (cxn.pid == pidUnknown)
+ cxn.pid = pid;
+ else
+ AssertImportant(cxn.pid == pid);
- AssertImportant(FImplies(cxn.iTCB != 0, cxn.iTCB == iTCB));
+ this.ConfirmProcess(cxn);
+ }
if (cxn.iTCB == 0)
{
tcbR.SetType(Protocol.Winsock);
- cxn.iTCB = iTCB;
+ cxn.iTCB = allTables.tcpTable.IFromTcbr(tcbR);
+ }
+ else
+ {
+ tcbR.CheckType(Protocol.Winsock);
+ AssertImportant(cxn.iTCB == allTables.tcpTable.IFromTcbr(tcbR));
}
-
- AssertImportant(FImplies(cxn.socket != 0, cxn.socket == tcbR.socket));
if (cxn.socket == 0)
cxn.socket = tcbR.socket;
+ else
+ AssertImportant(cxn.socket == tcbR.socket);
+
+ if (cxn.addrRemote.Empty())
+ cxn.addrRemote = tcbR.addrRemote;
+ else
+ AssertCritical(cxn.addrRemote.Equals(tcbR.addrRemote));
+
+ return cxn;
+ }
+
+
+ /*
+ The UDP code correlates a UDP.EndpointSendMessages event with the corresponding WinSock/AFD Connection.
+ Used by: UDP.EndpointSendMessages
+
+ Correlate this pattern of SEND events:
+ AFD.SendMessageWithAddress Enter PID TID AFD-Endpoint cb Address [socket from AFD.BindWithAddress]
+ UDP.EndpointSendMessages PID TID UDP-Endpoint cb RemoteSockAddr [socket from LocalSockAdr]
+ */
+ public Connection CorrelateUDPSendEvent(IDVal pid, IDVal tid, uint cb, ushort socket, IPEndPoint ipAddr)
+ {
+ AssertCritical(pid >= pidSystem);
+ AssertCritical(tid != pidUnknown);
+ AssertCritical(socket != 0);
+ AssertImportant(ipAddr != null);
+
+ // Correlate via a recent AFD.SendMessageWithAddress event.
+ Connection cxn = this.FindLast(c =>
+ c.tidConnect == tid &&
+ c.cbSendConnect == cb &&
+ c.MatchUDPAddr(pid, socket, ipAddr)
+ );
+
+ if (cxn != null)
+ {
+ // It was a one-time correlation.
+ cxn.tidConnect = tidUnknown;
+ cxn.cbSendConnect = 0;
+ }
return cxn;
}
- void CloseConnection(AddrVal qwEndpoint, IDVal tid, in TimestampUI timeStamp)
+ /*
+ For UDP Send and Receive events there may be multiple addresses per connection.
+ Either set the Connection's address to this one,
+ OR extend a chain of linked Connections with the same Endpoint but different addresses.
+
+ Used by: AFD.SendMessageWithAddress/.ReceiveFromWithAddress/.ReceiveMessageWithAddress
+ */
+ Connection HandleUDPAddress(Connection cxn, in IPEndPoint ipAddr)
+ {
+ Connection cxnFound = cxn;
+
+ AssertImportant(!ipAddr.Empty() || cxn.socktype == SOCKTYPE.SOCK_RAW);
+
+ if (cxn.addrRemote == null)
+ {
+ cxn.addrRemote = ipAddr;
+ }
+ else
+ {
+ for (; cxnFound != null; cxnFound = cxnFound.cxnNext)
+ {
+ if (cxnFound.ipProtocol == IPPROTO.UDP && cxnFound.addrRemote.Equals(ipAddr))
+ break;
+ }
+ if (cxnFound == null)
+ {
+ cxnFound = cxn.Clone(ipAddr);
+
+ AssertCritical(cxnFound.cxnNext == null);
+ cxnFound.cbSend = cxnFound.cbRecv = 0;
+#if DEBUG
+ cxnFound.cbSendNested = 0;
+#endif
+ cxnFound.cxnNext = cxn;
+ cxnFound.xlink.AddRefLink();
+ Add(cxnFound);
+ }
+ }
+
+ if (cxnFound.iDNS == 0)
+ cxnFound.iDNS = allTables.dnsTable.IDNSFromAddress(ipAddr.Address);
+
+ return cxnFound;
+ }
+
+
+ /*
+ Do the work for: AFD.Close
+ */
+ void CloseConnection(in IGenericEvent evt)
{
+ AssertImportant(evt.GetUInt32("Status") == S_OK);
+
+ TimestampUI timeStamp = evt.Timestamp.ToGraphable();
+ AddrVal hProc = evt.GetAddrValue("Process");
+ AddrVal addrEndpoint = evt.GetAddrValue("Endpoint");
+
Connection cxnPrev = null;
Connection cxnNext = null;
- for (Connection cxn = FindConnection(qwEndpoint); cxn != null; cxn = cxnNext)
+ for (Connection cxn = FindConnection(addrEndpoint, hProc); cxn != null; cxn = cxnNext)
{
+ AssertImportant(!cxn.FClosed);
cxnNext = cxn.cxnNext;
cxn.timeClose = timeStamp;
if (cxn.addrRemote.Empty() && cxn.socket == 0 && (cxn.cbSend | cxn.cbRecv) == 0)
{
// This was one of many useless, unclaimed Open/Close connections
- // which didn't get the event: Connect[Ex]WithAddress
+ // which didn't get the event: BindWithAddress / Connect[Ex]WithAddress
// Remove it.
cxn.xlink.Unlink();
@@ -378,8 +569,13 @@ void CloseConnection(AddrVal qwEndpoint, IDVal tid, in TimestampUI timeStamp)
}
else
{
- cxn.tidClose = tid;
+ cxn.tidClose = evt.ThreadId;
cxnPrev = cxn;
+
+ if (cxn.addrRemote == null)
+ cxn.addrRemote = new IPEndPoint(0, 0);
+ else if (cxn.iDNS == 0)
+ cxn.iDNS = this.allTables.dnsTable.IDNSFromAddress(cxn.addrRemote.Address);
}
}
}
@@ -432,8 +628,9 @@ bool FKnownDoubleCountEvent(uint location)
case 3062: // AfdRioQueueSendCompletion
case 3066: // AfdRioFlushSendQueue
case 3402: // AfdRestartFastDatagramSend
+ default: // Missed one!?
AssertImportant(false); // confirm primary counting for these cases
- goto default;
+ break;
// CONFIRMED:
case 3014: // AfdSend
@@ -444,9 +641,213 @@ bool FKnownDoubleCountEvent(uint location)
case 3051: // AfdFastConnectionSend
case 3201: // AfdFastDatagramSend
case 3403: // AfdTLFastDgramSendComplete
+ break;
#endif // DEBUG
- default:
- return false;
+ }
+ return false;
+ }
+
+
+ /*
+ AFD location codes for operations where evt.ProcessId is empirically believed to be reliable.
+ */
+ static readonly HashSet Reliable = new HashSet
+ {
+ // AfdCreate
+ /*Enter*/ 1002, 1006, 1015,
+ /*Exit*/ 1012, 1013,
+
+ // AfdConnectWithAddress
+ /*Enter*/ 5023, 5502,
+
+ // AfdConnectExWithAddress
+ /*Enter*/ 5031,
+
+ // AfdBindWithAddress
+ /*Enter*/ 7010,
+ /*Exit*/ 7022,
+
+ // AfdSend
+ /*Enter*/ 3003, 3047, 3058, 3100,
+ /*Exit*/ 3014, 3051, 3201,
+
+ // AfdSendMessageWithAddress
+ /*Enter*/ 3100, 3044,
+ /*Exit*/ 3200, 3045,
+
+ // AfdReceive
+ /*Enter*/ 4106, 4115, 4117, 4200,
+ /*Exit*/ 4109, 4110, 4111, 4116, 4118, 4122,
+
+ // AfdReceiveMessageWithAddress
+ // AfdReceiveFromWithAddress
+ /*Exit*/ 4201,
+
+ // AfdClose
+ /*Enter*/ 2000, // except when pidSystem
+ /*Exit*/ 2001, // except when pidSystem
+
+ // AfdAbort
+ /*Abort*/ 8000, 8004
+ };
+
+
+ /*
+ AFD location codes for operations where evt.ProcessId is empirically known to be unreliable.
+ */
+ static readonly HashSet Unreliable = new HashSet
+ {
+#if DEBUG
+ // AfdCreate
+ // AfdConnectWithAddress
+ // AfdConnectExWithAddress
+ // AfdBindWithAddress
+ /* None */
+
+ // AcceptExWithAddress
+ /*Exit*/ 6101,
+
+ // AfdSend
+ /*Enter*/ 3006, 3056,
+ /*Exit*/ 3018, 3023, 3024, 3025, 3073,
+
+ // AfdSendMessageWithAddress
+ /*Exit*/ 3102,
+
+ // AfdReceive
+ /*Enter*/ 4107,
+ /*Exit*/ 4114, 4123,
+
+ // AfdReceiveMessageWithAddress
+ // AfdReceiveFromWithAddress
+ /*Exit*/ 4052,
+
+ // AfdClose
+ /*Enter*/ // 2000, // when pidSystem
+ /*Exit*/ // 2001, // when pidSystem
+
+ // AfdAbort
+ /*Abort*/ 8016, 8028
+#endif // DEBUG
+ };
+
+
+ /*
+ Confirm our understanding of the reliability of evt.ProcessId
+ according to the Location value of the event.
+ */
+ [System.Diagnostics.Conditional("DEBUG")]
+ void TestProcessId(in IGenericEvent evt)
+ {
+ uint location = evt.GetUInt32("Location");
+
+ if (Unreliable.Contains(location)) // evt.ProcessId could be anything
+ return;
+
+ if (evt.ProcessId == pidSystem && (location == 2000 || location == 2001)) // special case for AFD.Close
+ return;
+
+ AddrVal hProcess = evt.GetAddrValue("Process");
+ if (this.hashProcess.TryGetValue(hProcess, out IDVal pidHash))
+ {
+ AssertImportant(pidHash == evt.ProcessId); // else location belongs in Unreliable{}
+
+ if (pidHash == evt.ProcessId)
+ AssertImportant(Reliable.Contains(location));
+ else
+ Unreliable.Add(location); // suppress further assertion failures for this location
+ }
+ else
+ {
+ AssertImportant(Reliable.Contains(location)); // since it's not in Unreliable{}
+ }
+ }
+
+
+ /*
+ Build the correlation between process handle (reliable in all WinSock events)
+ and a process id. (evt.ProcessId is often unreliable.)
+ Most AFD (Winsock) tasks should call either TrySetProcessId or GetProcessId.
+ */
+ void SetProcessId(AddrVal hProcess, IDVal pid)
+ {
+ AssertCritical(pid != pidUnknown);
+ this.hashProcess[hProcess] = pid;
+ }
+
+ void TrySetProcessId(AddrVal hProcess, IDVal pid)
+ {
+ AssertImportant(pid != 0);
+ AssertCritical(pid != pidUnknown);
+
+ if (!this.hashProcess.TryGetValue(hProcess, out IDVal pidHash))
+ SetProcessId(hProcess, pid);
+ else
+ AssertImportant(pidHash == pid);
+ }
+
+ void TrySetProcessId(in IGenericEvent evt)
+ {
+ uint location = evt.GetUInt32("Location");
+ if (Reliable.Contains(location))
+ TrySetProcessId(evt.GetAddrValue("Process"), evt.ProcessId);
+ else
+ AssertImportant(Unreliable.Contains(location));
+ }
+
+
+ /*
+ Get a reliable ProcessID from the event, whether or not evt.ProcessId is reliable.
+ The Process Handle is reliable, and we have built up a correlation.
+ May possibly return: pidUnknown
+ */
+ IDVal GetProcessId(AddrVal hProc, IDVal pidEvt, uint location)
+ {
+ if (hashProcess.TryGetValue(hProc, out IDVal pid))
+ {
+ AssertImportant(FImplies(Reliable.Contains(location), pid == pidEvt));
+ return pid;
+ }
+
+ pid = pidUnknown;
+ if (Reliable.Contains(location))
+ {
+ pid = pidEvt;
+ this.hashProcess[hProc] = pid; // destructive
+ }
+ else
+ {
+ AssertImportant(Unreliable.Contains(location));
+ }
+
+ return pid;
+ }
+
+
+ IDVal GetProcessId(AddrVal hProc, in IGenericEvent evt)
+ {
+ uint location = evt.GetUInt32("Location");
+ return GetProcessId(hProc, evt.ProcessId, location);
+ }
+
+ /*
+ If the connection still has an unknown process, maybe we can update it now.
+ Else assert its consistency.
+ */
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ void ConfirmProcess(Connection cxn)
+ {
+ AssertCritical(cxn.hProcess != 0);
+
+ if (cxn.pid == pidUnknown)
+ {
+ // Perhaps we've since acquired the Process ID.
+ if (this.hashProcess.TryGetValue(cxn.hProcess, out IDVal pid))
+ cxn.pid = pid;
+ }
+ else
+ {
+ AssertCritical(this.hashProcess.TryGetValue(cxn.hProcess, out IDVal pid) && cxn.pid == pid);
}
}
@@ -474,38 +875,45 @@ public enum AFD
public void Dispatch(in IGenericEvent evt)
{
uint status;
+ uint cb;
Connection cxn;
+ AddrVal hProc;
AddrVal addrEndpoint;
+ IDVal tid, pid;
+ IPEndPoint ipAddr;
switch ((AFD)evt.Id)
{
case AFD.Create:
if (evt.GetUInt32("EnterExit") == 0)
{
- IDVal pid = (IDVal)evt.GetAddrValue("ProcessId");
- AssertCritical(pid == evt.ProcessId); // Else can we trust evt.ThreadId!?
-
- this.hashProcess[evt.GetAddrValue("Process")] = pid; // destructive
+ // On rare occasions, when a process ends, a new process can pick up its handle.
+ // We can force a new assocation with this Create event.
+ AssertImportant(Reliable.Contains(evt.GetUInt32("Location")));
+ pid = evt.ProcessId; // dependable
+ hProc = evt.GetAddrValue("Process");
+ SetProcessId(hProc, pid);
status = evt.GetUInt32("Status");
- AssertImportant(status == S_OK);
+ AssertInfo(status == S_OK);
if (!SUCCEEDED(status))
break;
addrEndpoint = evt.GetAddrValue("Endpoint");
- AssertImportant(FindConnection(addrEndpoint) == null);
+ AssertImportant(FindConnection(addrEndpoint, hProc) == null);
cxn = new Connection(addrEndpoint, pid, evt.ThreadId, evt.Timestamp)
{
stack = evt.Stack,
+ hProcess = hProc,
ipProtocol = (IPPROTO)evt.GetUInt32("Protocol"),
socktype = (SOCKTYPE)evt.GetUInt32("SocketType")
};
+
// Find counterexamples (not critical)
- AssertImportant((cxn.ipProtocol==IPPROTO.TCP || cxn.ipProtocol==IPPROTO.ICMP) == (cxn.socktype==SOCKTYPE.SOCK_STREAM));
- AssertImportant((cxn.ipProtocol==IPPROTO.UDP) == (cxn.socktype==SOCKTYPE.SOCK_DGRAM));
+ AssertImportant((cxn.ipProtocol == IPPROTO.UDP) == (cxn.socktype == SOCKTYPE.SOCK_DGRAM || cxn.socktype == SOCKTYPE.SOCK_RAW));
- var family = (System.Net.Sockets.AddressFamily)evt.GetUInt32("AddressFamily");
+ var family = (AddressFamily)evt.GetUInt32("AddressFamily");
if (family == AF_HYPERV) cxn.ipProtocol = IPPROTO.HyperV;
else if (family == AF_VSOCK) cxn.ipProtocol = IPPROTO.VSock;
@@ -516,36 +924,97 @@ public void Dispatch(in IGenericEvent evt)
case AFD.Close:
if (evt.GetUInt32("EnterExit") == 1)
+ CloseConnection(in evt);
+
+ break;
+/*
+ PATTERN: Winsock & TCP Stream with Enter/Exit
+ Thread1 AFD.Create 0/1
+ Thread2 AFD.BindWithAddress 0/1
+ Thread2 AFD.Connect[Ex]WithAddress 0
+ ThreadX AFD.ConnectEx 1 (opt)
+ ThreadX AFD.Send/.Receive 0/1
+
+ PATTERN: Winsock & UDP Datagram with Enter/Exit
+ Thread1 AFD.Create 0/1
+ Thread1 AFD.BindWithAddress 0/1
+ ThreadX AFD.SendMessageWithAddress 0/[1]
+ ThreadX AFD.ReceiveMessage/FromWithAddress 1
+*/
+ case AFD.BindWithAddress:
+ // Note: This event has two opcodes:
+ // OPEN (10) - EnterExit == 0
+ // CONNECTED (12) - EnterExit == 1
+
+ AssertImportant(evt.Opcode == (evt.GetUInt32("EnterExit") == 0 ? 10 : 12));
+
+ hProc = evt.GetAddrValue("Process");
+ addrEndpoint = evt.GetAddrValue("Endpoint");
+ cxn = FindConnection(addrEndpoint, hProc);
+
+ if (cxn == null)
+ cxn = RestoreConnection(addrEndpoint, in evt);
+
+ // Overwritten by AFD.Connect*WithAddress, if available.
+ cxn.tidConnect = evt.ThreadId;
+
+ if (cxn.socket == 0)
+ {
+ // This is used for correlating with TCP or UDP.
+ if (evt.GetUInt32("AddressLen") != 0)
{
- AssertImportant(evt.GetUInt32("Status") == S_OK);
- TimestampUI timeStamp = evt.Timestamp.ToGraphable();
- addrEndpoint = evt.GetAddrValue("Endpoint");
- CloseConnection(addrEndpoint, evt.ThreadId, in timeStamp);
+ SocketAddress sa = evt.GetSocketAddress();
+
+ // This is a curious value for correlating with TCP/UDP.
+ // If it is 0 then the protocol is probably not IPv4/IPv6.
+ if (sa.Port() != 0)
+ cxn.socket = sa.Port();
+#if DEBUG
+ if (!sa.IsAddrZero())
+ cxn.addrLocal = sa;
+#endif // DEBUG
}
+
+ // If this is Exit and socket==0 then this must be a non-standard protocol.
+ AssertImportant(FImplies(evt.GetUInt32("EnterExit")==1 && cxn.socket==0, cxn.ipProtocol!=IPPROTO.TCP && cxn.ipProtocol!=IPPROTO.UDP));
+ }
+#if DEBUG
+ if (evt.GetUInt32("AddressLen") != 0)
+ {
+ SocketAddress sa = evt.GetSocketAddress();
+ ushort socket = sa.Port();
+ AssertCritical(FImplies(socket != 0, socket == cxn.socket));
+ AssertImportant(cxn.addrLocal?.SafeEquals(sa) ?? sa.IsAddrZero());
+ }
+#endif // DEBUG
+ // Last non-zero status wins.
+ status = evt.GetUInt32("Status");
+ AssertImportant(status == S_OK); // else ignore?
+ if (status != S_OK)
+ cxn.status = status;
+
break;
case AFD.ConnectWithAddress:
case AFD.ConnectExWithAddress:
if (evt.GetUInt32("EnterExit") == 0)
{
+ TrySetProcessId(in evt);
+
+ hProc = evt.GetAddrValue("Process");
addrEndpoint = evt.GetAddrValue("Endpoint");
- cxn = FindConnection(addrEndpoint);
+ cxn = FindConnection(addrEndpoint, hProc);
if (cxn == null)
cxn = RestoreConnection(addrEndpoint, in evt);
- if (cxn == null) break;
-
cxn.timeConnect = evt.Timestamp.ToGraphable();
- if (evt.GetUInt32("AddressLen") != 0)
- {
- cxn.addrRemote = NewEndPoint(evt.GetSocketAddress());
- cxn.iDNS = allTables.dnsTable.IDNSFromAddress(cxn.addrRemote.Address);
- }
+ cxn.addrRemote = NewEndPoint(in evt);
+ cxn.iDNS = allTables.dnsTable.IDNSFromAddress(cxn.addrRemote.Address);
cxn.tidConnect = evt.ThreadId;
- AssertImportant(cxn.tidConnect == cxn.tidOpen); // for CorrelateByTimeThread
+ // Note: cxn.tidConnect ?= cxn.tidOpen
if ((AFD)evt.Id == AFD.ConnectExWithAddress)
cxn.fSuperConnect = true;
@@ -561,55 +1030,38 @@ public void Dispatch(in IGenericEvent evt)
// evt.ProcessId is not dependable
if (evt.GetUInt32("EnterExit") == 1)
{
- addrEndpoint = evt.GetAddrValue("AcceptEndpoint");
- cxn = FindConnection(addrEndpoint);
-
- if (cxn == null)
- cxn = RestoreConnection(addrEndpoint, in evt);
-
- if (cxn == null) break;
-
- AssertCritical(cxn.addrRemote.Empty());
-
- if (evt.GetUInt32("AddressLen") != 0)
- {
- cxn.addrRemote = NewEndPoint(evt.GetSocketAddress());
- cxn.iDNS = allTables.dnsTable.IDNSFromAddress(cxn.addrRemote.Address);
- }
-
- cxn.tidConnect = evt.ThreadId;
-
- // Last non-zero status wins.
- status = evt.GetUInt32("Status");
- if (status != S_OK)
- cxn.status = status;
- }
- break;
+ hProc = evt.GetAddrValue("Process");
- case AFD.BindWithAddress:
- // Note: This event has two opcodes:
- // OPEN (10) - EnterExit == 0
- // CONNECTED (12) - EnterExit == 1
+ // This endpoint will transfer the Socket and perhaps the Local Address.
+ addrEndpoint = evt.GetAddrValue("Endpoint");
+ cxn = FindConnection(addrEndpoint, hProc);
+ ushort socket = cxn?.socket ?? 0;
+#if DEBUG
+ pid = GetProcessId(hProc, evt);
+ AssertImportant(cxn?.pid == pid);
- AssertImportant(evt.Opcode == (evt.GetUInt32("EnterExit") == 0 ? 10 : 12));
+ SocketAddress addrLocal = cxn?.addrLocal;
+#endif // DEBUG
+ addrEndpoint = evt.GetAddrValue("AcceptEndpoint");
- // evt.ProcessId is not dependable
- if (evt.GetUInt32("EnterExit") == 1)
- {
- addrEndpoint = evt.GetAddrValue("Endpoint");
- cxn = FindConnection(addrEndpoint);
+ cxn = FindConnection(addrEndpoint, hProc);
if (cxn == null)
cxn = RestoreConnection(addrEndpoint, in evt);
- if (cxn == null) break;
+ AssertImportant(cxn.pid == pid);
+ AssertCritical(cxn.addrRemote.Empty());
+ AssertImportant(cxn.socket == 0);
- // This is used for correlating with TcpIp.
- if (evt.GetUInt32("AddressLen") != 0)
- {
- // This is a curious value for correlating with TCP.
- cxn.socket = evt.GetSocketAddress().Port();
- }
+ cxn.socket = socket;
+ cxn.addrRemote = NewEndPoint(in evt);
+ cxn.iDNS = allTables.dnsTable.IDNSFromAddress(cxn.addrRemote.Address);
+#if DEBUG
+ AssertImportant(cxn.addrLocal.Empty());
+ cxn.addrLocal = addrLocal;
+#endif // DEBUG
+ // This thread matches with TCP.AcceptListenerComplete via CorrelateListener.
+ cxn.tidConnect = evt.ThreadId;
// Last non-zero status wins.
status = evt.GetUInt32("Status");
@@ -619,16 +1071,17 @@ public void Dispatch(in IGenericEvent evt)
break;
case AFD.Send:
- // evt.ProcessId is not dependable
- if (evt.GetUInt32("EnterExit") == 1) // WSAPIEXIT
+ TrySetProcessId(in evt);
+ if (evt.GetUInt32("EnterExit") == 1)
{
+ hProc = evt.GetAddrValue("Process");
addrEndpoint = evt.GetAddrValue("Endpoint");
- cxn = FindConnection2(addrEndpoint, in evt);
+ cxn = FindConnection2(addrEndpoint, hProc);
if (cxn == null)
- cxn = RestoreConnection(addrEndpoint, in evt);
+ cxn = RestoreConnection(addrEndpoint, hProc, in evt);
- if (cxn == null) break;
+ AssertCritical(cxn.ipProtocol != IPPROTO.UDP);
uint cbSend = evt.GetUInt32("BufferLength");
uint location = evt.GetUInt32("Location");
@@ -651,18 +1104,17 @@ public void Dispatch(in IGenericEvent evt)
break;
case AFD.Receive:
- // evt.ProcessId is not dependable
+ TrySetProcessId(in evt);
if (evt.GetUInt32("EnterExit") == 1)
{
+ hProc = evt.GetAddrValue("Process");
addrEndpoint = evt.GetAddrValue("Endpoint");
- cxn = FindConnection2(addrEndpoint, in evt);
+ cxn = FindConnection2(addrEndpoint, hProc);
if (cxn == null)
- cxn = RestoreConnection(addrEndpoint, in evt);
+ cxn = RestoreConnection(addrEndpoint, hProc, in evt);
- // Can't resolve the connection or even the process, and there's no address to correlate.
- if (cxn == null)
- break;
+ AssertCritical(cxn.ipProtocol != IPPROTO.UDP);
cxn.cbRecv += evt.GetUInt32("BufferLength");
@@ -672,109 +1124,181 @@ public void Dispatch(in IGenericEvent evt)
cxn.status = status;
}
break;
-
+/*
+ PATTERN: Winsock & UDP Datagram
+ Thread0 AFD.Create Enter AFD-Endpoint SocketType Protocol
+ Thread0 AFD.Create Exit AFD-Endpoint
+
+ Thread0 AFD.BindWithAddress Enter AFD-Endpoint
+ Thread0 AFD.BindWithAddress Exit AFD-Endpoint Address=0:Socket
+ ...
+ Thread1 AFD.SendMessageWithAddress Enter AFD-Endpoint Address=Addr:Port BufferLength
+ Thread1 UDP.EndpointSendMessages LocalSockAddr=0:Socket RemoteSockAddress=Addr:Port Pid UDP-Endpoint
+ Thread? AFD.SendMessageWithAddress Exit AFD-Endpoint Address=Addr:Port BufferLength // ***OPTIONAL***
+ ...
+ Thread3 UDP.EndpointReceiveMessages LocalSockAddr=XXXX:Socket RemoteSockAddr=Addr:Port Pid UDP-Endpoint
+ Thread3 AFD.Receive*WithAddress Exit AFD-Endpoint Location=4052 Address=Addr:Port
+ ...
+ Thread4 UDP.EndpointReceiveMessages LocalSockAddr=XXXX:Socket RemoteSockAddr=Addr:Port Pid UDP-Endpoint
+ Thread? AFD.Receive*WithAddress Exit AFD-Endpoint Location=4201 Address=Addr:Port
+ ...
+ Thread5 UDP.CloseEndpointBound LocalAddress=0:Socket UDP-Endpoint
+
+ ^ The SendMessage pairs and ReceiveMessage pairs have corresponding Endpoint values and Addr:Port.
+ ^ But one Endpoint can have multiple Addr.
+
+ v This pattern matches separate UDP-Endpoints for send/receive.
+
+ Thread0 AFD.BindWithAddress ProcessId AFD-Endpoint Address:Port
+ Thread1 UDP.EndpointSendMessages PID UDP-Endpoint1 LocalSockAddr:Port
+ Thread1 UDP.EndpointReceiveMessages PID UDP-Endpoint2 RemoteSockAddr:Port
+*/
// UDP Datagram
case AFD.SendMessageWithAddress:
- if (evt.GetUInt32("EnterExit") != 0)
- goto case AFD.ReceiveMessageWithAddress;
+ if (evt.GetUInt32("EnterExit") == 0)
+ {
+ // In this case it appears that the evt.ProcessId is dependable.
+ TrySetProcessId(in evt);
+
+ ipAddr = NewEndPoint(in evt);
+ hProc = evt.GetAddrValue("Process");
+ addrEndpoint = evt.GetAddrValue("Endpoint");
+ cxn = FindConnectionUDP(addrEndpoint, hProc, in ipAddr);
- // In this case it appears that the evt.ProcessId is dependable.
- AssertImportant(evt.GetUInt32("Location") == 3100); // only know of this case
- TrySetProcess(in evt, evt.ProcessId);
+ if (cxn == null)
+ cxn = RestoreConnectionUDP(addrEndpoint, hProc, in ipAddr, evt.ProcessId, evt.ThreadId, evt.Timestamp);
+
+ AssertCritical(!cxn.FClosed);
+ AssertCritical(cxn.ipProtocol == IPPROTO.UDP);
+ AssertImportant(cxn.socktype == SOCKTYPE.SOCK_DGRAM || cxn.socktype == SOCKTYPE.SOCK_RAW);
+
+ cxn = HandleUDPAddress(cxn, in ipAddr);
+
+ AssertCritical(cxn.qwEndpoint == addrEndpoint);
+ AssertImportant(allTables.tcpTable.TcbrFromI(cxn.iTCB)?.addrRemote?.Address.Equals(cxn.addrRemote.Address) ?? true);
+
+ if (cxn.pid == pidUnknown)
+ cxn.pid = evt.ProcessId;
+ else
+ AssertCritical(cxn.pid == evt.ProcessId);
+
+ cxn.tidConnect = evt.ThreadId;
+
+ cb = evt.GetUInt32("BufferLength");
+ cxn.cbSend += cb;
+ cxn.cbSendConnect = cb;
+
+ // Last non-zero status wins.
+ status = evt.GetUInt32("Status");
+ if (status != S_OK)
+ cxn.status = status;
+ }
break;
+ // UDP Datagram
case AFD.ReceiveFromWithAddress:
case AFD.ReceiveMessageWithAddress:
- // evt.ProcessId is not dependable
- if (evt.GetUInt32("EnterExit") == 1)
+ AssertImportant(evt.GetUInt32("EnterExit") == 1);
+ hProc = evt.GetAddrValue("Process");
+
+ if (evt.GetUInt32("Location") == 4052)
+ {
+ // ASYNCHRONOUS but on the same thread as UdpEndpointReceiveMessages
+ // evt.ProcessId is not relevant.
+ pid = GetProcessId(hProc, in evt);
+ tid = evt.ThreadId; // match by thread
+ }
+ else
{
- bool fSend = (AFD)evt.Id == AFD.SendMessageWithAddress;
+ AssertCritical(evt.GetUInt32("Location") == 4201);
- addrEndpoint = evt.GetAddrValue("Endpoint");
- cxn = FindConnection2(addrEndpoint, in evt);
+ // SYNCHRONOUS but match to an asynchronous UdpEndpointReceiveMessages
+ pid = evt.ProcessId; // match the real Process ID
+ tid = tidUnknown; // cannot match by thread
+ TrySetProcessId(hProc, pid);
+ }
- uint cb = evt.GetUInt32("BufferLength");
+ ipAddr = NewEndPoint(in evt);
+ addrEndpoint = evt.GetAddrValue("Endpoint");
+ cxn = FindConnectionUDP(addrEndpoint, hProc, in ipAddr);
- IPEndPoint ipAddr = null;
- if (evt.GetUInt32("AddressLen") != 0)
- ipAddr = NewEndPoint(evt.GetSocketAddress());
+ if (cxn == null)
+ cxn = RestoreConnectionUDP(addrEndpoint, hProc, in ipAddr, pid, (pid==evt.ProcessId)?evt.ThreadId:tidUnknown, evt.Timestamp);
- if (cxn == null)
- {
- cxn = RestoreConnection(addrEndpoint, in evt);
- if (cxn == null)
- {
- // Couldn't resolve the ProcessID.
- // It's in the corresponding UDP event.
- // Try again.
+ AssertImportant(!cxn.FClosed); // lingering received message!?
+ AssertCritical(cxn.ipProtocol == IPPROTO.UDP);
+ AssertImportant(cxn.socktype == SOCKTYPE.SOCK_DGRAM || cxn.socktype == SOCKTYPE.SOCK_RAW);
- if (ipAddr == null)
- break;
+ cxn = HandleUDPAddress(cxn, in ipAddr);
- IDVal pid = allTables.tcpTable.PidFromUDPEvent(ipAddr, cb, fSend);
- if (pid == pidUnknown)
- break;
+ AssertCritical(cxn.qwEndpoint == addrEndpoint);
- TrySetProcess(in evt, pid);
- cxn = RestoreConnection(addrEndpoint, in evt);
- if (cxn == null)
- break;
- }
+ if (cxn.pid == pidUnknown)
+ cxn.pid = pid;
+ else
+ AssertCritical(cxn.pid == pid);
- cxn.ipProtocol = IPPROTO.UDP;
- }
+ cb = evt.GetUInt32("BufferLength");
+ cxn.cbRecv += cb;
+
+ // Last non-zero status wins.
+ status = evt.GetUInt32("Status");
+ if (status != S_OK)
+ cxn.status = status;
- AssertImportant(cxn.ipProtocol == IPPROTO.UDP);
+ if (cxn.socktype == SOCKTYPE.SOCK_RAW)
+ break;
+
+ // CorrelateUDPRecvEvent has side effects to clear caches for found records.
+ uint iTCB = allTables.tcpTable.CorrelateUDPRecvEvent(pid, tid, cb, cxn.socket, ipAddr);
+
+ if (cxn.iTCB == 0)
+ {
+ cxn.iTCB = iTCB;
- if (ipAddr != null)
+ if (iTCB != 0 && (cxn.pid == pidUnknown || cxn.socket == 0))
{
- if (cxn.addrRemote.Empty())
+ var tcb = allTables.tcpTable.TcbrFromI(iTCB);
+
+ AssertImportant(tcb.CheckType(Protocol.Winsock));
+
+ if (cxn.pid == pidUnknown)
{
- cxn.addrRemote = ipAddr;
- cxn.iDNS = allTables.dnsTable.IDNSFromAddress(ipAddr.Address);
+ AssertCritical(tcb.pid != pidUnknown);
+ cxn.pid = tcb.pid;
+ TrySetProcessId(hProc, cxn.pid);
}
else
{
- Connection cxnFound;
- for (cxnFound = cxn; cxnFound != null; cxnFound = cxnFound.cxnNext)
- {
- AssertCritical(cxn.qwEndpoint == addrEndpoint);
- if (cxnFound.ipProtocol == IPPROTO.UDP && cxnFound.addrRemote.Equals(ipAddr))
- break;
- }
- if (cxnFound == null)
- {
- cxnFound = cxn.Clone(ipAddr);
- cxnFound.iDNS = allTables.dnsTable.IDNSFromAddress(ipAddr.Address);
-
- AssertCritical(cxnFound.cxnNext == null);
- cxnFound.cbSend = cxnFound.cbRecv = 0;
-#if DEBUG
- cxnFound.cbSendNested = 0;
-#endif
- cxnFound.cxnNext = cxn;
- cxnFound.xlink.AddRefLink();
- Add(cxnFound);
- }
- cxn = cxnFound;
+ AssertCritical(cxn.pid == tcb.pid);
}
- if (cxn.iTCB == 0)
- cxn.iTCB = allTables.tcpTable.CorrelateUDPAddress(ipAddr, cb, cxn.pid, fSend);
+ if (cxn.socket == 0)
+ cxn.socket = tcb.socket;
+ else
+ AssertCritical(cxn.socket == tcb.socket);
}
-
- if (fSend)
- cxn.cbSend += cb;
else
- cxn.cbRecv += cb;
-
- // Last non-zero status wins.
- status = evt.GetUInt32("Status");
- if (status != S_OK)
- cxn.status = status;
+ {
+ // This could still be a RAW socket (and we missed the Create event). So no associated TcpIp event.
+ AssertImportant(iTCB != 0 || tid == tidUnknown);
+ }
+ }
+ else
+ {
+ AssertImportant(cxn.iTCB == iTCB);
}
+
+ AssertImportant(allTables.tcpTable.TcbrFromI(cxn.iTCB)?.addrRemote?.Address.Equals(cxn.addrRemote.Address) ?? true);
break;
- }
+#if DEBUG
+ default:
+ Unreliable.Add(evt.GetUInt32("Location")); // for TestProcessId
+ break;
+#endif // DEBUG
+ } // switch evt.id
+
+ TestProcessId(in evt);
}
}
}
\ No newline at end of file
diff --git a/src/NetBlame/Tables/NetBlameTable.ThreadPool.cs b/src/NetBlame/Tables/NetBlameTable.ThreadPool.cs
index 9e4c97f..35ee1c7 100644
--- a/src/NetBlame/Tables/NetBlameTable.ThreadPool.cs
+++ b/src/NetBlame/Tables/NetBlameTable.ThreadPool.cs
@@ -15,7 +15,6 @@
using TimestampUI = Microsoft.Performance.SDK.Timestamp;
-using IDVal = System.Int32; // type of Event.pid/tid / ideally: System.UInt32
using QWord = System.UInt64;
@@ -233,7 +232,7 @@ static class Generators
public static uint Index(ThreadPoolItem tpObj) => tpObj.IFromTask;
- public static IDVal TID(ThreadPoolItem tpObj) => tpObj.tpTask.tidExec;
+ public static string TID(ThreadPoolItem tpObj) => StringFromInt(tpObj.tpTask.tidExec);
public static TimestampUI StartExecTime(ThreadPoolItem tpObj) => tpObj.tpTask.timeStartExec;
@@ -254,7 +253,7 @@ public static TimestampDelta ExecDuration(ThreadPoolItem tpObj, TimestampUI time
public static IStackSnapshot InvokerStack(ThreadPoolItem tpObj) => tpObj.tpTask.xlink.taskLinkNext?.stack;
- public static IDVal InvokerTID(ThreadPoolItem tpObj) => tpObj.tpTask.xlink.taskLinkNext?.tidCreate ?? 0;
+ public static string InvokerTID(ThreadPoolItem tpObj) => StringFromInt(tpObj.tpTask.xlink.taskLinkNext?.tidCreate ?? NetBlameCustomDataSource.Tables.TID.Unknown);
} // Generators
@@ -277,7 +276,7 @@ public override void Build(ITableBuilder tableBuilder)
var xlStatus = Projection.Project(xlBaseProjector, Generators.Status);
var xlState = Projection.Project(xlBaseProjector, Generators.State);
- // int -> IDVal
+ // int -> numeric string
var xlThreadExec = Projection.Project(xlBaseProjector, Generators.TID);
var xlInvokerTID = Projection.Project(xlBaseProjector, Generators.InvokerTID);
diff --git a/src/NetBlame/Tables/NetBlameTable.URL.cs b/src/NetBlame/Tables/NetBlameTable.URL.cs
index 37a00e2..76634bc 100644
--- a/src/NetBlame/Tables/NetBlameTable.URL.cs
+++ b/src/NetBlame/Tables/NetBlameTable.URL.cs
@@ -4,7 +4,6 @@
using Microsoft.Windows.EventTracing.Symbols;
-using IDVal = System.Int32; // type of Event.pid/tid / ideally: System.UInt32
using QWord = System.UInt64;
@@ -350,10 +349,10 @@ public static string GeoLoc(URL urlObj, GeoCache geoCache)
public static QWord TCB(URL urlObj) => urlObj.tcbId;
- public static IDVal PID(URL urlObj) => urlObj.Pid;
+ public static string PID(URL urlObj) => StringFromInt(urlObj.Pid);
// TID which corresponds to StackFirst, hopefully that of WinMain.
- public static IDVal ThreadIdFirst(URL urlObj) => urlObj.myStack.stackFirst?.ThreadId ?? urlObj.myStack.rgAttrib?[0].tidEnqueue ?? 0;
+ public static string ThreadIdFirst(URL urlObj) => StringFromInt(urlObj.myStack.TidFirst);
// This is the full aggregation of call stacks: First + Middle + Last
// This will upcast back to MyStackSnapshot by FullStackSnapshotAccessProvider.
@@ -403,7 +402,7 @@ public override void Build(ITableBuilder tableBuilder)
var urlPortProjector = Projection.Project(urlBaseProjector, Generators.Port);
var urlSocketProjector = Projection.Project(urlBaseProjector, Generators.Socket);
- // int -> IDVal
+ // int -> numeric string
var urlPIDProjector = Projection.Project(urlBaseProjector, Generators.PID);
var urlThreadFirstProjector = Projection.Project(urlBaseProjector, Generators.ThreadIdFirst);
diff --git a/src/NetBlame/Tables/NetBlameTable.Winsock.cs b/src/NetBlame/Tables/NetBlameTable.Winsock.cs
index 4302702..e8918b8 100644
--- a/src/NetBlame/Tables/NetBlameTable.Winsock.cs
+++ b/src/NetBlame/Tables/NetBlameTable.Winsock.cs
@@ -11,7 +11,6 @@
using static NetBlameCustomDataSource.Util;
-using IDVal = System.Int32; // type of Event.pid/tid / ideally: System.UInt32
using QWord = System.UInt64;
@@ -201,7 +200,7 @@ static class Generators
public static uint Socket(Connection cxnObj) => cxnObj.socket;
- public static IDVal TidClose(Connection cxnObj) => cxnObj.tidClose;
+ public static string TidClose(Connection cxnObj) => StringFromInt(cxnObj.tidClose);
public static QWord TCB(Connection cxnObj, TcpIp.TcpTable tcpTable) => tcpTable.TcbrFromI(cxnObj.iTCB)?.tcb ?? 0;
} // Generators
@@ -224,7 +223,7 @@ public override void Build(ITableBuilder tableBuilder)
var wsDNSTableProjector = Projection.Constant(this.Tables.dnsTable);
var wsTcpTableProjector = Projection.Constant(this.Tables.tcpTable);
- // int -> IDVal: TID Create/Close
+ // int -> numeric string: TID Create/Close
var wsTidCloseProjector = Projection.Project(wsBaseProjector, Generators.TidClose);
// int -> string: ws, Server, Protocol, IPProto, SockType
diff --git a/src/NetBlame/Tables/TableBase.cs b/src/NetBlame/Tables/TableBase.cs
index eb4ed64..e81d51a 100644
--- a/src/NetBlame/Tables/TableBase.cs
+++ b/src/NetBlame/Tables/TableBase.cs
@@ -14,17 +14,22 @@
using TimestampUI = Microsoft.Performance.SDK.Timestamp;
-using IDVal = System.Int32; // type of Event.pid/tid / ideally: System.UInt32
+using IDVal = System.Int32; // type of Event.pid/tid
namespace NetBlameCustomDataSource.Tables
{
+ static class PID { public const IDVal Unknown = -1; } // PID.Unknown
+ static class TID { public const IDVal Unknown = -1; } // TID.Unknown
+
// BuildTableCore does this:
// var table = Activator.CreateInstance(type, parms) as OfficeTaskPoolTableBase;
// If this creates more than one table type then it helps to share a base type.
public abstract class NetBlameTableBase
{
+ public static string StringFromInt(IDVal val) => (val >= 0) ? val.ToString("N0") : Util.strNA;
+
protected PendingSources Sources { get; }
protected AllTables Tables { get; }
@@ -238,12 +243,12 @@ public static string ProcFullName(IProcess proc)
// TODO: race condition / concurrency problem?
}
- builder.AppendFormat(" ({0})", proc?.Id ?? 0);
+ builder.AppendFormat(" ({0})", NetBlameTableBase.StringFromInt(proc?.Id ?? PID.Unknown));
return builder.ToString();
}
- public static IDVal Thread(T obj) => obj.TidOpen;
+ public static string Thread(T obj) => NetBlameTableBase.StringFromInt(obj.TidOpen);
public static IStackSnapshot OpenStack(T obj) => obj.Stack;
@@ -283,8 +288,8 @@ public class ProjectorCommon where T: IGraphableEntry
// int -> IProcess -> cached string (proc name, friendly name, pid)
public readonly IProjection processCachedFullNameProjector;
- // int -> IDVal: Thread
- public readonly IProjection threadProjector;
+ // int -> numeric string: Thread
+ public readonly IProjection threadProjector;
// int -> uint
public readonly IProjection linkIndexProjector;
@@ -321,7 +326,7 @@ public ProjectorCommon(in PendingSources sources, in IProjection basePro
var processFullNameProjector = Projection.Project(processProjector, GeneratorCommon.ProcFullName);
this.processCachedFullNameProjector = Projection.CacheOnFirstUse(cTableBase, processFullNameProjector);
- // int -> IDVal: Thread
+ // int -> numeric string: Thread
this.threadProjector = Projection.Project(baseProjector, GeneratorCommon.Thread);
this.linkTypeProjector = Projection.Project(baseProjector, GeneratorCommon.LinkType);