Search code examples
.netgenericsprofilinginstrumentationcil

How to find TypeSpec of generic argument


Some preliminary info

Given the following C# function:

public static void func<T>(T t)
{
    System.Console.WriteLine(t);
}

It is compiled into the following CIL:

.method public hidebysig static void  func<T>(!!T t) cil managed
{
    ldarg.0
    box        !!T
    call       void [mscorlib]System.Console::WriteLine(object)
    ret
}

The signature of the above method is 10 01 01 01 1E 00 where:

10 - Calling convention (IMAGE_CEE_CS_CALLCONV_GENERIC)
01 - Function generic arguments count (which is 1)
01 - Function arguments count (which is 1)
01 - Return type (ELEMENT_TYPE_VOID)
1E - First argument type (ELEMENT_TYPE_MVAR)
00 - Index of above MVAR (which is 0)

Also see the following instruction with it's actual byte code:

box !!T - 8C 1B000001

1B000001 points to the first entry in the TypeSpec table, which points to the blob 02 1E 00 where:

02 - Blob length
1E - Type type (ELEMENT_TYPE_MVAR)
00 - Index of above MVAR (which is 0)

As we can see, the method signature contains the generic argument in a descriptive way, where we have the actual type signature.
However, when using an OpCode which requires a TypeDef/Ref/Spec, a TypeSpec is supplied, and the TypeSpec points to a signature with the type information.


So my question is:
I'm writing a profiler which does some IL Rewriting, and given the function signature, I would like to add some OpCodes to the function body which will operate with the arguments.

Using the IMetaDataImport2 interface, how can I get the TypeSpec token I would need for a given generic parameter?

I can see 2 options:

  1. Iterating over EnumTypeSpecs till I find a signature that matches
  2. Using the IMetaDataEmit interface to create a new TypeSpec

However, for obvious reasons, I would like to avoid those 2 options and choose a more sensible alternative.


Solution

  • So I ended up going with my first suggestion.
    I guess it's not ideal, but it does work.

    Here's the code, if anyone is interested (error handling omitted):

    HCORENUM typeSpecEnum = NULL;
    mdTypeSpec typeSpec = mdTypeSpecNil;
    ULONG outNum = -1;
    
    // Loop through enum
    while (true)
    {
        // Get next enum
        HRESULT hr = pMetadataImport->EnumTypeSpecs(&typeSpecEnum, &typeSpec, 1, &outNum);
        if (hr == S_FALSE && outNum == 0) // According to doc, this means no more. End loop
            break;
    
        // Get the signature of this typespec
        PCCOR_SIGNATURE curSpecSig = NULL;
        ULONG curSpecSigLen = -1;
        pMetadataImport->GetTypeSpecFromToken(typeSpec, &curSpecSig, &curSpecSigLen);
    
        if (curSpecSigLen == <my_len> && memcmp(curSpecSig, <my_sig>, <my_len>) == 0)
            ; // Token found
        else
            typeSpec = mdTypeSpecNil; // Reset and goto next token
    }
    
    pMetadataImport->CloseEnum(typeSpecEnum); // Don't forget to close enum