3

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()...
}
Christian
  • 149
  • 1
  • 8
  • 1
    I think the keyword you're looking for is *instrumentation*. See, for example, [here](https://www.codeproject.com/Articles/30028/Basic-Instrumentation-and-Profiling-Framework-for) – Ale Feb 06 '20 at 17:23
  • You can write multi-line macros by terminating each line with a backslash '\'. Change your argument name to `_FuncPrefix` and use the `##` operator to insert the assigned prefix into the code. – jwdonahue Feb 06 '20 at 18:23
  • Is the only difference you are looking for here, that the declarations should either be in-lined or not? – jwdonahue Feb 06 '20 at 18:31
  • Your example doesn't really use a hook, it's actually just conditionally compiled code. The question is not very clear. – jwdonahue Feb 06 '20 at 18:37
  • The hooks are Foo_PRE_HOOK and Foo_PORST_HOOK and shall be executed before and after function executin, if hooking is enabled. – Christian Feb 07 '20 at 16:56

3 Answers3

3

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;
}
Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
  • I'll admit that's more than I can digest in a single viewing, though I get the concept of pair expansion though `MAP` and the concatenation that occurs afterwards. Though the immediate readability from a non-kernel *syscall_trace_xxxx_event* developer standpoint -- leaves a bit to be desired. (kinda like using the kernel macro linked list implementations...) – David C. Rankin Feb 07 '20 at 02:26
  • @DavidC.Rankin you might find helpful [this other answer of mine](https://stackoverflow.com/a/60105769/3889449) which is basically a simplified version of what is happening here. – Marco Bonelli Feb 07 '20 at 11:35
  • My intention was less effort, not coding each hook anchor, and clean code. Her I would have to code each hooked function separately, right? And some more code in addition... – Christian Feb 07 '20 at 17:04
  • @Christian I don't really understand what you mean by "code each hooked function separately". No you don't.. that's exactly what the macros I created do. They wrap your original function for you, exactly how you show in your question. – Marco Bonelli Feb 07 '20 at 17:07
  • @Christian to be 100% clear, the code you will have to write is only the one where I say *"You can then define your functions like this:"* – Marco Bonelli Feb 07 '20 at 17:10
  • @Marco I guess I will try. Maybe I'm wrong and the effort will be less than expected. – Christian Feb 10 '20 at 10:12
  • @Christian if you put those macros in a header file or just at the top of the source code then all it's left is a simple macro call, can't get more simple than that haha. – Marco Bonelli Feb 10 '20 at 10:27
3

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.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • What is the advantage to the macro-soup over just defining two different versions of the functions with/without the hooked functions depending on the define? This is certainly a neat way to do it, albeit bewildering at first glance... – David C. Rankin Feb 07 '20 at 01:54
  • The primary advantage is that the core or base function doesn’t have to change at all. Whether that’s sufficient benefit is perhaps debatable, but not having to modify the base function is a benefit. That also means that unrecompiled code continues to work because the unmodified function still exists to be used. The pre-hook and post-hook functions are much less clearly beneficial. Two names, two implementations, for each hooked function. I’d not do that, but it meets the stipulated requirements. The technique for dealing with type/variable argument pairs shown in another answer is intriguing. – Jonathan Leffler Feb 07 '20 at 02:06
  • Thank you. Yes, I'm still digesting Marco's answer with a 2nd helping of bewilderment. – David C. Rankin Feb 07 '20 at 02:12
  • Not so bad. I guess my problem is not solve-able in the way I'm dreaming of. – Christian Feb 07 '20 at 17:05
0

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.

Christian
  • 149
  • 1
  • 8