Skip to content

CLR Expression Evaluator Intrinsics

Patrick Nelson edited this page Sep 4, 2019 · 4 revisions

Overview

Debugger intrinsics are special functions that can be called by an Expression Compiler to get or set values in the debugger. Intrinsics are used to support the following features (using C# as an example):

  1. $exception to get the pending exception on the current thread.
  2. $stowedexception to get the stowed exception - this is only used when debugging dumps of Windows Store Apps.
  3. $ReturnValue, $ReturnValue1, etc.
  4. Pseudo-variables - these are variables that are declared in the Immediate window. When debugging C# you can declare a variable from the Immediate window by evaluating an expression such as int i = 0;.
  5. Object IDs - these are values generated using the "Make Object ID" feature. Object IDs are a way to track objects in the managed heap. Managed objects can't be tracked by address because garbage collection can move them around in memory.
  6. Debug Managed Memory - To support DataTips in the Debug Managed Memory feature, the Expression Evaluator must have the ability to evaluate an address value as a managed reference.

Getting the list of current valid Aliases

The debugger provides a method, DkmClrRuntimeInstance.GetAliases, to get the list of valid aliases. An alias is made up of a name, full name, alias kind, and type. The name and full name values are used to populate the DkmClrLocalVariableInfo. The kind is one of (Exception, ObjectId, ReturnValue, StowedException, or Variable). The type is an assembly qualified name indicating the type of the variable. There is also a CustomTypeInfoPayload and CustomTypeInfoPayloadTypeId that can be used to communicate custom compiler-specific type information that can't be represented in CLR metadata.

Generating query methods to get alias values

To get the value of a alias, for example $exception, the Expression Compiler generates a query calling an "intrinsic method". To get the value of $exception, the C# Expression Compiler generates code that looks like this:

.method public hidebysig static System.Exception M1() cil managed
{
   call System.Exception Microsoft.VisualStudio.Debugger.Clr.IntrinsicMethods.GetException()
   ret
}

Notice the call to Microsoft.VisualStudio.Debugger.Clr.IntrinsicMethods.GetException. This is the intrinsic method call to get the current exception object. For the compiler to link to this method, it needs the metadata for all of the intrinsic methods. The intrinsic methods metadata block can be obtained by calling DkmClrRuntimeInstance.GetIntrinsicAssemblyMetaDataBytesPtr().

Intrinsic methods metadata

The intrinsic methods metadata is embedded in the debugger binaries and is generated by compiling this C# code:

(note: the implementations of the methods aren't used)

using System;

namespace Microsoft.VisualStudio.Debugger.Clr
{
    public class IntrinsicMethods
    {
        public static object GetObjectAtAddress(UInt64 address)
        {
            return null;
        }

        public static Exception GetException()
        {
            return null;
        }

        public static Exception GetStowedException()
        {
            return null;
        }

        public static object GetReturnValue(int index)
        {
            return null;
        }

        public static void CreateVariable(Type type, string name, Guid customTypeInfoPayloadTypeId, byte[] customTypeInfoPayload)
        {
        }

        public static object GetObjectByAlias(string name)
        {
            return null;
        }

        public static ref T GetVariableAddress<T>(string name)
        {
            throw null;
        }
    }
}

These methods do the following:

  1. GetObjectAtAddress: This function returns the object at a given address and is used by the Debug Managed Memory feature. In the C# Expression Compiler, this function is called by evaluating expressions such as @0x12341234. This means evaluate the object at address 0x12341234.
  2. GetException: This function returns the exception on the current thread
  3. GetStowedException: This function returns the stowed exception
  4. GetReturnValue: This function returns the return value of the given index. The indexes are 1-based. If 0 is passed, this function returns the last return value.
  5. CreateVariable: This method is used to define new variables in the debugger. It takes a type and name. If there is additional type information needed by the compiler that can't be represented in the metadata, it can pass the information in the custom type info payload. The custom type id is a GUID unique to the language defining the variable. These values are passed back for future calls to GetAliases.
  6. GetObjectByAlias: This function is used to get the value of an alias. It is used only for pseudo-variables created using CreateVariable and for objects IDs that are created using the Make Object ID feature.
  7. GetVariableAddress: This returns a reference (or L-value) to a pseudo-variable. It is used to assign values to pseudo-variables created using CreateVariable. It cannot be used for object IDs because they are immutable.
Clone this wiki locally