在 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);
}
使用 ps aux 找到目標進程(狀態為 R,CPU 高),然後用 gdb -p <PID> 附加。附加後進程狀態會變為 T(被追蹤),此時進程暫停執行。
(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) 內的數組操作。
關鍵幀是 #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);。至此精確定位到死循環的具體代碼行。
PHP 源碼根目錄提供 .gdbinit,載入後可直接用 zbacktrace 看到 PHP 函數級調用棧:
source /path/to/php-src/.gdbinit
zbacktrace
輸出將清晰顯示 while(1) -> array_flip -> test 等調用關係,極大提升分析效率。
掌握這套流程,面對 PHP 高佔 CPU、疑似死循環等疑難雜癥,即可從容定位、快速修復。