About two weeks ago, I started to add generics support to the debugger.
After doing some research, I soon realized that things are a bit more complicated than I expected, and I need to modify some things in the debugger.
Let's assume we stopped inside some method Foo.Hello() and we want to print some local variable.
Without generics, the debugger simply used Cecil to get the type of that variable.
However, in order to actually do anything useful with the variable, the debugger needs to know a bit more than just its Cecil type. Precisely, it needs to know the exact layout of its fields and - if it wants to access a property - also the MonoMethod * addresses. Because of this, the debugger needs to read the type's MonoClass *.
At the moment, the debugger keeps an internal mapping between Cecil types and MonoClass * pointers. This works because a Cecil type could uniquely be identified by its image and type token - the debugger just read the image and type_token fields from the MonoClass * and then it had something to uniquely identify the corresponding Cecil type.
With generics, things become more complicated.
While Cecil knows about generic types etc., it obviously has no way of knowing the actual generic instantiation of a type - that's something which dynamically happens at run-time.
My first approach to the problem was to read the current generic context (MonoGenericContext *) and construct the Cecil type from it.
This worked, I could instantiate the type in the debugger and display it to the user - for instance IList<int>.
However, the debugger still needs to know the MonoClass * - and here comes the problem.
It's no longer enough just to read image and type_token, we also need to read the generic_class and interpret it. We can't get any unique identification of the class unless we fully read and interpret the generic_class. Of course, this is an expensive operation. We can't just fully read any single MonoClass * that's created just for the sake of inserting it into a hashtable.
Because of this, I had another idea.
Rather than reading the type of the local variable from the symbol file / Cecil and then get its MonoClass * from a hashtable, we directly read it from the target. The debugging info generated by the runtime will be extended by a MonoClass * field.
Key point is this:
It is easy for the debugger to construct a Cecil type from a MonoClass * - but it has no way of getting a MonoClass * from a Cecil type. So the thing the debugger really needs is the MonoClass *, not the Cecil type.