You can discern a lot of information about an API from what appear to be subtle or irrelevant details
For example, each ICorDebug object has a logical parent. (See here for a brief explanation of the different ICorDebug interfaces). Here's a chart:
ICorDebugProcess ICorDebugThread ICorDebugChain ICorDebugFrame, ICorDebugILFrame, ICorDebugNativeFrame ICorDebugAppDomain ICorDebugAssembly ICorDebugModule ICorDebugClass ICorDebugFunction ICorDebugCode ICorDebugBreakpoint ICorDebugType ICorDebugStepper
ICorDebugValue may have a variety of parents depending on what value it is representing: global (static) variables, thread-locals, app-domain locals, and local variables, can all have different parents.
Overall, that may seem like very boring trivia. If you think about what each of these interfaces represent, the grouping should make sense. However, this demonstrates some key relationships in the APIs. For example:
- Steppers can step across appdomain boundaries. Threads can cross appdomain boundaries. Therefore, steppers and threads are process-wide and not just children of ICorDebugAppDomain.
- Breakpoints have appdomain affinity. This means truly setting a process-wide breakpoint means creating a new ICorDebugBreakpoint for each appdomain. MDbg's breakpoint layer does this under the covers.
- All classes live in a module.
- Functions may not necessarily live in just a class. Some languages (like C++) support global functions which live at the module scope.
- All code bytes are associated with a function.
- ICDClass is a child of Module, which means that all classes live in a module. In fact, in V1.1, Class and Type were interchangable, so this really mean that in V1.1, all types lived in a module.
- In V2, we added generics, would allowed more complex types that could spawn multiple modules. So ICDType (which was added in V2) is not a child of ICDModule. For example, you could have moduleA!List<moduleB!Number>. So that generic instantiation has parts in both moduleA and moduleB.
- Technically Module should be under Assembly. The fact that it's not really shows how module-centric debugging is.
Considering other possibilities:
As a further thought experiment, imagine making a random mutation in the tree above, and then thinking out what the ramifications of that decision would be. For example, if you pushed ICDType up to be a child of ICDProcess, then that would imply that Types could spawn multiple appdomains, and that would change a lot of rules about how AppDomains and serialization work. If you pushed ICDThread down under an AppDomain, that would imply threads are trapped inside an AppDomain.