tl;dr - It is a deep implementation detail - it can be ignored by a typical .NET developer. It is an object that the runtime leaves on the stack to help with stack walking.
Context:
In the early days of computing, there is no exception handling and there is no garbage collection. So there isn't a hard requirement for stacks to be walkable. Therefore, in general, the stack is not walkable.
Technically, the debuggers need them. The debuggers pulled off magic to achieve it. I will skip the actual mechanism here.
To support exception, garbage collection, and debugging, the CLR has to make sure that all managed frames are walkable. To do that, the JIT generates code that is described so that the stack walker knows how to unwind the individual managed frames.
Once in a while, managed code needs to call into some code written in C++. It could be because we are doing a PInvoke, but it could also be various other reasons, for example, when we need to do a GC, or we are doing reflection, etc. In those cases, the stack will have some C++ code on it, and there is no guarantee that c++ code produces stack frames that are walkable, so we have a problem.
Solution:
That's why we have these Frame objects. These Frame objects are meant to solve that problem.
The naming is a bit unfortunate. We have stack frames and we have Frame objects. Note the capital F in Frame, that is how we differentiate them.
A Frame object is allocated on the stack, it is a linked list so every Frame can have a parent Frame (or FRAME_TOP
if we don't have one), and the top Frame can be accessed through a thread static. Together, these form a separate linked list that is side-by-side with the actual stack.
With the two, now we can walk them side by side, and interleave them by their addresses. Remember stack pointer values decrease as we make function calls. So from the callee to caller, the stack pointer should always increase, so we know exactly how to interleave them. More interestingly, these Frame objects are responsible for skipping through the c++ frames, so we know exactly how to walk the whole stack for managed frames.
Caveat - do not attempt to debug frame chain creation by setting breakpoints on constructors. Some Frame objects are directly placed on the stack by assembly stubs or jitted code.
Ever since x64, the platform application binary interface is designed so that the stack is always walkable, at least for Windows. So helping the stack walker is technically no longer needed. But since x86 has to be supported, and it is a nice thing to have that we can skip through the native frames, this separate chain of Frame objects stayed.
Technically, it is a bit confusing that !clrstack
show them there as a row just like the others. That line does NOT represent a function call. Instead, it is a special object that happens to be on the stack, just like your arguments or locals. It might be useful for a .NET runtime developer to debug what's gone wrong in the stack walker, but it is almost useless for developers writing .NET code.
Feel free to learn more about CLR stack walking here