top
products_top_active company_top_inactive right_margin_top
zero_bugs_logo

Advanced Usage

Leaner Executables: External Debug Symbols

Suppose that you are building a C or C++ Linux program that is going to be installed on tens or hundreds of your production machines. Since this software is not shipped to customers, you may as well leave the debug information in, to help you later with troubleshooting.

For complex programs the size of the debug information (especially for C++ programs where symbol names often get mangled into very long strings) may be considerable, and it may impact your deployment time.

Hopefully you will not need the debug symbols as often. What if you could store the debug information on only one server instead of N?

Turns out you can pull this trick easily with the following bash script (which you can include in your Makefile as a post-build step):

#! /bin/bash
DBGFILE=DebugInfoServerNetworkMountedPath/$1.dbg
if objcopy --only-keep-debug $1 $DBGFILE; then
    #strip -d $1 # strip debug info, or strip everything:
    strip $1
    objcopy --add-gnu-debuglink=$DBGFILE $1
fi

And that's it. "But how is the debugger going to know how to locate the debug information, since we stripped it out?" one may ask.

Simple. The objcopy --add-gnu-debuglink step creates a special section inside the ELF executable, which will point to the (network) location of the debug information. Both GDB and ZeroBUGS know how to handle it transparently.


Custom Data Visualization

Let us consider the following (however contrived) example:

#include <iostream>
#include <string>

enum AccessLevel
{
    ACCESS_NONE,
    ACCESS_GUEST,
    ACCESS_NORMAL,
    ACCESS_ADMIN
};

class User
{
    std::string name_;
    AccessLevel access_;

public:
    explicit User(const char* name, AccessLevel a = ACCESS_NORMAL)
        : access_(a)
    {
        if (name) name_.assign(name);
    }

    void print(std::ostream& out) const
    {
        out << "name=" << name_ << ", access=" << access_;
    }
};

inline std::ostream&
operator<<(std::ostream& out, const User& user)
{
    user.print(out);
    return out;
}

int main()
{
    User jack("John Doe");
    User admin("Kahuna", ACCESS_ADMIN);

    std::cout << jack << std::endl;
    std::cout << admin << std::endl;
    return 0;
}

When this code is compiled and executed under the debugger, the variables "jack" and "admin" can be visualized in the Local Variables tab:
user_1

The name_ member variable (which is of the std::string type) is being shown by the debugger as if it were a good old C-style string.

The magic behind this convenient feature is implemented in a piece of Python code in the file /usr/local/zero/plugin/.zero.py.

The debugger looks for a Python function conventionaly named on_debug_symbol every time a variable in the target program is about to be rendered. The variables are represented by DebugSymbol objects (the Help that ships with the product contains a reference of the Python API).

The on_debug_symbol takes a DebugSymbol argument, and returns either None or a new DebugSymbol as created by the user. A predefined implementenation lives in /usr/local/zero/plugin/.zero.py, which takes care of rendering some of the most usual standard C++ types, such as std::string, std::vector and std::set.

Users can go further and extend and override the default implementation, for example:

import zero

def do_nothing(sym):
    pass

if __name__ == "__main__":
    try:
        oldfun = on_debug_symbol
    except:
        oldfun = do_nothing


def on_debug_symbol(sym):
    result = oldfun(sym)
    if not result:
        if sym.type().name() == "User":
            mem = sym.children()
            name = oldfun(mem[0]) #transform std::string
            if not name:
                name = mem[0]
            tip = name.value() + ", " + mem[1].value()
            sym.set_tooltip(tip)
    return result
The code above does not alter how variables are displayed, but introduces a custom tool-tip for all instances of the User class.

user_2

Assuming that the python code shown above lives in a file named user.py, you may invoke it by passing the --py-run=user.py to the debugger command line.

Another alternative is to modify the code in /usr/local/zero/plugin/.zero.py based on the example above.

If you chose to make changes to the .zero.py script, then you can arrange for your custom data visualizations to be enabled and disabled from the graphical user interface. In the Language tab of the Tools, Options dialog, note the second group of check buttons:

options

If you peek at the .zero.py script you will notice this code at the beginning of the file:


# tunable params
param = {
    'vector_as_array'   :[True, 'Show std::vectors as C arrays'],
    'set_as_array'      :[False, 'Show std::set objects as arrays'],
    'string_as_cstr'    :[True, 'Show std::string as C string'],
    'qstring_as_cstr'   :[True, 'Show Qt Strings as C strings'],
}

def get_configurable_params():
    return param
        
The get_configurable_params functions is the internal protocol between the script and the main debugger program. Try adding this line to the param dictionary:

    'user_tooltip'   :[True, 'Show User-defined Tooltip'],
        

Restart the debugger, then open the Tools, Options dialog, and select the Language tab. Notice the new check button? Now add this code right at the top of your user-defined function:


if param['user_tooltip'][0] == False:
    return None
        
so that any custom data transformation is skipped when the check box is turned off.


intro documentation_stroke features sshots credits right_bottom

Home | Top | Up | Community