{
	"id": "aa1f18f9-92f0-46b5-9c0f-74b80fa6d217",
	"created_at": "2026-04-06T00:09:39.286819Z",
	"updated_at": "2026-04-10T03:21:52.194511Z",
	"deleted_at": null,
	"sha1_hash": "901b58edb6068517ff2b706ae8c89e6ea5aa608e",
	"title": "Profiling Overview - .NET Framework",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 88856,
	"plain_text": "Profiling Overview - .NET Framework\r\nBy tommcdon\r\nArchived: 2026-04-05 14:04:50 UTC\r\nA profiler is a tool that monitors the execution of another application. A common language runtime (CLR) profiler\r\nis a dynamic link library (DLL) that consists of functions that receive messages from, and send messages to, the\r\nCLR by using the profiling API. The profiler DLL is loaded by the CLR at runtime.\r\nTraditional profiling tools focus on measuring the execution of the application. That is, they measure the time that\r\nis spent in each function or the memory usage of the application over time. The profiling API targets a broader\r\nclass of diagnostic tools such as code-coverage utilities and even advanced debugging aids. These uses are all\r\ndiagnostic in nature. The profiling API not only measures but also monitors the execution of an application. For\r\nthis reason, the profiling API should never be used by the application itself, and the application’s execution should\r\nnot depend on (or be affected by) the profiler.\r\nProfiling a CLR application requires more support than profiling conventionally compiled machine code. This is\r\nbecause the CLR introduces concepts such as application domains, garbage collection, managed exception\r\nhandling, just-in-time (JIT) compilation of code (converting common intermediate language, or CIL, code into\r\nnative machine code), and similar features. Conventional profiling mechanisms cannot identify or provide useful\r\ninformation about these features. The profiling API provides this missing information efficiently, with minimal\r\neffect on the performance of the CLR and the profiled application.\r\nJIT compilation at runtime provides good opportunities for profiling. The profiling API enables a profiler to\r\nchange the in-memory CIL code stream for a routine before it is JIT-compiled. In this manner, the profiler can\r\ndynamically add instrumentation code to particular routines that need deeper investigation. Although this\r\napproach is possible in conventional scenarios, it is much easier to implement for the CLR by using the profiling\r\nAPI.\r\nThe Profiling API\r\nTypically, the profiling API is used to write a code profiler, which is a program that monitors the execution of a\r\nmanaged application.\r\nThe profiling API is used by a profiler DLL, which is loaded into the same process as the application that is being\r\nprofiled. The profiler DLL implements a callback interface (ICorProfilerCallback in the .NET Framework version\r\n1.0 and 1.1, ICorProfilerCallback2 in version 2.0 and later). The CLR calls the methods in that interface to notify\r\nthe profiler of events in the profiled process. The profiler can call back into the runtime by using the methods in\r\nthe ICorProfilerInfo and ICorProfilerInfo2 interfaces to obtain information about the state of the profiled\r\napplication.\r\nNote\r\nhttps://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/profiling-overview\r\nPage 1 of 6\n\nOnly the data-gathering part of the profiler solution should be running in the same process as the profiled\r\napplication. All user interface and data analysis should be performed in a separate process.\r\nThe following illustration shows how the profiler DLL interacts with the application that is being profiled and the\r\nCLR.\r\nThe Notification Interfaces\r\nICorProfilerCallback and ICorProfilerCallback2 can be considered notification interfaces. These interfaces consist\r\nof methods such as ClassLoadStarted, ClassLoadFinished, and JITCompilationStarted. Each time the CLR loads\r\nor unloads a class, compiles a function, and so on, it calls the corresponding method in the profiler's\r\nICorProfilerCallback or ICorProfilerCallback2 interface.\r\nFor example, a profiler could measure code performance through two notification functions: FunctionEnter2 and\r\nFunctionLeave2. It just time-stamps each notification, accumulates results, and outputs a list that indicates which\r\nfunctions consumed the most CPU or wall-clock time during the execution of the application.\r\nThe Information Retrieval Interfaces\r\nThe other main interfaces involved in profiling are ICorProfilerInfo and ICorProfilerInfo2. The profiler calls these\r\ninterfaces as required to obtain more information to help its analysis. For example, whenever the CLR calls the\r\nFunctionEnter2 function, it supplies a function identifier. The profiler can get more information about that\r\nfunction by calling the ICorProfilerInfo2::GetFunctionInfo2 method to discover the function's parent class, its\r\nname, and so on.\r\nSupported Features\r\nThe profiling API provides information about a variety of events and actions that occur in the common language\r\nruntime. You can use this information to monitor the inner workings of processes and to analyze the performance\r\nof your .NET Framework application.\r\nThe profiling API retrieves information about the following actions and events that occur in the CLR:\r\nhttps://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/profiling-overview\r\nPage 2 of 6\n\nCLR startup and shutdown events.\r\nApplication domain creation and shutdown events.\r\nAssembly loading and unloading events.\r\nModule loading and unloading events.\r\nCOM vtable creation and destruction events.\r\nJust-in-time (JIT) compilation and code-pitching events.\r\nClass loading and unloading events.\r\nThread creation and destruction events.\r\nFunction entry and exit events.\r\nExceptions.\r\nTransitions between managed and unmanaged code execution.\r\nTransitions between different runtime contexts.\r\nInformation about runtime suspensions.\r\nInformation about the runtime memory heap and garbage collection activity.\r\nThe profiling API can be called from any (non-managed) COM-compatible language.\r\nThe API is efficient with regard to CPU and memory consumption. Profiling does not involve changes to the\r\nprofiled application that are significant enough to cause misleading results.\r\nThe profiling API is useful to both sampling and non-sampling profilers. A sampling profiler inspects the profile at\r\nregular clock ticks, say, at 5 milliseconds apart. A non-sampling profiler is informed of an event synchronously\r\nwith the thread that causes the event.\r\nUnsupported Functionality\r\nThe profiling API does not support the following functionality:\r\nUnmanaged code, which must be profiled using conventional Win32 methods. However, the CLR profiler\r\nincludes transition events to determine the boundaries between managed and unmanaged code.\r\nSelf-modifying applications that modify their own code for purposes such as aspect-oriented programming.\r\nBounds checking, because the profiling API does not provide this information. The CLR provides intrinsic\r\nsupport for bounds checking of all managed code.\r\nRemote profiling, which is not supported for the following reasons:\r\nhttps://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/profiling-overview\r\nPage 3 of 6\n\nRemote profiling extends execution time. When you use the profiling interfaces, you must minimize\r\nexecution time so that profiling results will not be unduly affected. This is especially true when\r\nexecution performance is being monitored. However, remote profiling is not a limitation when the\r\nprofiling interfaces are used to monitor memory usage or to obtain runtime information about stack\r\nframes, objects, and so on.\r\nThe CLR code profiler must register one or more callback interfaces with the runtime on the local\r\ncomputer on which the profiled application is running. This limits the ability to create a remote code\r\nprofiler.\r\nNotification Threads\r\nIn most cases, the thread that generates an event also executes notifications. Such notifications (for example,\r\nFunctionEnter and FunctionLeave) do not need to supply the explicit ThreadID . Also, the profiler might decide\r\nto use thread-local storage to store and update its analysis blocks instead of indexing the analysis blocks in global\r\nstorage, based on the ThreadID of the affected thread.\r\nNote that these callbacks are not serialized. Users must protect their code by creating thread-safe data structures\r\nand by locking the profiler code where necessary to prevent parallel access from multiple threads. Therefore, in\r\ncertain cases you can receive an unusual sequence of callbacks. For example, assume that a managed application\r\nis spawning two threads that are executing identical code. In this case, it is possible to receive a\r\nICorProfilerCallback::JITCompilationStarted event for some function from one thread and a FunctionEnter\r\ncallback from the other thread before receiving the ICorProfilerCallback::JITCompilationFinished callback. In\r\nthis case, the user will receive a FunctionEnter callback for a function that may not have been fully just-in-time\r\n(JIT) compiled yet.\r\nSecurity\r\nA profiler DLL is an unmanaged DLL that runs as part of the common language runtime execution engine. As a\r\nresult, the code in the profiler DLL is not subject to the restrictions of managed code access security. The only\r\nlimitations on the profiler DLL are those imposed by the operating system on the user who is running the profiled\r\napplication.\r\nProfiler authors should take appropriate precautions to avoid security-related issues. For example, during\r\ninstallation, a profiler DLL should be added to an access control list (ACL) so that a malicious user cannot modify\r\nit.\r\nCombining Managed and Unmanaged Code in a Code Profiler\r\nAn incorrectly written profiler can cause circular references to itself, resulting in unpredictable behavior.\r\nA review of the CLR profiling API may create the impression that you can write a profiler that contains managed\r\nand unmanaged components that call each other through COM interop or indirect calls.\r\nhttps://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/profiling-overview\r\nPage 4 of 6\n\nAlthough this is possible from a design perspective, the profiling API does not support managed components. A\r\nCLR profiler must be completely unmanaged. Attempts to combine managed and unmanaged code in a CLR\r\nprofiler may cause access violations, program failure, or deadlocks. The managed components of the profiler will\r\nfire events back to their unmanaged components, which would subsequently call the managed components again,\r\nresulting in circular references.\r\nThe only location where a CLR profiler can call managed code safely is in the common intermediate language\r\n(CIL) body of a method. The recommended practice for modifying the CIL body is to use the JIT recompilation\r\nmethods in the ICorProfilerCallback4 interface.\r\nIt is also possible to use the older instrumentation methods to modify CIL. Before the just-in-time (JIT)\r\ncompilation of a function is completed, the profiler can insert managed calls in the CIL body of a method and then\r\nJIT-compile it (see the ICorProfilerInfo::GetILFunctionBody method). This technique can successfully be used for\r\nselective instrumentation of managed code, or to gather statistics and performance data about the JIT.\r\nAlternatively, a code profiler can insert native hooks in the CIL body of every managed function that calls into\r\nunmanaged code. This technique can be used for instrumentation and coverage. For example, a code profiler could\r\ninsert instrumentation hooks after every CIL block to ensure that the block has been executed. The modification of\r\nthe CIL body of a method is a very delicate operation, and there are many factors that should be taken into\r\nconsideration.\r\nProfiling Unmanaged Code\r\nThe common language runtime (CLR) profiling API provides minimal support for profiling unmanaged code. The\r\nfollowing functionality is provided:\r\nEnumeration of stack chains. This feature enables a code profiler to determine the boundary between\r\nmanaged code and unmanaged code.\r\nDetermination whether a stack chain corresponds to managed code or native code.\r\nIn the .NET Framework versions 1.0 and 1.1, these methods are available through the in-process subset of the\r\nCLR debugging API. They are defined in the CorDebug.idl file.\r\nIn the .NET Framework 2.0 and later, you can use the ICorProfilerInfo2::DoStackSnapshot method for this\r\nfunctionality.\r\nUsing COM\r\nAlthough the profiling interfaces are defined as COM interfaces, the common language runtime (CLR) does not\r\nactually initialize COM to use these interfaces. The reason is to avoid having to set the threading model by using\r\nthe CoInitialize function before the managed application has had a chance to specify its desired threading model.\r\nSimilarly, the profiler itself should not call CoInitialize , because it may pick a threading model that is\r\nincompatible with the application being profiled and may cause the application to fail.\r\nCall Stacks\r\nhttps://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/profiling-overview\r\nPage 5 of 6\n\nThe profiling API provides two ways to obtain call stacks: a stack snapshot method, which enables sparse\r\ngathering of call stacks, and a shadow stack method, which tracks the call stack at every instant.\r\nStack Snapshot\r\nA stack snapshot is a trace of the stack of a thread at an instant in time. The profiling API supports the tracing of\r\nmanaged functions on the stack, but it leaves the tracing of unmanaged functions to the profiler's own stack\r\nwalker.\r\nFor more information about how to program the profiler to walk managed stacks, see the\r\nICorProfilerInfo2::DoStackSnapshot method in this documentation set, and Profiler Stack Walking in the .NET\r\nFramework 2.0: Basics and Beyond.\r\nShadow Stack\r\nUsing the snapshot method too frequently can quickly create a performance issue. If you want to take stack traces\r\nfrequently, your profiler should instead build a shadow stack by using the FunctionEnter2, FunctionLeave2,\r\nFunctionTailcall2, and ICorProfilerCallback2 exception callbacks. The shadow stack is always current and can\r\nquickly be copied to storage whenever a stack snapshot is needed.\r\nA shadow stack may obtain function arguments, return values, and information about generic instantiations. This\r\ninformation is available only through the shadow stack and may be obtained when control is handed to a function.\r\nHowever, this information may not be available later during the run of the function.\r\nCallbacks and Stack Depth\r\nProfiler callbacks may be issued in very stack-constrained circumstances, and a stack overflow in a profiler\r\ncallback will lead to an immediate process exit. A profiler should make sure to use as little stack as possible in\r\nresponse to callbacks. If the profiler is intended for use against processes that are robust against stack overflow,\r\nthe profiler itself should also avoid triggering stack overflow.\r\nSource: https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/profiling-overview\r\nhttps://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/profiling-overview\r\nPage 6 of 6",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"MITRE"
	],
	"references": [
		"https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/profiling-overview"
	],
	"report_names": [
		"profiling-overview"
	],
	"threat_actors": [],
	"ts_created_at": 1775434179,
	"ts_updated_at": 1775791312,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/901b58edb6068517ff2b706ae8c89e6ea5aa608e.pdf",
		"text": "https://archive.orkl.eu/901b58edb6068517ff2b706ae8c89e6ea5aa608e.txt",
		"img": "https://archive.orkl.eu/901b58edb6068517ff2b706ae8c89e6ea5aa608e.jpg"
	}
}