4.2 动手编译PHP

第一章我们曾介绍过,PHP编译前的configure有两个特殊的选项,打开它们对我们开发PHP扩展或者进行PHP嵌入式开发时非常有帮助。但是当我们正常使用PHP的时候,则不应该开启这两个选项。

—enable-debug

顾名思义,它的作用是激活调试模式。它将激活PHP源码中几个非常关键的函数,最典型的功能便是在每一个请求结束后给出这一次请求中内存的泄漏情况。回顾一下第三章《内存管理》部分,php内核中的ZendMM( Zend Memory Manager)将会在每一个请求结束后强制释放在这个请求中申请的内存。By running a series of aggressive regression tests against newly developed code, leak points can be easily spotted and plugged prior to any public release. Take a look at the following code snippet:

  1. void show_value(int n)
  2. {
  3. char *message = emalloc(1024);
  4. sprintf(message, "The value of n is %d\n", n);
  5. php_printf("%s", message);
  6. }

上面的代码执行后,将会导致1024B的内存泄漏,但是在ZendMM的帮助下,在请求结束后会被PHP内核自动的释放掉。但是如果你开启了—enable-debug选项,在请求结束后内核便会给出一条信息,告知我们程序猿这次请求的内存泄漏情况。/cvs/php5/ext/sample/sample.c(33) :Freeing 0x084504B8 (1024 bytes), script=-=== Total 1 memory leaks detected ===这条提示告知我们在这次请求结束后,ZendMM清理了泄漏的内存以及泄漏的内存位置。在它的帮助下,我们可以很快的定位到有问题的代码,然后通过efree等函数修正这个bug。其实,内存泄漏并不是我们在开发中碰到的唯一错误,还有很多其它的bug很难被检测出来。有时候这些bug是致命的,但很难定位到出问题的代码。很多时候我们忙活了大半个晚上,修改了很多文件,最后make,但是当我们运行脚本的时候却得到下面的段错误。

  1. $ sapi/cli/php -r 'myext_samplefunc();'
  2. Segmentation Fault
  3. //如果中文环境,则显示段错误

Orz…错误出在哪呢?我们遍历myext_samplefuc的所有实现代码也没有发现问题,扔进gdb里也仅仅显示几行无关紧要的信息而已。这种情况下,enable-debug就能帮你大忙了,打开这个选项后,你编译出来的php则会嵌入gdb或其它文件需要的所有调试信息。现在我们重新编译这个扩展,再扔进gdb里调试,便会得到如下的信息:

  1. #0 0x1234567 php_myext_find_delimiter(str=0x1234567 "foo@#(FHVN)@\x98\xE0...",
  2. strlen=3, tsrm_ls=0x1234567)
  3. p = strchr(str, ',');

现在所有的问题都水落石出了,字符串变量str没有以NULL结尾,而我们却把它当作一个参数传给了二进制不安全的字符串处理函数,str将会扫描str知道找到NULL为止,它的扫描肯定是越界了,然后引发了一个段错误。找到问题根源后,我们只要用memchr来替换strchr函数就能修复这个bug了。

—enable-maintainer-zts

第二个重要的参数便是激活php的线程安全机制(Thread Safe Resource Manager(TSRM)/Zend Thread Safety(ZTS)),使我们开发出的程序是线程安全的。对于TSRM的介绍大家可以参考第一章的介绍,在平时的开发中,建议打开这个选项。

—enable-embed

其实还有一个选项比较重要,那就是enable-embed,它主要用在你做php的嵌入式开发的场景中。平时我们把php作为apache的一个module进行编译,得到libphp5.so,而这个选项便使php编译后得到一个与我们设定的SAPI相对应的结果。