アプローチとしては2点。
- LD_PRELOAD を用いて malloc(), free(), realloc() 関数をフックしてシグナルブロック
- 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 ならファイルディスクリプタとかは
閉じてくれるみたいだけど他のグローバルリソースがどうなるかわからないからちょっと怖い。