uftrace: a function call graph tracer for C, C++, Rust and Python
Function graph tracer for C/C++/Rust/Python
At a glance
- What is it?
- uftrace records function entry and exit with timestamps, arguments and return values, and can patch a running binary so you do not have to recompile. It is aimed at developers who need a call graph, not just a stack sample.
- Who is it for?
- Adopt uftrace when you need per-call durations, arguments and return values from a C, C++, Rust or Python program, and you can build it from source or install a distro package. Skip it if you only need sampling profiles, or if your target is a stripped binary with no symbol file, because dynamic patching depends on symbol information.
- Can I use it commercially?
- Yes, with conditions. GPL-2.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
- Is it still maintained?
- Yes. The repository last received commits 6 days ago.
- What is it written in?
- Mainly C, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The gap uftrace fills between sampling profilers and printf debugging
A sampling profiler tells you which functions were on the stack when a timer fired. uftrace tells you the order in which functions were entered and left, how long each call took, and, when debug information or known prototypes are available, what arguments went in and what came back. The README describes the tool as hooking into the entry and exit of each function and recording timestamps along with the function's arguments and return values. That is a different data model: a call tree rather than a histogram.
The audience is narrow but deep. If you are chasing a latency spike in a C or C++ service, or you want to see how a Rust binary spends time inside library calls, a call graph with durations answers questions a flame graph cannot. The project also covers Python functions using Python's trace/profile infrastructure, so a mixed C and Python program can be inspected in one timeline. It is not a general application performance monitor. There is no agent, no daemon, no dashboard. You run a command, you get a trace.
One design decision matters more than the rest. uftrace began as a compiler-assisted tracer, but the README states it now allows users to trace function calls without recompilation by analyzing instructions in each function prologue and dynamically and selectively patching those instructions. That is the feature that makes it usable on a binary you did not build. The caveat is in the same paragraph: dynamic tracing works on executables as long as they are not stripped, or symbol information is available from a separate file.
How the recording pipeline works, from prologue patching to replay
The mechanism has three stages. First, uftrace attaches to the target and installs hooks. With compiler support, the program is built with -pg, -finstrument-functions or -fpatchable-function-entry=N. The README notes that -fpatchable-function-entry=5 is the usual form and that =2 is enough on aarch64. Without compiler support, the -P. or --patch=. option patches function prologues at runtime. Library calls are handled separately through PLT hooking, and -l or --nest-libcall extends that hooking into the procedure linkage tables of shared libraries. Depth is bounded with -D<num>, where 1 means flat call tracing.
Second, the trace is written to disk. A trace directory holds the recorded call data plus metadata the README describes as system information on which the trace was taken, along with generated symbol tables and memory maps of the traced program and library functions. This is why uftrace can replay a trace later, on a different machine, without the original process running.
Third, you replay or visualize. uftrace replay prints the nested call graph with durations, thread IDs and module names. Filters can be applied at record time or at replay time, which the README frames as a way to minimize the amount of trace data. Output can be rendered through Chrome trace viewer, flame graphs, or call-graph diagrams for graphviz and mermaid.
Argument capture is worth understanding before you rely on it. The README says -a or --auto-args records arguments and return values of known functions, and that without extra debug information this covers the API functions of standard C language or system libraries. With gcc -g, --auto-args also works on functions inside your own program. When no argument information is available, you supply a specification such as -A udev_new@arg1/s on the command line or in an options file. Note that -a implies --srcline, so source line information is recorded too.
Installing uftrace and recording your first trace
The README points at misc/install-deps.sh for Linux distributions. It installs the software needed to build uftrace, and the README calls those dependencies optional but highly recommended, because they gate the advanced features. Run it with sudo, then configure, build and install:
sudo misc/install-deps.sh
./configure
make
sudo make installINSTALL.md is the reference for dependencies and install details beyond this sequence. The Makefile sets prefix to /usr/local by default, with binaries under $(prefix)/bin and libraries under $(prefix)/lib/uftrace, so make install places the uftrace binary in /usr/local/bin unless you override prefix.
For a first real trace, the README's own example uses lsusb and a hand-written argument spec. The -la combination enables nested library calls with auto-args, and -A udev_new@arg1/s tells uftrace to print the first argument of udev_new as a string. The -f+module flag adds the module name column:
$ uftrace record -la -A udev_new@arg1/s lsusb >/dev/null
$ uftrace replay -f+moduleThe README notes the same thing can be done in one step:
$ uftrace -la -A udev_new@arg1/s -f+module lsusbIn the recorded output shown in the README, each line carries a duration in microseconds, a thread ID, a module name and a function name, with nested calls indented under their caller. Calls into libudev.so.1.7.2 appear alongside calls into lsusb itself, and return values are printed after the equals sign. That interleaving of your program and its libraries in one timeline is the part that distinguishes this from a plain stack trace. If you want to browse the result interactively rather than print it, uftrace tui is the command the README mentions for viewing source lines.
Where uftrace breaks down
Stripped binaries are the clearest failure mode. The README states dynamic tracing works on executables that are not stripped, or where symbol information is available from a separate file. A production binary with symbols removed and no debug package leaves the patching approach with nothing to resolve. You can still trace library calls in some configurations, but the picture of your own code will be missing.
Kernel tracing has a build-time dependency. The README says kernel functions can be traced with root privileges if the kernel was built with CONFIG_FUNCTION_GRAPH_TRACER=y. On a kernel without that option, the kernel side of the timeline is simply unavailable, and no uftrace flag changes that.
Argument capture degrades quietly. Without debug information, --auto-args covers standard C and system library APIs, not your functions. It is easy to read a trace, see arguments for malloc and fopen64, and assume your own functions were captured the same way. They were not unless you compiled with -g or wrote -A specifications. The README is explicit about this split, but the output itself does not announce the difference.
Finally, consider what a call-graph tracer does to the program under test. Every hooked function pays the cost of the hook, and a trace of a hot loop can produce a very large trace directory. The README positions filters as the answer, applied at record and replay time, and -D<num> limits call depth. That is a real mitigation, but it means the tool rewards users who already know which functions they care about. If you do not, the first trace may be unwieldy.
uftrace against perf and ftrace
The README says uftrace was heavily inspired by the ftrace framework of the Linux kernel, and the name is a combination of user and ftrace. The difference in approach is the target. ftrace instruments the kernel; uftrace instruments user space, and can pull kernel functions, kernel trace events, task scheduling events from perf_event, and SystemTap SDT probes into the same recording. So the honest comparison is not uftrace versus ftrace as rivals. It is uftrace as the user-space half of a timeline that ftrace alone cannot produce.
Against perf, the split is sampling versus instrumentation. perf record samples; uftrace hooks every entry and exit it is configured to hook. Sampling scales to long runs with low overhead and gives you statistical attribution. Instrumentation gives you exact call counts, nesting and per-call durations, at a cost that grows with call volume. If your question is which function dominates CPU time across a ten-minute run, perf is the better tool. If your question is why this one request took 40 milliseconds and which nested call inside it was slow, uftrace's replay output is the more direct answer.
There is also a scripting dimension. The README states users can write and run scripts for each function entry and exit using python/luajit APIs to create custom tools. That puts uftrace closer to a tracing framework than a single-purpose profiler, and it is the main reason a team might build internal tooling on top of it rather than just running it by hand. The pyproject.toml in the repository configures isort for the Python side, which is consistent with Python being a first-class part of the project rather than an afterthought.
Maintenance, packaging and the GPL-2.0 question
The repository is not archived, and the last push was on 2026-09-07. Release v0.20 landed on 2026-08-30, following v0.19 on 2026-03-02 and v0.18 on 2025-07-06. The Makefile pins VERSION := 0.20. There is a nightly test workflow referenced in the README badge, a CONTRIBUTING.md, a TODO file and a pre-commit configuration, so the project carries the usual furniture of a maintained C codebase. The README also points to a Discord channel and a Google Groups mailing list for support.
Upgrade cost is mostly a build question. uftrace is distributed as source with a configure script, and the README's packaging badge points to Repology for distribution status, so on many Linux distributions you can install a packaged build instead of compiling. The trade-off is that a packaged version may lag the 0.20 release, and the optional features you get depend on which build dependencies the packager enabled. If you need a specific optional feature, building from source after running misc/install-deps.sh is the path the README describes.
Licensing is GPL-2.0. That matters in two directions. uftrace itself is copyleft, so if you modify and redistribute it, the usual GPL obligations apply. Separately, the tool works by patching and instrumenting other programs at runtime; whether that creates obligations for the traced program is a question for your own legal review, not something the README or this article settles. The COPYING file in the repository holds the licence text. Nothing here is legal advice.
One practical note on the build: the Makefile supports out-of-tree builds through the O variable, and it accepts CROSS_COMPILE as a prefix for CC, AR and LD. That means cross-compiling uftrace for an embedded target is a supported path, which fits the project's origins in low-level systems work.
Editorial conclusion
Adopt uftrace when you need per-call durations, arguments and return values from a C, C++, Rust or Python program, and you can build it from source or install a distro package. Skip it if you only need sampling profiles, or if your target is a stripped binary with no symbol file, because dynamic patching depends on symbol information. Before committing, run ./configure and check the output of check-deps/ for the optional features you actually want, and confirm whether your kernel was built with CONFIG_FUNCTION_GRAPH_TRACER=y if kernel functions are part of the plan.
Frequently asked questions
Does uftrace require recompiling the program I want to trace?
No. The README states that uftrace can trace function calls without recompilation by analyzing instructions in each function prologue and dynamically and selectively patching them, using -P. or --patch=. The exception is that the executable must not be stripped, or symbol information must be available from a separate file.
How do I install uftrace on Linux?
The README gives sudo misc/install-deps.sh to install build dependencies, followed by ./configure, make and sudo make install. It also points to INSTALL.md for dependency and installation details, and the packaging badge links to Repology for distribution packages.
Can uftrace trace Python programs as well as C and C++?
Yes. The README lists Python functions among the data sources, traced using Python's trace/profile infrastructure, alongside user-space C, C++ and Rust functions, library functions, kernel functions and kernel trace events.
Why does uftrace show arguments for library calls but not for my own functions?
With -a or --auto-args, uftrace records arguments and return values of known functions, which without extra debug information means standard C language and system library APIs. The README states that debug information from gcc -g is needed for --auto-args to work on functions inside your own program, and otherwise you supply specifications such as -A udev_new@arg1/s.
What do I need to trace kernel functions with uftrace?
The README states that kernel functions can be traced with root privileges if the kernel was built with CONFIG_FUNCTION_GRAPH_TRACER=y. Kernel trace events and task scheduling events are recorded through other kernel facilities, but the function graph option is a kernel build-time requirement.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/namhyung-uftrace)