msgbartop
PHP语言, PHP扩展, Zend引擎相关的研究,技术,新闻分享 – 左手代码 右手诗
msgbarbottom

23 Sep 08 使用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/


分享到:



Related Posts:

Tags: , , ,

26 Responses to “使用PHP Embed SAPI实现Opcodes查看器”

  1. love has no gender essay titles |

    Hi there friends, its wonderful article about educationand fully defined, keep it
    up all the time.

    My blog – love has no gender essay titles

  2. ytf606的点滴 » 有关于变量自增与自减 |

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

  3. Xin24 |

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

  4. 在mac上开启php的embed模式 | 001/010/100/000/101 |

    [...] 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) [...]

  5. 神仙 |

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

  6. 砖家 |

    很通俗易懂。入门很好。

    PS:这代码颜色配色不错,能分享一下吗

  7. 使用PHP Embed SAPI实现Opcodes查看器 | 万维网黑客联盟 |

    [...] 本文地址: http://www.laruence.com/2008/09/23/539.html [...]

  8. (转)深入理解PHP原理之变量分离/引用(Variables Separation) » Creative Power |

    [...] 使用PHP Embed SAPI实现Opcodes查看器 [...]

  9. qq347835460 |

    补充一点,楼主的代码(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了,使用起来就更方便了…(^_^这只是一个构思,还没有成为事实.)

  10. qq347835460 |

    替楼主回到楼上的:
    下载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

  11. aaa110110 |

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

  12. 雪候鸟 |

    1. 编译的时候确定

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

  13. ddouble |

    现在已能够在c程序里嵌入php解释器,非常感谢,但还有几个问题不解,望赐教:

    1.如何在嵌入的方式下指定自己的php.ini文件;
    2.多个连续执行的 zend_eval_string之间无法延续变量作用域,所以前面的eval的变量,后面eval的语句中无法得到。

  14. 雪候鸟 |

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

  15. ddouble |

    到处找zend_api的资料,想把php解释器嵌入到c程序中,google上怎么也找不到。为啥zend网站上也没呢,还是完全没摸到门儿?

    博主知道哪里有吗?zend api reference之类的文档,最好还有指南示例之类的文档。谢谢啦!

  16. fybird |

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

  17. 雪候鸟 |

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

  18. fybird |

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

  19. 雪候鸟 |

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

  20. Leric |

    连接时总是报一个错误:
    opcodes_dumper.cpp:(.text+0×910): 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做过什么东西,也不知道该咋解决,还望博主指点迷津

  21. jackywdx |

    呵呵,终于看懂了.

  22. Anonymous |

    看不懂..菜啊

  23. Anonymous |

    fxgh

  24. snowrui |

    学习消化中……

  25. 雪候鸟 |

    svn: http://code.google.com/p/opcodesdumper/source/browse/#svn/trunk
    放进去了

  26. surfchen |

    google code的svn里还没源代码呢

Leave a Reply

*