mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2024-12-30 12:43:34 +08:00
1240 lines
34 KiB
C
1240 lines
34 KiB
C
/* CTF archive files.
|
|
Copyright (C) 2019-2021 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>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <elf.h>
|
|
#include "ctf-endian.h"
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#ifdef HAVE_MMAP
|
|
#include <sys/mman.h>
|
|
#endif
|
|
|
|
static off_t arc_write_one_ctf (ctf_dict_t * f, int fd, size_t threshold);
|
|
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. 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
|
|
the names array, which must have CTF_DICTS entries.
|
|
|
|
Returns 0 on success, or an errno, or an ECTF_* value. */
|
|
int
|
|
ctf_arc_write_fd (int fd, ctf_dict_t **ctf_dicts, size_t ctf_dict_cnt,
|
|
const char **names, size_t threshold)
|
|
{
|
|
const char *errmsg;
|
|
struct ctf_archive *archdr;
|
|
size_t i;
|
|
char dummy = 0;
|
|
size_t headersz;
|
|
ssize_t namesz;
|
|
size_t ctf_startoffs; /* Start of the section we are working over. */
|
|
char *nametbl = NULL; /* The name table. */
|
|
char *np;
|
|
off_t nameoffs;
|
|
struct ctf_archive_modent *modent;
|
|
|
|
ctf_dprintf ("Writing CTF archive with %lu files\n",
|
|
(unsigned long) ctf_dict_cnt);
|
|
|
|
/* Figure out the size of the mmap()ed header, including the
|
|
ctf_archive_modent array. We assume that all of this needs no
|
|
padding: a likely assumption, given that it's all made up of
|
|
uint64_t's. */
|
|
headersz = sizeof (struct ctf_archive)
|
|
+ (ctf_dict_cnt * sizeof (uint64_t) * 2);
|
|
ctf_dprintf ("headersz is %lu\n", (unsigned long) headersz);
|
|
|
|
/* From now on we work in two pieces: an mmap()ed region from zero up to the
|
|
headersz, and a region updated via write() starting after that, containing
|
|
all the tables. Platforms that do not support mmap() just use write(). */
|
|
ctf_startoffs = headersz;
|
|
if (lseek (fd, ctf_startoffs - 1, SEEK_SET) < 0)
|
|
{
|
|
errmsg = N_("ctf_arc_write(): cannot extend file while writing");
|
|
goto err;
|
|
}
|
|
|
|
if (write (fd, &dummy, 1) < 0)
|
|
{
|
|
errmsg = N_("ctf_arc_write(): cannot extend file while writing");
|
|
goto err;
|
|
}
|
|
|
|
if ((archdr = arc_mmap_header (fd, headersz)) == NULL)
|
|
{
|
|
errmsg = N_("ctf_arc_write(): cannot mmap");
|
|
goto err;
|
|
}
|
|
|
|
/* Fill in everything we can, which is everything other than the name
|
|
table offset. */
|
|
archdr->ctfa_magic = htole64 (CTFA_MAGIC);
|
|
archdr->ctfa_ndicts = htole64 (ctf_dict_cnt);
|
|
archdr->ctfa_ctfs = htole64 (ctf_startoffs);
|
|
|
|
/* We could validate that all CTF files have the same data model, but
|
|
since any reasonable construction process will be building things of
|
|
only one bitness anyway, this is pretty pointless, so just use the
|
|
model of the first CTF file for all of them. (It *is* valid to
|
|
create an empty archive: the value of ctfa_model is irrelevant in
|
|
this case, but we must be sure not to dereference uninitialized
|
|
memory.) */
|
|
|
|
if (ctf_dict_cnt > 0)
|
|
archdr->ctfa_model = htole64 (ctf_getmodel (ctf_dicts[0]));
|
|
|
|
/* Now write out the CTFs: ctf_archive_modent array via the mapping,
|
|
ctfs via write(). The names themselves have not been written yet: we
|
|
track them in a local strtab until the time is right, and sort the
|
|
modents array after construction.
|
|
|
|
The name table is not sorted. */
|
|
|
|
for (i = 0, namesz = 0; i < le64toh (archdr->ctfa_ndicts); i++)
|
|
namesz += strlen (names[i]) + 1;
|
|
|
|
nametbl = malloc (namesz);
|
|
if (nametbl == NULL)
|
|
{
|
|
errmsg = N_("ctf_arc_write(): error writing named CTF to archive");
|
|
goto err_unmap;
|
|
}
|
|
|
|
for (i = 0, namesz = 0,
|
|
modent = (ctf_archive_modent_t *) ((char *) archdr
|
|
+ sizeof (struct ctf_archive));
|
|
i < le64toh (archdr->ctfa_ndicts); i++)
|
|
{
|
|
off_t off;
|
|
|
|
strcpy (&nametbl[namesz], names[i]);
|
|
|
|
off = arc_write_one_ctf (ctf_dicts[i], fd, threshold);
|
|
if ((off < 0) && (off > -ECTF_BASE))
|
|
{
|
|
errmsg = N_("ctf_arc_write(): cannot determine file "
|
|
"position while writing to archive");
|
|
goto err_free;
|
|
}
|
|
if (off < 0)
|
|
{
|
|
errmsg = N_("ctf_arc_write(): cannot write CTF file to archive");
|
|
errno = off * -1;
|
|
goto err_free;
|
|
}
|
|
|
|
modent->name_offset = htole64 (namesz);
|
|
modent->ctf_offset = htole64 (off - ctf_startoffs);
|
|
namesz += strlen (names[i]) + 1;
|
|
modent++;
|
|
}
|
|
|
|
ctf_qsort_r ((ctf_archive_modent_t *) ((char *) archdr
|
|
+ sizeof (struct ctf_archive)),
|
|
le64toh (archdr->ctfa_ndicts),
|
|
sizeof (struct ctf_archive_modent), sort_modent_by_name,
|
|
nametbl);
|
|
|
|
/* Now the name table. */
|
|
|
|
if ((nameoffs = lseek (fd, 0, SEEK_CUR)) < 0)
|
|
{
|
|
errmsg = N_("ctf_arc_write(): cannot get current file position "
|
|
"in archive");
|
|
goto err_free;
|
|
}
|
|
archdr->ctfa_names = htole64 (nameoffs);
|
|
np = nametbl;
|
|
while (namesz > 0)
|
|
{
|
|
ssize_t len;
|
|
if ((len = write (fd, np, namesz)) < 0)
|
|
{
|
|
errmsg = N_("ctf_arc_write(): cannot write name table to archive");
|
|
goto err_free;
|
|
}
|
|
namesz -= len;
|
|
np += len;
|
|
}
|
|
free (nametbl);
|
|
|
|
if (arc_mmap_writeout (fd, archdr, headersz, &errmsg) < 0)
|
|
goto err_unmap;
|
|
if (arc_mmap_unmap (archdr, headersz, &errmsg) < 0)
|
|
goto err;
|
|
return 0;
|
|
|
|
err_free:
|
|
free (nametbl);
|
|
err_unmap:
|
|
arc_mmap_unmap (archdr, headersz, NULL);
|
|
err:
|
|
/* We report errors into the first file in the archive, if any: if this is a
|
|
zero-file archive, put it in the open-errors stream for lack of anywhere
|
|
else for it to go. */
|
|
ctf_err_warn (ctf_dict_cnt > 0 ? ctf_dicts[0] : NULL, 0, errno, "%s",
|
|
gettext (errmsg));
|
|
return errno;
|
|
}
|
|
|
|
/* Write out a CTF archive. The entries in CTF_DICTS are referenced by name:
|
|
the names are passed in the names array, which must have CTF_DICTS entries.
|
|
|
|
If the filename is NULL, create a temporary file and return a pointer to it.
|
|
|
|
Returns 0 on success, or an errno, or an ECTF_* value. */
|
|
int
|
|
ctf_arc_write (const char *file, ctf_dict_t **ctf_dicts, size_t ctf_dict_cnt,
|
|
const char **names, size_t threshold)
|
|
{
|
|
int err;
|
|
int fd;
|
|
|
|
if ((fd = open (file, O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0666)) < 0)
|
|
{
|
|
ctf_err_warn (ctf_dict_cnt > 0 ? ctf_dicts[0] : NULL, 0, errno,
|
|
_("ctf_arc_write(): cannot create %s"), file);
|
|
return errno;
|
|
}
|
|
|
|
err = ctf_arc_write_fd (fd, ctf_dicts, ctf_dict_cnt, names, threshold);
|
|
if (err)
|
|
goto err_close;
|
|
|
|
if ((err = close (fd)) < 0)
|
|
ctf_err_warn (ctf_dict_cnt > 0 ? ctf_dicts[0] : NULL, 0, errno,
|
|
_("ctf_arc_write(): cannot close after writing to archive"));
|
|
goto err;
|
|
|
|
err_close:
|
|
(void) close (fd);
|
|
err:
|
|
if (err < 0)
|
|
unlink (file);
|
|
|
|
return err;
|
|
}
|
|
|
|
/* Write one CTF file out. Return the file position of the written file (or
|
|
rather, of the file-size uint64_t that precedes it): negative return is a
|
|
negative errno or ctf_errno value. On error, the file position may no longer
|
|
be at the end of the file. */
|
|
static off_t
|
|
arc_write_one_ctf (ctf_dict_t * f, int fd, size_t threshold)
|
|
{
|
|
off_t off, end_off;
|
|
uint64_t ctfsz = 0;
|
|
char *ctfszp;
|
|
size_t ctfsz_len;
|
|
int (*writefn) (ctf_dict_t * fp, int fd);
|
|
|
|
if (ctf_serialize (f) < 0)
|
|
return f->ctf_errno * -1;
|
|
|
|
if ((off = lseek (fd, 0, SEEK_CUR)) < 0)
|
|
return errno * -1;
|
|
|
|
if (f->ctf_size > threshold)
|
|
writefn = ctf_compress_write;
|
|
else
|
|
writefn = ctf_write;
|
|
|
|
/* This zero-write turns into the size in a moment. */
|
|
ctfsz_len = sizeof (ctfsz);
|
|
ctfszp = (char *) &ctfsz;
|
|
while (ctfsz_len > 0)
|
|
{
|
|
ssize_t writelen = write (fd, ctfszp, ctfsz_len);
|
|
if (writelen < 0)
|
|
return errno * -1;
|
|
ctfsz_len -= writelen;
|
|
ctfszp += writelen;
|
|
}
|
|
|
|
if (writefn (f, fd) != 0)
|
|
return f->ctf_errno * -1;
|
|
|
|
if ((end_off = lseek (fd, 0, SEEK_CUR)) < 0)
|
|
return errno * -1;
|
|
ctfsz = htole64 (end_off - off);
|
|
|
|
if ((lseek (fd, off, SEEK_SET)) < 0)
|
|
return errno * -1;
|
|
|
|
/* ... here. */
|
|
ctfsz_len = sizeof (ctfsz);
|
|
ctfszp = (char *) &ctfsz;
|
|
while (ctfsz_len > 0)
|
|
{
|
|
ssize_t writelen = write (fd, ctfszp, ctfsz_len);
|
|
if (writelen < 0)
|
|
return errno * -1;
|
|
ctfsz_len -= writelen;
|
|
ctfszp += writelen;
|
|
}
|
|
|
|
end_off = LCTF_ALIGN_OFFS (end_off, 8);
|
|
if ((lseek (fd, end_off, SEEK_SET)) < 0)
|
|
return errno * -1;
|
|
|
|
return off;
|
|
}
|
|
|
|
/* qsort() function to sort the array of struct ctf_archive_modents into
|
|
ascending name order. */
|
|
static int
|
|
sort_modent_by_name (const void *one, const void *two, void *n)
|
|
{
|
|
const struct ctf_archive_modent *a = one;
|
|
const struct ctf_archive_modent *b = two;
|
|
char *nametbl = n;
|
|
|
|
return strcmp (&nametbl[le64toh (a->name_offset)],
|
|
&nametbl[le64toh (b->name_offset)]);
|
|
}
|
|
|
|
/* bsearch_r() function to search for a given name in the sorted array of struct
|
|
ctf_archive_modents. */
|
|
static int
|
|
search_modent_by_name (const void *key, const void *ent, void *arg)
|
|
{
|
|
const char *k = key;
|
|
const struct ctf_archive_modent *v = ent;
|
|
const char *search_nametbl = arg;
|
|
|
|
return strcmp (k, &search_nametbl[le64toh (v->name_offset)]);
|
|
}
|
|
|
|
/* Make a new struct ctf_archive_internal wrapper for a ctf_archive or a
|
|
ctf_dict. Closes ARC and/or FP on error. Arrange to free the SYMSECT or
|
|
STRSECT, as needed, on close. Possibly do not unmap on close. */
|
|
|
|
struct ctf_archive_internal *
|
|
ctf_new_archive_internal (int is_archive, int unmap_on_close,
|
|
struct ctf_archive *arc,
|
|
ctf_dict_t *fp, const ctf_sect_t *symsect,
|
|
const ctf_sect_t *strsect,
|
|
int *errp)
|
|
{
|
|
struct ctf_archive_internal *arci;
|
|
|
|
if ((arci = calloc (1, sizeof (struct ctf_archive_internal))) == NULL)
|
|
{
|
|
if (is_archive)
|
|
{
|
|
if (unmap_on_close)
|
|
ctf_arc_close_internal (arc);
|
|
}
|
|
else
|
|
ctf_dict_close (fp);
|
|
return (ctf_set_open_errno (errp, errno));
|
|
}
|
|
arci->ctfi_is_archive = is_archive;
|
|
if (is_archive)
|
|
arci->ctfi_archive = arc;
|
|
else
|
|
arci->ctfi_dict = fp;
|
|
if (symsect)
|
|
memcpy (&arci->ctfi_symsect, symsect, sizeof (struct ctf_sect));
|
|
if (strsect)
|
|
memcpy (&arci->ctfi_strsect, strsect, sizeof (struct ctf_sect));
|
|
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). */
|
|
ctf_archive_t *
|
|
ctf_arc_bufopen (const ctf_sect_t *ctfsect, const ctf_sect_t *symsect,
|
|
const ctf_sect_t *strsect, int *errp)
|
|
{
|
|
struct ctf_archive *arc = NULL;
|
|
int is_archive;
|
|
ctf_dict_t *fp = NULL;
|
|
|
|
if (ctfsect->cts_size > sizeof (uint64_t) &&
|
|
(le64toh ((*(uint64_t *) ctfsect->cts_data)) == CTFA_MAGIC))
|
|
{
|
|
/* The archive is mmappable, so this operation is trivial.
|
|
|
|
This buffer is nonmodifiable, so the trick involving mmapping only part
|
|
of it and storing the length in the magic number is not applicable: so
|
|
record this fact in the archive-wrapper header. (We cannot record it
|
|
in the archive, because the archive may very well be a read-only
|
|
mapping.) */
|
|
|
|
is_archive = 1;
|
|
arc = (struct ctf_archive *) ctfsect->cts_data;
|
|
}
|
|
else
|
|
{
|
|
is_archive = 0;
|
|
if ((fp = ctf_bufopen (ctfsect, symsect, strsect, errp)) == NULL)
|
|
{
|
|
ctf_err_warn (NULL, 0, *errp, _("ctf_arc_bufopen(): cannot open CTF"));
|
|
return NULL;
|
|
}
|
|
}
|
|
return ctf_new_archive_internal (is_archive, 0, arc, fp, symsect, strsect,
|
|
errp);
|
|
}
|
|
|
|
/* Open a CTF archive. Returns the archive, or NULL and an error in *err (if
|
|
not NULL). */
|
|
struct ctf_archive *
|
|
ctf_arc_open_internal (const char *filename, int *errp)
|
|
{
|
|
const char *errmsg;
|
|
int fd;
|
|
struct stat s;
|
|
struct ctf_archive *arc; /* (Actually the whole file.) */
|
|
|
|
libctf_init_debug();
|
|
if ((fd = open (filename, O_RDONLY)) < 0)
|
|
{
|
|
errmsg = N_("ctf_arc_open(): cannot open %s");
|
|
goto err;
|
|
}
|
|
if (fstat (fd, &s) < 0)
|
|
{
|
|
errmsg = N_("ctf_arc_open(): cannot stat %s");
|
|
goto err_close;
|
|
}
|
|
|
|
if ((arc = arc_mmap_file (fd, s.st_size)) == NULL)
|
|
{
|
|
errmsg = N_("ctf_arc_open(): cannot read in %s");
|
|
goto err_close;
|
|
}
|
|
|
|
if (le64toh (arc->ctfa_magic) != CTFA_MAGIC)
|
|
{
|
|
errmsg = N_("ctf_arc_open(): %s: invalid magic number");
|
|
errno = ECTF_FMT;
|
|
goto err_unmap;
|
|
}
|
|
|
|
/* This horrible hack lets us know how much to unmap when the file is
|
|
closed. (We no longer need the magic number, and the mapping
|
|
is private.) */
|
|
arc->ctfa_magic = s.st_size;
|
|
close (fd);
|
|
return arc;
|
|
|
|
err_unmap:
|
|
arc_mmap_unmap (arc, s.st_size, NULL);
|
|
err_close:
|
|
close (fd);
|
|
err:
|
|
if (errp)
|
|
*errp = errno;
|
|
ctf_err_warn (NULL, 0, errno, gettext (errmsg), filename);
|
|
return NULL;
|
|
}
|
|
|
|
/* Close an archive. */
|
|
void
|
|
ctf_arc_close_internal (struct ctf_archive *arc)
|
|
{
|
|
if (arc == NULL)
|
|
return;
|
|
|
|
/* See the comment in ctf_arc_open(). */
|
|
arc_mmap_unmap (arc, arc->ctfa_magic, NULL);
|
|
}
|
|
|
|
/* Public entry point: close an archive, or CTF file. */
|
|
void
|
|
ctf_arc_close (ctf_archive_t *arc)
|
|
{
|
|
if (arc == NULL)
|
|
return;
|
|
|
|
if (arc->ctfi_is_archive)
|
|
{
|
|
if (arc->ctfi_unmap_on_close)
|
|
ctf_arc_close_internal (arc->ctfi_archive);
|
|
}
|
|
else
|
|
ctf_dict_close (arc->ctfi_dict);
|
|
free (arc->ctfi_syms);
|
|
free (arc->ctfi_symdicts);
|
|
ctf_dynhash_destroy (arc->ctfi_dicts);
|
|
if (arc->ctfi_free_symsect)
|
|
free ((void *) arc->ctfi_symsect.cts_data);
|
|
if (arc->ctfi_free_strsect)
|
|
free ((void *) arc->ctfi_strsect.cts_data);
|
|
free (arc->ctfi_data);
|
|
if (arc->ctfi_bfd_close)
|
|
arc->ctfi_bfd_close (arc);
|
|
free (arc);
|
|
}
|
|
|
|
/* 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_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_dict_open_internal(%s): opening\n", name);
|
|
|
|
modent = (ctf_archive_modent_t *) ((char *) arc
|
|
+ sizeof (struct ctf_archive));
|
|
|
|
search_nametbl = (const char *) arc + le64toh (arc->ctfa_names);
|
|
modent = bsearch_r (name, modent, le64toh (arc->ctfa_ndicts),
|
|
sizeof (struct ctf_archive_modent),
|
|
search_modent_by_name, (void *) search_nametbl);
|
|
|
|
/* This is actually a common case and normal operation: no error
|
|
debug output. */
|
|
if (modent == NULL)
|
|
{
|
|
if (errp)
|
|
*errp = ECTF_ARNNAME;
|
|
return NULL;
|
|
}
|
|
|
|
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
|
|
non-NULL. A name of NULL means to open the default file.
|
|
|
|
Use the specified string and symbol table sections.
|
|
|
|
Public entry point. */
|
|
ctf_dict_t *
|
|
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_dict_open_internal (arc->ctfi_archive, symsect, strsect,
|
|
name, arc->ctfi_symsect_little_endian,
|
|
errp);
|
|
if (ret)
|
|
{
|
|
ret->ctf_archive = (ctf_archive_t *) arc;
|
|
ctf_arc_import_parent (arc, ret);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
if ((name != NULL) && (strcmp (name, _CTF_SECTION) != 0))
|
|
{
|
|
if (errp)
|
|
*errp = ECTF_ARNNAME;
|
|
return NULL;
|
|
}
|
|
arc->ctfi_dict->ctf_archive = (ctf_archive_t *) arc;
|
|
|
|
/* Bump the refcount so that the user can ctf_dict_close() it. */
|
|
arc->ctfi_dict->ctf_refcnt++;
|
|
return arc->ctfi_dict;
|
|
}
|
|
|
|
/* 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.
|
|
|
|
Public entry point. */
|
|
ctf_dict_t *
|
|
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 (symsect->cts_name == NULL)
|
|
symsect = NULL;
|
|
if (strsect->cts_name == NULL)
|
|
strsect = NULL;
|
|
|
|
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. */
|
|
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++;
|
|
|
|
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_syms);
|
|
ctf_dynhash_destroy (wrapper->ctfi_dicts);
|
|
wrapper->ctfi_symdicts = NULL;
|
|
wrapper->ctfi_syms = NULL;
|
|
wrapper->ctfi_dicts = 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_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_dict_open_by_offset(%lu): opening\n", (unsigned long) offset);
|
|
|
|
memset (&ctfsect, 0, sizeof (ctf_sect_t));
|
|
|
|
offset += le64toh (arc->ctfa_ctfs);
|
|
|
|
ctfsect.cts_name = _CTF_SECTION;
|
|
ctfsect.cts_size = le64toh (*((uint64_t *) ((char *) arc + offset)));
|
|
ctfsect.cts_entsize = 1;
|
|
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));
|
|
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)
|
|
{
|
|
if (!wrapper->ctfi_is_archive)
|
|
return 1;
|
|
|
|
return wrapper->ctfi_archive->ctfa_ndicts;
|
|
}
|
|
|
|
/* Look up a symbol in an archive. 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 and mapping are both
|
|
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). */
|
|
|
|
ctf_dict_t *
|
|
ctf_arc_lookup_symbol (ctf_archive_t *wrapper, unsigned long symidx,
|
|
ctf_id_t *typep, int *errp)
|
|
{
|
|
ctf_dict_t *fp;
|
|
ctf_id_t type;
|
|
|
|
/* The usual non-archive-transparent-wrapper special case. */
|
|
if (!wrapper->ctfi_is_archive)
|
|
{
|
|
if ((type = ctf_lookup_by_symbol (wrapper->ctfi_dict, symidx)) == 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 symbols, if not already done.
|
|
We cache both the ctf_id_t and the originating dictionary of all symbols.
|
|
The dict links are weak, to the dictionaries cached in ctfi_dicts: their
|
|
refcnts are *not* bumped. */
|
|
|
|
if (!wrapper->ctfi_syms)
|
|
{
|
|
if ((wrapper->ctfi_syms = calloc (wrapper->ctfi_symsect.cts_size
|
|
/ wrapper->ctfi_symsect.cts_entsize,
|
|
sizeof (ctf_id_t))) == NULL)
|
|
{
|
|
if (errp)
|
|
*errp = ENOMEM;
|
|
return NULL;
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
|
|
/* Perhaps it's cached. */
|
|
if (wrapper->ctfi_symdicts[symidx] != NULL)
|
|
{
|
|
if (wrapper->ctfi_symdicts[symidx] == &enosym)
|
|
{
|
|
if (errp)
|
|
*errp = ECTF_NOTYPEDAT;
|
|
if (typep)
|
|
*typep = CTF_ERR;
|
|
return NULL;
|
|
}
|
|
|
|
if (typep)
|
|
*typep = wrapper->ctfi_syms[symidx];
|
|
wrapper->ctfi_symdicts[symidx]->ctf_refcnt++;
|
|
return wrapper->ctfi_symdicts[symidx];
|
|
}
|
|
|
|
/* 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 ((type = ctf_lookup_by_symbol (fp, symidx)) != CTF_ERR)
|
|
{
|
|
wrapper->ctfi_syms[symidx] = type;
|
|
wrapper->ctfi_symdicts[symidx] = fp;
|
|
ctf_next_destroy (i);
|
|
|
|
if (typep)
|
|
*typep = type;
|
|
return fp;
|
|
}
|
|
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;
|
|
|
|
wrapper->ctfi_symdicts[symidx] = &enosym;
|
|
|
|
if (errp)
|
|
*errp = ECTF_NOTYPEDAT;
|
|
if (typep)
|
|
*typep = CTF_ERR;
|
|
return NULL;
|
|
}
|
|
|
|
/* 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
|
|
ctf_archive_raw_iter_internal (const struct ctf_archive *arc,
|
|
ctf_archive_raw_member_f *func, void *data)
|
|
{
|
|
int rc;
|
|
size_t i;
|
|
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));
|
|
|
|
for (i = 0; i < le64toh (arc->ctfa_ndicts); i++)
|
|
{
|
|
const char *name;
|
|
char *fp;
|
|
|
|
name = &nametbl[le64toh (modent[i].name_offset)];
|
|
fp = ((char *) arc + le64toh (arc->ctfa_ctfs)
|
|
+ le64toh (modent[i].ctf_offset));
|
|
|
|
if ((rc = func (name, (void *) (fp + sizeof (uint64_t)),
|
|
le64toh (*((uint64_t *) fp)), data)) != 0)
|
|
return rc;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Raw iteration over all CTF files in an archive: public entry point.
|
|
|
|
Returns -EINVAL if not supported for this sort of archive. */
|
|
int
|
|
ctf_archive_raw_iter (const ctf_archive_t *arc,
|
|
ctf_archive_raw_member_f * func, void *data)
|
|
{
|
|
if (arc->ctfi_is_archive)
|
|
return ctf_archive_raw_iter_internal (arc->ctfi_archive, func, data);
|
|
|
|
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)
|
|
{
|
|
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));
|
|
|
|
for (i = 0; i < le64toh (arc->ctfa_ndicts); i++)
|
|
{
|
|
const char *name;
|
|
|
|
name = &nametbl[le64toh (modent[i].name_offset)];
|
|
if ((f = ctf_dict_open_internal (arc, symsect, strsect,
|
|
name,
|
|
wrapper->ctfi_symsect_little_endian,
|
|
&rc)) == NULL)
|
|
return rc;
|
|
|
|
f->ctf_archive = (ctf_archive_t *) wrapper;
|
|
ctf_arc_import_parent (wrapper, f);
|
|
if ((rc = func (f, name, data)) != 0)
|
|
{
|
|
ctf_dict_close (f);
|
|
return rc;
|
|
}
|
|
|
|
ctf_dict_close (f);
|
|
}
|
|
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.
|
|
|
|
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. */
|
|
|
|
ctf_dict_t *
|
|
ctf_archive_next (const ctf_archive_t *wrapper, ctf_next_t **it, const char **name,
|
|
int skip_parent, int *errp)
|
|
{
|
|
ctf_dict_t *f;
|
|
ctf_next_t *i = *it;
|
|
struct ctf_archive *arc;
|
|
struct ctf_archive_modent *modent;
|
|
const char *nametbl;
|
|
const char *name_;
|
|
|
|
if (!i)
|
|
{
|
|
if ((i = ctf_next_create()) == NULL)
|
|
{
|
|
if (errp)
|
|
*errp = ENOMEM;
|
|
return NULL;
|
|
}
|
|
i->cu.ctn_arc = wrapper;
|
|
i->ctn_iter_fun = (void (*) (void)) ctf_archive_next;
|
|
*it = i;
|
|
}
|
|
|
|
if ((void (*) (void)) ctf_archive_next != i->ctn_iter_fun)
|
|
{
|
|
if (errp)
|
|
*errp = ECTF_NEXT_WRONGFUN;
|
|
return NULL;
|
|
}
|
|
|
|
if (wrapper != i->cu.ctn_arc)
|
|
{
|
|
if (errp)
|
|
*errp = ECTF_NEXT_WRONGFP;
|
|
return NULL;
|
|
}
|
|
|
|
/* Iteration is made a bit more complex by the need to handle ctf_dict_t's
|
|
transparently wrapped in a single-member archive. These are parents: if
|
|
skip_parent is on, they are skipped and the iterator terminates
|
|
immediately. */
|
|
|
|
if (!wrapper->ctfi_is_archive && i->ctn_n == 0)
|
|
{
|
|
i->ctn_n++;
|
|
if (!skip_parent)
|
|
{
|
|
wrapper->ctfi_dict->ctf_refcnt++;
|
|
return wrapper->ctfi_dict;
|
|
}
|
|
}
|
|
|
|
arc = wrapper->ctfi_archive;
|
|
|
|
/* The loop keeps going when skip_parent is on as long as the member we find
|
|
is the parent (i.e. at most two iterations, but possibly an early return if
|
|
*all* we have is a parent). */
|
|
|
|
do
|
|
{
|
|
if ((!wrapper->ctfi_is_archive) || (i->ctn_n >= le64toh (arc->ctfa_ndicts)))
|
|
{
|
|
ctf_next_destroy (i);
|
|
*it = NULL;
|
|
if (errp)
|
|
*errp = ECTF_NEXT_END;
|
|
return 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);
|
|
|
|
if (name)
|
|
*name = name_;
|
|
|
|
f = ctf_dict_open_cached ((ctf_archive_t *) wrapper, name_, errp);
|
|
return f;
|
|
}
|
|
|
|
#ifdef HAVE_MMAP
|
|
/* Map the header in. Only used on new, empty files. */
|
|
static void *arc_mmap_header (int fd, size_t headersz)
|
|
{
|
|
void *hdr;
|
|
if ((hdr = mmap (NULL, headersz, PROT_READ | PROT_WRITE, MAP_SHARED, fd,
|
|
0)) == MAP_FAILED)
|
|
return NULL;
|
|
return hdr;
|
|
}
|
|
|
|
/* mmap() the whole file, for reading only. (Map it writably, but privately: we
|
|
need to modify the region, but don't need anyone else to see the
|
|
modifications.) */
|
|
static void *arc_mmap_file (int fd, size_t size)
|
|
{
|
|
void *arc;
|
|
if ((arc = mmap (NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE,
|
|
fd, 0)) == MAP_FAILED)
|
|
return NULL;
|
|
return arc;
|
|
}
|
|
|
|
/* Persist the header to disk. */
|
|
static int arc_mmap_writeout (int fd _libctf_unused_, void *header,
|
|
size_t headersz, const char **errmsg)
|
|
{
|
|
if (msync (header, headersz, MS_ASYNC) < 0)
|
|
{
|
|
if (errmsg)
|
|
*errmsg = N_("arc_mmap_writeout(): cannot sync after writing "
|
|
"to %s: %s");
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Unmap the region. */
|
|
static int arc_mmap_unmap (void *header, size_t headersz, const char **errmsg)
|
|
{
|
|
if (munmap (header, headersz) < 0)
|
|
{
|
|
if (errmsg)
|
|
*errmsg = N_("arc_mmap_munmap(): cannot unmap after writing "
|
|
"to %s: %s");
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
#else
|
|
/* Map the header in. Only used on new, empty files. */
|
|
static void *arc_mmap_header (int fd _libctf_unused_, size_t headersz)
|
|
{
|
|
void *hdr;
|
|
if ((hdr = malloc (headersz)) == NULL)
|
|
return NULL;
|
|
return hdr;
|
|
}
|
|
|
|
/* Pull in the whole file, for reading only. We assume the current file
|
|
position is at the start of the file. */
|
|
static void *arc_mmap_file (int fd, size_t size)
|
|
{
|
|
char *data;
|
|
|
|
if ((data = malloc (size)) == NULL)
|
|
return NULL;
|
|
|
|
if (ctf_pread (fd, data, size, 0) < 0)
|
|
{
|
|
free (data);
|
|
return NULL;
|
|
}
|
|
return data;
|
|
}
|
|
|
|
/* Persist the header to disk. */
|
|
static int arc_mmap_writeout (int fd, void *header, size_t headersz,
|
|
const char **errmsg)
|
|
{
|
|
ssize_t len;
|
|
size_t acc = 0;
|
|
char *data = (char *) header;
|
|
ssize_t count = headersz;
|
|
|
|
if ((lseek (fd, 0, SEEK_SET)) < 0)
|
|
{
|
|
if (errmsg)
|
|
*errmsg = N_("arc_mmap_writeout(): cannot seek while writing header to "
|
|
"%s: %s");
|
|
return -1;
|
|
}
|
|
|
|
while (headersz > 0)
|
|
{
|
|
if ((len = write (fd, data, count)) < 0)
|
|
{
|
|
if (errmsg)
|
|
*errmsg = N_("arc_mmap_writeout(): cannot write header to %s: %s");
|
|
return len;
|
|
}
|
|
if (len == EINTR)
|
|
continue;
|
|
|
|
acc += len;
|
|
if (len == 0) /* EOF. */
|
|
break;
|
|
|
|
count -= len;
|
|
data += len;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Unmap the region. */
|
|
static int arc_mmap_unmap (void *header, size_t headersz _libctf_unused_,
|
|
const char **errmsg _libctf_unused_)
|
|
{
|
|
free (header);
|
|
return 0;
|
|
}
|
|
#endif
|