Basic memory management

The engine's memory management is implemented with features important to a system like PHP. The exact functionality of the engine's memory management and the optimizations performed are out of the scope of this document. However, a good understanding of its functionality provides a basis for a good understanding of the rest of the Hacker's Guide, and introduce you to terminology and functionality used throughout PHP.

The most important of its features for the Hacker, and the first thing to mention is tracking allocations. Tracking allocations allow the memory manager to avoid leaks, a thorn in the side of most Hackers. When PHP is built in debug mode (--enable-debug), detected leaks are reported, in a perfect world they would never get to deployment.

While tracking allocations is an important, and highly useful feature, the Hacker should not become lazy! Always attempt to resolve leaks before deploying your code, a memory leak in a SAPI environment can become a very big problem, very quickly.

Another, perhaps more incidental but still noteworthy, feature is that the memory manager is the part that allows a hard limit on memory usage for each instance of PHP. As we all know, there is no such thing as unlimited. If some code is running out of memory, it is likely to be written wrong, either by the Hacker, or the programmer of PHP. Limiting the memory therefore is not a restriction on the language that is supposed to be experienced in production, it is simply a way from stopping development environments from spiraling out of control when mistakes are made, and equally, when bugs are found in production.

From the Hacker's perspective, the memory management API looks very much like libc's (or whoever the Hacker prefers !) malloc implementation.

Main memory APIs
Prototype Description
void *emalloc(size_t size) Allocate size bytes of memory.
void *ecalloc(size_t nmemb, size_t size) Allocate a buffer for nmemb elements of size bytes and makes sure it is initialized with zeros.
void *erealloc(void *ptr, size_t size) Resize the buffer ptr, which was allocated using emalloc to hold size bytes of memory.
void efree(void *ptr) Free the buffer pointed by ptr. The buffer had to be allocated by emalloc.
void *safe_emalloc(size_t nmemb, size_t size, size_t offset) Allocate a buffer for holding nmemb blocks of each size bytes and an additional offset bytes. This is similar to emalloc(nmemb * size + offset) but adds a special protection against overflows.
char *estrdup(const char *s) Allocate a buffer that can hold the NULL-terminated string s and copy the s into that buffer.
char *estrndup(const char *s, unsigned int length) Similar to estrdup while the length of the NULL-terminated string is already known.

Hinweis: The engine's memory management functions do not return NULL upon failure, if memory cannot be allocated at runtime, the engine bails and raises an error.

Achtung

Always use valgrind before deploying code and as a normal part of the Hacker's process. The engine can only report and detect leaks where it has allocated the memory. All of PHP is only a thin wrapper around third parties, those third parties do not use the engines memory management. Additionally, valgrind will catch errors that do not always halt or even have an apparent effect at execution time, it is just as important that there should be no errors, as it is important that avoidable leaks should be avoided.

Hinweis: Some leaks are unavoidable, some libraries rely on the end of a process to free some of their structures, this is normal under some circumstances and acceptable where it is out of the Hacker's control.

While executing in a debug environment, configured with --enable-debug and --enable-zend-test, the leak function used in the next example is actually implemented by the engine and is available to call in userland.

Beispiel #1 Leak Detection in Action

ZEND_FUNCTION(zend_leak_bytes)
{
    zend_long leakbytes = 3;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &leakbytes) == FAILURE) {
        return;
    }

    emalloc(leakbytes);
}

Das oben gezeigte Beispiel erzeugt eine ähnliche Ausgabe wie:

$ sapi/cli/php -r 'zend_leak_bytes();'
[Mon Apr 23 17:43:42 2018]  Script:  'Standard input code'
/home/mbaynton/projects/php-src/ext/zend_test/test.c(106) :  Freeing 0x00007f3203c92050 (3 bytes), script=Standard input code
=== Total 1 memory leaks detected ===

Hinweis: USE_ZEND_ALLOC=0 in the environment will stop the memory manager from functioning, all allocations fall back on the default system allocators which can be useful for debugging leaks, and should only be used for this purpose.