「深入理解PHP内核」 - 如何使用GDB调试代码

前言

GDB是一个由GNU开源组织发布的、UNIX/Linux操作系统下的、基于命令行的、功能强大的程序调试工具。当程序发生coredump,通过GDB可以从core文件中复现场景,定位问题。

GDB调试常用命令

  • file <文件名>

    加载被调试的可执行程序文件。因为一般都在被调试程序所在目录下执行GDB,因而文本名不需要带路径。

(gdb) file gdb-sample

  • r

    Run的简写,运行被调试的程序。如果此前没有下过断点,则执行完整个程序;如果有断点,则程序暂停在第一个可用断点处。

(gdb) r

  • c

    Continue的简写,继续执行被调试程序,直至下一个断点或程序结束.

(gdb) c

  • b 设置断点
    • b <行号>
    • b <函数名称>
    • b *<函数名称>
    • b *<代码地址>

      b: Breakpoint的简写,设置断点。两可以使用“行号”“函数名称”“执行地址”等方式指定断点位置。
      其中在函数名称前面加“*”符号表示将断点设置在“由编译器生成的prolog代码处”。如果不了解汇编,可以不予理会此用法。

1
2
3
4
5
(gdb) b 8
(gdb) b main
(gdb) b *main
(gdb) b *0x804835c
(gdb) d
  • d

    Delete breakpoint的简写,删除指定编号的某个断点,或删除所有断点。断点编号从1开始递增。

  • s

    s 相当于其它调试器中的“Step Into (单步跟踪进入)。 执行一行源程序代码,如果此行代码中有函数调用,则进入该函数;

  • n

    执行一行源程序代码,此行代码中的函数调用也一并执行。相当于其它调试器中的“Step Over (单步跟踪)”。
    这两个命令必须在有源代码调试信息的情况下才可以使用(GCC编译时使用“-g”参数)。

si, ni si命令类似于s命令,ni命令类似于n命令。所不同的是,这两个命令(si/ni)所针对的是汇编指令,而s/n针对的是源代码。

1
2
(gdb) si
(gdb) ni

  • p <变量名称>

    Print的简写,显示指定变量(临时变量或全局变量)的值。

1
2
(gdb) p i
(gdb) p nGlobalVar
  • display …

    display,设置程序中断后欲显示的数据及其格式。
    例如,如果希望每次程序中断后可以看到即将被执行的下一条汇编指令,可以使用命令"display /i $pc"其中 $pc 代表当前汇编指令,/i 表示以十六进行显示。当需要关心汇编代码时,此命令相当有用。

  • undisplay <编号>

    undispaly,取消先前的display设置,编号从1开始递增。

    1
    2
    (gdb) display /i $pc  
    (gdb) undisplay 1
  • i

    Info的简写,用于显示各类信息,详情请查阅“help i”。

(gdb) i r

  • q

    Quit的简写,退出GDB调试环境。 (gdb) q

  • help [命令名称]

    GDB帮助命令,提供对GDB名种命令的解释说明。
    如果指定了“命令名称”参数,则显示该命令的详细说明;如果没有指定参数,则分类显示所有GDB命令,供用户进一步浏览和查询。

(gdb) help display

实战

GDB调试php

我们在使用GDB调试的时候经常会查看某个变量的信息,如果你的php是开启了编译器优化的,那么是看不到变量信息的,显示<value optimized out>你需要修改MakeFile禁止编译器优化

####修改方法:

vim MakeFile

88 CFLAGS_CLEAN = -I/usr/include -g -O0 -fvisibility=hidden -DZEND_SIGNALS $(PROF_FLAGS)// 将-O0 修改为-O2

保存修改文件执行make clean && make && make install 来重新编译安装PHP

准备代码test.php

1
2
3
<?php
$a = 'hello';
echo $a;

开始进行GDB调试

gdb php

1
2
3
4
5
6
7
8
9
10
11
12
gdb php
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-56.el6)
Copyright (C) 2010 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 /home/php7internal/php7/output/bin/php...done.
(gdb)

在main函数处设置断点

1
(gdb) b mainBreakpoint 1 at 0xa4e894: file /home/php7internal/soft/php-7.1.0/sapi/cli/php_cli.c, line 1195. // 断点位置

运行test.php

1
2
3
4
5
6
7
(gdb) r test.php

Starting program: /home/php7internal/php7/output/bin/php
[Thread debugging using libthread_db enabled]
Breakpoint 1, main (argc=1, argv=0x7fffffffe448)
at /home/php7internal/soft/php-7.1.0/sapi/cli/php_cli.c:1195
1195 int exit_status = SUCCESS;

从上面的输出我们看到代码在main函数处停止下来,然后使用n命令执行下一步

1
2
3
4
5
6
7
8
(gdb) n
1196 int module_started = 0, sapi_started = 0;
(gdb) n
1197 char *php_optarg = NULL;
(gdb) n
1198 int php_optind = 1, use_extended_info = 0;
(gdb) n
1199 char *ini_path_override = NULL;

使用p查看变量信息

1
2
3
4
5
6
(gdb) p php_optind
$1 = 1
(gdb) p ini_path_override
$2 = 0x0
(gdb) p ini_entries
$3 = 0x0

使用c命令执行到下一个断点,如果没有其余断点则执行到程序结束

1
2
3
4
5
(gdb) c
Continuing.
string(10) "hello php7"
123
Program exited normally.

DBD调试C程序

准备代码文件test.c

1
2
3
4
5
#include <stdio.h>
int main(int argc, const char * argv[]) {
printf("hello world");
return 0;
}

gcc编译

使用gdb调试,要使用-g参数保留代码的文字信息,便于调试 输出到test可执行文件

  1. 创建符号表,符号表包含了程序中使用的变量名称的列表。
  2. 关闭所有的优化机制,以便程序执行过程中严格按照原来的C代码进行。

gcc -g test.c -o test

gdb 调试

1
2
3
4
5
6
7
8
9
10
11
12
gdb test
NU gdb (GDB) Red Hat Enterprise Linux (7.2-56.el6)
Copyright (C) 2010 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 /home/php7internal/members/zhaoyunfeng/test...done.
(gdb)

然后就可以愉快的调试啦

扩展阅读

gcc -o 优化等级

-O设置一共有五种:-O0、-O1、-O2、-O3和-Os。
除了-O0以外,每一个-O设置都会多启用几个选项

各个优化等级:

  • -O0:这个等级(字母“O”后面跟个零)关闭所有优化选项,也是CFLAGS或CXXFLAGS中没有设置-O等级时的默认等级。这样就不会优化代码,这通常不是我们想要的。
  • -O1:这是最基本的优化等级。编译器会在不花费太多编译时间的同时试图生成更快更小的代码。这些优化是非常基础的,但一般这些任务肯定能顺利完成。
  • -O2:-O1的进阶。这是推荐的优化等级,除非你有特殊的需求。-O2会比-O1启用多一些标记。设置了-O2后,编译器会试图提高代码性能而不会增大体积和大量占用的编译时间。
  • -O3:这是最高最危险的优化等级。用这个选项会延长编译代码的时间,并且在使用gcc4.x的系统里不应全局启用。自从3.x版本以来gcc的行为已经有了极大地改变。在3.x,-O3生成的代码也只是比-O2快一点点而已,而gcc4.x中还未必更快。用-O3来编译所有的软件包将产生更大体积更耗内存的二进制文件,大大增加编译失败的机会或不可预知的程序行为(包括错误)。这样做将得不偿失,记住过犹不及。在gcc 4.x.中使用-O3是不推荐的。
  • -Os:这个等级用来优化代码尺寸。其中启用了-O2中不会增加磁盘空间占用的代码生成选项。这对于磁盘空间极其紧张或者CPU缓存较小的机器非常有用。但也可能产生些许问题,因此软件树中的大部分ebuild都过滤掉这个等级的优化。使用-Os是不推荐的。