recorder_configure_type(3)

πŸ“¦ version 1.2.3, in fedora 43, fedora 44

πŸ’– recorder_configure_type(3) πŸ’–

okay so this one is a section 3 page from the Recorder Library, and it's genuinely one of the coolest little superpowers in the whole recorder situation. buckle up bestie, we're teaching printf new tricks ✨


✨ NAME ✨

record_configure_type β€” Configure format characters for event records πŸŽ€


🌸 SYNOPSIS 🌸

#include <recorder/recorder.h>

typedef size_t (*recorder_type_fn)(intptr_t trace,
                                   const char *format,
                                    char *buffer,
                                    size_tlength,
                                    uintptr_tdata);
recorder_type_fn   recorder_configure_type(uint8_tid, recorder_type_fntype);

πŸ’… DESCRIPTION πŸ’…

So record_configure_type configures a type formatting character identified by id that you can then use in record(3) format strings. it's giving "teach the recorder a new letter" energy πŸ‘‘

the normal use case is adding special extensions to the printf format for data types your program uses all the time. in the example below we do record_configure_type('t', todo_format) so that the %t format sequence will format struct todo * pointers. iconic behaviour honestly.

the function you identify by type gets called to do the actual data formatting. it receives these arguments:

  • trace β€” the tracing value associated to the recorder. you can use this to implement safe pointer rendering similar to %+s, i.e. only dereference the given pointer if you know that it is safe to do so. and heads up: if the format string is %+id then the value of trace will be non-zero.
  • format β€” the actual format string. this lets your type function handle format modifiers or precision indicators similar to the way printf does.
  • buffer β€” the buffer where your function should write its output.
  • length β€” the amount of space available in the buffer.
  • data β€” the actual value stored in the recorder event, for example a pointer to your data.

πŸ¦‹ RETURN VALUE πŸ¦‹

the return value of recorder_configure_type() is the previous function handling the given format identifier. so you can chain and restore, cute 🫢

and your type function should return the size it would have written, in the same way snprintf(3) does, i.e. the number of characters that it would have written, irrespective of the size of the buffer. yes bestie, "irrespective of the size of the buffer" β€” that's the whole point, don't count what you actually fit, count what you would have fit πŸ’€


πŸ“ EXAMPLES πŸ“

the following program uses recorder_configure_type() to associate the function todo_format() to the %t format sequence. todo_format() interprets its argument as a struct todo * pointer. the main program builds a to-do list from command-line arguments, but it contains a subtle bug that causes it to crash if any of the arguments happens to be "crash". πŸ’€ (yes that's on purpose, keep reading, it's about to be so good)

#include <recorder/recorder.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

RECORDER(todo, 32, "To-Do List");

typedef struct todo
{
    const char *name;
    struct todo *next;
} todo;

size_t todo_format(intptr_t tracing,
                   const char *format,
                   char *buffer, size_t size,
                   uintptr_t arg)
{
    struct todo *t = (struct todo *) arg;
    if (!tracing || !t)
        return snprintf(buffer, size, "%p", arg);
    return snprintf(buffer, size, "todo@%p('%s')", t, t->name);
}

int main(int argc, char **argv)
{
    int a;
    todo *list = NULL;
    int crash = 0;
    recorder_dump_on_common_signals(0, 0);
    recorder_configure_type('t', todo_format);
    for (a = 1; a < argc; a++)
    {
        record(todo, "Argument %d is '%+s'", a, argv[a]);
        todo *next = malloc(sizeof(todo));
        next->name = argv[a];
        next->next = list;
        record(todo, "Created entry %t previous was %t", next, list);
        list = next;
        crash += strcmp(argv[a], "crash") == 0;
    }

    while (list || crash)
    {
        todo *next = list->next;
        record(todo, "Had item %t", list);
        free(list);
        list = next;
    }
}

🌈 run #1: the chill run

the following shows what happens if you run the program normally:

% todo eat pray love

🌈 run #2: traced, no crash

the following shows what happens if you trace the program: the struct todo * values are formatted, showing additional information. look at those gorgeous todo@0x…('name') strings, we ate πŸ”₯

% RECORDER_TRACES=todo todo eat pray love
[34 0.000273] todo: Argument 1 is 'eat'
[35 0.000339] todo: Created entry todo@0x50ba80('eat') previous was (nil)
[36 0.000352] todo: Argument 2 is 'pray'
[37 0.000360] todo: Created entry todo@0x50ba60('pray') previous was todo@0x50ba80('eat')
[38 0.000368] todo: Argument 3 is 'love'
[39 0.000373] todo: Created entry todo@0x50ba40('love') previous was todo@0x50ba60('pray')
[40 0.000386] todo: Had item todo@0x50ba40('love')
[41 0.000394] todo: Had item todo@0x50ba60('pray')
[42 0.000477] todo: Had item todo@0x50ba80('eat')

🌈 run #3: it crashes 😭

the following shows what happens if the program crashes due to bad input: the crash dump no longer indicates the content of the todo pointers, since it would be unsafe to dereference them at that time. see how the nice todo@…('…') rendering just becomes a plain pointer? that's the trace argument doing its job, bestie 🫢

% todo crash and burn
[...]
[25 0.000052] recorders: Configure type 't' to 0x4011c6 from (nil)
[26 0.000067] todo: Argument 1 is 'crash'
[27 0.000146] todo: Created entry 0x1f9d260 previous was (nil)
[28 0.000150] todo: Argument 2 is 'and'
[29 0.000150] todo: Created entry 0x1f9d280 previous was 0x1f9d260
[30 0.000150] todo: Argument 3 is 'burn'
[31 0.000150] todo: Created entry 0x1f9d2a0 previous was 0x1f9d280
[32 0.000151] todo: Had item 0x1f9d2a0
[33 0.000152] todo: Had item 0x1f9d280
[34 0.000152] todo: Had item 0x1f9d260
[35 0.000172] signals: Received signal Segmentation fault (11) si_addr=0x8, dumping recorder
[36 0.000209] recorders: Recorder dump

🌈 run #4: traced AND crashing, the main character run πŸ‘‘

finally, the following shows what happens if you activate tracing and the program crashes. in that case, the tracing part at the top shows the detailed information. however, during the crash dump, the same events are replayed again (putting them in context with other events), this time without dereferencing the pointer. so you get both versions of the story and honestly that's a serve πŸ’…

% RECORDER_TRACES=todo todo crash and burn
[...]
[34 0.000184] todo: Argument 1 is 'crash'
[35 0.000240] todo: Created entry todo@0xf6aa80('crash') previous was (nil)
[36 0.000250] todo: Argument 2 is 'and'
[37 0.000255] todo: Created entry todo@0xf6aa60('and') previous was todo@0xf6aa80('crash')
[38 0.000260] todo: Argument 3 is 'burn'
[39 0.000265] todo: Created entry todo@0xf6aa40('burn') previous was todo@0xf6aa60('and')
[40 0.000270] todo: Had item todo@0xf6aa40('burn')
[41 0.000275] todo: Had item todo@0xf6aa60('and')
[42 0.000279] todo: Had item todo@0xf6aa80('crash')
[...]
[33 0.000180] recorders: Configure type 't' to 0x4011c6 from (nil)
[34 0.000184] todo: Argument 1 is 'crash'
[35 0.000240] todo: Created entry 0xf6aa80 previous was (nil)
[36 0.000250] todo: Argument 2 is 'and'
[37 0.000255] todo: Created entry 0xf6aa60 previous was 0xf6aa80
[38 0.000260] todo: Argument 3 is 'burn'
[39 0.000265] todo: Created entry 0xf6aa40 previous was 0xf6aa60
[40 0.000270] todo: Had item 0xf6aa40
[41 0.000275] todo: Had item 0xf6aa60
[42 0.000279] todo: Had item 0xf6aa80
[43 0.000297] signals: Received signal Segmentation fault (11) si_addr=0x8, dumping recorder
[44 0.000309] recorders: Recorder dump

πŸ™ˆ BUGS πŸ™ˆ

Using this function will render your format strings wildly incompatible with the standard printf(3) format, possibly making your code less readable. 🫠
There is a very limited number of possible type identifiers. Using this feature in a shared library may cause conflicts with other code that would also want to override the same format character. sharing is caring until it isn't 😭
It is possible to override standard format characters using this function. Whether this is a bug or a feature remains to be seen. πŸ’€

bugs should be reported using https://github.com/c3d/recorder/issues.


🧚 SEE ALSO 🧚

RECORDER_DEFINE(3), RECORDER_DECLARE(3) recorder_trace_set(3) RECORDER_TRACE(3) recorder_dump(3), recorder_dump_for(3), recorder_configure_output(3), recorder_configure_show(3) recorder_configure_format(3), recorder_configure_type(3)

additional documentation and tutorials can be found at https://github.com/c3d/recorder.


πŸ‘‘ AUTHOR πŸ‘‘

Written by Christophe de Dinechin 🌟


and that's the whole page bestie!! version 1.0, dated 2019-03-09, out of the Recorder Library β€” go configure yourself a format character, you've got this πŸ’–βœ¨πŸŽ€πŸ¦‹

πŸ“¦ where this page comes from