PHP扩展开发实践

Posted by jabin on August 16, 2017

一、场景

鉴于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(中文翻译版)

五、参考资料

《PHP扩展开发与内核应用》 《深入理解PHP内核》