/* CTF archive files.
- Copyright (C) 2019-2020 Free Software Foundation, Inc.
+ Copyright (C) 2019-2021 Free Software Foundation, Inc.
This file is part of libctf.
#endif
static off_t arc_write_one_ctf (ctf_dict_t * f, int fd, size_t threshold);
-static ctf_dict_t *ctf_arc_open_by_offset (const struct ctf_archive *arc,
- const ctf_sect_t *symsect,
- const ctf_sect_t *strsect,
- size_t offset, int *errp);
+static ctf_dict_t *ctf_dict_open_by_offset (const struct ctf_archive *arc,
+ const ctf_sect_t *symsect,
+ const ctf_sect_t *strsect,
+ size_t offset, int little_endian,
+ int *errp);
static int sort_modent_by_name (const void *one, const void *two, void *n);
static void *arc_mmap_header (int fd, size_t headersz);
static void *arc_mmap_file (int fd, size_t size);
static int arc_mmap_writeout (int fd, void *header, size_t headersz,
const char **errmsg);
static int arc_mmap_unmap (void *header, size_t headersz, const char **errmsg);
+static void ctf_arc_import_parent (const ctf_archive_t *arc, ctf_dict_t *fp);
+
+/* Flag to indicate "symbol not present" in ctf_archive_internal.ctfi_symdicts
+ and ctfi_symnamedicts. Never initialized. */
+static ctf_dict_t enosym;
/* Write out a CTF archive to the start of the file referenced by the passed-in
fd. The entries in CTF_DICTS are referenced by name: the names are passed in
arci->ctfi_free_symsect = 0;
arci->ctfi_free_strsect = 0;
arci->ctfi_unmap_on_close = unmap_on_close;
+ arci->ctfi_symsect_little_endian = -1;
return arci;
}
+/* Set the symbol-table endianness of an archive (defaulting the symtab
+ endianness of all ctf_file_t's opened from that archive). */
+void
+ctf_arc_symsect_endianness (ctf_archive_t *arc, int little_endian)
+{
+ arc->ctfi_symsect_little_endian = !!little_endian;
+ if (!arc->ctfi_is_archive)
+ ctf_symsect_endianness (arc->ctfi_dict, arc->ctfi_symsect_little_endian);
+}
+
+/* Get the CTF preamble from data in a buffer, which may be either an archive or
+ a CTF dict. If multiple dicts are present in an archive, the preamble comes
+ from an arbitrary dict. The preamble is a pointer into the ctfsect passed
+ in. */
+
+const ctf_preamble_t *
+ctf_arc_bufpreamble (const ctf_sect_t *ctfsect)
+{
+ if (ctfsect->cts_size > sizeof (uint64_t) &&
+ (le64toh ((*(uint64_t *) ctfsect->cts_data)) == CTFA_MAGIC))
+ {
+ struct ctf_archive *arc = (struct ctf_archive *) ctfsect->cts_data;
+ return (const ctf_preamble_t *) ((char *) arc + le64toh (arc->ctfa_ctfs)
+ + sizeof (uint64_t));
+ }
+ else
+ return (const ctf_preamble_t *) ctfsect->cts_data;
+}
+
/* Open a CTF archive or dictionary from data in a buffer (which the caller must
preserve until ctf_arc_close() time). Returns the archive, or NULL and an
error in *err (if not NULL). */
}
else
ctf_dict_close (arc->ctfi_dict);
+ free (arc->ctfi_symdicts);
+ free (arc->ctfi_symnamedicts);
+ ctf_dynhash_destroy (arc->ctfi_dicts);
if (arc->ctfi_free_symsect)
free ((void *) arc->ctfi_symsect.cts_data);
if (arc->ctfi_free_strsect)
/* Return the ctf_dict_t with the given name, or NULL if none, setting 'err' if
non-NULL. A name of NULL means to open the default file. */
static ctf_dict_t *
-ctf_arc_open_by_name_internal (const struct ctf_archive *arc,
- const ctf_sect_t *symsect,
- const ctf_sect_t *strsect,
- const char *name, int *errp)
+ctf_dict_open_internal (const struct ctf_archive *arc,
+ const ctf_sect_t *symsect,
+ const ctf_sect_t *strsect,
+ const char *name, int little_endian,
+ int *errp)
{
struct ctf_archive_modent *modent;
const char *search_nametbl;
if (name == NULL)
name = _CTF_SECTION; /* The default name. */
- ctf_dprintf ("ctf_arc_open_by_name(%s): opening\n", name);
+ ctf_dprintf ("ctf_dict_open_internal(%s): opening\n", name);
modent = (ctf_archive_modent_t *) ((char *) arc
+ sizeof (struct ctf_archive));
return NULL;
}
- return ctf_arc_open_by_offset (arc, symsect, strsect,
- le64toh (modent->ctf_offset), errp);
+ return ctf_dict_open_by_offset (arc, symsect, strsect,
+ le64toh (modent->ctf_offset),
+ little_endian, errp);
}
/* Return the ctf_dict_t with the given name, or NULL if none, setting 'err' if
Public entry point. */
ctf_dict_t *
-ctf_arc_open_by_name_sections (const ctf_archive_t *arc,
- const ctf_sect_t *symsect,
- const ctf_sect_t *strsect,
- const char *name,
- int *errp)
+ctf_dict_open_sections (const ctf_archive_t *arc,
+ const ctf_sect_t *symsect,
+ const ctf_sect_t *strsect,
+ const char *name,
+ int *errp)
{
if (arc->ctfi_is_archive)
{
ctf_dict_t *ret;
- ret = ctf_arc_open_by_name_internal (arc->ctfi_archive, symsect, strsect,
- name, errp);
+ ret = ctf_dict_open_internal (arc->ctfi_archive, symsect, strsect,
+ name, arc->ctfi_symsect_little_endian,
+ errp);
if (ret)
- ret->ctf_archive = (ctf_archive_t *) arc;
+ {
+ ret->ctf_archive = (ctf_archive_t *) arc;
+ ctf_arc_import_parent (arc, ret);
+ }
return ret;
}
Public entry point. */
ctf_dict_t *
-ctf_arc_open_by_name (const ctf_archive_t *arc, const char *name, int *errp)
+ctf_dict_open (const ctf_archive_t *arc, const char *name, int *errp)
{
const ctf_sect_t *symsect = &arc->ctfi_symsect;
const ctf_sect_t *strsect = &arc->ctfi_strsect;
if (strsect->cts_name == NULL)
strsect = NULL;
- return ctf_arc_open_by_name_sections (arc, symsect, strsect, name, errp);
+ return ctf_dict_open_sections (arc, symsect, strsect, name, errp);
+}
+
+static void
+ctf_cached_dict_close (void *fp)
+{
+ ctf_dict_close ((ctf_dict_t *) fp);
+}
+
+/* Return the ctf_dict_t with the given name and cache it in the archive's
+ ctfi_dicts. If this is the first cached dict, designate it the
+ crossdict_cache. */
+static ctf_dict_t *
+ctf_dict_open_cached (ctf_archive_t *arc, const char *name, int *errp)
+{
+ ctf_dict_t *fp;
+ char *dupname;
+
+ /* Just return from the cache if possible. */
+ if (arc->ctfi_dicts
+ && ((fp = ctf_dynhash_lookup (arc->ctfi_dicts, name)) != NULL))
+ {
+ fp->ctf_refcnt++;
+ return fp;
+ }
+
+ /* Not yet cached: open it. */
+ fp = ctf_dict_open (arc, name, errp);
+ dupname = strdup (name);
+
+ if (!fp || !dupname)
+ goto oom;
+
+ if (arc->ctfi_dicts == NULL)
+ if ((arc->ctfi_dicts
+ = ctf_dynhash_create (ctf_hash_string, ctf_hash_eq_string,
+ free, ctf_cached_dict_close)) == NULL)
+ goto oom;
+
+ if (ctf_dynhash_insert (arc->ctfi_dicts, dupname, fp) < 0)
+ goto oom;
+ fp->ctf_refcnt++;
+
+ if (arc->ctfi_crossdict_cache == NULL)
+ arc->ctfi_crossdict_cache = fp;
+
+ return fp;
+
+ oom:
+ ctf_dict_close (fp);
+ free (dupname);
+ if (errp)
+ *errp = ENOMEM;
+ return NULL;
+}
+
+/* Flush any caches the CTF archive may have open. */
+void
+ctf_arc_flush_caches (ctf_archive_t *wrapper)
+{
+ free (wrapper->ctfi_symdicts);
+ free (wrapper->ctfi_symnamedicts);
+ ctf_dynhash_destroy (wrapper->ctfi_dicts);
+ wrapper->ctfi_symdicts = NULL;
+ wrapper->ctfi_symnamedicts = NULL;
+ wrapper->ctfi_dicts = NULL;
+ wrapper->ctfi_crossdict_cache = NULL;
}
/* Return the ctf_dict_t at the given ctfa_ctfs-relative offset, or NULL if
none, setting 'err' if non-NULL. */
static ctf_dict_t *
-ctf_arc_open_by_offset (const struct ctf_archive *arc,
- const ctf_sect_t *symsect,
- const ctf_sect_t *strsect, size_t offset,
- int *errp)
+ctf_dict_open_by_offset (const struct ctf_archive *arc,
+ const ctf_sect_t *symsect,
+ const ctf_sect_t *strsect, size_t offset,
+ int little_endian, int *errp)
{
ctf_sect_t ctfsect;
ctf_dict_t *fp;
- ctf_dprintf ("ctf_arc_open_by_offset(%lu): opening\n", (unsigned long) offset);
+ ctf_dprintf ("ctf_dict_open_by_offset(%lu): opening\n", (unsigned long) offset);
memset (&ctfsect, 0, sizeof (ctf_sect_t));
ctfsect.cts_data = (void *) ((char *) arc + offset + sizeof (uint64_t));
fp = ctf_bufopen (&ctfsect, symsect, strsect, errp);
if (fp)
- ctf_setmodel (fp, le64toh (arc->ctfa_model));
+ {
+ ctf_setmodel (fp, le64toh (arc->ctfa_model));
+ if (little_endian >= 0)
+ ctf_symsect_endianness (fp, little_endian);
+ }
return fp;
}
+/* Backward compatibility. */
+ctf_dict_t *
+ctf_arc_open_by_name (const ctf_archive_t *arc, const char *name,
+ int *errp)
+{
+ return ctf_dict_open (arc, name, errp);
+}
+
+ctf_dict_t *
+ctf_arc_open_by_name_sections (const ctf_archive_t *arc,
+ const ctf_sect_t *symsect,
+ const ctf_sect_t *strsect,
+ const char *name,
+ int *errp)
+{
+ return ctf_dict_open_sections (arc, symsect, strsect, name, errp);
+}
+
+/* Import the parent into a ctf archive, if this is a child, the parent is not
+ already set, and a suitable archive member exists. No error is raised if
+ this is not possible: this is just a best-effort helper operation to give
+ people useful dicts to start with. */
+static void
+ctf_arc_import_parent (const ctf_archive_t *arc, ctf_dict_t *fp)
+{
+ if ((fp->ctf_flags & LCTF_CHILD) && fp->ctf_parname && !fp->ctf_parent)
+ {
+ ctf_dict_t *parent = ctf_dict_open_cached ((ctf_archive_t *) arc,
+ fp->ctf_parname, NULL);
+ if (parent)
+ {
+ ctf_import (fp, parent);
+ ctf_dict_close (parent);
+ }
+ }
+}
+
/* Return the number of members in an archive. */
size_t
ctf_archive_count (const ctf_archive_t *wrapper)
return wrapper->ctfi_archive->ctfa_ndicts;
}
+/* Look up a symbol in an archive by name or index (if the name is set, a lookup
+ by name is done). Return the dict in the archive that the symbol is found
+ in, and (optionally) the ctf_id_t of the symbol in that dict (so you don't
+ have to look it up yourself). The dict is cached, so repeated lookups are
+ nearly free.
+
+ As usual, you should ctf_dict_close() the returned dict once you are done
+ with it.
+
+ Returns NULL on error, and an error in errp (if set). */
+
+static ctf_dict_t *
+ctf_arc_lookup_sym_or_name (ctf_archive_t *wrapper, unsigned long symidx,
+ const char *symname, ctf_id_t *typep, int *errp)
+{
+ ctf_dict_t *fp;
+ void *fpkey;
+ ctf_id_t type;
+
+ /* The usual non-archive-transparent-wrapper special case. */
+ if (!wrapper->ctfi_is_archive)
+ {
+ if (!symname)
+ {
+ if ((type = ctf_lookup_by_symbol (wrapper->ctfi_dict, symidx)) == CTF_ERR)
+ {
+ if (errp)
+ *errp = ctf_errno (wrapper->ctfi_dict);
+ return NULL;
+ }
+ }
+ else
+ {
+ if ((type = ctf_lookup_by_symbol_name (wrapper->ctfi_dict,
+ symname)) == CTF_ERR)
+ {
+ if (errp)
+ *errp = ctf_errno (wrapper->ctfi_dict);
+ return NULL;
+ }
+ }
+ if (typep)
+ *typep = type;
+ wrapper->ctfi_dict->ctf_refcnt++;
+ return wrapper->ctfi_dict;
+ }
+
+ if (wrapper->ctfi_symsect.cts_name == NULL
+ || wrapper->ctfi_symsect.cts_data == NULL
+ || wrapper->ctfi_symsect.cts_size == 0
+ || wrapper->ctfi_symsect.cts_entsize == 0)
+ {
+ if (errp)
+ *errp = ECTF_NOSYMTAB;
+ return NULL;
+ }
+
+ /* Make enough space for all possible symbol indexes, if not already done. We
+ cache the originating dictionary of all symbols. The dict links are weak,
+ to the dictionaries cached in ctfi_dicts: their refcnts are *not* bumped.
+ We also cache similar mappings for symbol names: these are ordinary
+ dynhashes, with weak links to dicts. */
+
+ if (!wrapper->ctfi_symdicts)
+ {
+ if ((wrapper->ctfi_symdicts = calloc (wrapper->ctfi_symsect.cts_size
+ / wrapper->ctfi_symsect.cts_entsize,
+ sizeof (ctf_dict_t *))) == NULL)
+ {
+ if (errp)
+ *errp = ENOMEM;
+ return NULL;
+ }
+ }
+ if (!wrapper->ctfi_symnamedicts)
+ {
+ if ((wrapper->ctfi_symnamedicts = ctf_dynhash_create (ctf_hash_string,
+ ctf_hash_eq_string,
+ free, NULL)) == NULL)
+ {
+ if (errp)
+ *errp = ENOMEM;
+ return NULL;
+ }
+ }
+
+ /* Perhaps the dict in which we found a previous lookup is cached. If it's
+ supposed to be cached but we don't find it, pretend it was always not
+ found: this should never happen, but shouldn't be allowed to cause trouble
+ if it does. */
+
+ if ((symname && ctf_dynhash_lookup_kv (wrapper->ctfi_symnamedicts,
+ symname, NULL, &fpkey))
+ || (!symname && wrapper->ctfi_symdicts[symidx] != NULL))
+ {
+ if (symname)
+ fp = (ctf_dict_t *) fpkey;
+ else
+ fp = wrapper->ctfi_symdicts[symidx];
+
+ if (fp == &enosym)
+ goto no_sym;
+
+ if (symname)
+ {
+ if ((type = ctf_lookup_by_symbol_name (fp, symname)) == CTF_ERR)
+ goto cache_no_sym;
+ }
+ else
+ {
+ if ((type = ctf_lookup_by_symbol (fp, symidx)) == CTF_ERR)
+ goto cache_no_sym;
+ }
+
+ if (typep)
+ *typep = type;
+ fp->ctf_refcnt++;
+ return fp;
+ }
+
+ /* Not cached: find it and cache it. We must track open errors ourselves even
+ if our caller doesn't, to be able to distinguish no-error end-of-iteration
+ from open errors. */
+
+ int local_err;
+ int *local_errp;
+ ctf_next_t *i = NULL;
+ const char *name;
+
+ if (errp)
+ local_errp = errp;
+ else
+ local_errp = &local_err;
+
+ while ((fp = ctf_archive_next (wrapper, &i, &name, 0, local_errp)) != NULL)
+ {
+ if (!symname)
+ {
+ if ((type = ctf_lookup_by_symbol (fp, symidx)) != CTF_ERR)
+ wrapper->ctfi_symdicts[symidx] = fp;
+ }
+ else
+ {
+ if ((type = ctf_lookup_by_symbol_name (fp, symname)) != CTF_ERR)
+ {
+ char *tmp;
+ /* No error checking, as above. */
+ if ((tmp = strdup (symname)) != NULL)
+ ctf_dynhash_insert (wrapper->ctfi_symnamedicts, tmp, fp);
+ }
+ }
+
+ if (type != CTF_ERR)
+ {
+ if (typep)
+ *typep = type;
+ ctf_next_destroy (i);
+ return fp;
+ }
+ if (ctf_errno (fp) != ECTF_NOTYPEDAT)
+ {
+ if (errp)
+ *errp = ctf_errno (fp);
+ ctf_next_destroy (i);
+ return NULL; /* errno is set for us. */
+ }
+ ctf_dict_close (fp);
+ }
+ if (*local_errp != ECTF_NEXT_END)
+ {
+ ctf_next_destroy (i);
+ return NULL;
+ }
+
+ /* Don't leak end-of-iteration to the caller. */
+ *local_errp = 0;
+
+ cache_no_sym:
+ if (!symname)
+ wrapper->ctfi_symdicts[symidx] = &enosym;
+ else
+ {
+ char *tmp;
+
+ /* No error checking: if caching fails, there is only a slight performance
+ impact. */
+ if ((tmp = strdup (symname)) != NULL)
+ if (ctf_dynhash_insert (wrapper->ctfi_symnamedicts, tmp, &enosym) < 0)
+ free (tmp);
+ }
+
+ no_sym:
+ if (errp)
+ *errp = ECTF_NOTYPEDAT;
+ if (typep)
+ *typep = CTF_ERR;
+ return NULL;
+}
+
+/* The public API for looking up a symbol by index. */
+ctf_dict_t *
+ctf_arc_lookup_symbol (ctf_archive_t *wrapper, unsigned long symidx,
+ ctf_id_t *typep, int *errp)
+{
+ return ctf_arc_lookup_sym_or_name (wrapper, symidx, NULL, typep, errp);
+}
+
+/* The public API for looking up a symbol by name. */
+
+ctf_dict_t *
+ctf_arc_lookup_symbol_name (ctf_archive_t *wrapper, const char *symname,
+ ctf_id_t *typep, int *errp)
+{
+ return ctf_arc_lookup_sym_or_name (wrapper, 0, symname, typep, errp);
+}
+
/* Raw iteration over all CTF files in an archive. We pass the raw data for all
CTF files in turn to the specified callback function. */
static int
return -EINVAL; /* Not supported. */
}
-/* Iterate over all CTF files in an archive. We pass all CTF files in turn to
- the specified callback function. */
-static int
-ctf_archive_iter_internal (const ctf_archive_t *wrapper,
- const struct ctf_archive *arc,
- const ctf_sect_t *symsect,
- const ctf_sect_t *strsect,
- ctf_archive_member_f *func, void *data)
+/* Iterate over all CTF files in an archive: public entry point. We pass all
+ CTF files in turn to the specified callback function. */
+int
+ctf_archive_iter (const ctf_archive_t *arc, ctf_archive_member_f *func,
+ void *data)
{
- int rc;
- size_t i;
- ctf_dict_t *f;
- struct ctf_archive_modent *modent;
- const char *nametbl;
-
- modent = (ctf_archive_modent_t *) ((char *) arc
- + sizeof (struct ctf_archive));
- nametbl = (((const char *) arc) + le64toh (arc->ctfa_names));
+ ctf_next_t *i = NULL;
+ ctf_dict_t *fp;
+ const char *name;
+ int err;
- for (i = 0; i < le64toh (arc->ctfa_ndicts); i++)
+ while ((fp = ctf_archive_next (arc, &i, &name, 0, &err)) != NULL)
{
- const char *name;
-
- name = &nametbl[le64toh (modent[i].name_offset)];
- if ((f = ctf_arc_open_by_name_internal (arc, symsect, strsect,
- name, &rc)) == NULL)
- return rc;
+ int rc;
- f->ctf_archive = (ctf_archive_t *) wrapper;
- if ((rc = func (f, name, data)) != 0)
+ if ((rc = func (fp, name, data)) != 0)
{
- ctf_dict_close (f);
+ ctf_dict_close (fp);
+ ctf_next_destroy (i);
return rc;
}
-
- ctf_dict_close (f);
+ ctf_dict_close (fp);
}
return 0;
}
-/* Iterate over all CTF files in an archive: public entry point. We pass all
- CTF files in turn to the specified callback function. */
-int
-ctf_archive_iter (const ctf_archive_t *arc, ctf_archive_member_f *func,
- void *data)
-{
- const ctf_sect_t *symsect = &arc->ctfi_symsect;
- const ctf_sect_t *strsect = &arc->ctfi_strsect;
-
- if (symsect->cts_name == NULL)
- symsect = NULL;
- if (strsect->cts_name == NULL)
- strsect = NULL;
-
- if (arc->ctfi_is_archive)
- return ctf_archive_iter_internal (arc, arc->ctfi_archive, symsect, strsect,
- func, data);
-
- return func (arc->ctfi_dict, _CTF_SECTION, data);
-}
-
/* Iterate over all CTF files in an archive, returning each dict in turn as a
ctf_dict_t, and NULL on error or end of iteration. It is the caller's
- responsibility to close it. Parent dicts may be skipped. Regardless of
- whether they are skipped or not, the caller must ctf_import the parent if
- need be.
+ responsibility to close it. Parent dicts may be skipped.
+
+ The archive member is cached for rapid return on future calls.
We identify parents by name rather than by flag value: for now, with the
linker only emitting parents named _CTF_SECTION, this works well enough. */
if (!skip_parent)
{
wrapper->ctfi_dict->ctf_refcnt++;
+ if (name)
+ *name = _CTF_SECTION;
return wrapper->ctfi_dict;
}
}
is the parent (i.e. at most two iterations, but possibly an early return if
*all* we have is a parent). */
- const ctf_sect_t *symsect;
- const ctf_sect_t *strsect;
-
do
{
if ((!wrapper->ctfi_is_archive) || (i->ctn_n >= le64toh (arc->ctfa_ndicts)))
return NULL;
}
- symsect = &wrapper->ctfi_symsect;
- strsect = &wrapper->ctfi_strsect;
-
- if (symsect->cts_name == NULL)
- symsect = NULL;
- if (strsect->cts_name == NULL)
- strsect = NULL;
-
modent = (ctf_archive_modent_t *) ((char *) arc
+ sizeof (struct ctf_archive));
nametbl = (((const char *) arc) + le64toh (arc->ctfa_names));
name_ = &nametbl[le64toh (modent[i->ctn_n].name_offset)];
i->ctn_n++;
- } while (skip_parent && strcmp (name_, _CTF_SECTION) == 0);
+ }
+ while (skip_parent && strcmp (name_, _CTF_SECTION) == 0);
if (name)
*name = name_;
- f = ctf_arc_open_by_name_internal (arc, symsect, strsect,
- name_, errp);
- f->ctf_archive = (ctf_archive_t *) wrapper;
+ f = ctf_dict_open_cached ((ctf_archive_t *) wrapper, name_, errp);
return f;
}