PHP內核分析之GDB使用(一)

參考:http://www.phpmianshi.com/?id=5

 

1.PHP源碼下載和安裝

https://github.com/php/php-src/releases

 

$ ./configure --prefix=/usr/local/php7 --enable-debug --enable-fpm
$ make && sudo make install

 

2.環境工具介紹

CENTOS 7.2

PHP-7.4.1

GDB    命令行調試工具

CLion   圖形界面調試工具 C C++開發工具

 

3.GDB使用說明

參考:https://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/gdb.html

gdb來排查比如這些問題:

  1. 某個php進程佔用cpu 100%問題
  2. 出現core dump問題,比如“Segmentation fault”
  3. php擴展出現錯誤
  4. 死循環問題

一些快捷命令

  • p:print,打印C變量的值
  • c:continue 繼續執行,到下一個斷點處(或運行結束)
  • b:breakpoint,設置斷點,可以按照函數名設置,如b zif_php_function,也可以按照源代碼的行數指定斷點,如b src/networker/Server.c:1000
  • t:thread,切換線程,如果進程擁有多個線程,可以使用t指令,切換到不同的線程
  • ctrl + c:中斷當前正在運行的程序,和c指令配合使用
  • n:next,單步跟蹤程序,當遇到函數調用時,也不進入此函數體;此命令同 step 的主要區別是,step 遇到用戶自定義的函數,將步進到函數中去運行,而 next 則直接調用函數,不會進入到函數體內。
  • s:step 單步調試如果有函數調用,則進入函數;與命令n不同,n是不進入調用的函數的
  • info threads:查看運行的所有線程
  • l:list,查看源碼,可以使用l 函數名 或者 l 行號
  • bt:backtrace,查看運行時的函數調用棧。當程序出錯後用於查看調用棧信息
  • finish:完成當前函數
  • f:frame,與bt配合使用,可以切換到函數調用棧的某一層
  • r:run,運行程序

gdb 調試php:

gdb有3種使用方式:

  1. 跟蹤正在運行的PHP程序,使用 “gdb -p 進程ID” 進行附加到進程上
  2. 運行並調試PHP程序,使用 “gdb php -> run server.php” 進行調試
  3. 當PHP程序發生coredump後使用gdb加載core內存鏡像進行調試 gdb php core

php在解釋執行過程中,zend引擎用executor_globals變量保存了執行過程中的各種數據,包括執行函數、文件、代碼行等。zend虛擬機是使用C編寫,gdb來打印PHP的調用棧時,實際是打印的虛擬機的執行信息。

GDB調試PHP程序:

<?php
for($i = 0; $i < 10; $i++){
    echo $i."\n";
    sleep(3);
    if(in_array($i,[1,9,20])){
        print_r($i*$i);
        var_dump($i*$i);

        print $i*$i;
    }
[root@VM_0_15_centos test]# gdb php   
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-80.el7
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /usr/local/php/bin/php...done.
(gdb) b zif_sleep
Breakpoint 1 at 0x7d1ec0: file /root/oneinstack/src/php-7.3.5/ext/standard/basic_functions.c, line 4557.
(gdb) b zif_in_array
Breakpoint 2 at 0x7c51b0: file /root/oneinstack/src/php-7.3.5/ext/standard/array.c, line 1637.
(gdb) b zif_printf
Function "zif_printf" not defined.
Make breakpoint pending on future shared library load? (y or [n]) n
(gdb) b zif_echo
Function "zif_echo" not defined.
Make breakpoint pending on future shared library load? (y or [n]) n
(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x00000000007d1ec0 in zif_sleep 
                                                   at /root/oneinstack/src/php-7.3.5/ext/standard/basic_functions.c:4557
2       breakpoint     keep y   0x00000000007c51b0 in zif_in_array 
                                                   at /root/oneinstack/src/php-7.3.5/ext/standard/array.c:1637
(gdb) 

Function "zif_echo" not defined. 這裏大致可以看一下 echo print等不是函數了

然後開始調試

(gdb) run gdb.php 
Starting program: /usr/local/php/bin/php gdb.php
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
0

Breakpoint 1, zif_sleep (execute_data=0x7ffff1c1d140, return_value=0x7fffffffac30)
    at /root/oneinstack/src/php-7.3.5/ext/standard/basic_functions.c:4557

打印返回值return_value

(gdb) p *return_value
$2 = {value = {lval = 0, dval = 0, counted = 0x0, str = 0x0, arr = 0x0, obj = 0x0, res = 0x0, ref = 0x0, 
    ast = 0x0, zv = 0x0, ptr = 0x0, ce = 0x0, func = 0x0, ww = {w1 = 0, w2 = 0}}, u1 = {v = {type = 1 '\001', 
      type_flags = 0 '\000', u = {call_info = 0, extra = 0}}, type_info = 1}, u2 = {next = 0, cache_slot = 0, 
    opline_num = 0, lineno = 0, num_args = 0, fe_pos = 0, fe_iter_idx = 0, access_flags = 0, property_guard = 0, 
    constant_flags = 0, extra = 0}}
(gdb) p return_value.value
$3 = {lval = 0, dval = 0, counted = 0x0, str = 0x0, arr = 0x0, obj = 0x0, res = 0x0, ref = 0x0, ast = 0x0, 
  zv = 0x0, ptr = 0x0, ce = 0x0, func = 0x0, ww = {w1 = 0, w2 = 0}}
(gdb) 

查看當前堆棧,PHP內核的執行過程

(gdb) bt
#0  zif_sleep (execute_data=0x7ffff1c1d140, return_value=0x7fffffffac30)
    at /root/oneinstack/src/php-7.3.5/ext/standard/basic_functions.c:4557
#1  0x00007fffeb4a2e55 in xdebug_execute_internal (current_execute_data=0x7ffff1c1d140, 
    return_value=0x7fffffffac30) at /root/oneinstack/src/xdebug-2.7.2/xdebug.c:2050
#2  0x0000000000483041 in ZEND_DO_FCALL_SPEC_RETVAL_UNUSED_HANDLER ()
    at /root/oneinstack/src/php-7.3.5/Zend/zend_vm_execute.h:982
#3  0x0000000000955caf in execute_ex (ex=0x7ffff1c1d140)
    at /root/oneinstack/src/php-7.3.5/Zend/zend_vm_execute.h:55557
#4  0x00007fffeb4a2499 in xdebug_execute_ex (execute_data=0x7ffff1c1d030)
    at /root/oneinstack/src/xdebug-2.7.2/xdebug.c:1928
#5  0x000000000095e0a8 in zend_execute (op_array=op_array@entry=0x7ffff1c74380, 
    return_value=return_value@entry=0x0) at /root/oneinstack/src/php-7.3.5/Zend/zend_vm_execute.h:60881
#6  0x00000000008d9274 in zend_execute_scripts (type=type@entry=8, retval=retval@entry=0x0, 
    file_count=file_count@entry=3) at /root/oneinstack/src/php-7.3.5/Zend/zend.c:1568
#7  0x000000000087d100 in php_execute_script (primary_file=primary_file@entry=0x7fffffffd120)
    at /root/oneinstack/src/php-7.3.5/main/main.c:2630
#8  0x0000000000960445 in do_cli (argc=2, argv=0x11e7c40)
    at /root/oneinstack/src/php-7.3.5/sapi/cli/php_cli.c:997
#9  0x000000000048cdaf in main (argc=2, argv=0x11e7c40) at /root/oneinstack/src/php-7.3.5/sapi/cli/php_cli.c:1389

使用zbacktrace更簡單的調試:
php源代碼中還提供了zbacktrace這樣的方便的對gdb命令的封裝的工具。zbacktrace是PHP源碼包提供的一個gdb自定義指令,功能與bt指令類似,與bt不同的是zbacktrace看到的調用棧是PHP函數調用棧,而不是c函數。zbacktrace可以直接看到當前執行函數、文件名和行數,簡化了直接使用gdb命令的很多步驟。在php-src的根目錄中有一個.gdbinit文件,我的是用oneinstack安裝的,所以文件目錄爲:source /root/oneinstack/src/php-7.3.5/.gdbinit,裏面提供了20多個 gdb 的自定義命令,用於方便PHP的調試

輸入:

(gdb) source /root/oneinstack/src/php-7.3.5/.gdbinit
(gdb) zbacktrace
[0x7ffff1c1d140] sleep(3) [internal function]
[0x7ffff1c1d030] (main) /data/wwwroot/test/gdb.php:4
(gdb) c
Continuing.
4

Breakpoint 1, zif_sleep (execute_data=0x7ffff1c1d140, return_value=0x7fffffffac30)
    at /root/oneinstack/src/php-7.3.5/ext/standard/basic_functions.c:4557
4557    {
(gdb) zbacktrace
[0x7ffff1c1d140] in_array(4, array(3)[0x7ffff1c1d1a0]) [internal function]
[0x7ffff1c1d030] (main) /data/wwwroot/test/gdb.php:5 
(gdb) printzv 0x7ffff1c1d1a0
[0x7ffff1c1d1a0] (refcount=2) array:     Packed(3)[0x7ffff1c5f310]: {
      [0] 0 => [0x7ffff1c65648] long: 1
      [1] 1 => [0x7ffff1c65668] long: 9
      [2] 2 => [0x7ffff1c65688] long: 20
}

1. print_cvs 打印當前執行環境中已編譯的PHP變量, 如:

(gdb) print_cvs
Compiled variables count: 0

2. printzv 打印指定的PHP變量, 需要指定地址, 如打印一個數組:
(gdb) printzv 0x7ffff1c1d1a0
[0x7ffff1c1d1a0] (refcount=2) array:     Packed(3)[0x7ffff1c5f310]: {
      [0] 0 => [0x7ffff1c65648] long: 1
      [1] 1 => [0x7ffff1c65668] long: 9
      [2] 2 => [0x7ffff1c65688] long: 20
}

3. 打印PHP的函數調用棧, 如:
(gdb) zbacktrace
[0x7ffff1c1d140] in_array(4, array(3)[0x7ffff1c1d1a0]) [internal function]
[0x7ffff1c1d030] (main) /data/wwwroot/test/gdb.php:5

4. print_ft 打印函數表( HashTable )
(gdb) set $eg = executor_globals
(gdb) print $eg.function_table  
$6 = (HashTable *) 0xa5bd450
(gdb) print_ft $eg.function_table
[0xa5bd450] {
“zend_version\0” => “zend_version”
“func_num_args\0” => “func_num_args”
“func_get_arg\0” => “func_get_arg”
“func_get_args\0” => “func_get_args”
“strlen\0” => “strlen”
“strcmp\0” => “strcmp”
“strncmp\0” => “strncmp”
“strcasecmp\0” => “strcasecmp”
“strncasecmp\0” => “strncasecmp”
“each\0” => “each”

一些使用gdb排查問題例子:

還可以加一下監控watch、設置一些調試變量set 等等
其他的調試工具還有 strace 查看系統調用、ltrace 查看類庫的調用、vld查看opcode

 

常見問題:

一、Missing separate debuginfos, use: debuginfo-install glibc-2.17-157.el7_3.5.x86_64

在centos7上面gdb調試程序時候,報錯信息是:
Missing separate debuginfos, use: debuginfo-install glibc-2.17-157.el7_3.5.x86_64

解決方案:
1 先修改"/etc/yum.repos.d/CentOS-Debuginfo.repo"文件的 enable=1;有時候該文件不存在,則需要手工創建此文件並加入以下內容:

[debug]
name=CentOS-7 - Debuginfo
baseurl=http://debuginfo.centos.org/7/$basearch/ 
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-Debug-7 
enabled=1

2 執行sudo yum install -y glibc
3 執行debuginfo-install glibc
即可解決該問題!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章