> 백엔드 개발 > PHP 튜토리얼 > PHP에서 메모리 디버깅을 수행하는 방법

PHP에서 메모리 디버깅을 수행하는 방법

coldplay.xixi
풀어 주다: 2023-04-09 11:52:02
앞으로
2753명이 탐색했습니다.

메모리 디버깅 是 이 장에서는 PHP 소스 코드의 메모리 디버깅에 대해 간략하게 소개합니다. 이것은 완전한 과정은 아닙니다. 메모리 디버깅은 어렵지 않지만 이를 사용하는 데 약간의 경험이 필요하며 C로 작성된 코드를 디자인할 때 많은 연습을 해야 할 것입니다. 여기서는 매우 유명한 메모리 디버거인 PHP에서 메모리 디버깅을 수행하는 방법valgrind

를 소개하고 이를 PHP와 함께 사용하여 메모리 문제를 디버깅하는 방법을 소개합니다.

관련 학습 권장 사항: 초보부터 마스터까지 PHP 프로그래밍

Valgrind 소개Valgrind는 많은 Unix 환경에서 사용되는 잘 알려진 도구이며 C/C++로 작성된 모든 소프트웨어에서 흔히 발생하는 많은 메모리 문제를 디버깅할 수 있습니다. . Valgrind는 메모리 디버깅을 위한 다목적 프런트 엔드 도구입니다. 가장 일반적으로 사용되는 하위 수준 도구는 "memcheck"입니다. 작동 방식은 각 libc의 힙 할당을 자체 힙 할당으로 대체하고 이를 사용하여 수행하는 작업을 추적하는 것입니다. "massif"에도 관심이 있을 수 있습니다. 이는 프로그램의 일반적인 힙 메모리 사용량을 이해하는 데 유용한 메모리 추적기입니다.

참고

더 자세히 이해하려면 Valgrind 설명서를 읽어야 합니다. 몇 가지 훌륭한 예와 함께 잘 작성되었습니다.

메모리 할당 교체를 수행하려면 프로파일링하려는 프로그램(이 경우 PHP)을 valgrind를 통해 실행해야 합니다. 즉, valgrind 바이너리를 시작해야 합니다.

valgrind가 모든 libc의 힙 할당을 교체하고 추적하면 디버거 속도가 크게 느려지는 경향이 있습니다. PHP의 경우에는 이를 알 수 있습니다. PHP 속도 저하가 그렇게 심하지는 않지만, 눈에 띄게 느껴지더라도 걱정하지 마십시오. 정상적인 현상입니다.

Valgrind는 여러분이 사용할 수 있는 유일한 도구는 아니지만 가장 일반적으로 사용되는 도구입니다. Dr.Memory, LeakSanitizer, Electric Fence, AddressSanitizer와 같은 다른 도구도 있습니다.

시작하기 전에

다음은 메모리 디버깅에 대한 좋은 경험을 갖고 결함 발견 가능성을 줄이고 디버깅 시간을 줄이는 데 필요한 단계입니다.

- 항상 디버그 버전의 PHP를 사용해야 합니다. 프로덕션 빌드에서 메모리를 디버깅하는 것은 관련이 없습니다.

- 항상

USE_ZEND_ALLOC = 0

으로 디버거를 시작해야 합니다. Zend 메모리 관리자 장에서 배웠듯이 이 환경 변수는 현재 프로세스가 시작될 때 ZendMM을 비활성화합니다. 메모리 디버거를 시작할 때 이 작업을 수행하는 것이 좋습니다. Valgrind에 의해 생성된 추적을 이해하려면 ZendMM을 완전히 우회하는 것이 도움이 됩니다.

-

ZEND_DONT_UNLOAD_MODULES = 1
환경에서 메모리 디버거를 시작하는 것이 좋습니다. 이렇게 하면 프로세스가 끝날 때 PHP가 확장의 .so 파일을 언로드하는 것을 방지할 수 있습니다. 이는 Valgrind의 보고서를 더 잘 추적하기 위한 것입니다. Valgrind가 오류를 표시하려고 할 때 PHP가 확장을 언로드하면 정보를 얻은 파일이 더 이상 프로세스 메모리 이미지의 일부가 아니기 때문에 나중에 불완전하게 됩니다. -억압이 필요할 수도 있습니다. 프로세스가 끝날 때 확장을 언로드하지 않도록 PHP에 지시하면 valgrind 출력에 잘못된 긍정이 표시될 수 있습니다. PHP 확장은 누출 여부를 검사하며, 플랫폼에서 거짓 긍정이 발생하면 억제 기능을 사용하여 이와 같이 기능을 끌 수 있습니다. 이와 같은 사례를 바탕으로 자유롭게 작성해 보세요. - Zend Memory Manager에 비해 Valgrind는 누수 및 기타 메모리 관련 문제를 찾는 데 확실히 더 나은 도구입니다. 항상 코드에서 valgrind를 실행해야 합니다. 이는 거의 모든 C 프로그래머가 수행해야 하는 단계입니다. 충돌이 나서 찾아서 디버깅하고 싶거나 해를 끼치지 않는 것처럼 보이는 고품질 도구로 실행하고 있기 때문에 valgrind는 숨겨진 결함을 지적하고 날려버릴 준비가 되어 있는 도구입니다. 한 번 또는 나중에. 코드에 모든 것이 괜찮아 보인다고 생각되더라도 이를 사용하세요. 놀랄 수도 있습니다.
경고

** 프로그램에서 valgrind(또는 모든 메모리 디버거)를 사용해야 합니다. 모든 강력한 C 프로그램과 마찬가지로 메모리 디버깅 없이는 100% 확신을 가질 수 없습니다. 메모리 오류는 유해한 보안 문제를 일으킬 수 있으며 프로그램 충돌은 종종 무작위로 많은 매개 변수에 따라 달라집니다.

메모리 누수 감지 예

시작하기

Valgrind는 완전한 힙 메모리 디버거입니다. 또한 절차적 메모리 맵과 함수 스택을 디버깅할 수도 있습니다. 자세한 내용은 해당 설명서를 참조하세요.

동적 메모리 누수를 감지하고 간단하고 가장 일반적인 누수를 시도해 보겠습니다.

PHP_RINIT_FUNCTION(pib)
{
    void *foo = emalloc(128);
}
로그인 후 복사

위의 코드는 해당 버퍼에 대한 efree() 관련 호출이 없기 때문에 요청당 128바이트를 누출합니다. 이는 emaloc()에 대한 호출이므로 Zend 메모리 관리자를 통과하므로 ZendMM 장에서 본 것처럼 나중에 경고를 받게 됩니다. 또한 valgrind가 누출을 인지할 수 있는지 살펴보겠습니다. 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

PHP_MINIT_FUNCTION(pib)
{
    char *foo = malloc(16);
    foo[16] = 'a';
    foo[-1] = 'a';
}
로그인 후 복사

우리는 valgrind를 사용하여 PHP-CLI 프로세스를 시작합니다. 여기서는 "pib"라는 확장자를 가정하겠습니다. 출력은 다음과 같습니다.
==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)
로그인 후 복사

우리 의견으로는 "완전히 잃어버린 것"이 우리가 집중해야 할 것입니다.

Note

memcheck가 출력하는 다양한 필드에 대한 자세한 내용을 확인하세요.

Note

Zend Memory Manager를 비활성화하고 완전히 우회하려면 USE_ZEND_ALLOC = 0을 사용합니다. calgrind 출력 스택 프레임에서 볼 수 있듯이 해당 API(예: emaloc())에 대한 모든 호출은 libc 호출로 직접 이어집니다.
  • Valgrind는 우리의 취약점을 포착했습니다.

쉽습니다. 이제 영구 할당(ZendMM을 우회하고 기존 libc를 사용하는 동적 메모리 할당이라고도 함)을 사용하여 누수를 생성할 수 있습니다. 이동:

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);
로그인 후 복사
로그인 후 복사

신고 내용은 다음과 같습니다.

==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)
로그인 후 복사
로그인 후 복사
도 잡혔습니다.

Note

Valgrind는 실제로 모든 것을 포착합니다. 거대한 프로세스 메모리 맵 어딘가에 있는 잊혀진 모든 작은 바이트는 Valgrind의 눈에 의해 보고됩니다. 통과할 수 없습니다.

더 복잡한 사용 사례

이것은 더 복잡한 설정입니다. 아래 코드에서 누수를 발견할 수 있나요?

size_t len   = strlen("foo") + strlen("bar") + 1;   /* note the +1 for <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false;">char *foo = strdup("foo");
free(foo);

memcpy(foo, "foo", sizeof("foo"));
로그인 후 복사
로그인 후 복사
*/ char *foobar = malloc(len); /* ... ... same code ... ... */ foobar[len - 1] = '
==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&#39;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&#39;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)
로그인 후 복사
로그인 후 복사
'; /* terminate the string properly */🎜여기서 두 가지 누출이 발생했습니다. 먼저, zend_string을 할당했지만 해제하지는 않았습니다. 둘째, 새로운 zend_hash를 할당하지만 해제하지는 않습니다. valgrind로 시작하여 결과를 살펴보겠습니다. 🎜rrreee🎜 예상대로 두 누출이 모두 보고되었습니다. 보시다시피, valgrind는 정확하며 필요한 곳에 눈을 둡니다. 🎜🎜지금 수정하세요: 🎜rrreee🎜 PHP 프로그램이 끝날 때 MSHUTDOWN에서 영구 배열을 삭제했습니다. 이를 생성할 때 ZVAL_PTR_DTOR에 소멸자로 전달하면 삽입된 모든 항목에 대해 해당 콜백이 실행됩니다. 이는 내용을 구문 분석하는 zval을 파괴하는 zval의 소멸자입니다. IS_STRING 유형의 경우 소멸자는 zend_string을 해제하고 필요한 경우 해제합니다. 완료 🎜🎜🎜 참고 🎜🎜 보시다시피 PHP는 C의 강력한 프로그램과 마찬가지로 중첩 포인터로 가득 차 있습니다. zend_stringzend_array의 일부인 zval에 캡슐화되어 있습니다. 배열을 누출하면 분명히 zvalzend_string이 누출되지만 zvals는 힙에 할당되지 않으므로(스택에 할당) 누출이 없습니다. 보고됩니다. zend_array와 같은 복합 구조를 해제/해제하는 것을 잊어버리면 구조에 내장된 구조, 내장된 구조 등이 있는 경우가 많기 때문에 많은 누출이 발생할 수 있다는 사실에 익숙해져야 합니다. 🎜🎜🎜버퍼 오버플로/언더플로 감지🎜🎜메모리 누수가 짜증납니다. 이로 인해 프로그램이 OOM을 한 번 이상 트리거하게 되며 호스트는 시간이 지남에 따라 사용 가능한 메모리가 점점 줄어들기 때문에 호스트 속도가 크게 느려집니다. 이는 메모리 누수의 징후입니다. 🎜🎜하지만 더 나쁜 점은 경계를 벗어난 액세스를 버퍼링하는 것입니다. 할당 제한을 넘어서 포인터에 액세스하는 것은 많은 악의적인 작업(예: 컴퓨터에 루트 셸을 가져오는 것)의 근원이므로 반드시 방지해야 합니다. 범위를 벗어난 사소한 액세스로 인해 메모리 손상으로 인해 프로그램이 중단되는 경우도 종종 있습니다. 그러나 이는 모두 하드웨어 대상 시스템, 사용된 컴파일러 및 옵션, OS 메모리 레이아웃, 사용된 libc 등에 따라 달라집니다. 많은 요인이 있습니다. 🎜🎜 따라서 범위를 벗어난 접근은 매우 성가신 일입니다. 이는 1분 내에 폭발할 수도 있고 그렇지 않을 수도 있는 🎜폭탄🎜이거나 운이 좋다면 결코 폭발하지 않을 것입니다. 🎜🎜🎜Valgrind*는 메모리 디버거이므로 모든 메모리 영역(힙 및 스택)에서 범위를 벗어난 액세스를 감지할 수 있습니다. 이는 누수를 찾는 데 사용되는 것과 동일한 memcheck 도구입니다. 🎜🎜🎜간단한 예를 살펴보겠습니다. 🎜rrreee🎜이 코드는 버퍼를 할당하고 의도적으로 경계 뒤 1바이트와 경계 뒤 1바이트에 데이터를 씁니다. 이제 이와 같은 코드를 실행하면 즉시 충돌이 발생하고 무작위로 충돌이 발생할 가능성이 약 2분의 1입니다. PHP에 보안 허점을 만들었을 수도 있지만 원격으로 악용할 수는 없습니다(이런 동작은 드뭅니다). 🎜🎜🎜경고🎜🎜범위를 벗어난 액세스는 정의되지 않은 동작으로 이어집니다. 어떤 일이 일어날지 예측할 수 있는 방법은 없지만 좋지 않거나(즉각적인 충돌) 끔찍하지 않은지(보안 문제) 확인하세요. 기억하다. 🎜🎜🎜valgrind에게 물어보고 이전과 똑같은 명령줄을 사용하여 시작하세요. 출력 외에는 아무것도 변경되지 않았습니다. 🎜rrreee🎜 두 가지 잘못된 쓰기가 모두 감지되었으며 이제 목표는 이를 추적하고 수정하는 것입니다. 🎜

在这里,我们使用了一个示例,其中我们超出范围地写入内存,这是最糟糕的情况,因为您的写入操作成功后(可能会立即导致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&#39;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] = &#39;\0&#39;; /* 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&#39;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&#39;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에서 메모리 디버깅을 수행하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
php
원천:learnku.com
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 추천
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿