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
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
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_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 *,
ctf_id_t, unsigned long,
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);

View File

@@ -1276,18 +1276,21 @@ ctf_add_enumerator (ctf_dict_t *fp, ctf_id_t enid, const char *name,
}
int
ctf_add_member_offset (ctf_dict_t *fp, ctf_id_t souid, const char *name,
ctf_id_t type, unsigned long bit_offset)
ctf_add_member_bitfield (ctf_dict_t *fp, ctf_id_t souid, const char *name,
ctf_id_t type, unsigned long bit_offset,
int bit_width)
{
ctf_dict_t *ofp = 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;
uint32_t kind, vlen, root;
ssize_t msize, ssize;
uint32_t kind, kflag;
size_t vlen;
size_t i;
int is_incomplete = 0;
ctf_lmember_t *memb;
ctf_member_t *memb;
if (fp->ctf_flags & LCTF_NO_STR)
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;
}
dtd = ctf_dtd_lookup (fp, souid);
if (souid < fp->ctf_stypes)
return (ctf_set_errno (ofp, ECTF_RDONLY));
if (dtd == NULL)
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. */
if (name != NULL && name[0] == '\0')
name = NULL;
kind = LCTF_INFO_KIND (fp, dtd->dtd_data.ctt_info);
root = LCTF_INFO_ISROOT (fp, dtd->dtd_data.ctt_info);
vlen = LCTF_INFO_VLEN (fp, dtd->dtd_data.ctt_info);
if ((prefix = (ctf_type_t *) ctf_find_prefix (fp, dtd->dtd_buf, CTF_K_BIG)) == NULL)
return (ctf_set_errno (ofp, ECTF_CORRUPT));
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)
return (ctf_set_errno (ofp, ECTF_NOTSOU));
if (!kflag && bit_width != 0)
return (ctf_set_errno (ofp, ECTF_NOTBITSOU));
if (vlen == CTF_MAX_VLEN)
return (ctf_set_errno (ofp, ECTF_DTFULL));
if (ctf_grow_vlen (fp, dtd, sizeof (ctf_lmember_t) * (vlen + 1)) < 0)
return (ctf_set_errno (ofp, ctf_errno (fp)));
memb = (ctf_lmember_t *) dtd->dtd_vlen;
/* Figure out the offset of this field: all structures in DTDs
are CTF_K_BIG, which means their offsets are all encoded as
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)
{
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));
}
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
and no alignment: it can correspond to any number of compiler-inserted
types. We allow incomplete types through since they are routinely
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
no alignment: this is often wrong, but problems can be avoided in this
case by explicitly specifying the size of the structure via the _sized
functions. The deduplicator always does this. */
structures by the deduplicator and by the padding inserter below. They
are assumed to be zero-size with no alignment: this is often wrong, but
problems can be avoided in this case by explicitly specifying the size
of the structure via the _sized functions. The deduplicator always
does this. */
msize = 0;
malign = 0;
if (ctf_errno (fp) == ECTF_NONREPRESENTABLE)
ctf_set_errno (fp, 0);
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. */
}
memb[vlen].ctlm_name = ctf_str_add (fp, name);
memb[vlen].ctlm_type = type;
if (memb[vlen].ctlm_name == 0 && name != NULL && name[0] != '\0')
return -1; /* errno is set for us. */
/* Figure out the right offset for naturally-aligned types, if need be,
and insert additional unnamed members as needed. */
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)
{
/* 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)
{
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 "
"specifying explicit offset\n"),
name ? name : _("(unnamed member)"), type, souid);
return (ctf_set_errno (ofp, ECTF_INCOMPLETE));
}
if (ctf_type_encoding (fp, ltype, &linfo) == 0)
off += linfo.cte_bits;
else if ((lsize = ctf_type_size (fp, ltype)) > 0)
off += lsize * CHAR_BIT;
else if (lsize == -1 && ctf_errno (fp) == ECTF_INCOMPLETE)
if ((off = ctf_type_align_natural (fp, memb[vlen - 1].ctm_type,
type, dtd->dtd_last_offset)) < 0)
{
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_add_member_offset: cannot add member %s of "
"type %lx to struct %lx without specifying "
"explicit offset after member %s of type %lx, "
"which is an incomplete type\n"),
_("ctf_add_member_offset: cannot add member %s "
"of type %lx to struct %lx without "
"specifying explicit offset after member %s"
"of type %x, which is an incomplete type\n"),
name ? name : _("(unnamed member)"), type, souid,
lname ? lname : _("(unnamed member)"), ltype);
return (ctf_set_errno (ofp, ECTF_INCOMPLETE));
lname ? lname : _("(unnamed member)"),
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
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 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;
/* Convert the offset to a gap-since-the-last. */
off -= dtd->dtd_last_offset;
bit_offset = off;
}
/* Insert as many nameless members as needed. */
if (kflag)
bound = CTF_MAX_BIT_OFFSET;
else
{
/* Specified offset in bits. */
bound = CTF_MAX_SIZE;
memb[vlen].ctlm_offsethi = CTF_OFFSET_TO_LMEMHI (bit_offset);
memb[vlen].ctlm_offsetlo = CTF_OFFSET_TO_LMEMLO (bit_offset);
ssize = ctf_get_ctt_size (fp, &dtd->dtd_data, NULL, NULL);
ssize = MAX (ssize, ((signed) bit_offset / CHAR_BIT) + msize);
while (bit_offset > bound)
{
added_padding = 1;
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
{
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);
}
memb[vlen].ctm_offset = bit_offset;
dtd->dtd_data.ctt_size = CTF_LSIZE_SENT;
dtd->dtd_data.ctt_lsizehi = CTF_SIZE_TO_LSIZE_HI (ssize);
dtd->dtd_data.ctt_lsizelo = CTF_SIZE_TO_LSIZE_LO (ssize);
dtd->dtd_data.ctt_info = CTF_TYPE_INFO (kind, root, vlen + 1);
vlen = LCTF_VLEN (fp, prefix);
if (ctf_grow_vlen (fp, dtd, sizeof (ctf_member_t) * (vlen + 1)) < 0)
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;
}
@@ -1466,15 +1522,31 @@ ctf_add_member_encoded (ctf_dict_t *fp, ctf_id_t souid, const char *name,
if (dtd == NULL)
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))
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)
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

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 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)
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
needed for the type storage in bytes.

View File

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