Example: mcGDB support from scratch¶
The easiest way to document mcGDB extension methodology might be with an detailed illustration. So in this document, we'll see, step by step, how to build an mcGDB extension for our own programming environment.
Task-Based Programming Environment and Application¶
In example/task-model.h
you'll find the public API of a
simple programming environment based on concurrent tasks
(implementation is in example/task-model.c
):
struct task_s *
task_new(task_body_f body);
void
task_set_dependency(struct task_s *src, struct task_s *dst);
void
task_run(struct task_s *task);
int
task_get_future(struct task_future_int_s *future);
int
task_get_result(struct task_s *task);
void
task_destroy(struct task_s *task);
And example/appli.c
contains an application running on top
of this environment:
#include "task-model.h"
int produce(void *not_used) {
return 4; // chosen by fair dice roll.
// guaranteed to be random
/* http://xkcd.com/221/ */
}
int consume(void *data) {
int result = 0;
struct task_future_int_s *future_input = (struct task_future_int_s *) data;
/* prepare computation */
result -= 6;
/* consume input */
result += task_get_future(future_input) * 12;
return result;;
}
int main(void) {
struct task_s *prod = task_new(produce);
struct task_s *cons = task_new(consume);
task_set_dependency(NULL, prod);
task_set_dependency(prod, cons);
task_run(prod);
task_run(cons);
printf("Async result: %d\n", task_get_result(cons));
task_destroy(prod);
task_destroy(cons);
}
Fairly easy and straightforward, everything is sequential,
task_run()
does barely nothing and body functions are actually
executed inside task_get_result()
.
mcGDB Debugging Support¶
mcGDB debugging support is implemented inside
example/task_model_debugging.py
.
Initialization¶
To load you Python module, you need to these two commands (or similar) in GDB:
1 2 | python sys.path.append(".")
python import task_model_debugging
|
Line 1 tells Python where to look for your packages, and line 2 actually loads it.
For the example, I put these lines into example/gdbinit
,
and run GDB with gdb -ex "source ./gdbinit" (for security reasons,
GDB may not want to load .gdbinit from an untrusted directory).
Capture Module¶
Let's look at the function struct task_s *
task_new(task_body_f body);
capture breakpoint: TaskNewBreakpoint
:
# Capture `task_new` function calls
class TaskNewBreakpoint(mcgdb.capture.FunctionBreakpoint):
def __init__(self):
mcgdb.capture.FunctionBreakpoint.__init__(self, "task_new")
def prepare_before (self):
data = {}
data["body"] = gdb.parse_and_eval("body")
TaskBodyExecutionBreakpoint(str(data["body"]).split(" ")[0])
return False, True, data
def prepare_after (self, data):
data["task_handle"] = gdb.parse_and_eval("(struct task_s *) {}".format(RETURN_VALUE_REGISTER))
debug_capture("New task created: {} (id: {})".format(data["body"], data["task_handle"]))
Task(data["task_handle"], data["body"])
In prepare_before()
,
we capture the first parameter, corresponding to the body function of
the task. We also set a dynamic breakpoint, at the address of task
body function.
In prepare_after()
,
we capture the return parameter, corresponding to the task handler.
The call to debug_capture()
remains (on
purpose) from the early stage of the development: it was used to
ensure that the breakpoint was correctly triggered, and the parameters
successfully captured.
Lastly, we pass these parameters to the representation module, by
creating a new Task
instance.
All the other capture breakpoints are based on the same template:
# Instantiate capture breakpoints
def init_capture():
TaskNewBreakpoint()
TaskSetDependencyBreakpoint()
TaskRunBreakpoint()
TaskGetResultBreakpoint()
TaskGetFutureBreakpoint()
TaskDestroyedBreakpoint()
Representation Module¶
The representation module consists here of a single class
definition, Task
:
# Debugger representation for tasks
class Task:
tasks = {}
uids = 0
def __init__(self, handle, body):
self.body = body
self.alive = True
self.result = None
self.depends_of_task = None
self.executing = False
self.running = False
self.uid = Task.uids
Task.uids += 1
handle = str(handle)
self.handle = handle
Task.tasks[handle] = self
info_representation("New task {} #{}".format(handle, self.uid))
def run(self):
self.running = True
## <Documentation representation start_exec example> # called when the task starts its execution
def start_execution(self):
if mcgdb.toolbox.catchable.is_set("catch_exec", self.uid):
mcgdb.capture.FunctionBreakpoint.push_stop_request(
"Execution catchpoint triggered by Task #{}".format(self.uid))
self.executing = True
## </Documentation representation start_exec example>#
def finish_execution(self):
self.executing = False
def depends_of(self, src):
self.depends_of_task = src
def got_result(self, res):
self.running = False
self.result = res
def ask_result_from(self, rmt):
self.running = False
pass
def got_result_from(self, rmt, res):
self.running = True
def destroy(self):
self.alive = False
info_representation("Task {} #{} destroyed".format(self.handle, self.uid))
There is a more-or-less one-to-one mapping between the capture breakpoints
and methods of class Task
:
TaskBodyExecutionBreakpoint
callsstart_execution()
andfinish_execution()
,TaskRunBreakpoint
callsrun()
TaskDestroyedBreakpoint
callsdestroy()
- ...
Note
The code in this class is independent of the actual implementation of the supportive environment, another environment based on the same model could reuse this class.
Interaction Module¶
The last consist in implementing the user interaction module. In this example that's fairly simple and limited.
First, for listing the tasks of the application, we browse the
task_model_debugging.Task.tasks
list and display the
different parameters of the task instances:
# List the tasks of an application
class cmd_task_info(gdb.Command):
def __init__(self):
gdb.Command.__init__(self, "task info", gdb.COMMAND_OBSCURE, prefix=True)
def invoke(self, arg, from_tty):
for t in reversed(Task.tasks.values()):
print("#{} Task {} {}".format(t.uid, t.handle, str(t.body).split(" ")[1]))
if t.depends_of_task is not None:
print("\tDepends of: #{} Task {}".format(t.depends_of_task.uid, t.depends_of_task.handle))
print("\t{},\t{}, \tResult: {}".format(
"Alive" if t.alive else "Dead",
"Running" if t.running else "Not running",
t.result))
The second example offers catchpoints on the task body execution. It
parses the parameters to select specific tasks, or all of them, and
then activates the catchpoints in mcgdb.toolbox.catchable
.
# Offer catchpoint on task body execution
class cmd_task_catch_exec(gdb.Command):
"""task catch_exec on|off [task id]*
Sets (on) or remove (off) a catchpoint on one (task id), several or all (empty) the tasks.
`task info` not implemented ...
"""
def __init__ (self):
gdb.Command.__init__(self, "task catch_exec", gdb.COMMAND_OBSCURE)
mcgdb.toolbox.catchable.register("catch_exec")
def invoke(self, arg, from_tty):
args = gdb.string_to_argv(arg)
if not args:
print("Invalid parameters ...")
print(cmd_task_catch_exec.__doc__)
return
do_activate = args.pop(0) == "on"
tids = [mcgdb.toolbox.catchable.ALL_ENTITIES] if not args else args
for tid in args:
try:
mcgdb.toolbox.catchable.activateRemove("catch_exec",
int(tid),
do_activate)
print("{} execution catchpoint on task #{}.".format("Set" if do_activate else "Unset",
tid))
except Exception as e:
print("Didn't work for task {}: {}".format(tid, e))
The second part of this mechanism is implemented in
task_model_debugging.Task.start_execution()
, when a stop
request is registered
(mcgdb.capture.FunctionBreakpoint.push_stop_request()
) when a
task with a catchpoint starts its execution:
# called when the task starts its execution
def start_execution(self):
if mcgdb.toolbox.catchable.is_set("catch_exec", self.uid):
mcgdb.capture.FunctionBreakpoint.push_stop_request(
"Execution catchpoint triggered by Task #{}".format(self.uid))
self.executing = True
Python Code for Debugging Support¶
Module task_model_debugging¶
-
class
task_model_debugging.
TaskBodyExecutionBreakpoint
(addr)[source]¶ Bases:
mcgdb_lite.FunctionBreakpoint
-
class
task_model_debugging.
TaskSetDependencyBreakpoint
[source]¶ Bases:
mcgdb_lite.FunctionBreakpoint
Module mcgdb_lite¶
-
class
mcgdb_lite.
capture
[source]¶ Bases:
object