Skip to main content
Redhat Developers  Logo
  • AI

    Get started with AI

    • Red Hat AI
      Accelerate the development and deployment of enterprise AI solutions.
    • AI learning hub
      Explore learning materials and tools, organized by task.
    • AI interactive demos
      Click through scenarios with Red Hat AI, including training LLMs and more.
    • AI/ML learning paths
      Expand your OpenShift AI knowledge using these learning resources.
    • AI quickstarts
      Focused AI use cases designed for fast deployment on Red Hat AI platforms.
    • No-cost AI training
      Foundational Red Hat AI training.

    Featured resources

    • OpenShift AI learning
    • Open source AI for developers
    • AI product application development
    • Open source-powered AI/ML for hybrid cloud
    • AI and Node.js cheat sheet

    Red Hat AI Factory with NVIDIA

    • Red Hat AI Factory with NVIDIA is a co-engineered, enterprise-grade AI solution for building, deploying, and managing AI at scale across hybrid cloud environments.
    • Explore the solution
  • Learn

    Self-guided

    • Documentation
      Find answers, get step-by-step guidance, and learn how to use Red Hat products.
    • Learning paths
      Explore curated walkthroughs for common development tasks.
    • Guided learning
      Receive custom learning paths powered by our AI assistant.
    • See all learning

    Hands-on

    • Developer Sandbox
      Spin up Red Hat's products and technologies without setup or configuration.
    • Interactive labs
      Learn by doing in these hands-on, browser-based experiences.
    • Interactive demos
      Click through product features in these guided tours.

    Browse by topic

    • AI/ML
    • Automation
    • Java
    • Kubernetes
    • Linux
    • See all topics

    Training & certifications

    • Courses and exams
    • Certifications
    • Skills assessments
    • Red Hat Academy
    • Learning subscription
    • Explore training
  • Build

    Get started

    • Red Hat build of Podman Desktop
      A downloadable, local development hub to experiment with our products and builds.
    • Developer Sandbox
      Spin up Red Hat's products and technologies without setup or configuration.

    Download products

    • Access product downloads to start building and testing right away.
    • Red Hat Enterprise Linux
    • Red Hat AI
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform
    • See all products

    Featured

    • Red Hat build of OpenJDK
    • Red Hat JBoss Enterprise Application Platform
    • Red Hat OpenShift Dev Spaces
    • Red Hat Developer Toolset

    References

    • E-books
    • Documentation
    • Cheat sheets
    • Architecture center
  • Community

    Get involved

    • Events
    • Live AI events
    • Red Hat Summit
    • Red Hat Accelerators
    • Community discussions

    Follow along

    • Articles & blogs
    • Developer newsletter
    • Videos
    • Github

    Get help

    • Customer service
    • Customer support
    • Regional contacts
    • Find a partner

    Join the Red Hat Developer program

    • Download Red Hat products and project builds, access support documentation, learning content, and more.
    • Explore the benefits

ltrace for RHEL 6 and 7

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

    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

    • Every layer counts: Defense in depth for AI agents with Red Hat AI

    • Fun in the RUN instruction: Why container builds with distroless images can surprise you

    • Trusted software factory: Building trust in the agentic AI era

    • Build a zero trust AI pipeline with OpenShift and RHEL CVMs

    • Red Hat Hardened Images: Top 5 benefits for software developers

    Red Hat Developers logo LinkedIn YouTube Twitter Facebook

    Platforms

    • Red Hat AI
    • Red Hat Enterprise Linux
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform
    • See all products

    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
    © 2026 Red Hat

    Red Hat legal and privacy links

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

    Chat Support

    Please log in with your Red Hat account to access chat support.