Question
I recently saw a reference to the LD_PRELOAD trick, but it was not explained clearly.
For example, I saw a comment like this:
do you know that you have allocation performance issue?
If the LD_PRELOAD trick leads to considerable performance increases you might have one. Trying it is easy.
I suspect this has something to do with replacing memory allocation behavior at runtime, but I am not sure. What exactly is the LD_PRELOAD trick, and how does it work?
Short Answer
By the end of this page, you will understand what LD_PRELOAD is on Linux, how it lets you load a shared library before others, and why that can change program behavior without recompiling. You will also see how it is used to override functions such as malloc, add logging, debug issues, and sometimes improve performance.
Concept
LD_PRELOAD is an environment variable used by the Linux dynamic linker/loader. It tells the system to load one or more shared libraries before the normal libraries a program would use.
Because those libraries are loaded first, functions inside them can be chosen instead of the usual implementations from system libraries such as libc.
Why this matters
Many programs on Linux use dynamic linking. That means functions like these are not always built directly into the executable:
malloc()
printf()
open()
read()
Instead, the program calls versions provided by shared libraries at runtime.
If you set LD_PRELOAD to your own shared library, and that library defines a function with the same name and compatible signature, your version can be used first.
Why people call it a “trick”
It feels like a trick because you can:
- change behavior without modifying source code
- inspect calls inside a program
- replace slow implementations with faster ones
- inject debugging, metrics, or validation
A classic example is replacing the default memory allocator with another allocator such as jemalloc or tcmalloc:
LD_PRELOAD=/path/to/libjemalloc.so ./my_program
If performance improves a lot, that may suggest the program spends significant time in memory allocation or suffers from allocator contention/fragmentation.
Mental Model
Imagine a program is about to enter a building and asks a receptionist where to find services like memory allocation, file access, and printing.
Normally, the receptionist points to the standard staff in the building.
LD_PRELOAD is like placing your own staff at the front desk before the normal staff arrive.
So when the program asks for malloc(), the receptionist sees your replacement first and sends the program there.
That replacement can:
- do the work itself
- log the request and then forward it
- reject the request
- optimize the behavior
So the key idea is:
- the program asks for a function name
- the dynamic linker decides which library provides it
LD_PRELOADchanges that search order
Syntax and Examples
The basic syntax is:
LD_PRELOAD=/path/to/library.so ./program
You can also preload multiple libraries:
LD_PRELOAD="/path/a.so /path/b.so" ./program
Example: overriding puts
Here is a small shared library that replaces puts:
#define _GNU_SOURCE
#include <stdio.h>
int puts(const char *s) {
return printf("[intercepted] %s\n", s);
}
Compile it as a shared library:
gcc -shared -fPIC -o libhook.so hook.c
Now run another program with it preloaded:
LD_PRELOAD=./libhook.so ./my_program
If my_program calls puts("Hello"), it may print:
Step by Step Execution
Consider this wrapper:
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
void *malloc(size_t size) {
static void *(*real_malloc)(size_t) = NULL;
if (!real_malloc) {
real_malloc = dlsym(RTLD_NEXT, "malloc");
}
void *ptr = real_malloc(size);
fprintf(stderr, "allocated %zu bytes at %p\n", size, ptr);
return ptr;
}
Suppose the target program runs this code:
char *buffer = malloc(32);
Here is what happens step by step:
- The shell starts the program with
LD_PRELOAD=./libmalloc_log.so. - The dynamic linker loads
libmalloc_log.sobefore the standard libraries. - The target program calls
malloc(32).
Real World Use Cases
LD_PRELOAD is useful in several practical situations.
1. Testing another memory allocator
A team suspects that allocation is a bottleneck in a multithreaded C or C++ program. They try:
LD_PRELOAD=/usr/lib/libjemalloc.so ./server
If latency or throughput improves, that points to allocator-related performance issues.
2. Logging file access
A developer wants to know which files a program opens. They wrap open() and log every path.
3. Debugging library calls
A bug only happens in production. Without changing the app code, a developer injects a wrapper around read(), write(), or connect() to capture parameters.
4. Temporary hotfixes
If a third-party binary uses a problematic library function in a known way, a wrapper can sometimes patch behavior until a proper fix is deployed.
5. Enforcing policy
An organization may preload a library that blocks dangerous calls or adds audit logging.
6. Fault injection
During testing, a wrapper can randomly fail malloc() or file operations to ensure the program handles errors correctly.
Real Codebase Usage
In real projects, LD_PRELOAD is usually not the main application architecture. It is more often a tooling or systems technique.
Common patterns
Guarded instrumentation
Teams build a shared library that wraps functions only for debugging environments:
LD_PRELOAD=./libdebug_hooks.so ./app
This avoids changing production code paths permanently.
Performance experiments
Instead of rewriting memory management immediately, developers first test whether a different allocator changes performance.
That is why comments like this appear:
“If the LD_PRELOAD trick helps, you may have an allocation performance issue.”
The idea is not that LD_PRELOAD itself is faster. The idea is that the replacement library may be faster or more scalable.
Validation and error detection
A preloaded library may:
- detect invalid frees
- count allocations
- track leaks
- verify API usage
Wrappers with early-return checks
A wrapper might validate arguments before forwarding the call:
if (size == 0) {
return ;
}
Common Mistakes
1. Forgetting that function signatures must match
If your replacement function does not exactly match the original signature, behavior is undefined or broken.
Broken example:
int malloc(int size) {
return 0;
}
Correct:
void *malloc(size_t size) {
return NULL;
}
2. Causing infinite recursion
If your replacement malloc calls malloc again, it will call itself repeatedly.
Broken example:
void *malloc(size_t size) {
printf("allocating %zu\n", size);
return malloc(size);
}
Why it breaks:
malloccalls itself- recursion never ends
Use to call the real one.
Comparisons
| Concept | What it does | Best use | Limitation |
|---|---|---|---|
LD_PRELOAD | Loads a library first so its symbols can override others | Runtime interception, debugging, allocator replacement | Mostly for dynamically linked programs |
| Recompiling the program | Changes the source and rebuilds the binary | Permanent fixes and clean integration | Requires source code and build access |
| Static linking | Bundles library code into the executable | Self-contained binaries | Harder to override at runtime |
| Debugger breakpoints | Inspects behavior while running | Interactive debugging | Less convenient for always-on interception |
strace | Traces system calls | Observing kernel-facing behavior |
Cheat Sheet
# Preload one library
LD_PRELOAD=/path/to/libhook.so ./program
# Preload multiple libraries
LD_PRELOAD="/path/a.so /path/b.so" ./program
Key points
LD_PRELOADis an environment variable used by the dynamic linker.- It loads your shared library before normal libraries.
- Your library can override dynamically linked functions.
- Common uses: logging, debugging, allocator replacement, testing.
- Usually works with dynamically linked executables.
- Often restricted for security-sensitive binaries.
Common pattern for wrapping a function
#define _GNU_SOURCE
#include <dlfcn.h>
static return_type (*real_func)(args...) = NULL;
return_type func(args...) {
if (!real_func) {
real_func = dlsym(RTLD_NEXT, "func");
}
// custom logic
return real_func(...);
}
Watch out for
- exact function signatures
- recursion
- thread-safety
- functions that allocate inside allocation hooks
- static binaries
Performance interpretation
FAQ
What does LD_PRELOAD do in Linux?
It tells the dynamic linker to load specific shared libraries before the normal ones, allowing their functions to override standard implementations.
Is LD_PRELOAD only for memory allocation?
No. It can override many dynamically linked functions, such as file, network, and output functions, not just malloc.
Why would changing the allocator improve performance?
Different allocators perform differently under different workloads, especially with many threads or many small allocations.
Does LD_PRELOAD work on statically linked binaries?
Usually no. Static binaries include their own code directly, so there is no dynamic symbol lookup to override in the same way.
Is LD_PRELOAD safe to use in production?
It can be useful, but it should be used carefully. A bad wrapper can cause crashes, recursion, or hard-to-debug behavior.
What is RTLD_NEXT used for?
It finds the next matching symbol after the current shared library, which is how wrappers call the original function.
Why do privileged programs ignore LD_PRELOAD?
Because allowing arbitrary library injection into privileged processes would be a major security risk.
Is the “LD_PRELOAD trick” itself a performance optimization?
Not directly. The improvement comes from whatever library you preload, such as a faster allocator or instrumentation library.
Mini Project
Description
Build a small shared library that intercepts puts() and adds a prefix to every printed line. This project demonstrates the core idea behind LD_PRELOAD: changing a program’s behavior at runtime without editing or recompiling the target program.
Goal
Create a preloadable shared library in C and use it to override puts() in another dynamically linked program.
Requirements
- Write a C shared library that defines a replacement
puts()function. - Compile the library with position-independent code and shared library flags.
- Create a small test program that calls
puts(). - Run the test program normally and then again with
LD_PRELOADset. - Observe how the output changes when the library is preloaded.
Keep learning
Related questions
Array-to-Pointer Conversion in C and C++ Explained
Learn what array-to-pointer conversion means in C and C++, how array decay works, and how it differs from a pointer to an array.
Building More Fault-Tolerant Embedded C++ Applications for Radiation-Prone ARM Systems
Learn practical C++ and compile-time techniques to reduce soft-error damage in embedded ARM systems exposed to radiation.
C Pointer to Array vs Array of Pointers: How to Read Complex Declarations
Learn the difference between pointer-to-array and array-of-pointers in C, plus a simple rule for reading complex declarations correctly.