msgbartop
PHP源码分析,Zend引擎分析,Web相关技术研究,Web技术分享–左手代码 右手诗
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: , , ,

Reader's Comments

  1. |

    google code的svn里还没源代码呢

  2. |

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

  3. |

    学习消化中……

  4. |

    fxgh

  5. |

    看不懂..菜啊

  6. |

    呵呵,终于看懂了.

  7. |

    连接时总是报一个错误:
    opcodes_dumper.cpp:(.text+0×910): undefined reference to `php_embed_init’
    opcodes_dumper.cpp:(.text+0×9e9): undefined reference to `php_embed_shutdown’
    collect2: ld returned 1 exit status
    make: *** [ALL] Error 1

    没用C做过什么东西,也不知道该咋解决,还望博主指点迷津

  8. |

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

  9. |

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

  10. |

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

  11. |

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

  12. |

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

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

  13. |

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

  14. |

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

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

  15. |

    1. 编译的时候确定

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

  16. |

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

  17. |

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

  18. |

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

Leave a Comment

*
To prove you're a person (not a spam script), type the security word shown in the picture. Click on the picture to hear an audio file of the word.
Click to hear an audio file of the anti-spam word