ほっしーの技術ネタ備忘録

技術ネタの備忘録です。基本的に私が忘れないためのものです。他の人の役にも立つといいなぁ。

PHP のバグ 〜回避パッチ編〜


アプローチとしては2点。

  1. LD_PRELOAD を用いて malloc(), free(), realloc() 関数をフックしてシグナルブロック
  2. zend_error() から戻った後に exit() で終了


前者だけだと Zend のヒープ管理が破綻して

zend_mm_heap corrupted

こんな funny なメッセージが出ます。
なのでおとなしく exit() するようにしました。

1. LD_PRELOAD で malloc(), free(), realloc() をフックする

/*=========================================================*
**                                                         *
**  malloc.c  --  malloc 系関数のシグナルブロック          *
**                                                         *
**=========================================================*/

#include <stdio.h>
#include <signal.h>
#include <sys/time.h>
#include <dlfcn.h>

/* オリジナル ( libc の関数 ) へのポインタ */
static void* (*malloc_org) (size_t size) = NULL;
static void (*free_org) (void* ptr) = NULL;
static void* (*realloc_org) (void* ptr, size_t size) = NULL;

/* オリジナルへのポインタを取得する ( constructor で自動コール ) */
__attribute__((constructor))
void _save_original_functions()
{
	malloc_org = (void*(*)(size_t)) dlsym( RTLD_NEXT, "malloc" );
	free_org = (void(*)(void*)) dlsym( RTLD_NEXT, "free" );
	realloc_org = (void*(*)(void*,size_t)) dlsym( RTLD_NEXT, "realloc" );
}

/* ITIMER_PROF が有効か確認する  */
/*  0: 無効, 1: 有効, -1: エラー */
int is_enable_itimer()
{
	struct itimerval timer;

	if( getitimer( ITIMER_PROF, &timer  ) != 0 ) return -1;
	if( timer.it_value.tv_sec == 0 && timer.it_value.tv_usec == 0 ) return 0;

	return 1;
}

/* SIGPROF をブロックする */
/*  0: 成功, -1: エラー   */
int block_sigprof( sigset_t* procmask )
{
	sigset_t blocked;

	if( sigemptyset( &blocked ) != 0 ) return -1;
	if( sigaddset( &blocked, SIGPROF ) != 0 ) return -1;
	if( sigprocmask( SIG_BLOCK, &blocked, procmask ) ) return -1;

	return 0;
}

/* malloc() のフック関数 */
void* malloc( size_t size )
{
	void* ret = NULL;
	sigset_t procmask;

	switch( is_enable_itimer() ) {
		case 0: /* 無効 */
			return malloc_org( size );
		case -1: /* エラー */
			return NULL;
	}

	if( block_sigprof( &procmask ) != 0 ) return NULL;

	ret = malloc_org( size );

	sigprocmask( SIG_SETMASK, &procmask, NULL );

	return ret;
}

/* free() のフック関数 */
void free( void* ptr )
{
	sigset_t procmask;

	switch( is_enable_itimer() ) {
		case 0: /* 無効 */
			free_org( ptr );
		case -1: /* エラー */
			return;
	}

	if( block_sigprof( &procmask ) != 0 ) return;

	free_org( ptr );

	sigprocmask( SIG_SETMASK, &procmask, NULL );
}

/* realloc() のフック関数 */
void* realloc( void* ptr, size_t size )
{
	void* ret = NULL;
	sigset_t procmask;

	switch( is_enable_itimer() ) {
		case 0: /* 無効 */
			return realloc_org( ptr, size );
		case -1: /* エラー */
			return NULL;
	}

	if( block_sigprof( &procmask ) != 0 ) return NULL;

	ret = realloc_org( ptr, size );

	sigprocmask( SIG_SETMASK, &procmask, NULL );

	return ret;
}

これを malloc.c として

# gcc -fPIC -shared -o malloc.so malloc.c
# setenv LD_PRELOAD /full/path/malloc.so
# /usr/local/etc/rc.d/apache22 restart

これでフック。

2. zend_timeout() にパッチを当てて終了させる

--- Zend/zend_execute_API.c.orig        2009-02-22 14:36:10.000000000 +0900
+++ Zend/zend_execute_API.c     2009-02-22 14:43:29.000000000 +0900
@@ -1309,12 +1309,17 @@
 {
        TSRMLS_FETCH();

+zend_try {
        if (zend_on_timeout) {
                zend_on_timeout(EG(timeout_seconds) TSRMLS_CC);
        }

        zend_error(E_ERROR, "Maximum execution time of %d second%s exceeded",
                          EG(timeout_seconds), EG(timeout_seconds) == 1 ? "" : "s");
+} zend_catch {
+       /* force shutdown */
+       exit( -2 );
+} zend_end_try();
 }

 #ifdef ZEND_WIN32

これを /usr/ports/lang/php5/files/patch-zend_execute_API.c としておいて

# portupgrade -f php5-5.2.8

再インストール。


とりあえずこんな感じで問題なく動いてる感じ。

3. 別の方法

--- Zend/zend_execute_API.c.orig        2009-02-22 15:19:41.000000000 +0900
+++ Zend/zend_execute_API.c     2009-02-22 15:21:43.000000000 +0900
@@ -1307,6 +1307,9 @@

 ZEND_API void zend_timeout(int dummy)
 {
+       /* force shutdown */
+       _exit( -2 );
+
        TSRMLS_FETCH();

        if (zend_on_timeout) {


1. をやらずにこれだけでもいいのかもしれない。


試してないから分からない。_exit ならファイルディスクリプタとかは
閉じてくれるみたいだけど他のグローバルリソースがどうなるかわからないからちょっと怖い。