升级 PHP5.4.x后遇到segmentationfault

升级到5.4.x后,在rotate日志之后, 发现httpd segmentatioin fault了,排查了好长时间,还是一无所获啊,网上搜索了下,也发现了类似的问题:

https://bugs.php.net/bug.php?id=62129&thanks=3

http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=694473

http://www.mail-archive.com/debian-apache@lists.debian.org/msg14183.html

先描述下表象:

1. session.c 里面有一个全局的函数指针变量 php_session_rfc1867_orig_callback

2. 在sessioin  minit的时候会将 php_session_rfc1867_orig_callback 设置为 默认的php_rfc1867_callback(main/rfc1867.c中定义的一个函数指针)

3. session minit中 同时将 php_rfc1867_callback 设置为 session 中定义的一个php_session_rfc1867_callback这个函数

    php_session_rfc1867_orig_callback = php_rfc1867_callback;
    php_rfc1867_callback = php_session_rfc1867_callback;
 

4. 在 php_session_rfc1867_callback函数中有这么一段代码,如果orig函数不为空,则首先调用orig函数

static int php_session_rfc1867_callback(unsigned int event, void *event_data, void **extra TSRMLS_DC) /* {{{ */
{
    php_session_rfc1867_progress *progress;
    int retval = SUCCESS;

    if (php_session_rfc1867_orig_callback) {
        retval = php_session_rfc1867_orig_callback(event, event_data, extra TSRMLS_CC);
    } 

5. 在 mshutdown的时候没有显式将 php_session_rfc1867_orig_callback 设定为null, 同时全局变量定义的时候也没有显式设定为null

6. reload httpd 之后,因为未知的原因, php_session_callback 保存了 reload之前的值, php_session_rfc1867_callback

7. 重新调用minit函数的时候, 将 php_rfc1867_callback赋值给 php_session_rfc1867_orig_callback, 这个时候php_session_rfc1867_orig_callback 和 php_rfc1867_callback 以及 php_session_rfc1867_callback 其实是相等的了

    php_session_rfc1867_orig_callback = php_rfc1867_callback;
    php_rfc1867_callback = php_session_rfc1867_callback;
 

8。 当再次调用 4 中的php_session_rfc1867_callback的时候,就死循环了,直到资源耗尽,segmentation fault.

 

那为啥 reload之后 php_rfc1867_callback 的值还保留了呢?

首先分析下httpd的启动过程以及reload的过程

1. httpd 以root身份启动, 假定进程号为 A, 然后load所有的module,监听配置的端口比如80

2. A启动守护进程(root身份运行)B,然后就退出了。

3. B加载所有module的扩展(比如php扩展)

4. B根据apache的配置启动子进程

5. 当B监听到有request来的时候,转交给子进程处理请求,然后继续监听

6.  当B接受到SIGHUP信号的时候,通知子进程退出,然后unload所有的module的扩展, 再unload所有的module

7.  B 重新加载所有的module, 接下来module加载自己的扩展(比如php扩展), B根据配置创建子进程

8.  B继续监听

 

在reload前后, libphp5.so是首先被unload掉, 然后又被load了的,而且 php_rfc1867_callback 定义的时候显式指定了是null.

难道重新加载.so的时候,全局变量不会被重新初始化?

还是没有搞明白。

 

实际上遇到的不仅仅是 libphp5.so 里面的全局变量有这样的问题, 我们在用的ice-php也有类似的问题,还有就是自己写的一个扩展同样也有类似的问题,如果不在mshutdown的时候将全局变量设置为 null, reload之后还是保留了原先的值,然后就会出错了。

所以在session.c 的 mshutdown 增加了几行代码将minit修改的值恢复回来:

diff -cr php-5.4.11/ext/session/session.c php-5.4.11-2/ext/session/session.c
*** php-5.4.11/ext/session/session.c    2013-01-16 15:10:30.000000000 +0800
--- php-5.4.11-2/ext/session/session.c    2013-01-20 15:22:17.427613455 +0800
***************
*** 2224,2229 ****
--- 2224,2235 ----
      ps_serializers[PREDEFINED_SERIALIZERS].name = NULL;
      memset(&ps_modules[PREDEFINED_MODULES], 0, (MAX_MODULES-PREDEFINED_MODULES)*sizeof(ps_module *));
 
+     // reset the callback pointer to null
+     php_session_rfc1867_orig_callback = NULL;
+     if(php_rfc1867_callback == php_session_rfc1867_callback) {
+         php_rfc1867_callback = NULL;
+     }
+
      return SUCCESS;
  }
  /* }}} */

 

运行一段时间看看是否有问题。

This article is posted by on , link is .

Leave a reply