Annotations¶
Annotations are extra information added to the execution history when a program is recorded. This information can later be queried using the info annotations and ugo annotation commands in UDB.
For example, a unit test program might use this feature to annotate the start of each test case with the name of the test, making it easy to jump to the start of a failed test. A server program might annotate the start of each request from the client, making it easy to jump to the start of a failed request.
Annotations are implemented by the functions in the libundoex library and
declared in undoex/undoex-annotations.h. For each annotation, the caller
provides the following information:
name
A short string identifying the annotation.
detail
An optional short string distinguishing annotations with the same name.
content
Optional data associated with the annotation: an integer, a null-terminated string, or raw binary data.
Creating annotations¶
Here’s an example of a unit test program using
undoex_annotation_add_int() to annotate the start and end of each test
case.
/* Using undoex-annotations to annotate test cases with pass or fail. */
#include <assert.h>
#include <setjmp.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include "undoex-annotations.h"
/* A simple unit test framework. Define a test using the TEST macro. A test
* passes if it reaches the end of its function without raising a signal, or
* fails if it raises SIGABRT. */
/* Array of test cases. */
static struct test
{
const char *name;
void (*function)(void);
} *tests;
static int n_tests;
/* Define a test case. */
#define TEST(NAME) \
static void test_##NAME(void); \
static void __attribute__((constructor)) \
register_##NAME(void) \
{ \
tests = realloc(tests, (n_tests + 1) * sizeof *tests); \
struct test *t = &tests[n_tests++]; \
t->name = #NAME; \
t->function = test_##NAME; \
} \
static void test_##NAME(void)
/* Jump buffer for recovery after SIGABRT. */
static sigjmp_buf jmpbuf;
/* Signal handler for SIGABRT. */
static void
handler(int signum)
{
siglongjmp(jmpbuf, signum);
}
int
main(void)
{
srand(2);
signal(SIGABRT, handler);
bool all_pass = true;
int i;
for (i = 0; i < n_tests; ++i)
{
const struct test *t = &tests[i];
bool pass = false;
if (!sigsetjmp(jmpbuf, true))
{
undoex_annotation_add_int(t->name, "start", i);
t->function();
pass = true;
}
undoex_annotation_add_int(t->name, "result", pass);
printf("%s: %s\n", t->name, pass ? "pass" : "fail");
all_pass &= pass;
}
return all_pass ? EXIT_SUCCESS : EXIT_FAILURE;
}
TEST(passing)
{
/* A passing test. */
}
TEST(failing)
{
/* A failing test. */
abort();
}
TEST(flaky)
{
/* A flaky test. */
assert(rand() % 3);
}
This program uses the name of the test case as the annotation name, and "start" or "result" for the detail. The content is the test number for the former, and the pass/fail result for the latter.
Use undoex_annotation_add_raw_data() to add raw binary data to the
annotation, or undoex_annotation_add_text() to add a null-terminated
string to the annotation. See the undoex/undoex-annotations.h header for
full documentation of these functions.
You can find the libundoex library and header files in the undoex
subdirectory of the UDB release. The library is available as both a statically
linked library (libundoex_pic_<arch>.a) and a dynamically linked library
(libundoex_<arch>.so). There are no other dependencies, so the compilation
command will be similar to this:
$ gcc -ggdb -Iundoex undoex/examples/annotations.c undoex/libundoex_pic_x64.a -o annotations
When annotations is not being recorded, the annotations functions
return -1 and set errno to ENOTSUP. When
annotations is recorded, the functions return 0 and
annotations are added to the recording, whether using the live-record tool, the LiveRecorder library, or UDB.
Querying annotations¶
info annotation name [detail]¶
Show a single annotation with the specified name and detail. Omitting the detail shows a single annotation with the specified name. The content of the annotation is shown in full.
If there are multiple annotations matching the specification, the first one at or after the current time is shown.
info annotation -t|-time time¶
Show the annotation at time.
info annotations [name [detail]]¶
Show annotations with the specified name and detail. Omitting the detail shows all annotations with the specified name. Omitting the name show all annotations. The content of the annotation is truncated if it is too long to display in table form.
When debugging the example program shown above, the output looks like this:
$ udb ./annotations Reading symbols from ./annotations... not running> handle SIGABRT noprint nostop Signal Stop Print Pass to program Description SIGABRT No No Yes Aborted not running> run Starting program: annotations passing: pass failing: fail annotations: undoex/examples/annotations.c:84: test_flaky: Assertion `rand() % 3' failed. flaky: fail Program received signal SIGSTOP, Stopped (signal). The program has exited, but is still being debugged. You can use UDB reverse commands to go backwards; see "help udb" for details. __GI__exit (status=status@entry=1) at ../sysdeps/unix/sysv/linux/_exit.c:30 30 INLINE_SYSCALL (exit_group, 1, status); end 16,007> info annotations Time Name Detail Type Content ------ ------- -------- ------ --------- 12,776 passing start int 0 12,796 passing result int 1 12,968 failing start int 1 13,001 failing result int 0 13,111 flaky start int 2 15,809 flaky result int 0
Jumping to annotations¶
ugo annotation [-e|-exact] name [detail]¶
Jump to the annotation with the specified name and detail. Omitting the detail jumps to the annotation with the specified name.
If there are multiple annotations matching the specification, the first one at or after the current time is used.
- -e|-exact¶
Stop at the time when the annotation was added to the event log. Otherwise, stop at the time when the
undoex_annotation_add()function was called.We can use this to jump to the start of the flaky test:
end 16,007> ugo annotation flaky start Moving out from libundoex into user code. You can disable this behavior by using the "-exact" option. 0x0000555555567803 in main () at undoex/examples/annotations.c:59 59 undoex_annotation_add_int(t->name, "start", i); 81% 13,103> next 60 t->function(); 81% 13,121> step test_flaky () at undoex/examples/annotations.c:84 84 assert(rand() % 3);