runner.extender
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
To demonstrate how this works, we will go over our plugins as examples.
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.
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]
- Overview
- Runner Introduction
- Runner Invocation
- Runner Plugins
- Runner Extender
- Runner FAQ
- Runner Reference
- Unit Introduction
- Unit Tutorial
- Unit FAQ
- Unit Reference
- Calltrace Introduction
- Calltrace Tutorial
- Calltrace FAQ
- Calltrace Plugin
- Calltrace Reference
- Serial Introduction
- Serial Tutorial
- Serial Invocation
- Serial Reference