The main problem with shadow-stack is that it’s not thread-safe.
It sounds like you’ve outgrown it, then.
What about modifying the shadow-stack to use a thread-local linked list?
Another alternative is to add an additional parameter to every function containing the address of the head of the stack frame list.
and have a good understanding of why. Many managed runtimes tackle this threadstate problem.
The ideal solution would be to walk the stack directly, without needing an auxiliary data structure. […] which would likely be different on different processors and maybe different OSs as well.
Yes, this requires platform-specific code. One of the unique design advantages of the shadow stack is that it doesn’t.
Also, even if I had the address of the call frame, I don’t know if the table of offsets from the call frame to the stack roots would be reliable, since I imagine things might shift around during code generation.
The stack root offsets are computed during code generation. You ought not be concerned about it.
The various _Unwind functions give me the IP and the function start, but not the address of the call frame. There is an ability to get the value of any arbitrary register, but I don’t know which register contains what I would need
Then you should look it up.
Another problem with the _Unwind functions is that they aren’t supported on all platforms either. (This is a problem I will eventually have to solve anyway, since my exception personality function also uses these functions.)
In practice, so long as frame pointers are not elided, a handful of lines of assembly per platform should get you a stack walker. If frame pointers are elided, however, you’re looking at parsing DWARF to find out where it was saved, which is probably too expensive for the job.