최근 동료들이 Swoole Server 문제를 해결하도록 도와주던 중 한 작업자 프로세스가 항상 R 상태에 있고 CPU 소비가 매우 높은 것을 발견했습니다. 예비 결론은 PHP 코드에서 무한 루프가 발생한다는 것입니다.
다음 코드는 PHP 무한 루프 문제를 해결하는 방법을 보여줍니다.
다음과 같이 ps aux를 통해 프로세스 ID 및 상태를 가져오고, gdb -p process 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
zend_mm_check_ptr의 #0 0x00000000008cc03f(힙=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)
/home/htf/workspace/php-5.4.27/Zend/zend_alloc.c:1485
_zend_mm_free_int의 #1 0x000000008cd643(힙 = 0x1eaa2c0, P = 0x2584910, __zend_filename = 0xee3d40 "/home/workspace/5.4.27/zend/VEND_V ariables.c ", __zend_lineno = 182,
__zend_orig_filename=0xee1888 "/home/htf/workspace/php-5.4.27/Zend/zend_execute_API.c", __zend_orig_lineno=437) /home/htf/workspace/php-5.4.27/Zend/zend_alloc.c:2064
#2 _efree의 0x00000000008cebf7 (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) /home/htf/workspace/php-5.4.27/Zend/zend_alloc.c:2436
#3 _zval_ptr_dtor의 0x00000000008eda0a (zval_ptr=0x25849a0, __zend_filename=0xee3d40 "/home/htf/workspace/php-5.4.27/Zend/zend_variables.c", __zend_lineno=182)
/home/htf/workspace/php-5.4.27/Zend/zend_execute_API.c:437
/home/htf/workspace/php-5.4.27/Zend/zend_variables.c:182에 있는 _zval_ptr_dtor_wrapper(zval_ptr=0x25849a0)의 #4 0x00000000008fe687
#5 0x000000000091259f zend_hash_destroy(ht=0x7f7263f6e380), /home/htf/workspace/php-5.4.27/Zend/zend_hash.c:560
_zval_dtor_func의 #6 0x00000000008fe2c5 (zvalue=0x7f726426fe50, __zend_filename=0xeea290 "/home/htf/workspace/php-5.4.27/Zend/zend_execute.c", __zend_lineno=901)
/home/htf/workspace/php-5.4.27/Zend/zend_variables.c:45
_zval_dtor의 #7 0x0000000000936656 (zvalue=0x7f726426fe50, __zend_filename=0xeea290 "/home/htf/workspace/php-5.4.27/Zend/zend_execute.c", __zend_lineno=901)
/home/htf/workspace/php-5.4.27/Zend/zend_variables.h:35
/home/htf/workspace/php-5.4.27/Zend/zend_execute.c:901의 #8 0x0000000000939747 in zend_sign_to_variable (variable_ptr_ptr=0x7f7263f8e738, value=0x7f726426f6a8)
/home/htf/workspace/php-5.4.27/Zend/zend_vm_execute.h:33168의 ZEND_ASSIGN_SPEC_CV_VAR_HANDLER(execute_data=0x7f726d04b2a8)의 #9 0x0000000000997ee5
/home/htf/workspace/php-5.4.27/Zend/zend_vm_execute.h:410에서 실행(op_array=0x21d58b0)의 #10 0x000000000093b5fd
/home/htf/workspace/php-5.4.27/Zend/zend.c:1315의 zend_execute_scripts(유형=8, retval=0x0, file_count=3)의 #11 0x0000000000901692
/home/htf/workspace/php-5.4.27/main/main.c:2502에 있는 php_execute_script(primary_file=0x7ffffe0038d0)의 #12 0x000000000087926a
#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(argc=2, argv=0x7ffffe004d18) /home/htf/workspace/php-5.4.27/sapi/cli/php_cli.c:1365
gdb를 실행한 후 무한 루프에 있는 프로세스는 추적 중임을 나타내는 T 상태로 변경됩니다. 이는 독점적이므로 더 이상 strace/gdb 또는 기타 ptrace 도구를 사용하여 이 프로세스를 디버깅할 수 없습니다. 또한 이 프로세스는 실행을 중단합니다. gdb가 c에 들어간 후 프로그램은 계속해서 아래쪽으로 실행됩니다. 그런 다음 Ctrl c를 다시 눌러 프로그램을 중단합니다. bt 명령을 통해 프로세스의 호출 스택을 봅니다.
(gdb)BT
#0 _zend_mm_alloc_int (힙=0x1eaa2c0, 크기=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) /home/htf/workspace/php-5.4.27/Zend/zend_alloc.c:1895
#1 _emalloc의 0x00000000008ceb86(크기=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) /home/htf/workspace/php-5.4.27/Zend/zend_alloc.c:2425
_zend_hash_index_update_or_next_insert의 #2 0x0000000000911d85(ht=0x2257a10, h=3972, pData=0x7ffffe0012b0, nDataSize=8, pDest=0x0, 플래그=1,
__zend_filename=0xe43410 "/home/htf/workspace/php-5.4.27/ext/standard/array.c", __zend_lineno=2719) /home/htf/workspace/php-5.4.27/Zend/zend_hash.c: 412
#3 zif_array_flip의 0x00000000007767e1(ht=1, return_value=0x7f726424ea68, return_value_ptr=0x0, this_ptr=0x0, return_value_used=1)
/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
/home/htf/workspace/php-5.4.27/Zend/zend_vm_execute.h:2233에 있는 ZEND_DO_FCALL_SPEC_CONST_HANDLER(execute_data=0x7f726d04b2a8)의 #5 0x00000000009400e6
#6 /home/htf/workspace/php-5.4.27/Zend/zend_vm_execute.h:410에서 실행(op_array=0x21d58b0)의 0x000000000093b5fd
두 차례의 BT 정보가 다른 이유는 프로그램이 서로 다른 위치에서 중단되기 때문입니다. 실행 줄(oparray=0x21d58b0)이 표시되면 이는 PHP가 oparray를 실행하는 진입점입니다. gdb 아래에 f 6을 입력합니다(호출 스택 번호를 통해 사용 가능).
(gdb) f 6
#6 /home/htf/workspace/php-5.4.27/Zend/zend_vm_execute.h:410에서 실행(op_array=0x21d58b0)의 0x000000000093b5fd
410 if ((ret = OPLINE->handler(execute_data TSRMLS_CC)) > 0) {
(gdb) p *op_array
$2 = {유형 = 2 '
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, 파일 이름 = 0x7f726424ba38 "/home/htf/wwwroot/include.php", line_start = 12, line_end = 15, doc_comment = 0x0,
doc_comment_len = 0, early_bound = 4294967295, 리터럴 = 0x7f726424eae0, last_literal = 4, run_time_cache = 0x7f726450bfb0, last_cache_slot = 1, 예약됨 = {0x0, 0x0, 0x0, 0x0}}
여기의 파일 이름은 op_array가 어떤 PHP 파일인지 표시합니다. 그런 다음 f 0을 입력하여 현재 위치를 입력합니다.
(gdb) p **executor_globals.opline_ptr
$4 = {핸들러 = 0x93ff9c , op1 = {상수 = 1680133296, var = 1680133296, num = 1680133296, 해시 = 140129283132592, opline_num = 1680133296,
jmp_addr = 0x7f726424ccb0, zv = 0x7f726424ccb0, 리터럴 = 0x7f726424ccb0, ptr = 0x7f726424ccb0}, op2 = {상수 = 0, var = 0, num = 0, 해시 = 0, opline_num = 0, jmp_addr = 0x 0,
zv = 0x0, 리터럴 = 0x0, ptr = 0x0}, 결과 = {상수 = 32, var = 32, num = 32, 해시 = 32, opline_num = 32, jmp_addr = 0x20, zv = 0x20, 리터럴 = 0x20, ptr = 0x20},
확장_값 = 1, lineno = 5, opcode = 60 '
여기서 lineno는 OPCODE가 위치한 코드의 줄 번호를 나타냅니다. 해당 파일로 이동하면 어떤 코드 줄인지 확인할 수 있습니다. 여기서는 하나씩 소개하지 않고, GDB를 이용하면 더 많은 정보를 볼 수 있습니다.
zbacktrace 사용법
zend는 명령을 캡슐화하고 PHP 함수의 호출 관계를 직접 볼 수 있는 gdb 스크립트를 공식적으로 제공합니다. PHP 소스 코드 패키지의 루트 디렉터리에 .gdbinit가 있습니다.
사용
소스 your_php_src_path/.gdbinit
zbacktrace
PHP 함수의 호출 스택을 직접 볼 수 있습니다.
위 내용은 이 글의 전체 내용입니다. 모두 마음에 드셨으면 좋겠습니다.