Recently, I asked myself how I would print the call stack of my program. After reading a bit about this, mainly in this document, I decided that I wanted to try it out. Here is my minimal solution (which is not recommended for use in practice).
The basic idea is that once you have a valid game pointer you can get the base address of the previous stack frame and the current return address. These are just the next words down the stack. To get the initial frame pointer, I use gcc’s built-in function for that. Theoretically, it should be possible to get the initial frame pointer via a pointer to the first stack variable of the function, but this method seems unreliable because the compile might deviate from that convention.
#include void testStack() { Dl_info info; uint64_t* esb = reinterpret_cast<uint64_t*>(__builtin_frame_address(0)); uint64_t* ra = reinterpret_cast<uint64_t*>(__builtin_return_address(0)); uint64_t* saved_esb; std::cout << "Return address according to gcc: " << ra << std::endl; std::cout << "--------------------------------------------------------------------" << std::endl; while (*saved_esb != 0) { saved_esb = esb; uint64_t my_ra = *(esb+1); std::cout << "Value of current stack base pointer: " << esb << std::endl; std::cout << "Return address according to me: " << std::hex << my_ra << std::endl; dladdr((void*)my_ra, &info); std::cout << info.dli_fname; if (info.dli_sname) { std::cout << ", " << info.dli_sname; } std::cout << std::endl; std::cout << "Value of previous stack base pointer: " << std::hex << *(saved_esb) << std::endl; std::cout << "--------------------------------------------------------------------" << std::endl; if (saved_esb != 0) { esb = reinterpret_cast<uint64_t*>(*saved_esb); } } }
Here are, for reference, the documentation pages for the functions that I’ve used: