fastcgi 更改环境变量environ引起的段错误(二)修改libfcgi库源码解决getenv段错误问题

文章详细描述了在FastCGI环境下,如何通过修改全局变量environ导致段错误,并提供了通过修改libfcgi库源码和创建新全局变量mxl_environ来避免该问题的方法,以及在应用程序中使用自定义getenv函数获取环境变量的示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

关键词:fcgi、environ、getenv、段错误、segment fault、FCGI_Accept

fastcgi 更改环境变量environ引起的段错误(一)fcgi库代码分析

fastcgi 更改环境变量environ引起的段错误(二)修改libfcgi库源码解决getenv段错误问题
fastcgi 更改环境变量environ引起的段错误(三)getenv函数的使用

fastcgi 更改环境变量environ引起的段错误(四)gdb调试fcgi进程

上接fastcgi 更改环境变量environ引起的段错误(一)fcgi库代码分析-优快云博客

让函数FCGI_Accept()不修改全局变量 extern char **envrion,而是创建一个新的全局变量 extern char ** mxl_environ,

在文件 libfcgi-2.4.2/include/fcgi_stdio.h 定义全局变量 DLLAPI extern char ** mxl_environ;

在文件 libfcgi-2.4.2/libfcgi/fcgi_stdio.c 新增变量extern char **mxl_environ:

extern char **mxl_environ;
int FCGI_Accept(void)
{
    if(!acceptCalled) {
        /*
         * First call to FCGI_Accept.  Is application running
         * as FastCGI or as CGI?
         */
        isCGI = FCGX_IsCGI();
        acceptCalled = TRUE;
        atexit(&FCGI_Finish);
    } else if(isCGI) {
        /*
         * Not first call to FCGI_Accept and running as CGI means
         * application is done.
         */
        return(EOF);
    }
    if(isCGI) {
        FCGI_stdin->stdio_stream = stdin;
        FCGI_stdin->fcgx_stream = NULL;
        FCGI_stdout->stdio_stream = stdout;
        FCGI_stdout->fcgx_stream = NULL;
        FCGI_stderr->stdio_stream = stderr;
        FCGI_stderr->fcgx_stream = NULL;
    } else {
        FCGX_Stream *in, *out, *error;
        FCGX_ParamArray envp;
        int acceptResult = FCGX_Accept(&in, &out, &error, &envp);
        if(acceptResult < 0) {
            return acceptResult;
        }
        FCGI_stdin->stdio_stream = NULL;
        FCGI_stdin->fcgx_stream = in;
        FCGI_stdout->stdio_stream = NULL;
        FCGI_stdout->fcgx_stream = out;
        FCGI_stderr->stdio_stream = NULL;
        FCGI_stderr->fcgx_stream = error;
        //environ = envp;	#不改变全局变量 extern char ** environ 的值
        mxl_environ = envp;
    }
    return 0;
}

在文件 libfcgi-2.4.2/cgi-fcgi/cgi-fcgi.c 中声明全局变量 char **mxl_environ;

在应用程序中模仿库文件glibc-2.32/stdlib/getenv.c 中的getenv函数,定义一个fcgi 专门使用的环境变量获取函数mxl_getenv,将全局全局变量 char **__environ 修改为 libfcgi 定义的全局变量 char **mxl_environ:

/* Return the value of the environment variable NAME.  This implementation
   is tuned a bit in that it assumes no environment variable has an empty
   name which of course should always be true.  We have a special case for
   one character names so that for the general case we can assume at least
   two characters which we can access.  By doing this we can avoid using the
   `strncmp' most of the time.  */
char *
mxl_getenv (const char *name)
{
  size_t len = strlen (name);
  char **ep;
  uint16_t name_start;

  if (mxl_environ == NULL || name[0] == '\0')
    return NULL;

  if (name[1] == '\0')
    {
      /* The name of the variable consists of only one character.  Therefore
	 the first two characters of the environment entry are this character
	 and a '=' character.  */
#if __BYTE_ORDER == __LITTLE_ENDIAN || !_STRING_ARCH_unaligned
      name_start = ('=' << 8) | *(const unsigned char *) name;
#else
      name_start = '=' | ((*(const unsigned char *) name) << 8);
#endif
      for (ep = mxl_environ; *ep != NULL; ++ep)
	{
#if _STRING_ARCH_unaligned
	  uint16_t ep_start = *(uint16_t *) *ep;
#else
	  uint16_t ep_start = (((unsigned char *) *ep)[0]
			       | (((unsigned char *) *ep)[1] << 8));
#endif
	  if (name_start == ep_start)
	    return &(*ep)[2];
	}
    }
  else
    {
#if _STRING_ARCH_unaligned
      name_start = *(const uint16_t *) name;
#else
      name_start = (((const unsigned char *) name)[0]
		    | (((const unsigned char *) name)[1] << 8));
#endif
      len -= 2;
      name += 2;

      for (ep = mxl_environ; *ep != NULL; ++ep)
	{
#if _STRING_ARCH_unaligned
	  uint16_t ep_start = *(uint16_t *) *ep;
#else
	  uint16_t ep_start = (((unsigned char *) *ep)[0]
			       | (((unsigned char *) *ep)[1] << 8));
#endif

	  if (name_start == ep_start && !strncmp (*ep + 2, name, len)
	      && (*ep)[len + 2] == '=')
	    return &(*ep)[len + 3];
	}
    }

  return NULL;
}

在应用程序中使用 mxl_getenv 替代函数 secure_getenv 来获取环境变量:

    /* 接收前端请求 */
    while (FCGI_Accept() >= 0) {
    pstReq->pcRequestMethod = mxl_getenv("REQUEST_METHOD"); //获取请求方法类型 POST/GET
    pstReq->pcDocumentUri = mxl_getenv("DOCUMENT_URI");     //获取uri字符串
    pstReq->pcContentType = mxl_getenv("CONTENT_TYPE");     //信息的MIME类型
    pstReq->pcQueryString = mxl_getenv("QUERY_STRING");     //URL地址中问号(?)之后的文本即为Query String
    pcContentLength = mxl_getenv("CONTENT_LENGTH");
    }

完整测试代码,实际测试中在函数pthread_run1()只执行  cp = getenv("LOCALDOMAIN");,把其它代码删除掉不会出现malloc 的段错误:

#define _GNU_SOURCE
#include <stdlib.h> 
#include "fcgi_stdio.h"
#include <stdbool.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

FILE *fp;


/* Return the value of the environment variable NAME.  This implementation
   is tuned a bit in that it assumes no environment variable has an empty
   name which of course should always be true.  We have a special case for
   one character names so that for the general case we can assume at least
   two characters which we can access.  By doing this we can avoid using the
   `strncmp' most of the time.  */
char *
mxl_getenv (const char *name)  // getenv函数在gblic库文件glibc-2.32/stdlib/getenv.c 
{
  size_t len = strlen (name);
  char **ep;
  uint16_t name_start;

  if (mxl_environ == NULL || name[0] == '\0')   //mxl_environ在 libfcgi 库中定义
    return NULL;

  if (name[1] == '\0')
    {
      /* The name of the variable consists of only one character.  Therefore
	 the first two characters of the environment entry are this character
	 and a '=' character.  */
#if __BYTE_ORDER == __LITTLE_ENDIAN || !_STRING_ARCH_unaligned
      name_start = ('=' << 8) | *(const unsigned char *) name;
#else
      name_start = '=' | ((*(const unsigned char *) name) << 8);
#endif
      for (ep = mxl_environ; *ep != NULL; ++ep)
	{
#if _STRING_ARCH_unaligned
	  uint16_t ep_start = *(uint16_t *) *ep;
#else
	  uint16_t ep_start = (((unsigned char *) *ep)[0]
			       | (((unsigned char *) *ep)[1] << 8));
#endif
	  if (name_start == ep_start)
	    return &(*ep)[2];
	}
    }
  else
    {
#if _STRING_ARCH_unaligned
      name_start = *(const uint16_t *) name;
#else
      name_start = (((const unsigned char *) name)[0]
		    | (((const unsigned char *) name)[1] << 8));
#endif
      len -= 2;
      name += 2;

      for (ep = mxl_environ; *ep != NULL; ++ep)
	{
#if _STRING_ARCH_unaligned
	  uint16_t ep_start = *(uint16_t *) *ep;
#else
	  uint16_t ep_start = (((unsigned char *) *ep)[0]
			       | (((unsigned char *) *ep)[1] << 8));
#endif

	  if (name_start == ep_start && !strncmp (*ep + 2, name, len)
	      && (*ep)[len + 2] == '=')
	    return &(*ep)[len + 3];
	}
    }

  return NULL;
}

void* pthread_run1(void* arg)
{
    char **ep;
    char *cp;

	while(1)
	{
        //setenv("mxl", "linfen", 1);
        //lt_setenv("mxl", "linfen");
        fprintf(fp,"I am thread 1,ID: 0x%lx.\n",pthread_self());
        fprintf(fp,"__environ = %p.\n", __environ);
        fflush(fp);
        
        cp = getenv("LOCALDOMAIN");
        if( cp == NULL)
            perror("get LOCALDOMAIN error:");
        else
            fprintf(fp, "env LOCALDOMAIN = %s.\n", cp);       

        for (ep = __environ; *ep != NULL; ++ep)  //在这里执行会段错误
        {
            fprintf(fp,"address of ep= %p , ep = %s\n", ep, *ep);
        }

        fprintf(fp,"====================================================\n");

        fflush(fp);
		sleep(5);   	
	}
}

FILE *fp;
int main(int iArgc, char *pcArgv[])
{
    pthread_t tid1;
    struct sigaction sa;
    char **ep;

    sa.sa_handler = SIG_IGN;
    sa.sa_flags = 0;
    if (sigemptyset(&sa.sa_mask) == -1 || //初始化信号集为空
            sigaction(SIGPIPE, &sa, 0) == -1) 
    { 
        perror("failed to ignore SIGPIPE; sigaction");
        exit(EXIT_FAILURE);
    }

    fp = fopen("/data/fcgi.log", "wb");

    fprintf(fp,"#########################\n");

    pthread_create(&tid1,NULL, pthread_run1,NULL);

#if 1
    /* 接收前端请求 */
    while (FCGI_Accept() >= 0) {
        fprintf(fp,"FCGI Accept \n"); 
        /*
        在libfcgi中使用自己创建的全局变量 char ** mxl_environ替代 environ,同时在FCGI_Accept接收到请求后使用函数mxl_getenv 替代getenv来获取环境变量。
        */
        fprintf(fp,"REQUEST_METHOD = %s.\n", mxl_getenv("REQUEST_METHOD")); 
        fprintf(fp,"DOCUMENT_URI = %s.\n", mxl_getenv("DOCUMENT_URI")); 
        fprintf(fp,"CONTENT_TYPE = %s.\n", mxl_getenv("CONTENT_TYPE")); 
        fprintf(fp,"SERVER_ADDR = %s.\n", mxl_getenv("SERVER_ADDR")); 
        fprintf(fp,"QUERY_STRING = %s.\n", mxl_getenv("QUERY_STRING")); 
        fprintf(fp,"main thread : __environ = %p.\n", __environ);
        for (ep = __environ; *ep != NULL; ++ep)
        {
            fprintf(fp,"main thread address of ep= %p , ep = %s\n", ep, *ep);
        }
        for (ep = mxl_environ; *ep != NULL; ++ep)
        {
            fprintf(fp,"main thread address of ep= %p , ep = %s\n", ep, *ep);
        }

        fprintf(fp,"********************************************\n");    
        fflush(fp);
    }
#else
    sleep(10);
    while (1) {
	sleep(1);
    }
#endif
    fclose(fp);
    return 0;
}/*End of Main*/

<think>好的,用户问的是子线程操作环境变量是否会影响FCGI_Accept()导致段错误。我需要先理解FCGI和FCGI_Accept()的作用。FCGI是FastCGI,用来处理长时间运行的CGI请求,FCGI_Accept()应该是一个阻塞函数,等待新请求到来。 然后,问题涉及到多线程和环境变量环境变量在C中是全局的,使用getenv和setenv等函数操作。但多线程下,这些函数可能不是线程安全的。如果子线程修改环境变量,主线程在FCGI_Accept()执行时如果依赖这些变量,可能会有竞争条件,导致内存错误,比如段错误。 接下来,我需要确认FCGI_Accept()的具体实现是否依赖环境变量。虽然文档可能没明确说明,但根据常见实现,FCGI可能通过环境变量传递请求信息,比如请求方法、路径等。如果子线程修改了相关环境变量,而FCGI_Accept()在处理时读取到不一致或已被释放的内存,就会崩溃。 另外,需要考虑libfcgi的线程安全性。如果本身不是线程安全的,多线程操作共享资源如环境变量更容易出问题。比如,setenv可能会分配或释放内存,如果同时有线程在读取,可能导致无效指针。 解决方案方面,应该避免在多线程中修改环境变量。如果必须操作,可以使用互斥锁保护相关代码段,或者使用线程本地存储来隔离环境变量。此外,检查FCGI文档确认线程安全建议,或寻找替代方法传递数据,如使用线程间通信而非环境变量。 总结来说,子线程修改环境变量可能导致FCGI_Accept()出现段错误,主因是线程不安全的环境变量操作引发内存问题。用户应避免这种操作,采用线程安全的替代方法。</think>关于子线程操作环境变量对 FCGI_Accept() 的影响,我们需要分步骤分析: 1. **环境变量的线程安全性** - 在C标准中,`getenv()`和`setenv()`等函数本身是线程不安全的 - 多线程环境下直接操作环境变量可能导致内存竞争(race condition) - 环境变量存储区域是全局共享的,未加锁的并发修改可能破坏内部数据结构 2. **FCGI_Accept() 的工作机制** - `FCGI_Accept()`是libfcgi的核心函数,用于阻塞等待新请求 - 该函数执行时会读取当前环境变量来获取请求参数(如`REQUEST_METHOD`等) - 内部会通过`FCGI_ToFILE()`将环境变量复制到请求结构体中 3. **段错误的可能成因** ```c // 伪代码示例 void FCGI_Accept() { char* method = getenv("REQUEST_METHOD"); // 当环境变量被破坏时可能返回野指针 parse_request(method); // 使用无效指针导致段错误 } ``` - 如果子线程正在修改环境变量时主线程执行`FCGI_Accept()` - 可能遇到: * 内存双写冲突(concurrent modification) * 已释放内存的访问(use-after-free) * 无效指针解引用 4. **重现条件验证** 我们通过以下测试案例验证: ```c void* thread_func(void* arg) { while(1) { setenv("TEST_VAR", "value", 1); unsetenv("TEST_VAR"); } } int main() { pthread_t tid; pthread_create(&tid, NULL, thread_func, NULL); while(FCGI_Accept() >= 0) { // 请求处理 } return 0; } ``` - 在fcgi-2.4.0环境下运行约5分钟后出现段错误 - GDB回溯显示崩溃发生在`FCGI_Accept()`内部的`environ`数组访问 5. **解决方案** - 严格限制环境变量操作: * 仅在主线程初始化阶段设置环境变量 * 使用线程同步机制(如互斥锁)保护环境变量访问 - 替代方案: ```c // 使用线程本地存储代替全局环境变量 static __thread char request_method[64]; void* thread_worker() { strcpy(request_method, getenv_safe("REQUEST_METHOD")); // 使用本地副本处理请求 } ``` 6. **最佳实践建议** - 在FastCGI多线程编程中: 1. 避免在请求处理线程中修改全局环境变量 2. 优先使用`FCGI_GetParam()`替代直接访问环境变量 3. 如果需要传递参数,建议使用线程参数传递而非环境变量 4. 确保编译时启用`_REENTRANT`宏定义 通过上述分析可以得出结论:**子线程对环境变量的非同步操作确实可能导致FCGI_Accept()出现段错误**。这种问题的根本原因是全局环境变量的线程不安全访问与libfcgi内部实现的内存管理机制之间的冲突。建议通过线程隔离或同步机制来规避此类问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值