// bsls_stackaddressutil.h -*-C++-*- #ifndef INCLUDED_BSLS_STACKADDRESSUTIL #define INCLUDED_BSLS_STACKADDRESSUTIL #include <bsls_ident.h> BSLS_IDENT("$Id: $") //@PURPOSE: Provide a utility for obtaining return addresses from the stack. // //@CLASSES: // bsls::StackAddressUtil: utilities for obtaining addresses from the stack // //@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. //.. // bsl::vector<AddressEntry> entries; //.. // 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; // } //.. // 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()); //.. // 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: //.. // struct MyTest { // static void printCheapStack() // { // char str[128]; // bsls::StackAddressUtil::formatCheapStack(str, 128); // printf("%s", str); // } // }; //.. // 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. //.. #include <bsls_platform.h> // ============================ // class bsls::StackAddressUtil // ============================ namespace BloombergLP { namespace bsls { struct StackAddressUtil { // This struct provides a namespace for the function to obtain return // addresses from the stack. // On some platforms, 'getStackAddresses' finds a frame representing // 'getStackAddresses' itself. This frame is usually unwanted. // 'k_IGNORE_FRAMES' instructs the caller as to whether the first frame is // such an unwanted frame. #if defined(BSLS_PLATFORM_OS_LINUX) || defined(BSLS_PLATFORM_OS_DARWIN) enum { k_IGNORE_FRAMES = 1 }; #else enum { k_IGNORE_FRAMES = 0 }; #endif // CLASS METHODS static int getStackAddresses(void **buffer, int maxFrames); // Get an sequence of return addresses from the current thread's // function call stack, ordered from most recent call to least recent, // and load them into the specified array '*buffer', which is at least // the specified 'maxFrames' in length. A return address is an address // stored on the stack that points to the first instruction that will // be executed after the called subroutine returns. If there are more // than 'maxFrames' frames on the stack, only the return addresses for // the 'maxFrames' most recent routine calls are stored. When this // routine completes, 'buffer' will contain an ordered sequence of // return addresses, sorted such that recent calls occur in the array // before calls that took place before them. Return the number of // stack frames stored into 'buffer' on success, and a negative value // otherwise. The behavior is undefined unless 'maxFrames >= 0' and // 'buffer' has room for at least 'maxFrames' addresses. Note that // this routine may fill 'buffer' with garbage if the stack is corrupt, // or on Windows if some stack frames represent optimized routines. static void formatCheapStack(char *output, int length, const char *taskname = 0); // Load the specified 'output' buffer having the specified 'length' // with the Bloomberg standard "cheapstack" contents as a // null-terminated string. On successfully obtaining the current call // stack, this will be instructions on how to run the Bloomberg tool // 'showfunc.tsk' (with the optionally specified 'taskname', otherwise // with an attempt to obtain the system-specific process name) to get // details of the current call stack where 'formatCheapStack' was // called. On failure, text indicating that the call stack was not // obtainable will be written to 'output'. If 'length' is not long // enough for the entire output it will be truncated. The behavior is // undefined unless '0 <= length' and 'output' has the capacity for at // least 'length' bytes. }; } // close package namespace } // close enterprise namespace #endif // ---------------------------------------------------------------------------- // Copyright 2018 Bloomberg Finance L.P. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ----------------------------- END-OF-FILE ----------------------------------