如何使用PHP Embed SAPI实现Opcodes查看器
PHP提供了一个EmbedSAPI,也就是说,PHP容许你在C/C++语言中调用PHP/ZE提供的函数。本文就通过基于EmbedSAPI实现一个PHP的opcodes查看器。
首先,下载PHP源码以供编译,我现在使用的是PHP5.3alpha2
进入源码目录:
./configure--enable-embed--with-config-file-scan-dir=/etc/php.d--with-mysql --with-config-file-path=/etc/
./make
./makeinstall
最后,记得要将生成的libphp5.so复制到运行时库的目录,我直接拷贝到了/lib/,否则会在运行你自己的embed程序的时候报错:
./embed:errorwhileloadingsharedlibraries:libphp5.so:cannotopensharedobjectfile:Nosuchfileordirectory
如果你对PHP的SAPI还不熟悉的话,我建议你看看我的这篇文章:深入理解ZendSAPIs(ZendSAPIInternals)
这个时候,你就可以在你的C代码中,嵌入PHP脚本解析器了,我的例子:
#include"sapi/embed/php_embed.h"
intmain(intargc,char*argv[]){
PHP_EMBED_START_BLOCK(argc,argv);
char*script="print'HelloWorld!';";
zend_eval_string(script,NULL,
"SimpleHelloWorldApp"TSRMLS_CC);
PHP_EMBED_END_BLOCK();
return0;
}
然后就是要指明includepath了,一个简单的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)-oembedembed.cpp$(CFLAGS)$(LDFLAGS)
编译成功以后,运行,我们可以看到,stdout输出HelloWorld!
基于这个,我们就可以很容易的实现一个类似于vld的Opcodesdumper:
首先我们定义opcode的转换函数(全部的opcodes可以查看Zend/zend_vm_opcodes.h);
char*opname(zend_ucharopcode){
switch(opcode){
caseZEND_NOP:return"ZEND_NOP";break;
caseZEND_ADD:return"ZEND_ADD";break;
caseZEND_SUB:return"ZEND_SUB";break;
caseZEND_MUL:return"ZEND_MUL";break;
caseZEND_DIV:return"ZEND_DIV";break;
caseZEND_MOD:return"ZEND_MOD";break;
caseZEND_SL:return"ZEND_SL";break;
caseZEND_SR:return"ZEND_SR";break;
caseZEND_CONCAT:return"ZEND_CONCAT";break;
caseZEND_BW_OR:return"ZEND_BW_OR";break;
caseZEND_BW_AND:return"ZEND_BW_AND";break;
caseZEND_BW_XOR:return"ZEND_BW_XOR";break;
caseZEND_BW_NOT:return"ZEND_BW_NOT";break;
/*...省略....*/
default:return"UNKNOW";break;
然后定义zval和znode的输出函数:
char*format_zval(zval*z)
{
staticcharbuffer[BUFFER_LEN];
intlen;
switch(z->type){
caseIS_NULL:
return"NULL";
caseIS_LONG:
caseIS_BOOL:
snprintf(buffer,BUFFER_LEN,"%d",z->value.lval);
returnbuffer;
caseIS_DOUBLE:
snprintf(buffer,BUFFER_LEN,"%f",z->value.dval);
returnbuffer;
caseIS_STRING:
snprintf(buffer,BUFFER_LEN,"\"%s\"",z->value.str.val);
returnbuffer;
caseIS_ARRAY:
caseIS_OBJECT:
caseIS_RESOURCE:
caseIS_CONSTANT:
caseIS_CONSTANT_ARRAY:
return"";
default:
return"unknown";
}
}
char*format_znode(znode*n){
staticcharbuffer[BUFFER_LEN];
switch(n->op_type){
caseIS_CONST:
returnformat_zval(&n->u.constant);
break;
caseIS_VAR:
snprintf(buffer,BUFFER_LEN,"$%d",n->u.var/sizeof(temp_variable));
returnbuffer;
break;
caseIS_TMP_VAR:
snprintf(buffer,BUFFER_LEN,"~%d",n->u.var/sizeof(temp_variable));
returnbuffer;
break;
default:
return"";
break;
}
}
然后定义op_array的输出函数:
voiddump_op(zend_op*op,intnum){
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));
}
voiddump_op_array(zend_op_array*op_array){
if(op_array){
inti;
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);
}
}
}
最后,就是程序的主函数了:
intmain(intargc,char**argv){
zend_op_array*op_array;
zend_file_handlefile_handle;
if(argc!=2){
printf("usage:op_dumper<script>\n");
return1;
}
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_INCLUDETSRMLS_CC);
if(!op_array){
printf("Errorparsingscript:%s\n",file_handle.filename);
return1;
}
dump_op_array(op_array);
PHP_EMBED_END_BLOCK();
return0;
}
编译,运行测试脚本(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
呵呵,怎么样,是不是很好玩呢?