使用 Valgrind 检测 CGI 内存泄漏的简易方法

项目中使用了基于CGIEx构建的CGI,并且通过CGI调用Protobuf API来完成一些动态解析proto定义之类的功能,上线前使用Valgrind的memcheck工具检测CGI是否存在内存泄漏的风险。

Valgrind的使用十分简单,通过设置一定的参数启动二进制可执行程序,并且在执行结束之后收集结果输出即可。但是我们的CGI是通过Apache运行的,不能直接使用Valgrind启动,Google一圈之后没有找到相关的实践,只好自己动手。

首先,直接执行CGI二进制可执行程序,可以看到进入了交互模式(Intractive Mode),并且提示等待用户输入(如下图)。不过如何输入参数?如何确定GET和POST的调用方法?以及如何区分两种方法的参数,却仍是未知数。

继续深入研究,全局搜索”cgihtml Interactive Mode”后找到了线索,在cgi-lib.c文件中找到了get_DEBUG函数定义如下:

char *get_DEBUG()
{
    int bufsize = 1024;
    char *buffer;
    int i = 0;
    char ch;

    if ((buffer = (char *)malloc(sizeof(char) * bufsize + 1)) == NULL)
        exit(1);

    fprintf(stderr,"\n--- cgihtml Interactive Mode ---\n");
    fprintf(stderr,"Enter CGI input string.  Remember to encode appropriate ");
    fprintf(stderr,"characters.\nPress ENTER when done:\n\n");
    while ( (i<=bufsize) && ((ch = getc(stdin)) != '\n') ) {
        buffer[i] = ch;
        i++;
        if (i>bufsize) {
            bufsize *= 2;
            if ((buffer = (char *)realloc(buffer,bufsize)) == NULL)
                exit(1);
        }
    }
    buffer[i] = '\0';
    fprintf(stderr,"\n Input string: %s\nString length: %d\n",buffer,i);
    fprintf(stderr,"--- end cgihtml Interactive Mode ---\n\n");
    return buffer;
}

继续搜索get_DEBUG的调用之处,发现只有一处调用,如下:

char* read_cgi_input(llist* entries)
{
    char *input;
    int status;

    /* check for form upload.  this needs to be first, because the
    standard way of checking for POST data is inadequate.  If you
    are uploading a 100 MB file, you are unlikely to have a buffer
    in memory large enough to store the raw data for parsing.
    Instead, parse_form_encoded parses stdin directly.

    In the future, I may modify parse_CGI_encoded so that it also
    parses POST directly from stdin.  I'm undecided on this issue,
    because doing so will make parse_CGI_encoded less general. */
    if ((CONTENT_TYPE != NULL) &&
        (strstr(CONTENT_TYPE,"multipart/form-data") != NULL ))
    {
        parse_form_encoded(entries);
        return NULL;
    }

    /* get the input */
    if (REQUEST_METHOD == NULL)
        input = get_DEBUG();
    else if (!strcmp(REQUEST_METHOD,"POST"))
    {
        input = get_POST();

        if ((input != NULL) && (strstr(input, "application/x-www-form-urlencoded") == NULL ))
        {
            return input;
        }
    }
    else if (!strcmp(REQUEST_METHOD,"GET"))
        input = get_GET();
    else { /* error: invalid request method */
        fprintf(stderr,"caught by cgihtml: REQUEST_METHOD invalid\n");
        exit(1);
    }
    /* parse the input */
    if (input == NULL)
    {
        return NULL;
    }

    status = parse_CGI_encoded(entries,input);
    FREE(input);
    return NULL;
}
  • 查看read_cgi_input函数,可以看到是通过宏REQUEST_METHOD来获取调用模式,宏REQUEST_METHOD的作用是获取环境变量“REQUEST_METHOD”。同时,还看到除了get_DEBUG函数之外,还定义了get_POST以及get_GET函数来分别处理POST和GET请求。
  • 其中get_DEBUG和get_GET函数的输出结果input都直接作为参数传递给parse_CGI_encoded函数进一步处理,所以DEBUG模式应该跟GET模式的输入参数是一致的。
  • 来看POST接口的调用方式,read_cgi_input函数首先通过CONTENT_TYPE环境变量确定输入参数的格式(text/json等),然后通过REQUEST_METHOD环境变量的取值(POST)进入调用get_POST函数的分支,并且在get_POST函数中通过CONTENT_LENGTH环境变量获取输出参数字符串的长度。

分析到这里,现在大致可以确定如何直接启动CGI二进制文件并输入参数了。

GET

CGI Interactive模式下,输入的参数就是通过GET方式调用时,URL后部所带的参数,形如: param1=val1&param2=val2,所以对于GET接口的测试的步骤很简单

  1. 使用valgrind启动CGI二进制文件进入Intractive模式valgrind --tool=memcheck --log-file=./valgrind_report.log --leak-check=full --show-reachable=yes --track-origins=yes ./cgi_get_sample
  2. 输入参数列表,并以回车结束。如果参数中有需要urlencode的字符请自行转换。starttime=2017-07-31%2014%3A59%3A31&endtime=2017-07-31%2015%3A59%3A31&id=1024
  3. 查看valgrind_report.log中的结果输出

POST

POST接口的调用方式稍微复杂一些。

  1. 设置环境变量export REQUEST_METHOD=POST export CONTENT_LENGTH=381 export CONTENT_TYPE="application/json"
  2. valgrind启动cgi,进入get_POST分支,等待用户输入,只是没有提示信息valgrind --tool=memcheck --log-file=./valgrind_report.log --leak-check=full --show-reachable=yes --track-origins=yes ./cgi_post_sample
  3. 输入json格式字符串参数{"id":1024,"name":"calvin"}
  4. 查看valgrind_report.log中的结果输出
  5. 测试完成后,将环境变量恢复 unset REQUEST_METHOD unset CONTENT_LENGTH unset CONTENT_TYPE

最后,贴一个集成POST/GET两种方式的脚本:

#!/bin/bash
# create by calvinshao

if [ $# -lt 3 ]; then
    echo "USAGE: $0 CGI_NAME DATA_LEN METHOD"
    exit 1
fi

if [ "$3" == "POST" ]; then
    #Set Env param
    export REQUEST_METHOD=POST
    export CONTENT_LENGTH=$2
    export CONTENT_TYPE="application/json"

    echo "Input JSON format string:"

    valgrind --tool=memcheck --log-file=./valgrind_report.log --leak-check=full --show-reachable=yes --track-origins=yes ./$1
else
    unset REQUEST_METHOD
    unset CONTENT_LENGTH
    unset CONTENT_TYPE

    valgrind --tool=memcheck --log-file=./valgrind_report.log --leak-check=full --show-reachable=yes --track-origins=yes ./$1
fi

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏天天P图攻城狮

深入Android Runtime: 指令优化与Java方法调用

在进行apk热修复、插件化、动态加载的时候,会经常多个jar/dex包含相同的class,如果class结构因为需要升级出现了变化,会隐藏一些很难解释的坑在里面...

3776
来自专栏Python研发

Django之Model世界

django为使用一种新的方式,即:关系对象映射(Object Relational Mapping,简称ORM)

982
来自专栏Jerry的SAP技术分享

使用JPA + Eclipselink操作PostgreSQL数据库

首先确保您已经安装了PostgreSQL。您可以参考我这篇文章PostgreSQL扫盲教程。

3004
来自专栏坚毅的PHP

my php & mysql FAQ

php中文字符串长度及定长截取问题使用str_len("中国") 结果为6,php系统默认一个中文字符长度为3,可改用mb_strlen函数获得长度,mb_su...

3526
来自专栏郭霖

Android图片加载框架最全解析(四),玩转Glide的回调与监听

大家好,今天我们继续学习Glide。 在上一篇文章当中,我带着大家一起深入探究了Glide的缓存机制,我们不光掌握了Glide缓存的使用方法,还通过源码分析对缓...

4616
来自专栏androidBlog

一步步拆解 LeakCanary

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gdutxiaoxu/article/de...

691
来自专栏向治洪

Android数据库Realm实践

Android开发中常用的数据库有5个: 1. OrmLite OrmLite 不是 Android 平台专用的ORM框架,它是Java ORM。支持JDBC连...

2239
来自专栏向治洪

picasso图片缓存框架

picasso是Square公司开源的一个Android图形缓存库,地址http://square.github.io/picasso/,可以实现图片下载和缓...

2558
来自专栏chenssy

这些Spring中的设计模式,你都知道吗?

设计模式作为工作学习中的枕边书,却时常处于勤说不用的尴尬境地,也不是我们时常忘记,只是一直没有记忆。

661
来自专栏小灰灰

EventBus 源码学习笔记(三)

EventBus 深入学习三之Guava小结 上一篇讲述了 EventBus 的整个执行流程, 本片则从细节处出发,探讨下设计的精妙 巧妙的利用缓存, 解决重...

2086

扫码关注云+社区