Skip to content

Profiler: Session

lordmilko edited this page Sep 4, 2023 · 23 revisions

Contents

Management

When you begin profiling a process using the Start-DbgProfiler cmdlet, this creates a profiler session within your profiler controller PowerShell prompt. This profiler session, among other things, controls a background thread that records all ETW events that are being transmitted from the profiler inside of the target process. While DebugTools allows you to toggle frame tracing on and off at will, certain events (such as new modules being loaded and new methods being JITted) will always be reported to the profiler controller ETW thread so that these constant values are known when you actually go to start profiling. Multiple processes can be profiled at once from within a single profiler controller PowerShell instance.

Many cmdlets provided by DebugTools require a profiler session to be specified so they know which session to operate on. For simplicity, DebugTools allows invoking cmdlets that operate on a session without explicitly specifying a session to the cmdlet. When a session is not explicitly specified, DebugTools will attempt to determine an implicit session to use, according to the following heuristic

  • Is there a single session targeting a live process?
  • Is there a single session targeting a dead process?
  • Are there any dead processes? If so use the last one
  • Is there a Global Profiler Session?

If a profiler session is found, or multiple sessions targeting live processes are found, an exception will be thrown. The list of profiler sessions known to DebugTools can be retrieved using the Get-DbgProfiler cmdlet.

C:\> Get-DbgProfiler

Get-DbgProfiler allows specifying a wildcard expression specifying the process name to filter by as well as the -Id of the process ID to filter by.

By default, Get-DbgProfiler will only return sessions belonging to live processes. To list sessions belonging to all processes, specify -Force. To retrieve the Global Profiler Session (if DebugTools has been notified of one) specify -Global

# Attempt to retrieve the Global Profiler Session. Will output nothing if
# this session does not exist.
C:\> Get-DbgProfiler -Global

To terminate a profiler session, use the Stop-DbgProfiler cmdlet

C:\> Stop-DbgProfiler

Like other profile cmdlets, the Stop-DbgProfiler cmdlet will attempt to guess the profiler session it should target if one is not piped in. Upon terminating the profiler session, the target process will also be terminated if it is still running. Profiler sessions terminated via Stop-DbgProfiler will be removed from the list of profiler sessions known to DebugTools that may implicitly be used in cmdlets when an explicit session is not specified. Stop-DbgProfiler will then immediately trigger a garbage collection, in case the terminated session was consuming a large amount of memory. If you wish to use the terminated session with other cmdlets after it has been terminated, ensure you still hold a reference to the session by calling Get-DbgProfiler beforehand.

Detailed

For performance reasons, by default, DebugTools only records frame invocation information. In order to record the parameters/return values that are passed to and from each function, the profiler must be started in detailed tracing mode.

DebugTools understands how to trace any type of object that is passed to or from a method. In the case of classes and structs, DebugTools will enumerate all of the fields defined on the object that was passed in, and then recursively trace the next level of values. Given each field member may point to another class or struct with its own field members, this can exponentially grow to encompass a large number of objects very quickly. And given all of this work is performed every time a method is invoked, in a complex application (such as Visual Studio) you can very quickly bring the entire program to a standstill.

To deal with this challenge, DebugTools allows you to specify the maximum number of levels it should recurse when tracing parameter/return values.

# Record classes/structs, their fields, and their fields' fields
C:\> Start-DbgProfiler powershell -Detailed -ValueDepth 3

By default, -ValueDepth is 1. Object references representing some form of indirection do not count towards the value depth:

  • In int*, the pointer indirection is ignored to ensure we get the underlying int
  • If a parameter or field takes a value of type object, this indirection is followed to record the actual type of object that has been specified
  • If an Array or SZArray has been specified, the array is always inspected to record the objects inside of it

Note that while theoretically an infinite number of value levels can be traversed, in practice, due to limitations of ETW, only 64KB of data can be recorded for each frame. Attempting to exceed the length of the DebugTools Profiler uses to record ETW events will leave the recorded data in a corrupt state, and the profiler controller will ignore all of the parameter or return value data that was recorded for the frame. Processes launched by the Start-DbgProfiler cmdlet automatically have their lifetimes bound to the profiler controller process. If the profiler controller process is terminated, the target process will automatically be terminated as well if it is still running.

Blacklist/Whitelist

Each method that is profiled adds a performance hit to the target process. In a massive program like Visual Studio, profiling every single method not only will bring the program to a standstill, but also inevitably lead to your computer running out of memory from all the frame information the profiler controller has recorded.

In order to improve performance, DebugTools allows specifying a blacklist and a whitelist of modules that should be processed. When a module has been blacklisted, none of the methods within it will be recorded as having been invoked whenever they are called. As far as DebugTools is concerned, these frames simply never existed. If you have a sequence of method calls A -> B -> C, if the module containing method B is blacklisted, DebugTools will report to you that the invocation was A -> C.

A whitelist is a list of module patterns that should not be blacklisted. As such, attempting to whitelist a module that was never blacklisted to begin with will have no effect. To exclusively profile a given module, you must first blacklist all modules, then whitelist just the modules you are interested in profiling.

# Only profile methods within System.Management.Automation.dll
C:\> Start-DbgProfiler powershell -ModuleBlacklist * -ModuleWhitelist System.Management.Automation.dll

Blacklists/whitelists may consist of simple wildcard expressions describing the module names that should be matched. The following table lists some examples of module values that can be specified

Value Description
* Matches all modules
Foo.dll Match the module named Foo.dll, ignoring case
Foo Match the module named Foo.dll, ignoring case
*foo* Match all modules whose module name or file path contains "foo"

StartsWith or EndsWith expressions (Foo*, *Foo) are not allowed.

By default, DebugTools automatically blacklists system modules containing an excessive number of methods that would otherwise slow down your program. The list of blacklisted modules/module paths that are blacklisted by default can be found here. Individual modules included in the default module blacklist can be included by specifying them to -ModuleWhitelist. To ignore the default module blacklist, specify -IgnoreDefaultBlacklist.

After having launched a profiled process, the following command can be used at any time to gauge which modules contain the highest number of methods that are being profiled by DebugTools.

C:\> Get-DbgProfilerMethod | group ModuleName | sort count -desc | select -first 10

Global Session

The easiest way to start a profiling session is typically to use the Start-DbgProfiler cmdlet, which will automatically set a variety of environment variables needed to load and configure the DebugTools Profiler. In the event that you need to profile a process that can't be started by Start-DbgProfiler, you can set these environment variables yourself (either globally or within a command/PowerShell prompt) and then launch the target process. DebugTools cmdlets that operate on profiler sessions can then be instructed to use the -Global profiler session, which targets all processes on the system emitting events to DebugTools Profiler ETW Event Provider.

The following table lists the environment variables that are required to load DebugTools into the target process in the first place

Variable Runtime Value Description
COR_ENABLE_PROFILING .NET Framework 1 Specifies that a profiler should be loaded
COR_PROFILER .NET Framework {9FA9EA80-BE5D-419E-A667-15A672CBD280} Identifies the DebugTools Profiler
COR_PROFILER_PATH_32 .NET Framework C:\DebugTools\x86\Profiler.x86.dll The path to the profiler for x86 processes
COR_PROFILER_PATH_64 .NET Framework C:\DebugTools\x64\Profiler.x64.dll The path to the profiler for x64 processes
CORECLR_ENABLE_PROFILING .NET Core 1 Specifies that a profiler should be loaded
CORECLR_PROFILER .NET Core {9FA9EA80-BE5D-419E-A667-15A672CBD280} Identifies the DebugTools Profiler
CORECLR_PROFILER_PATH_32 .NET Core C:\DebugTools\x86\Profiler.x86.dll The path to the profiler for x86 processes
CORECLR_PROFILER_PATH_64 .NET Core C:\DebugTools\x64\Profiler.x64.dll The path to the profiler for x64 processes

At a minimum, 3 environment variables need to be specified: [COR/CORECLR]_ENABLE_PROFILING, [COR/CORECLR]_PROFILER and [COR/CORECLR]_PROFILER_PATH_[32/64]. The variables required vary depending on whether the target process is x86 or x64, and a .NET Framework or .NET Core process. For simplicity, all 8 environment variables can be specified, and then the .NET runtime will figure out which variables are relevant to it on its own.

The settings of the profiler itself can be configured by the following environment variables. While all environment variables are normally optional, when starting a process outside of the control of Start-DbgProfiler, it will not be possible to manually turn frame tracing off and on, so DEBUGTOOLS_TRACESTART must always be specified else no frame events will be generated.

Variable Value Description
DEBUGTOOLS_WAITFORDEBUG 1 Halts the target process under a debugger is attached
DEBUGTOOLS_DETAILED 1 Specifies that method parameters/return values should also be recorded
DEBUGTOOLS_TRACESTART 1 Specifies that the profiler should begin recording frames immediately from process startup
DEBUGTOOLS_PARENT_PID Number The PID of the parent process that launched the target process. The target process will automatically terminate when the specified parent process terminates
DEBUGTOOLS_TRACEVALUEDEPTH Number The number of levels to traverse classes/structs when recording values. e.g. 1
DEBUGTOOLS_TARGET_PROCESS String The name of the process to limit tracing to. e.g. devenv.exe. The profiler will automatically unload if the process name does not match this value
DEBUGTOOLS_MODULEBLACKLIST String A tab delimited list of modules to blacklist. This list must end in a double tab. e.g. foo.dll\tbar.dll\t\t
DEBUGTOOLS_MODULEWHITELIST String A tab delimited list of modules to whitelist. This list must end in a double tab. e.g. foo.dll\tbar.dll\t\t
DEBUGTOOLS_IGNORE_DEFAULT_BLACKLIST 1 Specifies that the default module blacklist embedded in the profiler should be ignored
DEBUGTOOLS_SYNCHRONOUS_TRANSFERS 1 Specifies that DebugTools should use its own mechanism for reading events from the profiler, rather than using ETW. This will guarantee no events are dropped, at the cost of performance
DEBUGTOOLS_IGNORE_POINTERVALUE 1 Specifies that profiler should not attempt to read unmanaged pointer values

The following demonstrates how environment variables can be configured to manually load the DebugTools Profiler into a process that is started from an existing PowerShell prompt

$env:COR_ENABLE_PROFILING="1"
$env:COR_PROFILER="{9FA9EA80-BE5D-419E-A667-15A672CBD280}"
$env:COR_PROFILER_PATH_32="C:\DebugTools\x86\Profiler.x86.dll"
$env:COR_PROFILER_PATH_64="C:\DebugTools\x64\Profiler.x64.dll"
$env:DEBUGTOOLS_TRACESTART="1"

Start-Process powershell

In order to ensure essential profiling events are captured (including module loads and method JITs) it is recommended to instruct the profiler controller to begin watching for global events prior to launching the target process. This can easily be done by running any profiler session cmdlet and specifying the -Global parameter

# Start watching for global events prior to launching the target process
C:\> Trace-DbgProfilerStack -Global

Once the global session has been initialized, it will continue to run for the life of the profiler controller process. It is recommended to only profile a single global process at a time, and reopen the profiler controller process when you wish to profile a different global process.

Parameters

The following table lists the configuration parameters that are available when invoking the Start-DbgProfiler cmdlet

Parameter Description
ProcessName Specifies the the process to launch.
Detailed Specifies that detailed tracing should be performed, capturing method parameters and return values.
TraceStart Instructs the profiler to begin recording frames immediately from process startup. Cannot specify in conjunction with -PassThru
ValueDepth Specifies the number of levels deep that objects should be traced when using Detailed tracing mode.
TargetProcess Specifies that the profiler should only be injected into the specified process. e.g. devenv.exe. Allows you to prevent the profiler from being injected into child processes created by the target process (which will inherit the environment variables of the parent process), or doing the opposite, and only attaching to a given child process, not the target process that you actually launch.
Dbg For development purposes only. Indicates that the Visual Studio instance that is debugging DebugTools should attach to the target process as well. If you are not debugging DebugTools in Visual Studio, this parameter should not be used.
WinDbg For development purposes only. Launches the target process under WinDbg. Attempts to automatically detect whether to use WinDbg x86 or x64
IgnoreDefaultBlacklist Instructs the profiler to ignore the default module blacklist.
Synchronous Specifies that DebugTools should use its own mechanism for reading events from the profiler, rather than using ETW (which writes events asynchronously). This will guarantee no events are dropped, at the cost of performance
ModuleWhitelist Specifies the module patterns to not blacklist amongst the amongst the module patterns you have specified to blacklist.
ModuleBlacklist Specifies the module patterns to blacklist, excluding all methods within these modules from profiling.
DisablePipe For development purposes only. Instructs DebugTools to not attempt to establish a named pipe connection to the target process.
IncludeUnknownUnmanagedTransitions Specifies whether unmanaged transition frames belonging to methods we aren't tracing should otherwise be included in the trace output.
IgnorePointerValue Specifies that profiler should not attempt to read unmanaged pointer values
Minimized Specifies whether the target process should be started minimized
PassThru Emits the new ProfilerSession from the cmdlet. Cannot specify in conjunction with -TraceStart

Troubleshooting

Profiler Not Being Loaded

When the .NET runtime attempts to load a profiler, an event will be logged under Event Viewer.

To check the status of the DebugTools Profiler in a target process

  1. Run eventvwr.msc
  2. Navigate to the Application event log
  3. Profiler events will be logged under source .NET Runtime with Event ID 1022. Events will specify whether the profiler was successfully loaded, whether the profiler rejected the load attempt (because a Target Process was specified), or whether some other type of error occurred.

When Start-DbgProfiler launches the target process, by default it will attempt to establish a connection via a named pipe between itself and the target process. If this fails, DebugTools will automatically attempt to determine what went wrong, including inspecting Event Viewer for the aforementioned event. If your antivirus blocks .NET from attempting to load the DebugTools profiler, you may not get an event logged in Event Viewer, however a debug message will be printed by .NET which is visible when launching the process under a debugger such as WinDbg (specify -WinDbg to automatically do this).

Unexpected Sequence Number

The DebugTools Profiler relies on being able to accurately capture a shadow stack of all frames as they execute in the target process. In the event there is an error in DebugTools' bookkeeping, or a frame is lost, this will completely throw off all future events that are logged. To protect against this, DebugTools assigns a unique per-thread sequence number to each frame. If the profiler detects its shadow stack has become corrupted, the HRESULT PROFILER_E_UNKNOWN_FRAME will be emitted to the profiler controller, which will cause tracing to immediately terminate.

As DebugTools uses ETW to receive events from the profiler, when a large amount of events are being logged it is possible that events may sometimes be dropped. This is due to the fact that ETW is asynchronous; it does not wait for events to be read, favoring performance over reliability. In the event you are experiencing issues with frame events potentially being dropped (causing traces to throw exceptions about sequence number mismatches) you can consider using synchronous logging, wherein events will be communicated from the profiler to the profiler controller one at a time. This can be done by specifying the -Synchronous parameter to Start-DbgProfiler

Start-DbgProfiler -Synchronous

Project Overview

User Guide

Clone this wiki locally