Christian
Christian

Reputation: 179

Use a macro for function hooking in C

I want to enable hooks for a function by a compiler switch. I want to get following result:

#ifdef COMPILER_SWITCH
static inline int Foo_HOOKED(int x, int y);

int Foo(int x, int y)
{
    int rval;

    Foo_PRE_HOOK();
    rval = Foo_HOOKED(x,y);
    Foo_POST_HOOK();

    return rval;
}

static inline int Foo_HOOKED(int x, int y)
#else
int Foo(int x, int y)
#endif  // COMPILER_SWITCH
{
    // Implementation of Foo
}

The function name, return type, number and types of arguments should be variable. But the names of hooks etc. shall always contain the function name with the same suffix as shown. My dream would be to get anything like this:

#ifdef COMPILER_SWITCH
#define HOOKED_FUNCTION(x) <magic>
#else
#define HOOKED_FUNCTION(x) #x
#endif

HOOKED_FUNCTION(int Foo(int x, int y))
{
    // Implementation of Foo
}

Does anybody have an idea, how to solve this in a generalized way, which doesn't impact readability too much? It's also allowed to include a file in front of the hooked function etc.

EDIT: My subject is like a guard in c++. Unfortunately I'm in c...

#define HOOKED_FUNCTION  hook_t my_hook

class hook_t
{
public:
    hook_t() { pre_hook(); }
    ~hook_t() { post_hook(); }
};


int Foo(int x, int y)
{
#if COMPILER_SWITCH
    HOOKED_FUNCTION;
#endif
    // implementation of Foo()...
}

Upvotes: 3

Views: 995

Answers (3)

Christian
Christian

Reputation: 179

Finally I took Marco's solution with one change: by counting the macro arguments, I can use a single macro for defining all functions:

#define DEFINE(rettype, funcname, ...) DEFINEx(ARG_COUNT(__VA_ARGS__), rettype, funcname, __VA_ARGS__)

For counting macro arguments, refer to Macro to count number of arguments.

Upvotes: 0

Marco Bonelli
Marco Bonelli

Reputation: 69346

The function name, return type, number and types of arguments should be variable.

There is no way of doing this with a single preprocessor macro. The problem lies in the fact that "the number of arguments should be variable". However... there's a solution which I believe achieves exactly what you want.

A classic example of getting around this issue is in the Linux kernel source code, where you can see different SYSCALL_DEFINE<n> macros used to define syscalls with different numbers of arguments, like:

SYSCALL_DEFINE3(lseek, unsigned int, fd, off_t, offset, unsigned int, whence)
{
    return ksys_lseek(fd, offset, whence);
}

You can see how those macros are defined in include/linux/syscalls.h. They are actually pretty convoluted, but at the end of the day the real magic is behind that __MAP(...) macro defined for up to 6 arguments.

Similarly, you could do this:

#define MAP0(m,...)
#define MAP1(m,t,a,...) m(t,a)
#define MAP2(m,t,a,...) m(t,a), MAP1(m,__VA_ARGS__)
#define MAP3(m,t,a,...) m(t,a), MAP2(m,__VA_ARGS__)
// ... add more as needed
#define MAP(n,...) MAP##n(__VA_ARGS__)

#define DEFINE_ARG(argtype, argname) argtype argname
#define CALL_ARG(argtype, argname) argname

#define DEFINE1(...) DEFINEx(1, __VA_ARGS__)
#define DEFINE2(...) DEFINEx(2, __VA_ARGS__)
#define DEFINE3(...) DEFINEx(3, __VA_ARGS__)
// ... add more as needed

#define SIGNATUREx(x, rettype, funcname, ...) rettype funcname(MAP(x, DEFINE_ARG, __VA_ARGS__))

#define HOOKx(x, rettype, funcname, ...)                                      \
    static inline rettype funcname##_HOOKED(MAP(x, DEFINE_ARG, __VA_ARGS__)); \
                                                                              \
    SIGNATUREx(x, rettype, funcname, __VA_ARGS__)                             \
    {                                                                         \
        funcname##_PRE_HOOK();                                                \
        rettype rval = funcname##_HOOKED(MAP(x, CALL_ARG, __VA_ARGS__));      \
        funcname##_POST_HOOK();                                               \
        return rval;                                                          \
    }                                                                         \
                                                                              \
    static inline rettype funcname##_HOOKED(MAP(x, DEFINE_ARG, __VA_ARGS__))

#ifdef COMPILER_SWITCH
#define DEFINEx(...) HOOKx(__VA_ARGS__)
#else
#define DEFINEx(...) SIGNATUREx(__VA_ARGS__)
#endif

Put the above in a separate hook.h header file, and you'll have a pretty clean solution.

You can then define your functions like this:

#include "hook.h"

DEFINE1(int, foo, int, x)
{
    return x + 1;
}

DEFINE2(float, bar, int, x, float, y)
{
    return x + y;
}

The above produces the following if compiled with gcc -DCOMPILER_SWITCH:

static inline int foo_HOOKED(int x);

int foo(int x) {
    foo_PRE_HOOK();
    int rval = foo_HOOKED(x);
    foo_POST_HOOK();
    return rval;
}

static inline int foo_HOOKED(int x)
{
    return x + 1;
}

static inline float bar_HOOKED(int x, float y);

float bar(int x, float y)
{
    bar_PRE_HOOK();
    float rval = bar_HOOKED(x, y);
    bar_POST_HOOK();
    return rval;
}

static inline float bar_HOOKED(int x, float y)
{
    return x + y;
}

And the following if compiled normally:

int foo(int x)
{
    return x + 1;
}

float bar(int x, float y)
{
    return x + y;
}

Upvotes: 3

Jonathan Leffler
Jonathan Leffler

Reputation: 754030

Does this get close enough?

//#define COMPILE_SWITCH

#ifdef COMPILE_SWITCH

#define HOOK_FUNCTION(name, rtype, args_defn, args_list) \
    static rtype (name)args_defn; \
    extern void name ## _PRE_HOOK(void); \
    extern void name ## _POST_HOOK(void); \
    static inline rtype name ## _HOOKED args_defn { \
        name ## _PRE_HOOK(); \
        rtype rval = (name)args_list; \
        name ## _POST_HOOK(); \
        return rval; \
    }

HOOK_FUNCTION(Foo, int, (int x, int y), (x, y))
#define Foo(x, y) Foo_HOOKED(x, y)

HOOK_FUNCTION(Bar, double, (int x, int y, int z), (x, y, z))
#define Bar(x, y, z) Bar_HOOKED(x, y, z)

#endif /* COMPILE_SWITCH */

#include <stdio.h>
#include <math.h>

static int (Foo)(int x, int y)
{
    int z = x * x + y * y;
    return z;
}

static double (Bar)(int x, int y, int z)
{
    return sqrt(x * x + y * y + z * z);
}

int main(void)
{
    int x = 3;
    int y = 5;
    int z = Foo(x, y);
    printf("x = %d, y = %d, z = %d\n", x, y, z);
    printf("x = %d, y = %d, z = %d, r = %.3f\n", x, y, z, Bar(x, y, z));
    return 0;
}

#ifdef COMPILE_SWITCH

void Foo_PRE_HOOK(void)
{
    printf("-->> Foo() (%s)\n", __func__);
}

void Foo_POST_HOOK(void)
{
    printf("<<-- Foo() (%s)\n", __func__);
}

void Bar_PRE_HOOK(void)
{
    printf("-->> Bar() (%s)\n", __func__);
}

void Bar_POST_HOOK(void)
{
    printf("<<-- Bar() (%s)\n", __func__);
}

#endif /* COMPILE_SWITCH */

This compiles and runs:

$ rmk -u hook67 UFLAGS=-UCOMPILE_SWITCH && hook67
    gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes        -UCOMPILE_SWITCH hook67.c -o hook67  
x = 3, y = 5, z = 34
x = 3, y = 5, z = 34, r = 34.496
$ rmk -u hook67 UFLAGS=-DCOMPILE_SWITCH && hook67
    gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes        -DCOMPILE_SWITCH hook67.c -o hook67  
-->> Foo() (Foo_PRE_HOOK)
<<-- Foo() (Foo_POST_HOOK)
x = 3, y = 5, z = 34
-->> Bar() (Bar_PRE_HOOK)
<<-- Bar() (Bar_POST_HOOK)
x = 3, y = 5, z = 34, r = 34.496
$

The rmk program is a variant of make; the -u flag means 'compile unconditionally'. The makefile has a CFLAGS macro which includes ${UFLAGS} (for user-flags) — UFLAGS is only set on the command line.

You could put the macro definition for HOOK_FUNCTION into a header. You'd still write the HOOK_FUNCTION invocation, and the per-hooked-function macro definition in the code. You have to define the pre-hook and post-hook functions for each hooked function.

There isn't an easy way to avoid the args_defn and args_list parts to the macro AFAIK.

Upvotes: 3

Related Questions