在C中使用宏进行函数挂钩

在C中使用宏进行函数挂钩,c,c-preprocessor,C,C Preprocessor,我想通过编译器开关为函数启用钩子。我想得到以下结果: #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_HOOK

我想通过编译器开关为函数启用钩子。我想得到以下结果:

#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
}
函数名、返回类型、参数数量和类型应该是可变的。但挂钩等的名称应始终包含与所示后缀相同的功能名称。我的梦想是得到这样的东西:

#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
}
#include "hook.h"

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

DEFINE2(float, bar, int, x, float, y)
{
    return x + y;
}
函数名、返回类型、参数数量和类型应该是可变的

使用单个预处理器宏无法实现这一点。问题在于“参数的数量应该是可变的”然而。。。我相信有一个解决方案可以实现你想要的

Linux内核源代码中有一个解决此问题的经典示例,您可以看到不同的
SYSCALL\u DEFINE
宏用于定义具有不同数量参数的系统调用,如:

SYSCALL_DEFINE3(lseek, unsigned int, fd, off_t, offset, unsigned int, whence)
{
    return ksys_lseek(fd, offset, whence);
}
您可以看到这些宏是如何在中定义的。它们实际上相当复杂,但归根结底,真正的魔力在于为多达6个参数定义的宏

同样,您可以这样做:

#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
将上述内容放在一个单独的
hook.h
头文件中,您将得到一个非常干净的解决方案

然后,您可以这样定义您的函数:

#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
}
#include "hook.h"

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

DEFINE2(float, bar, int, x, float, y)
{
    return x + y;
}
如果使用
gcc-DCOMPILER\u开关编译,则上述操作将产生以下结果:

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;
}
如果编译正常,则会出现以下情况:

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

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

这距离够近吗

//#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 */
/#定义编译开关
#ifdef编译_开关
#定义钩子函数(名称、rtype、参数定义、参数列表)\
静态rtype(名称)参数\u defn\
外部无效名称###u PRE_HOOK(无效)\
外部无效名称###########后#(无效);\
静态内联rtype名称35;##HOOKED参数defn{\
名称###u PRE_HOOK()\
rtype rval=(名称)参数列表\
名称###u POST_HOOK()\
返回rval\
}
HOOK_函数(Foo,int,(intx,inty),(x,y))
#定义Foo(x,y)Foo_HOOKED(x,y)
HOOK_函数(Bar,double,(intx,inty,intz),(x,y,z))
#定义杆(x,y,z)钩状杆(x,y,z)
#endif/*编译_开关*/
#包括
#包括
静态整数(Foo)(整数x,整数y)
{
intz=x*x+y*y;
返回z;
}
静态双精度(条形)(整数x,整数y,整数z)
{
返回sqrt(x*x+y*y+z*z);
}
内部主(空)
{
int x=3;
int y=5;
intz=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));
返回0;
}
#ifdef编译_开关
void Foo_PRE_HOOK(void)
{
printf(“-->>Foo()(%s)\n)”,函数;
}
无效Foo_POST_HOOK(无效)
{
printf(“Bar()(%s)\n)”,函数;
}
空心杆、柱、钩(空心)
{

printf(“最后,我对Marco的解决方案做了一个更改:通过计算宏参数,我可以使用单个宏定义所有函数:

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

有关计算宏参数的信息,请参阅。

我认为您要查找的关键字是instrumentation。例如,请参阅,您可以通过在每行末尾加反斜杠“\”来编写多行宏。将参数名称更改为
\u FuncPrefix
,并使用
\code>运算符将指定的前缀插入到代码中。是否启用您在这里寻找的唯一区别是,声明应该是线性的还是非线性的?您的示例并没有真正使用钩子,它实际上只是有条件地编译的代码。问题不是很清楚。钩子是Foo_PRE_钩子和Foo_PORST_钩子,如果启用了钩子,则应该在函数executin之前和之后执行。什么是钩子相对于只定义两个不同版本的函数(有/没有挂钩函数取决于定义),宏汤的优势是什么?这当然是一个简洁的方法,尽管乍一看令人困惑…主要优势是核心函数或基础函数根本不需要更改。这是否足够t可能是有争议的,但不必修改基函数是一个好处。这也意味着未编译的代码可以继续工作,因为未修改的函数仍然存在,可以使用。前钩子函数和后钩子函数的好处要小得多。每个钩子函数有两个名称,两个实现。我不会这么做,但它确实有用符合规定的要求。另一个答案中显示的处理类型/变量参数对的技巧很有趣。谢谢。是的,我仍然在用第二份困惑来消化马可的答案。还不错。我猜我的问题无法以我梦想的方式解决。我承认这比我一次消化的能力还多查看,尽管我通过
MAP
获得了成对扩展的概念,以及随后发生的连接。尽管从非内核系统调用\u trace\u xxxx\u事件开发人员的角度来看,即时可读性有点不尽如人意(有点像使用内核宏链表实现…)@DavidC.Rankin你可能会发现这很有帮助,基本上是这里发生的事情的简化版本。我的目的是减少工作量,不编写每个钩子锚和干净的代码。她说,我必须分别编写每个钩子函数,对吗?还有更多的代码…@Christian我真的不明白你说的是什么意思”分别对每个钩住的函数进行编码”。不,你不需要。这正是我创建的宏所做的。它们为你包装了原始函数,这正是你在问题中的表现方式。@Christian 100%清楚地说,你需要编写的代码只是我说“你可以这样定义你的函数:”