In the Oracle Linux Sustaining team, drgn has become an instrumental tool. It is a debugger library for Python, enabling access to the variables and memory of a program (including the currently running Linux kernel, or a vmcore) in a very natural way. It allows us to write code that understands the complex data structures of the kernel so that we can more easily track down the root cause of bugs. However, like any debugger, drgn requires debuginfo to function. For Oracle Linux, this comes in the form of the kernel-uek-debuginfo package, which is an 800 MiB download, and takes up around 4.7 GiB of disk space when installed. It contains DWARF debuginfo, which is quite powerful, but frequently overkill for common debugging tasks.
For a long time, Oracle has led the effort to bring a more compact debugging information format to the Linux community: the Compact C Type Format (CTF). It is a crucial piece of Oracle Linux DTrace. Oracle has contributed CTF support to GCC, the GNU binutils, libabigail, and GDB. We are committed to an ongoing effort to enable building the upstream Linux kernel with CTF debuginfo, and our UEK kernels have had this support for years, including our upstream-tracking UEK-next kernels.
As part of this ecosystem of CTF enabled tooling, we’re pleased to share that Oracle Linux users can now take advantage of CTF support in drgn. With CTF, users can immediately begin debugging their kernel, without setting up or installing heavy debuginfo packages. Drgn scripts can be run on production systems, enabling enhanced visibility into the state of the kernel. In this blog post, we’ll share background about the CTF format, describe how it compares to DWARF when using drgn, and share an overview of the implementation of CTF in drgn. We’ll also describe the current state of the CTF project with drgn upstream, and share how Oracle Linux users can get started with CTF.
What is CTF?
The Compact C Type Format (CTF) is a debuginfo format that describes the C types used by a program. It is designed to be compact, and it succeeds at this goal! While a kernel debuginfo package consumes around 4.7 GiB of disk space when installed, the latest UEK7 kernel’s CTF information is 14 MiB. CTF achieves compactness in a variety of ways. While DWARF includes information about source code locations of declarations, instructions for unwinding stacks and retrieving variable values from the stack, and far more, CTF omits this information, storing only the basic type information and a mapping of variable / function names to types. CTF also aggressively de-duplicates type information, avoids storing duplicate string tables (e.g. relying on the ELF symbol table where possible), and uses compression to further reduce its footprint.
All this means that CTF information is lightweight enough to be included in the main RPM for a given package, rather than a separate -debuginfo RPM. It is not intended to be stripped: applications can use CTF for runtime introspection, and debuggers and tracers can use CTF at runtime. For many years, the Oracle Linux kernel-uek package has contained CTF information installed at /lib/modules/$(uname -r)/kernel/vmlinux.ctfa.
On its own, CTF does not contain any information related to stack unwinding, nor does it attempt to provide source code mappings for functions, variables, and types. Some of these functionalities can be provided by alternative formats. For instance, the kernel typically uses frame pointers or ORC for stack unwinding, which we compared on this blog a few years ago. In userspace, the SFrame format is rapidly gaining traction as a supplementary stack tracing format. Debuggers and profilers can take advantage of any of these approaches to satisfy their requirements.
It’s important to understand that CTF is not a competitor to DWARF; it is a supplement. DWARF is an incredibly powerful and rich source of debugging information. For long, in-depth debugging situations, it is simply the best tool for the job. But many common situations don’t require the depth of information provided by DWARF, and so CTF can fill that niche.
Implementing CTF in drgn
Our goal when implementing CTF for drgn was to allow users to get a full debugging experience without requiring anything from the kernel-uek-debuginfo package. Without debuginfo, drgn can only provide very limited functionality: it can read memory, and that’s it. Here’s what such a drgn session without debuginfo might look like:
$ drgn -q
drgn 0.0.27 (using Python 3.9.18, elfutils 0.190, with libkdumpfile)
For help, type help(drgn).
>>> import drgn
>>> from drgn import FaultError, NULL, Object, cast, container_of, execscript, offsetof, reinterpret, sizeof, stack_trace
>>> from drgn.helpers.common import *
>>> from drgn.helpers.linux import *
>>>
>>>
>>> # Read 16 bytes from memory
>>> prog.read(0xffffffff89e79840, 16)
b'h2\x00\x80\xf2\x90\xff\xffh \x04@\xf2\x90\xff\xff'
>>>
>>> # Lookup the symbol "slab_caches"
>>> prog.symbol("slab_caches")
Traceback (most recent call last):
File "<console>", line 1, in <module>
LookupError: not found
>>>
>>> # Get the definition of type "struct list_head"
>>> prog.type("struct list_head")
Traceback (most recent call last):
File "<console>", line 1, in <module>
LookupError: could not find 'struct list_head'
>>>
>>> # Get the variable value "slab_caches"
>>> prog["slab_caches"]
Traceback (most recent call last):
File "<console>", line 1, in <module>
KeyError: 'slab_caches'
>>>
>>> # Get the stack trace for the init task
>>> prog.thread(1).stack_trace()
Traceback (most recent call last):
File "<console>", line 1, in <module>
LookupError: could not find variable 'init_pid_ns'
We’ll need to replace each capability that the DWARF debuginfo would have provided with a suitable alternative: symbol table, type format, object lookup, and stack tracing.
Thankfully, drgn is designed to be very extensible. While type information is typically provided by its built-in support for DWARF, drgn contains APIs for constructing and working with types. What’s more, it allows users to register a type finder which is called to lookup information about a type. Similarly, users can register object finders which can lookup a name (and declaring source filename) and return the drgn representation of that object, complete with type information. These APIs are available to Python users, but also to C implementation code within drgn itself. Thus, our basic approach to implementing CTF with drgn is to create our own CTF “type finder”, using libctf (from binutils). But we must also replace the missing functionality that drgn already provided with the DWARF: namely, symbol lookup and variable lookup. We also need to ensure that drgn can unwind stacks without DWARF information. Each of these elements of the project are described below.
Symbol Tables Without DWARF
Unlike types and objects, drgn did not yet have an extensible API for symbol tables. But without an ELF debuginfo file, we wouldn’t be able to rely on drgn’s built-in ELF symbol table support. Thus, the first element of this project had to be enabling some alternative way to resolve symbol names to addresses. We contributed a new API similar to the type and object finder API, but for pluggable symbol finders. This support is now available in the 0.0.27 release of drgn.
Using this extensible API, we need to provide an alternative source of symbols for drgn to use when debugging the Linux kernel. Thankfully, the kernel comes with its own built-in symbol table: kallsyms. You may have used this before, if you’ve ever used the file /proc/kallsyms. The table contains names and addresses of symbols, as well as some basic information about what kind each symbol is (e.g. function or data, global or local, etc).
It is possible for kallsyms to only contain information about function symbols (since it is intended to enable, among other things, readable stack traces). However, the kernel configuration KALLSYMS_ALL results in all symbols (including variables) being included in kallsyms. It is enabled by almost every distribution kernel, so in practice, this isn’t much of a concern.
When we are debugging the running kernel, it’s quite easy to get access to kallsyms data: just open /proc/kallsyms and parse it! However, the /proc/kallsyms file can’t be relied upon in all cases. First, if we are debugging a core dump, then the /proc/kallsyms file corresponding to that kernel is gone. We need to instead find that information from within the core dump itself, and we need to do it without any other source of debuginfo. Second, if we are running as a non-root user (which drgn supports since 0.0.25), then reading the /proc/kallsyms file will give back addresses that are all zero, to avoid defeating KASLR.
To resolve both of these cases, we can directly use the tables of kallsyms data which are embedded in the kernel image, and thus within the vmcore. But to find them, we would normally need a symbol table, creating a chicken and egg problem! Thankfully, this can be resolved by introducing the locations of these tables into the VMCOREINFO note. If you’re not familiar with VMCOREINFO, it is a small piece of metadata embedded inside of kernel core dumps (and /proc/kcore) that contains helpful information on how to interpret it. You can read more about it here. We introduced the necessary information in this patch, which was merged in version 6.0. The patch is backported to all supported UEK releases, starting with:
- UEK5:
v4.14.35-2047.518.0 - UEK6:
v5.4.17-2136.312.2 - UEK7:
v5.15.0-100.60.5
Any core dump created from a Linux kernel with these patches will contain information like the below inside the VMCOREINFO note:
$ sudo eu-readelf -n /proc/kcore | grep kallsyms
SYMBOL(kallsyms_names)=ffffffffa0e43f78
SYMBOL(kallsyms_num_syms)=ffffffffa0e43f70
SYMBOL(kallsyms_token_table)=ffffffffa109ded8
SYMBOL(kallsyms_token_index)=ffffffffa109e2a0
SYMBOL(kallsyms_offsets)=ffffffffa109e4a0
SYMBOL(kallsyms_relative_base)=ffffffffa11643e8
With this information, drgn is able to find each of the tables which comprise the kallsyms information. Then, it is a simple matter of implementing the kernel’s custom “table compression” algorithm (which is outside the scope of this post, but you can see scripts/kallsyms.c for details).
Unfortunately, while /proc/kallsyms contains information about built-in symbols and module symbols, the tables found here only contain built-in symbols. The kallsyms information for each kernel module is loaded from the module’s ELF file at runtime, and the kernel stores a pointer to the symbol table in its corresponding struct module. In order to provide kernel module symbols, we need to support finding these symbol tables and returning them, too. Thankfully, this can be done after we’ve loaded the CTF and kallsyms data, so we can rely on having the full type information for struct module available.
All told, this means we have three new symbol finders which can provide kallsyms data in various situations!
- A finder which directly parses and uses
/proc/kallsyms. - A finder which extracts built-in symbols from the tables in the core dump (requiring some metadata in VMCOREINFO to help find them).
- A finder which extracts the symbol tables from each kernel module.
These finders are all in review upstream, but they aren’t yet merged into drgn’s main branch. Below, you can see what the same drgn session from above would look like after adding the symbol finder. Please keep in mind that the APIs demonstrated here are not final! They are based on 0.0.27, and they are just for demonstration.
>>> from drgn.helpers.linux.kallsyms import load_vmlinux_kallsyms
>>> finder = load_vmlinux_kallsyms(prog)
>>> prog.register_symbol_finder("kallsyms", finder, enable_index=0)
>>>
>>> # Lookup the symbol "slab_caches"
>>> prog.symbol("slab_caches")
Symbol(name='slab_caches', address=0xffffffff89e79840, size=0x20, binding=<SymbolBinding.GLOBAL: 2>, kind=<SymbolKind.OBJECT: 1>)
>>>
>>> # Get the definition of type "struct list_head"
>>> prog.type("struct list_head")
Traceback (most recent call last):
File "<console>", line 1, in <module>
LookupError: could not find 'struct list_head'
>>>
>>> # Get the variable value "slab_caches"
>>> prog["slab_caches"]
Traceback (most recent call last):
File "<console>", line 1, in <module>
KeyError: 'slab_caches'
>>>
>>> prog.thread(1).stack_trace()
Traceback (most recent call last):
File "<console>", line 1, in <module>
LookupError: could not find variable 'init_pid_ns'
Drgn is still able to read from memory (not shown anymore) but can now lookup symbols! However, it cannot lookup the variable named slab_caches, because a variable is more than a symbol. It requires a type! Since drgn can’t understand the types of variables yet, it also cannot find the tasks of the kernel, and thus no stack traces are possible.
CTF Type & Object Finders
With a reliable source of symbol information available to us, the next step is to enable drgn to understand the CTF format, allowing us to read variables and treat them as objects. Thankfully, we can use libctf from binutils to read the CTF data. Next, we implement our own type finder and attach it to drgn. This allows drgn to understand all of the types used by the kernel. Because CTF info comes with a mapping from variable names to the corresponding type, we can also create an object finder which enables us to fully understand the global variables in the kernel. The only requirement is that we already have a way to get the address of these variables – and thankfully we do, thanks to the kallsyms symbol finders that were addressed previously.
Unfortunately, the details of the CTF type finder implementation itself are well outside the scope of this blog post. However, we welcome more eyes and comments on the code, which can be found in this ctf git branch. Those curious about the inner workings of the code can view the branch there, as well as the libctf API header, since libctf is used as the foundation of our implementation.
With the above type & object finders, we can now look at the types of variables, and drgn can now interpret a variable just like you’d expect it to, if you were using DWARF debuginfo. Continuing from the above example, we can now load the CTF info and see that functionality in action:
>>> from drgn.helpers.linux.ctf import load_ctf
>>> load_ctf(prog, use_orc=False)
>>> prog.type("struct list_head")
struct list_head {
struct list_head *next;
struct list_head *prev;
}
>>> prog["slab_caches"]
(struct list_head){
.next = (struct list_head *)0xffff8b4303c5aa68,
.prev = (struct list_head *)0xffff8b4300042068,
}
And in fact, we can now use the vast majority of drgn’s built-in helper API functions to make it easier to analyze the kernel’s data structures. For example, instead of looking at the variable slab_caches, we could use the for_each_slab_cache() helper to enumerate each of the slab caches, and then print their names:
>>> for obj in for_each_slab_cache(prog): ... print(obj.name.string_().decode()) ... nf_conntrack_expect nf_conntrack fat_inode_cache fat_cache # ... this continues for a while ...
However, there are still gaps in functionality. The major problem is that stack tracing is not available! See below:
>>> prog.thread(1).stack_trace() #0 __schedule+0x224/0x590 #1 0x0 #2 0x0 #3 0x109f53bff
While drgn is now able to find the necessary symbols and objects to begin the stack trace, and it is able to find the symbol corresponding to the function at the top of the stack, it is still unable to unwind the stack!
ORC Unwinding Without Debuginfo
The reason drgn still cannot unwind stacks, even with a symbol table & CTF support, is because this kernel (UEK7) is compiled without frame pointers. While drgn is able to use frame pointers transparently to unwind stack traces, if they are not present, drgn is stuck. Normally, drgn can fall back to the ORC format to unwind stacks even if frame pointers are disabled. But if you actually read the code, you find that drgn gets this ORC data from the vmlinux ELF file. Since we don’t have access to the vmlinux file (it is in the kernel-uek-debuginfo package), drgn can’t use ORC to unwind the stack.
However, this is not actually a major problem! The whole purpose of ORC is to serve as a compact format which allows the kernel itself to unwind its own stacks. That means that the kernel memory image itself must contain the ORC data. All we need to do is to take drgn’s pre-existing ORC support, and allow it to operate on the ORC data stored within the kernel image.
The ORC data is stored in the vmlinux ELF file in three sections:
.orc_unwind, which contains a table of unwind instructions..orc_unwind_ip, which contains a mapping from instruction pointer to unwind instructions..orc_header, which contains a hash of all the ORC-related structure definitions, which allows it to serve as a “version” for the ORC format. This is very important, as changes to the format make it difficult for debuggers like drgn to understand the format, even if it isn’t intended for broad use outside the Linux kernel. This section was added in Linux 6.4 (and backported to 6.3) to help catch a format change.
Thankfully, each of these sections has a pair of corresponding symbols, present in the kallsyms symbol table, which can help us find their beginning and end:
__start_orc_unwind,__stop_orc_unwind__start_orc_unwind_ip,__stop_orc_unwind_ip__start_orc_header,__stop_orc_header
Similarly, each kernel module (i.e. struct module) on x86_64 contains its own orc_unwind and orc_unwind_ip tables. The orc_header is unnecessary because it must match the one for the core kernel.
With this information, all that’s really necessary is a refactor of drgn that can allow all of this information to be used, even when there is no ELF file or DWARF information loaded. That refactor is a bit involved, but thanks to the excellent quality of drgn’s implementation, once done, the pre-existing ORC support works perfectly. We can use it as seen below, thanks to a newly added private helper. The below code listing continues from where the previous ones left off, assuming all the previous debuginfo was loaded:
>>> from _drgn import _linux_helper_load_orc >>> _linux_helper_load_orc(prog) >>> prog.thread(1).stack_trace() #0 __schedule+0x224/0x590 #1 schedule+0x5f/0xe0 #2 schedule_hrtimeout_range_clock+0x125/0x140 #3 ep_poll+0x306/0x400 #4 do_epoll_wait+0xcf/0x100 #5 __x64_sys_epoll_wait+0x72/0x120 #6 do_syscall_64+0x3b/0x90 #7 entry_SYSCALL_64_after_hwframe+0x69/0xd3 #8 0x7f5c0b35429e
et voila! Drgn is now using the ORC information that was already present inside the kernel image, and so it can unwind the stack.
Putting Everything Together
With the major ingredients above (kallsyms, CTF, and ORC), we are able to achieve a debugging experience that is quite powerful. However, the setup described above is a bit involved. Instead of using each of these hidden functions, we’ve bundled up all the necessary setup into a single function, load_ctf(), which is available as soon as you launch drgn:
>>> load_ctf(prog) >>> print(prog["saved_command_line"].string_().decode()) BOOT_IMAGE=(hd0,gpt2)/vmlinuz-5.15.0-206.153.7.el9uek.x86_64 ...
To make this even more convenient, we’ve added a provisional --ctf option to the drgn command starting in version 0.0.27-1.0.1, which will load CTF information at startup, without trying to load the DWARF info. Of course, this is a provisonal argument, and it’s not something that’s guaranteed to be maintained once this feature is fully upstream. With this, you can even run many of the drgn helper scripts from the contrib/ directory. These scripts are designed to run with DWARF info, but thanks to CTF, they can run without any of that:
$ drgn --ctf /usr/share/drgn/contrib/mount.py Mount Type Devname Dirname ffff90f240205a00 rootfs none / ffff90f280816780 proc proc /proc ffff90f280816b40 sysfs sysfs /sys ffff90f280816000 devtmpfs devtmpfs /dev ffff90f280816dc0 securityfs securityfs /sys/kernel/security ffff90f2808168c0 tmpfs tmpfs /dev/shm ffff90f280816c80 devpts devpts /dev/pts ffff90f27f3cadc0 tmpfs tmpfs /run ffff90f27f3ca8c0 cgroup2 cgroup2 /sys/fs/cgroup ffff90f27f3cac80 pstore pstore /sys/fs/pstore ffff90f27f3ca500 efivarfs efivarfs /sys/firmware/efi/efivars ffff90f27f3ca140 bpf bpf /sys/fs/bpf ffff90f251417280 configfs configfs /sys/kernel/config ffff90f251417c80 xfs /dev/mapper/ocivolume-root / ffff90f2513dc780 rpc_pipefs rpc_pipefs /var/lib/nfs/rpc_pipefs ffff90f2513dc3c0 selinuxfs selinuxfs /sys/fs/selinux ffff90f28061fc80 autofs systemd-1 /proc/sys/fs/binfmt_misc ffff90f28061f500 debugfs debugfs /sys/kernel/debug ffff90f2513dc280 hugetlbfs hugetlbfs /dev/hugepages ffff90f2513dcb40 mqueue mqueue /dev/mqueue ffff90f2513dc000 tracefs tracefs /sys/kernel/tracing ffff90f244dc2000 ramfs none /run/credentials/systemd-sysctl.service ffff90f2800e2dc0 ramfs none /run/credentials/systemd-tmpfiles-setup-dev.service ffff90f244dc2dc0 fusectl fusectl /sys/fs/fuse/connections ffff90f2800e2780 xfs /dev/mapper/ocivolume-oled /var/oled ffff90f2800e2500 xfs /dev/sdb2 /boot ffff90f244e08dc0 vfat /dev/sdb1 /boot/efi ffff90f2800e23c0 ramfs none /run/credentials/systemd-tmpfiles-setup.service ffff90f2462ac000 tmpfs tmpfs /run/user/983 ffff90f25153d640 tmpfs tmpfs /run/user/1000
Comparing to DWARF
Of course, we know that there are no silver bullets: CTF may be able to do a lot, but it must give up some functionality in order to fit within a sliver (0.29%, to be exact) of the disk space of the kernel-uek-debuginfo package.
The answer is that there are several items that we sacrifice. We’ll describe each of them below.
- Source filenames and line numbers in stack traces.
- Inlined functions in stack traces.
- Variable and register values within stack traces.
- Simplified access to conflicting variable, type, & function names.
The majority of these items have to do with stack traces. DWARF information includes a very rich set of instructions for unwinding application stacks. This information doesn’t just allow you to recover the call chain (i.e. the list of functions on the stack). It also provides information about inlined functions, and even gives information on how to find the values of functions and registers which may be pushed to the stack. And finally, DWARF typically comes with a mapping from source code instructions back to the filenames and line numbers of the source code.
CTF itself has no information to help unwind the stack. As mentioned above, drgn has to rely on either frame pointers, or ORC to unwind the stack. Neither of these options can provide the amount of detail which DWARF is able to achieve. To show an example of this, let’s go back to the stack trace above which used CTF (or really, ORC). Here is the equivalent stack trace, using DWARF debuginfo:
>>> prog.thread(1).stack_trace() #0 context_switch (kernel/sched/core.c:5022:2) #1 __schedule (kernel/sched/core.c:6368:8) #2 schedule (kernel/sched/core.c:6451:3) #3 schedule_hrtimeout_range_clock (kernel/time/hrtimer.c:2298:3) #4 ep_poll (fs/eventpoll.c:1894:17) #5 do_epoll_wait (fs/eventpoll.c:2272:10) #6 __do_sys_epoll_wait (fs/eventpoll.c:2284:9) #7 __se_sys_epoll_wait (fs/eventpoll.c:2279:1) #8 __x64_sys_epoll_wait (fs/eventpoll.c:2279:1) #9 do_syscall_x64 (arch/x86/entry/common.c:50:14) #10 do_syscall_64 (arch/x86/entry/common.c:80:7) #11 entry_SYSCALL_64+0xa1/0x19c (arch/x86/entry/entry_64.S:119) #12 0x7f5c0b35429e
First, there are 13 stack frames in this trace, compared to 9 with CTF and ORC. This is because stack frame 0 is actually an inline function call from frame 1. Similarly, frames 6 and 7 are in fact inline function calls from frame 8. When unwinding the stack without DWARF information, we don’t know about these inline functions, and so the stack traces just include the original function names.
What’s more, this stack trace obviously contains a filename and line number for each function. This is not present for the CTF & ORC trace. Finally, thanks to drgn’s ability to use DWARF to recover stack variables, we can even read a local variable from the ep_poll() function (frame 4):
>>> prog.thread(1).stack_trace()[4]["ep"]
*(struct eventpoll *)0xffff90f243ecd900 = {
.mtx = (struct mutex){
.owner = (atomic_long_t){
.counter = (s64)0,
},
# ... this continues for a long time, it's a big struct!
If we try this when only using CTF, we won’t get anything at all:
>>> prog.thread(1).stack_trace()[4]["ep"] Traceback (most recent call last): File "<console>", line 1, in <module> KeyError: 'ep'
Finally, the other major item that CTF cannot do, compared to DWARF, is enable simpler access to conflicting type names. With DWARF, drgn allows you to disambiguate between two types of the same name, by selecting the filename in which it was defined. CTF does contain all of the conflicting definitions for types, but it doesn’t record the filenames for each definition. Instead, it makes the most frequently used definition available for lookup by name. The less frequently used definitions are still available, but they need to be looked up based on the kernel module that they were defined or used in. This is not a perfect scheme, as some conflicting types may appear in the same module, or even together in the core kernel. However, in practice, we’ve found this limitation to be almost invisible, even in a big project like the Linux kernel.
Upstream Status
The CTF support we have described is available for all released UEK versions, and it is available in the drgn RPMs published in our “addons” channel for Oracle Linux 8 and 9. However, not all of the pieces are upstream yet. We are committed to upstreaming them.
For the components of drgn, we have divided the work into a few major groups, all of which are tracked on a Github issue:
- Designing and implementing an extensible interface for symbol finders. This was implemented and merged, and has been released as part of drgn 0.0.27.
- Next is creating kallsyms finders which implement the symbol interface defined above. This is under review at the time of writing.
- Finally, the last component is implementing the CTF type & object finders, as well as the refactors for ORC loading. These are available in our development branch but not yet part of a pull request.
In parallel, we are upstreaming our support for CTF as a debuginfo format for the kernel. Up-to-date patches for CTF can be found on the dtrace-linux-kernel repository, based on each upstream kernel version. We’re continuing to iterate on the patches based on feedback upstream.
In conclusion, we’ve extended the functionality of drgn in a way that allows Oracle Linux users to debug live and crashed kernels without needing to deal with debuginfo, in the vast majority of cases. We’d encourage you to read more details about our corelens tool which uses this CTF support to provide a powerful kernel diagnostic reporting system. We’re committed to continuing to improve and upstream these technologies so that the rest of the Linux ecosystem can also take advantage of them.
Links & References
Related to drgn:
- Project home
- Documentation
- Symbol Finders
- [Type Finders][type finders]
- Object Finders
- Issue #176: DWARFless Debugging
- Pull Request #241: Introduces Symbol Finder API
- Pull Request #388: Introduces Symbol Finder API
- Full CTF Development Branch
Related to CTF:
- CTF v3 Specification
- libctf API header of binutils
- LWN Coverage of CTF
- DTrace
- Linux Kernel source tree with CTF patches
Other debugging formats:
- DWARF
- SFrame (covered on LWN, and again)
- ORC (covered on LWN)
Related posts: