Skip to content
This repository has been archived by the owner on Jul 22, 2020. It is now read-only.

runner.extender

Klemens Morgenstern edited this page Apr 4, 2018 · 1 revision

Building a plugin

Your plugin only needs to implement the break_points you want to use and return them in a specially named function, given below.

void metal_dbg_setup_bps(std::vector<std::unique_ptr<metal::debug::break_point>> & bps);

This function will return your break_point implementations.

Additionally you can add program options that shall be passed to your plugin you need to add another function, which looks like this.

void metal_gdb_setup_options(boost::program_options::options_description & po);

This function will return your added program options.

To assert the function signatures and linkage is correct, the functions are declared in metal/debug/plugin.hpp, which you should include.

You can use global variables without any problems, since the library is dynamically loaded. That means that there will be no link-conflicts.

See

Examples

To demonstrate how this works, we will go over our plugins as examples.

Exit Code

In embedded systems using the newlib, there is a function _exit(int) which allows to capture the return value of main. So we have the following function in the code, which gets called at the end of the program:

void _exit(int value)
{
}

What we want to do is to put a breakpoint into this function and forward the value passed to the host. The declaration of the breakpoint is as simple as this.

struct exit_stub : metal::debug::break_point
{
    exit_stub() : metal::debug::break_point("_exit")
    {
    }

    void invoke(metal::debug::frame & fr, const std::string & file, int line) override;
};

So we have a breakpoint declared, which may of course contain other members, but none are needed here. Now the implementation of the function looks as follows.

void exit_stub::invoke(metal::debug::frame & fr, const std::string & file, int line)
{
    fr.log() << "***metal-newlib*** Log: Invoking _exit" << std::endl;
    fr.set_exit(std::stoi(fr.arg_list().at(0).value));
}

If you have other overloads of _exit gdb will put a breakpoint into each one, if not explicit (e.g. "_exit(int)").

Our example includes writing to the metal.runner log. It is accessed through fr.log, which returns an std::ostream reference. What happens here, is that we obtain the argument list with fr.arg_list and access the first member of it. This is converted to an int with std::stoi and then passed to the metal.runner with set_exit.

The function set_exit is only provided for exactly this purpose.

As a last step we need to export our breakpoint specialization.

void metal_dbg_setup_bps(std::vector<std::unique_ptr<metal::debug::break_point>> & bps)
{
    bps.push_back(std::make_unique<exit_stub>());
};

You can export several breakpoints, which is why a vector of break_points is passed.

Test library

Since we have established that a plugin can be created very easily, we will demonstrated some of the more elaborate functionality. We will implement a small test library, similar to what our test-backend does. We will however only implement one test.

The test we want to have is a comparison, which also prints out the values of the passed types and the position. In order to do this, we use a macro and a function, so the location get's recorded automatically.

We do not use a template here, because this would lead to a breakpoint for every instanciation. This leads to problems on embedded devices.

void test_equal(bool condition, const char * lhs_name, const char * rhs_name, const char* file, int line) {}
#define TEST_EQUAL(lhs, rhs)  test_equal(lhs == rhs, #lhs, #rhs, __FILE__, __LINE__)

As an example call we'll have this code.

int main(int argc, char*argv[])
{
    int x =  1;
    int y = -1;
    TEST_EQUAL(x, y);
    //yields test_equal(x==y, "x", "y", "test_equal_target.cpp", 13);
    return 0;
}

Let's start with the plugin:

struct test_equal : metal::gdb::break_point
{
    test_equal() : metal::gdb::break_point("test_equal(bool, const char *, const char *, const char*, int)")
    {
    }

    void invoke(metal::gdb::frame & fr, const std::string & file, int line) override;

};

So except for the explicit setting of the function name, there's nothing special here. Now for the invoke function, we first obtain the values.

void test_equal::invoke(metal::gdb::frame & fr, const std::string & file, int line)
{
    bool condition = std::stoi(fr.arg_list(0).value) != 0;
    std::string lhs_name = fr.get_cstring(1);
    std::string rhs_name = fr.get_cstring(2);
    std::string __file__ = fr.get_cstring(3);
    int __line__ = std::stoi(fr.arg_list(4).value);

Thus far there's nothing special, we just get the values passed to the function, utilizing the convenience overload of arg_list and the get_cstring function, to get the string values.

Now what we want to do, is to get the actual values of x and y. Now they are defined by their names in the higher frame, i.e. in main for the example. So what our plugin does is: it steps into the next higher frame, prints the values and then resets the frame.

    fr.select(1); //select on outer
    std::string lhs_value = fr.print(lhs_name).value;
    std::string rhs_value = fr.print(rhs_name).value;
    fr.select(2);

And as a last step we output everything to the console.

    std::cout << __file__ << "(" << __line__ << ")" <<
    std::cout << "equality test [" << lhs_name << " == " << rhs_name << "] ";
    if (condition)
        std::cout << "succeeded: ";
    else
        std::cout << "failed: ";

    std::cout << "[" << lhs_value << " == " << rhs_name << "]" << std::endl;
}

Which would output

test_equal_target.cpp(13) equality test [x == y] failed: [1 == -1]