那些年踩过的坑---宏函数的使用

今天在测试协议的时候发现网关设备老是重启,查看下原因发现是内核对NULL进行解引用了。
出现问题的代码如下:

static int qq_match_prepare(struct app_param *param, struct app_protocol *protocol)
{
    struct http_header *hdr = &__get_cpu_var(http_header);
    const char *buf = param->buf;
    short index;
    struct app_conntrack *app=param->app;
    const char *tmp;
    char *pos = NULL;
    char qq[16] = {0};
    unsigned int qq_num = 0;

    if (hdr->req.len > 104 &&
        app_memeql(buf, "GET /gchatpic") &&
        http_hdr_match_head(buf, hdr, HTTP_REFERER, "http://im.qq.com/mobileqq") &&
        http_url_search2(buf, hdr, "uin=", pos)){

        int i;

        pos += 5;
        for (i = 0; i < 16 && *pos != '&' && *pos != ' '; i++, pos++)
            qq[i] = *pos;

        qq_num  = simple_strtoul(qq, NULL, 10);
        memcpy(param->app->protocol_private, &qq_num, 4);

        return 1;
    }
    return 0;
}

其中http_url_search2为宏函数,定义如下

#define http_url_search2(buf, hdr, str, p) \
    ({ int ret = 0;\
     char c;\
     char *pos;\
     pos = (char *)(buf + (hdr)->req.begin + (hdr)->req.len);\
     c = *pos;\
     *pos = 0;\
       ret = ((hdr)->req.len >= STRSIZE(str) && \
        (p = strstr(buf, str)));\
     *pos = c;\
     ret;\
     })

出现怪异的现象有两个:

  1. 原字符串缓冲区buf内容会有一个字符被修改;
  2. 解引用NULL指针

看了很久也没看出头绪,最后把注意力集中了在http_url_search2宏函数中,因为这个宏函数在此处的调用和其它地方有点不同。仔细分析之后发现原来是宏替换引发的问题,宏函数外面和里面使用了同样的变量名pos,导致替换后的结果与预期相背。于是预编译下该文件验证自己的想法,代码如下:

static int qq_match_prepare(struct app_param *param, struct app_protocol *protocol)
{
    struct http_header *hdr = &__get_cpu_var(http_header);
    const char *buf = param->buf;
    short index;
    struct app_conntrack *app=param->app;
    const char *tmp;
    char *pos = ((void *)0);
    char qq[16] = {0};
    unsigned int qq_num = 0;


    if (hdr->req.len > 104 &&
        app_memeql(buf, "GET /gchatpic") &&
        ({ short index; int ret; if (HTTP_REFERER >= MAX_HTTP_TYPE || ((index = (hdr)->http_type[HTTP_REFERER]) == -1)) ret = 0; else { ret = ((hdr)->fields[index].value_len >= (sizeof(char [1 - 2 * is_ptr("http://im.qq.com/mobileqq")]) * 0 + sizeof("http://im.qq.com/mobileqq") - 1) && app_memeql((buf) + ((hdr)->fields[index].value_begin), "http://im.qq.com/mobileqq")); } ret; }) &&
        ({ int ret = 0; char c; char *pos; pos = (char *)(buf + (hdr)->req.begin + (hdr)->req.len); c = *pos; *pos = 0; ret = ((hdr)->req.len >= (sizeof(char [1 - 2 * is_ptr("uin=")]) * 0 + sizeof("uin=") - 1) && (pos = strstr(buf, "uin="))); *pos = c; ret; })){

        int i;

        pos += 5;
        for (i = 0; i < 16 && *pos != '&' && *pos != ' '; i++, pos++)
            qq[i] = *pos;

        qq_num = simple_strtoul(qq, ((void *)0), 10);
        memcpy(param->app->protocol_private, &qq_num, 4);


        return 1;
    }

    return 0;
}

由预处理后的代码可以看出,传入宏函数的参数和宏函数体使用了相同的变量名,由于宏函数仅是简单的文本替换,所以导致本来意义不同的变量被编译器误认为是相同的变量,pos = strstr(buf, "uin="); 之后*pos = c,所以当strstr调用成功的时候原字符串缓冲区buf内容会有一个字符被修改,当调用失败的时候会对NULL指针解引用。

如果http_url_search2是一个函数的话就不会出现这种问题!

总结:在使用宏函数的时候一定要千万谨慎小点,最重要的一点是记住宏只是简单的文本替换

你可能感兴趣的:(那些年踩过的坑---宏函数的使用)