Skip to main content
Redhat Developers  Logo
  • Products

    Platforms

    • Red Hat Enterprise Linux
      Red Hat Enterprise Linux Icon
    • Red Hat AI
      Red Hat AI
    • Red Hat OpenShift
      Openshift icon
    • Red Hat Ansible Automation Platform
      Ansible icon
    • View All Red Hat Products

    Featured

    • Red Hat build of OpenJDK
    • Red Hat Developer Hub
    • Red Hat JBoss Enterprise Application Platform
    • Red Hat OpenShift Dev Spaces
    • Red Hat OpenShift Local
    • Red Hat Developer Sandbox

      Try Red Hat products and technologies without setup or configuration fees for 30 days with this shared Openshift and Kubernetes cluster.
    • Try at no cost
  • Technologies

    Featured

    • AI/ML
      AI/ML Icon
    • Linux
      Linux Icon
    • Kubernetes
      Cloud icon
    • Automation
      Automation Icon showing arrows moving in a circle around a gear
    • View All Technologies
    • Programming Languages & Frameworks

      • Java
      • Python
      • JavaScript
    • System Design & Architecture

      • Red Hat architecture and design patterns
      • Microservices
      • Event-Driven Architecture
      • Databases
    • Developer Productivity

      • Developer productivity
      • Developer Tools
      • GitOps
    • Automated Data Processing

      • AI/ML
      • Data Science
      • Apache Kafka on Kubernetes
    • Platform Engineering

      • DevOps
      • DevSecOps
      • Ansible automation for applications and services
    • Secure Development & Architectures

      • Security
      • Secure coding
  • Learn

    Featured

    • Kubernetes & Cloud Native
      Openshift icon
    • Linux
      Rhel icon
    • Automation
      Ansible cloud icon
    • AI/ML
      AI/ML Icon
    • View All Learning Resources

    E-Books

    • GitOps Cookbook
    • Podman in Action
    • Kubernetes Operators
    • The Path to GitOps
    • View All E-books

    Cheat Sheets

    • Linux Commands
    • Bash Commands
    • Git
    • systemd Commands
    • View All Cheat Sheets

    Documentation

    • Product Documentation
    • API Catalog
    • Legacy Documentation
  • Developer Sandbox

    Developer Sandbox

    • Access Red Hat’s products and technologies without setup or configuration, and start developing quicker than ever before with our new, no-cost sandbox environments.
    • Explore Developer Sandbox

    Featured Developer Sandbox activities

    • Get started with your Developer Sandbox
    • OpenShift virtualization and application modernization using the Developer Sandbox
    • Explore all Developer Sandbox activities

    Ready to start developing apps?

    • Try at no cost
  • Blog
  • Events
  • Videos

ltrace for RHEL 6 and 7

July 10, 2014
Matt Newsome Petr Machata
Related topics:
Developer ToolsLinux
Related products:
Developer ToolsRed Hat Enterprise Linux

Share:

    Debugging software is something akin to an art form but, regardless of the approach you prefer, having good information on what's happening in your application is key. ltrace is one tool you may wish to add to your belt - a debugging tool that attaches to a running process, and prints to the terminal or a log file the library calls and/or system calls made by that process. In both its mode of operation and command line interface, ltrace is similar to strace. While strace only works for system calls, however, ltrace has no such restriction.

    Like strace, ltrace is useful for roughly pinning down crashes of programs. It puts things into context, providing a sequence of events that led to the crash. It's also a useful tool for introspection, for figuring out what it is that the application does, what files it reads and what environment variables it inspects, whether there are certain calls. One could use ltrace to figure out whether some plugin is called, for example, and if yes, with what result, figuring out whether the application responds to configuration the expected way.

    As a small example, let's take a look what library calls a simple invocation of "echo" entails:

    $ ltrace -n2 echo
    __libc_start_main([ "echo" ] <unfinished ...>
    getenv("POSIXLY_CORRECT") = nil
    strrchr("echo", '/') = nil
    setlocale(LC_ALL, "") = "cs_CZ.UTF-8"
    bindtextdomain("coreutils", "/usr/share/locale") = "/usr/share/locale"
    textdomain("coreutils") = "coreutils"
    __cxa_atexit(0x402310, 0, 0, 0x736c6974756572) = 0
    __overflow(0x7f3d476b18e0, 10, 0x7f3d476b25f0, 0) = 10
    exit(0 <unfinished ...>
    __fpending(0x7f3d476b18e0, 0, 0x7f3d476b25f0, 4) = 0
    fclose(0x7f3d476b18e0) = 0
    __fpending(0x7f3d476b1800, 0, 0x7f3d476b3150, 0) = 0
    fclose(0x7f3d476b1800) = 0
    +++ exited (status 0) +++

    Like strace, ltrace uses the Linux kernel interface called ptrace, which allows one process to meddle with another process's innards. ptrace provides valuable service to both strace and ltrace in that it can report when a traced process exits, forks or clones, or performs a system call. Library calls however take place purely in user space, so ptrace can't report them directly. Instead, ltrace leverages two tools to achieve that end. The first is a mechanism that is used to implement calls to shared libraries—procedure linkage tables, or PLT's. Ian Lance Taylor published a good treatment of the way dynamic linking works, for us the necessary thing is that inter-library calls aren't direct, but instead are routed via the PLT (of which each module has one—the main binary and any shared libraries).

    The second tool is breakpoints. ltrace loads the ELF file corresponding to each module, finds its PLT table, and puts a breakpoint to each entry. If a program runs into one of these breakpoints, it stops, and ltrace is notified by ptrace. From the program counter it can deduce which PLT entry was hit, and from that which function was called.

    This essentially simple abstract view of ltrace operation is complicated by a number of details, but the fundamental mode of operation has remained as stated above since before Red Hat Enterprise Linux.

    Prototype Discovery

    When printing out a call, it would be very nice to print its arguments as well (just like strace does). But the set of system calls is finite and fairly constant, and at least on Linux, very very stable, so it's possible to encode the knowledge of those into strace itself. But of library calls there's essentially infinite number, so that approach can't work.

    One possibility is to decode argument types from the DWARF debug information. DWARF isn't always available however, and it isn't always practical to install either. Besides, DWARF won't tell us whether a char* is a character pointer or a string, that an int-typed member somewhere happens to be a conceptual enum, or which pointers are output arguments. DWARF support would still be nice to have as a backup plan, and indeed work is being done upstream to add that support, but it can't be the full story.

    Instead, ltrace supports something called prototype libraries. Those are plain-text formatted files with descriptions of prototypes. Each prototype library is named after the shared library that it describes. For example, ltrace itself ships prototype libraries called libc.so.conf, libm.so.conf, and libacl.so.conf. These files are stored in directory /usr/share/ltrace, and any library developer can ship such files with their library. Apart from /usr/share/ltrace/, there's a number of other places that ltrace looks into—$HOME/.config/ltrace, /etc, $HOME/.ltrace.conf. A user can also specify a location or a prototype library to load using the -F command line switch. This is all described in more detail in ltrace(1).

    A prototype library definition could look like this:

    # From actual libm.so.conf:
    void sincos(double, +double*, double*);
    
    # From nascent libgo.so.0.conf:
    typedef __go_string = struct(string(array(char, elt2)*), hide(int));
    ulong __go_type_hash_string(__go_string*, hide(ulong));

    For simple functions, the syntax may seem (almost) like that of C, but the latter more involved example shows that it's not C at all. The syntax is described in detail in ltrace.conf(5).

    With this information in hand, ltrace still needs to figure out where the parameter values actually reside (e.g. in registers and which ones, on stack and where exactly). For this it has backends that know about parameter passing conventions on a given architecture.

    If a prototype couldn't be discovered, ltrace assumes it's a function with four long parameters, returning a long. E.g. the __cxa_atexit and __fpending calls in the example above have clearly this default prototype.

    Filtering

    Today, an average application is linked to dozens of shared libraries, and not all of them are necessarily interesting. The overhead imposed by context switching due to ptrace is fairly significant, so it is best if the set of traced symbols is as small as possible. So ltrace makes it possible to select what symbols a user wishes to trace. There are two options for filtering PLT slots: -e and -l.

    If you are interested in calls related to a certain shared library, use -l with an argument of a SONAME of the interesting library. For example, date is linked to librt.so.0. We can request tracing calls into that library e.g. like this:

    $ ltrace -l librt* date
    date->clock_gettime(0, 0x7fff0d00a090, 0x7f80980c21b4, 0) = 0
    St čen 4 16:26:59 CEST 2014
    +++ exited (status 0) +++

    Note that as before ltrace doesn't really know whether the call to clock_gettime really ends up being serviced by a routine in librt.so.0. The call can end up somewhere else due to symbol interposition, e.g. if you use LD_PRELOAD. The important thing is that a function is called which is exported from librt.so.0.

    The other filtering option is -e, and it makes it possible to explicitly control which PLT slots of which PLT tables get selected. For example, to trace all calls to a function named "free" (i.e. to select "free" slots in all PLT tables), one would do:

    $ ltrace -e free ls
    libc.so.6->free(0x1de2030) = <void>
    libc.so.6->free(nil) = <void>
    libc.so.6->free(0x1de7c30) = <void>
    ls->free(0x1de7c10) = <void>
    ls->free(nil) = <void>
    ls->free(0x1de7be0) = <void>
    libselinux.so.1->free(0x1de2010) = <void>
    libselinux.so.1->free(nil) = <void>
    libselinux.so.1->free(nil) = <void>
    libselinux.so.1->free(nil) = <void>
    [... snip ...]

    On the other hand, to show only calls from libselinux.so.1 (i.e. to select all slots, but only in the PLT table of libselinux.so.1), one would do:

    $ ltrace -e '@libselinux*' ls
    libselinux.so.1->free(0x1a62010) = <void>
    libselinux.so.1->free(nil) = <void>
    libselinux.so.1->free(nil) = <void>
    [...]
    libselinux.so.1->free(nil) = <void>
    libselinux.so.1->__cxa_finalize([...]) = 0x7f5d8c0cb9d0
    +++ exited (status 0) +++

    Both of the above syntaxes can be used together, e.g. -e free@libselinux*.

    The selector syntax is actually richer—above we've used globs, but regular expressions can be used as well. One can also construct the set of traced symbols incrementally, e.g. 'f*-foo*' denotes that ltrace should trace calls to all functions that start with "f" except those that start with "foo". This is all described in ltrace(1).

    Tracing Symbol Entry Points

    There's one additional filtering option, and that is -x. It works very similarly to -e, but doesn't apply to PLT slots, but directly to symbols in the ELF symbol table—i.e. to entry points of those functions. To illustrate the difference, consider this example:

    $ ltrace -e textdomain -x textdomain ls
    ls->textdomain("coreutils" <unfinished ...>
    textdomain@libc.so.6("coreutils") = "coreutils"
    <... textdomain resumed> ) = "coreutils"

    The first message shows that a call to textdomain was made from the main binary. The second shows that the call actually landed in libc.so.6. If there were more libraries that implement textdomain, this would show us which of them actually services the symbol.

    Tracing System Calls

    While the main task of ltrace is showing function calls (whether they are local or library calls), it can trace system calls as well. The command line switch that enables this is -S. You can also use ltrace to trace purely the system calls by additionally passing -L, which switches off tracing of library calls:

    $ ltrace -S -L date >/dev/null
    brk@SYS(nil) = 0xd68000
    mmap@SYS(nil, 4096, 3, 34, -1, 0) = 0x7ffb1b4ac000
    access@SYS("/etc/ld.so.preload", 04) = -2
    open@SYS("/etc/ld.so.cache", 0, 01) = 3
    [...]
    write@SYS(1, "Wed Jun 18 11:27:45 CEST 2014n", 30) = 30
    Wed Jun 18 11:27:45 CEST 2014
    close@SYS(1) = 0
    munmap@SYS(0x7ffb1b4ab000, 4096) = 0
    close@SYS(2) = 0
    exit_group@SYS(0 <no return ...>
    +++ exited (status 0) +++

    ltrace doesn't have the same introspective power as strace when it comes to system call parameters—e.g. it can't factor out individual flags from a bitfield, doesn't show errno identifiers for negative return values, and possibly more.

    System call prototypes are described in syscalls.conf that's shipped with ltrace. The above-mentioned switch -F can be used to point at a directory with alternative syscalls.conf that ltrace will use instead of the system one, so it's possible to tweak and tune the prototypes.

    What's New in Red Hat Enterprise Linux 7?

    Between Red Hat Enterprise Linux releases 6 and 7, ltrace has seen many changes and improvements to its core functionality. Red Hat also hopes to include an updated version of ltrace as part of a future release of Red Hat Developer Toolset, which should bring newly-capable ltrace to Red Hat Enterprise Linux 6 developers as well. 

    The changes are in particular:

    • Calls made from shared libraries couldn't be traced by ltrace shipped with Red Hat Enterprise Linux 6. A limited support for tracing calls into libraries opened with dlopen was present, but in Red Hat Enterprise Linux 7, -e, -x and -l all work consistently irrespective of whether the module in question is the main binary, a linked-in shared library or a shared library opened with dlopen.
    • Support for calling conventions was vastly improved. Previous versions couldn't handle passing structures by value, passing double on 32-bit architectures and many other edge cases. The whole parameter-fetching logic was rewritten from scratch. One remaining limitation is lack of support for "long long".
    • Richer configuration language. It's possible to express NULL or NUL-terminated arrays, to hide certain parameters, convert values to hexadecimal or octal representation, and more.
    • ltrace can show stack trace of displayed event (use -w N to enable, where N is number of stack frames to show).
    • What was said about prototype libraries above is actually new to ltrace as shipped with Red Hat Enterprise Linux 7.

    What's Next

    There are still some obvious improvements to be made to ltrace. The emerging DWARF support has already been mentioned above. Besides, it would make sense to support tracing systemtap probe points. But there are also more fundamental issues—lack of support for long long has been limiting proper support of some interfaces.

    The manual pages shipped with ltrace (ltrace(1) and ltrace.conf(5)) provide a fair amount of detail about odds and ends of ltrace. What's above are perhaps the most interesting or useful functions, but there's quite a bit more.

    In conclusion, we would like to hear from you! If this topic is interesting to you, we can provide more in-depth, or perhaps more user-centric articles in future. If ltrace is interesting to you, we would like to hear how. Please let us know in comments or directly.

    Last updated: February 23, 2024

    Recent Posts

    • Cloud bursting with confidential containers on OpenShift

    • Reach native speed with MacOS llama.cpp container inference

    • A deep dive into Apache Kafka's KRaft protocol

    • Staying ahead of artificial intelligence threats

    • Strengthen privacy and security with encrypted DNS in RHEL

    Red Hat Developers logo LinkedIn YouTube Twitter Facebook

    Products

    • Red Hat Enterprise Linux
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform

    Build

    • Developer Sandbox
    • Developer Tools
    • Interactive Tutorials
    • API Catalog

    Quicklinks

    • Learning Resources
    • E-books
    • Cheat Sheets
    • Blog
    • Events
    • Newsletter

    Communicate

    • About us
    • Contact sales
    • Find a partner
    • Report a website issue
    • Site Status Dashboard
    • Report a security problem

    RED HAT DEVELOPER

    Build here. Go anywhere.

    We serve the builders. The problem solvers who create careers with code.

    Join us if you’re a developer, software engineer, web designer, front-end designer, UX designer, computer scientist, architect, tester, product manager, project manager or team lead.

    Sign me up

    Red Hat legal and privacy links

    • About Red Hat
    • Jobs
    • Events
    • Locations
    • Contact Red Hat
    • Red Hat Blog
    • Inclusion at Red Hat
    • Cool Stuff Store
    • Red Hat Summit
    © 2025 Red Hat

    Red Hat legal and privacy links

    • Privacy statement
    • Terms of use
    • All policies and guidelines
    • Digital accessibility

    Report a website issue