b437bfe0f4
These functions allow you to look up types given a name in a simple subset of C declarator syntax (no function pointers), to look up the types of variables given a name, and to look up the types of data objects and the type signatures of functions given symbol table offsets. (Despite its name, one function in this commit, ctf_lookup_symbol_name(), is for the internal use of libctf only, and does not appear in any public header files.) libctf/ * ctf-lookup.c (isqualifier): New. (ctf_lookup_by_name): Likewise. (struct ctf_lookup_var_key): Likewise. (ctf_lookup_var): Likewise. (ctf_lookup_variable): Likewise. (ctf_lookup_symbol_name): Likewise. (ctf_lookup_by_symbol): Likewise. (ctf_func_info): Likewise. (ctf_func_args): Likewise. include/ * ctf-api.h (ctf_func_info): New. (ctf_func_args): Likewise. (ctf_lookup_by_symbol): Likewise. (ctf_lookup_by_symbol): Likewise. (ctf_lookup_variable): Likewise.
428 lines
12 KiB
C
428 lines
12 KiB
C
/* Type lookup.
|
|
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 <elf.h>
|
|
#include <string.h>
|
|
|
|
/* Compare the given input string and length against a table of known C storage
|
|
qualifier keywords. We just ignore these in ctf_lookup_by_name, below. To
|
|
do this quickly, we use a pre-computed Perfect Hash Function similar to the
|
|
technique originally described in the classic paper:
|
|
|
|
R.J. Cichelli, "Minimal Perfect Hash Functions Made Simple",
|
|
Communications of the ACM, Volume 23, Issue 1, January 1980, pp. 17-19.
|
|
|
|
For an input string S of length N, we use hash H = S[N - 1] + N - 105, which
|
|
for the current set of qualifiers yields a unique H in the range [0 .. 20].
|
|
The hash can be modified when the keyword set changes as necessary. We also
|
|
store the length of each keyword and check it prior to the final strcmp().
|
|
|
|
TODO: just use gperf. */
|
|
|
|
static int
|
|
isqualifier (const char *s, size_t len)
|
|
{
|
|
static const struct qual
|
|
{
|
|
const char *q_name;
|
|
size_t q_len;
|
|
} qhash[] = {
|
|
{"static", 6}, {"", 0}, {"", 0}, {"", 0},
|
|
{"volatile", 8}, {"", 0}, {"", 0}, {"", 0}, {"", 0},
|
|
{"", 0}, {"auto", 4}, {"extern", 6}, {"", 0}, {"", 0},
|
|
{"", 0}, {"", 0}, {"const", 5}, {"register", 8},
|
|
{"", 0}, {"restrict", 8}, {"_Restrict", 9}
|
|
};
|
|
|
|
int h = s[len - 1] + (int) len - 105;
|
|
const struct qual *qp = &qhash[h];
|
|
|
|
return (h >= 0 && (size_t) h < sizeof (qhash) / sizeof (qhash[0])
|
|
&& (size_t) len == qp->q_len &&
|
|
strncmp (qp->q_name, s, qp->q_len) == 0);
|
|
}
|
|
|
|
/* Attempt to convert the given C type name into the corresponding CTF type ID.
|
|
It is not possible to do complete and proper conversion of type names
|
|
without implementing a more full-fledged parser, which is necessary to
|
|
handle things like types that are function pointers to functions that
|
|
have arguments that are function pointers, and fun stuff like that.
|
|
Instead, this function implements a very simple conversion algorithm that
|
|
finds the things that we actually care about: structs, unions, enums,
|
|
integers, floats, typedefs, and pointers to any of these named types. */
|
|
|
|
ctf_id_t
|
|
ctf_lookup_by_name (ctf_file_t *fp, const char *name)
|
|
{
|
|
static const char delimiters[] = " \t\n\r\v\f*";
|
|
|
|
const ctf_lookup_t *lp;
|
|
const char *p, *q, *end;
|
|
ctf_id_t type = 0;
|
|
ctf_id_t ntype, ptype;
|
|
|
|
if (name == NULL)
|
|
return (ctf_set_errno (fp, EINVAL));
|
|
|
|
for (p = name, end = name + strlen (name); *p != '\0'; p = q)
|
|
{
|
|
while (isspace (*p))
|
|
p++; /* Skip leading whitespace. */
|
|
|
|
if (p == end)
|
|
break;
|
|
|
|
if ((q = strpbrk (p + 1, delimiters)) == NULL)
|
|
q = end; /* Compare until end. */
|
|
|
|
if (*p == '*')
|
|
{
|
|
/* Find a pointer to type by looking in fp->ctf_ptrtab.
|
|
If we can't find a pointer to the given type, see if
|
|
we can compute a pointer to the type resulting from
|
|
resolving the type down to its base type and use
|
|
that instead. This helps with cases where the CTF
|
|
data includes "struct foo *" but not "foo_t *" and
|
|
the user tries to access "foo_t *" in the debugger.
|
|
|
|
TODO need to handle parent containers too. */
|
|
|
|
ntype = fp->ctf_ptrtab[LCTF_TYPE_TO_INDEX (fp, type)];
|
|
if (ntype == 0)
|
|
{
|
|
ntype = ctf_type_resolve_unsliced (fp, type);
|
|
if (ntype == CTF_ERR
|
|
|| (ntype =
|
|
fp->ctf_ptrtab[LCTF_TYPE_TO_INDEX (fp, ntype)]) == 0)
|
|
{
|
|
(void) ctf_set_errno (fp, ECTF_NOTYPE);
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
type = LCTF_INDEX_TO_TYPE (fp, ntype, (fp->ctf_flags & LCTF_CHILD));
|
|
|
|
q = p + 1;
|
|
continue;
|
|
}
|
|
|
|
if (isqualifier (p, (size_t) (q - p)))
|
|
continue; /* Skip qualifier keyword. */
|
|
|
|
for (lp = fp->ctf_lookups; lp->ctl_prefix != NULL; lp++)
|
|
{
|
|
/* TODO: This is not MT-safe. */
|
|
if ((lp->ctl_prefix[0] == '\0' ||
|
|
strncmp (p, lp->ctl_prefix, (size_t) (q - p)) == 0) &&
|
|
(size_t) (q - p) >= lp->ctl_len)
|
|
{
|
|
for (p += lp->ctl_len; isspace (*p); p++)
|
|
continue; /* Skip prefix and next whitespace. */
|
|
|
|
if ((q = strchr (p, '*')) == NULL)
|
|
q = end; /* Compare until end. */
|
|
|
|
while (isspace (q[-1]))
|
|
q--; /* Exclude trailing whitespace. */
|
|
|
|
/* Expand and/or allocate storage for a slice of the name, then
|
|
copy it in. */
|
|
|
|
if (fp->ctf_tmp_typeslicelen >= (size_t) (q - p) + 1)
|
|
{
|
|
memcpy (fp->ctf_tmp_typeslice, p, (size_t) (q - p));
|
|
fp->ctf_tmp_typeslice[(size_t) (q - p)] = '\0';
|
|
}
|
|
else
|
|
{
|
|
free (fp->ctf_tmp_typeslice);
|
|
fp->ctf_tmp_typeslice = strndup (p, (size_t) (q - p));
|
|
if (fp->ctf_tmp_typeslice == NULL)
|
|
{
|
|
(void) ctf_set_errno (fp, ENOMEM);
|
|
return CTF_ERR;
|
|
}
|
|
}
|
|
|
|
if ((type = ctf_hash_lookup_type (lp->ctl_hash, fp,
|
|
fp->ctf_tmp_typeslice)) == 0)
|
|
{
|
|
(void) ctf_set_errno (fp, ECTF_NOTYPE);
|
|
goto err;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (lp->ctl_prefix == NULL)
|
|
{
|
|
(void) ctf_set_errno (fp, ECTF_NOTYPE);
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
if (*p != '\0' || type == 0)
|
|
return (ctf_set_errno (fp, ECTF_SYNTAX));
|
|
|
|
return type;
|
|
|
|
err:
|
|
if (fp->ctf_parent != NULL
|
|
&& (ptype = ctf_lookup_by_name (fp->ctf_parent, name)) != CTF_ERR)
|
|
return ptype;
|
|
|
|
return CTF_ERR;
|
|
}
|
|
|
|
typedef struct ctf_lookup_var_key
|
|
{
|
|
ctf_file_t *clvk_fp;
|
|
const char *clvk_name;
|
|
} ctf_lookup_var_key_t;
|
|
|
|
/* A bsearch function for variable names. */
|
|
|
|
static int
|
|
ctf_lookup_var (const void *key_, const void *memb_)
|
|
{
|
|
const ctf_lookup_var_key_t *key = key_;
|
|
const ctf_varent_t *memb = memb_;
|
|
|
|
return (strcmp (key->clvk_name, ctf_strptr (key->clvk_fp, memb->ctv_name)));
|
|
}
|
|
|
|
/* Given a variable name, return the type of the variable with that name. */
|
|
|
|
ctf_id_t
|
|
ctf_lookup_variable (ctf_file_t *fp, const char *name)
|
|
{
|
|
ctf_varent_t *ent;
|
|
ctf_lookup_var_key_t key = { fp, name };
|
|
|
|
/* This array is sorted, so we can bsearch for it. */
|
|
|
|
ent = bsearch (&key, fp->ctf_vars, fp->ctf_nvars, sizeof (ctf_varent_t),
|
|
ctf_lookup_var);
|
|
|
|
if (ent == NULL)
|
|
{
|
|
if (fp->ctf_parent != NULL)
|
|
return ctf_lookup_variable (fp->ctf_parent, name);
|
|
|
|
return (ctf_set_errno (fp, ECTF_NOTYPEDAT));
|
|
}
|
|
|
|
return ent->ctv_type;
|
|
}
|
|
|
|
/* Given a symbol table index, return the name of that symbol from the secondary
|
|
string table, or the null string (never NULL). */
|
|
const char *
|
|
ctf_lookup_symbol_name (ctf_file_t *fp, unsigned long symidx)
|
|
{
|
|
const ctf_sect_t *sp = &fp->ctf_symtab;
|
|
Elf64_Sym sym, *gsp;
|
|
|
|
if (sp->cts_data == NULL)
|
|
{
|
|
ctf_set_errno (fp, ECTF_NOSYMTAB);
|
|
return _CTF_NULLSTR;
|
|
}
|
|
|
|
if (symidx >= fp->ctf_nsyms)
|
|
{
|
|
ctf_set_errno (fp, EINVAL);
|
|
return _CTF_NULLSTR;
|
|
}
|
|
|
|
if (sp->cts_entsize == sizeof (Elf32_Sym))
|
|
{
|
|
const Elf32_Sym *symp = (Elf32_Sym *) sp->cts_data + symidx;
|
|
gsp = ctf_sym_to_elf64 (symp, &sym);
|
|
}
|
|
else
|
|
gsp = (Elf64_Sym *) sp->cts_data + symidx;
|
|
|
|
if (gsp->st_name < fp->ctf_str[CTF_STRTAB_1].cts_len)
|
|
return (const char *) fp->ctf_str[CTF_STRTAB_1].cts_strs + gsp->st_name;
|
|
|
|
return _CTF_NULLSTR;
|
|
}
|
|
|
|
/* Given a symbol table index, return the type of the data object described
|
|
by the corresponding entry in the symbol table. */
|
|
|
|
ctf_id_t
|
|
ctf_lookup_by_symbol (ctf_file_t *fp, unsigned long symidx)
|
|
{
|
|
const ctf_sect_t *sp = &fp->ctf_symtab;
|
|
ctf_id_t type;
|
|
|
|
if (sp->cts_data == NULL)
|
|
return (ctf_set_errno (fp, ECTF_NOSYMTAB));
|
|
|
|
if (symidx >= fp->ctf_nsyms)
|
|
return (ctf_set_errno (fp, EINVAL));
|
|
|
|
if (sp->cts_entsize == sizeof (Elf32_Sym))
|
|
{
|
|
const Elf32_Sym *symp = (Elf32_Sym *) sp->cts_data + symidx;
|
|
if (ELF32_ST_TYPE (symp->st_info) != STT_OBJECT)
|
|
return (ctf_set_errno (fp, ECTF_NOTDATA));
|
|
}
|
|
else
|
|
{
|
|
const Elf64_Sym *symp = (Elf64_Sym *) sp->cts_data + symidx;
|
|
if (ELF64_ST_TYPE (symp->st_info) != STT_OBJECT)
|
|
return (ctf_set_errno (fp, ECTF_NOTDATA));
|
|
}
|
|
|
|
if (fp->ctf_sxlate[symidx] == -1u)
|
|
return (ctf_set_errno (fp, ECTF_NOTYPEDAT));
|
|
|
|
type = *(uint32_t *) ((uintptr_t) fp->ctf_buf + fp->ctf_sxlate[symidx]);
|
|
if (type == 0)
|
|
return (ctf_set_errno (fp, ECTF_NOTYPEDAT));
|
|
|
|
return type;
|
|
}
|
|
|
|
/* Return the pointer to the internal CTF type data corresponding to the
|
|
given type ID. If the ID is invalid, the function returns NULL.
|
|
This function is not exported outside of the library. */
|
|
|
|
const ctf_type_t *
|
|
ctf_lookup_by_id (ctf_file_t **fpp, ctf_id_t type)
|
|
{
|
|
ctf_file_t *fp = *fpp; /* Caller passes in starting CTF container. */
|
|
ctf_id_t idx;
|
|
|
|
if ((fp->ctf_flags & LCTF_CHILD) && LCTF_TYPE_ISPARENT (fp, type)
|
|
&& (fp = fp->ctf_parent) == NULL)
|
|
{
|
|
(void) ctf_set_errno (*fpp, ECTF_NOPARENT);
|
|
return NULL;
|
|
}
|
|
|
|
idx = LCTF_TYPE_TO_INDEX (fp, type);
|
|
if (idx > 0 && (unsigned long) idx <= fp->ctf_typemax)
|
|
{
|
|
*fpp = fp; /* Function returns ending CTF container. */
|
|
return (LCTF_INDEX_TO_TYPEPTR (fp, idx));
|
|
}
|
|
|
|
/* If this container is writable, check for a dynamic type. */
|
|
|
|
if (fp->ctf_flags & LCTF_RDWR)
|
|
{
|
|
ctf_dtdef_t *dtd;
|
|
|
|
if ((dtd = ctf_dynamic_type (fp, type)) != NULL)
|
|
{
|
|
*fpp = fp;
|
|
return &dtd->dtd_data;
|
|
}
|
|
}
|
|
(void) ctf_set_errno (*fpp, ECTF_BADID);
|
|
return NULL;
|
|
}
|
|
|
|
/* Given a symbol table index, return the info for the function described
|
|
by the corresponding entry in the symbol table. */
|
|
|
|
int
|
|
ctf_func_info (ctf_file_t *fp, unsigned long symidx, ctf_funcinfo_t *fip)
|
|
{
|
|
const ctf_sect_t *sp = &fp->ctf_symtab;
|
|
const uint32_t *dp;
|
|
uint32_t info, kind, n;
|
|
|
|
if (sp->cts_data == NULL)
|
|
return (ctf_set_errno (fp, ECTF_NOSYMTAB));
|
|
|
|
if (symidx >= fp->ctf_nsyms)
|
|
return (ctf_set_errno (fp, EINVAL));
|
|
|
|
if (sp->cts_entsize == sizeof (Elf32_Sym))
|
|
{
|
|
const Elf32_Sym *symp = (Elf32_Sym *) sp->cts_data + symidx;
|
|
if (ELF32_ST_TYPE (symp->st_info) != STT_FUNC)
|
|
return (ctf_set_errno (fp, ECTF_NOTFUNC));
|
|
}
|
|
else
|
|
{
|
|
const Elf64_Sym *symp = (Elf64_Sym *) sp->cts_data + symidx;
|
|
if (ELF64_ST_TYPE (symp->st_info) != STT_FUNC)
|
|
return (ctf_set_errno (fp, ECTF_NOTFUNC));
|
|
}
|
|
|
|
if (fp->ctf_sxlate[symidx] == -1u)
|
|
return (ctf_set_errno (fp, ECTF_NOFUNCDAT));
|
|
|
|
dp = (uint32_t *) ((uintptr_t) fp->ctf_buf + fp->ctf_sxlate[symidx]);
|
|
|
|
info = *dp++;
|
|
kind = LCTF_INFO_KIND (fp, info);
|
|
n = LCTF_INFO_VLEN (fp, info);
|
|
|
|
if (kind == CTF_K_UNKNOWN && n == 0)
|
|
return (ctf_set_errno (fp, ECTF_NOFUNCDAT));
|
|
|
|
if (kind != CTF_K_FUNCTION)
|
|
return (ctf_set_errno (fp, ECTF_CORRUPT));
|
|
|
|
fip->ctc_return = *dp++;
|
|
fip->ctc_argc = n;
|
|
fip->ctc_flags = 0;
|
|
|
|
if (n != 0 && dp[n - 1] == 0)
|
|
{
|
|
fip->ctc_flags |= CTF_FUNC_VARARG;
|
|
fip->ctc_argc--;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Given a symbol table index, return the arguments for the function described
|
|
by the corresponding entry in the symbol table. */
|
|
|
|
int
|
|
ctf_func_args (ctf_file_t * fp, unsigned long symidx, uint32_t argc,
|
|
ctf_id_t * argv)
|
|
{
|
|
const uint32_t *dp;
|
|
ctf_funcinfo_t f;
|
|
|
|
if (ctf_func_info (fp, symidx, &f) == CTF_ERR)
|
|
return CTF_ERR; /* errno is set for us. */
|
|
|
|
/* The argument data is two uint32_t's past the translation table
|
|
offset: one for the function info, and one for the return type. */
|
|
|
|
dp = (uint32_t *) ((uintptr_t) fp->ctf_buf + fp->ctf_sxlate[symidx]) + 2;
|
|
|
|
for (argc = MIN (argc, f.ctc_argc); argc != 0; argc--)
|
|
*argv++ = *dp++;
|
|
|
|
return 0;
|
|
}
|