This blog post explains how ltrace works, internally. This is a great companion post to our previous blog post which describes strace internals.

We’ll begin by examining the difference between ltrace and strace. Next, we’ll move on to examining the ptrace system call and the ways in which ltrace uses it to get information about library calls being made in a running process.

ltrace vs strace

strace is a system call and signal tracer. It is primarily used to trace system calls (that is, function calls made from programs to the kernel), print the arguments passed to system calls, print return values, timing information and more. It can also trace and output information about signals received by the process.

As described in our previous blog post, strace relies on the ptrace system call.

ltrace is a library call tracer and it is primarily used to trace calls made by programs to library functions. It can also trace system calls and signals, like strace.

Both programs have some similar command line options for things like printing timing information, return values, attaching to running processes, and following forked processes.

ltrace also relies on the ptrace system call, but tracing library functions works differently than tracing system calls and this is where the tools differ.

Before we can examine how ltrace uses ptrace, we need to understand a few key ideas.

How do programs call functions in libraries?

Shared libraries can be loaded at any address. This means that functions within shared libraries will exist at an address that is not known until the library is loaded at runtime. Subsequent runs of the same program with the same libraries will load the same libraries at different addresses.

So, how do programs call functions at addresses that are unknown?

The short answer is: it depends on the binary format, operating system, and loader. On Linux, it is a delicate dance between the program and the dynamic linker.

And now, the long answer.

Programs on Linux use the [ELF binary format]( which provides for many features. For the purposes of understanding how library functions are called, we’ll direct our attention to the Procedure Linkage Table (PLT) and the Global Offset Table (GOT).

The PLT contains a group of assembly instructions per library function that executes when a library function is called. Groups of assembly instructions are often called “trampolines.”

Here’s an example of a PLT trampoline: