PHP是个脚本语言, 错误定位容易, 编写起来速度较快..但是, 如果遇到了PHP自身的一些问题,那就会让人很恼火.

最近遇到一个很低概率的Core dump(大概是2年才出现一次), 经过和同事一起仔细分析,跟踪,终于找出了原因, 拿出来与大家分享.

先看Core文件展示的堆栈:

(gdb) bt
#0  0x000000302af2e2ed in raise () from /lib64/tls/libc.so.6
#1  0x000000302af2fa3e in abort () from /lib64/tls/libc.so.6
#2  0x000000302af62db1 in __libc_message () from /lib64/tls/libc.so.6
#3  0x000000302af6888e in _int_free () from /lib64/tls/libc.so.6
#4  0x000000302af68bd6 in free () from /lib64/tls/libc.so.6
#5  0x0000002a95bd4a8c in php_error_cb (type=1,
    error_filename=0x2a9755c608 "***********.php", error_lineno=272, format=Variable "format" is not available.
)
    at /home/huixinchen/php-5.2.4/main/main.c:803
#6  0x0000002a95c182ed in zend_error (type=1, format=0x2a95ebee68 "Maximum execution time of %d second%s exceeded")
    at /home/huixinchen/php-5.2.4/Zend/zend.c:976
#7  <signal handler called>
#8  0x000000302af6ac02 in malloc () from /lib64/tls/libc.so.6
#9  0x000000302af6fc92 in strdup () from /lib64/tls/libc.so.6
#10 0x0000002a95bd4720 in php_error_cb (type=8,
    error_filename=0x2a9755c608 "******.php", error_lineno=272, format=Variable "format" is not available.
)
    at /home/huixinchen/php-5.2.4/main/main.c:807


左分析,右分析,结论如下。

在PHP execute limit time 信号来的时候,PHP正在出错处理函数中, 这个时候,事件被响应,再次重入php_error_cb函数, 观察php_error_cb函数:

static void php_error_cb(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args){
    ....
    if (display) {
        if (PG(last_error_message)) {
            free(PG(last_error_message));
        }
        if (PG(last_error_file)) {
            free(PG(last_error_file));
        }
        PG(last_error_type) = type;
        PG(last_error_message) = strdup(buffer);
        PG(last_error_file) = strdup(error_filename);
        PG(last_error_lineno) = error_lineno;
    }
   ....
}


我们可以看到,在php_error_cb中, 会对上一次的错误文件名进行free操作, 并对当前错误文件名分配内存.

如果, 当php执行到free了旧的错误文件名以后, 响应了事件, 于是分配新的错误文件名内存这一步并没有被执行..

这个时候, 事件响应以后,又再一次进入到了php_error_cb函数, 当再次执行free(PG(last_error_file))的时候, 会导致core dump

另外,发现PHP(*nix下)是通过调用setitimer来实现脚本超时处理的。

具体代码可以grep zend_set_timeout

转一个可以较大概率重现这个Core的脚本(By Lili):

<?php
set_time_limit(1);
while(1) {
    $a = $arr['index_miss'];
    $a = $arr['index_miss'];
    $a = $arr['index_miss'];
    $a = $arr['index_miss'];
    $a = $arr['index_miss'];
    $a = $arr['index_miss'];
}
?>