Decyphering methods signature with .NET profiling APIs

Introduction

After introducing the CLR profiling API by tracing managed methods calls, then dealing with assemblies and types, it is time to look at methods signatures. Remember that the starting point is the FunctionID received by the Enter callback each time a method is executed.

The question answered by this post is how to build the signature of the method given a FunctionID.

A method signature is built from its return value (or void), its name and a list of parameters. All these details are stored in the module metadata generated by the C# compiler. So the first step is to get the metadata token corresponding to a FunctionID thanks to ICorProfilerInfo::GetFunctionInfo:

Next, use the IMetaDataImport corresponding to the module to call GetMethodProps and pass the function metadata token:

The return type and parameters type of the function are encoded in a binary format defined in the ECMA-335 specification. This binary blob is pointed to by the pSig parameter. Hopefully, you don’t have to implement a blob signature parser yourself. This has been done my Rico Mariani or Peter Sollich and it relies on low level helpers from cor.h such as CorSigUncompressData.

Here is an example of a signature blob for a non-static method returning void and accepting a float and a double as parameters:

The well-known types are encoded and available as ELEMENT_TYPE_xxx constants from corhdr.h.

For custom types identified as ELEMENT_TYPE_CLASS for reference types or ELEMENT_TYPE_VALUETYPE for value types, the metadata token of the type is “compressed” as part of the signature (see CorSigUncompressToken in cor.h for implementation details). If the type is defined in the same assembly as the method, you get a TypeDef token (starting with 02) used to call IMetaDataImport::GetTypeDefProps. If not, it will be a TypeRef token (starting with 01) used to call IMetaDataImport::GetTypeRefProps.

More on typeRef and typeDef later.

This is nice but since the parameter name is not encoded in the signature blob, you have to work more to get it. First, you have to call IMetaDataImport::EnumParams, to get the metadata token mdParamDef for each parameter:

Generic methods have more complicated signatures to compute

The following figure shows how to handle generic methods:

The main change for such a generic method is that, in the signature blob, you will get the number of generic arguments just after the total parameters count and the first “calling convention” data will be IMAGE_CEE_CS_CALLCONV_GENERIC. The other difference is how to deal with generic parameters in the blob that will all share the same ELEMENT_TYPE_MVAR value followed by a position (starting from 0). This is the position in the array returned by ICorProfilerInfo2::GetFunctionInfo2 for the ClassID.

The final code should look like the following:

One last detail before looking for the parameters value in the next post: for ALL reference types, the same implementation is generated by the JIT compiler. If you think about it, there is no need to implement a List<string> in a different way than a List of any other reference type: they all deal with references (i.e. addresses). The name picked by the CLR team to identify this “generic reference type” is System.__Canon that stands for “canonical”. So expect to receive that type name a lot!

References

Loves to understand how things work (MVP Developer Technologies)