Provide a utility for obtaining return addresses from the stack.
More...
Namespaces |
namespace | bsls |
Detailed Description
- Outline
-
-
- Purpose:
- Provide a utility for obtaining return addresses from the stack.
-
- Classes:
-
- See also:
- Component balst_stacktraceutil
-
- Description:
- This component defines a
struct
, bsls::StackAddressUtil
, that provides a namespace for a function, getStackAddresses
, that populates an array with an ordered sequence of return addresses from the current thread's function call stack. Each return address points to the (text) memory location of the first instruction to be executed upon returning from a called routine.
- This component also provides a function,
formatCheapStack
, that builds a current stack trace and formats it with instructions on how to use showfunc.tsk
to print out a stack trace matching where this function was called. This is a Bloomberg standard "cheapstack" output.
-
- Usage:
- In this section we show the intended usage of this component.
-
- Example 1: Obtaining Return Addresses and Verifying Their Validity:
- In the following example we demonstrate how to obtain the sequence of function return addresses from the stack using
getStackAddresses
.
- First, we define
AddressEntry
, which will contain a pointer to the beginning of a function and an index corresponding to the function. The <
operator is defined so that a vector of address entries can be sorted in the order of the function addresses. The address entries will be populated so that the entry containing &funcN
when N
is an integer will have an index of N
. struct AddressEntry {
void *d_funcAddress;
int d_index;
AddressEntry(void *funcAddress, int index)
: d_funcAddress(funcAddress)
, d_index(index)
{}
bool operator<(const AddressEntry& rhs) const
{
return d_funcAddress < rhs.d_funcAddress;
}
};
Then, we define entries
, a vector of address entries. This will be populated such that a given entry will contain function address &funcN
and index N
. The elements will be sorted according to function address. Next, we define findIndex
: static int findIndex(const void *retAddress)
{
unsigned int u = 0;
while (u < entries.size()-1 &&
retAddress >= entries[u+1].d_funcAddress) {
++u;
}
assert(u < entries.size());
assert(retAddress >= entries[u].d_funcAddress);
int ret = entries[u].d_index;
if (veryVerbose) {
P_(retAddress) P_(entries[u].d_funcAddress) P(ret);
}
return ret;
}
Then, we define a volatile global variable that we will use in calculation to discourage compiler optimizers from inlining: volatile unsigned int volatileGlobal = 1;
Next, we define a set of functions that will be called in a nested fashion -- func5
calls func4
who calls fun3
and so on. In each function, we will perform some inconsequential instructions to prevent the compiler from inlining the functions.
- Note that we know the
if
conditions in these 5 subroutines never evaluate to true
, however, the optimizer cannot figure that out, and that will prevent it from inlining here. static unsigned int func1();
static unsigned int func2()
{
if (volatileGlobal > 10) {
return (volatileGlobal -= 100) * 2 * func2();
}
else {
return volatileGlobal * 2 * func1();
}
}
static unsigned int func3()
{
if (volatileGlobal > 10) {
return (volatileGlobal -= 100) * 2 * func3();
}
else {
return volatileGlobal * 3 * func2();
}
}
static unsigned int func4()
{
if (volatileGlobal > 10) {
return (volatileGlobal -= 100) * 2 * func4();
}
else {
return volatileGlobal * 4 * func3();
}
}
static unsigned int func5()
{
if (volatileGlobal > 10) {
return (volatileGlobal -= 100) * 2 * func5();
}
else {
return volatileGlobal * 5 * func4();
}
}
static unsigned int func6()
{
if (volatileGlobal > 10) {
return (volatileGlobal -= 100) * 2 * func6();
}
else {
return volatileGlobal * 6 * func5();
}
}
Next, we define the macro FUNC_ADDRESS, which will take a parameter of &<function name>
and return a pointer to the actual beginning of the function's code, which is a non-trivial and platform-dependent exercise. Note: this doesn't work on Windows for global routines. #if defined(BSLS_PLATFORM_OS_AIX)
# define FUNC_ADDRESS(p) (((void **) (void *) (p))[0])
#else
# define FUNC_ADDRESS(p) ((void *) (p))
#endif
Then, we define func1
, the last function to be called in the chain of nested function calls. func1
uses bsls::StackAddressUtil::getStackAddresses
to get an ordered sequence of return addresses from the current thread's function call stack and uses the previously defined findIndex
function to verify those address are correct. Next, we populate and sort the entries
table, a sorted array of AddressEntry
objects that will allow findIndex
to look up within which function a given return address can be found. entries.clear();
entries.push_back(AddressEntry(0, 0));
entries.push_back(AddressEntry(FUNC_ADDRESS(&func1), 1));
entries.push_back(AddressEntry(FUNC_ADDRESS(&func2), 2));
entries.push_back(AddressEntry(FUNC_ADDRESS(&func3), 3));
entries.push_back(AddressEntry(FUNC_ADDRESS(&func4), 4));
entries.push_back(AddressEntry(FUNC_ADDRESS(&func5), 5));
entries.push_back(AddressEntry(FUNC_ADDRESS(&func6), 6));
bsl::sort(entries.begin(), entries.end());
Then, we obtain the stack addresses with getStackAddresses
. enum { BUFFER_LENGTH = 100 };
void *buffer[BUFFER_LENGTH];
bsl::memset(buffer, 0, sizeof(buffer));
int numAddresses = bsls::StackAddressUtil::getStackAddresses(
buffer,
BUFFER_LENGTH);
assert(numAddresses >= (int) entries.size());
assert(numAddresses < BUFFER_LENGTH);
assert(0 != buffer[numAddresses-1]);
assert(0 == buffer[numAddresses]);
Finally, we go through several of the first addresses returned in buffer
and verify that each address corresponds to the routine we expect it to.
- Note that on some, but not all, platforms there is an extra "narcissistic" frame describing
getStackAddresses
itself at the beginning of buffer
. By starting our iteration through buffer
at k_IGNORE_FRAMES
, we guarantee that the first address we examine will be in func1
on all platforms. int funcIdx = 1;
int stackIdx = bsls::StackAddressUtil::k_IGNORE_FRAMES;
for (; funcIdx < (int) entries.size(); ++funcIdx, ++stackIdx) {
assert(stackIdx < numAddresses);
assert(funcIdx == findIndex(buffer[stackIdx]));
}
if (testStatus || veryVerbose) {
Q(Entries:);
for (unsigned int u = 0; u < entries.size(); ++u) {
P_(u); P_((void *) entries[u].d_funcAddress);
P(entries[u].d_index);
}
Q(Stack:);
for (int i = 0; i < numAddresses; ++i) {
P_(i); P(buffer[i]);
}
}
return volatileGlobal;
}
-
- Example 2: Obtaining a "Cheapstack":
- In this example we demonstrate how to use
formatCheapStack
to generate a string containing the current stack trace and instructions on how to print it out from showfunc.tsk
. Note that showfunc.tsk
is a Bloomberg tool that, when given an executable along with a series of function addresses from a process that was running that executable, will print out a human-readable stack trace with the names of the functions being called in that stack trace.
- First, we define our function where we want to format the stack: Calling this function will then result in something like this being printed to standard output:
Please run "/bb/bin/showfunc.tsk <binary_name_here> 403308 402641 ...
... 3710C1ED1D 400F49" to see the stack trace.
Then, if you had encountered this output running the binary "mybinary.tsk", you could see your stack trace by running this command: /bb/bin/showfunc.tsk mybinary.tsk 403308 402641 3710C1ED1D 400F49
This will produce output like this: 0x403308 _ZN6MyTest15printCheapStackEv + 30
0x402641 main + 265
0x3710c1ed1d ???
0x400f49 ???
telling you that MyTest::printCheapStack
was called directly from main
. Note that if you had access to the binary name that was invoked, then that could be provided as the optional last argument to printCheapStack
to get a showfunc.tsk
command that can be more easily invoked, like this: resulting in output that looks like this: Please run "/bb/bin/showfunc.tsk mybinary.tsk 403308 402641 3710C1ED1D ...
... 400F49" to see the stack trace.