Skip to content
 
 

Repository files navigation

CIF

CIF (C Instrumentation Framework) is a tool that implements aspect-oriented programming for the C programming language.

Why aspects?

Some things a program has to do cannot live in one function or one file: check API contracts, trace calls, count resources, inject faults, collect facts about the code for other tools. Written by hand, such code ends up scattered over every place where it applies and stays there forever. An aspect describes such a concern once, in terms of join points of the program (calls, definitions, macro expansions, variable accesses), and CIF weaves it in at build time. The program itself stays as it is, and the aspect can be dropped, replaced or stacked with others.

Take a stack that trusts its callers. Pushing to a full stack silently corrupts memory; nothing tells you that until something else breaks:

struct stack {
    int *items;
    int size;
    int top;
};

void stack_push(struct stack *s, int value)
{
    s->items[s->top++] = value;
}

int stack_pop(struct stack *s)
{
    return s->items[--s->top];
}

int main(void)
{
    struct stack *s = stack_new(2);

    stack_push(s, 1);
    stack_push(s, 2);
    stack_push(s, 3);

    printf("%d\n", stack_pop(s));

    return 0;
}
$ gcc stack.c -o stack && ./stack
3

Here is an aspect that enforces the preconditions of the stack API at every call site. An advice consists of a pointcut (call(...), which matches calls of a function with the given signature) and a body in plain C that can use the arguments of the matched call by name:

before: call(void stack_push(struct stack *s, int value))
{
    if (s->top == s->size) {
        fprintf(stderr, "stack_push(): pushing %d to a full stack of size %d\n", value, s->size);
        abort();
    }
}

before: call(int stack_pop(struct stack *s))
{
    if (s->top == 0) {
        fprintf(stderr, "stack_pop(): popping from an empty stack\n");
        abort();
    }
}

CIF takes the program and the aspect and produces a binary, or, with --back-end src, a woven C source file:

$ cif --in stack.c --aspect stack.aspect --out stack --back-end bin && ./stack
stack_push(): pushing 3 to a full stack of size 2
Aborted (core dumped)

Under the hood every matched call is redirected to an auxiliary function that runs the advice body and then the original call, so the woven source is ordinary C that any compiler accepts (line directives and explicit casts that CIF adds are omitted here):

    cif_stack_push ( s , 1 );
    cif_stack_push ( s , 2 );
    cif_stack_push ( s , 3 );
...
static void cif_stack_push(struct stack *s, int value)
{
  if (( * s ) . top == ( * s ) . size)
  {
    fprintf ( stderr , "stack_push(): pushing %d to a full stack of size %d\n" , value , ( * s ) . size );
    abort ( );
  }
  stack_push ( s , value );
}

The same mechanism works for around advices that replace a call and decide whether to $proceed with it, for after advices that see the result, and for join points other than calls: function definitions, macro expansions, assignments to variables, definitions of structures. Aspects can also query the program instead of changing it, for example to list every call of every function together with the caller and the line, which is how Klever and Clade use CIF to extract facts from the Linux kernel.

The example above is available in docs/samples (stack.c and stack.aspect), and the tutorial walks through the other kinds of join points.

Installation

Archives with prebuilt binaries for Linux and macOS are attached to GitHub Releases. Download the one for your system, unpack it and put its bin directory to PATH:

$ mkdir ~/cif
$ tar -xf cif.tar.xz -C ~/cif
$ export PATH=~/cif/bin:$PATH
$ cif --version

The archive contains cif and aspectator, a GCC-based compiler that CIF runs to weave aspects in. Aspectator uses the system assembler and linker, so a regular C toolchain should be installed as well. Archives named after a target like linux-arm-unknown-eabi-cif contain cross versions of CIF that process code for that target; their executables are prefixed with the target name, e.g. arm-unknown-eabi-cif.

To build CIF yourself, see Building from source.

Documentation

Using CIF:

Developing CIF:

Acknowledgments

CIF and Aspectator are created by Evgeny Novikov.

About

Aspect-oriented programming for C, implemented on top of the GCC compiler

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages