关键词:fcgi、environ、getenv、段错误、segment fault、FCGI_Accept
fastcgi 更改环境变量environ引起的段错误(一)fcgi库代码分析
fastcgi 更改环境变量environ引起的段错误(二)修改libfcgi库源码解决getenv段错误问题
fastcgi 更改环境变量environ引起的段错误(三)getenv函数的使用
上接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*/