Press "Enter" to skip to content

使用PHP Embed SAPI实现Opcodes查看器

PHP提供了一个Embed SAPI,也就是说,PHP容许你在C/C++语言中调用PHP/ZE提供的函数。本文就通过基于Embed SAPI实现一个PHP的opcodes查看器。
首先,下载PHP源码以供编译, 我现在使用的是PHP5.3 alpha2
进入源码目录:

 ./configure --enable-embed --with-config-file-scan-dir=/etc/php.d --with-mysql  --with-config-file-path=/etc/
 ./make
 ./make install

最后,记得要将生成的libphp5.so复制到运行时库的目录,我直接拷贝到了/lib/, 否则会在运行你自己的embed程序的时候报错:

./embed: error while loading shared libraries: libphp5.so: cannot open shared object file: No such file or directory

如果你对PHP的SAPI还不熟悉的话,我建议你看看我的这篇文章:深入理解Zend SAPIs(Zend SAPI Internals)
这个时候,你就可以在你的C代码中,嵌入PHP脚本解析器了, 我的例子:

#include "sapi/embed/php_embed.h"
int main(int argc, char * argv[]){
    PHP_EMBED_START_BLOCK(argc,argv);
    char * script = " print 'Hello World!';";
    zend_eval_string(script, NULL,
                                      "Simple Hello World App" TSRMLS_CC);
    PHP_EMBED_END_BLOCK();
    return 0;
}
 

然后就是要指明include path了,一个简单的Makefile

CC = gcc
CFLAGS = -I/usr/local/include/php/ \
            -I/usr/local/include/php/main \
            -I/usr/local/include/php/Zend \
            -I/usr/local/include/php/TSRM \
            -Wall -g
LDFLAGS = -lstdc++ -L/usr/local/lib -lphp5
ALL:
    $(CC) -o embed embed.cpp $(CFLAGS) $(LDFLAGS)

编译成功以后, 运行,我们可以看到, stdout输出 Hello World!
基于这个,我们就可以很容易的实现一个类似于vld的Opcodes dumper:
首先我们定义opcode的转换函数(全部的opcodes可以查看Zend/zend_vm_opcodes.h);

char *opname(zend_uchar opcode){
    switch(opcode) {
        case ZEND_NOP: return "ZEND_NOP"; break;
        case ZEND_ADD: return "ZEND_ADD"; break;
        case ZEND_SUB: return "ZEND_SUB"; break;
        case ZEND_MUL: return "ZEND_MUL"; break;
        case ZEND_DIV: return "ZEND_DIV"; break;
        case ZEND_MOD: return "ZEND_MOD"; break;
        case ZEND_SL: return "ZEND_SL"; break;
        case ZEND_SR: return "ZEND_SR"; break;
        case ZEND_CONCAT: return "ZEND_CONCAT"; break;
        case ZEND_BW_OR: return "ZEND_BW_OR"; break;
        case ZEND_BW_AND: return "ZEND_BW_AND"; break;
        case ZEND_BW_XOR: return "ZEND_BW_XOR"; break;
        case ZEND_BW_NOT: return "ZEND_BW_NOT"; break;
        /*...省略 ....*/
        default : return "UNKNOW"; break;

然后定义zval和znode的输出函数:

 char *format_zval(zval *z)
{
    static char buffer[BUFFER_LEN];
    int len;
    switch(z->type) {
        case IS_NULL:
            return "NULL";
        case IS_LONG:
        case IS_BOOL:
            snprintf(buffer, BUFFER_LEN, "%d", z->value.lval);
            return buffer;
        case IS_DOUBLE:
            snprintf(buffer, BUFFER_LEN, "%f", z->value.dval);
            return buffer;
        case IS_STRING:
            snprintf(buffer, BUFFER_LEN, "\"%s\"", z->value.str.val);
            return buffer;
        case IS_ARRAY:
        case IS_OBJECT:
        case IS_RESOURCE:
        case IS_CONSTANT:
        case IS_CONSTANT_ARRAY:
            return "";
        default:
            return "unknown";
    }
}
char * format_znode(znode *n){
    static char buffer[BUFFER_LEN];
    switch (n->op_type) {
        case IS_CONST:
            return format_zval(&n->u.constant);
            break;
        case IS_VAR:
            snprintf(buffer, BUFFER_LEN, "$%d",  n->u.var/sizeof(temp_variable));
            return buffer;
            break;
        case IS_TMP_VAR:
            snprintf(buffer, BUFFER_LEN, "~%d",  n->u.var/sizeof(temp_variable));
            return buffer;
            break;
        default:
            return "";
            break;
    }
}
 

然后定义op_array的输出函数:

void dump_op(zend_op *op, int num){
    printf("%5d  %5d %30s %040s %040s %040s\n", num, op->lineno,
            opname(op->opcode),
            format_znode(&op->op1),
            format_znode(&op->op2),
            format_znode(&op->result)) ;
}
void dump_op_array(zend_op_array *op_array){
    if(op_array) {
        int i;
        printf("%5s  %5s %30s %040s %040s %040s\n", "opnum", "line", "opcode", "op1", "op2", "result");
        for(i = 0; i < op_array->last; i++) {
            dump_op(&op_array->opcodes[i], i);
        }
    }
}

最后,就是程序的主函数了:

int main(int argc, char **argv){
    zend_op_array *op_array;
    zend_file_handle file_handle;
    if(argc != 2) {
        printf("usage:  op_dumper <script>\n");
        return 1;
    }
    PHP_EMBED_START_BLOCK(argc,argv);
    printf("Script: %s\n", argv[1]);
    file_handle.filename = argv[1];
    file_handle.free_filename = 0;
    file_handle.type = ZEND_HANDLE_FILENAME;
    file_handle.opened_path = NULL;
    op_array =  zend_compile_file(&file_handle, ZEND_INCLUDE TSRMLS_CC);
    if(!op_array) {
        printf("Error parsing script: %s\n", file_handle.filename);
        return 1;
    }
    dump_op_array(op_array);
    PHP_EMBED_END_BLOCK();
    return 0;
}

编译,运行测试脚本(sample.php):
sample.php:

   echo "laruence";

命令:

./opcodes_dumper  sample.php

得到输出结果(如果你对下面的结果很迷惑,那么建议你再看看我的这篇文章:深入理解PHP原理之Opcodes):

Script: sample.php
opnum   line                         opcode                                      op1                                      op2                                   result
    0      2                      ZEND_ECHO                               "laruence"
    1      4                    ZEND_RETURN                                        1

呵呵,怎么样,是不是很好玩呢?
源码地址:http://code.google.com/p/opcodesdumper/

30 Comments

  1. […] 真的没有任何区别? 我们通过vld(具体安装请点击此处)打印下php得中间产物opcode(opcode得参数说明请点击此处)来看看: 原始代码: […]

  2. Xin24
    Xin24 November 25, 2013

    只能调用1次 第二次调用是就内存泄漏

  3. […] PHP源码里的configure –help中提到–enable-embed有两个类型:shared和static,默认为shared,如果你开启了–enable-embed,configure之后可以看看你Makefile里的OVERALL_TARGET是什么,我这里是libphp5.so。之前没有如前所述那么系统的概念,所以我毫不犹豫的编译了。兴奋地将最后得到的libs/libphp5.so弄到/usr/lib,执行opdump(想知道怎么得到这样一个dumper的同学,请移步使用PHP Embed SAPI实现Opcodes查看器),系统会报出一个非常令人沮丧的错误: can't link with bundle (MH_BUNDLE) only dylibs (MH_DYLIB) […]

  4. 神仙
    神仙 April 1, 2012

    如果自己做一个sapi,有相关文档么?

  5. 砖家
    砖家 January 1, 2012

    很通俗易懂。入门很好。
    PS:这代码颜色配色不错,能分享一下吗

  6. qq347835460
    qq347835460 November 23, 2009

    补充一点,楼主的代码(svn: http://code.google.com/p/opcodesdumper/source/)在windows下面理论上是可以编译成exe的.但是windows下的php的Embed SAPI是不一样,windows下的php默认支持Embed SAPI,在二进制包的根目录下面有一个.
    php5embed.lib到底干什么用的,情况不明.
    楼主的代码编译的时候要在最前面加上以下两句,否则编译阶段就会出错.
    #define ZEND_WIN32
    #define PHP_WIN32
    同样需要下载源码包,源码包里面才有sapi/embed/php_embed.h,同样需要设置vc的inlude目录,把php5embed.lib加到lib目录下后编译还是无法通过,链接的时候出错了,具体原因不清楚.如果编译成exe了,使用起来就更方便了…(^_^这只是一个构思,还没有成为事实.)

  7. qq347835460
    qq347835460 November 23, 2009

    替楼主回到楼上的:
    下载vld扩展,用c++编译成dll,怎么编译,可以baidu之,或者自己研究,这个过程要下载php的源码包和二进制包(最好版本一致).
    编译成了dll之后修改php.ini,加一行:
    extension=vld.dll
    像普通扩展一样加载这个扩展.调用vld的方法如下:
    C:\Documents and Settings\Administrator.TEST1111>php -dvld.active=1 d:\\1.php
    Failed loading php_xdebug.dll
    Branch analysis from position: 12647312
    Return found
    filename: D:\1.php
    function name: (null)
    number of ops: 4
    compiled vars: !0 = $a
    line # op fetch ext return operands
    ——————————————————————————-
    2 0 ASSIGN !0, 1
    3 1 ECHO !0
    4 2 RETURN 1
    3* ZEND_HANDLE_EXCEPTION
    1

  8. aaa110110
    aaa110110 November 23, 2009

    请问windows环境下面怎么得到opcode,以上代码怎么编译呢?

  9. 雪候鸟
    雪候鸟 October 23, 2009

    1. 编译的时候确定
    2. 我不知道有什么办法, 或者你可以使用c的变量来做个中转. 在PHP_EMBED_START_BLOCK(argc,argv)中做为第二个参数的一部分传入, 通过SG(request_info).argv来访问.

  10. ddouble
    ddouble October 23, 2009

    现在已能够在c程序里嵌入php解释器,非常感谢,但还有几个问题不解,望赐教:
    1.如何在嵌入的方式下指定自己的php.ini文件;
    2.多个连续执行的 zend_eval_string之间无法延续变量作用域,所以前面的eval的变量,后面eval的语句中无法得到。

  11. 雪候鸟
    雪候鸟 October 22, 2009

    @ddouble 文档工作做的不好…确实没有你想要的.

  12. ddouble
    ddouble October 22, 2009

    到处找zend_api的资料,想把php解释器嵌入到c程序中,google上怎么也找不到。为啥zend网站上也没呢,还是完全没摸到门儿?
    博主知道哪里有吗?zend api reference之类的文档,最好还有指南示例之类的文档。谢谢啦!

  13. fybird
    fybird October 3, 2009

    对,直接就下载了一个5.3 没想到有bug.

  14. 雪候鸟
    雪候鸟 October 3, 2009

    你是用的PHP5.3吧?
    是5.3的一个bug

  15. fybird
    fybird October 2, 2009

    我是在php_embed_init/shutdown 前加上了SAPI_SPI才通过的。ZEND_API不好使?不知道为什么

  16. 雪候鸟
    雪候鸟 September 8, 2009

    @leric 你需要用ZEND_API来修饰php_embed_init/shutdown. 符号可见性的问题.

  17. Leric
    Leric September 8, 2009

    连接时总是报一个错误:
    opcodes_dumper.cpp:(.text+0x910): undefined reference to `php_embed_init’
    opcodes_dumper.cpp:(.text+0x9e9): undefined reference to `php_embed_shutdown’
    collect2: ld returned 1 exit status
    make: *** [ALL] Error 1
    没用C做过什么东西,也不知道该咋解决,还望博主指点迷津

  18. jackywdx
    jackywdx May 17, 2009

    呵呵,终于看懂了.

  19. Anonymous
    Anonymous November 6, 2008

    看不懂..菜啊

  20. Anonymous
    Anonymous November 6, 2008

    fxgh

  21. snowrui
    snowrui September 26, 2008

    学习消化中……

  22. surfchen
    surfchen September 25, 2008

    google code的svn里还没源代码呢

Comments are closed.