如何使用PHP Embed SAPI实现Opcodes查看器

前端技术 2023/09/07 PHP

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):

深入理解PHP原理之Opcodes):

Script: sample.php

opnum   line                         opcode                                      op1                                      op2                                   result
    0      2                      ZEND_ECHO                               \"laruence\"
    1      4                    ZEND_RETURN                                        1

呵呵,怎么样,是不是很好玩呢?

本文地址:https://www.stayed.cn/item/18603

转载请注明出处。

本站部分内容来源于网络,如侵犯到您的权益,请 联系我

我的博客

人生若只如初见,何事秋风悲画扇。