Ltrace sits between strace and a debugger in the diagnostic toolbox: strace shows the system calls at the kernel boundary, ltrace shows the library calls at the userspace boundary, and the debugger shows everything but requires breakpoints. Ltrace traces the library function calls that a process makes, showing the function name, the arguments and the return value, and the trace is the answer to the question of what the application is doing inside its own libraries, the question that strace cannot answer because library calls happen before the system call boundary.
When ltrace is the right tool and when it is not
The diagnostic scenario that ltrace serves is the application that calls a library function with wrong arguments, that receives an unexpected return value, or that calls a function in a loop when it should call it once. The strace for this scenario shows the syscalls that the library functions make, but the library function names, the arguments at the library level and the return values at the library level are invisible to strace, because the library is compiled into the process and the calls are internal function calls, not system calls.
Ltrace's cost is higher than strace's: every library call is intercepted, decoded and printed, and the interception overhead on a hot path, a function called ten thousand times per second, is significant. The ltrace run on a production server is a diagnosis session, not a monitoring posture, and the tool is attached to a specific process for a specific question, then detached. The tool that runs continuously on production is perf or eBPF, and ltrace is the tool for the focused investigation of a specific library interaction.
Running ltrace and reading the output
The basic invocation follows a binary:
ltrace /usr/local/bin/myapp
which starts the application under ltrace's supervision and prints every library call with its arguments and return value. The output for a C program calling standard library functions looks like this:
malloc(1024) = 0x55a1b2c3d4e0
memcpy(0x55a1b2c3d4e0, "data", 4) = 0x55a1b2c3d4e0
strlen("data") = 4
free(0x55a1b2c3d4e0) = <void>
Each line is one library call: the function name, the arguments, and the return value after the equals sign. The return value is the diagnostic, and the NULL return from malloc, the negative return from a function expecting a non-negative value, and the unexpected value from a function that should return a specific result are the findings that the trace surfaces.
Filtering the trace to the library that matters
The full trace of a dynamically linked binary includes every library call to libc, libm, libpthread and every other linked library, and the output is enormous. The -e flag filters to specific functions, and the filter is the difference between a readable trace and an unreadable one:
ltrace -e 'malloc+free' /usr/local/bin/myapp # ltrace -e 'SSL_*' /usr/local/bin/myapp # ltrace -e 'pq*' /usr/local/bin/myapp
The first traces only memory allocation, the memory leak diagnostic. The second traces all OpenSSL functions, the TLS handshake diagnostic. The third traces all PostgreSQL client library functions, the database interaction diagnostic. The filter syntax is a glob pattern, and the asterisk matches any suffix, which is the wildcard that makes the function family filtering practical.
The attach mode for running processes
Ltrace attaches to a running process with the -p flag, the same attachment mode that strace uses:
ltrace -p <PID> -e 'SSL_read+SSL_write'
which attaches to the running process and traces only the SSL read and write calls, the diagnosis of a TLS connection issue without restarting the process. The attach uses ptrace, the same mechanism that the strace article described, and the overhead consideration is the same: the attached process runs slower while ltrace is attached, and the detachment, Ctrl+C, restores normal speed.
The comparison with strace and the boundary
The boundary between strace and ltrace is the system call boundary: strace shows everything above the kernel, ltrace shows everything below the application code. The strace output for a file read shows the open, read and close system calls, and the ltrace output for the same operation shows the fopen, fgets and fclose library calls that wrap those system calls. The two traces together give the full picture, the library calls that the application makes and the system calls that the library calls make, and the diagnostic that starts with strace and continues with ltrace is the diagnosis that follows the call chain from the application through the library to the kernel.
The checklist for the ltrace session
The ltrace session is a focused investigation, and the checklist is the procedure for the session. Identify the library that the diagnosis targets. Run ltrace with the -e filter for that library's functions. Exercise the code path that the diagnosis is investigating. Read the arguments and return values, looking for the unexpected. Detach, and correlate the ltrace findings with the strace findings and the application's own logs. The ltrace session is the diagnostic tool for the library-level bug, the argument mistake and the return value surprise, and the diagnosis that starts with the right tool ends in the right fix.
The C++ name demangling
The -C flag enables name demangling for C++ library calls:
ltrace -C -e 'std::*' /usr/local/bin/mycpp
The library filtering
The -l flag filters by library:
ltrace -l libssl.so.3 /usr/local/bin/myapp
The combination of -l and -e is the precise filter:
ltrace -l libssl.so.3 -e 'SSL_read+SSL_write' /usr/local/bin/myapp
The timing
The -tt flag adds timestamps to every line:
ltrace -tt -e 'malloc+free' /usr/local/bin/myapp
The relative timing between calls is valid while absolute numbers are inflated by tracing overhead.
The debugging workflow
The diagnostic workflow combines strace for system calls, ltrace for library calls, gdb for in-process state, and perf for sampling. Start with strace, continue with ltrace, use gdb for in-process state, perf for the statistical profile.
The interpreted languages
For Python, the ltrace on a process shows the libc calls the interpreter makes, not the Python calls. The C extension diagnostic is where ltrace serves: the Python module wrapping a C library, and ltrace shows the C library calls the extension makes.
The checklist
Identify the library to trace. Run ltrace with the filter. Exercise the code path. Read the arguments and return values. Use -C for C++ demangling. Use -tt for timing. Detach. Correlate with strace, gdb and perf. The ltrace session is the diagnostic for the library-level bug.
The signal handling and the interrupt behavior
Ltrace intercepts every library call, and the interception mechanism uses ptrace, the same mechanism that strace and gdb use. The ptrace attachment stops the process at every library call, decodes the arguments, prints the line and continues, and the stopping is what makes ltrace slower than the untraced process by orders of magnitude on hot paths. The Ctrl+C signal detaches ltrace and restores the process to normal execution, and the detach is clean: the process continues from wherever it was, with no side effects from the tracing session.
The signal handling inside the traced process is unaffected by ltrace: SIGUSR1, SIGTERM and the other signals are delivered normally, and the application's signal handlers run as they would without tracing. The signal delivery is the reason ltrace can trace long-running processes without disrupting their signal-based control flow, and the signal that the operator sends to the process arrives and is handled regardless of the tracing session.
The environment variables and the ltrace configuration
Ltrace's configuration is minimal compared to the other tools in this series: the command-line flags are the entire configuration surface, and there is no configuration file to maintain. The environment variables that affect ltrace are the standard library environment variables, LD_LIBRARY_PATH for the library search path and LD_PRELOAD for the preloaded libraries, and the LD_PRELOAD of a custom library can interfere with ltrace's interception, the interaction that the diagnostic session should check before blaming ltrace for a missing trace.
The library search path matters because ltrace traces the libraries that the process actually loads, and the process that loads a different version of a library than the one the developer expects will show calls to the wrong library's functions. The ldd preflight from the earlier section confirms which libraries are loaded, and the ltrace trace shows the calls to those libraries' functions, the correlation that makes the library version mismatch visible.
The debug symbols and the function name resolution
Ltrace resolves function names from the library's dynamic symbol table, and the symbol table is present in every shared library that is not stripped. The stripped library, the one whose symbols have been removed for size or for intellectual property protection, shows the function addresses rather than the names, and the addresses are the diagnostic dead end that the symbol table would have avoided. The debug symbols package, the -dbg or -debuginfo package that distributions ship separately, carries the symbols for the libraries that the distribution compiles without them, and installing the debug package restores the function names in the ltrace output.
The symbol resolution is also affected by the library version: the function that exists in version 1.2 of the library but not in version 1.1 shows in the trace only if the process loads version 1.2, and the library version mismatch between the development machine and the production machine is the mismatch that the trace reveals through the missing function names.
The practical scenarios from the field
The ltrace scenarios from production diagnostics are worth collecting for the runbook. The OpenSSL handshake failure: ltrace -e 'SSL_' shows the SSL_new, SSL_connect and SSL_get_error calls, and the return value from SSL_get_error names the specific TLS failure, the certificate error or the protocol mismatch. The malloc failure: ltrace -e 'malloc' shows the allocation requests and the NULL returns, the memory exhaustion diagnostic that the application's own error handling missed. The database connection: ltrace -e 'PQ' shows the PostgreSQL client library calls, the PQconnectdb, the PQexec and the PQgetvalue, and the arguments to the PQconnectdb call reveal the connection string that the application is using.
Each scenario is a specific library with a specific filter, and the filter is the difference between a diagnostic and a firehose. The scenarios are the runbook entries that the team collects over time, each one a recipe for a specific diagnostic question, and the collection is the diagnostic knowledge base that the team builds from its own incidents.
The closing position on ltrace in the diagnostic toolbox
Ltrace is the tool for the library-level diagnostic, the question that strace and perf cannot answer because the library boundary is below the system call and above the application code. The tool's cost is the tracing overhead, the tool's value is the function-level visibility, and the tool's place in the diagnostic workflow is the middle layer between the system calls and the application code. The diagnostic that needs ltrace is the one that has already been through strace and needs to go deeper, and the deeper look is the one that sees the library function's arguments and return values, the data that the application passes to the library and the result that the library gives back, the interaction that is the bug's location in the diagnostic hierarchy.