Nick Alcock [Thu, 26 Jun 2025 14:47:25 +0000 (15:47 +0100)]
libctf: create: check the right root-visible flag when adding enumerands
The root-visible flag we're dealing with here is directly out of the dict,
not a flag passed in to the API, so it does not have the values CTF_ADD_ROOT
or CTF_ADD_NONROOT: instead it's simply zero for non-root-visible, nonzero
otherwise. Fix the test.
Nick Alcock [Thu, 26 Jun 2025 14:45:31 +0000 (15:45 +0100)]
libctf: create: addition of non-root types should not return root types
If you add a non-root type to a dict, you should always get a new, unique
type ID back, even if a root-visible type with the same name already exists.
Unfortunately, if the root-visible type is a forward, and you're adding a
non-root-visible struct, union, or enum, the machinery to detect forwards
and promote them to the concrete type fires in this case and returns the
root-visible type! If this is an enum being inserted hidden because its
enumerands conflict with some other enum, this will lead to failure later
on: in any case, it's seriously counterintuitive to add a non-root- visible
type and get a root-visible one instead.
Fix this by checking the root-visible flag properly and only checking for
forwards if this type is root-visible. (This may lead to a certain degree
of proliferation of non-root-visible forwards: we can add a cleanup pass for
those later if needed.)
libctf/
* ctf-create.c (ctf_add_sou_sized): Check the root-visible flag when
doing forward promotion.
(ctf_add_enum_internal): Likewise.
(ctf_add_enum_encoded_internal): Likewise.
Nick Alcock [Tue, 3 Jun 2025 12:39:33 +0000 (13:39 +0100)]
libctf: use __attribute__((__gnu_printf__)) where appropriate
We don't use any GNU-specific printf args, but this prevents warnings about
%z, observed on MinGW even though every libc anyone is likely to use there
supports %z perfectly well, and we're not stopping using it just because
MinGW complains. Doing this means we stand more chance of seeing *actual*
problems on such platforms without them being drowned in noise.
We turn this off on clang, which doesn't support __gnu_printf__.
Suggested by Eli Zaretskii.
libctf/
PR libctf/31863
* ctf-impl.h (_libctf_printflike_): Use __gnu_printf__.
Nick Alcock [Tue, 3 Jun 2025 11:01:45 +0000 (12:01 +0100)]
libctf, dedup: reclaim space wasted by duplicate hidden types
In normal deduplicating links, we insert every type (identified by its
unique hash) precisely once. But conflicting types appear in multiple
dicts, so for those, we loop, inserting them into every target dict
in turn (each corresponding to an input dict that type appears in).
But in cu-mapped links, some of those dicts may have been merged into
one: now that we are hiding duplicate conflicting types more
aggressively in such links, we are getting duplicate identical hidden
types turning up in large numbers.
Fix this by eliminating them in cu-mapping phase 1 (the phase in which this
merging takes place), by checking to see if a type with this hash has
already been inserted in this dict and skipping it if so. This is
redundant and a waste of time in other cu-mapping phases and in normal
links, but in cu-mapped links it saves a few tens to hundreds of kilobytes
in kernel-sized links.
libctf/
PR libctf/33047
* ctf-dedup.c (ctf_dedup_emit_type): Check for already-emitted
types in cu-mapping phase 1.
Nick Alcock [Fri, 30 May 2025 21:12:37 +0000 (22:12 +0100)]
libctf: dedup: preserve non-root flag across normal links
The previous commits dropped preservation of the non-root flag in ctf_link
and arranged to use it somewhat differently to track conflicting types in
cu-mapped CUs when doing cu-mapped links. This was necessary to prevent
entirely spuriously hidden types from appearing on the output of such links.
Bring it (and the test for it) back. The problem with the previous design
was that it implicitly assumed that the non-root flag it saw on the input
was always meant to be preserved (when in the final phase of cu-mapped links
it merely means that conflicting types were found in intermediate links),
and also that it could figure out what the non-root flag on the input was by
sucking in the non-root flag of the input type corresponding to an output in
the output mapping (which maps type hashes to a corresponding type on some
input).
This method of getting properties of the input type *does* work *if* that
property was one of those hashed by the ctf_dedup_hash_type process. In
that case, every type with a given hash will have the same value for all
hashed-in properties, so it doesn't matter which one is consulted (the
output mapping points at an arbitrary one of those input types). But the
non-root flag is explicitly *not* hashed in: as a comment in
ctf_dedup_rhash_type notes, being non-root is not a property of a type, and
two types (one non-root, one not) can perfectly well be the same type even
though one is visible and one isn't. So just copying the non-root flag from
the output mapping's idea of the input type will copy in a value that is not
stabilized by the hash, so is more-or-less random!
So we cannot do that. We have to do something else, which means we have to
decide what to do if two identical types with different nonroot flag values
pop up. The most sensible thing to do is probably to say that if all
instances of a type are non-root-visible, the linked output should also be
non-root-visible: any root-visible types in that set, and the output type is
root-visible again.
We implement this with a new cd_nonroot_consistency dynhash, which maps type
hashes to the value 0 ("all instances root-visible"), 1 ("all instances
non-root-visible") or 2 ("inconsistent"). After hashing is over, we save a
bit of memory by deleting everything from this hashtab that doesn't have a
value of 1 ("non-root-visible"), then use this to decide whether to emit any
given type as non-root-visible or not.
However... that's not quite enough. In cu-mapped links, we want to
disregard this whole thing because we just hide everything -- but in phase
2, when we take the smushed-together CUs resulting from phase 1 and
deduplicate them against each other, we want to do what the previous commits
implemented and ignore the non-root flag entirely, instead falling back to
preventing clashes by hiding anything that would be considered conflicting.
We extend the existing cu_mapped parameter to various bits of ctf_dedup so
that it is now tristate: 0 means a normal link, 1 means the smush-it-
together phase of cu-mapped links, and 2 means the final phase of cu-mapped
links. We do the hide-conflicting stuff only in phase 2, meaning that
normal links by GNU ld can always respect the value of the nonroot flag put
on types in the input.
(One extra thing added as part of this: you can now efficiently delete the
last value returned by ctf_dynhash_next() by calling
ctf_dynhash_next_remove.)
We bring back the ctf-nonroot-linking test with one tweak: linking now works
on mingw as long as you're using the ucrt libc, so re-enable it for better
test coverage on that platform.
libctf/
PR libctf/33047
* ctf-hash.c (ctf_dynhash_next_remove): New.
* ctf-impl.h (struct ctf_dedup) [cd_nonroot_consistency]: New.
* ctf-link.c (ctf_link_deduplicating): Differentiate between
cu-mapped and non-cu-mapped links, even in the final phase.
* ctf-dedup.c (ctf_dedup_hash_type): Callback prototype addition.
Get the non-root flag and pass it down.
(ctf_dedup_rhash_type): Callback prototype addition. Document
restrictions on use of the nonroot flag.
(ctf_dedup_populate_mappings): Populate cd_nonroot_consistency.
(ctf_dedup_hash_type_fini): New function: delete now-unnecessary
values from cd_nonroot_consistency.
(ctf_dedup_init): Initialize it.
(ctf_dedup_fini): Destroy it.
(ctf_dedup): cu_mapping is now cu_mapping_phase. Call
ctf_dedup_hash_type_fini.
(ctf_dedup_emit_type): Use cu_mapping_phase and
cd_nonroot_consistency to propagate the non-root flag into outputs
for normal links, and to do name-based conflict checking only for
phase 2 of cu-mapped links.
(ctf_dedup_emit): cu_mapping is now cu_mapping_phase. Adjust
assertion accordingly.
* testsuite/libctf-writable/ctf-nonroot-linking.c: Bring back.
* testsuite/libctf-writable/ctf-nonroot-linking.lk: Likewise.
Nick Alcock [Fri, 30 May 2025 14:26:26 +0000 (15:26 +0100)]
libctf: dedup: improve hiding of conflicting types in the same dict
If types are conflicting, they are usually moved into separate child dicts
-- but not always. If they are added to the same dict by the cu-mapping
mechanism (as used e.g. for multi-TU kernel modules), we can easily end
up adding multiple conflicting types with the same name to the same dict.
The mechanism used for turning on the non-root-visible flag in order to do
this had a kludge attached which always hid types with the same name,
whether or not they were conflicting. This is unnecessary and can hide
types that should not be hidden, as well as hiding bugs. Remove it, and
replace it with two different approaches:
- for everything but cu-mapped links (the in-memory first phase of a link
with ctf_link_add_cu_mapping in force), check for duplicate names if
types are conflicting, and mark them as hidden if the names are found.
This will never happen in normal links (in an upcoming commit we will
suppress doing even this much in such cases).
- for cu-mapped links, the only case that merges multiple distinct target
dicts into one, we apply a big hammer and simply hide everything! The
non-root flag will be ignored in the next link phase anyway (which dedups
the cu-mapped pieces against each other), and this way we can be sure
that merging multiple types cannot incur name clashes at this stage.
The result seems to work: the only annoyance is that when enums with
conflicting enumerators are found in a single cu-mapped child (so, really
multiple merged children), you may end up with every instance of that enum
being hidden for reasons of conflictingness. I don't see a real way to
avoid that.
libctf/
PR libctf/33047
* ctf-dedup.c (ctf_dedup_emit_type): Only consider non
conflicting types. Improve type hiding in the presence of clashing
enumerators. Hide everything when doing a cu-mapped link: they will
be unhidden by the next link pass if nonconflicting.
It is based on a misconception, that hidden types in the deduplicator
input should always be hidden in the output. For cu-mapped links,
and final links following cu-mapped links, this is not true: we want
to hide inputs if they were conflicting on the output and no more.
We will reintroduce the testcase once a better fix is found.
Nick Alcock [Wed, 28 May 2025 14:53:52 +0000 (15:53 +0100)]
libctf: archive: endianness-flipping and range-checking
This does endianness-flipping just like CTF dicts, flipping aggressively on
open, taking advantage of the archive's mmapped nature to flip all the size
words before each archive member as well.
The range checking verifies non-overlappingness of archive sections and
non-overrunning: it does not verify that archive members don't overlap,
because any such overlap would almost certainly fail at open time anyway
(due to the prefixed size word if nothing else).
This dug up a bug in v1 archives, where the size word included the length
of *the size word itself*: we correspondingly reduce that size if v1
archives are encountered (and fail if the result underflows).
Nick Alcock [Wed, 28 May 2025 12:42:11 +0000 (13:42 +0100)]
libctf: archive: format v2
This commit does a bunch of things, all tangled together tightly enough that
disentangling them seemed no to be worth doing.
The biggest is a new archive format, v2, identified by a magic number which
is one higher than the v1 format's magic number. As usual with libctf we
can only write out the new format, but can still read the old one.
The new format has multiple improvements over the old:
- It is written native-endian and aggressively endian-swapped at open time,
just like CTF and BTF dicts; format v1 was little-endian, necessitating
byteswapping all over the place at read and write time rather than
localized in one pair of functions at read time.
- The modent array of name-offset -> archive-offset mappings for the CTF
archives is explicitly pointed at via a new ctfa_modents header member
rather than just starting after the end of the header.
- The length that prepends each archive member actually indicates its
length rather than always being sizeof (uint64_t) bytes too high (this
was an outright bug)
- There is a new shared properties table which in future we may be able to
use to unify common values from the constituent CTF headers, reducing the
size overhead of these (repeated, uncompressed) entities. Right now it
only contains one value, parent_name, which is the parent dict name if
one is common across all dicts in the archive (always true for any
archives derived from ctf_link()). This is used to let
ctf_archive_next() et al reliably open dicts in the archive even if they
are child BTF dicts (which do not contain a header name).
The properties table shares its property names with the CTF members,
and uses the same format (and shared code) for the property values as for
CTF archive members: length-prepended. The archive members and
name->value table ("modents") use distinct tables for properties and CTF
dicts, to ensure they are spatially separated in the file, to maximize
compressibility if we end up with a lot of properties and people compress
the whole thing.
We can also restrict various old bug-workaround kludges that only apply to
dicts found in v1 archives: in particular, we needed to dig out the preamble
of some CTF dicts without opening them to figure out whether they used the
.dynstr or .strtab sections: this whole bug workaround is now unnecessary
for v2 and above.
There are other changes for readability and consistency:
- The archive wrapper data structure, known outside ctf-archive.c as
ctf_archive_t, is now consistently referred to inside ctf-archive.c as
'struct ctf_archive_internal' and given the parameter name 'arci' rather
than sometimes using ctf_archive_t and sometimes using 'wrapper' or 'arc'
as parameter names. The archive itself is always called 'struct
ctf_archive' to emphasise that it is *not* a ctf_archive_t.
ctf_archive_t remains the public typedef: the fact that it's not actually
the same thing as the archive file format is an internal implementation
detail.
- We keep the archive header around in a new ctfi_hdr member, distinct
from the actual archive itself, to make upgrading from v1 and cross-
endianness support easier. The archive itself is now kept as a char *
and used only to root pointer arithmetic.
Nick Alcock [Mon, 12 May 2025 11:31:00 +0000 (12:31 +0100)]
libctf: archive, open: when opening, always set errp to something
ctf_arc_import_parent, called by the cached-opening machinery used by
ctf_archive_next and archive-wide lookup functions like
ctf_arc_lookup_symbol, has an err-pointer parameter like all other opening
functions. Unfortunately it unconditionally initializes it whenever
provided, even if there was no error, which can lead to its being
initialized to an uninitialized value. This is not technically an
API-contract violation, since we don't define what happens to the error
value except when an error happens, but it is still unpleasant.
Initialize it only when there is an actual error, so we never initialize it
to an uninitialized value.
While we're at it, improve all the opening pathways: on success, set errp to
0, rather than leaving it what it was, reducing the likelihood of
uninitialized error param returns in callers too. (This is inconsistent
with the treatment of ctf_errno(), but the err value being a parameter
passed in from outside makes the divergence acceptable: in open functions,
you're never going to be overwriting some old error value someone might want
to keep around across multiple calls, some of which are successful and some
of which are not.)
Soup up existing tests to verify all this.
Thanks to Bruce McCulloch for the original patch, and Stephen Brennan for
the report.
libctf/
PR libctf/32903
* ctf-archive.c (ctf_arc_open_internal): Zero errp on success.
(ctf_dict_open_sections): Zero errp at the start.
(ctf_arc_import_parent): Intialize err.
* ctf-open.c (ctf_bufopen): Zero errp at the start.
* testsuite/libctf-lookup/add-to-opened.c: Make sure one-element
archive opens update errp.
* testsuite/libctf-writable/ctf-compressed.c: Make sure real archive
opens update errp.
Nick Alcock [Fri, 25 Apr 2025 20:54:28 +0000 (21:54 +0100)]
libctf: API change documentation (NOT FOR UPSTREAMING)
These probably need to be turned into libctf/NEWS content once we decide (if
we decide) that these changes are good. (I do hope we don't make too many
changes because it'll be horribly disruptive, but I wouldn't be surprised to
see a few...)
Nick Alcock [Fri, 25 Apr 2025 20:53:18 +0000 (21:53 +0100)]
ld: testsuite: a tiny start on ld.ctf test adjustments
This is just allowing for changes in objdump itself -- the actual
test results cannot be adjusted until either CTFv4 is emitted or
back-compatibility upgrading is implemented (or, preferably, both,
plus testing of a subset of them with -gbtf as well).
Nick Alcock [Fri, 25 Apr 2025 20:49:22 +0000 (21:49 +0100)]
libctf: run_lookup_test: force BTF emission (NOT FOR UPSTREAMING)
Pro tem as a hack until GCC supports -gctf for v4, or v3 upgrading
is supported, or direct CTF-then-BTF tests are written, just emit
BTF for test purposes.
Nick Alcock [Fri, 25 Apr 2025 20:45:45 +0000 (21:45 +0100)]
binutils: objdump, readelf: BTF dumping support
objdump and readelf's --ctf option can now dump BTF as well (in CTF dumping
format, which is quite high-level and C-like compared to bpftool btf dump:
both have their uses).
Nick Alcock [Fri, 25 Apr 2025 20:33:53 +0000 (21:33 +0100)]
ld: BTF deduplication
Figuring out what to do when a mix of BTF and CTF sections is supplied is a
little magical. We hunt down all the .BTF and .ctf sections in the link,
but just because all the sections we found were .BTF doesn't mean that the
output isn't going to be CTF: if deduplication results in any conflicting
types, we'll need a CTF section to encode them (BTF cannot yet represent
such things).
So if we find that we've got nothing but .BTF sections, we do as we do for
.ctf and mark all but one of them as excluded from the link (with the intent
of creating the deduplicated output in the remaining one): but we also
create a provisional linker-created .ctf section, just in case we need it
later (we can't tell at this stage, before deduplication).
After deduplication, one of these sections is unneeded. Sometimes, we can
do this removal via Depending on the emulation, lang_write_ctf may be called
either early (long before bfd_elf_final_link) or late (after final link and
indeed symtab and strtab writeout). (ELF calls it late). If called early,
we can figure out what format was emitted by ctf_link and freely remove the
unwanted section by flipping on its SEC_EXCLUDE flag.
But that leaves late calls. We add a new ctf_remove_section hook to the
bfd_link_callbacks, which is invoked at the very start of
bfd_elf_final_link, when removal of sections is still permitted. We invoke
section removal for the .ctf-or-.BTF section via this hook if lang_write_ctf
is called late: this then does the extra trickery with section count
adjustment etc needed to remove sections so late, and communicates the fact
that it's removed sections to bfd_elf_final_link, which can then decide to
call _bfd_fix_excluded_sec_syms (which is quite expensive, so we do it only
once for all sections removed at this stage, by whatever means).
Nick Alcock [Fri, 25 Apr 2025 20:28:25 +0000 (21:28 +0100)]
bfd, ld: allow the disabling of CTF deduplication; BTF linking
This first half of ld support for BTF deduplication adds a facility to GNU
ld and BFD to entirely disable CTF or BTF deduplication via the new
--disable-ctf-dedup linker option, and (slightly entangled with it) modifies
the existing BFD CTF support so that it can also deduplicate BTF.
Determining whether deduplication is disabled when all you have is a section
requires a bit of digging around and the proxying of of _bfd_get_link_info
into bfd-elf.h via a new _bfd_elf_get_link_info, to dodge include ordering
problems.
(Note that BTF deduplication support is not yet complete: in particular,
relocs into BTF sections don't get handled at all yet.)
Nick Alcock [Fri, 25 Apr 2025 20:18:29 +0000 (21:18 +0100)]
libctf: dump: dump the header; dump enum64s; adapt to API changes
A bunch of dumper changes. Most importantly, adapt to the changes in the _f
iteration function prototypes by no longer carrying around our own cds_fp
dict pointer everywhere but just using the one we are given by the iteration
function.
But also, dump the v3 and v4/BTF headers separately, using the stored
original v3-pre-upgrade header copy if present. The v3 dumper is not tested
yet, of course, but is more or less unchanged from the old code, so probably
nearly works. The v4 dumper is tested.
Add enum64 support (basically just a bit of extra code to print the
signedness of enums).
Nick Alcock [Fri, 25 Apr 2025 20:09:34 +0000 (21:09 +0100)]
libctf: archive: allow opening BTF dicts in archives (not for upstreaming)
BTF dicts are normally suppressed in archives, but it is possible
to create them with enough cunning. If such an archive is
encountered, the BTF dicts in it have no parent name, which
means that ctf_arc_import_parent (used by ctf_dict_open_cached,
ctf_archive_next, and all the ctf_arc_lookup functions) fails
to figure out what parent to import, and fails.
Kludge around it by relying on our secret knowledge that ctf_link_write
always emits the parent dict into the archive first. If no name is set,
import the parent dict for now. (Before upstreaming, a new archive format
with a dedicated parent dict field will turn up, obviating this kludge.)
Nick Alcock [Fri, 25 Apr 2025 20:06:46 +0000 (21:06 +0100)]
libctf: link: improve BTF child dict naming
BTF dicts don't have a cuname, which means that when the deduplicator runs
over them any child dicts that result from conflicted types found in those
CUs end up with no name either. Detect such unnamed dicts and propagate
in the name the linker gave them at input time instead. (There is always
*some* such name, even if it's something totally useless like "#1"; usually
it's much more useful.)
Nick Alcock [Fri, 25 Apr 2025 19:49:26 +0000 (20:49 +0100)]
libctf: dedup: conflicting CU names and merging into the parent
The last two dedup changes are, firstly, to use ctf_add_conflicting() to
arrange that conflicting types that are hidden because they are added to the
same dict as the types they conflict with (e.g. conflicting types in
modules) are properly marked with the CU name that the type comes from.
This could of course not be done with the old non-root flag, but now that we
have proper prefix types, we can record it, and consumers can find out what
CU any type comes from via ctf_type_conflicting (or, for non-kernel CTF
generated by GNU ld, via the ctf_cuname of the per-cu dict).
Secondly, we add a new kind of CU mapping for cu-mapped (two-stage) links
(as a reminder, these carry out a second stage of dedupping in which they
squash specific CUs down to a named set of child dicts, fusing named inputs
into particular named outputs: the kernel linker uses this to make child
dicts that represent modules rather than translation units). You can now map
any CU name to "" (the null string). This indicates that types that would
land in the CU in question should not be emitted into any sort of per-module
dict but should instead just be emitted into the shared dict, possibly being
marked conflicting as they do so. The usual popcount mechanism will be used
to pick the type which is left unhidden. The usual forwarding stubs you
would expect to find for conflicting structs and unions will not be emitted:
instead, real structs and unions will take their place. Consumers must take
care when chasing parent types that point to tagged structs to make sure
that there isn't a correspondingly-named struct in the child they're looking
at (but this is generally a problem with type chasing in children anyway,
which I have a TODO open to find some sort of solution to: this should be
being done automatically, and isn't).
Nick Alcock [Fri, 25 Apr 2025 19:41:14 +0000 (20:41 +0100)]
libctf: dedup: decl tag support.
Decl tags to types and to functions and function arguments are relatively
straightforward, as are decl tags to structures as a whole or to members of
untagged structures; but decl tags to specific members of tagged structs and
unions have two separate nasty problems, entirely down to the use of tagged
structures to break cycles in the type graph.
The first is that we have to mark decl tags conflicting if their associated
struct is conflicting, but traversal from types to their parents halts at
tagged structs and unions, because the type graph is sharded via stubs at
those points and conflictedness ceases. But we don't want to do that here:
a decl_tag to member 10 of some struct is only valid if that struct *has*
ten members, and if the struct is conflicted, some may have only one. The
decl tag is only valid for the specific struct-with-ten-members it was
originally pointing at, anyway: other structs-with-ten-members may have
entirely different members there, which are not tagged or which are tagged
with something else.
So we track this by keeping track of the only thing that is knowable about
struct/union stubs: their decorated name. The citers graph gains mappings
from decorated SoU names to decl tags (where the decl tag has a
component_idx), and conflictedness marking chases that and marks
accordingly, via the new ctf_dedup_mark_conflicting_hash_citers.
The second problem is that we have to emit decl tags to struct members of
all kinds after the members are emitted, but the members are emitted later
than core type deduplication because they might refer to any types in the
dict, including types added after the struct was added. So we need to
accumulate decl tags to struct members in a new hashtab
(cd_emission_struct_decl_tags) and add yet *another* pass that traverses
that and emits all the decl tags in it. (If it turns out that decl tags to
other things can similarly appear before the type they refer to, we'll
either have to sort them earlier or emit them at the end as well -- but this
seems unlikely.)
None of this complexity is properly tested, because we're not yet emitting
decl tags (as far as I know). But at least it doesn't break anything else,
and it's somewhere to start.
Nick Alcock [Fri, 25 Apr 2025 19:32:58 +0000 (20:32 +0100)]
libctf: dedup: type tags
Another trivial case: they're just like pointers except that they have a
name (and we don't need to care about that, because names are hashed in, if
present, anyway).
Nick Alcock [Fri, 25 Apr 2025 18:22:42 +0000 (19:22 +0100)]
libctf: dedup: datasecs and vars
These are a bit trickier than previous things. Datasecs are unusual: the
content they contain for a given variable is conceptually part of that
variable, in that a variable can only appear in one datasec: so if two TUs
have different datasec values for a variable, you'll want to emit two
conflicting variables with different datasec entries. Equally, if they
have entries in different datasecs, they're conflicting. But the *index*
of a variable in a datasec has nothing to do with the variable: it's just
a property of how many other variables are in the datasec.
So we turn the type graph upside down for them. We track the variable ->
datasec mappings for every variable we are dedupping, and use this to hash
variables with datasec entries *twice*: firstly, as purely variable type,
name, and promoted-to-non-extern linkage, and secondly with all of that plus
the datasec name, offset and size: we indicate that the non-extern hash
*replaces* the extern one, and use this later on. The datasec itself is not
hashed at all! We skip it at both hashing and emission time (without
breaking anything else, because nothing points at datasecs, so nothing will
ever recurse down into one).
The popcount code (used to find the "most popular" type, the one to put in
the shared dict) changes to say that replaced types (extern vars) popcounts
are added to the counts of the types that replace them (the corresponding
non-extern vars).
At emission time, replaced variables (extern variables) are skipped,
ensuring that extern vars with non-conflicting non-extern counterparts are
skipped in favour of the non-extern ones. ctf_add_section_variable then
takes care of emitting both the var and its corresponding datasec for us.
Nick Alcock [Fri, 25 Apr 2025 18:14:02 +0000 (19:14 +0100)]
libctf: dedup: structs with bitfields, BTF floats
The last two trivial cases. Hash in the bitfieldness of structs and the
bit-width of members (their bit-offset is already being hashed in), and emit
them accordingly.
BTF floats hardly have any state: emitting them is even easier.
These are all fairly simple and are handled together because some of the
diffs are annoyingly entwined.
enum and enum64 are trivial: it's just like enums used to be, except that we
hash in the unsignedness value, and emit signed or unsigned enums or enum64s
appropriately. (The signedness stuff on the emission side is fairly
invisible: it's automatically handled for us by ctf_type_encoding and
ctf_add_enum*_encoded, via the CTF_INT_SIGNED encoding.)
Functions are also fairly simple: we hash in all the parameter names as well
as the args, and emit them accordingly.
Linkage is more difficult. We want to deduplicate extern and non-extern
declarations together, while leaving static ones separate. We do this by
promoting extern linkage to global at hashing time, and maintaining a
cd_linkages hashmap which maps from type hash values of func linkages (and
vars) to the best linkage known so far, then updating it if a better one
("less extern") comes along (relying on the fact that we are already
unifying the hashes of otherwise-identical extern and non-extern types). At
emission time, we use this hashtab to figure out what linkage to emit.
Nick Alcock [Fri, 25 Apr 2025 17:42:55 +0000 (18:42 +0100)]
libctf: dedup: fix a broken error path in string dedup
If we run out of memory updating the string counts, set the right errno:
ctf_dynhash_insert returns a *negative* error value, and we want a positive
one in the ctf_errno.
Nick Alcock [Fri, 25 Apr 2025 17:40:39 +0000 (18:40 +0100)]
libctf: dedup: chase API changes: use the public API more
To get ready for the deduplicator changes, we chase the API changes to
things like ctf_member_next, and add support for prefix types (using the
suffix where appropriate, etc). We use the ctf-types API for things like
forward lookup, using the private _tp functions to reduce overhead while
centralizing knowledge of things like the encoding of enum forwards outside
the deduplicator.
Nick Alcock [Fri, 25 Apr 2025 17:26:45 +0000 (18:26 +0100)]
libctf: open-bfd: open BTF dicts
Teaching ctf_open and ctf_fdopen to open BTF dicts if passed is quite
simple: we just need to check the magic number and allow BTF dicts
into the lower-level ctf_simple_open machinery (which ultimately
calls ctf_bufopen).
Nick Alcock [Fri, 25 Apr 2025 17:29:06 +0000 (18:29 +0100)]
libctf: link: drop unnecessary back-compatibility code
We no longer need to ensure that inputs have a new-format func info
section: no such sections exist in CTFv4 (and the v3 compatibility
code will throw away old-format sections).
The idea here is that callers can call ctf_link_output_is_btf on a
ctf_link()ed (deduplicated) dict to tell whether a link will yield
BTF-compatible output before actually generating that output, so
they can e.g. decide whether to avoid trying to compress the dict
if they know it would be BTF otherwise (since compressing a dict
renders it non-BTF-compatible).
ctf_link_write() gains an optional is_btf output parameter that
reports whether the dict that was finally generated is actually BTF
after all, perhaps because the caller didn't call
ctf_link_output_is_btf or wants to be robust against possible future
changes that may add other reasons why a written-out dict can't be BTF
at the last minute.
These are simple wrappers around already-existing machinery earlier in
this series.
Nick Alcock [Fri, 25 Apr 2025 17:17:33 +0000 (18:17 +0100)]
libctf: strings: don't check for non-deduplicable atoms in the parent
Callers of ctf_str_add_no_dedup_ref are indicating that they would like the
string they have added a reference to to appear in the current dict and not
be deduplicated into the parent. This is true even if the string already
exists in the parent, so we should not check for strings in the parent and
reuse them in this case.
Nick Alcock [Fri, 25 Apr 2025 17:12:47 +0000 (18:12 +0100)]
libctf: serialize: finish off the serializer
The only remaining parts of serialization that need fixing up is
ctf_preserialize, which despite its name does nearly all the work of
serialization: the only bit it doesn't do is write the string tables
(since that has to happen across dicts after all the dicts have otherwise
been laid out, in order to deduplicate the strtabs).
As usual in this series, there's adjustment for various field name changes
(maxtypes -> ntypes, the move into ctf_serialize, etc), and extra work to
figure out whether we're emitting BTF or not and to handle the distinction
between CTF and BTF headers, and not try to emit CTF-only stuff like the
symtypetabs into BTF dicts; we can also throw out a bunch of old code that
sets compatibility flags, everything to do with forcing variables into the
dynamic state in case they changed (we're going to handle that more
generally for everything in the types table at a later date, outside
serialization), and everything to do with special handling of variables in
general.
But much of that is only a couple of lines each, and most of the changes are
mechanical: this is probably the simplest serialization commit in this
series.
Nick Alcock [Fri, 25 Apr 2025 17:09:02 +0000 (18:09 +0100)]
libctf: open: fix closing of children with imported parents
Closing a parent dict for the last time erases all its types and strings,
which makes type and string lookups in any surviving children impossible
from then on. Since children hold a reference to their parent, this can
only happen in ctf_dict_close of the last child, after the parent has
been closed by the caller as well. Since DTD deletion now involves
doing type and string lookups in order to clean out the name tables,
close the parent only after the child DTDs have been deleted.
Nick Alcock [Fri, 25 Apr 2025 16:59:31 +0000 (17:59 +0100)]
libctf: open, types: ctf_import for BTF
ctf_import needs a bunch of fixes to work with pure BTF dicts -- and, for
that matter, importing newly-created parent dicts that have never been
written out, which may have a bunch of nonprovisional types (if types were
added to it before any imports were done) or may not (if at least one
ctf_import into it was done before any types were added).
So we adjust things so that the values that are checked against are the
nonprovisional-types values: the header revisions actually changed the name
of cth_parent_typemax to cth_parent_ntypes to make this clearer, so catch up
with that. In the parent, we have to use ctf_idmax, not ctf_typemax.
One thing we must prohibit is that you cannot add a bunch of types to a
child and then import a parent into it: the type IDs will all be wrong
and the string offsets more so. This was partly prohibited: prohibit it
entirely (excepting only that the not-actually-written-out void type
we might add to new BTF dicts does not influence this check).
Since BTF children don't have a cth_parent_ntypes or a cth_parent_strlen, we
cannot check this stuff, but just set them and hope.
Nick Alcock [Fri, 25 Apr 2025 16:54:48 +0000 (17:54 +0100)]
libctf: serialize: handle CTF-versus-BTF output format checks
The internal function ctf_serialize_output_format centralizes all the checks
for BTF-versus-CTF, checking to see if the type section, active
suppressions, and BTF-emission mode permit BTF emission, setting
ctf_serialize.cs_is_btf if we are actually BTF, and raising ECTF_NOTBTF if
we are requiring BTF emission but the type section is such that we can't
emit it.
(There is a forcing parameter in place, as with most of these serialization
functions, to allow for the caller to force CTF emission if it knows the
output will be compressed or will be part of multi-member archives or
something else external to the type section that BTF does not support.)
Nick Alcock [Fri, 25 Apr 2025 16:28:37 +0000 (17:28 +0100)]
libctf: serialize: size and emit the type section
As with sizing, this needs to support type suppression and CTF_K_BIG
elision, and adapt to the DTD representation changes. Those changes cause a
general complexity reduction because we no longer have to memcpy the vlen
into place separately for every type kind, but can do it all at once using
shared code above the per-kind switch statement. That statement's only job
now is generating refs out of type IDs and string offsets, and translating
the struct offset from gap- into non-gap representation for non-big structs.
We do three distinct things:
- check whether all the types in a section are BTF-compatible, after
suppression of unwanted type kinds (including types with unwanted
prefixes), and elision of unneeded struct/union CTF_K_BIGs
- size the type section, taking suppression and CTF_K_BIG elision into
account
- actually emit it, again taking all the above into account
These all have to come to the same conclusions for every type: if the first
one gets things wrong we might try to emit something as BTF when we can't;
if the latter two are inconsistent, we might have a buffer overrun.
So the type emission code double-checks BTF-compatibility and raises
ECTF_NOTBTF if necessary; we also aggressively check for potential overruns
before every memcpy() into the buffer and raise an ECTF_INTERNAL assertion
failure if need be. Thankfully there are a lot fewer memcpy()s than there
used to be: there are only four places we need to check, all close to each
other, which is pretty maintainable.
We add a bit of debugging when --enable-libctf-hash-debugging is on,
printing the translation from provisional to final type ID so that you can
use it to map back to the provisional ID again when trying to track down
deduplicator problems, since the IDs the deduplicator will report at its
emission time are only provisional (the final parent-relative IDs are not
assigned until now).
Nick Alcock [Fri, 25 Apr 2025 13:17:36 +0000 (14:17 +0100)]
libctf: serialize: type section sizing
This is made much simpler by the fact that the DTD representation
now tracks the size of each vlen, so we don't need per-type-kind
code to track it ourselves any more. There's extra code to handle
type suppression, CTF_K_BIG elision, and prefixes.
Nick Alcock [Fri, 25 Apr 2025 12:01:21 +0000 (13:01 +0100)]
libctf: serialize: check the type section for BTF-incompatible types
We add a new ctf_type_sect_is_btf function (internal to ctf-serialize.c) to
check the type section against the write prohibitions list and (after
write-suppression) against the set of types allowed in BTF, and determine
whether this type section contains any types BTF does not allow.
CTF-specific type kinds like CTF_K_FLOAT are obviously prohibited in BTF, as
are CTF-specific prefixes, except that CTF_K_BIG is allowed if and only if
both its ctt_size and vlen are still zero: in that case it will be elided by
type section writeout and will never appear in the BTF at all.
Structs are checked to make sure they don't use any nameless padding members
and that (if they are bitfields) all their offsets will still fit after
conversion from CTF_K_BIG gap-between-struct-members representation (if they
are not bitfields, we know they will fit, but for bitfields, they might be
too big).
Nick Alcock [Fri, 25 Apr 2025 11:56:58 +0000 (12:56 +0100)]
libctf: strings: no external strings in BTF
One of the things BTF doesn't have is the concept of external strings which
can be shared with the ELF strtab. Therefore, even if the linker has
reported strings which the dict is reusing, when we generate the strtab for
a BTF dict we should emit those strings into it (and we should certainly
not cause the presence of external strings to prevent BTF emission!)
Note that since already-written strtab entries are never erased, writing a
dict as BTF and then CTF will cause external strings to be emitted even for
the CTF. This sort of repeated writing in different formats seems to be
very rare: in any case, the problem can be avoided by simply doing the CTF
writeout first (the following BTF writeout will spot the missing external-
in-CTF strings and add them).
We also throw away the internal-only function ctf_strraw_explicit(), which
was used to add strings with a hardwired strtab: it was only ever used to
write out the variable section, which is gone in v4.
Nick Alcock [Fri, 25 Apr 2025 11:42:12 +0000 (12:42 +0100)]
libctf: serialize: kind suppression and prohibition
The CTF serialization machinery decides whether to write out a dict as BTF
or CTF (or, in LIBCTF_BTM_BTF mode, whether to write out a dict or fail with
ECTF_NOTBTF) in part by looking at the type kinds in the dictionary.
It is possible that you'd like to extend this check and ban specific type
kinds from the dictionary (possibly even if it's CTF); it's also possible
that you'd like to *not* fail even if a CTF-only kind is found, but rather
replace it with a still-valid stub (CTF_K_UNKNOWN / BTF_KIND_UNKNOWN) and
keep going. (The kernel's btfarchive machinery does this to ensure that
the compiler and previous link stages have emitted only valid BTF type
kinds.)
ctf_write_suppress_kind supports both these use cases:
+int ctf_write_suppress_kind (ctf_dict_t *fp, int kind, int prohibited);
This commit adds only the core population code: the actual suppression is
spread across the serializer and will be added in the next commits.
Nick Alcock [Fri, 25 Apr 2025 11:35:07 +0000 (12:35 +0100)]
libctf: serialize: user control over BTF-versus-CTF writeout
We need some way for users to declare that they want BTF or CTF in
particular to be written out when they ask for it, or that they don't mind
which. Adding this to all the ctf_write functions (like the compression
threshold already is) would be a bit of a nightmare: there are a great many
of them and this doesn't seem like something people would want to change
on a per-dict basis (even if we did, we'd need to think about archives and
linking, which work on a higher level than single dicts).
So we repurpose an unused, vestigial existing function, ctf_version(), which
was originally intended to do some sort of rather unclear API switching at
runtime, to allow switching between different CTF file format versions (not
yet supported, you have to pass CTF_VERSION) and BTF writeout modes:
/* BTF/CTF writeout version info.
ctf_btf_mode has three levels:
- LIBCTF_BTM_ALWAYS writes out full-blown CTFv4 at all times
- LIBCTF_BTM_POSSIBLE writes out CTFv4 if needed to avoid information loss,
BTF otherwise. If compressing, the same as LIBCTF_BTM_ALWAYS.
- LIBCTF_BTM_BTF writes out BTF always, and errors otherwise.
Note that no attempt is made to downgrade existing CTF dicts to BTF: if you
read in a CTF dict and turn on LIBCTF_BTM_POSSIBLE, you'll get a CTF dict; if
you turn on LIBCTF_BTM_BTF, you'll get an unconditional error. Thus, this is
really useful only when reading in BTF dicts or when creating new dicts. */
/* Set the CTF library client version to the specified version: this is the
version of dicts written out by the ctf_write* functions. If version is
zero, we just return the default library version number. The BTF version
(for CTFv4 and above) is indicated via btf_hdr_len, also zero for "no
change".
You can influence what type kinds are written out to a CTFv4 dict via the
ctf_write_suppress_kind() function. */
extern int ctf_version (int ctf_version_, size_t btf_hdr_len,
ctf_btf_mode_t btf_mode);
(We retain the ctf_version_ stuff to leave space in the API to let the
library possibly do file format downgrades in future, since we've already
had requests for such things from users.)
Nick Alcock [Fri, 25 Apr 2025 11:20:36 +0000 (12:20 +0100)]
libctf, serialize: preparatory steps
The new serializer is quite a lot more customizable than the old, because it
can write out BTF as well as CTF: you can ask to write out BTF or fail,
write out CTF if required to avoid information loss, otherwise BTF, or
always write out CTF.
Callers often need to find out whether a dict could be written out as BTF
before deciding how to write it out (because a dict can never be written out
as BTF if it is compressed, a caller might well want to ask if there is
anything else that prevents BTF writeout -- say, slices, conflicting types,
or CTF_K_BIG -- before deciding whether to compress it). GNU ld will do
this whenever it is passed only BTF sections on the input.
Figuring out whether a dict can be written out as BTF is quite expensive: we
have to traverse all the types and check them, including every member of
every struct. So we'd rather do that work only once. This means making a
lot of state once private to ctf_preserialize public enough that another
function can initialize it; and since the whole API is available after
calling this function and before serializing, we should probably arrange
that if we do things we know will invalidate the results of all this
checking, we are forced to do it again.
This commit does that, moving all the existing serialization state into a
new ctf_serialize_t and adding to it. Several functions grow force_ctf
arguments that allow the caller to force CTF emission even if the type
section looks BTFish: the writeout code and archive creation use this to
force CTF emission if we are compressing, and archive creation uses it
to force CTF emission if a CTF multi-member archive is in use, because
BTF doesn't support archives at all so there's no point maintaining
BTF compatibility in that case. The ctf_write* functions gain support for
writing out BTF headers as well as CTF, depending on whether what was
ultimately written out was actually BTF or not.
Even more than most commits in this series, there is no way this is
going to compile right now: we're in the middle of a major transition,
completed in the next few commits.
Nick Alcock [Fri, 25 Apr 2025 10:51:04 +0000 (11:51 +0100)]
libctf, open: new API for getting the size of CTF/BTF file sections
I wrote this for BTF type size querying programs, but it might be
of more general use and it's impossible to get this info in any
other way, so we might want to keep it.
New API:
+size_t ctf_sect_size (ctf_dict_t *, ctf_sect_names_t sect);
Nick Alcock [Fri, 25 Apr 2025 10:47:07 +0000 (11:47 +0100)]
libctf: types: access to raw type data
This new API lets users ask for the raw type data associated with a type
(either the whole lot including prefixes, or just the suffix if this is not
a CTF_K_BIG type), and then they can manipulate it using ctf.h functions
or whatever else they like. Doing this does not preclude using libctf
querying functions at the same time (just don't change the type! It's
const for a reason).
New API:
+const ctf_type_t *ctf_type_data (ctf_dict_t *, ctf_id_t, int prefix);
This function was unimplementable before the DTD changes, because the
ctf_type_t and vlen were separated in memory: but now they're always stored
in a single buffer, it's reliable and simple, indeed trivial.
Nick Alcock [Fri, 25 Apr 2025 10:44:19 +0000 (11:44 +0100)]
libctf: types: recursive type visiting
ctf_type_visit and ctf_type_rvisit have to adapt to the internal
API changes, but also to the change in the representation of
structures. The new code is quite a lot simpler than the old,
because we don't need to roll our own iterator but can just use
ctf_member_next.
API changes, the usual for the *_f typedefs and anything to do with
structures:
-typedef int ctf_visit_f (const char *name, ctf_id_t type, unsigned long offset,
- int depth, void *arg);
+typedef int ctf_visit_f (ctf_dict_t *, const char *name, ctf_id_t type,
+ size_t offset, int bit_width, int depth,
void *arg);
Nick Alcock [Fri, 25 Apr 2025 10:41:45 +0000 (11:41 +0100)]
libctf, create: typedefs
Nothing here but adjustment to internal API changes. Typedefs have no
special properties that need querying, so there are no changes to
ctf-types.c at all.
Nick Alcock [Fri, 25 Apr 2025 10:31:28 +0000 (11:31 +0100)]
libctf: create, types: reftypes and pointers
This is pure adjustment for internal API changes, and a change to the
type-compatibility of pointers to type 0 now that it can be void as well as
"unrepresentable".
Nick Alcock [Fri, 25 Apr 2025 10:23:46 +0000 (11:23 +0100)]
libctf: create, types: conflicting types
The conflicting type kind is a CTF-specific prefix kind consisting purely of
an optional translation unit name. It takes the place of the old hidden
bit: we have already seen it used to prefix types added with a
CTF_ADD_NONROOT flag. The deduplicator will also use them to label
conflicting types from different TUs smushed into the same dict by the
CU-mapping mechanism: unlike the hidden bit, with this scheme users can tell
which CUs the conflicting types came from.
(Frankly I expect ctf_set_conflicting to be used only by deduplicators and
things like that, but if we provide an option to query something we should
also provide an option to produce it...)
Nick Alcock [Fri, 25 Apr 2025 10:14:09 +0000 (11:14 +0100)]
libctf, create, types: type and decl tags
These are a little more fiddly than previous kinds, because their
namespacing rules are odd: they have names (so presumably we want an API to
look them up by name), but the names are not unique (they don't need to be,
because they are not entities you can refer to from C), so many distinct
tags in the same TU can have the same name. Type tags only refer to a type
ID: decl tags refer to a specific function parameter or structure member via
a zero-indexed "component index".
The name tables for these things are a hash of name to a set of type IDs;
rather different from all the other named entities in libctf. As a
consequence, they can presently be looked up only using their own dedicated
functions, not using ctf_lookup_by_name et al. (It's not clear if this
restriction could ever be lifted: ctf_lookup_by_name and friends return a
type ID, not a set of them.)
They are similar enough to each other that we can at least have one function
to look up both type and decl tags if you don't care about their
component_idx and only want a type ID: ctf_tag. (And one to iterate over
them, ctf_tag_next).
(A caveat: because tags aren't widely used or generated yet, much of this is
more or less untested and/or supposition and will need testing later.)
New API, more or less the minimum needed because it's not entirely clear how
these things will be used:
Functions change in CTFv4 by growing argument names as well as argument
types; the representation changes into a two-element array of (type, string
offset) rather than a simple array of arg types. Functions also gain an
explicit linkage in a different type kind (CTF_K_FUNC_LINKAGE, which
corresponds to BTF_KIND_FUNC).
New API:
typedef struct ctf_funcinfo {
/* ... */
- uint32_t ctc_argc; /* Number of typed arguments to function. */
+ size_t ctc_argc; /* Number of typed arguments to function. */
};
Adding this is fairly straightforward; the only annoying part is the way the
callers need to allocate space for the arg name and type arrays. Maybe we
should rethink these into something like ctf_type_aname(), allocating
space for the caller so the caller doesn't need to? It would certainly
make all the callers in libctf much less complex...
While we're at it, adjust ctf_type_reference, ctf_type_align, and
ctf_type_size for the new internal API changes (they also all have
special-case code for functions).
Nick Alcock [Thu, 24 Apr 2025 17:00:32 +0000 (18:00 +0100)]
libctf, types: ctf_type_kind_{iter,next} et al
These new functions let you iterate over types by kind, letting you get all
variables, all enums, all datasecs, etc. (This is amenable to future
optimization, and some is expected shortly.)
We also add new iternal functions ctf_type_kind_{forwarded_,unsliced_,}tp
which are like the corresponding non-_tp functions except that they
take a ctf_type_t rather than a type ID: doing this allows the deduplicator
to use these nearly-public functions more. The public ctf_type_kind*
functions are reimplemented in terms of these.
This machinery is the principal place where the magic encoding of forwards
is encoded.
Nick Alcock [Thu, 24 Apr 2025 16:42:16 +0000 (17:42 +0100)]
libctf: create, types: variables and datasecs (REVIEW NEEDED)
This is an area of significant difference from CTFv3. The API changes
significantly, with quite a few additions to allow creation and querying of
these new datasec entities:
+/* Search a datasec for a variable covering a given offset.
+
+ Errors with ECTF_NODATASEC if not found. */
+
+ctf_id_t ctf_datasec_var_offset (ctf_dict_t *fp, ctf_id_t datasec,
+ uint32_t offset);
+
+/* Return the datasec that a given variable appears in, or ECTF_NODATASEC if
+ none. */
+
+ctf_id_t ctf_variable_datasec (ctf_dict_t *fp, ctf_id_t var);
-int ctf_add_variable (ctf_dict_t *, const char *, ctf_id_t);
+/* ctf_add_variable adds variables to no datasec at all;
+ ctf_add_section_variable adds them to the given datasec, or to no datasec at
+ all if the datasec is NULL. */
+
+ctf_id_t ctf_add_variable (ctf_dict_t *, const char *, int linkage, ctf_id_t);
+ctf_id_t ctf_add_section_variable (ctf_dict_t *, uint32_t,
+ const char *datasec, const char *name,
+ int linkage, ctf_id_t type,
+ size_t size, size_t offset);
We tie datasecs quite closely to variables at addition (and, as should
become clear later, dedup) time: you never create datasecs, you only create
variables *in* datasecs, and the datasec springs into existence when you do
so: datasecs are always found in the same dict as the variables they contain
(the variables are never in the parent if the datasec is in a child or
anything). We keep track of the variable->datasec mapping in
ctf_var_datasecs (populating it at addition and open time), to allow
ctf_variable_datasec to work at reasonable speed. (But, as yet, there are
no tests of this function at all.)
The datasecs are created unsorted (to avoid variable addition becoming
O(n^2)) and sorted at serialization time, and when ctf_datasec_var_offset is
invoked.
We reuse the natural-alignment code from struct addition to get a plausible
offset in datasecs if an alignment of -1 is specified: maybe this is
unnecessary now (it was originally added when ctf_add_variable added
variables to a "default datasec", while now it just leaves them out of
all datasecs, like externs are).
One constraint of this is that we currently prohibit the addition of
nonrepresentable-typed variables, because we can't tell what their natural
alignment is: if we dropped the whole "align" and just required everyone
adding a variable to a datasec to specify an offset, we could drop that
restriction. WDYT?
One additional caveat: right now, ctf_lookup_variable() looks up the type of
a variable (because when it was invented, variables were not entities in
themselves that you could look up). This name is confusing as hell as a
result. It might be less confusing to make it return the CTF_K_VAR, but
that would be awful to adapt callers to, since both are represented with
ctf_id_t's, so the compiler wouldn't warn about the needed change at all...
I've vacillated on this three or four times now.
These all need fairly trivial revisions for prefix types. While
we're at it, we can add explicit handling of nonrepresentable types,
returning CTF_K_UNKNOWN for such types rather than throwing an error,
so that type printing prints (nonrepresentable type) for such types
as it always intended to.
Nick Alcock [Thu, 24 Apr 2025 16:28:34 +0000 (17:28 +0100)]
libctf, create, types: encoding, BTF floats
This adds support for the nearly useless BTF_KIND_FLOAT, under the name
CTF_K_BTF_FLOAT. At the same time we fix up the ctf_add_encoding and
ctf_type_encoding machinery for the new API changes.
I expect this to change a bit: Ali Bahrami reckons I've oversimplified the
CTFv4 encoding representation and need to reintroduce at least a width.
Nick Alcock [Thu, 24 Apr 2025 16:17:57 +0000 (17:17 +0100)]
libctf: create, types: enums and enum64s; type encoding
This commit adapts most aspects of enum handling: querying and iteration,
enumerator querying and iteration, ctf_type_add, etc. We have to adapt to
enum64s and to signed versus unsigned enums, to our vlen and DTD changes and
other internal API changes to handle prefix types etc, and fix the types of
things to allow for 64-bit enumerators. We can also (finally!) get useful
info on enum size rather than being restricted to a value hardwired into
libctf.
We also adjust all the type-encoding functions for the internal API changes,
since enums are the first encodable entities we have covered.
API changes:
-typedef int ctf_enum_f (const char *name, int val, void *arg);
+typedef int ctf_enum_f (const char *name, int64_t val, void *arg);
+typedef int ctf_unsigned_enum_f (const char *name, uint64_t val, void *arg);
-extern const char *ctf_enum_name (ctf_dict_t *, ctf_id_t, int);
-extern int ctf_enum_value (ctf_dict_t *, ctf_id_t, const char *, int *);
+extern const char *ctf_enum_name (ctf_dict_t *, ctf_id_t, int64_t);
+extern int ctf_enum_value (ctf_dict_t *, ctf_id_t, const char *, int64_t *);
+extern int ctf_enum_unsigned_value (ctf_dict_t *, ctf_id_t, const char *, uint64_t *);
+
+/* Return 1 if this enum's contents are unsigned, so you can tell which of the
+ above functions to use. */
+
+extern int ctf_enum_unsigned (ctf_dict_t *, ctf_id_t);
-/* Return all enumeration constants in a given enum type. */
-extern int ctf_enum_iter (ctf_dict_t *, ctf_id_t, ctf_enum_f *, void *);
+/* Return all enumeration constants in a given enum type. The return value, and
+ VAL argument, may need to be cast to uint64_t: see ctf_enum_unsigned(). */
+extern int64_t ctf_enum_iter (ctf_dict_t *, ctf_id_t, ctf_enum_f *, void *);
extern const char *ctf_enum_next (ctf_dict_t *, ctf_id_t, ctf_next_t **,
- int *);
+ int64_t *);
+
+/* enums are created signed by default. If you want an unsigned enum,
+ use ctf_add_enum_encoded() with an encoding of 0 (CTF_INT_SIGNED and
+ everything else off). This will not create a slice, unlike all other
+ uses of ctf_add_enum_encoded(), and the result is still representable
+ as BTF. */
+
+extern ctf_id_t ctf_add_enum64_encoded (ctf_dict_t *, uint32_t, const char *,
+ const ctf_encoding_t *);
+extern ctf_id_t ctf_add_enum64 (ctf_dict_t *, uint32_t, const char *);
-extern int ctf_add_enumerator (ctf_dict_t *, ctf_id_t, const char *, int);
+extern int ctf_add_enumerator (ctf_dict_t *, ctf_id_t, const char *, int64_t);
The only aspects of enums that are not now handled are forwards to enums,
dumping of enums, and deduplication of enums.
This new public API function allows you to find out if a struct has the
bitfield flag set or not. (There are no other properties specific to a
struct, so we needed a new function for it. I am open to a
ctf_struct_info() function handing back a struct if people prefer.)
Nick Alcock [Thu, 24 Apr 2025 15:59:23 +0000 (16:59 +0100)]
libctf: create: ctf_add_type modifications
This adapts ctf_add_type a little, adding support for prefix types, shifting
away from hardwired things towards API functions that can adapt to the CTFv4
changes, adapting to the structure/union API changes, and adding bitfielded
structures and structure members as needed.
Nick Alcock [Thu, 24 Apr 2025 15:55:20 +0000 (16:55 +0100)]
libctf: types: ctf_type_resolve_nonrepresentable
This new internal function allows us to say "resolve a type to its base
type, but treat type 0 like BTF, returning 0 if it is found rather than
erroring with ECTF_NONREPRESENTABLE". Used in the next commit.
Nick Alcock [Thu, 24 Apr 2025 15:47:14 +0000 (16:47 +0100)]
libctf: create: structure and union member addition
There is one API addition here:
int ctf_add_member_bitfield (ctf_dict_t *, ctf_id_t souid,
const char *, ctf_id_t type,
unsigned long bit_offset,
int bit_width);
SoU addition handles the representational changes for bitfields and for
CTF_K_BIG structs (i.e. all structs you can add members to), errors out if
you add bitfields to structs that aren't created with the
CTF_ADD_STRUCT_BITFIELDS flag, and arranges to add padding as needed if
there is too much of a gap for the offsets to encode in one hop (that
part is still untested).
Nick Alcock [Thu, 24 Apr 2025 15:28:26 +0000 (16:28 +0100)]
libctf: create: struct/union addition
There's one API addition here: the existing CTF_ADD_ROOT / CTF_ADD_NONROOT
flags can have a new flag ORed with them, CTF_ADD_STRUCT_BITFIELDS,
indicating that the newly-added struct/union is capable of having bitfields
added to it via the new ctf_add_member_bitfield function (see a later
commit).
Without this, you can only add bitfields via the deprecated slice or base
type encoding representations (the former will force CTF output).
Implementation notes: structs and unions are always added with a CTF_K_BIG
prefix: if promoting from a forward, one is added. These are elided at
serialization time if they are not needed to encode this size of struct /
this number of members. (This means you don't have to figure out in advance
if your struct will be too big for BTF: you can just add members to it,
and libctf will figure it out and upgrade the dict as needed, or tell you
it can't if you've forbidden such things.)
We take advantage of this to merge a couple of very similar functions,
saving a bit of code.
Nick Alcock [Thu, 24 Apr 2025 15:11:53 +0000 (16:11 +0100)]
libctf: create: DTD addition and deletion; ctf_rollback
DTD deletion changes mostly relate to the changes to the ctf_dtdef_t, but
also we no longer nede to have special handling for forwards (we can just
use ctf_type_kind_forwarded like everyone else).
Rollback no longer needs to delete things by hand (it hasn't needed to for
years): it can just call ctf_dtd_delete.
ctf_add_generic changes substantially, mostly to allow for the ctf_dtdef_t
changes. Rather than returning a type ID it now returns the DTD it just
allocated: it can also be asked to add some prefixes, and return the first
prefix added (which may not be the first prefix in the type, because if it
is asked to add a non-root-visible type it will additionally allocate a
CTF_K_CONFLICTING prefix to encode that).
Finally, duplicate name detection is suppressed for type and decl tags.
Variable handling in BTF and CTFv4 works quite differently from in CTFv3.
Rather than a separate section containing sorted, bsearchable variables,
they are simply named entities like types, stored in CTF_K_VARs.
As a first stage towards migrating to this, delete most references to
the ctf_varent_t and ctf_dvdef_t, including the DVD lookup code, all
the linking code, and quite a lot of the serialization code.
Note: CTF_LINK_OMIT_VARIABLES_SECTION, and the whole "delete variables that
already exist in the symtypetabs section" stuff, has yet to be
reimplemented. We can implement CTF_LINK_OMIT_VARIABLES_SECTION by simply
excising all CTF_K_VARs at deduplication time if requested. (Note:
symtypetabs should still point directly at the type, not at the CTF_K_VAR.)
(Symtypetabs in general need a bit more thought -- perhaps we can now store
them in a separate .ctf.symtypetab section with its own little four-entry
header for the symtypetabs and their indexes, making .ctf even more like
.BTF; the only difference would then be that .ctf could include prefix
types, CTF_K_FLOAT, and external string refs. For later discussion.)
We also add ctf_lookup_by_kind() at this stage (because it is hopelessly
diff-entangled with ctf_lookup_variable): this looks up a type of a
particular kind, without needing a per-kind lookup function for it,
nor needing to hack around adding string prefixes (so you can do
ctf_lookup_by_kind (fp, CTF_K_STRUCT, "foo") rather than having to
do ctf_lookup_by_name (fp, "struct foo"): often this is more convenient, and
anything that reduces string buffer manipulation in C is good.)
Nick Alcock [Thu, 24 Apr 2025 14:48:16 +0000 (15:48 +0100)]
libctf: create: vlen growth and prefix addition (NEEDS REVIEW)
This commit modifies ctf_grow_vlen to account for the recent changes to
ctf_dtdef_t, and adds a new ctf_add_prefix function to add a prefix to an
existing type, moving the dtd_data and dtd_vlen up accordinly.
It deserves close review, since this is probably the single greatest bug
cluster in libctf: the number of times I added to a variable of type
ctf_type_t and assumed it would move it in bytes rather than ctf_type_t
units is hard to believe.
Nick Alcock [Thu, 24 Apr 2025 14:43:57 +0000 (15:43 +0100)]
libctf: types: struct/union member querying and iteration
This commit revises ctf_member_next, ctf_member_iter, ctf_member_count, and
ctf_member_info for the new CTFv4 world. This also pulls in a bunch of
infrastructure used by most of the type querying functions, and fundamental
changes to the way DTD records are represented in libctf (ctf-create not yet
adjusted). Other type querying functions affected by changes in struct
representation are also changed.
There are some API changes here: new bit-width fields in ctf_member_f,
ctf_membinfo_t and ctf_member_next, and a fix to the type of the offset in
ctf_member_f, ctf_membinfo_t and and ctf_member_count. (ctf_member_next got
the offset type right already.)
ctf_member_f also gets a new ctf_dict_t arg so that you can actually use
the member type it passes in without having to package up and pass in the
dict type yourself (a frequent need). This change is later echoed in most
of the rest of the *_f typedefs.
typedef struct ctf_membinfo
{
ctf_id_t ctm_type; /* Type of struct or union member. */
- unsigned long ctm_offset; /* Offset of member in bits. */
+ size_t ctm_offset; /* Offset of member in bits. */
+ int ctm_bit_width; /* Width of member in bits: -1: not bitfield */
} ctf_membinfo_t;
-typedef int ctf_member_f (const char *name, ctf_id_t membtype,
- unsigned long offset, void *arg);
+typedef int ctf_member_f (ctf_dict_t *, const char *name, ctf_id_t membtype,
+ size_t offset, int bit_width, void *arg);
extern ssize_t ctf_member_next (ctf_dict_t *, ctf_id_t, ctf_next_t **,
const char **name, ctf_id_t *membtype,
- int flags);
+ int *bit_width, int flags);
The DTD changes are that where before the ctf_dtdef_t had a dtd_data which
was the ctf_type_t type node for a type, and a separate dtd_vlen which was
the vlen buffer which (in the final serialized representation) would
directly follow that type, now it has one single buffer, dtd_buf, which
consists of a stream of one or more ctf_type_t nodes, followed by a vlen,
as it will appear in the final serialized form. This buffer has internal
pointers into it: dtd_data is a pointer to the last ctf_type_t in the stream
(the true type node, after all prefixes), and dtd_vlen is a pointer to the
vlen (precisely one ctf_type_t after the dtd_data). This representation is
nice because it means there is even less distinction between a dynamic type
added by ctf_add_*() and a static one read directly out of a dict: you can
traverse the entire type without caring where it came from, simplifying
most of the type querying functions.
(There are a few more things in there which will be useful mostly when
adding new types: their uses will be seen later.)
Two new nontrivial functions exist (one of which is annoyingly tangled up in
the diff, sorry about that): ctf_find_prefix, which hunts down a given
prefix (if it exists) among the possibly many that may exist on a type (so
you can ask it to find the CTF_K_BIG prefix for a type if it exists, and
it'll return you a pointer to its ctf_type_t record), and ctf_vlen, which
you hand a type ID and its ctf_type_t *, and it gives you back a pointer to
its vlen and tells you how long it is. (This is one of only two places left
in ctf-types.c which cares whether a type is dynamic or not. The other has
yet to be added). Almost every function in ctf-types.c will end up calling
ctf_lookup_by_id and ctf_vlen in turn.
ctf_next_t has changed significantly: the ctn_type member is split in two so
that we can tell whether a given iterator works using types or indexes, and
we gain the ability to iterate over enum64s, DTDs themselves, and datasecs
(most of this will only be used in later commits).
The old internal function ctf_struct_member, which handled the distinction
between ctf_member_t and ctf_lmember_t, is gone. Instead we have new code
that handles the different representation of bitfield versus non-bitfield
structs and unions, and more code to handle the different representation of
CTF_K_BIG structs and unions (their offsets are the distance from the last
offset, rather than the distance from the start of the structure).
Nick Alcock [Thu, 24 Apr 2025 13:58:50 +0000 (14:58 +0100)]
libctf: CTFv4: type opening
The majority of this commit rejigs the core type table opening
code for CTFv4: there are a few ancillary bits it drags in,
indicated below.
The internal definition of a child dict (that may not have type or string
lookups performed in it until ctf_open time) used to be 'has a
cth_parent_name', but since BTF doesn't have one of those at all, we add
an additional check: a dict the first byte of whose strtab is not 0 must
be a child. (If *either* is true, this is a child dict, which allows for
the possibility of CTF dicts with non-deduplicated strtabs -- thus with
leading \0's -- to exist in future.)
The initial sweep through the type table in init_static_types (to size
the name-table lookup hashes) also now checks for various types which
indicate that this must be a CTF dict, in addition to being adjusted
to cater for new CTFv4 representations of things like forwards. (At
this early stage, we cannot rely on the functions in ctf-type.c to
abstract over this for us.)
We make some new hashtables for new namespace-like things: datasecs
and type and decl tags.
The main name-population loop in init_static_types_names_internal
takes prefixes into account, looking for the name on the suffix type
(where the name is always found). LSTRUCT handling is removed (they
no longer exist); ENUM64s, enum forwards, VARs, datasecs, and type
and decl tags get their names suitably populated. Some buggy code
which tried to populate the name tables for cvr-quals (which are
nameless) was dropped.
We add an extra pass which traverses all datasecs and keeps track of which
datasec each var is instantiated in (if any) in a new ctf_var_datasecs hash
table. (This uses a number of type-querying functions which don't yet
exist: they'll be added in the upcoming commits.)
We handle the type 0 == void case by pointing the first element of
ctf_txlate at a type read in named "void" (making type 0 an alias to it),
or, if one doesn't exist, creating a new one (outside the type table and dtd
arrays), and pointing type 0 at that. Since it is numbered 0 and not in the
type table or dtd arrays, it will never be written out at serialization
time, but since it is *present*, libctf consumers who expect the void type
to have an integral definition rather than being a magic number will get
what they expect.
Nick Alcock [Thu, 24 Apr 2025 13:44:12 +0000 (14:44 +0100)]
libctf: CTFv4: core opening (other than the type table)
This commit modifies the core opening code to handle opening CTFv4 and BTF.
Much of the back-compatibility side is left for later and is currently
untested, as is the type table side of things.
We keep the v3 header (if any) stashed away in ctf_dict_t.ctf_v3_header, for
the sake of the CTF dumper; we "upgrade" the BTF header to CTF (so that the
rest of the code can ignore the distinction, and so that you can do CTFish
things like adding symtypetab entries even to things opened as BTF), but
keep note of the fact that it was opened as BTF in ctf_dict_t.ctf_opened_btf,
so that things like ctf_import can allow for the absence of the various
parent-length fields.
A couple of ctf_dict_t fields are renamed for consistency with the headers'
names for them (ctf_parname becomes ctf_parent_name; ctf_dynparname becomes
ctf_dyn_parent_name; ctf_cuname becomes ctf_cu_name). Not all users are yet
adjusted.
Nick Alcock [Thu, 24 Apr 2025 13:20:28 +0000 (14:20 +0100)]
libctf, include: debuggability improvements
When --enable-libctf-hash-debugging is on, make ctf_set_errno and
ctf_set_typed_errno into real functions, not inlines, so you can
drop breakpoints on them. Since we are breaking API, also move
ECTF_NEXT_END to the start of the _CTF_ERRORS array, so you can
check for real (non-ECTF_NEXT_END) errors in breakpooints on those
functions by checking for err > 1000.
Nick Alcock [Thu, 24 Apr 2025 13:17:19 +0000 (14:17 +0100)]
libctf: ctf-lookup: support prefixes in ctf_lookup_by_id
ctf_lookup_by_id now has a new optional suffix argument, which,
if set, returns the suffix of a prefixed type: the ctf_type_t it
returns remains (as ever) the first one in the type (i.e. it
may be a prefix type). This is most convenient because the prefix
is the ctf_type_t that LCTF_KIND and other LCTF functions taking
ctf_type_t's expect.
Nick Alcock [Thu, 3 Apr 2025 14:21:47 +0000 (15:21 +0100)]
libctf: simplify ctf_txlate
Before now, this critical internal structure was an array mapping from a
type ID to the type index of the type with that ID. This was critical for
the old world in which ctf_update() reserialized the entire dict, so things
moved around in memory all the time: but these days, a ctf_type_t * never
moves after creation, so we can just make ctf_txlate an array of ctf_type_t *
and be done with it.
This lets us point type indexes anywhere in memory, not just to entries
in the ctf_buf, which means we can have synthetic ones for various purposes.
And we will.
Nick Alcock [Thu, 24 Apr 2025 12:50:38 +0000 (13:50 +0100)]
libctf: adapt core dictops for v4 and prefix types
The heart of libctf's reading code is the ctf_dictops_t and the functions it
provides for reading various things no matter what the CTF version in use:
these are called via LCTF_*() macros that translate into calls into the
dictops.
The introduction of prefix types in v4 requires changes here: in particular,
we want the ability to get the type kind of whatever ctf_type_t we are
looking at (the 'unprefixed' kind), as well as the ability to get the type
kind taking prefixes into account: and more generally we want the ability
to both look at a given prefix and look at the type as a whole. So several
ctf_dictops_t entries are added for this (ctfo_get_prefixed_kind,
ctfo_get_prefixed_vlen).
This means API changes (no callers yet adjusted, it'll happen as we go),
because the existing macros were mostly called with e.g. a ctt_info value
and returned a type kind, while now we need to be called with the actual
ctf_type_t itself, so we can possibly walk beyond it to find the real type
record. ctfo_get_vbytes needs adjusting for this.
We also add names to most of the ctf_type_t parameters, because suddenly we
can have up to three of them: one relating to the first entry in the type
record (which may be a prefix, usually called 'prefix'), one relating to the
true type record (which may be a suffix, so usually called 'suffix'), and
one possibly relating to some intermediate record if we have multiple
prefixes (usually called 'tp').
There is one horrible special case in here: the vlen of the new
CTF_K_FUNC_LINKAGE kind (equivalent to BTF_KIND_FUNC) is always zero: it
reuses the vlen field to encode the linkage (!). BTF is rife with ugly
hacks like this.
Nick Alcock [Thu, 24 Apr 2025 12:46:08 +0000 (13:46 +0100)]
libctf: don't warn about unused fp in ctf_assert
When hash debugging is enabled and NDEBUG is not set, ctf_assert()
translates into a true assert(). Don't leave the fp parameter
unused in this case (which can cause compiler errors when -Werror
is also on).
Nick Alcock [Thu, 24 Apr 2025 12:44:36 +0000 (13:44 +0100)]
libctf: split out compatibility code
The compatibility-opening code is quite voluminous, and is stuck right in
the middle of ctf-open.c, rather interfering with maintenance. Split it
out into a new ctf-open-compat.c. (Since it is not yet upgraded to support
v4, the new file is not added to the build system yet: indeed, even the
calls to it haven't been diked out at this stage.)
Nick Alcock [Thu, 24 Apr 2025 12:32:57 +0000 (13:32 +0100)]
include, libctf, binutils: drop labels
These have never been implemented properly and don't work with the linker or
deduplicator: BTF has nothing like them, so the default assumption should be
that we drop them. If we need something like them in future, we can add
them back (which we do not expect).
Quite a bit of label detritus is left in libctf after this: it's tied up
with later changes so will be removed as part of later commits. (Because
the entire thing is disabled, the non-compilability of this intermediate
state is not a concern.)
Nick Alcock [Thu, 24 Apr 2025 12:20:53 +0000 (13:20 +0100)]
include, libctf: header and soname changes for CTFv4
These changes bump the current file format version to CTF_VERSION_4, and
introduce a new VERSION_5 identical with it to get the version integer and
the name identical again. A great many changes are made to account for
the changes to handle CTFv4 (which is a BTF superset).
libctf will not compile after these changes, which is why it's been diked
out of the build system and forced-off until the series is complete.
Because all the CTF_K constants have changed values, this is necessarily an
ABI break: add a #define to make picking up this break at compile time
obvious.
Note that the ABI has broken by bumping the soname (deriving it now from
libctf/libtool-version) and folding all newer symbols in the symbol version
file into a new LIBCTF_2.0 version, which is now the only exported version.
Nick Alcock [Wed, 5 Mar 2025 19:01:12 +0000 (19:01 +0000)]
libctf: don't include cv-quals or pointers in the name table
Even if these types have a name recorded against them, we should
ignore it. They don't have names, full stop.
libctf/ChangeLog:
* ctf-open.c (init_static_types): Drop nameless types when sizing
the name table.
(init_static_types_names_internal): Never pass in their name.
Nick Alcock [Thu, 27 Feb 2025 19:27:00 +0000 (19:27 +0000)]
types: add some more error checking
A few places with inadequate error checking have fallen out of the
ctf_id_t work:
- ctf_add_slice doesn't make sure that the type it is slicing
actually exists
- ctf_add_member_offset doesn't check that the type of the member
exists (though it will often fail if it doesn't, it doesn't
explicitly check, so if you're unlucky it can sometimes succeed,
giving you a corrupted dict)
- ctf_type_encoding doesn't check whether its slied type exists:
it should verify it so it can return a decent error, rather than
a thoroughly misleading one
- ctf_type_compat has the same problem with respect to both of its
arguments. It would definitely be nicer if we could call
ctf_type_compat and just get a boolean answer, but it's not
clear to me whether a type can be said to be compatible *or*
incompatible with a nonexistent one, and we should probably alert
the users to a likely bug regardless. C error checking, sigh...
Nick Alcock [Sun, 16 Feb 2025 19:55:11 +0000 (19:55 +0000)]
libctf: consecutive ctf_id_t assignment
This change modifies type ID assignment in CTF so that it works like BTF:
rather than flipping the high bit on for types in child dicts, types ascend
directly from IDs in the parent to IDs in the child, without interruption
(so type 0x4 in the parent is immediately followed by 0x5 in all children).
Doing this while retaining useful semantics for modification of parents is
challenging. By definition, child type IDs are not known until the parent
is written out, but we don't want to find ourselves constrained to adding
types to the parent in one go, followed by all child types: that would make
the deduplicator a nightmare and would frankly make the entire ctf_add*()
interface next to useless: all existing clients that add types at all
add types to both parents and children without regard for ordering, and
breaking that would probably necessitate redesigning all of them.
So we have to be a litle cleverer.
We approach this the same way as we approach strings in the recent refs
rework: if a parent has children attached (or has ever had them attached
since it was created or last read in), any new types created in the parent
are assigned provisional IDs starting at the very top of the type space and
working down. (Their indexes in the internal libctf arrays remain
unchanged, so we don't suddenly need multigigabyte indexes!). At writeout
(preserialization) time, we traverse the type table (and all other table
containing type IDs) and assign refs to every type ID in exactly the same
way we assign refs to every string offset (just a different set of refs --
we don't want to update type IDs with string offset values!).
For a parent dict with children, these refs are real entities in memory:
pointers to the memory locations where type IDs are stored, tracked in the
DTD of each type. As we traverse the type table, we assign real IDs to each
type (by simple incrementation), storing those IDs in a new dtd_final_type
field in the DTD for each type. Once the type table and all other tables
containing type IDs are fully traversed, we update all the refs and
overwrite the IDs currently residing in each with the final IDs for each
type.
That fixes up IDs in the parent dict itself (including forward references in
structs and the like: that's why the ref updates only happen at the end);
but what about child dicts' references, both to parent types and to their
own? We add armouring to enforce that parent dicts are always serialized
before their children (which ctf-link.c already does, because it's a
precondition for strtab deduplication), and then arrange that when a ref is
added to a type whose ID has been assigned (has a dtd_final_type), we just
immediately do an update rather than storing a ref for later updating.
Since the parent is already serialized, all parent type IDs have a
dtd_final_type by this point, and all parent IDs in the children are
properly updated. The child types can now be renumbered now we now the
number of types in the parent, and their refs updated identically to what
was just done with the parent.
One wrinkle: before the child refs are updated, while we are working over
the child's type section, the type IDs in the child start from 1 (or
something like that), which might seem to overlap the parent IDs. But this
is not the case: when you serialize the parent, the IDs written out to disk
are changed, but the only change to the representation in memory is that we
remember a dtd_final_type for each type (and use it to update all the child
type refs): its ID in memory is the same as it always was, a nonoverlapping
provisional ID higher than any other valid ID. We enforce all of this by
asserting that when you add a ref to a type, the memory location that is
modified must be in the buffer being serialized: the code will not let you
accidentally modify the actual DTDs in memory.
We track the number of types in the parent in a new CTFv4 (not BTF) header
field (the dumper is updated): we will also use this to open CTFv3 child
dicts without change by simply declaring for them that the parent dict has
2^31 types in it (or 2^15, for v2 and below): the IDs in the children then
naturally come out right with no other changes needed. (Right now, opening
CTFv3 child dicts requires extra compatibility code that has not been
written, but that code will no longer need to worry about type ID
differences.)
Various things are newly forbidden:
- you cannot ctf_import() a child into a parent if you already ctf_add()ed
types to the child, because all its IDs would change (and since you
already cannot ctf_add() types to a child that hasn't had its parent
imported, this in practice means only that ctf_create() must be followed
immediately by a ctf_import() if this is a new child, which all sane
clients were doing anyway).
- You cannot import a child into a parent which has the wrong number of
(non-provisional) types, again because all its IDs would be wrong:
because parents only add types in the provisional space if children are
attached to it, this would break the not unknown case of opening an
archive, adding types to the parent, and only then importing children
into it, so we add a special case: archive members which are not children
in an archive with more than one member always pretend to have at least
one child, so type additions in them are always provisional even before
you ctf_import anything. In practice, this does exactly what we want,
since all archives so far are created by the linker and have one parent
and N children of that parent.
Because this introduces huge gaps between index and type ID for provisional
types, some extra assertions are added to ensure that the internal
ctf_type_to_index() is only ever called on types in the current dict (never
a parent dict): before now, this was just taken on trust, and it was often
wrong (which at best led to wrong results, as wrong array indexes were used,
and at worst to a buffer overflow). When hash debugging is on (suggesting
that the user doesn't mind expensive checks), every ctf_type_to_index()
triggers a ctf_index_to_type() to make sure that the operations are proper
inverses.
Lots and lots of tests are added to verify that assignment works and that
updating of every type kind works fine -- existing tests suffice for
type IDs in the variable and symtypetab sections.
The ld-ctf tests get a bunch of largely display-based updates: various
tests refer to 0x8... type IDs, which no longer exist, and because the
IDs are shorter all the spacing and alignment has changed.
Nick Alcock [Sun, 16 Feb 2025 19:53:40 +0000 (19:53 +0000)]
libctf: fix ctf_type_pointer on parent dicts, etc
Before now, ctf_type_pointer was crippled: it returned some type (if any)
that was a pointer to the type passed in, but only if both types were in the
current dict: if either (or both) was in the parent dict, it said there was
no pointer though there was. This breaks real users: it's past time to lift
the restriction.
Nick Alcock [Sun, 16 Feb 2025 19:41:08 +0000 (19:41 +0000)]
libctf: don't call ctf_type_to_index with types in other dicts
ctf_type_to_index has never given meaningful results when called with dicts
in which the specified type does not reside: its only purpose is to return
the offset in various dict-internal arrays in which this type is located, so
doing so makes no sense.
Stop ctf_lookup_by_name and refresh_pptrtab (which it calls) from doing so.
As part of this, refactor ctf_lookup_by_name so that it's a bit less
repetitive and squirrelly.