Before you start, make sure you have completed the previous lab. You can also use the code from the tp3_base branch of the git repository:
- git clone -b tp3_base https://gitlab.inf.telecom-sudparis.eu/csc4508/csc4508_facebook.git csc4508_tp3
- or git checkout tp3_base
The goal of this exercise is to create a library providing synchronization primitives, and use it in the facebook server developed during the previous labs. For this we use a mechanism allowing to "overload" functions: LD_PRELOAD .
When generating an executable, the linker stores the program using the ELF format: a section contains the code, a section contains the global variables, etc. The linker builds a symbol table that indicates, for each symbol (functions and variables), its section and its offset within the section. If the program uses a symbol without defining it, the linker marks the symbol as being located in the "U" ("unknown") section. A call to function foo (located in a shared library) thus translates into call<foo@plt> where <foo@plt> is an address in the plt ( Procedure Linkage Table ) section.
You can observe the contents of the table of symbols with the nm command:
When a program starts, the system loads the sections of the ELF file, then the required shared libraries. The addresses of loaded functions are inserted into the plt table. So, when the program calls the foo function, it jumps to the given address and therefore executes the code from the function located in the library.
The environment variable LD_PRELOAD allows to specify a list of libraries to be loaded before loading the libraries required by the program.
Using LD_PRELOAD, it is possible to modify the behavior of the foo function. We define a library (libfoo.so) which implements a foo function having the same signature as the foo function used by the program:
By running the application with LD_PRELOAD (LD_PRELOAD=./libfoo.so ./application ), the "new" foo function is inserted in the plt table first. When the application calls foo, it is therefore this function which is called:
LD_PRELOAD therefore makes it possible to modify the behavior of a library.
When intercepting a call to foo, it may be useful to create a "wrapper" function which performs a processing, then calls the original foo function, and then performs further processing. The wrapper system is typically used to log calls to a function:
For this, it is necessary to know the address of the original foo function. This address can be determined using the dlsym function:
The dlsym function allows to find the address of a symbol. The constant RTLD_NEXT is used to find the next definition of the symbol foo (i.e. the original foo function), and instead of the first entry in the plt table.
The __attribute __((constructor)) attribute indicates that the function must be called when loading the library. This initializes the library (here, to retrieve the address of the function original foo.
To invoke a function when unloading the library, you can use the attribute __attribute __((destructor)) in a similar way.
Create the liblock.so library. This library currently contains two functions static void liblock_init() and static void liblock_finalize() which are called when loading and terminating a program. These functions just display a message.
Modify the library so that it intercepts calls to pthread_mutex_lock. At each interception, print a message when entering and exiting the function and call the pthread_mutex_lock function. For example:
Complete the liblock.so library to intercept calls to other pthread_mutex_* and pthread_cond_* functions. For example:
Verify that the library is working by intercepting function calls from the facebook server.
We now want to use our own implementation of mutexes and conditions based on futex. For this it is necessary to use a counter for each mutex or condition. Since the application allocates pthread_mutex_t or pthread_cond_t, which we won't use (since we do not call the functions of the libpthread), let's use these memory areas to store our data! Just define a my_mutex_t structure, then cast a pthread_mutex_t into a my_mutex_t to access it.
Create the library libmylock.so which also intercepts pthread_mutex_* functions and pthread_cond_* but which implements the functions in based on futexes.
Make sure that the library is working by intercepting function calls from the facebook server.