(Linux) Library interposing: building a shim library

L7Sqr 2 Tallied Votes 1K Views Share

Often times it is useful to instrument a program you do not have the source to in an attempt to learn more about it. This can be for debugging purposes, blackbox testing, or for providing alternate versions of common functions/libraries (to name a few). On Linux, there is a very useful feature for doing just this: LD_PRELOAD.

In this post I will cover how to build a shim library for an alternate version of malloc. This is not an uncommon approach (valgrind originally did this) and can be a useful trick to have in your toolbox.

In general (and very simplified), the flow of execution can be thought of in the following way:

da2cbbe4d2b53e6e6c824e6fd0114f20

All functions defined in libraries (i.e. not in your program) will be loaded at runtime and when you call a function in that library control is handed over to the library implementation to do work on your behalf.

In the cases I describe above, however, you want to change that control flow without having access to the original source code or library implementation. Enter LD_PRELOAD. LD_PRELOAD allows you to modify the order of library loading to request a user-specified library be loaded prior to other libraries. Using this mechanism one can provide a function named the same as one found in libc.so (e.g. malloc) and have that code executed instead of the version found in libc.so.

This process is called interpositioning and it changes the above control flow into something similar to the following:

27799f3484a1e4434722bae51628e524

With that, you can provide whatever implementation you'd like for malloc. In this example, I simply print the details about the memory returned from libc malloc but you could provide an entirely separate memory allocation process here if you wanted to. You might do this, for example, if you wanted to test edge cases in a program where malloc returned NULL (often times not checked for).

Using the sample code I've provided you need to compile with _GNU_SOURCE into a shard library:

gcc -D_GNU_SOURCE -W -Wall -Wextra -Werror -fPIC -shared -Wl,-soname,libmymalloc.so -Wl,--no-as-needed -o libmymalloc.so -ldl mymalloc.c

Then you can provide that library when you execute the program:

LD_PRELOAD=/full/path/to/libmymalloc.so ./sample

Example:

[square@home (interpose)]$ LD_PRELOAD=/tmp/sample/libmymalloc.so ./sample 
Enter a number: 42
 [MALLOC] 42 bytes at 0x81c1008
#include <stdio.h>
#include <dlfcn.h>

/*
 * Sample user-supplied malloc implementation. 
 * This will be called prior to the libc version of malloc when
 * our library is preloaded using LD_PRELOAD
 * Will need to compile with -D_GNU_SOURCE to enable RTLD_NEXT support
 */
void *malloc (size_t size) {

    /*
     * dlsym with RTDL_NEXT pseudo handle grabs the next library
     * in the specified load order
     */
    void* (*libc_malloc)(size_t) = dlsym (RTLD_NEXT, "malloc");

    /*
     * In this example, there is no new implementation of malloc.
     * I simply report the memory that malloc returns back to the user.
     * Otherwise this is simply a passthrough to libc's version of 
     * malloc
     */
    void *mem = libc_malloc (size);

    if (mem) {
        fprintf (stderr, " [MALLOC] %zu bytes at %p\n", size, mem);
    }
    
    return mem;
}
Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.