存档

2015年8月 的存档

php扩展开发之GDB调试

2015年8月19日 没有评论

俗话说的好"磨刀不误砍柴工",我们在开发扩展的时候,会像写C语言代码一样,经常会遇到程序core掉了.然而PHP脚本中常用的调试方式"echo,print_r,var_dump"却不能给我们什么大的帮助.今天我们就看看如何利用GDB来调试我们的php扩展程序?

为了方便我们使用gdb调试,最好在编译php程序的时候,把configure工具的”–enable-debug”参数启用,默认它是禁用的,因为它会产生很多方便我们调试的信息(注意在生产环境不要启用它,它会降低一部分php的性能).今天会主要说一下利用gdb进行断点调试,利用core文件调试已经不少的教程,所以我就不想再多做这些重复工作了.

前面的文章大家都看到如果声明一个php扩展函数,例如PHP_FUNCTION(count),这里如果我们直接利用cout函数来做断点,可能我们捕捉不到(这里我用了count做例子,但大家不要自己扩展里面这么做,因为count已经是php内建的扩展函数了,这样会引起不必要的问题).因为zend为了避免我们写的函数和C系统函数或这其他类库重名所以这里使用了PHP_FUNCTION宏进行声明,那么这个宏是什么样子哪?


#define PHP_FUNCTION ZEND_FUNCTION

#define ZEND_FUNCTION(name) ZEND_NAMED_FUNCTION( ZEND_FN(name) )

#define ZEND_FN(name) zif_##name

#define ZEND_NAMED_FUNCTION(name) void name(INTERNAL_FUNCTION_PARAMETERS)

上面我们提到的提到的”PHP_FUNCTION(count)“最终的展开结果是”void zif_count(int ht, zval *return_value, zval **return_value_ptr,zval *this_ptr, int return_value_used TSRMLS_DC)“,因此如果我们直接断点count可能就不会有结果的,这里我们需要用zif_count来断点就会捕捉到了.

如果我们使用的是一个第三方的扩展,我们不是很清楚里面有那些函数,或者修改过函数的前缀,已经不再是”zif”了.那么我们怎么获得我们需要的函数信息那?

我们可以使用nm工具来查看动态链接库里面的函数信息,就那我们上一篇那个交换2个数字的函数扩展为例子,看看如果使用nm吧.


nm king.so

//下面信息为nm摘取的部分信息

0000000000000e80 T zif_confirm_king_compiled
0000000000000f10 T zif_king_author
0000000000000fd0 T zif_king_call_system_func
0000000000000f30 T zif_king_exchange_number
0000000000000e60 T zm_activate_king
0000000000000e70 T zm_deactivate_king
0000000000001190 T zm_info_king
0000000000000e50 T zm_shutdown_king
0000000000000e40 T zm_startup_king

具体的信息如下图:

nm

 

看一下我们的扩展是否加入到php解释器中去那


php -m|grep king

//king

如果看到了”king”说明我们的扩展已经ok了,现在就做开始做gdb调试吧


cd /home/king/web_project/test/ext_king

gdb php

b zif_king_exchange_number

//Make breakpoint pending on future shared library load? (y or [n])

//这里用于这动态链接中,所以在我们php中是找不到这符号的,所以点y即可

//运行我们的php测试脚本

run ./exchange.php

//后面就是正常的gdb调试了

如此我们就可以清楚看到程序是怎么执行,更方便我们开发扩展程序.

如果我们有php程序core掉的文件,那么我们可以利用core文件进行调试.


gdb php -c core

#后面的可以和正常的gdb调试core文件一样,不过php提供了一个.gdbinit的帮助脚本,这个脚本在你下载的源文件的根目录下面,下面source的路径自行替换

source  ~/download/php-5.6.10/.gdbinit

zbacktrace

如果你需要更多.gdbinit脚本信息请自行google,或者去鸟哥的博客看看.

参考资料:

 如何调试PHP的Core之获取基本信息 

http://www.kuqin.com/language/20111022/313184.html

分类: PHP 标签:

php扩展开发之传参

2015年8月9日 没有评论

最近跟着服务器组同学学写C++,不是想转做C++开发,只是想借此机会锻炼一下自己.php的扩展不止可以使用原生的C,同样的可以使用C++来开发扩展来扩展我们的php.不过今天我们还不会谈C++扩展php,依旧还是原生的C来扩展我们的php.上一篇,我给了一个简单的扩展开发HelloWorld的例子.除了证明我们可以用C扩展php,它对我们日常工作可能要解决的问题来说毫无作用.今天我们来看一下php扩展传参是如果实现的,我们在日常的php开发当中写函数时要传参,同样我们看系统提供的函数中也有参数,但是我们在扩展函数的声明中是没有传参相关的参数的,那么它是如何实现的哪?

Zend API提供了解析参数的函数帮我们来解决这个问题:

ZEND_API int zend_parse_parameters(int num_args TSRMLS_DC, const char *type_spec, …)

ZEND_API int zend_get_parameters(int ht, int param_count, …)

我这里列了2个函数,zend_parse_parameters和zend_get_parameters,你可以在Zend/zend_API.c中找到他们.今天我们主要了解的是zend_parse_parameters,应为这是现阶段php5扩展中开发主要的解析参数的函数,而zend_get_parameters是针对php5之前的扩展用到的.需要你需要扩展老版本的php或者兼容旧的版本,请自行google翻阅资料,我个人觉得其意义不是很大,这里就不再多加探讨了.

zend_parse_parameters有2个主要参数,还有若干可变的参数,可变参数的作用就是接受我们php脚本中传递给扩展的参数.我们先看看这2个主要参数的作用吧:

int num_args就是本函数参数的个数,const char *type_spec是一个用于格式化的字符串,类似C里面的printf,不过他们2个却是完全不一样的.

参数   代表着的类型
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**类型
f   表示函数、方法名称,PHP5.1里貌似木有... ...
下面的列表给出这些参数对应C里面的那些数据类型
参数  对应C里的数据类型
b   zend_bool
l   long
d   double
s   char*, int 前者接收指针,后者接收长度
r   zval*
a   zval*
o   zval*
O   zval*, zend_class_entry*
z   zval*
Z   zval**
后面跟的参数就是用来存储格式化字符串里面所表示的php传递的参数,需要注意的是字符串类型要占用2个参数,一个用来存储字符串,一个用来存储字符串长度.例子这里就不再单独的写了,细心的读者会发现上一篇扩展开发HelloWorld已经有介绍了.
今天我们写一个引用传参的例子,当初我在这个地方也纠结了好久,具体的是我们写一个互换2个数字的函数.
首先我们先用php写一下这个函数(注释部分为脚本输出):
<?php
$x = 1;
$y = 2;
exchange_num($x,$y);
echo $x."\n".$y."\n";
function exchange_num(&$a,&$b){
$c = $a;
$a = $b;
$b = $c;
}
//2
//1
这里我们使用了&符号来使用类似的功能,那么我们用扩展如何实现类似的引用哪?
PHP_FUNCTION(king_exchange_number){
long num1,num2;
zval *arg1,*arg2;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,"zz", &arg1, &arg2) == FAILURE){
RETURN_NULL();
}
num1 = Z_LVAL_P(arg1);
num2 = Z_LVAL_P(arg2);
php_printf("num1:%ld,num2:%ld\n",num1,num2);
ZVAL_LONG(arg1,num2);
ZVAL_LONG(arg2,num1);
RETURN_NULL();
}
细心的朋友会发现我没有使用long类型来接受参数,而是换成zval *来接受.最初的时候我也试图利用long来接受数据,事实上我们可以用long来接受参数,我们可以读取参数里面的值,也可以改变参数的.但是我们却无法改变外部传进来参数的值,因为参数是局部变量,它在函数执行结束以后就会被释放掉,所以我们即使改变了这些用来接受参数的变量的值,其外部参数还是不会变化的.那么如果才能做到改变外部的参数哪?答案当然是指针,只有我们传递来的是一个指针的时候,我们才可以改变外部的参数值.但是我们回顾上面格式化字符串对照表里面,没有指针,但是注意看z是对应任意类型的,所以我们这里使用z.
在你当前的zend_function_entry数组中加入(以后的例子中会省略掉这一部分,读者自行添加即可)
PHP_FE(king_exchange_number,NULL)
然后保存重新编译扩展.然后我们在用我们写的扩展使用上面的功能(注释部分为脚本的输出结果).
<?php
$num1 = 11;
$num2 = 89;
king_exchange_number($num1,$num2);
echo "\$num1:$num1\n";
echo "\$num2:$num2\n";
//$num1:89
//$num2:11
到此相信你对php扩展传参也有个大致的了解了.文中的扩展在php-5.10中运行正常,如有疑问可以留言.