Home  >  Article  >  Backend Development  >  windows上用c写php扩展(转)

windows上用c写php扩展(转)

WBOY
WBOYOriginal
2016-06-13 13:17:42912browse

windows下用c写php扩展(转)

windows下用c写php扩展(加密解密php源代码)


首先用hello world试手一下。
下载php源码包,ext目录就是扩展目录了,里面有2个重要的文件是ext_skel以及ext_skel_win32.php。
下载cygwin,有了这个就可以方便的在windows下创建php扩展了。
下载中……
下载完后用php ext_skel_win32.php --extname=hello来编译生成我们的扩展开发目录hello,然后开始写测试程序hello world。在hello.c文件里添加函数定义以及函数注册语句:
函数注册语句:

const zend_function_entry hello_functions[] = {
 PHP_FE(confirm_hello_compiled, NULL)  
    PHP_FE(sayHello,NULL)//这句是我们手动添加的
 {NULL, NULL, NULL} 
};

?

函数定义:

PHP_FUNCTION(sayHello){
 php_printf("Hello C extension");
}

?

好像说是一定要以PHP_FUNCTION出现的宏形式,因为如果直接裸写c代码可能会发生命名冲突或是其他的冲突。然后在php_hello.h里面添加函数声明语句:
PHP_FUNCTION(sayHello);
写好测试程序,编译结果出现:
../main/config.w32.h': No such file or directory
网上查了一下,好像是要下载额外的包。。。麻烦啊!
http://www.php.net/extra/bindlib_w32.zip
http://www.php.net/extra/win32build.zip
将这两个包放在一起,我把它们放在win32/build目录下,然后执行php源码包根目录下的buildconf.bat(最好在命令行运行,不然显示结果会一闪而过)。
然后把bison.exe(在刚下载的包里面)所在的目录设置为环境变量,再运行configure.bat。完后就生成/main/config.w32.h这个文件了。
然后再次编译刚才hello项目结果出现一大推错误。
形如:
..\..\main\streams/php_stream_transport.h(85) : error C2143: syntax error : missing ')' before '*'
..\..\main\streams/php_stream_transport.h(85) : error C2081: 'socklen_t' : name in formal parameter list illegal

网上说是由于找不到宏定义才会这样,那应该是socklen_t这个宏没定义了,但是它具体的宏定义应该是怎么样的呢,总不能随便写一个吧,所以打开
\main\streams/php_stream_transport.h
发现应该是个类型别名,而且是个int ,因为有socklen_t addrlen; addrlen 按字面应该是存储长度的值。
所以在这个文件中添加
typedef int socklen_t;
并保存,再编译刚才的项目,错误少了很多,但是还有7个,经检查发现是输入了中文符号。改正再编译。。。还有一个错误:
LINK : fatal error LNK1181: cannot open input file "php5ts.lib"
于是将寻找php5ts.lib这个文件并将它放到项目目录下或是VC6 lib文件默认搜索目录下也可以。找啊找啊找。。。
tmd,用windows搜索找了好久都没找到。百度是说在php二进制代码包里面。所以先下个同版本的二进制代码包(应该就是平时写php所必须下载的那个包吧)
这里先说下环境吧windows+vc6+php5.3.5(二进制代码包和源码包)+cygwin
下载完毕,找到,复制 ,编译,成功!
但是出现了一个很严重的问题,dll文件没出来,,,我哭!创建出来的是php_hello.exp和php_hello.lib。怎么会是静态的呢??
其实已经生成了,只是不在本目录下的Release_TS目录下,而是在ext上级的Release_TS目录下。
然后测试。哈哈,说是未定义函数,有没可能是测试的php版本和我扩展的php版本不一样的关系呢?
测试了一下也不是,只要在ini里一设置加载php_hello.dll重启apache就会出现内存不能读的错误。
总觉得代码没有问题,应该还是前面配置编译的时候有问题。
经过一番又一番的测试,发现时php二进制包下错了我下了vc9的应该下vc6的。

接下来要开始写加密和解密了。加密解密算法本身不是这里的重点,重点是如何在zend层用zend本身的接口结合c来编程,在zend编译源文件之前将文件解密(当然文件之前要是有加过密的)。
为了使用的方便。我的想法是像php_screw一样生成dll的同时,生成一个加密的可执行文件,这加密的可执行文件由我们手动执行,传入目录参数,能够对该目录下的所有文件进行加密。
在网上找了一堆资料后,再看看php_screw的代码,还有有些吃力,所以决定根据自己的思路来写。当然有些地方时会借鉴php_screw的代码
首先是先写一个对文件进行解密的函数。这个函数利用我们现成的解密算法对文件内容进行解密。
这个函数应该有一个参数用来接收当前请求文件的句柄(貌似是zend_compile_file这个东西,百度之,确认一下,几篇文章说是函数指针,我看了下源代码确实是函数指针)

extern ZEND_API zend_op_array *(*zend_compile_file)(zend_file_handle *file_handle, int type TSRMLS_DC);

?

那么要如何得到当前请求文件的文件指针呢?或许和zend_compile_file函数指针所指函数的作用有关。在源代码中发现了:
zend_compile_file = compile_file;
该函数的定义在zend_language_scanner.c,不过要看懂该函数有点难度,网上的说法是:
-------------
zend_compile_file负责将要执行的脚本文件编译成由ZE的基本指令序列构成的op codes 。
PHP执行这段代码会经过如下4个步骤:
1. Scanning (Lexing) ,将PHP代码转换为语言片段(Tokens)
2. Parsing , 将Tokens转换成简单而有意义的表达式
3. Compilation , 将表达式编译成Opocdes
4. Execution , 顺次执行Opcodes,每次一条,从而实现PHP脚本的功能。
-------------
所以我们应该要在这个四个步骤之前将文件解密。
想法是重写一个函数 myCompile 判断在文件被compile之前先将它解密,然后再调用默认的complie函数。定义好 myCompile 后,应该在请求初始化的时候将 myCompile 传递给函数指针zend_compile_file

PHP_MINIT_FUNCTION(dencrypt){
 old_comlie_file = zend_complie_file;//保留默认的compile,以便等一下调用
 zend_complie_file = myCompile;
 return SUCCESS;
}
ZEND_API zend_op_array *myCompile(zend_file_handle *file_handle,int type TSRMLS_DC){
  //这里的TSRMLS_DC是一个宏类似于,...(宏的定义暂时找不到)总之是跟多线程环境下全局变量的线程安全有关系的,以后再深究
  //这里是解密代码。。。
  old_comlie_file(file_handle);
  ....
}

?
但是问题还是没有解决,因为我们还是不知道如何获取到文件指针。我看php_screw里面的解密步骤挺长的,参考之。。。找到了:

fp = fopen(file_handle->filename, "r");

?原来file_handle里面有文件名的信息(其实如果找到file_handle的结构体定义语句也就知道了)。但是php_screw里面还有这么一段:

char fname[32];
memset(fname, 0, sizeof fname);
if (zend_is_executing(TSRMLS_C)) {
  //TSRMLS_C获得全局变量
  
  //获取当前调用函数的名字(当前调用函数?哪里的函数?php函数?想想当然是zend里面的函数。而不是php层的函数,因为现在都还没编译,更没执行呢)
  if (get_active_function_name(TSRMLS_C)) {
    strncpy(fname, get_active_function_name(TSRMLS_C), sizeof fname - 2);
  }
}
if (fname[0]) {
  if ( strcasecmp(fname, "show_source") == 0  //也就是说如果当前是这两个函数则不解密也不编译了。恩,看样子没错。
    || strcasecmp(fname, "highlight_file") == 0) {
   return NULL;
  }
}

?

所以这段还是必要的,不然遇到以上两个函数就没法实现了,compile_file函数里面应该也有这步才对。所以这里主要是不要让它被解密,而是直接显示密文。
还有这么一段:

fp = fopen(file_handle->filename, "r");
if (!fp) { //如果打开失败则直接调用默认compile函数
  return org_compile_file(file_handle, type);
}
fread(buf, PM9SCREW_LEN, 1, fp); //一下5句的作用是:如果发现时未加密的文件则不进行解密。
if (memcmp(buf, PM9SCREW, PM9SCREW_LEN) != 0) {
  fclose(fp);
  return org_compile_file(file_handle, type);
}
if (file_handle->type == ZEND_HANDLE_FP) fclose(file_handle->handle.fp); //判断文件句柄类型,应用相应的关闭函数。
if (file_handle->type == ZEND_HANDLE_FD) close(file_handle->handle.fd);
file_handle->handle.fp = pm9screw_ext_fopen(fp);                         //调用解密文件的函数,并用file_handle里面的fp接收函数返回结果
file_handle->type = ZEND_HANDLE_FP;                                      //将句柄类型设置为文件指针类型

file_handle->opened_path = expand_filepath(file_handle->filename, NULL TSRMLS_CC);   

?

上面这句有点不懂,猜想是不是接受当前文件的路径呢?一路追踪找到了expand_filepath_ex这个函数这个函数的最后一句是return real_path;看样子应该就是返回当前要编译文件的路径吧。但是为什么要有上面那两步呢?就是设置类型和路径这两部。如果我们不做解密操作就不用这两步,我的猜想是因为fclose(file_handle->handle.fp)这里改变了file_handle的状态,所以才需要重新设置吧!(但是有一个疑问是,这样做是Compilation之前解密,还是在Scanning之前解密?我初步认为从语义上来讲是在Compilation之前解密,但是从实际情况来看应该是要在Scanning之前解密,这边后期可以验证,事实证明后者是对的,因为compile_file函数里有对open_file_for_scanning函数的调用,也就是说在compile_file函数里执行了前面所说的123步)。
入口点有了(还有4分之3的问题需要解决)。

?

接下来开始写解密文件的函数。
需要考虑的问题有,解密文件后的明文代码,并不需要写入文件,那么如何取得这些明文的文件指针呢?用临时文件来做?但是如果每个请求都用不同的临时文件那会生成很多临时文件(显然不可行),如果对同一个php文件的不同请求只用一个临时文件,也会出现资源等待的问题(显然也不可行)。能不能直接在内存中指定一个文件指针呢?先看看php_screw的做法,调用tmpfile()产生临时文件,但是在程序退出时便会自动删除!其实我认为的在内存中的文件也是这样实现的。
直接下来把之前用php写的可逆加解密算法用c来实现,可是c并没有现成了md5和base64函数,看来只能抛弃这2个了。
写好后就开始编译了,遇到了一些错误也都改过来了。
然后链接的时候出现了错误:
error LNK2001: unresolved external symbol _zend_compile_file
搜索了一下,貌似是因为函数编译方式的不一样所导致的找不到函数,加入:

BEGIN_EXTERN_C()
ZEND_API zend_op_array *(*zend_compile_file)(zend_file_handle *file_handle, int type TSRMLS_DC);
END_EXTERN_C()

?

即可。
生成dll文件成功,但是不知道如何生成用于加密的可执行文件,当然最简单的解决方法就是另外建一个项目。看来只好先这样了,因为对vc的一些编译机制还有参数设置也不是很熟悉(以后再研究程序员的自我修炼好像有讲这个的)。
之后又遇到c的指针传递问题(很久没弄,一些基础的都忘了,最后用二级指针来解决指针作为参数回传的问题)。
又遇到了链接时找不到_zend_compile_file符号的问题(跟zend_api宏有关dllimport\dllexport)
然后又遇到读写文件的问题,其实没错,只是我把写入文件的密文手动复制内容到另一个文件中去测试,结果会出错,如果直接复制文件,或直接操作该原始文件则正确。
最后又遇到了内存不能读的错误。。。
最后是发现是由于文件读取方式的问题,以二进制读取就可以了,如果以文本方式读写,会将某些特殊字符进行处理。
...最后测试功能是可以了,不过如果真的要应用的话要改进一下,比如我加密的时候是在原来目录下直接生成加密后的文件,更好的做法应该是要把原来的php文件打包起来,方便管理。
接下来开始搞zf框架了,都快忘光了,而且当初还有还多东西没用到的。

?

?

===============================

原文地址:

http://blog.sina.com.cn/s/blog_4d06da1f0100pgmj.html

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn