Hi Christoph,
No, the "frametable" is not a single static, global object (it would be hard to make this work with separate compilation, etc).
Rather each function (both ML and C) is associated to a "frame descriptor" detailing where to find live values (for ML functions, the frame descriptor is associated to its function using the return address as a key). When the GC is triggered it traverses the stack (both ML and C) and collects the full list of GC roots. Of course there is also a list of "global" roots which *are* registered in a single, global table. For these roots indeed one must deregister them explicitly (see caml_{register,remove}_global_root and its "generational" friends).
Some pointers:
You can read the stack-walking code in
When the GC is invoked when running ML code, this code depends on some global pointers which are set up in the asm
glue, e.g.
Finally frame descriptors are emitted in the native-code backends, e.g.:
Hope this helps,
Nicolas