使用GDB调试PHP代码,解决PHP代码死循环问题

最近在帮同事解决Swoole Server问题时,发现有1个worker进程一直处于R的状态,而且CPU耗时非常高。初步断定是PHP代码中发生死循环。

下面通过一段代码展示如何解决PHP死循环问题。

代码如下:

#dead_loop.php
$array = array();
for($i = 0; $i < 10000; $i++)
{
    $array[] = $i;
}
include __DIR__."/include.php";
#include.php
while(1)
{
    usleep(10);
    $keys = array_flip($array);
    $index = array_search(rand(1500, 9999), $array);
    $str = str_repeat('A', $index);
    $strb = test($index, $str);
}
function test($index, $str)
{
    return str_replace('A', 'B', $str);
}

通过ps aux得到进程ID和状态如下,使用gdb -p 进程ptrace跟踪,通过bt命令得到调用栈

代码如下:

htf 3834 2.6 0.2 166676 22060 pts/12 R+ 10:50 0:12 php dead_loop.php
gdb -p 3834
(gdb) bt
#0 0x00000000008cc03f in zend_mm_check_ptr (heap=0x1eaa2c0, ptr=0x2584910, silent=1, __zend_filename=0xee3d40 "/home/htf/workspace/php-5.4.27/Zend/zend_variables.c",
__zend_lineno=182, __zend_orig_filename=0xee1888 "/home/htf/workspace/php-5.4.27/Zend/zend_execute_API.c", __zend_orig_lineno=437)
at /home/htf/workspace/php-5.4.27/Zend/zend_alloc.c:1485
#1 0x00000000008cd643 in _zend_mm_free_int (heap=0x1eaa2c0, p=0x2584910, __zend_filename=0xee3d40 "/home/htf/workspace/php-5.4.27/Zend/zend_variables.c", __zend_lineno=182,
__zend_orig_filename=0xee1888 "/home/htf/workspace/php-5.4.27/Zend/zend_execute_API.c", __zend_orig_lineno=437) at /home/htf/workspace/php-5.4.27/Zend/zend_alloc.c:2064
#2 0x00000000008cebf7 in _efree (ptr=0x2584910, __zend_filename=0xee3d40 "/home/htf/workspace/php-5.4.27/Zend/zend_variables.c", __zend_lineno=182,
__zend_orig_filename=0xee1888 "/home/htf/workspace/php-5.4.27/Zend/zend_execute_API.c", __zend_orig_lineno=437) at /home/htf/workspace/php-5.4.27/Zend/zend_alloc.c:2436
#3 0x00000000008eda0a in _zval_ptr_dtor (zval_ptr=0x25849a0, __zend_filename=0xee3d40 "/home/htf/workspace/php-5.4.27/Zend/zend_variables.c", __zend_lineno=182)
at /home/htf/workspace/php-5.4.27/Zend/zend_execute_API.c:437
#4 0x00000000008fe687 in _zval_ptr_dtor_wrapper (zval_ptr=0x25849a0) at /home/htf/workspace/php-5.4.27/Zend/zend_variables.c:182
#5 0x000000000091259f in zend_hash_destroy (ht=0x7f7263f6e380) at /home/htf/workspace/php-5.4.27/Zend/zend_hash.c:560
#6 0x00000000008fe2c5 in _zval_dtor_func (zvalue=0x7f726426fe50, __zend_filename=0xeea290 "/home/htf/workspace/php-5.4.27/Zend/zend_execute.c", __zend_lineno=901)
at /home/htf/workspace/php-5.4.27/Zend/zend_variables.c:45
#7 0x0000000000936656 in _zval_dtor (zvalue=0x7f726426fe50, __zend_filename=0xeea290 "/home/htf/workspace/php-5.4.27/Zend/zend_execute.c", __zend_lineno=901)
at /home/htf/workspace/php-5.4.27/Zend/zend_variables.h:35
#8 0x0000000000939747 in zend_assign_to_variable (variable_ptr_ptr=0x7f7263f8e738, value=0x7f726426f6a8) at /home/htf/workspace/php-5.4.27/Zend/zend_execute.c:901
#9 0x0000000000997ee5 in ZEND_ASSIGN_SPEC_CV_VAR_HANDLER (execute_data=0x7f726d04b2a8) at /home/htf/workspace/php-5.4.27/Zend/zend_vm_execute.h:33168
#10 0x000000000093b5fd in execute (op_array=0x21d58b0) at /home/htf/workspace/php-5.4.27/Zend/zend_vm_execute.h:410
#11 0x0000000000901692 in zend_execute_scripts (type=8, retval=0x0, file_count=3) at /home/htf/workspace/php-5.4.27/Zend/zend.c:1315
#12 0x000000000087926a in php_execute_script (primary_file=0x7ffffe0038d0) at /home/htf/workspace/php-5.4.27/main/main.c:2502
#13 0x00000000009a32e3 in do_cli (argc=2, argv=0x7ffffe004d18) at /home/htf/workspace/php-5.4.27/sapi/cli/php_cli.c:989
#14 0x00000000009a4491 in main (argc=2, argv=0x7ffffe004d18) at /home/htf/workspace/php-5.4.27/sapi/cli/php_cli.c:1365

执行gdb后,死循环的进程会变成T的状态,表示正在Trace。这个是独占的,所以不能再使用strace/gdb或者其他ptrace工具对此进程进行调试。另外此进程会中断执行。gdb输入c后,程序继续向下运行。然后再次按下ctrl + c中断程序。 通过bt命令查看进程的调用栈。

代码如下:

(gdb) bt
#0 _zend_mm_alloc_int (heap=0x1eaa2c0, size=72, __zend_filename=0xe43410 "/home/htf/workspace/php-5.4.27/ext/standard/array.c", __zend_lineno=2719,
__zend_orig_filename=0xee5a38 "/home/htf/workspace/php-5.4.27/Zend/zend_hash.c", __zend_orig_lineno=412) at /home/htf/workspace/php-5.4.27/Zend/zend_alloc.c:1895
#1 0x00000000008ceb86 in _emalloc (size=72, __zend_filename=0xe43410 "/home/htf/workspace/php-5.4.27/ext/standard/array.c", __zend_lineno=2719,
__zend_orig_filename=0xee5a38 "/home/htf/workspace/php-5.4.27/Zend/zend_hash.c", __zend_orig_lineno=412) at /home/htf/workspace/php-5.4.27/Zend/zend_alloc.c:2425
#2 0x0000000000911d85 in _zend_hash_index_update_or_next_insert (ht=0x2257a10, h=3972, pData=0x7ffffe0012b0, nDataSize=8, pDest=0x0, flag=1,
__zend_filename=0xe43410 "/home/htf/workspace/php-5.4.27/ext/standard/array.c", __zend_lineno=2719) at /home/htf/workspace/php-5.4.27/Zend/zend_hash.c:412
#3 0x00000000007767e1 in zif_array_flip (ht=1, return_value=0x7f726424ea68, return_value_ptr=0x0, this_ptr=0x0, return_value_used=1)
at /home/htf/workspace/php-5.4.27/ext/standard/array.c:2719
#4 0x000000000093c03e in zend_do_fcall_common_helper_SPEC (execute_data=0x7f726d04b2a8) at /home/htf/workspace/php-5.4.27/Zend/zend_vm_execute.h:643
#5 0x00000000009400e6 in ZEND_DO_FCALL_SPEC_CONST_HANDLER (execute_data=0x7f726d04b2a8) at /home/htf/workspace/php-5.4.27/Zend/zend_vm_execute.h:2233
#6 0x000000000093b5fd in execute (op_array=0x21d58b0) at /home/htf/workspace/php-5.4.27/Zend/zend_vm_execute.h:410

两次的BT信息不一样,这是因为程序在不同的位置中断。看到execute (oparray=0x21d58b0) 这一行,这里就是PHP执行oparray的入口了。gdb下输入f 6,(通过调用栈编号可得)。

代码如下:

(gdb) f 6
#6 0x000000000093b5fd in execute (op_array=0x21d58b0) at /home/htf/workspace/php-5.4.27/Zend/zend_vm_execute.h:410
410 if ((ret = OPLINE->handler(execute_data TSRMLS_CC)) > 0) {
(gdb) p *op_array
$2 = {type = 2 '\002', function_name = 0x7f726d086540 "test", scope = 0x0, fn_flags = 134217728, prototype = 0x0, num_args = 2, required_num_args = 2, arg_info = 0x7f726d086bd8,
refcount = 0x7f726d0870f0, opcodes = 0x7f726424d600, last = 8, vars = 0x7f726424e890, last_var = 2, T = 1, brk_cont_array = 0x0, last_brk_cont = 0, try_catch_array = 0x0,
last_try_catch = 0, static_variables = 0x0, this_var = 4294967295, filename = 0x7f726424ba38 "/home/htf/wwwroot/include.php", line_start = 12, line_end = 15, doc_comment = 0x0,
doc_comment_len = 0, early_binding = 4294967295, literals = 0x7f726424eae0, last_literal = 4, run_time_cache = 0x7f726450bfb0, last_cache_slot = 1, reserved = {0x0, 0x0, 0x0, 0x0}}

这里的filename就能看到op_array是哪个PHP文件的。然后输入f 0进入当前位置。

代码如下:

(gdb) p **executor_globals.opline_ptr
$4 = {handler = 0x93ff9c , op1 = {constant = 1680133296, var = 1680133296, num = 1680133296, hash = 140129283132592, opline_num = 1680133296,
jmp_addr = 0x7f726424ccb0, zv = 0x7f726424ccb0, literal = 0x7f726424ccb0, ptr = 0x7f726424ccb0}, op2 = {constant = 0, var = 0, num = 0, hash = 0, opline_num = 0, jmp_addr = 0x0,
zv = 0x0, literal = 0x0, ptr = 0x0}, result = {constant = 32, var = 32, num = 32, hash = 32, opline_num = 32, jmp_addr = 0x20, zv = 0x20, literal = 0x20, ptr = 0x20},
extended_value = 1, lineno = 5, opcode = 60 '

这里的lineno表示OPCODE所在的代码行数,可以到对应文件里去看下是哪行代码。使用GDB可以查看到更多的信息,这里就不再一一介绍了,有兴趣各位可以自行尝试。

zbacktrace的使用

zend官方提供了一个gdb的脚本,对指令进行了封装,可以直接看到php函数的调用关系。在php源代码包的根目录中有一个.gdbinit。使用

代码如下:

source your_php_src_path/.gdbinit
zbacktrace

可以直接看到PHP函数的调用堆栈。

以上就是本文的全部内容了,希望大家能够喜欢。

(0)

相关推荐

  • php计算数组相同值出现次数的代码(array_count_values)

    php计算数组相同值出现次数,可以使用php自带函数array_count_values : 说明 array array_count_values ( array $input )array_count_values() 返回一个数组,该数组用 input 数组中的值作为键名,该值在 input 数组中出现的次数作为值. array_count_values() 例子 复制代码 代码如下: <?php $array = array(1, "hello", 1, "wo

  • PHP开发中常用的十个代码样例

    一.黑名单过滤 function is_spam($text, $file, $split = ':', $regex = false){ $handle = fopen($file, 'rb'); $contents = fread($handle, filesize($file)); fclose($handle); $lines = explode("n", $contents); $arr = array(); foreach($lines as $line){ list($w

  • phpQuery让php处理html代码像jQuery一样方便

    简介 如何在php中方便地解析html代码,估计是每个phper都会遇到的问题.用phpQuery就可以让php处理html代码像jQuery一样方便. 项目地址:https://code.google.com/p/phpquery/ github地址:https://github.com/TobiaszCudnik/phpquery DEMO 下载库文件:https://code.google.com/p/phpquery/downloads/list 我下的是onefile版:phpQuer

  • php curl请求信息和返回信息设置代码实例

    在用curl抓取网页内容的时候,经常要知道,网页返回的请求头信息,和请求的相关信息,特别是在请求过程中存在重定向的时候获取请求返回头信息对分析请求内容很有帮助 下面就是一个请求中存在重定向的例子,我们的目的是要获取最终实际请求的url地址 $url='http://www.appchina.com/market/r/489267/com.appshare.android.ilisten.vapk?c=aplus.direct&uid=gAJ9cQEu1TlyZxsXN-aB4RaanvFL6t6

  • MySql数据库查询结果用表格输出PHP代码示例

    在一般的网站中,我们会通常看到,很多数据库中表的数据在浏览器都是出现在表格中的,一开始让自己感到很神奇,但是仔细想想也不算太复杂,既然可以dql和dml的一般返回,以表格的方式返回应该也不成问题,但是,有一点说明的是,在客户端设计脚本去实现问题是不对的,即便可以实现起来也是非常复杂,所以,只能在服务器的方面去考虑,想想问题解决的方式就有了,即在返回的时候打印表格标签和对应属性和属性值,虽然说这种方式看起来不太合理,但是这也是最为有效的方法.具体的代码如下: <?php //在表格中显示表的数据,

  • PHP 实现代码复用的一个方法 traits新特性

    在阅读yii2源码的时候接触到了trait,就学习了一下,写下博客记录一下. 自 PHP 5.4.0 起,PHP 实现了代码复用的一个方法,称为 traits. Traits 是一种为类似 PHP 的单继承语言而准备的代码复用机制.Trait 为了减少单继承语言的限制,使开发人员能够自由地在不同层次结构内独立的类中复用方法集.Traits 和类组合的语义是定义了一种方式来减少复杂性,避免传统多继承和混入类(Mixin)相关的典型问题. Trait 和一个类相似,但仅仅旨在用细粒度和一致的方式来组

  • SQL注入原理与解决方法代码示例

    一.什么是sql注入? 1.什么是sql注入呢? 所谓SQL注入,就是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令,比如先前的很多影视网站泄露VIP会员密码大多就是通过WEB表单递交查询字符暴出的,这类表单特别容易受到SQL注入式攻击.当应用程序使用输入内容来构造动态sql语句以访问数据库时,会发生sql注入攻击.如果代码使用存储过程,而这些存储过程作为包含未筛选的用户输入的字符串来传递,也会发生sql注入. 黑客通过SQL注入攻击

  • 一行代码解决 perl输入 排序 输出问题

    先来看基本的方法: 复制代码 代码如下: chomp(@strings=<STDIN>);  @strings=sort @strings;  print "@strings\n."; 上面的三行代码解决的是输入,然后排序,最后将排序后的结果输出的作用. 只用一行代码即完成的写法: 复制代码 代码如下: print sort<STDIN>; 得到相同的结果,相当给力哦.

  • 二行代码解决全部网页木马

    前不久一行代码解决iframe挂马(包含服务器端注入.客户端ARP注入等)>得到了很多朋友的认可,这确实是个避避风雨的好办法.可现在挂网马的方式真如我所料地改变了,现在流行挂<script>木马,汗了,看了几个网友的网站都被这样了--页面的顶部或底部加上了: 注意,以下地址含有木马,请不要轻易访问: <script src=http://%76%63%63%64%2E%63%6E></script> <script src=http://%76%63%63%

  • php生成缩略图质量较差解决方法代码示例

    近期弄个论坛,在首页要有个排名,显示评论最多的前十位用户的列表,列表有个略图是用户最新上传的作品,如果使用原来的图,图片过大,首页加载过慢,因此要使用略图 以上来使用imagecopyresized这个函数,显示质量很差如图 后来改用imagecopyresampled效果明显改变效果如图 附上完整代码: /** * @name thum 缩略图函数 * @param sting $img_name 图片路径 * @param int $max_width 略图最大宽度 * @param int

  • Python使用pickle模块存储数据报错解决示例代码

    本文研究的主要是Python使用pickle模块存储数据报错解决方法,以代码的形式展示,具体如下. 首先来了解下pickle模块 pickle提供了一个简单的持久化功能.可以将对象以文件的形式存放在磁盘上. pickle模块只能在python中使用,python中几乎所有的数据类型(列表,字典,集合,类等)都可以用pickle来序列化, pickle序列化后的数据,可读性差,人一般无法识别. 接下来我们看下Python使用pickle模块存储数据报错解决方法. 代码: # 写入错误 TypeEr

  • java网络爬虫连接超时解决实例代码

    本文研究的主要是java网络爬虫连接超时的问题,具体如下. 在网络爬虫中,经常会遇到如下报错.即连接超时.针对此问题,一般解决思路为:将连接时间.请求时间设置长一下.如果出现连接超时的情况,则在重新请求[设置重新请求次数]. Exception in thread "main" java.net.ConnectException: Connection timed out: connect 下面的代码便是使用httpclient解决连接超时的样例程序.直接上程序. package da

  • 详解AndroidStudio3.0开发调试安卓NDK的C++代码

    本文介绍了AndroidStudio3.0开发调试安卓NDK的C++代码,分享给大家,具有如下: 一.新建项目 新建项目,没有发现Include C++ Support 选项.因为印象中是有过该选项的,找了半天没找到. 后来无意间拖了下窗口大小,原来是被隐藏了,真特么坑. 新建一个测试项目,勾选Include C++ Support 选项,看看工程上有哪些不同. 1.gradle 首先看gradle文件,android节点下添加: externalNativeBuild { cmake { pa

  • Java多线程执行处理业务时间太久解决方法代码示例

    背景:在政府开发了一个应用系统,主要功能是让企业填写企业资质信息,然后通过给定的公式,统计这一系列的信息,以得分的形式展示给政府领导查看.目前有1300家企业填报.由于得分是实时显示的,所以导致统计功能很慢. 代码运行流程: 1.查出1300企业信息 2.遍历1300企业信息,ji计算每家企业得分信息.每家预计时间为0.3秒.合计390秒.导致页面请求超时 3.导出(用jxl jar) 解决方案: 由于处理业务的,所以需要能有返回值的线程.用:Callable 直接上代码 1.调用线程的代码 L

  • Python一行代码解决矩阵旋转的问题

    今天刷<剑指offer>的时候碰到这样一道题: 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10. 思路:可以模拟魔方逆时针旋转的方法,一直做取出第一行的操作,例如 : 1 2 3 4 5 6 7 8 9 利用pop输出并且删除第一行后,再进行一次逆时针旋转,就变成: 6 9

  • 解决阿里代码规范检测中方法缺少javadoc注释的问题

    一.问题描述 安装了阿里代码检测的插件后,敲一个简单的方法,发现提示有问题,如下 /** * 查找User的集合 */ List<User> findAll(); 提示信息为: 方法[findAll]缺少javadoc注释 进一步查看完整文档里面关于方法注释的规范为 所有的抽象方法(包括接口中的方法)必须要用javadoc注释.除了返回值.参数.异常说明外,还必须指出该方法做什么事情,实现什么功能. 说明:如有实现和调用注意事项,请一并说明. /** * fetch data by rule

随机推荐