一、场景
鉴于php扩展开发的复杂性高、可维护性较低,并且开发周期较长(每次的改动都涉及编译、部署、重启进程等),一般能用脚本解决的不轻易进行扩展的开发。 但是以下两种场景,则只能用扩展来实现。
1. 用c/c++的来实现某些对性能和效率要求比较高的功能
比如:涉及内存拷贝,并且频繁调用的函数,可以通过扩展来实现对内存空间的提前分配,提升php脚本的执行效率
2. 引入第三方的类库
现有的第三方静态库,我们可以直接移植到PHP,而无需对其进行重写。 比如一些加解密库,以及后面实践章节要提到的大厅CGI的ServiceApi的封装,特别是一些无法获取到源码的库
二、php内核的基本原理简介( PHP版本:5.x)
1. PHP的架构图
从图中,我们可以看出PHP扩展的开发主要是在上层的应用脚本和底层的ZEND引擎之间
2. 生命周期
在PHP的执行过程中,有四个重要的函数,标志这PHP的启动与终止。如下表: 函数名|调用时机
- |-
PHP_MINIT_FUNCTION|在apache/nginx启动时调用
PHP_RINIT_FUNCTION|每个请求来时调用
PHP_RSHUTDOWN_FUNCTION|请求结束时调用
PHP_MSHUTDOWN_FUNCTION|apache/nginx结束时调用
1)client 启动的生命周期:
2)多进程(如:apache的pre-fork模式启动)
3)多线程(如:apache的MPM模式启动)
3. 语法要点
1)变量和类型
a.存储变量的结构体: ``` a. struct _zval_struct { zvalue_value value; /* 变量的值 / zend_uint refcount__gc; zend_uchar type; / 变量当前的数据类型 */ zend_uchar is_ref__gc; };
b.变量的类型判断
//IS_NULL, IS_BOOL, IS_STRING, IS_LONG, IS_DOUBLE, //IS_ARRAY, IS_OBJECT, IS_RESOURSE //顾名思义对应 if (Z_TYPE_P(zstr) != IS_STRING) { php_printf(“这个变量不是字符串!\n”); return; } }
c.变量的定义
MAKE_STD_ZVAL(pzv); //分配一块内存空间
ZVAL_TRUE(pzv);
ZVAL_LONG(pzv, l);
ZVAL_STRINGL(pzv, str, len, dup); //dup=1, 复制; dup=0 不复制
ZVAL_STRING(pzv, str, dup);
d.变量的存储方式
所有的变量都是存储在用HashTable实现的符号表中。 其中全局变量存储在symbol_table, 局部变量存储在active_symbol_table
e.类型的强制转换
`convert_to_[type](val)`,如`convert_to_string(val)`
### 2)函数
a.函数返回值
- 直接返回
#define RETURN_RESOURCE(l) { RETVAL_RESOURCE(l); return; } #define RETURN_BOOL(b) { RETVAL_BOOL(b); return; } #define RETURN_NULL() { RETVAL_NULL(); return;} #define RETURN_LONG(l) { RETVAL_LONG(l); return; } #define RETURN_DOUBLE(d) { RETVAL_DOUBLE(d); return; } #define RETURN_STRING(s, duplicate) { RETVAL_STRING(s, duplicate); return; } #define RETURN_STRINGL(s, l, duplicate) { RETVAL_STRINGL(s, l, duplicate); return; } #define RETURN_EMPTY_STRING() { RETVAL_EMPTY_STRING(); return; } #define RETURN_ZVAL(zv, copy, dtor) { RETVAL_ZVAL(zv, copy, dtor); return; } #define RETURN_FALSE { RETVAL_FALSE; return; } #define RETURN_TRUE { RETVAL_TRUE; return; }
- 引用传递
//运行时的引用传递 ZEND_FUNCTION(byref_calltime) { zval *a;
//我们我接收的参数传给zval *a;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &a) == FAILURE)
{
RETURN_NULL();
}
//如果a不是以应用的方式传递的。
if (!a->is_ref__gc)
{
return;
}
//将a转成字符串
convert_to_string(a);
//更改数据
ZVAL_STRING(a," (modified by ref!)",1);
return; } //编译时引用传递 略 ``` b. 函数的参数
- zend_parse_parameters ``` //只需关注格式化字符串。 //foo为传递进来的第一个参数, name为传进来的的第二个参数,foo2为传进来的第三个可选参数 long foo; char *name; bool foo2; int name_len; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,”ls|b”, &foo, &name, &name_len, foo2) == FAILURE) { RETURN_NULL(); }
格式化的字符串中,常用的符号见下表
符号|含义
-|-
b| Boolean
l |Integer 整型
d| Floating point 浮点型
s |String 字符串
r |Resource 资源
a |Array 数组
o |Object instance 对象
O |Object instance of a specified type 特定类型的对象
z| Non-specific zval 任意类型~
Z |zval**类型
- 通过arg info的方式来实现类型绑定的功能(不常用)
### 3)HashTable和Array
a. HashTable
为了操作哈希表,php内核提供了如下表的接口:
操作|函数
-|-
创建|zend_hash_init
增改| zend_hash_add, zend_hash_update, zend_hash_index_update, zen_hash_next_index_insert. zend_hash_next_free_element
查找|zend_hash_find, zend_hash_index_find
拷贝&合并|zend_hash_copy, zend_hash_merge
遍历|zend_hash_apply, zend_hash_apply_with_arguments
详细的定义,参照相关资料
b.数组
PHP_FUNCTION(sample_array) { zval subarray; /创建数组/ array_init(return_value); /添加元素/ add_assoc_long(return_value, “life”, 42); add_index_bool(return_value, 123, 1); add_next_index_double(return_value, 3.1415926535); add_next_index_string(return_value, “Foo”, 1); / 创建数组 / MAKE_STD_ZVAL(subarray); array_init(subarray); / 继续添加元素 / add_next_index_long(subarray, 1); add_next_index_long(subarray, 20); add_next_index_long(subarray, 300); / 二维数组 */ add_index_zval(return_value, 444, subarray); }
### 4)资源类型
PHP内核的资源类型,包括文件句柄,mysql连接等, 示例参考实践部分的[用扩展实现一个类](#toc21)
a.创建
`ZEND_REGISTER_RESOURCE`
c.销毁
`zend_register_list_destructors_ex`
需要在ZEND_MINIT_FUNCTION定义销毁函数,如果需要主动销毁申请的资源,则需要先fetch到资源,然后调用zend_hash_index_del
d.资源的获取
`ZEND_FETCH_RESOURCE`
e.持久资源
(i).持久资源在脚本执行结束后不会销毁,可以在每个请求中直接使用
(ii).非持久资源存在:EG(regular_list), 持久资源存在EG(persistent_list),均是哈希表实现
(iii).在创建资源的时候,我们可以用`ZEND_REGISTER_RESOURCE`在EG(regular_list)存一份,同时用`zend_hash_update`在EG(persistent_list)也存一份。 这样在下一次(创建)获取资源的时候,如果存在持久资源则无需再创建,直接拿即可
(iiii)确保取出来的持久资源是有效的, 无效,则从EG(persisten_list)剔除
### 5)面向对象(类和对象)
a. 类的结构体
`zend_class_entry`
b. 属性
操作|函数
-|-
声明|zend_declare_property_*
获取|zend_read_property/zend_read_static_property
更新/赋值|zend_update_property | zend_update_static_property
c.方法
//首先,定义这个函数的C语言部分,不过这一次我们使用的是ZEND_METHOD ZEND_METHOD( myclass , public_method ) { php_printf(“我是public类型的方法\n”); }
ZEND_METHOD( myclass , __construct ) { php_printf(“我是__construct方法\n”); }
//然后,定义一个zend_function_entry zend_function_entry myclass_method[]= { ZEND_ME(myclass, public_method, NULL, ZEND_ACC_PUBLIC) ZEND_ME(myclass, __construct, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR) {NULL, NULL, NULL} }
//最后,在MINIT阶段register internal class的时候将它作为一个参数传递进去 ZEND_MINIT_FUNCTION(test) { zend_class_entry ce;
//这里使用了myclass_method这个zend_function_entry
INIT_CLASS_ENTRY(ce,"myclass",myclass_method);
//向内核注册创建的类
myclass_ce = zend_register_internal_class(&ce TSRMLS_CC);
return SUCCESS; } ``` d.接口和继承 ``` //三个zend_class_entry zend_class_entry *i_myinterface_ce,*parent_class_ce,*myclass_ce;
//parent_class的hello方法 ZEND_METHOD(parent_class,hello) { php_printf(“hello world!\n”); }
//myclass的call_hello方法 ZEND_METHOD(myclass,call_hello) { //这里涉及到如何调用对象的方法,详细内容下一章叙述 zval *this_zval; this_zval = getThis(); zend_call_method_with_0_params(&this_zval,myclass_ce,NULL,”hello”,NULL); }
//各自的zend_function_entry static zend_function_entry i_myinterface_method[]={ ZEND_ABSTRACT_ME(i_myinterface, hello, NULL) {NULL,NULL,NULL} };
static zend_function_entry parent_class_method[]={ ZEND_ME(parent_class,hello,NULL,ZEND_ACC_PUBLIC) {NULL,NULL,NULL} };
static zend_function_entry myclass_method[]={ ZEND_ME(myclass,call_hello,NULL,ZEND_ACC_PUBLIC) {NULL,NULL,NULL} };
ZEND_MINIT_FUNCTION(test) { zend_class_entry ce,p_ce,i_ce; INIT_CLASS_ENTRY(i_ce,”i_myinterface”,i_myinterface_method); i_myinterface_ce = zend_register_internal_interface(&i_ce TSRMLS_CC);
//定义父类,最后使用zend_class_implements函数声明它实现的接口
INIT_CLASS_ENTRY(p_ce,"parent_class",parent_class_method);
parent_class_ce = zend_register_internal_class(&p_ce TSRMLS_CC);
zend_class_implements(parent_class_ce TSRMLS_CC,1,i_myinterface_ce);
//定义子类,使用zend_register_internal_class_ex函数
INIT_CLASS_ENTRY(ce,"myclass",myclass_method);
myclass_ce = zend_register_internal_class_ex(&ce,parent_class_ce,"parent_class" TSRMLS_CC);
//注意:ZEND_ACC_FINAL是用来修饰方法的,而ZEND_ACC_FINAL_CLASS是用来修饰类的
myclass_ce->ce_flags |= ZEND_ACC_FINAL_CLASS;
return SUCCESS; } ```
6)内存管理
C语言原生函数 |PHP内核封装后的函数
- |-
void *malloc(size_t count);| void *emalloc(size_t count);
void *pemalloc(size_t count, char persistent); void *calloc(size_t count);| void *ecalloc(size_t count);
void *pecalloc(size_t count, char persistent); void *realloc(void *ptr, size_t count); |void *erealloc(void *ptr, size_t count);
void *perealloc(void *ptr, size_t count, char persistent); void *strdup(void *ptr); |void *estrdup(void *ptr);
void *pestrdup(void *ptr, char persistent); void free(void *ptr); |void efree(void *ptr);
void pefree(void *ptr, char persistent);4. 编码规范
具体可参考
path_to_php_source_code/CODING_STANDARDS
, 这里罗列了我们可能比较关心的几条 a. 函数/变量命名:小写,下划线分割 b.类的方法名:驼峰 c.类名:单词均大写,包括首字母 d.C风格的注释
三、实践
1. PHP源码编译
cd /path/to/php_source_code
./configure --prefix=/home/jabinhuang/php/ --enable-debug --enable-maintainer-zts
make
make test
make install
cp php.ini-development /home/jabinhuang/php/lib/php.ini
2. PHP扩展编译和部署
cd /path/to/php扩展目录
/home/jabinhuang/php/bin/phpize (得到configure文件)
./configure --with-php-config=/home/jabinhuang/php/bin/php-config 对应php版本的配置文件,获取Makefile文件
make && make install
若自定义了扩展目录,则拷贝so文件,到对应的扩展目录
修改/home/jabinhuang/php/lib/php.ini, 增加extension=[extension_name].so
重启php kill -USR2 [master php PID]
3. 第一个扩展–“hello world!”
这里我们使用php源码里的ext_skel
工具帮我们生成一个扩展的模板
cd /path_to_php_sourcecode_dir/ext
./ext_skel --extname=hello
这样我们在ext目录下得到一个名为hello的扩展开发目录
.
|-- CREDITS
|-- EXPERIMENTAL
|-- config.m4 <-------!非常重要! 指导phpize生成configure文件和Makefile等
|-- config.w32
|-- hello.c <------!非常重要! 扩展的源文件
|-- hello.php <------ 对扩展进行简单的测试
|-- php_hello.h <-----!非常重要! 扩展的头文件
|-- tests <----- 扩展的测试(分阶段),可自行查看001.phpt文件内容
| |-- 001.phpt
这里,我们需要修改这三个非常重要的文件,来定制我们自己的第一个hello world扩展。
1)./config.m4
配置的详细说明参见config.m4。 我们现在只需要做最简单配置。
./config.m4
PHP_ARG_ENABLE(hola, whether to enable hola support,
Make sure that the comment is aligned:
[ --enable-hola Enable hola support])
if test "$PHP_HOLA" != "no"; then
dnl 支持c++
PHP_ADD_LIBRARY(stdc++, 1, HOLA_SHARED_LIBADD)
dnl 让Makefile使用g++编译
PHP_REQUIRE_CXX()
dnl 标准autoconf的AC_SUBST()宏的php修改版, 它在将扩展构建为共享模块时需要
PHP_SUBST(HOLA_SHARED_LIBADD)
dnl 声明扩展的名称、需要的源文件名、扩展的编译形式
PHP_NEW_EXTENSION(hola, hello.cpp, $ext_shared)
fi
2)./php_hello.h
这里我们只需要增加一个函数的声明
...
PHP_FUNCTION(hello_echo_hello);
...
3)./hello.cpp
为了使用c++,我们需要把hello.c
改为./hello.cpp
./hello.cpp
...
const zend_function_entry hello_functions[] = {
PHP_FE(confirm_hola_compiled, NULL) /* For testing, remove later. */
PHP_FE(hello_echo_hello, NULL) /*将函数指针注册到ZEND内核*/
PHP_FE_END /* Must be the last line in hola_functions[] */
};
...
PHP_FUNCTION(hello_echo_hello)
{
char *hello_str = NULL;
int hola_str_len, len;
char *echo_str;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &hello_str, &hello_str_Len) == FAILURE) /*获取参数*/
{
RETURN_NULL();
}
len = spprintf(&echo_str, 0, "%s", hello_str);
RETURN_STRINGL(echo_str, len, 0); /*将参数返回至调用函数*/
}
接下来,我们按照 PHP扩展编译和部署 来编译我们我们的扩展。
./test_hello.php
<?php
echo hello_echo_hello("hello world");
//输出"hello world"
4. 引入第三方库
这里我们以引入大厅CGI的ServiceApi—- OpenAuth为例。
1)OpenAuth介绍
OpenAuthSvr
实现的功能是去oidb校验openid/openkey(accessToken)以及openid、uin之间的转换。
CGI侧的封装的ServiceApi, 则提供了CGI去请求OpenAuthSvr
的相关接口调用。
由于篇幅限制,我们仅实现VerifyAccessToken
, c++定义如下:
//支持传入openid, openkey, appid, 校验合法性,并通过GetUin 函数获取对应的用户uin
int VerifyAccessToken(TOpenID stOpenID, unsigned short ushTokenLen, const unsigned char* szToken, unsigned int uiGAID, int iTokenType);
iunsigned int GetUin() { return m_uiUin; }
2)编译libOpenAuthSvc.a
cd path_to_CgiLib/ServiceApi/OpenAuthSvc
cp -rf ../../CgiFrameLib/* ./ <---这里编译ServiceApi的时候需要拷贝相关的库文件,否则编译出来的静态库是不完整的
... <---这里省略万字, c++编译不是本篇讨论的重点
make
3)修改./config.m4文件
PHP_ARG_ENABLE(openauth, whether to enable openauth support,
Make sure that the comment is aligned:
[ --enable-openauth Enable openauth support])
if test "$PHP_OPENAUTH" != "no"; then
PHP_ADD_INCLUDE(./lib) <---头文件等的包含路径
PHP_ADD_LIBRARY(stdc++, 1, OPENAUTH_SHARED_LIBADD)
PHP_ADD_LIBRARY_WITH_PATH(OpenAuthSvc, ./lib/, OPENAUTH_SHARED_LIBADD) <---- 引入libOpenAuthSvc.a 静态库
PHP_ADD_LIBRARY_WITH_PATH(LogAdapter, ./lib/, OPENAUTH_SHARED_LIBADD) <----封装的基于syslog的日志静态库 其它
PHP_REQUIRE_CXX()
PHP_SUBST(OPENAUTH_SHARED_LIBADD)
PHP_NEW_EXTENSION(openauth, openauth.cpp functions.cpp, $ext_shared) <---functions.cpp 定义用c++实现的一些通用函数
fi
4)修改php_openauth.hpp文件
...
#include "OpenAuthService.hpp" <---包含相关头文件
#include "functions.h"
#include "LogAdapter.hpp"
using namespace QQGameCgiFrame; <---命名空间声明
...
PHP_FUNCTION(openauth_VerifyAccessToken); <---定义openauthAPI静态库的包裹函数
PHP_FUNCTION(openauth_GetErrmsg);
PHP_FUNCTION(openauth_GetResultID);
PHP_FUNCTION(openauth_SetLoginSession);
PHP_FUNCTION(openauth_GetUin);
PHP_FUNCTION(openauth_SetClientData);
ZEND_BEGIN_MODULE_GLOBALS(openauth)
COpenAuthService cOpenAuthService; <----定义COpenAuthService类对象的全局变量
ZEND_END_MODULE_GLOBALS(openauth)
...
5)修改openauth.cpp文件
...
ZEND_DECLARE_MODULE_GLOBALS(openauth) <----声明全局变量
...
const zend_function_entry openauth_functions[] = {
PHP_FE(openauth_VerifyAccessToken, NULL)
PHP_FE(openauth_GetUin, NULL)
PHP_FE(openauth_GetResultID, NULL)
PHP_FE(openauth_SetLoginSession, NULL)
PHP_FE(openauth_SetClientData, NULL)
PHP_FE_END /* Must be the last line in openauth_functions[] */
};
...
PHP_MINIT_FUNCTION(openauth)
{
COpenAuthService cOpenAuthService;
OPENAUTH_G(cOpenAuthService) = cOpenAuthService; <---php启动时初始化
return SUCCESS;
}
...
PHP_FUNCTION(openauth_GetUin)
{
RETURN_LONG(OPENAUTH_G(cOpenAuthService).GetUin()); <----调用静态库的GetUin函数, 并返回
}
PHP_FUNCTION(openauth_VerifyAccessToken)
{
char *openid = NULL;
char *openkey = NULL;
int openid_len, openkey_len;
long appid;
char *strg;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ssl",&openid, &openid_len, &openkey, &openkey_len, &appid) == FAILURE)
{
return;
}
TOpenID stOpenID;
memset(&stOpenID, 0, sizeof(stOpenID));
stOpenID.m_ushOpenIDLen = openidLen;
memcpy(stOpenID.m_szOpenID, openid, stOpenID.m_ushOpenIDLen);
char m_szStrToken[MAX_OPEN_KEY_LEN];
memset(m_szStrToken, 0, sizeof(m_szStrToken));
memcpy(m_szStrToken, openkey, openkeyLen);
int iRet = OPENAUTH_G(cOpenAuthService).VerifyAccessToken(stOpenID, strlen(m_szStrToken),(const unsigned char *) m_szStrToken, appid, 3); <---调用静态库的VerifyAccessToken,完成验证和账号转换
RETURN_LONG(iRet);
}
...
6)编译,部署,测试
make && make install
./test.php
$ret = openauth_VerifyAccessToken("E27211DF7C51A27ED3736A84B48FC2C2", "F3AD5EF58CD9DAE5254A0BD63F76995F", "1106095840");
echo $ret
echo "\n";
echo openauth_GetResultID();
echo "\n";
echo openauth_GetUin();
输出结果:
[root@TENCENT64_site /data/jabinhuang/openauth/tests]#/data/apps/php55/bin/php test.php
0
0
972292221
5. 自定义c++函数和日志静态库测试
config.m4、php_openauth.h文件和上一小节(引入第三方库)一致
1)编译libLogAdapter.a
基于qqgame_CgiLib的LogAdapter.cpp
./Makefile
all:
gcc -g -std=c++0x -c -fpic LogAdapter.cpp -I .
ar -r libLogAdapter.a LogAdapter.o
clean:
rm -f *.o
rm -f libLogAdapter.a
2)functions.h与functions.cpp
与扩展源文件放在同级目录
./functions.h
#ifndef PHPEXT_FUNTIONS_H
#define PHPEXT_FUNTIONS_H
#include <string>
#include <cstring>
using namespace std;
int HashStringToUin(const string& rstKey); <---实现字符串到uin的转换
#endif //PHPEXT_FUNTIONS_H
./functions.cpp
#include "functions.h"
int HashStringToUin(const string& rstKey)
{
unsigned int uiKeyLength = rstKey.size();
const char* pszStrKey = rstKey.c_str();
unsigned int uiHashSum = 10000;
unsigned int i;
for( i = 0; i < uiKeyLength / sizeof(unsigned int); ++i)
{
unsigned int uiTmp = 0;
memcpy(&uiTmp, (pszStrKey) + sizeof(uiTmp)*i, sizeof(uiTmp));
uiHashSum += uiTmp;
}
if(uiKeyLength % sizeof(unsigned int) > 0)
{
unsigned char* pByte = (unsigned char*)pszStrKey;
pByte += (uiKeyLength - (uiKeyLength % sizeof(unsigned int)));
unsigned int uiTemp = 0;
memcpy((void *)&uiTemp, (const void *)pByte, uiKeyLength%sizeof(unsigned int));
uiHashSum += uiTemp;
}
uiHashSum = (uiHashSum & ((unsigned int)0x7fffffff));
return uiHashSum;
}
3)增加函数openauth_log_test
PHP_FUNCTION(openauth_log_test)
{
long tmp_uin = HashStringToUin("helloworld!!!");
LOGERR("HashStringToUin Result:%u", tmp_uin);
}
4)rsyslog配置
...
$template LocalPhpext, "/data/jabinhuang/phpext.log"
if $programname startswith 'PHPEXT' then ?LocalPhpext
...
5)测试结果
[root@TENCENT64_site ~]#vi /data/jabinhuang/phpext.log
2017-08-15 02:59:26|127.0.0.1|php|ERR|/data/jabinhuang/openauth/openauth.cpp:zif_openauth_log_test:239>hello php extension!
2017-08-15 02:59:26|127.0.0.1|php|ERR|/data/jabinhuang/openauth/openauth.cpp:zif_openauth_log_test:241>HashStringToUin Result:2147313780
6. PHP扩展的调试
首先在编译php时,要开启--enable-debug
参数; 其次在config.m4
文件加入
./config.m4
if test -z "$PHP_DEBUG"; then
AC_ARG_ENABLE(debug,
[ --enable-debug compile with debugging symbols],[
PHP_DEBUG=$enableval
],[ PHP_DEBUG=no
])
fi
调试方式:
[jabinhuang@SWEBMYVMM001381 ~/phpext/hola/modules] $ nm hola.so |grep hola_echo <---查看要断点的函数对应的符号
0000000000000c2e T _Z13zif_hola_echoiP12_zval_structPS0_S0_iPPPv
[jabinhuang@SWEBMYVMM001381 ~/phpext/hola/modules] $ c++filt _Z13zif_hola_echoiP12_zval_structPS0_S0_iPPPv <---解析出真正的函数
zif_hola_echo(int, _zval_struct*, _zval_struct**, _zval_struct*, int, void***)
[jabinhuang@SWEBMYVMM001381 ~/phpext/hola/modules] $ gdb php
...
gdb) b zif_hola_echo <----在指定的函数打断点
Function "zif_hola_echo" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (zif_hola_echo) pending.
(gdb) run -q /home/jabinhuang/phpext/hola/tests/hola.php
...
Breakpoint 1, zif_hola_echo (ht=1, return_value=0x7ffff7ecb148, return_value_ptr=0x0, this_ptr=0x0, return_value_used=1, tsrm_ls=0x1084d90) at /home/jabinhuang/phpext/hola/hola.cpp:173
173 char *holaStr = NULL;
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.149.tl1.5.x86_64 libgcc-4.4.6-4.tl1.x86_64 libstdc++-4.4.6-4.tl1.x86_64 libxml2-2.7.6-4.el6_2.4.x86_64 nss-softokn-freebl-3.12.9-11.el6.x86_64 zlib-1.2.3-27.el6.x86_64
(gdb) bt <---函数调用的堆栈信息
#0 zif_hola_echo (ht=1, return_value=0x7ffff7ecb148, return_value_ptr=0x0, this_ptr=0x0, return_value_used=1, tsrm_ls=0x1084d90) at /home/jabinhuang/phpext/hola/hola.cpp:173
#1 0x00000000008f5c1f in zend_do_fcall_common_helper_SPEC (execute_data=0x7ffff7e95118, tsrm_ls=0x1084d90) at /home/jabinhuang/source_code/php-5.5.38/Zend/zend_vm_execute.h:550
#2 0x00000000008fb6c9 in ZEND_DO_FCALL_SPEC_CONST_HANDLER (execute_data=0x7ffff7e95118, tsrm_ls=0x1084d90) at /home/jabinhuang/source_code/php-5.5.38/Zend/zend_vm_execute.h:2336
...
7.内存泄露检测
鉴于PHP扩展可以直接对内存进行操作,那么内存的泄露检测是常见的场景。 我们可以使用valgrind
, 官网及源码下载
使用示例:valgrind --leak-check=full php leak.php
四、PHP7的扩展开发
Upgrading PHP extensions from PHP5 to NG(中文翻译版)