BDE 4.14.0 Production release
Loading...
Searching...
No Matches
bsls_stackaddressutil

Detailed Description

Outline

Purpose

Provide a utility for obtaining return addresses from the stack.

Classes

See also
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;
// CREATORS
AddressEntry(void *funcAddress, int index)
: d_funcAddress(funcAddress)
, d_index(index)
// Create an 'AddressEntry' object and initialize it with the
// specified 'funcAddress' and 'index'.
{}
bool operator<(const AddressEntry& rhs) const
// Return 'true' if the address stored in the object is lower than
// the address stored in 'rhs' and 'false' otherwise. Note that
// this is a member function for brevity, it only exists to
// facilitate sorting 'AddressEntry' objects in a vector.
{
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.

Definition bslstl_vector.h:1025

Next, we define findIndex:

static int findIndex(const void *retAddress)
// Return the index of the address entry whose function uses an
// instruction located at specified 'retAddress'. The behavior is
// undefined unless 'retAddress' is the address of an instruction in
// use by a function referred to by an address entry in 'entries'.
{
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;
}
size_type size() const BSLS_KEYWORD_NOEXCEPT
Return the number of elements in this vector.
Definition bslstl_vector.h:2664

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(); // RETURN
}
else {
return volatileGlobal * 2 * func1(); // RETURN
}
}
static unsigned int func3()
{
if (volatileGlobal > 10) {
return (volatileGlobal -= 100) * 2 * func3(); // RETURN
}
else {
return volatileGlobal * 3 * func2(); // RETURN
}
}
static unsigned int func4()
{
if (volatileGlobal > 10) {
return (volatileGlobal -= 100) * 2 * func4(); // RETURN
}
else {
return volatileGlobal * 4 * func3(); // RETURN
}
}
static unsigned int func5()
{
if (volatileGlobal > 10) {
return (volatileGlobal -= 100) * 2 * func5(); // RETURN
}
else {
return volatileGlobal * 5 * func4(); // RETURN
}
}
static unsigned int func6()
{
if (volatileGlobal > 10) {
return (volatileGlobal -= 100) * 2 * func6(); // RETURN
}
else {
return volatileGlobal * 6 * func5(); // RETURN
}
}

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.

unsigned int func1()
// Call 'getAddresses' and verify that the returned set of addresses
// matches our expectations.
{

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());
iterator begin() BSLS_KEYWORD_NOEXCEPT
Definition bslstl_vector.h:2511
iterator end() BSLS_KEYWORD_NOEXCEPT
Definition bslstl_vector.h:2519
void push_back(const VALUE_TYPE &value)
Definition bslstl_vector.h:3760
void swap(vector &other) BSLS_KEYWORD_NOEXCEPT_SPECIFICATION(AllocatorTraits void clear() BSLS_KEYWORD_NOEXCEPT
Definition bslstl_vector.h:1712

Then, we obtain the stack addresses with getStackAddresses.

enum { BUFFER_LENGTH = 100 };
void *buffer[BUFFER_LENGTH];
bsl::memset(buffer, 0, sizeof(buffer));
buffer,
BUFFER_LENGTH);
assert(numAddresses >= (int) entries.size());
assert(numAddresses < BUFFER_LENGTH);
assert(0 != buffer[numAddresses-1]);
assert(0 == buffer[numAddresses]);
static int getStackAddresses(void **buffer, int maxFrames)

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;
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;
}
@ k_IGNORE_FRAMES
Definition bsls_stackaddressutil.h:361

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:

struct MyTest {
static void printCheapStack()
{
char str[128];
printf("%s", str);
}
};
static void formatCheapStack(char *output, int length, const char *taskname=0)

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:

struct MyTest2 {
static void printCheapStack()
{
char str[128];
bsls::StackAddressUtil::formatCheapStack(str, 128, "mybinary.tsk");
printf("%s", str);
}
};

resulting in output that looks like this:

Please run "/bb/bin/showfunc.tsk mybinary.tsk 403308 402641 3710C1ED1D ...
... 400F49" to see the stack trace.