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脚本解析器了, 我的例子:
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
PHP;">
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);
PHP;">
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;
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,"%f",z->value.dval);
return buffer;
case IS_STRING:
snprintf(buffer,"\"%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,"$%d",n->u.var/sizeof(temp_variable));
return buffer;
break;
case IS_TMP_VAR:
snprintf(buffer,"~%d",n->u.var/sizeof(temp_variable));
return buffer;
break;
default:
return "";
break;
}
}
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);
}
}
}
最后,就是程序的主函数了:
PHP;">
int main(int argc,char **argv){
zend_op_array *op_array;
zend_file_handle file_handle;
if(argc != 2) {
printf("usage: op_dumper