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).
This commit is contained in:
Nick Alcock
2025-04-24 16:47:14 +01:00
parent cd8ea31666
commit 20e6f72dc7
5 changed files with 223 additions and 88 deletions

View File

@@ -871,7 +871,9 @@ extern int ctf_add_enumerator (ctf_dict_t *, ctf_id_t, const char *, int);
/* Add a member to a struct or union, either at the next available offset (with /* Add a member to a struct or union, either at the next available offset (with
suitable padding for the alignment) or at a specific offset, and possibly suitable padding for the alignment) or at a specific offset, and possibly
with a specific encoding (creating a slice for you). Offsets need not be with a specific encoding (creating a slice for you). Offsets need not be
unique, and need not be added in ascending order. */ unique, and need not be added in ascending order. ctf_add_member_bitfield
with a nonzero bit_width will fail unless the struct was created with
CTF_ADD_STRUCT_BITFIELDS. */
extern int ctf_add_member (ctf_dict_t *, ctf_id_t, const char *, ctf_id_t); extern int ctf_add_member (ctf_dict_t *, ctf_id_t, const char *, ctf_id_t);
extern int ctf_add_member_offset (ctf_dict_t *, ctf_id_t, const char *, extern int ctf_add_member_offset (ctf_dict_t *, ctf_id_t, const char *,
@@ -879,6 +881,10 @@ extern int ctf_add_member_offset (ctf_dict_t *, ctf_id_t, const char *,
extern int ctf_add_member_encoded (ctf_dict_t *, ctf_id_t, const char *, extern int ctf_add_member_encoded (ctf_dict_t *, ctf_id_t, const char *,
ctf_id_t, unsigned long, ctf_id_t, unsigned long,
const ctf_encoding_t); const ctf_encoding_t);
extern 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);
extern int ctf_add_variable (ctf_dict_t *, const char *, ctf_id_t); extern int ctf_add_variable (ctf_dict_t *, const char *, ctf_id_t);

View File

@@ -1276,18 +1276,21 @@ ctf_add_enumerator (ctf_dict_t *fp, ctf_id_t enid, const char *name,
} }
int int
ctf_add_member_offset (ctf_dict_t *fp, ctf_id_t souid, const char *name, ctf_add_member_bitfield (ctf_dict_t *fp, ctf_id_t souid, const char *name,
ctf_id_t type, unsigned long bit_offset) ctf_id_t type, unsigned long bit_offset,
int bit_width)
{ {
ctf_dict_t *ofp = fp; ctf_dict_t *ofp = fp;
ctf_dict_t *tmp = fp; ctf_dict_t *tmp = fp;
ctf_dtdef_t *dtd = ctf_dtd_lookup (fp, souid); ctf_dtdef_t *dtd;
ctf_type_t *prefix;
ssize_t msize, malign, ssize; ssize_t msize, ssize;
uint32_t kind, vlen, root; uint32_t kind, kflag;
size_t vlen;
size_t i; size_t i;
int is_incomplete = 0; int is_incomplete = 0;
ctf_lmember_t *memb; ctf_member_t *memb;
if (fp->ctf_flags & LCTF_NO_STR) if (fp->ctf_flags & LCTF_NO_STR)
return (ctf_set_errno (fp, ECTF_NOPARENT)); return (ctf_set_errno (fp, ECTF_NOPARENT));
@@ -1306,53 +1309,71 @@ ctf_add_member_offset (ctf_dict_t *fp, ctf_id_t souid, const char *name,
fp = fp->ctf_parent; fp = fp->ctf_parent;
} }
dtd = ctf_dtd_lookup (fp, souid);
if (souid < fp->ctf_stypes) if (souid < fp->ctf_stypes)
return (ctf_set_errno (ofp, ECTF_RDONLY)); return (ctf_set_errno (ofp, ECTF_RDONLY));
if (dtd == NULL) if (dtd == NULL)
return (ctf_set_errno (ofp, ECTF_BADID)); return (ctf_set_errno (ofp, ECTF_BADID));
if ((ctf_lookup_by_id (&tmp, type)) == NULL) if ((ctf_lookup_by_id (&tmp, type, NULL)) == NULL)
return -1; /* errno is set for us. */ return -1; /* errno is set for us. */
if (name != NULL && name[0] == '\0') if (name != NULL && name[0] == '\0')
name = NULL; name = NULL;
kind = LCTF_INFO_KIND (fp, dtd->dtd_data.ctt_info); if ((prefix = (ctf_type_t *) ctf_find_prefix (fp, dtd->dtd_buf, CTF_K_BIG)) == NULL)
root = LCTF_INFO_ISROOT (fp, dtd->dtd_data.ctt_info); return (ctf_set_errno (ofp, ECTF_CORRUPT));
vlen = LCTF_INFO_VLEN (fp, dtd->dtd_data.ctt_info);
kind = LCTF_KIND (fp, prefix);
kflag = CTF_INFO_KFLAG (dtd->dtd_data->ctt_info);
vlen = LCTF_VLEN (fp, prefix);
if (kind != CTF_K_STRUCT && kind != CTF_K_UNION) if (kind != CTF_K_STRUCT && kind != CTF_K_UNION)
return (ctf_set_errno (ofp, ECTF_NOTSOU)); return (ctf_set_errno (ofp, ECTF_NOTSOU));
if (!kflag && bit_width != 0)
return (ctf_set_errno (ofp, ECTF_NOTBITSOU));
if (vlen == CTF_MAX_VLEN) if (vlen == CTF_MAX_VLEN)
return (ctf_set_errno (ofp, ECTF_DTFULL)); return (ctf_set_errno (ofp, ECTF_DTFULL));
if (ctf_grow_vlen (fp, dtd, sizeof (ctf_lmember_t) * (vlen + 1)) < 0) /* Figure out the offset of this field: all structures in DTDs
return (ctf_set_errno (ofp, ctf_errno (fp))); are CTF_K_BIG, which means their offsets are all encoded as
memb = (ctf_lmember_t *) dtd->dtd_vlen; distances from the last field's. */
if (bit_offset != (unsigned long) -1)
{
if (bit_offset < dtd->dtd_last_offset)
return (ctf_set_errno (ofp, ECTF_DESCENDING));
bit_offset -= dtd->dtd_last_offset;
}
memb = (ctf_member_t *) dtd->dtd_vlen;
if (name != NULL) if (name != NULL)
{ {
for (i = 0; i < vlen; i++) for (i = 0; i < vlen; i++)
if (strcmp (ctf_strptr (fp, memb[i].ctlm_name), name) == 0) if (strcmp (ctf_strptr (fp, memb[i].ctm_name), name) == 0)
return (ctf_set_errno (ofp, ECTF_DUPLICATE)); return (ctf_set_errno (ofp, ECTF_DUPLICATE));
} }
if ((msize = ctf_type_size (fp, type)) < 0 || if ((msize = ctf_type_size (fp, type)) < 0 ||
(malign = ctf_type_align (fp, type)) < 0) (ctf_type_align (fp, type)) < 0)
{ {
/* The unimplemented type, and any type that resolves to it, has no size /* The unimplemented type, and any type that resolves to it, has no size
and no alignment: it can correspond to any number of compiler-inserted and no alignment: it can correspond to any number of compiler-inserted
types. We allow incomplete types through since they are routinely types. We allow incomplete types through since they are routinely
added to the ends of structures, and can even be added elsewhere in added to the ends of structures, and can even be added elsewhere in
structures by the deduplicator. They are assumed to be zero-size with structures by the deduplicator and by the padding inserter below. They
no alignment: this is often wrong, but problems can be avoided in this are assumed to be zero-size with no alignment: this is often wrong, but
case by explicitly specifying the size of the structure via the _sized problems can be avoided in this case by explicitly specifying the size
functions. The deduplicator always does this. */ of the structure via the _sized functions. The deduplicator always
does this. */
msize = 0; msize = 0;
malign = 0;
if (ctf_errno (fp) == ECTF_NONREPRESENTABLE) if (ctf_errno (fp) == ECTF_NONREPRESENTABLE)
ctf_set_errno (fp, 0); ctf_set_errno (fp, 0);
else if (ctf_errno (fp) == ECTF_INCOMPLETE) else if (ctf_errno (fp) == ECTF_INCOMPLETE)
@@ -1361,95 +1382,130 @@ ctf_add_member_offset (ctf_dict_t *fp, ctf_id_t souid, const char *name,
return -1; /* errno is set for us. */ return -1; /* errno is set for us. */
} }
memb[vlen].ctlm_name = ctf_str_add (fp, name); /* Figure out the right offset for naturally-aligned types, if need be,
memb[vlen].ctlm_type = type; and insert additional unnamed members as needed. */
if (memb[vlen].ctlm_name == 0 && name != NULL && name[0] != '\0')
return -1; /* errno is set for us. */
if (kind == CTF_K_STRUCT && vlen != 0) if (kind == CTF_K_UNION || vlen == 0)
{ {
bit_offset = 0;
ssize = ctf_get_ctt_size (fp, prefix, NULL, NULL);
ssize = MAX (ssize, msize);
}
else /* Subsequent struct member. */
{
size_t bound;
ssize_t off;
int added_padding = 0;
if (bit_offset == (unsigned long) - 1) if (bit_offset == (unsigned long) - 1)
{ {
/* Natural alignment. */ /* Natural alignment. */
ctf_id_t ltype = ctf_type_resolve (fp, memb[vlen - 1].ctlm_type);
size_t off = CTF_LMEM_OFFSET(&memb[vlen - 1]);
ctf_encoding_t linfo;
ssize_t lsize;
/* Propagate any error from ctf_type_resolve. If the last member was
of unimplemented type, this may be -ECTF_NONREPRESENTABLE: we
cannot insert right after such a member without explicit offset
specification, because its alignment and size is not known. */
if (ltype == CTF_ERR)
return -1; /* errno is set for us. */
if (is_incomplete) if (is_incomplete)
{ {
ctf_err_warn (ofp, 1, ECTF_INCOMPLETE, ctf_err_warn (ofp, 1, ECTF_INCOMPLETE,
_("ctf_add_member_offset: cannot add member %s of " _("ctf_add_member: cannot add member %s of "
"incomplete type %lx to struct %lx without " "incomplete type %lx to struct %lx without "
"specifying explicit offset\n"), "specifying explicit offset\n"),
name ? name : _("(unnamed member)"), type, souid); name ? name : _("(unnamed member)"), type, souid);
return (ctf_set_errno (ofp, ECTF_INCOMPLETE)); return (ctf_set_errno (ofp, ECTF_INCOMPLETE));
} }
if (ctf_type_encoding (fp, ltype, &linfo) == 0) if ((off = ctf_type_align_natural (fp, memb[vlen - 1].ctm_type,
off += linfo.cte_bits; type, dtd->dtd_last_offset)) < 0)
else if ((lsize = ctf_type_size (fp, ltype)) > 0)
off += lsize * CHAR_BIT;
else if (lsize == -1 && ctf_errno (fp) == ECTF_INCOMPLETE)
{ {
const char *lname = ctf_strraw (fp, memb[vlen - 1].ctlm_name); if (ctf_errno (fp) == ECTF_INCOMPLETE)
{
const char *lname = ctf_strraw (fp, memb[vlen - 1].ctm_name);
ctf_err_warn (ofp, 1, ECTF_INCOMPLETE, ctf_err_warn (ofp, 1, ECTF_INCOMPLETE,
_("ctf_add_member_offset: cannot add member %s of " _("ctf_add_member_offset: cannot add member %s "
"type %lx to struct %lx without specifying " "of type %lx to struct %lx without "
"explicit offset after member %s of type %lx, " "specifying explicit offset after member %s"
"which is an incomplete type\n"), "of type %x, which is an incomplete type\n"),
name ? name : _("(unnamed member)"), type, souid, name ? name : _("(unnamed member)"), type, souid,
lname ? lname : _("(unnamed member)"), ltype); lname ? lname : _("(unnamed member)"),
return (ctf_set_errno (ofp, ECTF_INCOMPLETE)); memb[vlen -1].ctm_type);
}
return (ctf_set_errno (ofp, ctf_errno (fp)));
} }
/* Round up the offset of the end of the last member to /* Convert the offset to a gap-since-the-last. */
the next byte boundary, convert 'off' to bytes, and off -= dtd->dtd_last_offset;
then round it up again to the next multiple of the bit_offset = off;
alignment required by the new member. Finally,
convert back to bits and store the result in
dmd_offset. Technically we could do more efficient
packing if the new member is a bit-field, but we're
the "compiler" and ANSI says we can do as we choose. */
off = roundup (off, CHAR_BIT) / CHAR_BIT;
off = roundup (off, MAX (malign, 1));
memb[vlen].ctlm_offsethi = CTF_OFFSET_TO_LMEMHI (off * CHAR_BIT);
memb[vlen].ctlm_offsetlo = CTF_OFFSET_TO_LMEMLO (off * CHAR_BIT);
ssize = off + msize;
} }
/* Insert as many nameless members as needed. */
if (kflag)
bound = CTF_MAX_BIT_OFFSET;
else else
{ bound = CTF_MAX_SIZE;
/* Specified offset in bits. */
memb[vlen].ctlm_offsethi = CTF_OFFSET_TO_LMEMHI (bit_offset); while (bit_offset > bound)
memb[vlen].ctlm_offsetlo = CTF_OFFSET_TO_LMEMLO (bit_offset); {
ssize = ctf_get_ctt_size (fp, &dtd->dtd_data, NULL, NULL); added_padding = 1;
ssize = MAX (ssize, ((signed) bit_offset / CHAR_BIT) + msize);
off = bound;
if (kflag)
off = CTF_MEMBER_BIT_OFFSET (bound);
if (ctf_add_member_bitfield (fp, souid, "", 0, off, 0) < 0)
return -1; /* errno is set for us. */
bit_offset =- off;
} }
off = bit_offset;
if (kflag)
off = CTF_MEMBER_BIT_OFFSET (off);
/* Possibly hunt down the prefix and member list again: they may have been
moved by the realloc()s involved in field additions. */
if (added_padding
&& (prefix = (ctf_type_t *) ctf_find_prefix (fp, dtd->dtd_buf, CTF_K_BIG)) == NULL)
return (ctf_set_errno (ofp, ECTF_CORRUPT));
vlen = LCTF_VLEN (fp, prefix);
memb = (ctf_member_t *) dtd->dtd_vlen;
bit_offset = off;
ssize = ctf_get_ctt_size (fp, prefix, NULL, NULL);
ssize = MAX (ssize, ((signed) ((bit_offset + dtd->dtd_last_offset)) / CHAR_BIT) + msize);
} }
if (kflag)
memb[vlen].ctm_offset = CTF_MEMBER_MAKE_BIT_OFFSET (bit_width, bit_offset);
else else
{ memb[vlen].ctm_offset = bit_offset;
memb[vlen].ctlm_offsethi = 0;
memb[vlen].ctlm_offsetlo = 0;
ssize = ctf_get_ctt_size (fp, &dtd->dtd_data, NULL, NULL);
ssize = MAX (ssize, msize);
}
dtd->dtd_data.ctt_size = CTF_LSIZE_SENT; vlen = LCTF_VLEN (fp, prefix);
dtd->dtd_data.ctt_lsizehi = CTF_SIZE_TO_LSIZE_HI (ssize);
dtd->dtd_data.ctt_lsizelo = CTF_SIZE_TO_LSIZE_LO (ssize); if (ctf_grow_vlen (fp, dtd, sizeof (ctf_member_t) * (vlen + 1)) < 0)
dtd->dtd_data.ctt_info = CTF_TYPE_INFO (kind, root, vlen + 1); return (ctf_set_errno (ofp, ctf_errno (fp)));
dtd->dtd_vlen_size += sizeof (ctf_member_t);
/* Hunt down the prefix and member list yet again, since they may have been
reallocated by ctf_grow_vlen. */
if ((prefix = (ctf_type_t *) ctf_find_prefix (fp, dtd->dtd_buf, CTF_K_BIG)) == NULL)
return (ctf_set_errno (ofp, ECTF_CORRUPT));
memb = (ctf_member_t *) dtd->dtd_vlen;
memb[vlen].ctm_name = ctf_str_add (fp, name);
memb[vlen].ctm_type = type;
if (memb[vlen].ctm_name == 0 && name != NULL && name[0] != '\0')
return -1; /* errno is set for us. */
dtd->dtd_data->ctt_size = CTF_SIZE_TO_LSIZE_LO (ssize);
prefix->ctt_size = CTF_SIZE_TO_LSIZE_HI (ssize);
dtd->dtd_data->ctt_info = CTF_TYPE_INFO (kind, kflag, CTF_VLEN_TO_VLEN_LO(vlen + 1));
prefix->ctt_info = CTF_TYPE_INFO (CTF_K_BIG, 0, CTF_VLEN_TO_VLEN_HI(vlen + 1));
dtd->dtd_last_offset += bit_offset;
return 0; return 0;
} }
@@ -1466,15 +1522,31 @@ ctf_add_member_encoded (ctf_dict_t *fp, ctf_id_t souid, const char *name,
if (dtd == NULL) if (dtd == NULL)
return (ctf_set_errno (fp, ECTF_BADID)); return (ctf_set_errno (fp, ECTF_BADID));
kind = LCTF_INFO_KIND (fp, dtd->dtd_data.ctt_info); kind = LCTF_KIND (fp, dtd->dtd_buf);
if ((kind != CTF_K_INTEGER) && (kind != CTF_K_FLOAT) && (kind != CTF_K_ENUM)) if ((kind != CTF_K_INTEGER) && (kind != CTF_K_FLOAT) && (kind != CTF_K_ENUM))
return (ctf_set_errno (fp, ECTF_NOTINTFP)); return (ctf_set_errno (fp, ECTF_NOTINTFP));
/* Create a slice if need be. */
if (encoding.cte_offset != 0 ||
encoding.cte_format != 0 ||
(encoding.cte_bits != 0 && CTF_INFO_KFLAG (dtd->dtd_data->ctt_info) == 0))
{
if ((type = ctf_add_slice (fp, CTF_ADD_NONROOT, otype, &encoding)) == CTF_ERR) if ((type = ctf_add_slice (fp, CTF_ADD_NONROOT, otype, &encoding)) == CTF_ERR)
return -1; /* errno is set for us. */ return -1; /* errno is set for us. */
}
else
type = otype;
return ctf_add_member_offset (fp, souid, name, type, bit_offset); return ctf_add_member_bitfield (fp, souid, name, type, bit_offset, 0);
}
int
ctf_add_member_offset (ctf_dict_t *fp, ctf_id_t souid, const char *name,
ctf_id_t type, unsigned long bit_offset)
{
return ctf_add_member_bitfield (fp, souid, name, type, bit_offset, 0);
} }
int int

View File

@@ -812,6 +812,8 @@ extern char *ctf_str_append_noerr (char *, const char *);
extern ctf_id_t ctf_type_resolve_unsliced (ctf_dict_t *, ctf_id_t); extern ctf_id_t ctf_type_resolve_unsliced (ctf_dict_t *, ctf_id_t);
extern int ctf_type_kind_unsliced (ctf_dict_t *, ctf_id_t); extern int ctf_type_kind_unsliced (ctf_dict_t *, ctf_id_t);
extern ssize_t ctf_type_align_natural (ctf_dict_t *fp, ctf_id_t prev_type,
ctf_id_t type, ssize_t bit_offset);
_libctf_printflike_ (1, 2) _libctf_printflike_ (1, 2)
extern void ctf_dprintf (const char *, ...); extern void ctf_dprintf (const char *, ...);

View File

@@ -1094,6 +1094,60 @@ ctf_type_size (ctf_dict_t *fp, ctf_id_t type)
} }
} }
/* Determine the natural alignment (in bits) for some type, given the previous
TYPE at BIT_OFFSET.
Not public because doing this entirely right requires arch-dependent
attention: this is just to reduce code repetition in ctf-create.c.
Errors if the TYPE or PREV_TYPE are unsuitable for automatic alignment
determination: in particular, you can insert incomplete or nonrepresentable
TYPEs, but PREV_TYPE cannot be incomplete or nonrepresentable. */
ssize_t
ctf_type_align_natural (ctf_dict_t *fp, ctf_id_t prev_type,
ctf_id_t type, ssize_t bit_offset)
{
ctf_encoding_t info;
ssize_t size;
ssize_t align;
if ((prev_type = ctf_type_resolve (fp, prev_type)) == CTF_ERR)
return -1; /* errno is set for us. */
if ((align = ctf_type_align (fp, type)) < 0)
{
/* Ignore incompleteness and nonrepresentability of the type we're
inserting: just assume such a type has no alignment constraints of its
own. */
if (ctf_errno (fp) == ECTF_NONREPRESENTABLE
|| ctf_errno (fp) == ECTF_INCOMPLETE)
align = 0;
else
return -1; /* errno is set for us. */
}
if (ctf_type_encoding (fp, prev_type, &info) == 0)
bit_offset += info.cte_bits;
else if ((size = ctf_type_size (fp, prev_type)) > 0)
bit_offset += size * CHAR_BIT;
else if (size < 0)
return -1; /* errno is set for us. */
/* Round up the offset of the end of the last member to the next byte
boundary, convert 'off' to bytes, and then round it up again to the next
multiple of the alignment required by the new member. Finally, convert
back to bits and store the result. Technically we could do more efficient
packing within structs if the new member is a bit-field, but we're the
"compiler" and the Standard says we can do as we choose. */
bit_offset = roundup (bit_offset, CHAR_BIT) / CHAR_BIT;
bit_offset = roundup (bit_offset, MAX (align, 1));
bit_offset *= CHAR_BIT;
return bit_offset;
}
/* Resolve the type down to a base type node, and then return the alignment /* Resolve the type down to a base type node, and then return the alignment
needed for the type storage in bytes. needed for the type storage in bytes.

View File

@@ -132,6 +132,7 @@ LIBCTF_2.0 {
ctf_add_member_offset; ctf_add_member_offset;
ctf_add_member_encoded; ctf_add_member_encoded;
ctf_add_variable; ctf_add_variable;
ctf_add_member_bitfield;
ctf_set_array; ctf_set_array;