What I Did On My CTF Vacation

Ali Bahrami — Friday July 17, 2026

Surfing with the Linker-Aliens

My day job is to take care of the Solaris Linkers, and all things that involve the ELF object file format, and its application to Solaris. A nice thing about this job is that the linkers are adjacent to, and intersect with, a wide swath of the OS. If working on something outside my area improves the overall linking experience in a significant way, I can go do that. This adds variety and keeps things interesting. These side trips are common, but are usually short lived, unlike my recent journey through the land of CTF.

CTF (Compact C Type Format) was a Sun invention from a few years before I joined Sun in 2005, created to enhance the abilities of core observability tools such as mdb/kmdb, DTrace, and the proc tools. CTF has been included in the objects delivered with the Solaris OS for many years, and is a linchpin of the Solaris observability story. From the day CTF was introduced to Solaris, there as been a desire to expand its use outside of the confines of the OSnet, at a minimum, to the Open Source software we deliver from the Solaris Userland consolidation, and ideally even to 3rd party and customer built objects. These wider goals went unaddressed for years. CTF intersects with linking and ELF, but for various reasons, I was not pulled into working on it until recently.

Starting in April 2024, I allowed myself to start poking at the Solaris CTF, and things snowballed into a large rolling project to modernize and improve things. Now that the dust is settling, I've written this article to describe the changes made, and provide the details of how we got there. The features should be of interest to anyone who builds code on Solaris, and would like to use mdb/DTrace with that code. The lower level details may be of interest to those who wonder how we threaded the needle through the possibilities. Some of it will not be news, and may retrace work you've already done yourself, but I hope there are also some fresh ideas and useful information.

If you just want to know how to add CTF to your objects, but are not all that interested in reading a very long article about how it works, please see the ctf(7) manpage. If you are interested in some of this, but not all of it, here is a Table Of Contents:

High Level Summary Of CTF Features Added to Solaris (2024-2026)

The following is a summary of the CTF improvements that I have made to Solaris over the last couple of years.

Dedicated CTF Section Type (SHT_SUNW_ctf)
Defined a dedicated ELF section type for use with .SUNW_ctf sections, in preference to the historical practice of defining these sections as SHT_PROGBITS. As a result, <sys/elf.h> now defines:
#define SHT_SUNW_ctf    0x6fffffeb

The CTF tools, libraries, and the OS have all been modified to use SHT_SUNW_ctf for this purpose:

% elfdump -cN.SUNW_ctf /lib/libc.so.1 

Section Header[36]:  sh_name: .SUNW_ctf
    sh_addr:      0               sh_flags:   0
    sh_size:      0x14d5f         sh_type:    [ SHT_SUNW_ctf ]
    sh_offset:    0x2627c8        sh_entsize: 0
    sh_link:      30              sh_info:    0
    sh_addralign: 0x4

Support for .SUNW_ctf sections of type SHT_PROGBITS will remain permanently in place to support existing objects in the field, but are no longer produced for new objects.

The ELF format allows section type to be defined for specific purposes. Doing this, rather than declaring sections as SHT_PROGBITS and relying on strcmp() to distinguish them is considered an best practice, as it is both more efficient, and self documenting. SHT_PROGBITS is intended primarily to label code sections that linkers do not decode or examine, but historically has also been used for debug sections. This is appropriate for the debug sections produced by compilers for use by their own debuggers (e.g. cc and dbx, or gcc and gdb) because as with code, the linkers and similar tools do not decode or examine them directly. I believe that SHT_PROGBITS was originally applied to CTF sections because they also also serve a debug purpose. However, CTF is different than those other sections, because CTF is standardized and manipulated by the operating system, and is a core part of our infrastructure. As a linker alien, I've long been aware that that CTF should probably have its own type, but it was a minor concern, and we left it alone. As I started this large CTF project, it seemed like a good time to warm up with this fix that falls squarely in my usual linkers/ELF box, an easy way to start changing CTF code and get my feet wet.

strip and ld -zstrip-class Treat DEBUG And CTF As Distinct Classes
Modified the strip utility, and the ld -zstrip-class option to treat the normal debug sections produced by the compilers, and CTF sections, as different classes of material, allowing one to be stripped while keeping the other. Prior to this change, they were all stripped without any fine grained control. That was an inadequate design. It is true that these are all indeed "debug" sections in some general sense, but these sections serve very different purposes and their use is disjoint. Debuggers tend to use one or the other, but not both. With increased use of CTF, I anticipated that this former unimportant corner case would become a common issue. This change resolves that concern.

CTF Format Now Public, Committed, And Supported
The CTF format used by Solaris is now public and committed, and subject to the Solaris backward compatibility guarantee.

CTF Utilities (ctfconvert, ctfdump, ctfmerge) Available In /usr/bin
The CTF utilities, ctfconvert, ctfdump, and ctfmerge, are now standard supported utilities delivered with the system in /usr/bin. As part of this, and prior to raising their commitment level, some default behaviors were changed and old options deprecated, in the interest of making the tools easily applicable in contexts other than that of building the Solaris OS itself. These changes are covered in the manpages (see next item).

Documentation
Manpages have been added to the system covering all aspects of CTF. The 3 utilities are described by ctfconvert(1), ctfdump(1), and ctfmerge(1). The format details are documented by ctf(5). General information of interest to anyone wanting to add CTF to their own objects can be found in ctf(7).

Callable CTF Library
The bulk of shared code used by the CTF utilities has moved to a new private shared library, libctfgen, which is used by all 3 utilities, but which is also available for use by other system utilities, and specifically, for use by the link-editor (ld).

While the intent was to make libctfgen usable by other callers, it was not initially in a state that would support that. In its original form, it called exit on any error, had its memory allocator hardwired to use malloc(), and printed all errors directly to stderr, rather than allowing the calling application to manage those details. These were all reasonable choices for the context that code was originally written to support, but library code needs to be written to give the calling application control over such details. The link-editor has its own memory allocator and error reporting mechanisms that it would need libctfgen to integrate with, and cannot tolerate having a library function call exit(). Those things needed to change, to continue support the original utilities, while allowing wider use. Once libctfgen was established, I audited and modified its code line by line, to allow the caller control over memory allocation, and error reporting, and to return an error status code indicating one of TRUE, FALSE, or ERROR, which each caller either handles, or passes to its caller, as appropriate. The result of this painstaking overhaul was to bring this code to library standard while preserving the correctness of its basic operation.

Cross Platform Support For Byte Swapping
To prepare for future use of libctfgen by the link-editor (ld), and knowing that ld is a cross link-editor, I modified libctfgen to xlate (byte swap) CTF data when processing objects from a different platform than the one on which the CTF operation is happening. CTF data is swapped to system byte order on input, and swapped to the target platform byte order on output.

At the same time, I modified ctfdump to xlate CTF content on input, allowing it to display CTF content for an object from platform with the opposite byte order (e.g. using ctfdump on x86 to examine a sparc object).

Add CTF To Objects Using New ld -zctf Option
Using the newly rehabilitated libctfgen, I added a new option to the link-editor (ld) that allows it to add CTF to the resulting object, eliminating the need for ctfconvert/ctfmerge. libctfgen is called as part of the normal reading and writing of data in the link-edit process, eliminating the post-processing and object rewriting done by those utilities, and is more efficient, as well as being easier to use. As described in ctf(7), prior to the addition of this link-editor option, the process of adding CTF to a program looked like the following:
% cc -c -g main.c
% ctfconvert -S main.o
% cc -c -g sub1.c
% ctfconvert -S sub1.o
% cc -c -g sub2.c
% ctfconvert -S sub2.o
% cc -o prog main.o sub1.o sub2.o
% ctfmerge -o prog main.o sub1.o sub2.o
        

That is still supported, but the ld -zctf option can greatly simplify the process:

% cc -c -g main.c
% cc -c -g sub1.c
% cc -c -g sub2.c
% cc -o prog main.o sub1.o sub2.o -zctf=convert

Or:

% cc -c -g main.c sub1.c sub2.c
% cc -o prog main.o sub1.o sub2.o -zctf=convert

Or Even:

cc -g -o prog main.c sub1.c sub2.c -zctf=convert

There is a subtly in my claim of efficiency that might be worth mentioning. When ld -zctf=convert is used, the ctfconvert operation is done for every input object, on every link. In the usual case where everything is compiled and linked once, there is one convert operation per object, and the objects are not rewritten, which is clearly more efficient. However, this may be less efficient overall in the case where you are engaged in an ongoing compile/link development cycle for a program with lots of input objects, only a few of which change. In that case, having ctfconvert run on each input object only when that object is recompiled may turn out to be more efficient in aggregate, than repeating it with each link. I doubt that you would feel the difference, and don't think it's worth worrying about, but mention it in the interest of full disclosure. The convenience of -zctf far outweighs any development time overhead for any but the largest of programs (see the next item).

Performance Work
While testing the new ld -zctf feature on a test link of an object provided by a customer well known for the extreme size of their objects, I found that the CTF merge phase required over 30 hours to complete. Without -zctf, the same link took 4 minutes. The source of the trouble was a doubly nested O(n^2) loop in a function named check_for_weak(). That loop compares all weak symbols in the output object to every other symbol in an attempt to locate a non-weak version of the same symbol. This O(n^2) algorithm has been used in ON since 1999, and is evidentially not too expensive for normal sized links, and particularly with code that (wisely) doesn't use many weak symbols. However, in this case, there were ~4.5 million symbols, ~100000 of which were weak. This means that we may end up doing 450 billion comparisons, which is simply going to take some time.

The largest speedup came from eliminating the use of gelf_getsym(). gelf_getsym() uses a mutex internally to make it thread safe, but in this context, there is only one thread, and the accesses are all readonly, so locking is unnecessary. I replaced the call to gelf_getsym() with a libctfgen-specific version that doesn't lock, and the runtime dropped to 3 hours, which is a dramatic win, while still being much too slow.

The other speedup came from rewriting check_for_weak() to use an AVL tree for these lookups, built only when weak symbols are present. There is a one time setup cost which is O(n), followed by an O(logN) cost per weak symbol. Using this version, the customer link dropped to ~6 minutes, which returns it to the realm of the possible. I would like to think that even this might be improved, but that is left for another day. The impact on more normal links is of course far less dramatic, but still provides a useful speedup for our OS builds. The per-objects savings may be small, but add up across a full build of many objects.

CTF Version 3
Added support for CTF version 3, following the lead of FreeBSD. Their version 2 is the same as Solaris version 2, while version 3 is a straightforward widening that replaces 16-bit CTF type IDs, and size fields, with 32-bit ones, removing some uncomfortably small size limits that tend to become an issue with some open source software, but which were never an issue for the OS itself.

Version 2 remains supported, and is still used by the Solaris OS, which has no issues with the limits of that version, but version 3 is now the default, and will be used unless the version is explicitly specified. The CTF version is a per-object choice. Each object loaded in a process is free to use either version, independently of any other object, and there is no performance penalty for such mixing.

Noting that the C in CTF stands for "Compact", it should be noted that version 2 remains smaller for obvious reasons, but version 3 is also very compact. When doing this work, I built the OS both ways, to quantify the cost. I found that on disk, where compression is used, that the CTF version 2 encoding of the entire OSnet takes 94.5% of the space required for a version 3 encoding, and that the absolute difference amounts to a mere 1M. The numbers in memory are slightly worse: CTF version 2 would require 78.9% as much memory as CTF version 3 for the entire OSnet. This difference is still only 11.2M, but note that getting to that number would require loading every OS object into a single process. That is probably not even possible, and is definitely not realistic. Hence, using version 2 is a harmless micro-optimization for code that can use it, but most users should not be concerned, or even give it a second thought. Our choice to continue using version 2 for the OS was primarily made on the basis of backward compatibility and a desire to see that code regularly exercised. The resulting space savings are nice, but inconsequential.

Support GNU CTF As Input
Building on top of CTF version 3, I added support for reading the GNU CTF produced by gcc -gctf, and transparently converting it to native Solaris CTF. This allows Solaris users to entirely bypass the ctfconvert process, and let the compiler provide the CTF content directly. Using this feature, the example given above can be reduced to a single line:
% gcc -gctf -o prog main.c sub1.c sub2.c -zctf

Thanks to our friend Rainer Orth at Bielefeld University, the latest version of gcc now has a -gsctf option that passes -gctf to the compiler internally, and -zctf to the link-editor, allowing those two options to be replaced by a single one:

% gcc -gsctf -o prog main.c sub1.c sub2.c

The best reference for GNU CTF that I know of is The CTF File Format by Nick Alcock. This document goes into sufficient detail that I was able to determine that while GNU CTF differs significantly from Solaris CTF in its container concept and its version of libctf, their version 3 CTF itself lines up very closely with Solaris CTF version 3, so much so that it was fairly straightforward (though still a pile of work) to have the Solaris libctfgen recognize their .ctf sections, and transparently convert them to native Solaris CTF. The only difference of high level significance is the addition of a single added CTF kind, CTF_K_SLICE. Initially, I did not implement SLICE, translating them instead into CTF_K_INTEGER with an encoding.

CTF_K_SLICE: Support Enum Bitfields
Fairly soon after implementing the support for GNU CTF, had second thoughts, and went back and added the GNU CTF_K_SLICE kind to the Solaris implementation. I had initially thought SLICE to be an unnecessary complication that could be dispensed with, but quickly came to understand and appreciate that it exists in order to allow CTF to properly represent enum bitfields. This is something that native Solaris CTF was unable to do, and has been the source of some bugs. Bitfields are an odd, but useful, feature of the C language, that can be used to specify integers of arbitrary width (in bits), but only within structures or unions. Consider these struct definitions containing integer, and enum, bitfields:
typedef int myint_t;
struct st_int {
        myint_t i_zero;
        myint_t i_one:4;
        myint_t i_two:5;
        myint_t i_three:9;
        myint_t i_four:32;
} x;

enum num { ZERO = 0, ONE, TWO, THREE, FOUR };
typedef enum num num_t;
struct st_enum {
        num_t e_zero;
        num_t e_one:4;
        num_t e_two:5;
        num_t e_three:9;
        num_t e_four:32;
} y;

The fields in x are integers, and can be properly represented in CTF using the CTF_K_INTEGER kind, with an appropriate encoding. However, the fields in y are enum type, represented with CTF_K_ENUM, and unlike CTF_K_INTEGER, CTF_K_ENUM makes no provision for specifying an encoding, causing CTF to represent them all as 32-bit ints. This bad data confuses mdb, which is how we first became aware of the flaw. One workaround for this is to turn such cases into CTF_K_INTEGER, but doing so is very unfortunate, because the nice mnemonic name for the value provided by the enum is lost in the process. SLICE is the answer for this, allowing an encoding to be applied to a kind that does not have that ability itself. As far as I know, the only kind for which this makes sense is ENUM, and Nick confirms for me that this is the main purpose for which GNU CTF uses SLICE.

file Utility Support For CTF
Modified the file utility to report the presence of CTF independently of reporting that debugging information is available. This is in line with the earlier changes to the strip utility and the ld -zstrip-class option.
% file /lib/64/libc.so
/lib/64/libc.so: ELF 64-bit LSB dynamic lib AMD64 Version 1 [SSE2 SSE],
    dynamically linked, not stripped, no debugging information available,
    CTF present

This is a small thing, but as people started using the new CTF features, they wanted to confirm that it was working. While they could have used elfdump to look for the resulting .SUNW_ctf section, it became apparent that file should provide an easier way to check, in line with its traditional reporting of debug information.

Add CTF To Open Source Delivered From Userland Consolidation
Using the link-editor (ld) LD_OPTIONS environment variable to pass the ld -zctf option, we are now adding CTF to a majority of the C based software to the open source software delivered with Solaris from the Userland consolidation, without substantially modifying their makefiles and/or auto-generated build systems. For instance:
% elfdump -cN.SUNW_ctf /usr/bin/python

Section Header[31]:  sh_name: .SUNW_ctf
    sh_addr:      0                   sh_flags:   0
    sh_size:      0x10f               sh_type:    [ SHT_SUNW_ctf ]
    sh_offset:    0x322c              sh_entsize: 0
    sh_link:      27                  sh_info:    0
    sh_addralign: 0x4

With this, we've achieved the overall goal of spreading CTF widely into code outside of the OSnet itself.

Restructure ctfdump Output
Allow ctfdump to accept multiple object arguments, including archives, and reorganize the output to make it easier to read, and comprehend. The resulting output will still be familiar to anyone who has used the older version, but is laid out more systematically, and with more attention to legibility. These improvements are influenced by our well regarded elfdump utility, which serves a similar purpose in the ELF ecosystem. The original output was serviceable, but with daily use, I started noticing things that could be improved. As ctfdump becomes public, and available to a much larger audience, it seemed that an improved presentation would be useful. The changes made are:

The result is shorter and sparser output that is easier for the eye to track.

Support .SUNW_ldynsym Symbol Table
Added support for the .SUNW_ldynsym symbol table to the CTF generation utilities (ctfmerge, libctfgen, libctf), and mdb, thereby allowing CTF that uses the .dynsym dynamic symbol table to also access local function symbols. This was long deferred work stemming from the introduction of .SUNW_ldynsym in 2007, made easier by my newfound understanding of the CTF code. It should be noted that use of the dynamic symbol table remains rare, as the default is to use .symtab symbol table, and we still recommend that as a best practice. The main advantage of using the dynamic symbol table is that one can strip the .symtab if desired, without also losing the CTF. In such cases, the new support for the .SUNW_ldynsym will be an improvement.

Fully Support Compressed ELF Sections
Compression has long been a problem for CTF. For historical reasons, there are 2 ways to compress CTF data in ELF objects on Solaris:

  1. Using the compression mechanism built into the CTF format itself. When this mechanism is used, the CTF header will have the CTF_F_COMPRESS flag set:
    % ctfdump /usr/bin/ls | grep cth_flags
    cth_flags             0x1 [ COMPRESS ]

  2. Using the general section compression mechanism provided by the ELF object format, as provided by libelf and the elf_compress() function:
    PSARC/2013/139 ELF debug section compression
    PSARC/2015/543 public libelf section compression APIs
    When this mechanism is used, the .SUNW_ctf section header will have the SHF_COMPRESSED flag set.

There is a simple explanation for this unfortunate state of affairs. The CTF format predates the addition of support for compression to ELF by over a decade. Had ELF support for compression existed in the late 90's, the CTF authors would almost certainly have used it rather than complicating their own work.

Over the years, we have built out our support for ELF section compression throughout the system, in linking utilities, libproc, debuggers, and similar tools. That support has been reasonably complete and functional for years, but our CTF generation code (ctfconvert, ctfdump, ctfmerge, libctfgen) was a notable exception:

This state of affairs has persisted for years, because the problems have been relatively low priority, because there was not much work happening on CTF, and because improvement was not necessary for building the OS/net, which was the only user of CTF. This project changes those calculations, and delivers several improvements:

I should say that the goal of this work is not to encourage or discourage the use of compression, or even to spur a conversion from the original CTF compression to the ELF version. My immediate concern was to ensure that our basic toolchain utilities can handle all the possibilities, and that end users can access those features using simple and documented interfaces. For more information on the options for CTF compression, see ctf(7).

Detect And Reject Unmerged CTF
Modified libctf and ctfdump to detect the case where an object contains a .SUNW_ctf section that was not created by ctfmerge. Previously, they would try to interpret this bad data and show plausible looking, but incorrect data. Now, it is rejected. This scenario cannot not occur when using ld -zctf, but is easy to demonstrate using the original CTF utilities. To do so, simply use ctfconvert on the input objects, and link the object, without running ctfmerge on the result:
% cc -g -c ~/hello.c
% ctfconvert hello.o
% cc hello.o 
% ctfdump a.out
ctfdump: a.out: CTF not associated to a symbol table: ctfmerge required

Support Ancillary Objects
As with compression, our existing support for Ancillary Objects was rudimentary and incomplete, yet another area where newfound knowledge of the CTF code allowed me to improve matters. ctfdump can now dump objects for which the CTF and/or symbol tables are not in the primary object, and ctfmerge can update output objects that have ancillary objects.

A CTF History: What Took So Long?

Historically, compilers have produced debug sections to describe the generated code. These sections, which initially were in the Stabs format, and then later DWARF, are used by debuggers such as dbx and gdb to enable debugging of code at the source level.

Low level operating system debuggers, notably adb, were shut out of using this information: It was too large to be included in all OS objects, often complex to access efficiently, and variable between different compilers. That data was intended to serve the needs of specific compilers and to provide a large amount of source level information, information not needed by these low level tools. When mdb was developed to replace adb, the CTF concept was invented to support it. The idea was that a tool could read the stabs/dwarf content from object files, and generate a small description of the basic type information that an assembly level debugger requires. CTF was introduced with Solaris 9 in 2001:

PSARC/2001/021 Kernel Stabs

CTF provides a description of the types used by code, expressed at the level of the C language, small enough to be included by default, and designed for rapid access, meeting the fast and light requirements of low level introspection tools.

In 2003, CTF was extended to userland, first appearing in Solaris 10:

PSARC/2003/283 Userland CTF Support
Initially, that support was used in libc, and then more recently, for all shared objects delivered by the OSnet. When I joined the Solaris OS group in late 2005, that is where things stood. CTF and the tools that enable it were an invaluable part of Solaris, but they were expert tools, usable by their creators in house, but possibly not yet ready for publication, nor easy enough to use for a mass audience.

Discussions about finishing this work, and raising the commitment level would come up periodically. The upside from raising the commitment level of CTF is clear: having CTF in more objects will improve the ability of mdb, DTrace, and other observability tools to extract useful information, in more situations. However, over the years, there have been various reasons why it didn't happen.

The original CTF authors took this post-processing approach by necessity. Ideally, CTF would be created by compilers, and merged during the link-edit, but the project would have been dead on arrival had they taken that approach. There are too many compilers, and they are outside of the operating system's purview. The link-editor is under our control, and a good place for this, but integrating a feature like that is distinctly non-trivial. At the beginning, this was an R&D effort with an uncertain future. The light weight and relatively agile post-processing approach allowed rapid progress, and kept the focus on proving the CTF concept. That was a full success, but the follow on work to deliver that functionality with an easier interface remained, and the best way to do that was unclear.

I recall discussing the idea of moving CTF generation to the link-editor (ld) around 2012 or so. It seemed the logical next move, but as noted, a very large job. We in the linking group did not have a working knowledge of that code, and its authors were no longer around to consult. Of course, the code was there to be read and studied, so doing something was definitely still possible, but it would have been slow going, and this was just one of many projects needing linker alien attention, and not necessarily the most urgent one. However, the larger reason why nothing was done is that there was a debate in the OS group at that time about whether CTF was necessarily the right way forward. CTF had successfully proved the benefit of a small efficient format for this purpose, so that was not in question. However, it was a non-standard addition, unique to Solaris, and systems descended from Solaris. In the same years, the wider world had moved to the DWARF format for representing debug data, which has become as close to a universal standard as we're likely to see. Did it makes sense to figure out a way to use that, rather than promoting a separate niche format just for low level debuggers?

DWARF is not the most efficient format to access, but that could have been managed. The important concern is that unlike CTF, DWARF is decidedly not compact. This is partly because the encoding is verbose, partly because compilers load it with all the information a source level debugger could ever want, and partly because the compilers don't always do a good job of minimizing redundant duplicate data. It is not unusual for DWARF debug data to be 10x the size of the code it describes. This is not a Solaris-specific problem, but rather, something that's felt industry wide. Given the generality of the issue, we had some reason to think that the situation might improve, and that compilers might evolve to offer a "slim DWARF" option that might create data compact enough to replace CTF. Another DWARF concern is that different compilers generate slightly different DWARF dialects, and here again, it was hoped that a slim DWARF might encourage more standardization. In general, we thought that as an industry standard format, a slimmed down DWARF option might emerge that would render CTF redundant.

And so, time passed. Efforts to create a slimmed version of DWARF were made, but there was not much progress, and the status quo persisted. DWARF debug data today is as large as ever, and there is no reason to think that will change any time soon. Meanwhile, CTF has quietly plugged along with minimal change, quietly solving the problem it was invented to solve, but only for core OS system objects.

In April 2024, the question came up again, this time as an expressed desire to add CTF to a selection of key open source packages delivered with Solaris from our Userland consolidation, to enable mdb to do a better job of diagnosing problems in that code. By this time, it had long been evident to me that the slim DWARF idea was a dead end, and that CTF was the obvious way forward. I knew very little about the CTF format, or the existing code, but the things I do know about adjacent areas (compilers, linkers, libproc, etc) made me well positioned to take a deeper look. No one else was presently working in that area, and I had just finished a long term project and was thinking about what my next one should be. So, I stated reading code, and weighing options.

I already knew that CTF had moved into the wider world with OpenSolaris, fueled by ports of DTrace, and existed on darwin (macos), FreeBSD, and Linux. CTF is no longer a Solaris-only concept, and appears to be thriving. Indications are that it will be around for many years, whatever else might also emerge. As I started looking at trying to make the Solaris version public, I learned that some versions of CTF outside of Solaris have evolved in some compelling ways:

The direct relevance of CTF version 3 and GNU CTF, to Solaris was not evident to me at that time, but I could see that CTF was progressing, which seemed a positive sign, and a reason to keep going. It was immediately evident that we should move to clean up and publish the CTF utilities, ctfconvert, ctfmerge, and ctfdump, as supported and documented system utilities. However, it was also clear that this would not be enough to cause a wide uptick in CTF adoption in Userland opensource. The changes necessary to their makefiles would be complicated, require ongoing maintenance, be hard to upstream, and as a Solaris-only feature, relatively uninteresting to those upstream projects. One might do it for a few key projects, but it would be difficult to scale it to a large number. Also evident was that if we could somehow give the Solaris link-editor the ability to do the work of ctfconvert/ctfmerge, turning the necessary upstream change into the simple addition of a linker option, that would be far more likely to work widely, and be sustainable.

The Solaris link-editor has the ability to read command line options from the LD_OPTIONS environment variable, a feature we've long used to pass Solaris-specific options to open source software in our Userland consolidation, behind the back of their build environments. Given an ld option for adding CTF to the output object, LD_OPTIONS could be used to trigger that option for a substantial fraction of the code built from Userland with almost no added effort. Of course, this won't work for everything. Some software has build systems that filter environment variables, and not everything is written in C, the sweet spot for CTF. Even so, a substantial percentage of Userland could be reached, including most of the things we really care about adding CTF to. And so, I came up with the following rough list of high level tasks as a starting point:

Behind each of these high level bullet points lie many subproblems. The detailed TODO list quickly became long, and raised many questions. The overall idea was large, speculative, with uncertain prospects. However, as is often the case in such situations, it was immediately clear that most of the subproblems were tractable and could definitely be solved, and each one would be of value, no matter how the larger project turned out. As each such subproblem was solved, the remaining pile would become smaller, and with luck, simpler, and clarity would emerge. In the worst case, we'd have make some improvements, and moved past the current block. In the best case, we'd make a breakthrough.

And so, it was time to stop studying, start coding, and see where things led. I made a detailed TODO list, a list of unresolved open questions, picked a problem I knew how to tackle, and got started. The result was a 2 year journey through the CTF space that far exceeded expectations, and which is only now winding down. For the first year, my TODO and open questions list grew even as I crossed items off, because the work led to more insights and exposed me to new things to think about. That eventually slowed down as things coalesced into their final shape. The TODO list began to shrink, and is nearly empty now.

The Fixes, Blow By Blow

The opening section of this blog lists the improvements that have been made to Solaris CTF support over the last 2 years. That summarized high level view gives the impression that this was predetermined, planned in advance, and inevitable, but the truth is that I went in not knowing a lot, and this was a step by step exploration, in which I'd pick a likely sub-problem, work on it, deal with whatever fallout resulted, reevaluate, and push on. Mistakes were made and corrected, experiments didn't always succeed, and the overall strategy shifted as I made progress and learned more.

This section describes the actual path taken, fix by fix, with additional comments for each describing the thought process at the time, and discussing lower level implementation details. This information is probably not of interest for most readers, but may be interesting to those also working their way through the issues of evolving CTF. It shows how we got from our starting point in 2024 to where we are now in 2026, while continuously maintaining the ability to build, use, and ship the system to customers, throughout that period.

Each subsection below describes one fix, given in the order fixed. Each subsection begins with a list of the bugs and PSARC cases it resolved. In most fixes, the buglist is followed by a "[ High Level Goal ]" link that if pressed will take you back to the high level discussion above, for the goal this fix contributed to. Note that multiple fixes can refer to the same goal. A discussion of the lower level details of the fix then follows. To establish context in this discussion, some information discussed above may be repeated, but I have tried to minimize such repetition.

Fix 1: Dedicated ELF Section Type (SHT_SUNW_ctf)
PSARC/2024/076 SHT_SUNW_ctf: Dedicated ELF section type for CTF
36675528 SHT_SUNW_ctf: Dedicated ELF section type for CTF

[ High Level Goal ]

An easy warmup, before getting down to business, this change creates an explicit ELF section type for Solaris .SUNW_ctf sections, SHT_SUNW_ctf, and modifies CTF code and the rest of the OS to use it. Support for the older use of SHT_PROGBITS is retained to handle older objects. SHT_SUNW_ctf is part of the Solaris specific ELF ABI, so only applicable to objects tagged as ELFOSABI_SOLARIS, which is the case for most Solaris objects:

% elfdump -e /lib/libc.so.1 | grep ei_osabi
  ei_osabi:   ELFOSABI_SOLARIS    ei_abiversion: EAV_SUNW_CURRENT

One reason for this change was just to tidy up something that I had long been aware of. Another was to further distinguish our native CTF .SUNW_ctf sections from the .ctf SHT_PROGBITS sections produced by gcc -gctf. None of this is strictly necessary, but it makes things nice and tidy. Independent of that, it is worth noting that Solaris and GNU ctf sections, by virtue of having different names, can coexist within a given object. However, there is no benefit to doing that, and it is not expected to happen in practice.

Fix 2: Improve Stripping Functionality For CTF And Debug Sections
PSARC/2024/077 ctf strip class for ld and strip
36675615 ctf strip class for ld and strip

[ High Level Goal ]

The strip utility, and the ld -zstrip-class option, treated the debug sections produced by compilers along with .SUNW_ctf sections as related generic debug content, and would remove all of it under the 'debug' category. Treating CTF as if it were part of normal debug data was a design error. The debug sections produced by the compilers (used by their associated debuggers), and the .SUNW_ctf sections (used by libproc, libctf, mdb, DTrace, etc) are disjoint, and exist for distinctly different purposes. It follows that users might want to keep one and remove the other. The inability to do that had not been an issue when all CTF use was done internally by the OS itself, but would clearly become one once CTF became committed, documented, and more widespread. This fix introduced a new 'ctf' debug class for strip and ld -zstrip-class, allowing them to be treated as distinct categories.

Fix 3: First Step Towards Making CTF Public And Committed
PSARC/2024/078 making CTF public
36650566 making CTF public

[ High Level Goal ]

This was the opening move in the effort to make CTF more widely available, and there was a lot to it:

Fix 4: Byte Swapping (xlate)
36730716 CTF utilities should handle non-native byte order

[ High Level Goal ]

In order to be used by the link-editor (ld) when operating as a cross link-editor, libctfgen was enhanced to swap CTF data when it moves from file to memory and back, when the byte order of the running system differs from that of the object.

The same enhancement was made to the ctfdump utility, allowing the x86 version of ctfdump to dump sparc objects, and vice versa, something that I personally find very convenient in my daily work, since I constantly move between such systems.

The ctfconvert process relies on libdwarf, and I had a concern about whether or not libdwarf would be able to operate on objects for a system with the opposite byte order from the running system. I was happy to find that libdwarf transparently handles that case.

Fix 5: Allow ctfdump To Accept Multiple Object Arguments And Archives
PSARC/2024/090 ctfdump support for multiple files and archives
36827583 ctfdump should handle multiple arguments and archives

[ High Level Goal ]

This change was a general cleanup and modernization of the ctfdump code to allow multiple input arguments, and to prepare it for the upcoming project to overhaul the error handling in libctfgen. It did the following:

Fix 6: Issues With ctfconvert Removing Input Objects On Error
36827626 ctfconvert should report unlink errors

PSARC/2024/097 ctfconvert and ctfmerge -r option
36847015 ctfconvert/ctfmerge should not unlink input files on error by default

[ High Level Goal ]

Shortly after integrating the fix to make CTF public, I had a reason to examine the CTF in libc, but accidentally typed 'ctfconvert' rather than 'ctfdump', and got this momentarily heart stopping result:

% ctfconvert /lib/libc.so.1
ctfconvert: /lib/libc.so.1: no type data to convert
ctfconvert: removing: /lib/libc.so.1

Deleting libc is a fast way to turn your system into a hot brick, so that definitely got my attention. Thankfully I was running as an unprivileged user when I did that, so the attempt to remove libc failed, and my system was unharmed. This immediately raised 2 questions:

  1. Why would it try unlink libc?
  2. Why didn't it report that the unlink attempt failed?

The failure to report that the unlink failed was nothing more than a missing error message, which was easily fixed.

The reason an unlink was attempted in the first place is that this is how ctfconvert was designed to work. ctfconvert and ctfmerge are usually run from a makefile. If the CTF operation failed, the utility would intentionally unlink the input file, ensuring that the next invocation of make will rerun the full build rule, rather than assuming that all is well, and using a half built object. This is undeniably useful behavior, and should be available, but in general, destructive side effects should require an explicit option rather than being the default.

I had already declared ctfconvert to be committed and public with PSARC/2024/078, but since it hadn't yet left our walls and been delivered as shipping product, there was still time to fix this before it would be seen in the wider world. I therefore quickly filed another PSARC case, and reversed the default behavior so that this unlink no longer happens by default. To get the old behavior, one can simply specify the new -r option to ctfconvert and ctfmerge, which I immediately did in the OSnet makefiles.

You can still get the scary message, but now, you have to be intentional about it, turning it into a requested action (like running rm), rather than being a hidden side effect. If you do that, you will now see that the unlink failed, which can be reassuring:

% ctfconvert -r /lib/libc.so.1
ctfconvert: /lib/libc.so.1: no type data to convert
ctfconvert: removing: /lib/libc.so.1
ctfconvert: unable to remove file: /lib/libc.so.1: Permission denied
Fix 7: Enable Use of SHT_SUNW_ctf
36918319 enable use of SHT_SUNW_ctf for CTF sections in ELF objects

[ High Level Goal ]

This fix enabled the use of the new SHT_SUNW_ctf section type. Support for SHT_SUNW_ctf was integrated earlier, but was not immediately used. The reason for this is that older versions of the operating system are used to build new ones, so new ELF features can't be used until they have arrived on the oldest supported build servers. We have rules capping how old a build server can be. When new features land, we seed those abilities without using them, delay for the waiting period, and then turn them on with a follow on fix, like this one.

Fix 8: Refactor libctfgen To Make It Usable As A General Library
36927818 libctfgen code needs to be brought to library standard

[ High Level Goal ]

Having moved the common CTF generation code to libctfgen, it was time to overhaul/refactor it to be usable as a general purpose library usable by the link-editor (ld). This meant:

These are only the high points. There are many details involved in turning old code like this into a good library. The code had good bones, but was written for a different context, and had not had a significant revision in decades. There were many smaller things to chase and address.

The most challenging part was reworking how errors are reported, and having the library return a status code to the caller, rather than simply calling exit(). The basic strategy was simple: Declare a standard return type, ctfgen_ret_t, with three possible values, CTFGEN_RET_TRUE, CTFGEN_RET_FALSE, and CTFGERN_RET_ERROR, and then use it throughout the code base. Code that used to return TRUE or FALSE was changed to return CTFGEN_RET_TRUE or CTFGEN_RET_FALSE. Code that used to output an error message and call exit() was changed to output the message and return CTFGEN_RET_ERROR. Code that had never checked a return status, because the called code never returned on failure, had to be modified to check, and handle that case. All routines at every layer had to be modified to return these code back to the caller, and ultimately, to the outside caller of the library.

It was more complicated that it sounds. The amount of code is large, there is a lot of nested indirection involving nested function pointers, and a small mistake could, and more than once did, break everything. The odds of making so much change in one go successfully are essentially zero. This sort of work really can't be done without excellent tests, and constant testing. I was lucky, because both mdb and DTrace have extensive and thorough test suites. The winning strategy was to iterate, being careful to take small bites at each step, to make constant backups at each step, saving all the backups, and then to run both the mdb unit tests and DTrace test suite frequently.

This was not a fast process, and it required a great deal of focus and careful deliberate strategy about what to change, and in what order. Furthermore, the build/test step takes 4-5 hours, futher slowing the pace. This was probably the most painful part of the CTF project, but once I accepted that this was how to do it, I made steady progress and came out the other side with a viable working library.

Once I got the hang of how big a swing to take with each iteration, I had more passes than fails, but there were definitely times where I broke something, and was unable to spot the change that did it. Here, the constant backups saved the day. By keeping track of which backups had been built, and their success/failure status, I was able to quickly binary chop, isolate the first backup with the problem, and then create an incremental webrev between it and the previous backup. By definition, the mistake would be in that webrev, and usually easy to spot and rectify.

Fix 9: Hide libctfgen's Internal Details
37154766 libctfgen should hide implementation details

[ High Level Goal ]

This was work that could have been done with the previous fix (36927818 libctfgen code needs to be brought to library standard), which I deferred to control code churn in what was already a very large fix.

The libctfgen.h header is used by callers of libctrgen, but it also exposed a lot of internal details of the library. This is bad on general principle, but in addition, introduced a lot of unprefixed generic names into the global namespace that could cause problems for calling code.

This fix split libctfgen.h into 2 versions. libctfgen.h remained as the interface for external callers, and all interfaces defined by it were given a ctfgen_ prefix in order to avoid namespace conflicts. A new implementation header, _libctfgen.h was created to hold the implementation details needed within the library, but not by its external users.

Fix 10: Allow ctfmerge -t to be used with -a
PSARC/2024/123 allow ctfmerge -t to be used with -a
37169748 ctfmerge should allow -t to be used with -a

[ High Level Goal ]

This change involve 2 ctfmerge options:

-a
Automatically run the ctfconvert operation on any input object that does not already have a .SUNW_ctf section, and merge the CTF data produced into the output. Unlike using the ctfconvert command, which adds CTF to the input object, the -a option does not modify the input object.
-t
Require each input file to have a CTF section

-a is a new option, added as part of the work to make CTF public (PSARC/2024/078). I had initially made -a and -t mutually exclusive options. In hindsight, as I tried to use it, I realized that this was overly restrictive and unnecessary, and removed the constraint.

Fix 11: Restore The Ability To Kill ctfmerge With ^C
37326964 ctfmerge during link is impervious to ctrl-C

This was a dumb error I introduced during the libctfgen work that caused the control-C signal handler to not properly end the process, and required a one-line fix. I mainly include it for completeness, but it serves to highlight the risks that come with touching working code, even when one is very careful, and even when the goal (merging multiple copies of the same thing into a single implementation) is worthwhile.

Fix 12: Add CTF Directly From The Link-Editor
PSARC/2024/139 link-editor should support CTF generation
37264872 link-editor should support CTF generation
37264912 gelf_getsym() locking is unnecessary and too slow for ctfgen
37264999 ctfconvert and ctfmerge -a mishandle archives
37265039 ctfgen stabs reader needs to xlate
37301852 ctfconvert: enum has too many values
37301886 ctfconvert: unrecognized real type size

[ High Level Goal ]

Following the massive overhaul of libctfgen, the table was set to use it from the link-editor (ld), allowing ld to perform the operations that previously required post processing input objects with ctfconvert, and the output object with ctfmerge. I added a new option, -zctf, which in turn accepts a list of suboptions corresponding to those of the ctfconvert and ctfmerge utilities. -zctf and its suboptions are documented by the ld(1) manpage.

There were multiple interacting pieces to this:

This went reasonably smoothly, and I soon had a link-editor that could add CTF to small toy programs. As soon as I had that working, I attempted to apply it to larger real programs, which predictably resulted in exposing a variety of new issues to grapple with. The first such experiment was an example link from a customer known for making very large objects. This is a good example of code for which applying the postprocessing approach with ctfconvert and ctfmerge would be very hard, as it has an extremely large number of input objects, many of them in archives. In contrast, applying ld -zctf was quite simple. Doing that revealed 2 issues:

I next pulled a copy of the Userland open source consolidation, installed the new link-editor with -zctf on a build machine, and took a shot at building as much of Userland with CTF as possible, using the LD_OPTIONS environment variable to cheaply apply it to as much of Userland as possible. This was a moderate success, and grounds for some celebration, as I was able to add CTF to many packages. However, there were many rough spots, and I learned a lot:

And so, I worked my way though Userland, getting CTF added to a good chunk of it, but turning it off for numerous packages. It must be said that CTF is not equally important to all things, and we did cover most of what we really care about. And so, I counted it as a success, and integrated the link-editor work. However, hoping to do better with Userland, I held off on integrating that, and put it temporarily aside.

The issue of our lagging support for gcc DWARF versions was probably the biggest issue preventing wider CTF adoption in Userland. Was it time to try and learn more about their DWARF and try to moderize the CTF code that deals with it? That would obviously be a good thing overall, and probably should be done. However, it points at an inherent problem with the DWARF to CTF conversion approach, in that we are always in the position of having to react to changes from elsewhere, sometimes on short notice. CTF conversion is viable, and should work, but was there a better way?

I knew that other systems were also adding CTF to the same sort of code as what we have in Userland, and must have dealt with these issues. Looking into that, I learned more about 2 significant developments.

  1. I came across the FreeBSD ctf(5) manpage, which claimed to support version 3. This caught my eye, because I knew they had started with the same version 2 Sun CTF, by way of OpenSolaris, that we were still using. Indeed, I also found an older version of that same manpage that documented version 2, which I was quickly able to confirm is the same as the original Sun CTF they started with. And reading it carefully, I found that it is a straightforward widening of Sun CTF, using 32-bit integers for type IDs and sizes rather than 16-bit ones. If Sun had set out to do this work, the FreeBSD version is exactly what we would have done. It was evident that this would be a good path for Solaris, and that it would solve the enum limit issues I had encountered.

    The differences between versions 2 and 3 are summarized in section CTF Version 2 and 3 Differences below.

  2. I once again found documentation for GNU CTF, which had been released a few years earlier. At the time that happened, I knew that having gcc produce CTF directly, rather than having to produce DWARF and then convert it, could be a game changer, but I had been told that it was very different than Solaris CTF, and not an easy thing to adopt, and hadn't pursued it further. But now, having learned enough about CTF to have an informed opinion, I was very pleased to note that GNU CTF is closely aligned with what FreeBSD had done with their version 3. The only substantial difference is that GNU CTF has one additional kind, CTF_K_SLICE. At the time, this seemed like an unimportant feature that we might just ignore, and rewrite into CTF_K_INTEGER, using an encoding to capture the width.

    The differences between CTF versions 3 and GNU CTF are summarized in section GNU CTF Differences below.

To summarize, our choices ("what others do") are:
  1. Maintain support in the CTF conversion code for all versions of gcc DWARF that gcc can produce.
  2. Use the CTF produced by gcc -gctf, stay current for free, and entirely bypass the overhead of the CTF conversion process as a bonus.
The gcc -gctf option is clearly the better choice, and with a fresh understanding of CTF v3 and GNU CTF, I could see a clear path to doing it. Since so much of the Userland consolidation is built with gcc, this had the potential to eliminate a lot of the problems I had encountered in my effort to build it. I therefore revised the plan for CTF, and in so doing, widened the scope of this CTF project considerably. We would try to adopt version 3 for Solaris, and then try to make use of GNU CTF directly.
Fix 13: CTF Version 3
PSARC/2025/013 CTF version 3
37409530 CTF version 3

[ High Level Goal ]

This project delivered the following:

The work for this touched both libctf and libctfgen deeply. libctfgen has 2 phases, one that reads CTF as input, and creates an in memory representation of the information it contains, and another that starts with the in memory representation, turns it into CTF, and writes it as output. libctf has its own independent code for reading this content into memory. The changes required to support version 3 essentially come down auditing all of this code line by line, and where the versions differ, adding conditionalized code to handle each version specially.

An important decision revolved around continued support for version 2 CTF. It was necessary to continue supporting it for input, to support existing objects in the field. However, we did have the option to drop the ability to create it in new objects. The argument for this is that the output code can be a bit simpler. The argument against is twofold: (1) The actual amount of code that can be removed is modest, and (2) Unused code rots, and without the ability to create version 2, it becomes hard to test. And so, I decided:

To support both versions, it became necessary to rename many of the definitions in <sys/ctf.h> to include the CTF version in their name, and to provide definitions for the each CTF version. These definitions remain non-public implementation details of the system. There is no exposure to code outside of the OS, and the code in the OS that was affected is the same code that needed updating for version 3. The result is that this renaming was straightforward, and did not impose a widespread cost outside of the CTF code itself.

I should note that libctf supports a "CTF container" concept, used by DTrace, which allows the caller to create CTF descriptions dynamically. These containers live only in memory, do not persist beyond the lifetime of the process containing them, and are not written to disk. As such, there was no benefit to continued support for CTF version 2 in this code, and it was updated to only use version 3.

Fix 14: CTF Issues Exposed By Userland Efforts With Version 3
37302044 ctfconvert: failed to get unsigned (form 0xd)
37302110 ctfconvert: func arg 1 has no name

[ High Level Goal ]

These are both issues in the CTF convert code, found as I rebuilt Userland with the new version 3 CTF support in place, and was able to push deeper. The first is an attempt to read the value from a DW_FORM_sdata as an unsigned value, and simply required a minor revision. The second results from code built by gcc that wants to represent 16-bit, and 128-bit float types, requiring an adjustment to the CTF floating point encoding. Ultimately, the need for these fixes would be reduced by the adoption of GNU CTF from gcc, but it was still useful to fix them, for correctness, and because we continue to support the postprocessing DWARF approach.

Fix 15: I Momentarily Wonder If I Broke DTrace
37700283 libctf dynamic containers can corrupt memory if parent is present

37820204 Dtrace scripts fail to compile in st_195 with - cannot find type: struct scsi_address*

[ High Level Goal ]

Bug 37700283 came in weeks after the CTF version 3 work was delivered. The error looked like this:

# dtrace -n 'fbt::kmem_alloc:entry{exit(1)}'
dtrace: invalid probe specifier fbt::kmem_alloc:entry{exit(1)}:
    "/usr/lib/dtrace/etrace.d", line 66: cannot find type: struct
    cku_private_s*: File data structure corruption detected

Examining the libctf debug log showed clear signs of memory corruption, and given the switch to CTF v3 in the DTrace-only container code, the fingers were pointing straight at my change. The problem was that I was unable to reproduce it on my own system, but eventually it emerged that this only happened when running IDR bits. IDR builds differ from normal ones in that kernel modules are uniquified against a reference base version of genunix. This exercises some different code paths in libctf to manage duplicate type definitions, where older parent genunix has a definition that has been superseded, and which is therefore also found in the child CTF. In addition, CTF version 3 is a bit larger than version 2, so these definitions can use a bit more memory. All of these differences could be part of the root cause for the observed difficulties.

It emerged that the new kernel module CTF had a structure definition with 47 fields, overriding the same type in the genunix that had 40, and when producing the container's copy, the smaller size was being used, while all the new data was being copied in. This is a simple buffer overrun that predates the version 3 conversion. The difference version 3 brings is that the copied data is larger, and smashes deeper. In this case, it was deep enough to have a visible effect.

The second bug (37820204) was essentially the same issue, but in a different area of the same code.

These bugs were fixed, and lesson learned, I started including IDR builds in my testing to guard against being surprised by others.

Fix 16: Translate Input GNU CTF To Native Solaris CTF
PSARC/2025/036 Translate GNU CTF from gcc to Solaris CTF
37776953 ctfgen should translate GNU CTF to native Solaris CTF
37577882 ctfconvert: failed to get mapping for tid 355

[ High Level Goal ]

With CTF version 3 delivered, the next big project was to take on GNU CTF, translating the variant of CTF produced by the gcc compilers (GNU CTF) to Solaris CTF. In so doing, the need to run ctfconvert is eliminated, and CTF produced by gcc can be sent directly to ctfmerge, or to 'ld -zctf'. This enables us to add CTF to a wide range of software built with gcc which was previously out of reach, improving the ability of tools like mdb and dtrace to analyze such code.

GNU CTF differs significantly from Solaris CTF in its container concept and its version of libctf, and none of that is a good fit for Solaris. However, as already noted, the CTF sections gcc produces in relocatable objects are very close to CTF version 3, and translating it to native Solaris version 3 is relatively straightforward.

The following work was done:

Prior to adding the support for translating GNU CTF, when doing Userland builds, some components built with gcc would report an error like the following:

ld: fatal: CTF: failed to get mapping for tid 355
These "failed to get mapping" issues arise when a CTF_K_UNKNOWN type is encountered within a type section during a merge process. The native Solaris ctfconvert utility does not produce this condition, and our merge code did not have a category for it, leading to this error. However, GNU CTF can produce this state, and part of the fix to support GNU CTF directly was to add an "unknown" category to the CTF merge code to accommodate it.
Fix 17: file Utility Reports CTF and Debug Independently
PSARC/2025/044 file utility should identify the presence of CTF
37674598 file command could provide debug type

[ High Level Goal ]

As noted earlier, with increased use of CTF, we found it useful to enhance the file utility to report the presence of general debug data, and CTF, as separate information:

% file /lib/64/libc.so
/lib/64/libc.so: ELF 64-bit LSB dynamic lib AMD64 Version 1 [SSE2 SSE],
    dynamically linked, not stripped, no debugging information available,
    CTF present
Fix 18: Strip .SUNW_ctf With .symtab
37898560 strip: removing symtab should also take CTF

[ High Level Goal ]

A simple bug fix. When the strip utility removes the non-dynamic symbol table, .symtab, it is supposed to also remove any other sections that rely on it, since those sections would otherwise be unusable. In the case of .SUNW_ctf, this was not happening, a problem that predated this project.

Fix 19: O(N^2) check_for_weak() Is Severe Bottleneck
37911743 ctfmerge: O(N^2) check_for_weak() is severe bottleneck

[ High Level Goal ]

The second half of the fix for the abysmal O(n^2) performance of check_for_weak() in the merge code. This fix employs an AVL tree, created only when needed, to turn the cost into O(n) + O(logN), greatly lowering the impact.

Fix 20: Pervasively Apply CTF Generation To Userland
37577916 Userland should use LD_xxx_OPTIONS to add CTF to delivered components

[ High Level Goal ]

I returned to my Userland workspace, and modified it to make use of the new ability to use GNU CTF directly from gcc -gctf without the need for a CTF convert operation. The result was greatly improved over what I had been able to do earlier, and I integrated it. A large chunk of Userland is now delivered with Solaris containing CTF from normal builds. After the better part of 2 years, this result was anticlimactic in the best possible way.

CTF remains best used with C, and languages at the same language level as C, and we disabled it for lots of C++ components, either due to issues encountered, or an awareness that it wasn't useful enough to bother with. C++ remains an open issue for CTF generally, but not necessarily due to limitations in our implementation of it.

Fix 21: Using cc -g To Enable CTF Can Alter The Code In Unwanted Ways
38192852 <subject redacted>

[ High Level Goal ]

This bug arrived after the change to deliver CTF with Userland integrated. This is not a CTF bug per se, but one that was caused by our attempt to deliver a package from Userland that was built with the Studio compilers, and which points at a limitation of the traditional approach of using the compiler -g option to generate DWARF, which is then converted to CTF.

In this case, the application of -g changed the code being generated, changes that did not affect our ability to build it, but which did cause problems later for other code that wanted to use it. It seems that when -g is used, Studio retains unused code that would otherwise be optimized away. In particular, static inline functions from headers end up being kept. This is a reasonable behavior for the compiler, or at least, one that it is allowed, but one that turned out to be inconvenient when using -g reasons other than what the compiler authors may have had in mind. The lesson here is not that the compiler was wrong, but rather, that when the compiler supplies the CTF directly, pitfalls like this are far less likely. In this case, we temporarily disabled the CTF generation for these objects, and ultimately, switched them over to use gcc, with the -gctf option for CTF.

Fix 22: Adopt GNU CTF_K_SLICE And Solve The Enum Bitfield Problem
PSARC/2025/090 Solaris CTF should adopt GNU CTF_K_SLICE
38226071 add support for CTF_K_SLICE to Solaris CTF implementation
35489361 mdb reports the base types for bitfields
35478862 mdb does not display enum bitfields correctly

[ High Level Goal ]

As the dust settled on the work to support GNU CTF, and deliver CTF with userland, we were in a position to look at lingering issues in the CTF space. The largest such issue was in our handing of C "bit enums", bitfields in structs or unions applied to an enum type, rather than integers. The native Solaris CTF was unable to represent an enum value that is not stored in a 32-bit field. This was seen in 2 different ways:

Neither of these were a satisfactory answer. The ctfconvert behavior was simply wrong, and I knew would eventually need attention. On the gcc -gctf side of things, using CTF_K_INTEGER was an expedient way to get the GNU CTF work in place, without having to take on the the added complexity of the SLICE concept, which has implications for the libctf APIs, and therefore widens the blast radius to include libctf consumers (mdb and DTrace).

The GNU CTF_K_SLICE kind is not particularly lovely, and it implies a kind of generality that I think isn't real, suggesting that it can modify any number of kinds, while really only making sense with CTF_K_ENUM. In discussing it with Nick Alcock, I found that he also was a bit ambivalent about it. So I decided to try and come up with a better answer. If I could do something clean and simple that was specific to ENUM, I thought that might be a better way to go. In such a case, ctfconvert could use that new thing, and the GNU support could translate SLICE to it.

I did several prototypes of different ideas for this, including 2 that I took to the stage of working code. I won't detail those efforts in this already way too long document, but all required the introduction of a new CTF kind of some sort, one of which was a variant of CTF_K_ENUM that had an encoding. In each case, I found that they didn't make things simple, particularly for code that calls libctf, which would need modification. libctf is a private library, and its interfaces can change, but we have a substantial code base in mdb and DTrace sitting on top of it, and API changes have large ripple effects.

These alternative designs and prototypes were valuable, as they convinced me that in comparison, SLICE was no worse, and in many ways, easier to handle. The value of embracing an existing solution widely used elsewhere, rather than going down the "not invented here" path, became obvious. I made a final decision, and leaned into adding support for CTF_K_SLICE to native Solaris CTF as a standard feature, equally applicable to versions 2 and 3. The CTF convert code was modified to issue SLICE when bitfield enums are seen, and the GNU CTF conversion code was simplified to remove the code implementing the CTF_K_INTEGER workaround, and simply passed through as part of Solaris CTF.

The changes to libctfgen and libctf were straightforward, but it was still necessary to change how the ctflib APIs operate when SLICE is present, and would require changes to mdb (fairly widespread) and DTrace (fairly small).

Working this out involved a lot of experimentation in modifying mdb, to use various experiments with libctf, and and observing whether the changes made things harder or easier. Ultimately, I was able to limit the necessary changes to callers of ctf_type_encoding(), which is mainly mdb and libctf, with only minor use elsewhere, and did not have to add or remove any APIs, or add or remove any arguments. The main work involved locating calls to ctf_type_encoding() with cscope, and then applying the advice summarized in Adapting libctf Consumers For CTF_K_SLICE.

This fix added the following definitions to <sys/ctf.h>:

#define CTF_K_SLICE     14      /* variant data is single ctf_slice_t  */

typedef struct ctf_slice_v2 {
        uint16_t cts_type;      /* type ID of type being sliced */
        uint16_t cts_offset;    /* offset of integral type in bits */
        uint16_t cts_bits;      /* bit-width of this integral type */
        uint16_t cts_pad;       /* padding */
} ctf_slice_v2_t;

typedef struct ctf_slice_v3 {
        uint32_t cts_type;
        uint16_t cts_offset;
        uint16_t cts_bits;
} ctf_slice_v3_t;

SLICE operates as a modifier, in the same manner as TYPEDEF, VOLATILE, CONST, and RESTRICT. cts_type is a reference to another CTF type, while cts_offset and cts_bits provide an overriding offset and size.

Fix 23: A Problem With Converting SLICE Into INT Solved By SLICE
38242531 warning: CTF: unexpected unnamed INTRINSIC(2) tdesc

[ High Level Goal ]

As mentioned previously, the initial integration of support for GNU CTF did not support their CTF_K_SLICE, and instead mapped them to CTF_K_INTEGER. In doing so, we sacrificed the mnemonic names provided by the enum, one of the reasons for then going back and adding SLICE support properly.

An unintended consequence of that mapping was this bug that came in during the window between the original GNU work, and the later addition of SLICE. Quoting from my analysis of the bug:

The problem is in the process of turning the GNU CTF_K_SLICE kind, which is not supported, into something that can be represented in Solaris CTF. The basic idea is that we create a narrowed CTF_K_INTEGER of the desired width, and give it the name of the item SLICE points at. This mimics the way integer bitfields have long been supported with CTF produced from objects built with the Studio compilers.

The problem, in this particular case, is that the underlying type we turn into a narrowed INTEGER is an anonymous (no name) ENUM. As such, the INTEGER we create also has no name. This falls afoul of a different part of the CTF merge process, in which an "INTRINSIC" (an INTEGER, or FLOAT) with no name is detected. That's not supposed to happen, so the warning is appropriate, albeit inconvenient for this particular case.

I integrated a temporary fix for this, but happily it was all swept away when the support for SLICE replaced it. I mention it here only for the obvious moral, which is that simple expedient hacks to large and complex systems sometimes have surprising side effects.
Fix 24: Enable Use of CTF_K_SLICE
38226138 enable use of CTF_K_SLICE to represent bitfields

[ High Level Goal ]

As with the earlier fix that switched .SUNW_ctf sections to use the SHT_SUNW_ctf section type, it was necessary to seed support for CTF_K_SLICE and wait for the build servers to catch up before enabling it.

Fix 25: C++ Can Push Limits Too Hard For CTF
38554022 ctfmerge runs out of stack

[ High Level Goal ]

This is an interesting case that combines a small bug with an algorithmic problem, and one that is not fully resolved. After I integrated the support for generating CTF for the Userland consolidation, it was reported that a new version of ghostscript was causing the CTF merge process to exhaust the stack after running for some time. When building without CTF, the link, though large, finished quickly and successfully.

The stack trace was full of odd repeating cycles, which we were initially unable to explain, but which were eventually revealed to be caused by variables defined as 'int', used by the traverse process, overflowing and wrapping around. Defining those things as 'size_t' cured the cycles, but the resulting link took an inordinate amount of time.

My colleague Vladimir Marek got to the bottom of this second mystery, saying:

    > Do you know how the comparison of two 'types' works in
    > ctfmerge? First the structure is stored into hash. The
    > hash has 8191 buckets. The types which fall into the same
    > bucket are arranged into a list specific to the given bucket.
    >
    > Problem is that all the anon pointers and anon functions fall
    > into the same bucket
    >
    > My test showed that the list in single bucket had more than
    > 40000 'types' in the list
The CTF merge process uses hash tables to provide rapid lookups of types by name. This doesn't help when dealing with unnamed types, as is the case with this particular component.

This is a general issue for CTF merge, and worthy of thought going forward. However, for reasons already touched on, the value of CTF for C++ is not high, and C typically doesn't have this issue, or at least not at this scale. A fix for for the overflowing integers was integrated. For now, we simply disabled CTF for ghostscipt, and the issue of how ctfmerge should deal with large numbers of anonymous types is left as a problem for another day.

Fix 26: Improve ctfdump Output
38673244 ctfdump output could be more polished

[ High Level Goal ]

As noted in the high level comments, sustained use of ctfdump caused me to be come intimately familiar with ctfdump output, and gave me a desire to streamline and organize it a bit more than had been the case. The result is not too different, and a casual user might not notice, but takes some inspiration from elfdump in terms of layout and labeling to make the output easier to read and understand.

Fix 27: Full Support for .SUNW_ldynsym Symbol Table
PSARC/2026/015 Enable CTF to use the .SUNW_ldynsym symbol table
38845627 libctf should support SUNW_ldynsym symbol table
38870260 mdb should support SUNW_ldynsym symbol table 
38912882 Solaris CTF should include type information for SUNW_ldynsym symbols

[ High Level Goal ]

As the CTF project wound down, the knowledge gained about CTF and mdb internals allowed me to finish work to fully make use of the .SUNW_ldynsym symbol tables that were added to Solaris in 2007.

Fix 28: Detect And Reject Unmerged CTF
39048226 unmerged CTF should be detected and diagnosed

[ High Level Goal ]

When using the postprocessing approach to adding CTF to objects, it is possible to apply ctfconvert to the input objects, but forget to run ctfmerge on the resulting output object. Such objects appear to have CTF, but it is incomplete and invalid. Historically, libctf, or ctfdump, did not detect this case, and would blindly try to interpret the bad CTF. ctfdump in particular would produce plausible looking, but incorrect, output.

Over the years, I have thought about this whenever I came across makefile rules to add CTF. Our make rules were correct, and so this was never more than a theoretical observation. "Doesn't Happen" can be an acceptable answer for an internal tool, but is not good enough for one that is public and supported. This fix adds missing checks to detect and reject unmerged CTF.

Fix 29: CTF Fully Supports ELF Section Compression
39147002 krtld should be hardened against SHF_COMPRESSED ELF sections
39147030 CTF generation tools should support SHF_COMPRESSED ELF sections

[ High Level Goal ]

As mentioned above, CTF predates the introduction of ELF Section Compression, and as such, has its own built in compression mechanism. For historical reasons, the CTF version is still generally used today for .SUNW_ctf sections, rather than the standard ELF form. However, other sections (symbol tables, etc), may be compressed using the ELF format, and the ELF form can even be applied to .SUNW_ctf, with some effort. As such, our CTF code needs to handle ELF section compression.

The support for ELF section compression in our CTF code was incomplete. I've addressed that with this fix. The issue of using it for .SUNW_ctf sections is left to another day, as I mention in What's Left?.

Fix 30: CTF Supports Ancillary Objects
39693462 ctfmerge and ctfdump should support ancillary objects
39665830 ctfconvert may mishandle gelf_update_shdr() return code
39685356 mcs/strip/elfcompress can botch e_shstrtab from extended ELF header
39692966 elfdump misdiagnoses symbol table with ABSENT string table
39693296 private libelf _elf_zdata() incompatible with GElf APIs
39693395 libelf can set sh_offset/sh_addralign incorrectly for ABSENT sections
39717393 ancillary object checksums are not always unique as required
39739198 ctfconvert can deference NULL pointer for missing type id

[ High Level Goal ]

The primary fix in this cluster of changes is to properly support ancillary objects from the libctfgen code. The other bugs are things that were discovered while testing that work.

Support for ELF Ancillary objects was added to Solaris in Solaris 11 Update 1:

PSARC/2012/064 ELF Ancillary Objects (Separate Debug Files)
15487884 SUNBT6715267 ld: support separate debug file

Support for ancillary objects was not added to our CTF utilities at that time. This was primarily because the OS itself does not use ancillary objects, and as CTF was an OS-only mechanism, such support was not needed. However, complexity of the CTF code, and a lack of familiarity, were also reasons to defer that work. Later, in 2013, minimal support was added to ctfmerge to support the case of a single ancillary object, intended to work around a specific issue with the Studio Performance Analyzer, and in no way really qualifying as "support". And then, that work was obsoleted by more recent work that allows more than one ancillary to be produced:

PSARC/2022/009 Support for Multiple ELF Ancillary Objects
33756071 link-editor should support multiple ancillary objects

The recently added support to the link-editor (ld -zctf) does support adding CTF to ancillary objects, and libctf does support them, so that gave us some minimal support. However, without the ability to look at it with ctfdump, and with support missing from the ctfmerge utility, this support was badly incomplete. As was the case with ELF section compression, my recent work on CTF put me in a good position to finish off this incomplete corner of support. With this fix:

ctfconvert
The primary work for ctfconvert consisted of checking the input objects and disallowing ancillary objects, or objects with associated ancillary objects. This is done for 2 reasons:

  1. It's not useful: The input to ctfconvert are relocatable objects created by compilers, and relocatable objects do not use ancillary objects. This is partly because the compilers don't support ancillary objects, which are are Solaris-only feature. More importantly though, it's because ancillary objects mainly only benefit executables and shared objects, where there might be reasons to separate code from debug information, and deliver them separately, or where sizes become very large.

  2. CTF conversion relies on other libraries, notably libdwarf, that do not support ancillary objects.

ctfmerge
ctfmerge is updated to fully support output objects with associated ancillary objects. All ancillary objects must be present in the same directory as the specified primary object. If the objects have an existing .SUNW_ctf section, those sections that do not have the SHF_SUNW_ABSENT section flag set are rewritten with the newly merged CTF data. If the objects do not have an existing .SUNW_ctf section, one is added. In this case, the section in the primary object receives the merged data, and the sections in the ancillary objects are all marked as SHF_SUNW_ABSENT.

ctfdump
Objects that have associated ancillary objects can be inspected with the ctfdump utility. If the object has associated ancillary objects, and those ancillary objects are available in the same directory as the primary object, ctfdump will transparently read those ancillary objects to obtain any sections absent from the primary object which are needed to display the CTF. Support for reading ancillary objects is limited to plain objects. The ctfdump utility will not access ancillary objects for objects found in an archive.
Fix 31: Correct The Placement of .SUNW_ctf Section Data In Ancillary Objects
PSARC/2026/069 Revised placement of .SUNW_ctf section data in ELF ancillary objects
39760475 ld -zancillary -zstrip-class=ctf should work like -zstrip-class=symbol

[ High Level Goal ]

As I worked on the CTF support for ancillary objects, I noticed that when the ld -zctf option was used in conjunction with -zancillary, that the CTF data was written only to the ancillary object, and was marked as ABSENT in the primary object. This is generally how ancillary objects are intended to work, but it is the wrong default behavior for CTF. CTF is designed to be compact enough to always include in the primary object, and there is a large benefit to always having it available for use by libproc, mdb, and DTrace without also requiring the ancillary objects to be present. The nature of in the field debugging is that one rarely has the option to install additional software first. The tools have to work with what is already present.

There are two broad categories of non-allocable section produced by the link-editor:

  1. Those primarily consumed by debuggers such as dbx and gdb. These sections are well served by being placed only in an ancillary object, and serving this case is the main reason ancillary objects were invented.

  2. Sections that are used by the OS itself, particularly those used by libproc, mdb, and DTrace. The primary example of this is the non-dynamic symbol table, .symtab, and its associated sections (string table, sort, etc). Such sections are best written to the primary object, since they are examined by core OS utilities, and because they are generally small enough not to be a problem.

Category (2) sections were anticipated in the initial implementation of ancillary objects (PSARC/2012/064), and these sections are given special treatment, as documented by the ld(1) manpage for the ld -zstrip-class option :

Stripped sections are completely removed from the output object. The use of the -z ancillary option alters this behavior with regard to the non-dynamic symbol table .symtab, and the sections related to it. By default, the symbol table is written to both the primary and ancillary objects. If stripped, the symbol table is written to the ancillary object only, and is identified as absent in the pri- mary object. If .symtab is stripped from an object without the use of -z ancillary, the section is completely removed in the usual manner.
CTF sections should be treated in the same way, and doing so requires only that the right flags are set for the .SUNW_ctf output section descriptor. The fact that this was not originally done when the -zctf option was introduced (PSARC/2024/139) was a simple oversight, which this fix corrects.
Fix 32: User Control Of CTF Compression
PSARC/2026/071 Controlling CTF compression
39806653 ctfconvert and ctfmerge should give user control over compression
39806766 ld and elfcompress lack a ctf compress class

[ High Level Goal ]

A previous fix added basic support for standard ELF compression, in CTF, as well as for other sections. This fix builds on that work by adding a new -z option to ctfconvert and ctfmerge that allows the user to control whether compression is done, and whether it is the original CTF style, or ELF. In addition, it adds an ELF 'ctf' compression class to the ELF compression support provided by ld -zcompress-class, and elfcompress.

What's Left To Do?

Over the last 2 years, we've accomplished most of the goals we had for CTF, and then some. The experience gained from working on that code also opened the door to finishing off related work in other areas, notably ELF compression and ancillary objects. Today, the TODO list is short. However, software is never done, and I do have some thoughts about future work that might eventually be worth thinking about.

Support Newer gcc DWARF Versions
The gcc specific code in ctfconvert for converting DWARF to CTF is limited to gcc dwarf version 2. Meanwhile, gcc has moved on to version 5, and it is reasonable to assume that there will be additional versions in the future. The new support for GNU CTF (gcc -gctf) blunts the urgency of this, but it might still make sense to backfill that missing support, if only for the sake of completeness.

Use Standard ELF Section Compression
As noted previously, the CTF code now supports both the original style of CTF compression, as well as ELF compression. This raises the question of whether there should be a shift from the original style, to the more general ELF style. CTF predates ELF Section Compression by many years, and as such, has its own internal support for compression, based on the industry standard Zlib. There are obvious benefits that come with leveraging standard system facilities for features like this, rather than doing your own. Had ELF section compression existed when CTF was invented, it would have been used, and CTF would not include its own support for it. The resulting CTF format would be a bit simpler as a result, while offering the same functionality. A benefit of this is that any improvements to ELF compression would be immediately available. One example of this is the relatively recent addition of ZSTD compression in the ELF format.

CTF could still shift to using ELF section compression rather than its own, and perhaps we will eventually do that. However, the old CTF compression support would have to remain to provide backward compatibility for existing objects, so there can be no net simplification from doing so. It may still be worth it, for the conceptual simplicity, and to gain access to Zstd compression. But these are small concerns: ZLIB does an excellent job, and the resulting CTF is small enough that further reduction is not an urgent priority.

Before that can be done, a prerequisite would be to add support for ELF section compression into the kernel runtime linker (krtld). Currently, the kernel only supports the original CTF style of compression.

Future Versions of GNU CTF
A substantial update to the GNU CTF produced by gcc is under development upstream, and will eventually be released. When that happens, it will be necessary to add support for it, as well as retaining support for the older version. From my discussions with Nick, I expect this to involve some new work, but do not expect any show stopping barriers.

CTF Version 2 and 3 Differences

This section provides a detailed list of the format differences between CTF version 2 and version 3.

The primary difference between the versions is that in version 3, the integers representing types, and some metadata, are widened from 16-bit to 32-bit.

Many CTF data structures are unchanged between versions 2 and 3. Where the structures differ, fields and their purposes remain unchanged, and the differences are all due to the widening of integer types.

The differences between Solaris CTF versions 2 and 3 are:

GNU CTF Differences

Prior to the introduction of support for CTF version 3, the gap between GNU CTF and Solaris CTF was considerably wider than it is today. With the version 3 differences out of the way, what's left is considerably easier to deal with. As noted earlier, the best available reference for GNU CTF is The CTF File Format by Nick Alcock.

The following is a summary of the differences between Solaris version 3 CTF and GNU CTF.

Adapting libctf Consumers For CTF_K_SLICE

This section provides notes on the Solaris implementation of ctf_type_resolve() and ctf_type_encoding(), and their interaction with each other.

A typical scenario for a CTF consumer is to determine the data type of a given variable, and then, its encoding. The encoding specifies type details, offset, and width (bits), and is part of the INTEGER and FLOAT types. In addition, the SLICE type can be used to add or modify the offset/bits details. SLICE is used to describe INTEGER or ENUM bitfields in struct or union members, adapting the base encoding to fit the bitfield.

Consider the example of a bitfield integer in a structure. The CTF for that field it might look like:

SLICE -> CONST -> VOLATILE -> TYPEDEF -> INTEGER

There can be an arbitrary number of attribute type IDs before the underlying base type. In this example, SLICE, CONST, VOLATILE, and TYPEDEF are all attributes of a base INTEGER type. ctf_type_resolve() and ctf_type_encoding() are used to manage such type chains:

ctf_type_resolve()
Given the type ID for the starting type, ctf_type_resolve() returns the type ID of the "base" underlying type, which in this case is INTEGER.

ctf_type_encoding()
Given the type ID for the starting type, ctf_type_encoding() returns the encoding for the underlying base type, incorporating the information from SLICE if a SLICE is present.

It should be noted that in both cases, the same starting ID is passed. Both functions employ the same resolution algorithm for moving through the type chain to locate the information they return. Historically, this was not the case. Prior to the introduction of SLICE, standard practice was to resolve the base type, and then call ctf_type_encoding() on that base type to extract the encoding. This worked reliably because the encoding was always found in the base type. The introduction of SLICE changes this dynamic: Calling ctf_type_encoding() on the resolved type, rather than the start type, will provide an encoding that ignores any SLICE that might be present. In this case, the encoding will be too large, and accessing that memory will yield bad results.

Code written before the introduction of SLICE may need to be adapted in order to operate properly when SLICE is present. Existing code that does something like this:

if (((rid = ctf_type_resolve(fp, id)) != CTF_ERR) &&                     
    (ctf_type_encoding(fp, rid, &e) == 0))

should be modified to pass 'id' to ctf_type_encoding(), rather than 'rid':

if ((rid = ctf_type_resolve(fp, id) != CTF_ERR) &&                       
    (ctf_type_encoding(fp, id, &e) == 0)) 

If rid is not needed elsewhere, the call to ctf_type_resolve() can be entirely dropped, leaving simply:

if (ctf_type_encoding(fp, id, &e) == 0)

That said, there are some situations in which one can safely call ctf_type_encoding() on the resolved type, rather than the start type:

Documentation And Resources

Current manpages for the utilities affected by this project, as they existed when I wrote this blog, are available:

Text (html)
ctfconvert(1), ctfdump(1), ctfmerge(1), elfcompress(1), ld(1), strip(1), ctf(5), ctf(7).
Text (plain)
ctfconvert(1), ctfdump(1), ctfmerge(1), elfcompress(1), ld(1), strip(1), ctf(5), ctf(7).
PDF
ctfconvert(1), ctfdump(1), ctfmerge(1), elfcompress(1), ld(1), strip(1), ctf(5), ctf(7).
roff
ctfconvert(1), ctfdump(1), ctfmerge(1), elfcompress(1), ld(1), strip(1), ctf(5), ctf(7).

FreeBSD ctf(5) manpages covering CTF version 2 and 3.

Version 2
ctf(5)
Version 3
ctf(5)

The CTF File Format by Nick Alcock

The current Solaris <sys/ctf.h> header, providing definitions for CTF versions 1 - 3.

Acknowledgements

Throughout this work, but especially at the start, I was overwhelmed by the layers of code, history, and the critical importance of not breaking our core observability tools. We were making committed changes to a living system that is regularly released to end users, and mistakes are not easy to take back. It really had to work on the first try, and this was all uncharted territory. None of what was achieved would have been possible without the following people and their willingness to pour their own time and efforts into the effort.

Robert David was responsible for CTF and DTrace on Solaris in the years before I took on this work. His response to my proposal to walk into his area without really knowing what I was doing and do a massive restructuring of it in pursuit of something that might or might not work out, was to welcome me in and hold my hand throughout the first year as I got going. This included all of the churn to the libctfgen code base to make it library ready, and multiple in depth code reviews for all of it.

Chris Gerhard, our mdb expert, was my constant companion in this work. Chris made me aware of CTF issues I had formerly not known about, and helped me work my way through the massive mdb code base, with its many layers of abstraction. He did that repeatedly as I moved through each stage of the project. Chris acted as a sounding board for all the various attempted schemes, helped me debug and crack issues I was stuck on, helped modify the vital mdb unit tests to keep up, and took on considerable follow on work to take advantage and/or support it. He also code reviewed nearly every fix. He did this for each phase, over the entire 2 year span.

Jan Zaloha took over the CTF/DTrace space from Robert about a year into things, but his evident depth of knowledge betrays a deep preexisting familiarity, and he jumped right into the deep end. Jan has also helped me substantially, as a source of advice, and for code reviews. In addition, he was the front line for any bugs I created (notably 37700283). Fortunately, there was not a lot of this, but when there was, Jan never pointed the finger of blame, and instead was unfailingly helpful, going above and beyond to help diagnose the issues and set me up to fix them.

Alan Coopersmith, Darren Moffat, and the engineers and leadership of the Solaris OS were unfailing sources of support for this work, willing to take the necessary calculated risks to move us forward, and to be ongoing sounding boards for ideas, even the harebrained ones. Their deep knowledge of Solaris history and implementation were essential.

Vladimir Marek, our Userland Gatekeeper (which deeply undersells the scope of what he does to keep the Userland trains running). Though I worked to minimize the impact, my changes to add CTF to Userland still created some churn. Vlad was unfailingly cheerful and encouraging in the face of it, taking the breaks in stride, and some cases even doing a root cause analysis to identify the problem in the CTF tools.

Rainer Orth, for his enthusiasm and support for this project (and so many others), and for rushing to provide gcc -sctf as soon as ld -zctf had stabilized.

FreeBSD, your CTF version 3 was the right thing, and you did it right.

Nick Alcock, and his colleagues in the Oracle Linux Tools Group, the developers of the GNU CTF work, and the gcc -ctf feature we leveraged. Thank you for your help, and for the bug fixes. My discovery of Nick's spec for GNU CTF was a key turning point in this project. Once I made contact, he was unfailingly generous in discussing the hows and whys of what they had done, and in helping me understand how I might make use of it. I asked way too many questions, and Nick took the time to answer them all. Thank you for doing this good work, and thank you for doing it as you did. You were solving your own problems, but in preserving the essential shape and spirit of the original CTF, you made it possible for others with unrelated problems of their own to benefit as well. I'm sure you didn't set out to kick Solaris CTF into wider use for open source, but as the saying goes, a rising tide floats all boats.

Thank You.

Surfing with the Linker-Aliens

[38] kldd: ldd Style Analysis For Solaris Kernel Modules
Blog Index (ali)
[1] Testing...Without...System Into A Brick