首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

使用 GDB 深度除錯 PHP 死循環:從現象到定位的完整實戰指南

     編輯:PHP 相關知識

在 Swoole Server 等長運行 PHP 應用中,Worker 進程佔用高 CPU 且處於「R」狀態,通常意味著 PHP 代碼陷入死循環。本文通過一個具體案例,詳細演示如何使用 GDB 附加到運行中的進程,結合回溯(bt)與 opcode 分析,快速定位問題代碼行。

問題現場重現

首先構造一個包含死循環的測試腳本:

# 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);
}

第一步:獲取進程 ID 並附加 GDB

使用 ps aux 找到目標進程(狀態為 R,CPU 高),然後用 gdb -p <PID> 附加。附加後進程狀態會變為 T(被追蹤),此時進程暫停執行。

第二步:首次回溯(bt)觀察調用棧

(gdb) bt
#0  0x00000000008cc03f in zend_mm_check_ptr ...
#1  0x00000000008cd643 in _zend_mm_free_int ...
...
#10 0x000000000093b5fd in execute (op_array=0x21d58b0) ...

此時停在內存釋放(_zval_ptr_dtor)階段,說明循環中涉及大量變量銷毀與創建。

第三步:繼續執行並再次中斷,捕捉不同位置

輸入 c 讓進程繼續,隨後 Ctrl+C 再次中斷,再執行 bt:

(gdb) bt
#0  _zend_mm_alloc_int (heap=0x1eaa2c0, size=72, ...) at zend_alloc.c:1895
#1  0x00000000008ceb86 in _emalloc ...
#2  0x0000000000911d85 in _zend_hash_index_update_or_next_insert ...
#3  0x00000000007767e1 in zif_array_flip ...
#4  0x000000000093c03e in zend_do_fcall_common_helper_SPEC ...
#5  0x00000000009400e6 in ZEND_DO_FCALL_SPEC_CONST_HANDLER ...
#6  0x000000000093b5fd in execute (op_array=0x21d58b0) ...

這次停在 array_flip 內部分配內存處,說明死循環核心在 while(1) 內的數組操作。

第四步:定位到具體 opcode 與源碼行號

關鍵幀是 #6 execute (op_array=0x21d58b0)。切換到該幀:

(gdb) f 6
#6  0x000000000093b5fd in execute (op_array=0x21d58b0) at zend_vm_execute.h:410
410     if ((ret = OPLINE->handler(execute_data TSRMLS_CC)) > 0) {

打印 op_array 結構:

(gdb) p *op_array
$2 = {
  type = 2,
  function_name = 0x7f726d086540 "test",
  filename = 0x7f726424ba38 "/home/htf/wwwroot/include.php",
  line_start = 12,
  line_end = 15,
  ...
}

filename 指向 include.php,line_start=12, line_end=15 對應 test 函數定義。再查看當前執行的 opcode:

(gdb) p **executor_globals.opline_ptr
$4 = {
  handler = 0x93ff9c,
  lineno = 5,
  opcode = 60,
  ...
}

lineno = 5 即 include.php 第 5 行:$keys = array_flip($array);。至此精確定位到死循環的具體代碼行。

進階技巧:使用官方 .gdbinit 腳本

PHP 源碼根目錄提供 .gdbinit,載入後可直接用 zbacktrace 看到 PHP 函數級調用棧:

source /path/to/php-src/.gdbinit
zbacktrace

輸出將清晰顯示 while(1) -> array_flip -> test 等調用關係,極大提升分析效率。

總結

  1. ps + gdb -p 非侵入式附加生產進程。
  2. 多次 bt 捕捉不同執行位置,避免單一快照誤導。
  3. op_array.filename + opline.lineno 直達源碼行。
  4. zbacktrace 讓 C 棧轉為 PHP 棧,更符合開發者直覺。

掌握這套流程,面對 PHP 高佔 CPU、疑似死循環等疑難雜癥,即可從容定位、快速修復。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved