1. Overview

gdb (GNU Debugger) is a popular tool for debugging programs written in several programming languages, including C and C++.

We usually set environment variables in the shell, and programs spawned from the shell inherit these environment variables. However, while debugging a program, we may need to run our program in a modified environment. The LD_PRELOAD environment variable is such an example that affects the selection of shared libraries at run-time.

In this tutorial, we’ll discuss how to use gdb together with the LD_PRELOAD environment variable. Firstly, we’ll look at an example. Then, we’ll learn about using gdb and LD_PRELOAD together using this example.

2. Examination of the Problem

We’ll use a shared library and a program that uses this shared library. Both are in the C programming language.

2.1. Example Setup

The shared library code is in hello_world.c:

#include <stdio.h>
#include "hello_world.h"

void hello() {
    printf("Hello World\n");
}

The hello() function just prints Hello World. The declaration of hello() is in the header file, hello_world.h:

#ifndef _HELLO_WORLD_H_
#define _HELLO_WORLD_H_

void hello();

#endif

The program that uses the shared library is main.c:

#include "hello_world.h"

int main() {
    hello();
    return 0;
}

The program calls the function hello() and then exits.

Let’s first build the shared library using gcc:

$ gcc -shared -fPIC -o libhello_world.so hello_world.c -Wl,-soname,libhello_world.so

The -shared option of gcc is necessary to build a shared library. The -fPIC option specifies the generation of position-independent code, which is necessary for shared libraries.

The -o option specifies the name of the shared library, which is libhello_world.so in our case.

The last option we pass to gcc, -Wl,-soname,libhello_world.so, specifies the soname of the shared library as libhello_world.so. The -Wl option is, in general, used for passing parameters to the linker.

Having built the shared library libhello_world.so, let’s build the executable main:

$ gcc -o main main.c -L. -lhello_world

The -L. option is necessary for the linker to locate the shared library libhello_world.so in the current directory. We specify the library to be linked with main using -lhello_world.

2.2. Running the Program

Having built the executable main, let’s run it first without using LD_PRELOAD:

$ ./main
./main: error while loading shared libraries: libhello_world.so: cannot open shared object file: No such file or directory

We aren’t successful in running the program as the shared library libhello_world.so cannot be located at run-time. This is the expected behavior. Now, let’s run it using LD_PRELOAD:

$ LD_PRELOAD=./libhello_world.so ./main 
Hello World

Now, the program runs successfully as expected. We specify the path of the shared library libhello_world.so that main depends on using LD_PRELOAD=./libhello_world.so.

Sometimes, we may need to debug a process using gdb. How can we use LD_PRELOAD with gdb then? Let’s first run the program with gdb without using LD_PRELOAD:

$ gdb --quiet ./main
Reading symbols from ./main...
(No debugging symbols found in ./main)
(gdb)

We use the –quiet option of gdb to suppress the introductory and copyright messages at the beginning. gdb starts and waits for us to start debugging the program. Let’s run the program using the run command of gdb:

(gdb) run
Starting program: /home/centos/work/gdb_ldpreload/main 
/home/centos/work/gdb_ldpreload/main: error while loading shared libraries: libhello_world.so: cannot open shared object file: No such file or directory
[Inferior 1 (process 5846) exited with code 0177]

We aren’t successful in running the program under gdb. The shared library couldn’t be located at run-time as before.

3. Solution

We can set an environment variable for the program that is debugged under gdb. We can use the set env command of gdb for this purpose:

(gdb) set env LD_PRELOAD ./libhello_world.so

This sets the LD_PRELOAD environment for the program to ./libhello_world.so. We can also use the set environment command instead of set env. They’re the same:

(gdb) set environment LD_PRELOAD=./libhello_world.so

As is apparent from the last assignment, we can do the assignment as LD_PRELOAD=./libhello_world.so using =.

We can check the value of an environment variable under gdb using the show env or show environment commands:

(gdb) show env LD_PRELOAD
LD_PRELOAD = ./libhello_world.so

Having set the environment variable LD_PRELOAD within gdb for our program, we can debug it now. Let’s run the program using run:

(gdb) run
Starting program: /home/centos/work/gdb_ldpreload/main 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Hello World
[Inferior 1 (process 2884) exited normally]

We see from the output that the program successfully runs this time and prints the message Hello World.

Let’s set LD_PRELOAD in the shell before gdb and start debugging the program:

$ LD_PRELOAD=./libhello_world.so gdb --quiet ./main
Reading symbols from ./main...
(No debugging symbols found in ./main)
(gdb) run
Starting program: /home/centos/work/gdb_ldpreload/main 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Hello World
[Inferior 1 (process 3037) exited normally]

We’re again successful in running the program. However, this isn’t a preferred usage because, this time, we set LD_PRELOAD for gdb. The process, which we spawn by using the run command of gdb, is a child process of the process corresponding to gdb. Therefore, the child process inherits LD_PRELOAD from the parent process. That is why we’re successful in the last example.

This isn’t a problem in our example, as gdb isn’t dependent on libhello_world.so. However, if we load a shared library on which gdb depends, then by using LD_PRELOAD, we may affect the behavior of gdb. For example, the shared library might be a mocking library that mocks some system calls either for debugging or for profiling.

4. Conclusion

In this article, we discussed how to use gdb together with LD_PRELOAD.

We saw that we can use the set env command of gdb for setting the environment of a process before starting the process under gdb.

Additionally, we learned that we could call LD_PRELOAD before the gdb command in the shell. However, this isn’t a preferred usage as this affects the environment of the process corresponding to gdb.