使用 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 条评论
登录 后参与评论

相关文章

来自专栏androidBlog

一步步拆解 LeakCanary

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

961
来自专栏技术记录

JAVA-FTP批量大文件传输

FTP的具体使用      FTP是一种网络协议,用于进行不同服务器主机之间的文件传输,或者简单地说两台不同IP的机器之间的文件传输。在java中我们什么时候需...

7926
来自专栏郭霖

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

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

6106
来自专栏小灰灰

EventBus 源码学习笔记(三)

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

2366
来自专栏Python研发

Django之Model世界

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

1292
来自专栏清晨我上码

spring boot 使用ReloadableResourceBundleMessageSource的坑

所以我们重点关注的AbstractMessageSource的getMessage方法。以其中一个为例分析

4802
来自专栏坚毅的PHP

my php & mysql FAQ

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

3946
来自专栏LanceToBigData

Mybatis(一)走进Mybatis与FisrtExample

一直在使用,从未系统的总结起来。所以这里给大家带来的是mybatis的总结,系统大家能够对这个框架有一定的系统的学习与认识。 mybatis和Hibernat...

1002
来自专栏JackieZheng

照虎画猫写自己的Spring——依赖注入

前言 上篇《照虎画猫写自己的Spring》从无到有讲述并实现了下面几点 声明配置文件,用于声明需要加载使用的类 加载配置文件,读取配置文件 解析配置文件,需要将...

2038
来自专栏向治洪

picasso图片缓存框架

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

3148

扫码关注云+社区

领取腾讯云代金券