mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2024-11-27 03:51:15 +08:00
6da9267482
The recent change to detect duplicate enum values and return ECTF_DUPLICATE when found turns out to perturb a great many callers. In particular, the pahole-created kernel BTF has the same problem we historically did, and gleefully emits duplicated enum constants in profusion. Handling the resulting duplicate errors from BTF -> CTF converters reasonably is unreasonably difficult (it amounts to forcing them to skip some types or reimplement the deduplicator). So let's step back a bit. What we care about mostly is that the deduplicator treat enums with conflicting enumeration constants as conflicting types: programs that want to look up enumeration constant -> value mappings using the new APIs to do so might well want the same checks to apply to any ctf_add_* operations they carry out (and since they're *using* the new APIs, added at the same time as this restriction was imposed, there is likely to be no negative consequence of this). So we want some way to allow processes that know about duplicate detection to opt into it, while allowing everyone else to stay clear of it: but we want ctf_link to get this behaviour even if its caller has opted out. So add a new concept to the API: dict-wide CTF flags, set via ctf_dict_set_flag, obtained via ctf_dict_get_flag. They are not bitflags but simple arbitrary integers and an on/off value, stored in an unspecified manner (the one current flag, we translate into an LCTF_* flag value in the internal ctf_dict ctf_flags word). If you pass in an invalid flag or value you get a new ECTF_BADFLAG error, so the caller can easily tell whether flags added in future are valid with a particular libctf or not. We check this flag in ctf_add_enumerator, and set it around the link (including on child per-CU dicts). The newish enumerator-iteration test is souped up to check the semantics of the flag as well. The fact that the flag can be set and unset at any time has curious consequences. You can unset the flag, insert a pile of duplicates, then set it and expect the new duplicates to be detected, not only by ctf_add_enumerator but also by ctf_lookup_enumerator. This means we now have to maintain the ctf_names and conflicting_enums enum-duplication tracking as new enums are added, not purely as the dict is opened. Move that code out of init_static_types_internal and into a new ctf_track_enumerator function that addition can also call. (None of this affects the file format or serialization machinery, which has to be able to handle duplicate enumeration constants no matter what.) include/ * ctf-api.h (CTF_ERRORS) [ECTF_BADFLAG]: New. (ECTF_NERR): Update. (CTF_STRICT_NO_DUP_ENUMERATORS): New flag. (ctf_dict_set_flag): New function. (ctf_dict_get_flag): Likewise. libctf/ * ctf-impl.h (LCTF_STRICT_NO_DUP_ENUMERATORS): New flag. (ctf_track_enumerator): Declare. * ctf-dedup.c (ctf_dedup_emit_type): Set it. * ctf-link.c (ctf_create_per_cu): Likewise. (ctf_link_deduplicating_per_cu): Likewise. (ctf_link): Likewise. (ctf_link_write): Likewise. * ctf-subr.c (ctf_dict_set_flag): New function. (ctf_dict_get_flag): New function. * ctf-open.c (init_static_types_internal): Move enum tracking to... * ctf-create.c (ctf_track_enumerator): ... this new function. (ctf_add_enumerator): Call it. * libctf.ver: Add the new functions. * testsuite/libctf-lookup/enumerator-iteration.c: Test them.
384 lines
9.2 KiB
C
384 lines
9.2 KiB
C
/* Simple subrs.
|
|
Copyright (C) 2019-2024 Free Software Foundation, Inc.
|
|
|
|
This file is part of libctf.
|
|
|
|
libctf is free software; you can redistribute it and/or modify it under
|
|
the terms of the GNU General Public License as published by the Free
|
|
Software Foundation; either version 3, or (at your option) any later
|
|
version.
|
|
|
|
This program is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
See the GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; see the file COPYING. If not see
|
|
<http://www.gnu.org/licenses/>. */
|
|
|
|
#include <ctf-impl.h>
|
|
#ifdef HAVE_MMAP
|
|
#include <sys/mman.h>
|
|
#endif
|
|
#include <sys/types.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#ifndef ENOTSUP
|
|
#define ENOTSUP ENOSYS
|
|
#endif
|
|
|
|
int _libctf_version = CTF_VERSION; /* Library client version. */
|
|
int _libctf_debug = 0; /* Debugging messages enabled. */
|
|
|
|
/* Private, read-only mmap from a file, with fallback to copying.
|
|
|
|
No handling of page-offset issues at all: the caller must allow for that. */
|
|
|
|
_libctf_malloc_ void *
|
|
ctf_mmap (size_t length, size_t offset, int fd)
|
|
{
|
|
void *data;
|
|
|
|
#ifdef HAVE_MMAP
|
|
data = mmap (NULL, length, PROT_READ, MAP_PRIVATE, fd, offset);
|
|
if (data == MAP_FAILED)
|
|
data = NULL;
|
|
#else
|
|
if ((data = malloc (length)) != NULL)
|
|
{
|
|
if (ctf_pread (fd, data, length, offset) <= 0)
|
|
{
|
|
free (data);
|
|
data = NULL;
|
|
}
|
|
}
|
|
#endif
|
|
return data;
|
|
}
|
|
|
|
void
|
|
ctf_munmap (void *buf, size_t length _libctf_unused_)
|
|
{
|
|
#ifdef HAVE_MMAP
|
|
(void) munmap (buf, length);
|
|
#else
|
|
free (buf);
|
|
#endif
|
|
}
|
|
|
|
ssize_t
|
|
ctf_pread (int fd, void *buf, ssize_t count, off_t offset)
|
|
{
|
|
ssize_t len;
|
|
size_t acc = 0;
|
|
char *data = (char *) buf;
|
|
|
|
#ifdef HAVE_PREAD
|
|
while (count > 0)
|
|
{
|
|
errno = 0;
|
|
if (((len = pread (fd, data, count, offset)) < 0) &&
|
|
errno != EINTR)
|
|
return len;
|
|
if (errno == EINTR)
|
|
continue;
|
|
|
|
acc += len;
|
|
if (len == 0) /* EOF. */
|
|
return acc;
|
|
|
|
count -= len;
|
|
offset += len;
|
|
data += len;
|
|
}
|
|
return acc;
|
|
#else
|
|
off_t orig_off;
|
|
|
|
if ((orig_off = lseek (fd, 0, SEEK_CUR)) < 0)
|
|
return -1;
|
|
if ((lseek (fd, offset, SEEK_SET)) < 0)
|
|
return -1;
|
|
|
|
while (count > 0)
|
|
{
|
|
errno = 0;
|
|
if (((len = read (fd, data, count)) < 0) &&
|
|
errno != EINTR)
|
|
return len;
|
|
if (errno == EINTR)
|
|
continue;
|
|
|
|
acc += len;
|
|
if (len == 0) /* EOF. */
|
|
break;
|
|
|
|
count -= len;
|
|
data += len;
|
|
}
|
|
if ((lseek (fd, orig_off, SEEK_SET)) < 0)
|
|
return -1; /* offset is smashed. */
|
|
#endif
|
|
|
|
return acc;
|
|
}
|
|
|
|
/* Set the CTF library client version to the specified version. If version is
|
|
zero, we just return the default library version number. */
|
|
int
|
|
ctf_version (int version)
|
|
{
|
|
if (version < 0)
|
|
{
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
if (version > 0)
|
|
{
|
|
/* Dynamic version switching is not presently supported. */
|
|
if (version != CTF_VERSION)
|
|
{
|
|
errno = ENOTSUP;
|
|
return -1;
|
|
}
|
|
ctf_dprintf ("ctf_version: client using version %d\n", version);
|
|
_libctf_version = version;
|
|
}
|
|
|
|
return _libctf_version;
|
|
}
|
|
|
|
/* Get and set CTF dict-wide flags. We are fairly strict about returning
|
|
errors here, to make it easier to determine programmatically which flags are
|
|
valid. */
|
|
|
|
int
|
|
ctf_dict_set_flag (ctf_dict_t *fp, uint64_t flag, int set)
|
|
{
|
|
if (set < 0 || set > 1)
|
|
return (ctf_set_errno (fp, ECTF_BADFLAG));
|
|
|
|
switch (flag)
|
|
{
|
|
case CTF_STRICT_NO_DUP_ENUMERATORS:
|
|
if (set)
|
|
fp->ctf_flags |= LCTF_STRICT_NO_DUP_ENUMERATORS;
|
|
else
|
|
fp->ctf_flags &= ~LCTF_STRICT_NO_DUP_ENUMERATORS;
|
|
break;
|
|
default:
|
|
return (ctf_set_errno (fp, ECTF_BADFLAG));
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
ctf_dict_get_flag (ctf_dict_t *fp, uint64_t flag)
|
|
{
|
|
switch (flag)
|
|
{
|
|
case CTF_STRICT_NO_DUP_ENUMERATORS:
|
|
return (fp->ctf_flags & LCTF_STRICT_NO_DUP_ENUMERATORS) != 0;
|
|
default:
|
|
return (ctf_set_errno (fp, ECTF_BADFLAG));
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
libctf_init_debug (void)
|
|
{
|
|
static int inited;
|
|
if (!inited)
|
|
{
|
|
_libctf_debug = getenv ("LIBCTF_DEBUG") != NULL;
|
|
inited = 1;
|
|
}
|
|
}
|
|
|
|
void ctf_setdebug (int debug)
|
|
{
|
|
/* Ensure that libctf_init_debug() has been called, so that we don't get our
|
|
debugging-on-or-off smashed by the next call. */
|
|
|
|
libctf_init_debug();
|
|
_libctf_debug = debug;
|
|
ctf_dprintf ("CTF debugging set to %i\n", debug);
|
|
}
|
|
|
|
int ctf_getdebug (void)
|
|
{
|
|
return _libctf_debug;
|
|
}
|
|
|
|
_libctf_printflike_ (1, 2)
|
|
void ctf_dprintf (const char *format, ...)
|
|
{
|
|
if (_libctf_unlikely_ (_libctf_debug))
|
|
{
|
|
va_list alist;
|
|
|
|
va_start (alist, format);
|
|
fflush (stdout);
|
|
(void) fputs ("libctf DEBUG: ", stderr);
|
|
(void) vfprintf (stderr, format, alist);
|
|
va_end (alist);
|
|
}
|
|
}
|
|
|
|
/* This needs more attention to thread-safety later on. */
|
|
static ctf_list_t open_errors;
|
|
|
|
/* Errors and warnings. Report the warning or error to the list in FP (or the
|
|
open errors list if NULL): if ERR is nonzero it is the errno to report to the
|
|
debug stream instead of that recorded on fp. */
|
|
_libctf_printflike_ (4, 5)
|
|
extern void
|
|
ctf_err_warn (ctf_dict_t *fp, int is_warning, int err,
|
|
const char *format, ...)
|
|
{
|
|
va_list alist;
|
|
ctf_err_warning_t *cew;
|
|
|
|
/* Don't bother reporting errors here: we can't do much about them if they
|
|
happen. If we're so short of memory that a tiny malloc doesn't work, a
|
|
vfprintf isn't going to work either and the caller will have to rely on the
|
|
ENOMEM return they'll be getting in short order anyway. */
|
|
|
|
if ((cew = malloc (sizeof (ctf_err_warning_t))) == NULL)
|
|
return;
|
|
|
|
cew->cew_is_warning = is_warning;
|
|
va_start (alist, format);
|
|
if (vasprintf (&cew->cew_text, format, alist) < 0)
|
|
{
|
|
free (cew);
|
|
va_end (alist);
|
|
return;
|
|
}
|
|
va_end (alist);
|
|
|
|
/* Include the error code only if there is one; if this is a warning,
|
|
only use the error code if it was explicitly passed and is nonzero.
|
|
(Warnings may not have a meaningful error code, since the warning may not
|
|
lead to unwinding up to the user.) */
|
|
if ((!is_warning && (err != 0 || (fp && ctf_errno (fp) != 0)))
|
|
|| (is_warning && err != 0))
|
|
ctf_dprintf ("%s: %s (%s)\n", is_warning ? _("warning") : _("error"),
|
|
cew->cew_text, err != 0 ? ctf_errmsg (err)
|
|
: ctf_errmsg (ctf_errno (fp)));
|
|
else
|
|
ctf_dprintf ("%s: %s\n", is_warning ? _("warning") : _("error"),
|
|
cew->cew_text);
|
|
|
|
if (fp != NULL)
|
|
ctf_list_append (&fp->ctf_errs_warnings, cew);
|
|
else
|
|
ctf_list_append (&open_errors, cew);
|
|
}
|
|
|
|
/* Move all the errors/warnings from an fp into the open_errors. */
|
|
void
|
|
ctf_err_warn_to_open (ctf_dict_t *fp)
|
|
{
|
|
ctf_list_splice (&open_errors, &fp->ctf_errs_warnings);
|
|
}
|
|
|
|
/* Error-warning reporting: an 'iterator' that returns errors and warnings from
|
|
the error/warning list, in order of emission. Errors and warnings are popped
|
|
after return: the caller must free the returned error-text pointer.
|
|
|
|
An fp of NULL returns CTF-open-time errors from the open_errors variable
|
|
above.
|
|
|
|
The treatment of errors from this function itself is somewhat unusual: it
|
|
will often be called on an error path, so we don't want to overwrite the
|
|
ctf_errno unless we have no choice. So, like ctf_bufopen et al, this
|
|
function takes an errp pointer where errors are reported. The pointer is
|
|
optional: if not set, errors are reported via the fp (if non-NULL). Calls
|
|
with neither fp nor errp set are mildly problematic because there is no clear
|
|
way to report end-of-iteration: you just have to assume that a NULL return
|
|
means the end, and not an iterator error. */
|
|
|
|
char *
|
|
ctf_errwarning_next (ctf_dict_t *fp, ctf_next_t **it, int *is_warning,
|
|
int *errp)
|
|
{
|
|
ctf_next_t *i = *it;
|
|
char *ret;
|
|
ctf_list_t *errlist;
|
|
ctf_err_warning_t *cew;
|
|
|
|
if (fp)
|
|
errlist = &fp->ctf_errs_warnings;
|
|
else
|
|
errlist = &open_errors;
|
|
|
|
if (!i)
|
|
{
|
|
if ((i = ctf_next_create ()) == NULL)
|
|
{
|
|
if (errp)
|
|
*errp = ENOMEM;
|
|
else if (fp)
|
|
ctf_set_errno (fp, ENOMEM);
|
|
return NULL;
|
|
}
|
|
|
|
i->cu.ctn_fp = fp;
|
|
i->ctn_iter_fun = (void (*) (void)) ctf_errwarning_next;
|
|
*it = i;
|
|
}
|
|
|
|
if ((void (*) (void)) ctf_errwarning_next != i->ctn_iter_fun)
|
|
{
|
|
if (errp)
|
|
*errp = ECTF_NEXT_WRONGFUN;
|
|
else if (fp)
|
|
ctf_set_errno (fp, ECTF_NEXT_WRONGFUN);
|
|
return NULL;
|
|
}
|
|
|
|
if (fp != i->cu.ctn_fp)
|
|
{
|
|
if (errp)
|
|
*errp = ECTF_NEXT_WRONGFP;
|
|
else if (fp)
|
|
ctf_set_errno (fp, ECTF_NEXT_WRONGFP);
|
|
return NULL;
|
|
}
|
|
|
|
cew = ctf_list_next (errlist);
|
|
|
|
if (!cew)
|
|
{
|
|
ctf_next_destroy (i);
|
|
*it = NULL;
|
|
if (errp)
|
|
*errp = ECTF_NEXT_END;
|
|
else if (fp)
|
|
ctf_set_errno (fp, ECTF_NEXT_END);
|
|
return NULL;
|
|
}
|
|
|
|
if (is_warning)
|
|
*is_warning = cew->cew_is_warning;
|
|
ret = cew->cew_text;
|
|
ctf_list_delete (errlist, cew);
|
|
free (cew);
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
ctf_assert_fail_internal (ctf_dict_t *fp, const char *file, size_t line,
|
|
const char *exprstr)
|
|
{
|
|
ctf_set_errno (fp, ECTF_INTERNAL);
|
|
ctf_err_warn (fp, 0, 0, _("%s: %lu: libctf assertion failed: %s"),
|
|
file, (long unsigned int) line, exprstr);
|
|
}
|