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

相关文章

来自专栏安富莱嵌入式技术分享

【RL-TCPnet网络教程】第18章 BSD Sockets基础知识

本章节为大家讲解BSD Sockets,需要大家对BSD Sockets有个基础的认识,方便后面章节Socket实战操作。

923
来自专栏张善友的专栏

在ASP.NET MVC 4中使用Kendo UI Grid

Kendo UI 是Telerik推出的一套based on jQuery 的 Framework,提供了很多控件(Menu 、Grid 、Combox等......

2007
来自专栏xingoo, 一个梦想做发明家的程序员

web中的cookie管理

  本篇是以JSP为背景介绍,但是在web开发中也是相同的原理。   什么是cookie   由于http是一种无状态的协议,因此服务器收到请求后,只会当做一次...

18610
来自专栏Golang语言社区

Golang:使用 httprouter 构建 API 服务器

我 10 个月前开始成为一名 Gopher,没有回头。像许多其他 gopher 一样,我很快发现简单的语言特性对于快速构建快速、可扩展的软件非常有用。当我刚开始...

59314
来自专栏Netkiller

SOA 面向服务框架设计与实现

文章节选自 《Netkiller Architect 手札》 由于Java 语言的编译与重启不可抗拒缺陷,所选择使用PHP弥补这个缺陷。 在合适的场景中使用PH...

2715
来自专栏游戏开发那些事

【LINUX/UNIX网络编程】之使用SOCKET进行UDP编程

(2)客户可向服务器发送多种指令:DOWNLOAD、UPLOAD、YES、NO、START、END、SHUTDOWN、CONTENT、OKDOWLOAD格式:D...

992
来自专栏Flutter&Dart

DartVM服务器开发(第十七天)--Jaguar_websocket结合Flutter搭建简单聊天室

我们这里定义了一个ChatMessageData,如果你想需要更多字段,可以再添加

1641
来自专栏老码农专栏

ActFramework r1.2.0 带来的新特性

893
来自专栏青蛙要fly的专栏

项目需求讨论-APP手势解锁及指纹解锁

好久没写文章了,最近也比较偷懒,今天继续讨论我实际开发中遇到的需求,那就是关于APP解锁,大家都知道。现在越来越多的APP在填入账号密码后,第二次登录后,基本不...

852
来自专栏逸鹏说道

C# 温故而知新:Stream篇(七)

NetworkStream 目录: NetworkStream的作用 简单介绍下TCP/IP 协议和相关层次 简单说明下 TCP和UDP的区别 简单介绍下套接字...

2815

扫码关注云+社区