529 lines
14 KiB
C
529 lines
14 KiB
C
![]() |
/* CTF linking.
|
||
|
Copyright (C) 2019 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 <string.h>
|
||
|
|
||
|
/* Linker machinery.
|
||
|
|
||
|
CTF linking consists of adding CTF archives full of content to be merged into
|
||
|
this one to the current file (which must be writable) by calling
|
||
|
ctf_link_add_ctf(). Once this is done, a call to ctf_link() will merge the
|
||
|
type tables together, generating new CTF files as needed, with this one as a
|
||
|
parent, to contain types from the inputs which conflict.
|
||
|
ctf_link_add_strtab() takes a callback which provides string/offset pairs to
|
||
|
be added to the external symbol table and deduplicated from all CTF string
|
||
|
tables in the output link; ctf_link_shuffle_syms() takes a callback which
|
||
|
provides symtab entries in ascending order, and shuffles the function and
|
||
|
data sections to match; and ctf_link_write() emits a CTF file (if there are
|
||
|
no conflicts requiring per-compilation-unit sub-CTF files) or CTF archives
|
||
|
(otherwise) and returns it, suitable for addition in the .ctf section of the
|
||
|
output. */
|
||
|
|
||
|
/* Add a file to a link. */
|
||
|
|
||
|
static void ctf_arc_close_thunk (void *arc)
|
||
|
{
|
||
|
ctf_arc_close ((ctf_archive_t *) arc);
|
||
|
}
|
||
|
|
||
|
static void ctf_file_close_thunk (void *file)
|
||
|
{
|
||
|
ctf_file_close ((ctf_file_t *) file);
|
||
|
}
|
||
|
|
||
|
int
|
||
|
ctf_link_add_ctf (ctf_file_t *fp, ctf_archive_t *ctf, const char *name)
|
||
|
{
|
||
|
char *dupname = NULL;
|
||
|
|
||
|
if (fp->ctf_link_outputs)
|
||
|
return (ctf_set_errno (fp, ECTF_LINKADDEDLATE));
|
||
|
if (fp->ctf_link_inputs == NULL)
|
||
|
fp->ctf_link_inputs = ctf_dynhash_create (ctf_hash_string,
|
||
|
ctf_hash_eq_string, free,
|
||
|
ctf_arc_close_thunk);
|
||
|
|
||
|
if (fp->ctf_link_inputs == NULL)
|
||
|
goto oom;
|
||
|
|
||
|
if ((dupname = strdup (name)) == NULL)
|
||
|
goto oom;
|
||
|
|
||
|
if (ctf_dynhash_insert (fp->ctf_link_inputs, dupname, ctf) < 0)
|
||
|
goto oom;
|
||
|
|
||
|
return 0;
|
||
|
oom:
|
||
|
free (fp->ctf_link_inputs);
|
||
|
fp->ctf_link_inputs = NULL;
|
||
|
free (dupname);
|
||
|
return (ctf_set_errno (fp, ENOMEM));
|
||
|
}
|
||
|
|
||
|
typedef struct ctf_link_in_member_cb_arg
|
||
|
{
|
||
|
ctf_file_t *out_fp;
|
||
|
const char *file_name;
|
||
|
ctf_file_t *in_fp;
|
||
|
ctf_file_t *main_input_fp;
|
||
|
const char *cu_name;
|
||
|
char *arcname;
|
||
|
int done_main_member;
|
||
|
int share_mode;
|
||
|
int in_input_cu_file;
|
||
|
} ctf_link_in_member_cb_arg_t;
|
||
|
|
||
|
/* Link one type into the link. We rely on ctf_add_type() to detect
|
||
|
duplicates. This is not terribly reliable yet (unnmamed types will be
|
||
|
mindlessly duplicated), but will improve shortly. */
|
||
|
|
||
|
static int
|
||
|
ctf_link_one_type (ctf_id_t type, int isroot _libctf_unused_, void *arg_)
|
||
|
{
|
||
|
ctf_link_in_member_cb_arg_t *arg = (ctf_link_in_member_cb_arg_t *) arg_;
|
||
|
ctf_file_t *per_cu_out_fp;
|
||
|
int err;
|
||
|
|
||
|
if (arg->share_mode != CTF_LINK_SHARE_UNCONFLICTED)
|
||
|
{
|
||
|
ctf_dprintf ("Share-duplicated mode not yet implemented.\n");
|
||
|
return ctf_set_errno (arg->out_fp, ECTF_NOTYET);
|
||
|
}
|
||
|
|
||
|
/* Simply call ctf_add_type: if it reports a conflict and we're adding to the
|
||
|
main CTF file, add to the per-CU archive member instead, creating it if
|
||
|
necessary. If we got this type from a per-CU archive member, add it
|
||
|
straight back to the corresponding member in the output. */
|
||
|
|
||
|
if (!arg->in_input_cu_file)
|
||
|
{
|
||
|
if (ctf_add_type (arg->out_fp, arg->in_fp, type) != CTF_ERR)
|
||
|
return 0;
|
||
|
|
||
|
err = ctf_errno (arg->out_fp);
|
||
|
if (err != ECTF_CONFLICT)
|
||
|
{
|
||
|
ctf_dprintf ("Cannot link type %lx from archive member %s, input file %s "
|
||
|
"into output link: %s\n", type, arg->arcname, arg->file_name,
|
||
|
ctf_errmsg (err));
|
||
|
return -1;
|
||
|
}
|
||
|
ctf_set_errno (arg->out_fp, 0);
|
||
|
}
|
||
|
|
||
|
if ((per_cu_out_fp = ctf_dynhash_lookup (arg->out_fp->ctf_link_outputs,
|
||
|
arg->arcname)) == NULL)
|
||
|
{
|
||
|
int err;
|
||
|
|
||
|
if ((per_cu_out_fp = ctf_create (&err)) == NULL)
|
||
|
{
|
||
|
ctf_dprintf ("Cannot create per-CU CTF archive for member %s: %s\n",
|
||
|
arg->arcname, ctf_errmsg (err));
|
||
|
ctf_set_errno (arg->out_fp, err);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
if (ctf_dynhash_insert (arg->out_fp->ctf_link_outputs, arg->arcname,
|
||
|
per_cu_out_fp) < 0)
|
||
|
{
|
||
|
ctf_set_errno (arg->out_fp, ENOMEM);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
ctf_import (per_cu_out_fp, arg->out_fp);
|
||
|
ctf_cuname_set (per_cu_out_fp, arg->cu_name);
|
||
|
}
|
||
|
|
||
|
if (ctf_add_type (per_cu_out_fp, arg->in_fp, type) != CTF_ERR)
|
||
|
return 0;
|
||
|
|
||
|
err = ctf_errno (per_cu_out_fp);
|
||
|
if (err == ECTF_CONFLICT)
|
||
|
/* Conflicts are possible at this stage only if a non-ld user has combined
|
||
|
multiple TUs into a single output dictionary. Even in this case we do not
|
||
|
want to stop the link or propagate the error. */
|
||
|
ctf_set_errno (arg->out_fp, 0);
|
||
|
|
||
|
return 0; /* As above: do not lose types. */
|
||
|
}
|
||
|
|
||
|
/* Merge every type and variable in this archive member into the link, so we can
|
||
|
relink things that have already had ld run on them. We use the archive
|
||
|
member name, sans any leading '.ctf.', as the CU name for ambiguous types if
|
||
|
there is one and it's not the default: otherwise, we use the name of the
|
||
|
input file. */
|
||
|
static int
|
||
|
ctf_link_one_input_archive_member (ctf_file_t *in_fp, const char *name, void *arg_)
|
||
|
{
|
||
|
ctf_link_in_member_cb_arg_t *arg = (ctf_link_in_member_cb_arg_t *) arg_;
|
||
|
int err = 0;
|
||
|
|
||
|
if (strcmp (name, _CTF_SECTION) == 0)
|
||
|
{
|
||
|
/* This file is the default member of this archive, and has already been
|
||
|
explicitly processed.
|
||
|
|
||
|
In the default sharing mode of CTF_LINK_SHARE_UNCONFLICTED, it does no
|
||
|
harm to rescan an existing shared repo again: all the types will just
|
||
|
end up in the same place. But in CTF_LINK_SHARE_DUPLICATED mode, this
|
||
|
causes the system to erroneously conclude that all types are duplicated
|
||
|
and should be shared, even if they are not. */
|
||
|
|
||
|
if (arg->done_main_member)
|
||
|
return 0;
|
||
|
arg->arcname = strdup (".ctf.");
|
||
|
if (arg->arcname)
|
||
|
{
|
||
|
char *new_name;
|
||
|
|
||
|
new_name = ctf_str_append (arg->arcname, arg->file_name);
|
||
|
if (new_name)
|
||
|
arg->arcname = new_name;
|
||
|
else
|
||
|
free (arg->arcname);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
arg->arcname = strdup (name);
|
||
|
|
||
|
/* Get ambiguous types from our parent. */
|
||
|
ctf_import (in_fp, arg->main_input_fp);
|
||
|
arg->in_input_cu_file = 1;
|
||
|
}
|
||
|
|
||
|
if (!arg->arcname)
|
||
|
return ctf_set_errno (in_fp, ENOMEM);
|
||
|
|
||
|
arg->cu_name = name;
|
||
|
if (strncmp (arg->cu_name, ".ctf.", strlen (".ctf.")) == 0)
|
||
|
arg->cu_name += strlen (".ctf.");
|
||
|
arg->in_fp = in_fp;
|
||
|
|
||
|
err = ctf_type_iter_all (in_fp, ctf_link_one_type, arg);
|
||
|
|
||
|
arg->in_input_cu_file = 0;
|
||
|
free (arg->arcname);
|
||
|
|
||
|
if (err < 0)
|
||
|
return -1; /* Errno is set for us. */
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* Link one input file's types into the output file. */
|
||
|
static void
|
||
|
ctf_link_one_input_archive (void *key, void *value, void *arg_)
|
||
|
{
|
||
|
const char *file_name = (const char *) key;
|
||
|
ctf_archive_t *arc = (ctf_archive_t *) value;
|
||
|
ctf_link_in_member_cb_arg_t *arg = (ctf_link_in_member_cb_arg_t *) arg_;
|
||
|
int err;
|
||
|
|
||
|
arg->file_name = file_name;
|
||
|
arg->done_main_member = 0;
|
||
|
if ((arg->main_input_fp = ctf_arc_open_by_name (arc, NULL, &err)) == NULL)
|
||
|
if (err != ECTF_ARNNAME)
|
||
|
{
|
||
|
ctf_dprintf ("Cannot open main archive member in input file %s in the "
|
||
|
"link: skipping: %s.\n", arg->file_name,
|
||
|
ctf_errmsg (err));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (ctf_link_one_input_archive_member (arg->main_input_fp,
|
||
|
_CTF_SECTION, arg) < 0)
|
||
|
{
|
||
|
ctf_file_close (arg->main_input_fp);
|
||
|
return;
|
||
|
}
|
||
|
arg->done_main_member = 1;
|
||
|
if (ctf_archive_iter (arc, ctf_link_one_input_archive_member, arg) < 0)
|
||
|
ctf_dprintf ("Cannot traverse archive in input file %s: link "
|
||
|
"cannot continue: %s.\n", arg->file_name,
|
||
|
ctf_errmsg (ctf_errno (arg->out_fp)));
|
||
|
else
|
||
|
{
|
||
|
/* The only error indication to the caller is the errno: so ensure that it
|
||
|
is zero if there was no actual error from the caller. */
|
||
|
ctf_set_errno (arg->out_fp, 0);
|
||
|
}
|
||
|
ctf_file_close (arg->main_input_fp);
|
||
|
}
|
||
|
|
||
|
/* Merge types and variable sections in all files added to the link
|
||
|
together. */
|
||
|
int
|
||
|
ctf_link (ctf_file_t *fp, int share_mode)
|
||
|
{
|
||
|
ctf_link_in_member_cb_arg_t arg;
|
||
|
|
||
|
memset (&arg, 0, sizeof (struct ctf_link_in_member_cb_arg));
|
||
|
arg.out_fp = fp;
|
||
|
arg.share_mode = share_mode;
|
||
|
|
||
|
if (fp->ctf_link_inputs == NULL)
|
||
|
return 0; /* Nothing to do. */
|
||
|
|
||
|
if (fp->ctf_link_outputs == NULL)
|
||
|
fp->ctf_link_outputs = ctf_dynhash_create (ctf_hash_string,
|
||
|
ctf_hash_eq_string, free,
|
||
|
ctf_file_close_thunk);
|
||
|
|
||
|
if (fp->ctf_link_outputs == NULL)
|
||
|
return ctf_set_errno (fp, ENOMEM);
|
||
|
|
||
|
ctf_dynhash_iter (fp->ctf_link_inputs, ctf_link_one_input_archive,
|
||
|
&arg);
|
||
|
|
||
|
if (ctf_errno (fp) != 0)
|
||
|
return -1;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
typedef struct ctf_link_out_string_cb_arg
|
||
|
{
|
||
|
const char *str;
|
||
|
uint32_t offset;
|
||
|
int err;
|
||
|
} ctf_link_out_string_cb_arg_t;
|
||
|
|
||
|
/* Intern a string in the string table of an output per-CU CTF file. */
|
||
|
static void
|
||
|
ctf_link_intern_extern_string (void *key _libctf_unused_, void *value,
|
||
|
void *arg_)
|
||
|
{
|
||
|
ctf_file_t *fp = (ctf_file_t *) value;
|
||
|
ctf_link_out_string_cb_arg_t *arg = (ctf_link_out_string_cb_arg_t *) arg_;
|
||
|
|
||
|
fp->ctf_flags |= LCTF_DIRTY;
|
||
|
if (ctf_str_add_external (fp, arg->str, arg->offset) == NULL)
|
||
|
arg->err = ENOMEM;
|
||
|
}
|
||
|
|
||
|
/* Repeatedly call ADD_STRING to acquire strings from the external string table,
|
||
|
adding them to the atoms table for this CU and all subsidiary CUs.
|
||
|
|
||
|
If ctf_link() is also called, it must be called first if you want the new CTF
|
||
|
files ctf_link() can create to get their strings dedupped against the ELF
|
||
|
strtab properly. */
|
||
|
int
|
||
|
ctf_link_add_strtab (ctf_file_t *fp, ctf_link_strtab_string_f *add_string,
|
||
|
void *arg)
|
||
|
{
|
||
|
const char *str;
|
||
|
uint32_t offset;
|
||
|
int err = 0;
|
||
|
|
||
|
while ((str = add_string (&offset, arg)) != NULL)
|
||
|
{
|
||
|
ctf_link_out_string_cb_arg_t iter_arg = { str, offset, 0 };
|
||
|
|
||
|
fp->ctf_flags |= LCTF_DIRTY;
|
||
|
if (ctf_str_add_external (fp, str, offset) == NULL)
|
||
|
err = ENOMEM;
|
||
|
|
||
|
ctf_dynhash_iter (fp->ctf_link_outputs, ctf_link_intern_extern_string,
|
||
|
&iter_arg);
|
||
|
if (iter_arg.err)
|
||
|
err = iter_arg.err;
|
||
|
}
|
||
|
|
||
|
return -err;
|
||
|
}
|
||
|
|
||
|
/* Not yet implemented. */
|
||
|
int
|
||
|
ctf_link_shuffle_syms (ctf_file_t *fp _libctf_unused_,
|
||
|
ctf_link_iter_symbol_f *add_sym _libctf_unused_,
|
||
|
void *arg _libctf_unused_)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
typedef struct ctf_name_list_accum_cb_arg
|
||
|
{
|
||
|
char **names;
|
||
|
ctf_file_t *fp;
|
||
|
ctf_file_t **files;
|
||
|
size_t i;
|
||
|
} ctf_name_list_accum_cb_arg_t;
|
||
|
|
||
|
/* Accumulate the names and a count of the names in the link output hash,
|
||
|
and run ctf_update() on them to generate them. */
|
||
|
static void
|
||
|
ctf_accumulate_archive_names (void *key, void *value, void *arg_)
|
||
|
{
|
||
|
const char *name = (const char *) key;
|
||
|
ctf_file_t *fp = (ctf_file_t *) value;
|
||
|
char **names;
|
||
|
ctf_file_t **files;
|
||
|
ctf_name_list_accum_cb_arg_t *arg = (ctf_name_list_accum_cb_arg_t *) arg_;
|
||
|
int err;
|
||
|
|
||
|
if ((err = ctf_update (fp)) < 0)
|
||
|
{
|
||
|
ctf_set_errno (arg->fp, ctf_errno (fp));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ((names = realloc (arg->names, sizeof (char *) * ++(arg->i))) == NULL)
|
||
|
{
|
||
|
(arg->i)--;
|
||
|
ctf_set_errno (arg->fp, ENOMEM);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ((files = realloc (arg->files, sizeof (ctf_file_t *) * arg->i)) == NULL)
|
||
|
{
|
||
|
(arg->i)--;
|
||
|
ctf_set_errno (arg->fp, ENOMEM);
|
||
|
return;
|
||
|
}
|
||
|
arg->names = names;
|
||
|
arg->names[(arg->i) - 1] = (char *) name;
|
||
|
arg->files = files;
|
||
|
arg->files[(arg->i) - 1] = fp;
|
||
|
}
|
||
|
|
||
|
/* Write out a CTF archive (if there are per-CU CTF files) or a CTF file
|
||
|
(otherwise) into a new dynamically-allocated string, and return it.
|
||
|
Members with sizes above THRESHOLD are compressed. */
|
||
|
unsigned char *
|
||
|
ctf_link_write (ctf_file_t *fp, size_t *size, size_t threshold)
|
||
|
{
|
||
|
ctf_name_list_accum_cb_arg_t arg;
|
||
|
char **names;
|
||
|
ctf_file_t **files;
|
||
|
FILE *f = NULL;
|
||
|
int err;
|
||
|
long fsize;
|
||
|
const char *errloc;
|
||
|
unsigned char *buf = NULL;
|
||
|
|
||
|
memset (&arg, 0, sizeof (ctf_name_list_accum_cb_arg_t));
|
||
|
arg.fp = fp;
|
||
|
|
||
|
if (ctf_update (fp) < 0)
|
||
|
{
|
||
|
errloc = "CTF file construction";
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
if (fp->ctf_link_outputs)
|
||
|
{
|
||
|
ctf_dynhash_iter (fp->ctf_link_outputs, ctf_accumulate_archive_names, &arg);
|
||
|
if (ctf_errno (fp) < 0)
|
||
|
{
|
||
|
errloc = "hash creation";
|
||
|
goto err;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* No extra outputs? Just write a simple ctf_file_t. */
|
||
|
if (arg.i == 0)
|
||
|
return ctf_write_mem (fp, size, threshold);
|
||
|
|
||
|
/* Writing an archive. Stick ourselves (the shared repository, parent of all
|
||
|
other archives) on the front of it with the default name. */
|
||
|
if ((names = realloc (arg.names, sizeof (char *) * (arg.i + 1))) == NULL)
|
||
|
{
|
||
|
errloc = "name reallocation";
|
||
|
goto err_no;
|
||
|
}
|
||
|
arg.names = names;
|
||
|
memmove (&(arg.names[1]), arg.names, sizeof (char *) * (arg.i));
|
||
|
arg.names[0] = (char *) _CTF_SECTION;
|
||
|
|
||
|
if ((files = realloc (arg.files,
|
||
|
sizeof (struct ctf_file *) * (arg.i + 1))) == NULL)
|
||
|
{
|
||
|
errloc = "ctf_file reallocation";
|
||
|
goto err_no;
|
||
|
}
|
||
|
arg.files = files;
|
||
|
memmove (&(arg.files[1]), arg.files, sizeof (ctf_file_t *) * (arg.i));
|
||
|
arg.files[0] = fp;
|
||
|
|
||
|
if ((f = tmpfile ()) == NULL)
|
||
|
{
|
||
|
errloc = "tempfile creation";
|
||
|
goto err_no;
|
||
|
}
|
||
|
|
||
|
if ((err = ctf_arc_write_fd (fileno (f), arg.files, arg.i + 1,
|
||
|
(const char **) arg.names,
|
||
|
threshold)) < 0)
|
||
|
{
|
||
|
errloc = "archive writing";
|
||
|
ctf_set_errno (fp, err);
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
if (fseek (f, 0, SEEK_END) < 0)
|
||
|
{
|
||
|
errloc = "seeking to end";
|
||
|
goto err_no;
|
||
|
}
|
||
|
|
||
|
if ((fsize = ftell (f)) < 0)
|
||
|
{
|
||
|
errloc = "filesize determination";
|
||
|
goto err_no;
|
||
|
}
|
||
|
|
||
|
if (fseek (f, 0, SEEK_SET) < 0)
|
||
|
{
|
||
|
errloc = "filepos resetting";
|
||
|
goto err_no;
|
||
|
}
|
||
|
|
||
|
if ((buf = malloc (fsize)) == NULL)
|
||
|
{
|
||
|
errloc = "CTF archive buffer allocation";
|
||
|
goto err_no;
|
||
|
}
|
||
|
|
||
|
while (!feof (f) && fread (buf, fsize, 1, f) == 0)
|
||
|
if (ferror (f))
|
||
|
{
|
||
|
errloc = "reading archive from temporary file";
|
||
|
goto err_no;
|
||
|
}
|
||
|
|
||
|
*size = fsize;
|
||
|
free (arg.names);
|
||
|
free (arg.files);
|
||
|
return buf;
|
||
|
|
||
|
err_no:
|
||
|
ctf_set_errno (fp, errno);
|
||
|
err:
|
||
|
free (buf);
|
||
|
if (f)
|
||
|
fclose (f);
|
||
|
free (arg.names);
|
||
|
free (arg.files);
|
||
|
ctf_dprintf ("Cannot write archive in link: %s failure: %s\n", errloc,
|
||
|
ctf_errmsg (ctf_errno (fp)));
|
||
|
return NULL;
|
||
|
}
|