百分百源码网-让建站变得如此简单! 登录 注册 签到领金币!

主页 | 如何升级VIP | TAG标签

当前位置: 主页>网站教程>网页制作> php怎样进行内存调试
分享文章到:

php怎样进行内存调试

发布时间:09/01 来源:未知 浏览: 关键词:

Valgrind简介

Valgrind是很多Unix环境下使用的知名工具,可以在任何C/C++编写的软件中调试很多常见的内存问题。 Valgrind 是有关内存调试的多功效前端工具。最常用的底层工具称为 “memcheck”。它的工作方式是用本人的堆分配更换每个libc的堆分配,并跟踪你对它们所做的事情。你大概还会对 “massif” 感乐趣: 它是一个内存跟踪器,关于理解程序的常规堆内存使用状况非常有用。

留意

你应当阅读Valgrind文档,以进一步理解。 它写得很好,带有一些典型的例子。

为了停止内存分配更换,你需要通过 valgrind 运转要剖析的程序(此处为PHP),也就是启动 valgrind 二进制文件。

当 valgrind 更换并跟踪所有 libc 的堆分配时,它往往会大大落低调试程序的速度。关于PHP,你会留意到它。尽管 PHP 的速度下落并不那么猛烈,但是依然可以分明地感受到;假如你留意到它,不消担忧,这是正常的。

Valgrind 不是你大概会使用的独一工具,但是是最常用的工具。还有其他工具,例如 Dr.Memory、LeakSanitizer、Electric Fence、AddressSanitizer。

在开端此前

以下是在储备器调试方面具有良好经历并减轻发明缺陷并减少调试时间的时机所需的步骤:

-您应始终使用PHP的调试版本。尝试调试生产版本中的内存是可有可无的。
-您应当始终在 USE_ZEND_ALLOC = 0 环境下启动调试器。您大概已经在Zend Memory Manager章节中理解到,此环境var会在当前进程启动时禁用ZendMM。热烈倡议在启动内存调试器时这样做。完全绕过ZendMM有助于理解valgrind生成的跟踪。
-热烈倡议在环境 ZEND_DONT_UNLOAD_MODULES = 1 下启动内存调试器。这样可以防止PHP在历程完毕时卸载扩展程序的.so文件。这是为了获得更好的valgrind报告跟踪;假如在valgrind将要显示其错误时PHP将卸载扩展名,则稍后将不完全,由于从中猎取信息的文件不再是进程内存映像的一部分。
-您大概需要一些按捺办法。当您告诉PHP在历程完毕时不要卸载其扩展名时,大概会在valgrind输出中给您误报。将检查PHP扩展可否走漏,假如您在平台上误报,则可以使用按捺功效将其关闭像这样。可以按照这样的示例随便编写本人的文件。
-与Zend Memory Manager比拟,Valgrind明显是更好的工具,可以查寻走漏和其他与内存相关的问题。您应当始终在代码上运转valgrind,这实际上是每个C程序员都必需施行的步骤。不管是由于崩溃而想要寻到并调试它,还是作为看起来仿佛没有任何害处的高质量工具来运转它,valgrind都是这种工具,它可以指出潜藏的瑕疵,预备好将其吹拂一次或今后。即便您认为代码好像一切都很好,也可以使用它:您大概会感到惊奇。

Warning

您**必需在程序上使用valgrind(或任何内存调试器)。关于每个强大的C程序,要不调试内存就不成能100%充满信念。内存错误解致使有害的平安问题,并且程序崩溃平常取决于很多参数,平常是随机的。

内存走漏检测示例

入门

Valgrind是一个完全的堆内存调试器。它还可以调试历程内存映射和功效堆栈。请在其文档中猎取更多信息。

让我们去检测动态内存走漏,并尝试一个简便的,最常见的走漏:

PHP_RINIT_FUNCTION(pib)
{
    void *foo = emalloc(128);
}

上面的代码每次恳求都会走漏128字节,由于它没有与此类缓冲区有关的efree()相关调取。由于它是对emalloc()的调取,因此会通过Zend Memory Manager,因此稍后会警告我们就像我们在ZendMM章节中看到的那样。我们还要看看valgrind可否可以留意到走漏:

> ZEND_DONT_UNLOAD_MODULES=1 USE_ZEND_ALLOC=0 valgrind --leak-check=full --suppressions=/path/to/suppression
--show-reachable=yes --track-origins=yes ~/myphp/bin/php -dextension=pib.so /tmp/foo.php

我们使用valgrind启动PHP-CLI进程。我们在这里假设一个名为“ pib”的扩展名。这是输出:

==28104== 128 bytes in 1 blocks are definitely lost in loss record 1 of 1
==28104==    at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==28104==    by 0xA3701E: __zend_malloc (zend_alloc.c:2820)
==28104==    by 0xA362E7: _emalloc (zend_alloc.c:2413)
==28104==    by 0xE896F99: zm_activate_pib (pib.c:1880)
==28104==    by 0xA79F1B: zend_activate_modules (zend_API.c:2537)
==28104==    by 0x9D31D3: php_request_startup (main.c:1673)
==28104==    by 0xB5909A: do_cli (php_cli.c:964)
==28104==    by 0xB5A423: main (php_cli.c:1381)

==28104== LEAK SUMMARY:
==28104==    definitely lost: 128 bytes in 1 blocks
==28104==    indirectly lost: 0 bytes in 0 blocks
==28104==    possibly lost: 0 bytes in 0 blocks
==28104==    still reachable: 0 bytes in 0 blocks
==28104==    suppressed: 7,883 bytes in 40 blocks

在我们看来,“绝对失落”是我们必需关注的。

Note

有关memcheck输出的不一样字段的具体信息,请查看。

Note

我们使用USE_ZEND_ALLOC = 0禁用并完全绕过Zend Memory Manager。对其API的每次调取(例如emalloc())将直接致使libc调取,就像我们在calgrind输出堆栈帧上可以看到的那样。

Valgrind抓住了我们的破绽。

很容易,此刻我们可以使用耐久分配(也就是绕过ZendMM并使用传统libc的动态内存分配)来发生走漏。走:

PHP_RINIT_FUNCTION(pib)
{
    void *foo = malloc(128);
}

这是报告:

==28758==    128 bytes in 1 blocks are definitely lost in loss record 1 of 1
==28758==    at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==28758==    by 0xE896F82: zm_activate_pib (pib.c:1880)
==28758==    by 0xA79F1B: zend_activate_modules (zend_API.c:2537)
==28758==    by 0x9D31D3: php_request_startup (main.c:1673)
==28758==    by 0xB5909A: do_cli (php_cli.c:964)
==28758==    by 0xB5A423: main (php_cli.c:1381)

也抓到了。

Note

Valgrind确实可以捕捉所有内容。宏大的进程内存映射中某个地方的每一个被忘记的小字节都会被valgrind的眼睛报告。您没法通过。

更复杂的用例

这是一个更复杂的设定。您可以鄙人面的代码中发明走漏吗?

static zend_array ar;

PHP_MINIT_FUNCTION(pib)
{
    zend_string *str;
    zval string;

    str = zend_string_init("yo", strlen("yo"), 1);
    ZVAL_STR(&string, str);

    zend_hash_init(&ar, 8, NULL, ZVAL_PTR_DTOR, 1);
    zend_hash_next_index_insert(&ar, &string);
}

这里有两个走漏。第一,我们分配一个zend_string,但我们没有开释它。其次,我们分配一个新的zend_hash,但是我们也不开释它。让我们用valgrind启动它,然后查看结果:

==31316== 296 (264 direct, 32 indirect) bytes in 1 blocks are definitely lost in loss record 1 of 2
==32006==    by 0xA3701E: __zend_malloc (zend_alloc.c:2820)
==32006==    by 0xA814B2: zend_hash_real_init_ex (zend_hash.c:133)
==32006==    by 0xA816D2: zend_hash_check_init (zend_hash.c:161)
==32006==    by 0xA83552: _zend_hash_index_add_or_update_i (zend_hash.c:714)
==32006==    by 0xA83D58: _zend_hash_next_index_insert (zend_hash.c:841)
==32006==    by 0xE896AF4: zm_startup_pib (pib.c:1781)
==32006==    by 0xA774F7: zend_startup_module_ex (zend_API.c:1843)
==32006==    by 0xA77559: zend_startup_module_zval (zend_API.c:1858)
==32006==    by 0xA85AF5: zend_hash_apply (zend_hash.c:1508)
==32006==    by 0xA77B25: zend_startup_modules (zend_API.c:1969)

==31316== 32 bytes in 1 blocks are indirectly lost in loss record 2 of 2
==31316==    by 0xA3701E: __zend_malloc (zend_alloc.c:2820)
==31316==    by 0xE880B0D: zend_string_alloc (zend_string.h:122)
==31316==    by 0xE880B76: zend_string_init (zend_string.h:158)
==31316==    by 0xE896F9D: zm_activate_pib (pib.c:1781)
==31316==    by 0xA79F1B: zend_activate_modules (zend_API.c:2537)
==31316==    by 0x9D31D3: php_request_startup (main.c:1673)
==31316==    by 0xB5909A: do_cli (php_cli.c:964)
==31316==    by 0xB5A423: main (php_cli.c:1381)

==31316== LEAK SUMMARY:
==31316== definitely lost: 328 bytes in 2 blocks

如预测的那样,两个走漏都被报告。如您所见,valgrind是准确的,它将您的眼睛放在需要的地方。

此刻修复它们:

PHP_MSHUTDOWN_FUNCTION(pib)
{
    zend_hash_destroy(&ar);
}

我们在PHP程序完毕时在MSHUTDOWN中烧毁了耐久数组。创立它时,我们将其作为析构函数传递给ZVAL_PTR_DTOR,它将在插入的所有项目上运转该回调。这是zval的析构函数,它将毁坏zval剖析它们的内容。关于IS_STRING类型,析构函数将开释zend_string并在必要时开释它。做完了

Note

如您所见,PHP-像任何C说话强程序一样-充满了嵌套的指针。zend_string封装在zval中,其本身是zend_array的一部分。走漏数组明显会走漏zvalzend_string,但是zvals没有分配堆(我们在堆栈上分配),因此没有走漏报告。您应当习惯这样一个事实,即健忘开释/开释诸如zend_array之类的复合构造会致使大量走漏,由于构造经常会嵌入构造,嵌入构造等。

缓冲区上溢/下溢检测

内存走漏很糟糕。这将致使您的程序一次或今后触发OOM,并且将大大落低主机的速度,由于随着时间的流逝,后者将获得越来越少的可用内存。这是内存走漏的征兆。

但是更糟的是:缓冲区越界拜访。拜访超出分配限制的指针是很多险恶操纵(例如在运算机上获得root shell)的泉源,因此您绝对应当防止它们。较轻的越界拜访也经常会由于内存破坏而致使程序崩溃。但是,这全部取决于硬件目标运算机,使用的编译器和选项,操纵系统内存规划,使用的libc等……很多因素。

因此,越界拜访非常令人厌恶,它们是炸弹,大概会爆炸,也大概不会爆炸,或者在一分钟内,或者假如您非常荣幸,它们将永久不会爆炸。

  • Valgrind *是一个内存调试器,因此能够检测到来自任何内存区域(堆和堆栈)的任何越界拜访。这与查寻走漏使用的是雷同的memcheck工具。

让我们看一个简便的例子:

PHP_MINIT_FUNCTION(pib)
{
    char *foo = malloc(16);
    foo[16] = 'a';
    foo[-1] = 'a';
}

这段代码分配了一个缓冲区,并成心在边界后一个字节和边界后一个字节写入数据。此刻,假如您运转这样的代码,您就有大约两次时机中有一次马上崩溃,然后随机崩溃。您大概还已经在PHP中创立了一个平安破绽,但是它大概没法被长途利用(这种行动很少见)。

Warning

越界拜访致使不肯定的行动。没法预感会发生什么,但是请确保它不好(马上崩溃)或可怕(平安问题)。记得。

让我们问一下valgrind,使用与此前完全雷同的命令行来启动它,除了输出内容外,其他都没有改动:

==12802== Invalid write of size 1
==12802==    at 0xE896A98: zm_startup_pib (pib.c:1772)
==12802==    by 0xA774F7: zend_startup_module_ex (zend_API.c:1843)
==12802==    by 0xA77559: zend_startup_module_zval (zend_API.c:1858)
==12802==    by 0xA85AF5: zend_hash_apply (zend_hash.c:1508)
==12802==    by 0xA77B25: zend_startup_modules (zend_API.c:1969)
==12802==    by 0x9D4541: php_module_startup (main.c:2260)
==12802==    by 0xB5802F: php_cli_startup (php_cli.c:427)
==12802==    by 0xB5A367: main (php_cli.c:1348)
==12802==  Address 0xeb488f0 is 0 bytes after a block of size 16 alloc'd
==12802==    at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==12802==    by 0xE896A85: zm_startup_pib (pib.c:1771)
==12802==    by 0xA774F7: zend_startup_module_ex (zend_API.c:1843)
==12802==    by 0xA77559: zend_startup_module_zval (zend_API.c:1858)
==12802==    by 0xA85AF5: zend_hash_apply (zend_hash.c:1508)
==12802==    by 0xA77B25: zend_startup_modules (zend_API.c:1969)
==12802==    by 0x9D4541: php_module_startup (main.c:2260)
==12802==    by 0xB5802F: php_cli_startup (php_cli.c:427)
==12802==    by 0xB5A367: main (php_cli.c:1348)
==12802==
==12802== Invalid write of size 1
==12802==    at 0xE896AA6: zm_startup_pib (pib.c:1773)
==12802==    by 0xA774F7: zend_startup_module_ex (zend_API.c:1843)
==12802==    by 0xA77559: zend_startup_module_zval (zend_API.c:1858)
==12802==    by 0xA85AF5: zend_hash_apply (zend_hash.c:1508)
==12802==    by 0xA77B25: zend_startup_modules (zend_API.c:1969)
==12802==    by 0x9D4541: php_module_startup (main.c:2260)
==12802==    by 0xB5802F: php_cli_startup (php_cli.c:427)
==12802==    by 0xB5A367: main (php_cli.c:1348)
==12802==  Address 0xeb488df is 1 bytes before a block of size 16 alloc'd
==12802==    at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==12802==    by 0xE896A85: zm_startup_pib (pib.c:1771)
==12802==    by 0xA774F7: zend_startup_module_ex (zend_API.c:1843)
==12802==    by 0xA77559: zend_startup_module_zval (zend_API.c:1858)
==12802==    by 0xA85AF5: zend_hash_apply (zend_hash.c:1508)
==12802==    by 0xA77B25: zend_startup_modules (zend_API.c:1969)
==12802==    by 0x9D4541: php_module_startup (main.c:2260)
==12802==    by 0xB5802F: php_cli_startup (php_cli.c:427)
==12802==    by 0xB5A367: main (php_cli.c:1348)

这两个无效的写入都已被检测到,此刻您的目标是跟踪并修复它们。

在这里,我们使用了一个示例,其中我们超出范畴地写入内存,这是最糟糕的状况,由于您的写入操纵成功后(大概会马上致使SIGSEGV)将覆盖该指针旁边的一些关键区域。当我们使用libc的malloc()停止分配时,我们将覆盖libc用于治理和跟踪其分配的关键头尾块。取决于很多因素(平台,使用的libc,怎样编译等等),这将致使崩溃。

Valgrind也大概报告无效读取。这意味着您将在分配的指针的范畴之外施行内存读取操纵。更好的状况是块被覆盖,但您依然不该该拜访内存区域,在这种状况下又大概会致使马上崩溃,或者稍后崩溃,或者永久不会拜访?不要那样做

Note

一旦您在valgrind的输出中读取“ Invalid”,那对您来说真的很不好。不管是无效的读取还是写入,您的代码中都存在问题,因此您应当将这个问题视为高风险:此刻就真正修复它。

这是有关字符串连接的第二个示例:

char *foo = strdup("foo");
char *bar = strdup("bar");

char *foobar = malloc(strlen("foo") + strlen("bar"));

memcpy(foobar, foo, strlen(foo));
memcpy(foobar + strlen("foo"), bar, strlen(bar));

fprintf(stderr, "%s", foobar);

free(foo);
free(bar);
free(foobar);

你能发明问题吗?

让我们问一下valgrind:

==13935== Invalid read of size 1
==13935==    at 0x4C30F74: strlen (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==13935==    by 0x768203E: fputs (iofputs.c:33)
==13935==    by 0xE896B91: zm_startup_pib (pib.c:1779)
==13935==    by 0xA774F7: zend_startup_module_ex (zend_API.c:1843)
==13935==    by 0xA77559: zend_startup_module_zval (zend_API.c:1858)
==13935==    by 0xA85AF5: zend_hash_apply (zend_hash.c:1508)
==13935==    by 0xA77B25: zend_startup_modules (zend_API.c:1969)
==13935==    by 0x9D4541: php_module_startup (main.c:2260)
==13935==    by 0xB5802F: php_cli_startup (php_cli.c:427)
==13935==    by 0xB5A367: main (php_cli.c:1348)
==13935==  Address 0xeb48986 is 0 bytes after a block of size 6 alloc'd
==13935==    at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==13935==    by 0xE896B14: zm_startup_pib (pib.c:1774)
==13935==    by 0xA774F7: zend_startup_module_ex (zend_API.c:1843)
==13935==    by 0xA77559: zend_startup_module_zval (zend_API.c:1858)
==13935==    by 0xA85AF5: zend_hash_apply (zend_hash.c:1508)
==13935==    by 0xA77B25: zend_startup_modules (zend_API.c:1969)
==13935==    by 0x9D4541: php_module_startup (main.c:2260)
==13935==    by 0xB5802F: php_cli_startup (php_cli.c:427)
==13935==    by 0xB5A367: main (php_cli.c:1348)

第1779行指向fprintf()调取。该调取确实要求fputs(),其本身称为strlen()(均来自libc),在这里strlen()读取1个字节无效。

我们只是健忘了\ 0来终止我们的字符串。我们传递fprintf()无效的字符串。它第一尝试运算调取strlen()的字符串的长度。然后strlen()将扫描缓冲区,直到寻到\ 0,并且它将扫描缓冲区的边界,由于我们健忘了对其停止零终止。我们在这里很荣幸,strlen()仅从末尾传递一个字节。那大概更多,并且大概崩溃了,由于我们真的不知道下一个\ 0在内存中的位置,这是随机的。

解:

size_t len   = strlen("foo") + strlen("bar") + 1;   /* note the +1 for \0 */
char *foobar = malloc(len);

/* ... ... same code ... ... */

foobar[len - 1] = '\0'; /* terminate the string properly */

Note

上述错误是C说话中最常见的错误之一。它们被称为一次性错误:您健忘仅分配一个字节,但是由于以下缘由,您将在代码中发生大量问题那。

最后,这里是最后一个示例,展现了一个有余使用的场景。这也是C编程中的一个非常常见的错误,与错误的内存拜访一样严峻:它创立了平安缺陷,大概致使非常厌恶的行动。明显,valgrind可以检测到无用后使用。这是一个:

char *foo = strdup("foo");
free(foo);

memcpy(foo, "foo", sizeof("foo"));

一样,这里是一个与PHP无关的PHP场景。我们开释一个指针,然后再使用它。这是一个大错误。让我们问一下valgrind:

==14594== Invalid write of size 1
==14594==    at 0x4C3245C: memcpy@GLIBC_2.2.5 (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==14594==    by 0xE896AA1: zm_startup_pib (pib.c:1774)
==14594==    by 0xA774F7: zend_startup_module_ex (zend_API.c:1843)
==14594==    by 0xA77559: zend_startup_module_zval (zend_API.c:1858)
==14594==    by 0xA85AF5: zend_hash_apply (zend_hash.c:1508)
==14594==    by 0xA77B25: zend_startup_modules (zend_API.c:1969)
==14594==    by 0x9D4541: php_module_startup (main.c:2260)
==14594==    by 0xB5802F: php_cli_startup (php_cli.c:427)
==14594==    by 0xB5A367: main (php_cli.c:1348)
==14594==  Address 0xeb488e0 is 0 bytes inside a block of size 4 free'd
==14594==    at 0x4C2EDEB: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==14594==    by 0xE896A86: zm_startup_pib (pib.c:1772)
==14594==    by 0xA774F7: zend_startup_module_ex (zend_API.c:1843)
==14594==    by 0xA77559: zend_startup_module_zval (zend_API.c:1858)
==14594==    by 0xA85AF5: zend_hash_apply (zend_hash.c:1508)
==14594==    by 0xA77B25: zend_startup_modules (zend_API.c:1969)
==14594==    by 0x9D4541: php_module_startup (main.c:2260)
==14594==    by 0xB5802F: php_cli_startup (php_cli.c:427)
==14594==    by 0xB5A367: main (php_cli.c:1348)
==14594==  Block was alloc'd at
==14594==    at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==14594==    by 0x769E8D9: strdup (strdup.c:42)
==14594==    by 0xE896A70: zm_startup_pib (pib.c:1771)
==14594==    by 0xA774F7: zend_startup_module_ex (zend_API.c:1843)
==14594==    by 0xA77559: zend_startup_module_zval (zend_API.c:1858)
==14594==    by 0xA85AF5: zend_hash_apply (zend_hash.c:1508)
==14594==    by 0xA77B25: zend_startup_modules (zend_API.c:1969)
==14594==    by 0x9D4541: php_module_startup (main.c:2260)
==14594==    by 0xB5802F: php_cli_startup (php_cli.c:427)
==14594==    by 0xB5A367: main (php_cli.c:1348)

这里的一切再次变得清楚。

结论

在投入生产此前,请使用内存调试器。正如您在本章中学到的那样,您在运算中健忘的小字节大概致使可利用的平安破绽。它还经常(非常频繁地)致使简便的崩溃。这意味着您的扩展很酷,可以减少整个效劳器(效劳器)及其每个客户端的数目。

C是一种非常严厉的编程说话。您将获得数十亿字节的内存来停止编程,并且必需安排这些内存来施行一些运算。但是请不要搞砸这种强大的功效:在最好的状况下(很少见到),什么都不会发生,在更坏的状况下(非常常见),您会在这里和那里随机崩溃,在最坏的状况下,您会创立一个破绽在刚好可以被长途利用的程序中...

您的工具纯熟,聪慧,请确实照料机器内存。

以上就是php怎样停止内存调试的具体内容,更多请关注百分百源码网其它相关文章!

打赏

打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

百分百源码网 建议打赏1~10元,土豪随意,感谢您的阅读!

共有151人阅读,期待你的评论!发表评论
昵称: 网址: 验证码: 点击我更换图片
最新评论

本文标签

广告赞助

能出一分力是一分吧!

订阅获得更多模板

本文标签

广告赞助

订阅获得更多模板