libgmalloc(3) | Library Functions Manual | libgmalloc(3) |
libgmalloc
—
(Guard Malloc), an aggressive debugging malloc
library
libgmalloc
is a debugging malloc library
that can track down insidious bugs in your code or library. If your
application crashes when using libgmalloc, then you've found a bug.
libgmalloc is used in place of the standard system malloc, and uses the virtual memory system to identify memory access bugs. Each malloc allocation is placed on its own virtual memory page (or pages). By default, the returned address for the allocation is positioned such that the end of the allocated buffer is at the end of the last page, and the next page after that is kept unallocated. Thus, accesses beyond the end of the buffer cause a bad access error immediately. When memory is freed, libgmalloc deallocates its virtual memory, so reads or writes to the freed buffer cause a bad access error. Bugs which had been difficult to isolate become immediately obvious, and you'll know exactly which code is causing the problem.
Guard Malloc is thread-safe and works for all uses of malloc(), Objective-C's alloc method, C++'s new operator, and other functions which result in allocation in the malloc heap.
As of Mac OS X 10.11, Guard Malloc works with purgeable memory. Allocations of *any* size that are allocated through malloc_zone_malloc(malloc_default_purgeable_zone(), <size>) are handled as purgeable memory. When malloc_make_purgeable() is called for a purgeable allocation, the memory is immediately purged. Attempting to access that memory will then cause a crash. To access that memory successfully, first call malloc_make_nonpurgeable() then recreate the data.
As of Mac OS X 10.5, libgmalloc aligns the start of allocated buffers on 16-byte boundaries by default, to allow proper use of vector instructions (e.g., SSE). (The use of vector instructions is common, including in some Mac OS X system libraries. The regular system malloc also uses 16-byte alignment.) Because of this 16-byte alignment, up to 15 bytes at the end of an allocated block may be excess at the end of the page, and libgmalloc will not detect buffer overruns into that area by default. This default alignment can be changed with environment variables.
libgmalloc is available in /usr/lib/libgmalloc.dylib. To use it, set this environment variable:
set DYLD_INSERT_LIBRARIES to /usr/lib/libgmalloc.dylib
Note: it is no longer necessary to set DYLD_FORCE_FLAT_NAMESPACE.
This tells dyld to use Guard Malloc instead of the standard version of malloc. Run the program, and wait for the crash indicating the bad access. When the program crashes, examine it in the debugger to identify the cause.
As of Mac OS X 10.6, libgmalloc can be used with the standard malloc stack logging by setting the MallocStackLogging environment variable. The malloc_history(1) command can then be used to show backtraces of all malloc and free events made when using libgmalloc.
Because the goal of libgmalloc is to "encourage" your application to crash if memory access errors occur, it is best to run your application under a debugger such as the Xcode IDE's debugger, or lldb at the command line.
To use Guard Malloc with the Xcode debugger, choose Edit Scheme... from the Scheme popup. Click on the Diagnostics tab then turn on the Enable Guard Malloc check box. Then when launching the target application, Xcode automatically sets the DYLD_INSERT_LIBRARIES environment variable properly. Xcode retains that setting with that executable. To set any of the additional environment variables described below, click on the Arguments tab in the Scheme editor and add them in the Environment Variables section.
If you're using lldb from the command line, use lldb's "settings set target.env-vars VAR=VALUE" command to set the environment variables. Or simply use the "env VAR=VALUE" command alias.
% cat gmalloctest.c #include <stdlib.h> #include <stdio.h> int main(int argc, char **argv) { unsigned *buffer = (unsigned *)malloc(sizeof(unsigned) * 100); unsigned i; for (i = 0; i < 200; i++) { buffer[i] = i; } for (i = 0; i < 200; i++) { printf ("%d ", buffer[i]); } } % cc -g -o gmalloctest gmalloctest.c % lldb gmalloctest Current executable set to 'gmalloctest' (x86_64). (lldb) env DYLD_INSERT_LIBRARIES=/usr/lib/libgmalloc.dylib (lldb) process launch Process 7895 launched: '/private/tmp/testit/gmalloctest' (x86_64) GuardMalloc[gmalloctest-7895]: Allocations will be placed on 16 byte boundaries. GuardMalloc[gmalloctest-7895]: - Some buffer overruns may not be noticed. GuardMalloc[gmalloctest-7895]: - Applications using vector instructions (e.g., SSE) should work. GuardMalloc[gmalloctest-7895]: version 105 Process 7895 stopped * thread #1: tid = 0x6880e, 0x0000000100000eda gmalloctest`main(argc=1, argv=0x00007fff5fbffa00) + 74 at gmalloctest.c:9, stop reason = EXC_BAD_ACCESS (code=1, address=0x100342000) frame #0: 0x0000000100000eda gmalloctest`main(argc=1, argv=0x00007fff5fbffa00) + 74 at gmalloctest.c:9 6 unsigned i; 7 8 for (i = 0; i < 200; i++) { -> 9 buffer[i] = i; 10 } 11 12 for (i = 0; i < 200; i++) { (lldb) print i (unsigned int) $0 = 100 (lldb) print &buffer[i] (unsigned int *) $1 = 0x0000000100342000
Once you have the backtrace, you can examine that line of source code to see what variable was accessed, and determine why that address was invalid memory. In the example above, notice that it crashes when it tries to write one character beyond the end of the malloc'ed buffer it's operating on, causing a bad access error when accessing the protected page following the string.
These sorts of problems may seem minor, especially when the application normally behaves correctly. However, they're usually the hallmark of intermittent bugs or unexplained crashes in long running programs. In normal use, the bug in the example program might have caused no problems at all... or it might have trashed the following buffer, leading occasionally to corrupted data. If the application had been referencing freed memory, the program might have worked fine until the one time where the freed memory was immediately reused and modified.
libgmalloc's behavior can be changed with several additional environment variables:
MALLOC_LOG_FILE
<f>
MALLOC_PROTECT_BEFORE
MALLOC_FILL_SPACE
MALLOC_ALLOW_READS
MALLOC_VECTOR_SIZE
MALLOC_WORD_SIZE
MALLOC_STRICT_SIZE
MALLOC_ALLOW_LARGE_REQUESTS
MALLOC_MAXIMUM_VM
MALLOC_CHECK_HEADER
MallocStackLogging
It's often useful to understand how Guard Malloc uses memory when debugging. Guard Malloc writes strange byte sequences to catch certain problems. If the MALLOC_FILL_SPACE environment variable is set, newly allocated buffers will be filled with the value 0x55 in hopes of catching references to uninitialized memory.
The space right before the buffer is dedicated to header information. If MALLOC_PROTECT_BEFORE was set, the header immediately follows the buffer. The header is 16 bytes in 32-bit processes and 32 bytes in 64-bit processes and is organized as:
magic number (0xdeadbeef in 32-bit, or 0xdeadbeefdeadbeef in
64-bit)
size of buffer + size of header
thread id
magic number again
Because each allocation requires at least two pages of virtual memory, in 32-bit processes only about 500,000 malloc allocations could exist before the process runs out of virtual memory.
Processes using Guard Malloc may run more slowly. In addition, the extra pressure on the virtual memory system when running a process with Guard Malloc can cause top(1) to update its output more slowly.
Don't forget -- if there's a memory bug in your program, the program will crash in Guard Malloc. This is a feature!
Mar. 18, 2015 | Mac OS X |