diff options
author | Robert Mustacchi <rm@joyent.com> | 2015-07-27 00:35:52 +0000 |
---|---|---|
committer | Robert Mustacchi <rm@joyent.com> | 2015-07-28 19:05:39 +0000 |
commit | 14c3d85bba96b10c225341f4c7f4af93c314b508 (patch) | |
tree | 8ef3a2513e220291581564d4db16bd5b4fbf127e /usr/src/lib/libctf/common | |
parent | 9211d9b4c9ccc64292132e8e87c92ad6084b29a8 (diff) | |
download | illumos-joyent-14c3d85bba96b10c225341f4c7f4af93c314b508.tar.gz |
OS-4548 CTF Everywhere: Phase 1
OS-4549 ctfconvert should be implemented in terms of libctf
OS-4550 ctfconvert could convert multiple compilation units
OS-4553 want multi-threaded ctfmerge
OS-4552 Want general workq
OS-4551 Want general mergeq
OS-4554 ctfdiff doesn't properly handle unknown options
OS-4555 ctfdiff's symbols could be more consistently prefixed
OS-4048 new ctfmerge uses tmpfile after freeing it
OS-4556 ctfdump should drive on when incomplete files exist
OS-4557 ctf_add_encoded assigns() incorrect byte size to types
OS-4558 ctf_add_{struct,union,enum} can reuse forwards
OS-4559 ctf_add_{struct,union,enum} occasionally forget to dirty the ctf_file_t
OS-4560 ctf_add_member could better handle bitfields
OS-4561 ctf_type_size() reports wrong size for forwards
OS-4563 diffing CTF typedefs needs to walk multiple definitions
OS-4564 build scripts shouldn't hardcode CTF paths
OS-4565 ctf_fdcreate could be more flexible
OS-4566 Want libctf ctf_kind_name() function
OS-4567 Want libctf function to set struct/union size
OS-4568 Want ctfmerge altexec
Diffstat (limited to 'usr/src/lib/libctf/common')
-rw-r--r-- | usr/src/lib/libctf/common/ctf_convert.c | 210 | ||||
-rw-r--r-- | usr/src/lib/libctf/common/ctf_diff.c | 95 | ||||
-rw-r--r-- | usr/src/lib/libctf/common/ctf_dwarf.c | 2952 | ||||
-rw-r--r-- | usr/src/lib/libctf/common/ctf_elfwrite.c | 198 | ||||
-rw-r--r-- | usr/src/lib/libctf/common/ctf_lib.c | 211 | ||||
-rw-r--r-- | usr/src/lib/libctf/common/ctf_merge.c | 492 | ||||
-rw-r--r-- | usr/src/lib/libctf/common/ctf_subr.c | 28 | ||||
-rw-r--r-- | usr/src/lib/libctf/common/libctf.h | 9 | ||||
-rw-r--r-- | usr/src/lib/libctf/common/libctf_impl.h | 59 | ||||
-rw-r--r-- | usr/src/lib/libctf/common/mapfile-vers | 6 |
10 files changed, 4070 insertions, 190 deletions
diff --git a/usr/src/lib/libctf/common/ctf_convert.c b/usr/src/lib/libctf/common/ctf_convert.c new file mode 100644 index 0000000000..cbb4d48c76 --- /dev/null +++ b/usr/src/lib/libctf/common/ctf_convert.c @@ -0,0 +1,210 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2015 Joyent, Inc. + */ + +/* + * Main conversion entry points. This has been designed such that there can be + * any number of different conversion backends. Currently we only have one that + * understands DWARFv2 (and bits of DWARFv4). Each backend should be placed in + * the ctf_converters list and each will be tried in turn. + */ + +#include <libctf_impl.h> +#include <gelf.h> + +ctf_convert_f ctf_converters[] = { + ctf_dwarf_convert +}; + +#define NCONVERTS (sizeof (ctf_converters) / sizeof (ctf_convert_f)) + +typedef enum ctf_convert_source { + CTFCONV_SOURCE_NONE = 0x0, + CTFCONV_SOURCE_UNKNOWN = 0x01, + CTFCONV_SOURCE_C = 0x02, + CTFCONV_SOURCE_S = 0x04 +} ctf_convert_source_t; + +static void +ctf_convert_ftypes(Elf *elf, ctf_convert_source_t *types) +{ + int i; + Elf_Scn *scn = NULL, *strscn; + *types = CTFCONV_SOURCE_NONE; + GElf_Shdr shdr; + Elf_Data *data, *strdata; + + while ((scn = elf_nextscn(elf, scn)) != NULL) { + + if (gelf_getshdr(scn, &shdr) == NULL) + return; + + if (shdr.sh_type == SHT_SYMTAB) + break; + } + + if (scn == NULL) + return; + + if ((strscn = elf_getscn(elf, shdr.sh_link)) == NULL) + return; + + if ((data = elf_getdata(scn, NULL)) == NULL) + return; + + if ((strdata = elf_getdata(strscn, NULL)) == NULL) + return; + + for (i = 0; i < shdr.sh_size / shdr.sh_entsize; i++) { + GElf_Sym sym; + const char *file; + size_t len; + + if (gelf_getsym(data, i, &sym) == NULL) + return; + + if (GELF_ST_TYPE(sym.st_info) != STT_FILE) + continue; + + file = (const char *)((uintptr_t)strdata->d_buf + sym.st_name); + len = strlen(file); + if (len < 2 || file[len - 2] != '.') { + *types |= CTFCONV_SOURCE_UNKNOWN; + continue; + } + + switch (file[len - 1]) { + case 'c': + *types |= CTFCONV_SOURCE_C; + break; + case 'h': + /* We traditionally ignore header files... */ + break; + case 's': + *types |= CTFCONV_SOURCE_S; + break; + default: + *types |= CTFCONV_SOURCE_UNKNOWN; + break; + } + } +} + +static ctf_file_t * +ctf_elfconvert(int fd, Elf *elf, const char *label, uint_t nthrs, uint_t flags, + int *errp, char *errbuf, size_t errlen) +{ + int err, i; + ctf_file_t *fp = NULL; + boolean_t notsup = B_TRUE; + ctf_convert_source_t type; + + if (errp == NULL) + errp = &err; + + if (elf == NULL) { + *errp = EINVAL; + return (NULL); + } + + if (flags & ~CTF_CONVERT_F_IGNNONC) { + *errp = EINVAL; + return (NULL); + } + + if (elf_kind(elf) != ELF_K_ELF) { + *errp = ECTF_FMT; + return (NULL); + } + + ctf_convert_ftypes(elf, &type); + ctf_dprintf("got types: %d\n", type); + if (flags & CTF_CONVERT_F_IGNNONC) { + if (type == CTFCONV_SOURCE_NONE || + (type & CTFCONV_SOURCE_UNKNOWN)) { + *errp = ECTF_CONVNOCSRC; + return (NULL); + } + } + + for (i = 0; i < NCONVERTS; i++) { + ctf_conv_status_t cs; + + fp = NULL; + cs = ctf_converters[i](fd, elf, nthrs, errp, &fp, errbuf, + errlen); + if (cs == CTF_CONV_SUCCESS) { + notsup = B_FALSE; + break; + } + if (cs == CTF_CONV_ERROR) { + fp = NULL; + notsup = B_FALSE; + break; + } + } + + if (notsup == B_TRUE) { + if ((flags & CTF_CONVERT_F_IGNNONC) != 0 && + (type & CTFCONV_SOURCE_C) == 0) { + *errp = ECTF_CONVNOCSRC; + return (NULL); + } + *errp = ECTF_NOCONVBKEND; + return (NULL); + } + + /* + * Succsesful conversion. + */ + if (fp != NULL) { + if (label == NULL) + label = ""; + if (ctf_add_label(fp, label, fp->ctf_typemax, 0) == CTF_ERR) { + *errp = ctf_errno(fp); + ctf_close(fp); + return (NULL); + } + if (ctf_update(fp) == CTF_ERR) { + *errp = ctf_errno(fp); + ctf_close(fp); + return (NULL); + } + } + + return (fp); +} + +ctf_file_t * +ctf_fdconvert(int fd, const char *label, uint_t nthrs, uint_t flags, int *errp, + char *errbuf, size_t errlen) +{ + int err; + Elf *elf; + ctf_file_t *fp; + + if (errp == NULL) + errp = &err; + + elf = elf_begin(fd, ELF_C_READ, NULL); + if (elf == NULL) { + *errp = ECTF_FMT; + return (NULL); + } + + fp = ctf_elfconvert(fd, elf, label, nthrs, flags, errp, errbuf, errlen); + + (void) elf_end(elf); + return (fp); +} diff --git a/usr/src/lib/libctf/common/ctf_diff.c b/usr/src/lib/libctf/common/ctf_diff.c index e819fe02cb..d070488bbb 100644 --- a/usr/src/lib/libctf/common/ctf_diff.c +++ b/usr/src/lib/libctf/common/ctf_diff.c @@ -72,6 +72,8 @@ struct ctf_diff { ctf_file_t *cds_ofp; ctf_id_t *cds_forward; ctf_id_t *cds_reverse; + size_t cds_fsize; + size_t cds_rsize; ctf_diff_type_f cds_func; ctf_diff_guess_t *cds_guess; void *cds_arg; @@ -147,6 +149,39 @@ ctf_diff_number(ctf_file_t *ifp, ctf_id_t iid, ctf_file_t *ofp, ctf_id_t oid) } /* + * Two typedefs are equivalent, if after we resolve a chain of typedefs, they + * point to equivalent types. This means that if a size_t is defined as follows: + * + * size_t -> ulong_t -> unsigned long + * size_t -> unsigned long + * + * That we'll ultimately end up treating them the same. + */ +static int +ctf_diff_typedef(ctf_diff_t *cds, ctf_file_t *ifp, ctf_id_t iid, + ctf_file_t *ofp, ctf_id_t oid) +{ + ctf_id_t iref = CTF_ERR, oref = CTF_ERR; + + while (ctf_type_kind(ifp, iid) == CTF_K_TYPEDEF) { + iref = ctf_type_reference(ifp, iid); + if (iref == CTF_ERR) + return (CTF_ERR); + iid = iref; + } + + while (ctf_type_kind(ofp, oid) == CTF_K_TYPEDEF) { + oref = ctf_type_reference(ofp, oid); + if (oref == CTF_ERR) + return (CTF_ERR); + oid = oref; + } + + VERIFY(iref != CTF_ERR && oref != CTF_ERR); + return (ctf_diff_type(cds, ifp, iref, ofp, oref)); +} + +/* * Two qualifiers are equivalent iff they point to two equivalent types. */ static int @@ -274,7 +309,8 @@ out: /* * Two structures are the same if every member is identical to its corresponding - * type, at the same offset, and has the same name. + * type, at the same offset, and has the same name, as well as them having the + * same overall size. */ static int ctf_diff_struct(ctf_diff_t *cds, ctf_file_t *ifp, ctf_id_t iid, ctf_file_t *ofp, @@ -296,6 +332,9 @@ ctf_diff_struct(ctf_diff_t *cds, ctf_file_t *ifp, ctf_id_t iid, ctf_file_t *ofp, if ((otp = ctf_lookup_by_id(&ofp, oid)) == NULL) return (ctf_set_errno(oifp, ctf_errno(ofp))); + if (ctf_type_size(ifp, iid) != ctf_type_size(ofp, oid)) + return (B_TRUE); + if (LCTF_INFO_VLEN(ifp, itp->ctt_info) != LCTF_INFO_VLEN(ofp, otp->ctt_info)) return (B_TRUE); @@ -639,8 +678,10 @@ ctf_diff_type(ctf_diff_t *cds, ctf_file_t *ifp, ctf_id_t iid, ctf_file_t *ofp, case CTF_K_FORWARD: ret = ctf_diff_forward(ifp, iid, ofp, oid); break; - case CTF_K_POINTER: case CTF_K_TYPEDEF: + ret = ctf_diff_typedef(cds, ifp, iid, ofp, oid); + break; + case CTF_K_POINTER: case CTF_K_VOLATILE: case CTF_K_CONST: case CTF_K_RESTRICT: @@ -664,9 +705,12 @@ ctf_diff_type(ctf_diff_t *cds, ctf_file_t *ifp, ctf_id_t iid, ctf_file_t *ofp, /* * Walk every type in the first container and try to find a match in the second. * If there is a match, then update both the forward and reverse mapping tables. + * + * The self variable tells us whether or not we should be comparing the input + * ctf container with itself or not. */ static int -ctf_diff_pass1(ctf_diff_t *cds) +ctf_diff_pass1(ctf_diff_t *cds, boolean_t self) { int i, j, diff; int istart, iend, jstart, jend; @@ -689,6 +733,17 @@ ctf_diff_pass1(ctf_diff_t *cds) for (i = istart; i <= iend; i++) { diff = B_TRUE; + + /* + * If we're doing a self diff for dedup purposes, then we want + * to ensure that we compare a type i with every type in the + * range, [ 1, i ). Yes, this does mean that when i equals 1, + * we won't compare anything. + */ + if (self == B_TRUE) { + jstart = istart; + jend = i - 1; + } for (j = jstart; j <= jend; j++) { ctf_diff_guess_t *cdg, *tofree; @@ -788,12 +843,14 @@ ctf_diff_init(ctf_file_t *ifp, ctf_file_t *ofp, ctf_diff_t **cdsp) ctf_free(cds, sizeof (ctf_diff_t)); return (ctf_set_errno(ifp, ENOMEM)); } + cds->cds_fsize = fsize; cds->cds_reverse = ctf_alloc(rsize); if (cds->cds_reverse == NULL) { ctf_free(cds->cds_forward, fsize); ctf_free(cds, sizeof (ctf_diff_t)); return (ctf_set_errno(ifp, ENOMEM)); } + cds->cds_rsize = rsize; bzero(cds->cds_forward, fsize); bzero(cds->cds_reverse, rsize); @@ -811,7 +868,7 @@ ctf_diff_types(ctf_diff_t *cds, ctf_diff_type_f cb, void *arg) cds->cds_func = cb; cds->cds_arg = arg; - ret = ctf_diff_pass1(cds); + ret = ctf_diff_pass1(cds, B_FALSE); if (ret == 0) ret = ctf_diff_pass2(cds); @@ -821,12 +878,36 @@ ctf_diff_types(ctf_diff_t *cds, ctf_diff_type_f cb, void *arg) return (ret); } +/* + * Do a diff where we're comparing a container with itself. In other words we'd + * like to know what types are actually duplicates of existing types in the + * container. + * + * Note this should remain private to libctf and not be exported in the public + * mapfile for the time being. + */ +int +ctf_diff_self(ctf_diff_t *cds, ctf_diff_type_f cb, void *arg) +{ + if (cds->cds_ifp != cds->cds_ofp) + return (EINVAL); + + cds->cds_func = cb; + cds->cds_arg = arg; + + return (ctf_diff_pass1(cds, B_TRUE)); +} + + void ctf_diff_fini(ctf_diff_t *cds) { ctf_diff_guess_t *cdg; size_t fsize, rsize; + if (cds == NULL) + return; + cds->cds_ifp->ctf_refcnt--; cds->cds_ofp->ctf_refcnt--; @@ -855,6 +936,10 @@ ctf_diff_fini(ctf_diff_t *cds) cdg = cdg->cdg_next; ctf_free(tofree, sizeof (ctf_diff_guess_t)); } + if (cds->cds_forward != NULL) + ctf_free(cds->cds_forward, cds->cds_fsize); + if (cds->cds_reverse != NULL) + ctf_free(cds->cds_reverse, cds->cds_rsize); ctf_free(cds, sizeof (ctf_diff_t)); } @@ -867,7 +952,7 @@ ctf_diff_getflags(ctf_diff_t *cds) int ctf_diff_setflags(ctf_diff_t *cds, uint_t flags) { - if ((flags & ~CTF_DIFF_F_MASK) != 0) + if ((flags & ~CTF_DIFF_F_IGNORE_INTNAMES) != 0) return (ctf_set_errno(cds->cds_ifp, EINVAL)); cds->cds_flags = flags; diff --git a/usr/src/lib/libctf/common/ctf_dwarf.c b/usr/src/lib/libctf/common/ctf_dwarf.c new file mode 100644 index 0000000000..72e5aa9bd9 --- /dev/null +++ b/usr/src/lib/libctf/common/ctf_dwarf.c @@ -0,0 +1,2952 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ +/* + * Copyright 2012 Jason King. All rights reserved. + * Use is subject to license terms. + */ + +/* + * Copyright 2015 Joyent, Inc. + */ + +/* + * CTF DWARF conversion theory. + * + * DWARF data contains a series of compilation units. Each compilation unit + * generally refers to an object file or what once was, in the case of linked + * binaries and shared objects. Each compilation unit has a series of what DWARF + * calls a DIE (Debugging Information Entry). The set of entries that we care + * about have type information stored in a series of attributes. Each DIE also + * has a tag that identifies the kind of attributes that it has. + * + * A given DIE may itself have children. For example, a DIE that represents a + * structure has children which represent members. Whenever we encounter a DIE + * that has children or other values or types associated with it, we recursively + * process those children first so that way we can then refer to the generated + * CTF type id while processing its parent. This reduces the amount of unknowns + * and fixups that we need. It also ensures that we don't accidentally add types + * that an overzealous compiler might add to the DWARF data but aren't used by + * anything in the system. + * + * Once we do a conversion, we store a mapping in an AVL tree that goes from the + * DWARF's die offset, which is relative to the given compilation unit), to a + * ctf_id_t. + * + * Unfortunately, some compilers actually will emit duplicate entries for a + * given type that look similar, but aren't quite. To that end, we go through + * and do a variant on a merge once we're done processing a single compilation + * unit which deduplicates all of the types that are in the unit. + * + * Finally, if we encounter an object that has multiple compilation units, then + * we'll convert all of the compilation units separately and then do a merge, so + * that way we can result in one single ctf_file_t that represents everything + * for the object. + * + * Conversion Steps + * ---------------- + * + * Because a given object we've been given to convert may have multiple + * compilation units, we break the work into two halves. The first half + * processes each compilation unit (potentially in parallel) and then the second + * half optionally merges all of the dies in the first half. First, we'll cover + * what's involved in converting a single ctf_die_t's dwarf to CTF. This covers + * the work done in ctf_dwarf_convert_one(). + * + * An individual ctf_die_t, which represents a compilation unit, is converted to + * CTF in a series of multiple passes. + * + * Pass 1: During the first pass we walk all of the dies and if we find a + * function, variable, struct, union, enum or typedef, we recursively transform + * all of its types. We don't recurse or process everything, because we don't + * want to add some of the types that compilers may add which are effectively + * unused. + * + * During pass 1, if we encounter any structures or unions we mark them for + * fixing up later. This is necessary because we may not be able to determine + * the full size of a structure at the beginning of time. This will happen if + * the DWARF attribute DW_AT_byte_size is not present for a member. Because of + * this possibility we defer adding members to structures or even converting + * them during pass 1 and save that for pass 2. Adding all of the base + * structures without any of their members helps deal with any circular + * dependencies that we might encounter. + * + * Pass 2: This pass is used to do the first half of fixing up structures and + * unions. Rather than walk the entire type space again, we actually walk the + * list of structures and unions that we marked for later fixing up. Here, we + * iterate over every structure and add members to the underlying ctf_file_t, + * but not to the structs themselves. One might wonder why we don't, and the + * main reason is that libctf requires a ctf_update() be done before adding the + * members to structures or unions. + * + * Pass 3: This pass is used to do the second half of fixing up structures and + * unions. During this part we always go through and add members to structures + * and unions that we added to the container in the previous pass. In addition, + * we set the structure and union's actual size, which may have additional + * padding added by the compiler, it isn't simply the last offset. DWARF always + * guarantees an attribute exists for this. Importantly no ctf_id_t's change + * during pass 2. + * + * Pass 4: The next phase is to add CTF entries for all of the symbols and + * variables that are present in this die. During pass 1 we added entries to a + * map for each variable and function. During this pass, we iterate over the + * symbol table and when we encounter a symbol that we have in our lists of + * translated information which matches, we then add it to the ctf_file_t. + * + * Pass 5: Here we go and look for any weak symbols and functions and see if + * they match anything that we recognize. If so, then we add type information + * for them at this point based on the matching type. + * + * Pass 6: This pass is actually a variant on a merge. The traditional merge + * process expects there to be no duplicate types. As such, at the end of + * conversion, we do a dedup on all of the types in the system. The + * deduplication process is described in lib/libctf/common/ctf_merge.c. + * + * Once pass 6 is done, we've finished processing the individual compilation + * unit. + * + * The following steps reflect the general process of doing a conversion. + * + * 1) Walk the dwarf section and determine the number of compilation units + * 2) Create a ctf_die_t for each compilation unit + * 3) Add all ctf_die_t's to a workq + * 4) Have the workq process each die with ctf_dwarf_convert_one. This itself + * is comprised of several steps, which were already enumerated. + * 5) If we have multiple dies, we do a ctf merge of all the dies. The mechanics + * of the merge are discussed in lib/libctf/common/ctf_merge.c. + * 6) Free everything up and return a ctf_file_t to the user. If we only had a + * single compilation unit, then we give that to the user. Otherwise, we + * return the merged ctf_file_t. + * + * Threading + * --------- + * + * The process has been designed to be amenable to threading. Each compilation + * unit has its own type stream, therefore the logical place to divide and + * conquer is at the compilation unit. Each ctf_die_t has been built to be able + * to be processed independently of the others. It has its own libdwarf handle, + * as a given libdwarf handle may only be used by a single thread at a time. + * This allows the various ctf_die_t's to be processed in parallel by different + * threads. + * + * All of the ctf_die_t's are loaded into a workq which allows for a number of + * threads to be specified and used as a thread pool to process all of the + * queued work. We set the number of threads to use in the workq equal to the + * number of threads that the user has specified. + * + * After all of the compilation units have been drained, we use the same number + * of threads when performing a merge of multiple compilation units, if they + * exist. + * + * While all of these different parts do support and allow for multiple threads, + * it's important that when only a single thread is specified, that it be the + * calling thread. This allows the conversion routines to be used in a context + * that doesn't allow additional threads, such as rtld. + * + * Common DWARF Mechanics and Notes + * -------------------------------- + * + * At this time, we really only support DWARFv2, though support for DWARFv4 is + * mostly there. There is no intent to support DWARFv3. + * + * Generally types for something are stored in the DW_AT_type attribute. For + * example, a function's return type will be stored in the local DW_AT_type + * attribute while the arguments will be in child DIEs. There are also various + * times when we don't have any DW_AT_type. In that case, the lack of a type + * implies, at least for C, that it's C type is void. Because DWARF doesn't emit + * one, we have a synthetic void type that we create and manipulate instead and + * pass it off to consumers on an as-needed basis. If nothing has a void type, + * it will not be emitted. + * + * Architecture Specific Parts + * --------------------------- + * + * The CTF tooling encodes various information about the various architectures + * in the system. Importantly, the tool assumes that every architecture has a + * data model where long and pointer are the same size. This is currently the + * case, as the two data models illumos supports are ILP32 and LP64. + * + * In addition, we encode the mapping of various floating point sizes to various + * types for each architecture. If a new architecture is being added, it should + * be added to the list. The general design of the ctf conversion tools is to be + * architecture independent. eg. any of the tools here should be able to convert + * any architecture's DWARF into ctf; however, this has not been rigorously + * tested and more importantly, the ctf routines don't currently write out the + * data in an endian-aware form, they only use that of the currently running + * library. + */ + +#include <libctf_impl.h> +#include <sys/avl.h> +#include <sys/debug.h> +#include <gelf.h> +#include <libdwarf.h> +#include <dwarf.h> +#include <libgen.h> +#include <workq.h> +#include <errno.h> + +#define DWARF_VERSION_TWO 2 +#define DWARF_VARARGS_NAME "..." + +/* + * Dwarf may refer recursively to other types that we've already processed. To + * see if we've already converted them, we look them up in an AVL tree that's + * sorted by the DWARF id. + */ +typedef struct ctf_dwmap { + avl_node_t cdm_avl; + Dwarf_Off cdm_off; + Dwarf_Die cdm_die; + ctf_id_t cdm_id; + boolean_t cdm_fix; +} ctf_dwmap_t; + +typedef struct ctf_dwvar { + ctf_list_t cdv_list; + char *cdv_name; + ctf_id_t cdv_type; + boolean_t cdv_global; +} ctf_dwvar_t; + +typedef struct ctf_dwfunc { + ctf_list_t cdf_list; + char *cdf_name; + ctf_funcinfo_t cdf_fip; + ctf_id_t *cdf_argv; + boolean_t cdf_global; +} ctf_dwfunc_t; + +typedef struct ctf_dwbitf { + ctf_list_t cdb_list; + ctf_id_t cdb_base; + uint_t cdb_nbits; + ctf_id_t cdb_id; +} ctf_dwbitf_t; + +/* + * The ctf_die_t represents a single top-level DWARF die unit. While generally, + * the typical object file hs only a single die, if we're asked to convert + * something that's been linked from multiple sources, multiple dies will exist. + */ +typedef struct ctf_die { + Elf *cd_elf; /* shared libelf handle */ + char *cd_name; /* basename of the DIE */ + ctf_merge_t *cd_cmh; /* merge handle */ + ctf_list_t cd_vars; /* List of variables */ + ctf_list_t cd_funcs; /* List of functions */ + ctf_list_t cd_bitfields; /* Bit field members */ + Dwarf_Debug cd_dwarf; /* shared libdwarf handle */ + Dwarf_Die cd_cu; /* libdwarf compilation unit */ + Dwarf_Off cd_cuoff; /* cu's offset */ + Dwarf_Off cd_maxoff; /* maximum offset */ + ctf_file_t *cd_ctfp; /* output CTF file */ + avl_tree_t cd_map; /* map die offsets to CTF types */ + char *cd_errbuf; /* error message buffer */ + size_t cd_errlen; /* error message buffer length */ + size_t cd_ptrsz; /* object's pointer size */ + boolean_t cd_bigend; /* is it big endian */ + uint_t cd_mach; /* machine type */ + ctf_id_t cd_voidtid; /* void pointer */ + ctf_id_t cd_longtid; /* id for a 'long' */ +} ctf_die_t; + +static int ctf_dwarf_offset(ctf_die_t *, Dwarf_Die, Dwarf_Off *); +static int ctf_dwarf_convert_die(ctf_die_t *, Dwarf_Die); +static int ctf_dwarf_convert_type(ctf_die_t *, Dwarf_Die, ctf_id_t *, int); + +static int ctf_dwarf_function_count(ctf_die_t *, Dwarf_Die, ctf_funcinfo_t *, + boolean_t); +static int ctf_dwarf_convert_fargs(ctf_die_t *, Dwarf_Die, ctf_funcinfo_t *, + ctf_id_t *); + +typedef int (ctf_dwarf_symtab_f)(ctf_die_t *, const GElf_Sym *, ulong_t, + const char *, const char *, void *); + +/* + * This is a generic way to set a CTF Conversion backend error depending on what + * we were doing. Unless it was one of a specific set of errors that don't + * indicate a programming / translation bug, eg. ENOMEM, then we transform it + * into a CTF backend error and fill in the error buffer. + */ +static int +ctf_dwarf_error(ctf_die_t *cdp, ctf_file_t *cfp, int err, const char *fmt, ...) +{ + va_list ap; + int ret; + size_t off = 0; + ssize_t rem = cdp->cd_errlen; + if (cfp != NULL) + err = ctf_errno(cfp); + + if (err == ENOMEM) + return (err); + + ret = snprintf(cdp->cd_errbuf, rem, "die %s: ", cdp->cd_name); + if (ret < 0) + goto err; + off += ret; + rem = MAX(rem - ret, 0); + + va_start(ap, fmt); + ret = vsnprintf(cdp->cd_errbuf + off, rem, fmt, ap); + va_end(ap); + if (ret < 0) + goto err; + + off += ret; + rem = MAX(rem - ret, 0); + if (fmt[strlen(fmt) - 1] != '\n') { + (void) snprintf(cdp->cd_errbuf + off, rem, + ": %s\n", ctf_errmsg(err)); + } + va_end(ap); + return (ECTF_CONVBKERR); + +err: + cdp->cd_errbuf[0] = '\0'; + return (ECTF_CONVBKERR); +} + +/* + * DWARF often ops to put no explicit type to describe a void type. eg. if we + * have a reference type whose DW_AT_type member doesn't exist, then we should + * instead assume it points to void. Because this isn't represented, we + * instead cause it to come into existence. + */ +static ctf_id_t +ctf_dwarf_void(ctf_die_t *cdp) +{ + if (cdp->cd_voidtid == CTF_ERR) { + ctf_encoding_t enc = { CTF_INT_SIGNED, 0, 0 }; + cdp->cd_voidtid = ctf_add_integer(cdp->cd_ctfp, CTF_ADD_ROOT, + "void", &enc); + if (cdp->cd_voidtid == CTF_ERR) { + (void) snprintf(cdp->cd_errbuf, cdp->cd_errlen, + "failed to create void type: %s\n", + ctf_errmsg(ctf_errno(cdp->cd_ctfp))); + } + } + + return (cdp->cd_voidtid); +} + +/* + * There are many different forms that an array index may take. However, we just + * always force it to be of a type long no matter what. Therefore we use this to + * have a single instance of long across everything. + */ +static ctf_id_t +ctf_dwarf_long(ctf_die_t *cdp) +{ + if (cdp->cd_longtid == CTF_ERR) { + ctf_encoding_t enc; + + enc.cte_format = CTF_INT_SIGNED; + enc.cte_offset = 0; + /* All illumos systems are LP */ + enc.cte_bits = cdp->cd_ptrsz * 8; + cdp->cd_longtid = ctf_add_integer(cdp->cd_ctfp, CTF_ADD_NONROOT, + "long", &enc); + if (cdp->cd_longtid == CTF_ERR) { + (void) snprintf(cdp->cd_errbuf, cdp->cd_errlen, + "failed to create long type: %s\n", + ctf_errmsg(ctf_errno(cdp->cd_ctfp))); + } + + } + + return (cdp->cd_longtid); +} + +static int +ctf_dwmap_comp(const void *a, const void *b) +{ + const ctf_dwmap_t *ca = a; + const ctf_dwmap_t *cb = b; + + if (ca->cdm_off > cb->cdm_off) + return (1); + if (ca->cdm_off < cb->cdm_off) + return (-1); + return (0); +} + +static int +ctf_dwmap_add(ctf_die_t *cdp, ctf_id_t id, Dwarf_Die die, boolean_t fix) +{ + int ret; + avl_index_t index; + ctf_dwmap_t *dwmap; + Dwarf_Off off; + + VERIFY(id > 0 && id < CTF_MAX_TYPE); + + if ((ret = ctf_dwarf_offset(cdp, die, &off)) != 0) + return (ret); + + if ((dwmap = ctf_alloc(sizeof (ctf_dwmap_t))) == NULL) + return (ENOMEM); + + dwmap->cdm_die = die; + dwmap->cdm_off = off; + dwmap->cdm_id = id; + dwmap->cdm_fix = fix; + + ctf_dprintf("dwmap: %p %x->%d\n", dwmap, (uint32_t)off, id); + VERIFY(avl_find(&cdp->cd_map, dwmap, &index) == NULL); + avl_insert(&cdp->cd_map, dwmap, index); + return (0); +} + +static int +ctf_dwarf_attribute(ctf_die_t *cdp, Dwarf_Die die, Dwarf_Half name, + Dwarf_Attribute *attrp) +{ + int ret; + Dwarf_Error derr; + + if ((ret = dwarf_attr(die, name, attrp, &derr)) == DW_DLV_OK) + return (0); + if (ret == DW_DLV_NO_ENTRY) { + *attrp = NULL; + return (ENOENT); + } + (void) snprintf(cdp->cd_errbuf, cdp->cd_errlen, + "failed to get attribute for type: %s\n", + dwarf_errmsg(derr)); + return (ECTF_CONVBKERR); +} + +static int +ctf_dwarf_ref(ctf_die_t *cdp, Dwarf_Die die, Dwarf_Half name, Dwarf_Off *refp) +{ + int ret; + Dwarf_Attribute attr; + Dwarf_Error derr; + + if ((ret = ctf_dwarf_attribute(cdp, die, name, &attr)) != 0) + return (ret); + + if (dwarf_formref(attr, refp, &derr) == DW_DLV_OK) { + dwarf_dealloc(cdp->cd_dwarf, attr, DW_DLA_ATTR); + return (0); + } + + (void) snprintf(cdp->cd_errbuf, cdp->cd_errlen, + "failed to get unsigned attribute for type: %s\n", + dwarf_errmsg(derr)); + return (ECTF_CONVBKERR); +} + +static int +ctf_dwarf_refdie(ctf_die_t *cdp, Dwarf_Die die, Dwarf_Half name, + Dwarf_Die *diep) +{ + int ret; + Dwarf_Off off; + Dwarf_Error derr; + + if ((ret = ctf_dwarf_ref(cdp, die, DW_AT_type, &off)) != 0) + return (ret); + + off += cdp->cd_cuoff; + if ((ret = dwarf_offdie(cdp->cd_dwarf, off, diep, &derr)) != + DW_DLV_OK) { + (void) snprintf(cdp->cd_errbuf, cdp->cd_errlen, + "failed to get die from offset %llu: %s\n", + off, dwarf_errmsg(derr)); + return (ECTF_CONVBKERR); + } + + return (0); +} + +static int +ctf_dwarf_signed(ctf_die_t *cdp, Dwarf_Die die, Dwarf_Half name, + Dwarf_Signed *valp) +{ + int ret; + Dwarf_Attribute attr; + Dwarf_Error derr; + + if ((ret = ctf_dwarf_attribute(cdp, die, name, &attr)) != 0) + return (ret); + + if (dwarf_formsdata(attr, valp, &derr) == DW_DLV_OK) { + dwarf_dealloc(cdp->cd_dwarf, attr, DW_DLA_ATTR); + return (0); + } + + (void) snprintf(cdp->cd_errbuf, cdp->cd_errlen, + "failed to get unsigned attribute for type: %s\n", + dwarf_errmsg(derr)); + return (ECTF_CONVBKERR); +} + +static int +ctf_dwarf_unsigned(ctf_die_t *cdp, Dwarf_Die die, Dwarf_Half name, + Dwarf_Unsigned *valp) +{ + int ret; + Dwarf_Attribute attr; + Dwarf_Error derr; + + if ((ret = ctf_dwarf_attribute(cdp, die, name, &attr)) != 0) + return (ret); + + if (dwarf_formudata(attr, valp, &derr) == DW_DLV_OK) { + dwarf_dealloc(cdp->cd_dwarf, attr, DW_DLA_ATTR); + return (0); + } + + (void) snprintf(cdp->cd_errbuf, cdp->cd_errlen, + "failed to get unsigned attribute for type: %s\n", + dwarf_errmsg(derr)); + return (ECTF_CONVBKERR); +} + +static int +ctf_dwarf_boolean(ctf_die_t *cdp, Dwarf_Die die, Dwarf_Half name, + Dwarf_Bool *val) +{ + int ret; + Dwarf_Attribute attr; + Dwarf_Error derr; + + if ((ret = ctf_dwarf_attribute(cdp, die, name, &attr)) != 0) + return (ret); + + if (dwarf_formflag(attr, val, &derr) == DW_DLV_OK) { + dwarf_dealloc(cdp->cd_dwarf, attr, DW_DLA_ATTR); + return (0); + } + + (void) snprintf(cdp->cd_errbuf, cdp->cd_errlen, + "failed to get boolean attribute for type: %s\n", + dwarf_errmsg(derr)); + + return (ECTF_CONVBKERR); +} + +static int +ctf_dwarf_string(ctf_die_t *cdp, Dwarf_Die die, Dwarf_Half name, char **strp) +{ + int ret; + char *s; + Dwarf_Attribute attr; + Dwarf_Error derr; + + *strp = NULL; + if ((ret = ctf_dwarf_attribute(cdp, die, name, &attr)) != 0) + return (ret); + + if (dwarf_formstring(attr, &s, &derr) == DW_DLV_OK) { + if ((*strp = ctf_strdup(s)) == NULL) + ret = ENOMEM; + else + ret = 0; + dwarf_dealloc(cdp->cd_dwarf, attr, DW_DLA_ATTR); + return (ret); + } + + (void) snprintf(cdp->cd_errbuf, cdp->cd_errlen, + "failed to get string attribute for type: %s\n", + dwarf_errmsg(derr)); + return (ECTF_CONVBKERR); +} + +static int +ctf_dwarf_member_location(ctf_die_t *cdp, Dwarf_Die die, Dwarf_Unsigned *valp) +{ + int ret; + Dwarf_Error derr; + Dwarf_Attribute attr; + Dwarf_Locdesc *loc; + Dwarf_Signed locnum; + + if ((ret = ctf_dwarf_attribute(cdp, die, DW_AT_data_member_location, + &attr)) != 0) + return (ret); + + if (dwarf_loclist(attr, &loc, &locnum, &derr) != DW_DLV_OK) { + (void) snprintf(cdp->cd_errbuf, cdp->cd_errlen, + "failed to obtain location list for member offset: %s", + dwarf_errmsg(derr)); + dwarf_dealloc(cdp->cd_dwarf, attr, DW_DLA_ATTR); + return (ECTF_CONVBKERR); + } + dwarf_dealloc(cdp->cd_dwarf, attr, DW_DLA_ATTR); + + if (locnum != 1 || loc->ld_s->lr_atom != DW_OP_plus_uconst) { + (void) snprintf(cdp->cd_errbuf, cdp->cd_errlen, + "failed to parse location structure for member"); + dwarf_dealloc(cdp->cd_dwarf, loc->ld_s, DW_DLA_LOC_BLOCK); + dwarf_dealloc(cdp->cd_dwarf, loc, DW_DLA_LOCDESC); + return (ECTF_CONVBKERR); + } + + *valp = loc->ld_s->lr_number; + + dwarf_dealloc(cdp->cd_dwarf, loc->ld_s, DW_DLA_LOC_BLOCK); + dwarf_dealloc(cdp->cd_dwarf, loc, DW_DLA_LOCDESC); + return (0); +} + + +static int +ctf_dwarf_offset(ctf_die_t *cdp, Dwarf_Die die, Dwarf_Off *offsetp) +{ + Dwarf_Error derr; + + if (dwarf_dieoffset(die, offsetp, &derr) == DW_DLV_OK) + return (0); + + (void) snprintf(cdp->cd_errbuf, cdp->cd_errlen, + "failed to get die offset: %s\n", + dwarf_errmsg(derr)); + return (ECTF_CONVBKERR); +} + +static int +ctf_dwarf_tag(ctf_die_t *cdp, Dwarf_Die die, Dwarf_Half *tagp) +{ + Dwarf_Error derr; + + if (dwarf_tag(die, tagp, &derr) == DW_DLV_OK) + return (0); + + (void) snprintf(cdp->cd_errbuf, cdp->cd_errlen, + "failed to get tag type: %s\n", + dwarf_errmsg(derr)); + return (ECTF_CONVBKERR); +} + +static int +ctf_dwarf_sib(ctf_die_t *cdp, Dwarf_Die base, Dwarf_Die *sibp) +{ + Dwarf_Error derr; + int ret; + + *sibp = NULL; + ret = dwarf_siblingof(cdp->cd_dwarf, base, sibp, &derr); + if (ret == DW_DLV_OK || ret == DW_DLV_NO_ENTRY) + return (0); + + (void) snprintf(cdp->cd_errbuf, cdp->cd_errlen, + "failed to sibling from die: %s\n", + dwarf_errmsg(derr)); + return (ECTF_CONVBKERR); +} + +static int +ctf_dwarf_child(ctf_die_t *cdp, Dwarf_Die base, Dwarf_Die *childp) +{ + Dwarf_Error derr; + int ret; + + *childp = NULL; + ret = dwarf_child(base, childp, &derr); + if (ret == DW_DLV_OK || ret == DW_DLV_NO_ENTRY) + return (0); + + (void) snprintf(cdp->cd_errbuf, cdp->cd_errlen, + "failed to child from die: %s\n", + dwarf_errmsg(derr)); + return (ECTF_CONVBKERR); +} + +/* + * Compilers disagree on what to do to determine if something has global + * visiblity. Traditionally gcc has used DW_AT_external to indicate this while + * Studio has used DW_AT_visibility. We check DW_AT_visibility first and then + * fall back to DW_AT_external. Lack of DW_AT_external implies that it is not. + */ +static int +ctf_dwarf_isglobal(ctf_die_t *cdp, Dwarf_Die die, boolean_t *igp) +{ + int ret; + Dwarf_Signed vis; + Dwarf_Bool ext; + + if ((ret = ctf_dwarf_signed(cdp, die, DW_AT_visibility, &vis)) == 0) { + *igp = vis == DW_VIS_exported; + return (0); + } else if (ret != ENOENT) { + return (ret); + } + + if ((ret = ctf_dwarf_boolean(cdp, die, DW_AT_external, &ext)) != 0) { + if (ret == ENOENT) { + *igp = B_FALSE; + return (0); + } + return (ret); + } + *igp = ext != 0 ? B_TRUE : B_FALSE; + return (0); +} + +static int +ctf_dwarf_die_elfenc(Elf *elf, ctf_die_t *cdp, char *errbuf, size_t errlen) +{ + GElf_Ehdr ehdr; + + if (gelf_getehdr(elf, &ehdr) == NULL) { + (void) snprintf(errbuf, errlen, + "failed to get ELF header: %s\n", + elf_errmsg(elf_errno())); + return (ECTF_CONVBKERR); + } + + cdp->cd_mach = ehdr.e_machine; + + if (ehdr.e_ident[EI_CLASS] == ELFCLASS32) { + cdp->cd_ptrsz = 4; + VERIFY(ctf_setmodel(cdp->cd_ctfp, CTF_MODEL_ILP32) == 0); + } else if (ehdr.e_ident[EI_CLASS] == ELFCLASS64) { + cdp->cd_ptrsz = 8; + VERIFY(ctf_setmodel(cdp->cd_ctfp, CTF_MODEL_LP64) == 0); + } else { + (void) snprintf(errbuf, errlen, + "unknown ELF class %d", ehdr.e_ident[EI_CLASS]); + return (ECTF_CONVBKERR); + } + + if (ehdr.e_ident[EI_DATA] == ELFDATA2LSB) { + cdp->cd_bigend = B_FALSE; + } else if (ehdr.e_ident[EI_DATA] == ELFDATA2MSB) { + cdp->cd_bigend = B_TRUE; + } else { + (void) snprintf(errbuf, errlen, + "unknown ELF data encoding: %d", ehdr.e_ident[EI_DATA]); + return (ECTF_CONVBKERR); + } + + return (0); +} + +typedef struct ctf_dwarf_fpent { + size_t cdfe_size; + uint_t cdfe_enc[3]; +} ctf_dwarf_fpent_t; + +typedef struct ctf_dwarf_fpmap { + uint_t cdf_mach; + ctf_dwarf_fpent_t cdf_ents[4]; +} ctf_dwarf_fpmap_t; + +static const ctf_dwarf_fpmap_t ctf_dwarf_fpmaps[] = { + { EM_SPARC, { + { 4, { CTF_FP_SINGLE, CTF_FP_CPLX, CTF_FP_IMAGRY } }, + { 8, { CTF_FP_DOUBLE, CTF_FP_DCPLX, CTF_FP_DIMAGRY } }, + { 16, { CTF_FP_LDOUBLE, CTF_FP_LDCPLX, CTF_FP_LDIMAGRY } }, + { 0, { 0 } } + } }, + { EM_SPARC32PLUS, { + { 4, { CTF_FP_SINGLE, CTF_FP_CPLX, CTF_FP_IMAGRY } }, + { 8, { CTF_FP_DOUBLE, CTF_FP_DCPLX, CTF_FP_DIMAGRY } }, + { 16, { CTF_FP_LDOUBLE, CTF_FP_LDCPLX, CTF_FP_LDIMAGRY } }, + { 0, { 0 } } + } }, + { EM_SPARCV9, { + { 4, { CTF_FP_SINGLE, CTF_FP_CPLX, CTF_FP_IMAGRY } }, + { 8, { CTF_FP_DOUBLE, CTF_FP_DCPLX, CTF_FP_DIMAGRY } }, + { 16, { CTF_FP_LDOUBLE, CTF_FP_LDCPLX, CTF_FP_LDIMAGRY } }, + { 0, { 0 } } + } }, + { EM_386, { + { 4, { CTF_FP_SINGLE, CTF_FP_CPLX, CTF_FP_IMAGRY } }, + { 8, { CTF_FP_DOUBLE, CTF_FP_DCPLX, CTF_FP_DIMAGRY } }, + { 12, { CTF_FP_LDOUBLE, CTF_FP_LDCPLX, CTF_FP_LDIMAGRY } }, + { 0, { 0 } } + } }, + { EM_X86_64, { + { 4, { CTF_FP_SINGLE, CTF_FP_CPLX, CTF_FP_IMAGRY } }, + { 8, { CTF_FP_DOUBLE, CTF_FP_DCPLX, CTF_FP_DIMAGRY } }, + { 16, { CTF_FP_LDOUBLE, CTF_FP_LDCPLX, CTF_FP_LDIMAGRY } }, + { 0, { 0 } } + } }, + { EM_NONE } +}; + +static int +ctf_dwarf_float_base(ctf_die_t *cdp, Dwarf_Signed type, ctf_encoding_t *enc) +{ + const ctf_dwarf_fpmap_t *map = &ctf_dwarf_fpmaps[0]; + const ctf_dwarf_fpent_t *ent; + uint_t col = 0, mult = 1; + + for (map = &ctf_dwarf_fpmaps[0]; map->cdf_mach != EM_NONE; map++) { + if (map->cdf_mach == cdp->cd_mach) + break; + } + + if (map->cdf_mach == EM_NONE) { + (void) snprintf(cdp->cd_errbuf, cdp->cd_errlen, + "Unsupported machine type: %d\n", cdp->cd_mach); + return (ENOTSUP); + } + + if (type == DW_ATE_complex_float) { + mult = 2; + col = 1; + } else if (type == DW_ATE_imaginary_float || + type == DW_ATE_SUN_imaginary_float) { + col = 2; + } + + ent = &map->cdf_ents[0]; + for (ent = &map->cdf_ents[0]; ent->cdfe_size != 0; ent++) { + if (ent->cdfe_size * mult * 8 == enc->cte_bits) { + enc->cte_format = ent->cdfe_enc[col]; + return (0); + } + } + + (void) snprintf(cdp->cd_errbuf, cdp->cd_errlen, + "failed to find valid fp mapping for encoding %d, size %d bits\n", + type, enc->cte_bits); + return (EINVAL); +} + +static int +ctf_dwarf_dwarf_base(ctf_die_t *cdp, Dwarf_Die die, int *kindp, + ctf_encoding_t *enc) +{ + int ret; + Dwarf_Signed type; + + if ((ret = ctf_dwarf_signed(cdp, die, DW_AT_encoding, &type)) != 0) + return (ret); + + switch (type) { + case DW_ATE_unsigned: + case DW_ATE_address: + *kindp = CTF_K_INTEGER; + enc->cte_format = 0; + break; + case DW_ATE_unsigned_char: + *kindp = CTF_K_INTEGER; + enc->cte_format = CTF_INT_CHAR; + break; + case DW_ATE_signed: + *kindp = CTF_K_INTEGER; + enc->cte_format = CTF_INT_SIGNED; + break; + case DW_ATE_signed_char: + *kindp = CTF_K_INTEGER; + enc->cte_format = CTF_INT_SIGNED | CTF_INT_CHAR; + break; + case DW_ATE_boolean: + *kindp = CTF_K_INTEGER; + enc->cte_format = CTF_INT_SIGNED | CTF_INT_BOOL; + break; + case DW_ATE_float: + case DW_ATE_complex_float: + case DW_ATE_imaginary_float: + case DW_ATE_SUN_imaginary_float: + case DW_ATE_SUN_interval_float: + *kindp = CTF_K_FLOAT; + if ((ret = ctf_dwarf_float_base(cdp, type, enc)) != 0) + return (ret); + break; + default: + (void) snprintf(cdp->cd_errbuf, cdp->cd_errlen, + "encountered unkown DWARF encoding: %d", type); + return (ECTF_CONVBKERR); + } + + return (0); +} + +/* + * Different compilers (at least GCC and Studio) use different names for types. + * This parses the types and attempts to unify them. If this fails, we just fall + * back to using the DWARF itself. + */ +static int +ctf_dwarf_parse_base(const char *name, int *kindp, ctf_encoding_t *enc, + char **newnamep) +{ + char buf[256]; + char *base, *c; + int nlong = 0, nshort = 0, nchar = 0, nint = 0; + int sign = 1; + + if (strlen(name) + 1 > sizeof (buf)) + return (EINVAL); + + (void) strlcpy(buf, name, sizeof (buf)); + for (c = strtok(buf, " "); c != NULL; c = strtok(NULL, " ")) { + if (strcmp(c, "signed") == 0) { + sign = 1; + } else if (strcmp(c, "unsigned") == 0) { + sign = 0; + } else if (strcmp(c, "long") == 0) { + nlong++; + } else if (strcmp(c, "char") == 0) { + nchar++; + } else if (strcmp(c, "short") == 0) { + nshort++; + } else if (strcmp(c, "int") == 0) { + nint++; + } else { + /* + * If we don't recognize any of the tokens, we'll tell + * the caller to fall back to the dwarf-provided + * encoding information. + */ + return (EINVAL); + } + } + + if (nchar > 1 || nshort > 1 || nint > 1 || nlong > 2) + return (EINVAL); + + if (nchar > 0) { + if (nlong > 0 || nshort > 0 || nint > 0) + return (EINVAL); + base = "char"; + } else if (nshort > 0) { + if (nlong > 0) + return (EINVAL); + base = "short"; + } else if (nlong > 0) { + base = "long"; + } else { + base = "int"; + } + + if (nchar > 0) + enc->cte_format = CTF_INT_CHAR; + else + enc->cte_format = 0; + + if (sign > 0) + enc->cte_format |= CTF_INT_SIGNED; + + (void) snprintf(buf, sizeof (buf), "%s%s%s", + (sign ? "" : "unsigned "), + (nlong > 1 ? "long " : ""), + base); + + *newnamep = ctf_strdup(buf); + if (*newnamep == NULL) + return (ENOMEM); + *kindp = CTF_K_INTEGER; + return (0); +} + +static int +ctf_dwarf_create_base(ctf_die_t *cdp, Dwarf_Die die, ctf_id_t *idp, int isroot, + Dwarf_Off off) +{ + int ret; + char *name, *nname; + Dwarf_Unsigned sz; + int kind; + ctf_encoding_t enc; + ctf_id_t id; + + if ((ret = ctf_dwarf_string(cdp, die, DW_AT_name, &name)) != 0) + return (ret); + if ((ret = ctf_dwarf_unsigned(cdp, die, DW_AT_byte_size, &sz)) != 0) { + goto out; + } + ctf_dprintf("Creating base type %s from off %llu, size: %d\n", name, + off, sz); + + bzero(&enc, sizeof (ctf_encoding_t)); + enc.cte_bits = sz * 8; + if ((ret = ctf_dwarf_parse_base(name, &kind, &enc, &nname)) == 0) { + ctf_free(name, strlen(name) + 1); + name = nname; + } else { + if (ret != EINVAL) + return (ret); + ctf_dprintf("falling back to dwarf for base type %s\n", name); + if ((ret = ctf_dwarf_dwarf_base(cdp, die, &kind, &enc)) != 0) + return (ret); + } + + id = ctf_add_encoded(cdp->cd_ctfp, isroot, name, &enc, kind); + if (id == CTF_ERR) { + ret = ctf_errno(cdp->cd_ctfp); + } else { + *idp = id; + ret = ctf_dwmap_add(cdp, id, die, B_FALSE); + } +out: + ctf_free(name, strlen(name) + 1); + return (ret); +} + +/* + * Getting a member's offset is a surprisingly intricate dance. It works as + * follows: + * + * 1) If we're in DWARFv4, then we either have a DW_AT_data_bit_offset or we + * have a DW_AT_data_member_location. We won't have both. Thus we check first + * for DW_AT_data_bit_offset, and if it exists, we're set. + * + * Next, if we have a bitfield and we don't ahve a DW_AT_data_bit_offset, then + * we have to grab the data location and use the following dance: + * + * 2) Gather the set of DW_AT_byte_size, DW_AT_bit_offset, and DW_AT_bit_size. + * Of course, the DW_AT_byte_size may be omitted, even though it isn't always. + * When it's been omitted, we then have to say that the size is that of the + * underlying type, which forces that to be after a ctf_update(). Here, we have + * to do different things based on whether or not we're using big endian or + * little endian to obtain the proper offset. + */ +static int +ctf_dwarf_member_offset(ctf_die_t *cdp, Dwarf_Die die, ctf_id_t mid, + ulong_t *offp) +{ + int ret; + Dwarf_Unsigned loc, bitsz, bytesz, bitoff; + size_t off, tsz; + + if ((ret = ctf_dwarf_unsigned(cdp, die, DW_AT_data_bit_offset, + &loc)) == 0) { + *offp = loc; + return (0); + } else if (ret != ENOENT) { + return (ret); + } + + if ((ret = ctf_dwarf_member_location(cdp, die, &loc)) != 0) + return (ret); + off = loc * 8; + + if ((ret = ctf_dwarf_unsigned(cdp, die, DW_AT_bit_offset, + &bitoff)) != 0) { + if (ret != ENOENT) + return (ret); + *offp = off; + return (0); + } + + /* At this point we have to have DW_AT_bit_size */ + if ((ret = ctf_dwarf_unsigned(cdp, die, DW_AT_bit_size, &bitsz)) != 0) + return (ret); + + if ((ret = ctf_dwarf_unsigned(cdp, die, DW_AT_byte_size, + &bytesz)) != 0) { + if (ret != ENOENT) + return (ret); + if ((tsz = ctf_type_size(cdp->cd_ctfp, mid)) == CTF_ERR) { + int e = ctf_errno(cdp->cd_ctfp); + (void) snprintf(cdp->cd_errbuf, cdp->cd_errlen, + "failed to get type size: %s", ctf_errmsg(e)); + return (ECTF_CONVBKERR); + } + } else { + tsz = bytesz; + } + tsz *= 8; + if (cdp->cd_bigend == B_TRUE) { + *offp = off + bitoff; + } else { + *offp = off + tsz - bitoff - bitsz; + } + + return (0); +} + +/* + * We need to determine if the member in question is a bitfield. If it is, then + * we need to go through and create a new type that's based on the actual base + * type, but has a different size. We also rename the type as a result to help + * deal with future collisions. + * + * Here we need to look and see if we have a DW_AT_bit_size value. If we have a + * bit size member and it does not equal the byte size member, then we need to + * create a bitfield type based on this. + * + * Note: When we support DWARFv4, there may be a chance that we ned to also + * search for the DW_AT_byte_size if we don't have a DW_AT_bit_size member. + */ +static int +ctf_dwarf_member_bitfield(ctf_die_t *cdp, Dwarf_Die die, ctf_id_t *idp) +{ + int ret; + Dwarf_Unsigned bitsz; + ctf_encoding_t e; + ctf_dwbitf_t *cdb; + ctf_dtdef_t *dtd; + ctf_id_t base = *idp; + int kind; + + if ((ret = ctf_dwarf_unsigned(cdp, die, DW_AT_bit_size, &bitsz)) != 0) { + if (ret == ENOENT) + return (0); + return (ret); + } + + ctf_dprintf("Trying to deal with bitfields on %d:%d\n", base, bitsz); + /* + * Given that we now have a bitsize, time to go do something about it. + * We're going to create a new type based on the current one, but first + * we need to find the base type. This means we need to traverse any + * typedef's, consts, and volatiles until we get to what should be + * something of type integer or enumeration. + */ + VERIFY(bitsz < UINT32_MAX); + dtd = ctf_dtd_lookup(cdp->cd_ctfp, base); + VERIFY(dtd != NULL); + kind = CTF_INFO_KIND(dtd->dtd_data.ctt_info); + while (kind == CTF_K_TYPEDEF || kind == CTF_K_CONST || + kind == CTF_K_VOLATILE) { + dtd = ctf_dtd_lookup(cdp->cd_ctfp, dtd->dtd_data.ctt_type); + VERIFY(dtd != NULL); + kind = CTF_INFO_KIND(dtd->dtd_data.ctt_info); + } + ctf_dprintf("got kind %d\n", kind); + VERIFY(kind == CTF_K_INTEGER || kind == CTF_K_ENUM); + + /* + * As surprising as it may be, it is strictly possible to create a + * bitfield that is based on an enum. Of course, the C standard leaves + * enums sizing as an ABI concern more or less. To that effect, today on + * all illumos platforms the size of an enum is generally that of an + * int as our supported data models and ABIs all agree on that. So what + * we'll do is fake up a CTF enconding here to use. In this case, we'll + * treat it as an unsigned value of whatever size the underlying enum + * currently has (which is in the ctt_size member of its dynamic type + * data). + */ + if (kind == CTF_K_INTEGER) { + e = dtd->dtd_u.dtu_enc; + } else { + bzero(&e, sizeof (ctf_encoding_t)); + e.cte_bits = dtd->dtd_data.ctt_size * NBBY; + } + + for (cdb = ctf_list_next(&cdp->cd_bitfields); cdb != NULL; + cdb = ctf_list_next(cdb)) { + if (cdb->cdb_base == base && cdb->cdb_nbits == bitsz) + break; + } + + /* + * Create a new type if none exists. We name all types in a way that is + * guaranteed not to conflict with the corresponding C type. We do this + * by using the ':' operator. + */ + if (cdb == NULL) { + size_t namesz; + char *name; + + e.cte_bits = bitsz; + namesz = snprintf(NULL, 0, "%s:%d", dtd->dtd_name, + (uint32_t)bitsz); + name = ctf_alloc(namesz + 1); + if (name == NULL) + return (ENOMEM); + cdb = ctf_alloc(sizeof (ctf_dwbitf_t)); + if (cdb == NULL) { + ctf_free(name, namesz + 1); + return (ENOMEM); + } + (void) snprintf(name, namesz + 1, "%s:%d", dtd->dtd_name, + (uint32_t)bitsz); + + cdb->cdb_base = base; + cdb->cdb_nbits = bitsz; + cdb->cdb_id = ctf_add_integer(cdp->cd_ctfp, CTF_ADD_NONROOT, + name, &e); + if (cdb->cdb_id == CTF_ERR) { + (void) snprintf(cdp->cd_errbuf, cdp->cd_errlen, + "failed to get add bitfield type %s: %s", name, + ctf_errmsg(ctf_errno(cdp->cd_ctfp))); + ctf_free(name, namesz + 1); + ctf_free(cdb, sizeof (ctf_dwbitf_t)); + return (ECTF_CONVBKERR); + } + ctf_free(name, namesz + 1); + ctf_list_append(&cdp->cd_bitfields, cdb); + } + + *idp = cdb->cdb_id; + + return (0); +} + +static int +ctf_dwarf_fixup_sou(ctf_die_t *cdp, Dwarf_Die die, ctf_id_t base, boolean_t add) +{ + int ret, kind; + Dwarf_Die child, memb; + Dwarf_Unsigned size; + ulong_t nsz; + + kind = ctf_type_kind(cdp->cd_ctfp, base); + VERIFY(kind != CTF_ERR); + VERIFY(kind == CTF_K_STRUCT || kind == CTF_K_UNION); + + /* + * Members are in children. However, gcc also allows empty ones. + */ + if ((ret = ctf_dwarf_child(cdp, die, &child)) != 0) + return (ret); + if (child == NULL) + return (0); + + memb = child; + while (memb != NULL) { + Dwarf_Die sib, tdie; + Dwarf_Half tag; + ctf_id_t mid; + char *mname; + ulong_t memboff = 0; + + if ((ret = ctf_dwarf_tag(cdp, memb, &tag)) != 0) + return (ret); + + if (tag != DW_TAG_member) + continue; + + if ((ret = ctf_dwarf_refdie(cdp, memb, DW_AT_type, &tdie)) != 0) + return (ret); + + if ((ret = ctf_dwarf_convert_type(cdp, tdie, &mid, + CTF_ADD_NONROOT)) != 0) + return (ret); + ctf_dprintf("Got back type id: %d\n", mid); + + /* + * If we're not adding a member, just go ahead and return. + */ + if (add == B_FALSE) { + if ((ret = ctf_dwarf_member_bitfield(cdp, memb, + &mid)) != 0) + return (ret); + goto next; + } + + if ((ret = ctf_dwarf_string(cdp, memb, DW_AT_name, + &mname)) != 0 && ret != ENOENT) + return (ret); + if (ret == ENOENT) + mname = NULL; + + if (kind == CTF_K_UNION) { + memboff = 0; + } else if ((ret = ctf_dwarf_member_offset(cdp, memb, mid, + &memboff)) != 0) { + if (mname != NULL) + ctf_free(mname, strlen(mname) + 1); + return (ret); + } + + if ((ret = ctf_dwarf_member_bitfield(cdp, memb, &mid)) != 0) + return (ret); + + ret = ctf_add_member(cdp->cd_ctfp, base, mname, mid, memboff); + if (ret == CTF_ERR) { + (void) snprintf(cdp->cd_errbuf, cdp->cd_errlen, + "failed to add member %s: %s", + mname, ctf_errmsg(ctf_errno(cdp->cd_ctfp))); + if (mname != NULL) + ctf_free(mname, strlen(mname) + 1); + return (ECTF_CONVBKERR); + } + + if (mname != NULL) + ctf_free(mname, strlen(mname) + 1); + +next: + if ((ret = ctf_dwarf_sib(cdp, memb, &sib)) != 0) + return (ret); + memb = sib; + } + + /* + * If we're not adding members, then we don't know the final size of the + * structure, so end here. + */ + if (add == B_FALSE) + return (0); + + /* Finally set the size of the structure to the actual byte size */ + if ((ret = ctf_dwarf_unsigned(cdp, die, DW_AT_byte_size, &size)) != 0) + return (ret); + nsz = size; + if ((ctf_set_size(cdp->cd_ctfp, base, nsz)) == CTF_ERR) { + int e = ctf_errno(cdp->cd_ctfp); + (void) snprintf(cdp->cd_errbuf, cdp->cd_errlen, + "failed to set type size for %d to 0x%x: %s", base, + (uint32_t)size, ctf_errmsg(e)); + return (ECTF_CONVBKERR); + } + + return (0); +} + +static int +ctf_dwarf_create_sou(ctf_die_t *cdp, Dwarf_Die die, ctf_id_t *idp, + int kind, int isroot) +{ + int ret; + char *name; + ctf_id_t base; + Dwarf_Die child; + Dwarf_Bool decl; + + /* + * Deal with the terribly annoying case of anonymous structs and unions. + * If they don't have a name, set the name to the empty string. + */ + if ((ret = ctf_dwarf_string(cdp, die, DW_AT_name, &name)) != 0 && + ret != ENOENT) + return (ret); + if (ret == ENOENT) + name = NULL; + + /* + * We need to check if we just have a declaration here. If we do, then + * instead of creating an actual structure or union, we're just going to + * go ahead and create a forward. During a dedup or merge, the forward + * will be replaced with the real thing. + */ + if ((ret = ctf_dwarf_boolean(cdp, die, DW_AT_declaration, + &decl)) != 0) { + if (ret != ENOENT) + return (ret); + decl = 0; + } + + if (decl != 0) { + base = ctf_add_forward(cdp->cd_ctfp, isroot, name, kind); + } else if (kind == CTF_K_STRUCT) { + base = ctf_add_struct(cdp->cd_ctfp, isroot, name); + } else { + base = ctf_add_union(cdp->cd_ctfp, isroot, name); + } + ctf_dprintf("added sou %s (%d) (%d)\n", name, kind, base); + if (name != NULL) + ctf_free(name, strlen(name) + 1); + if (base == CTF_ERR) + return (ctf_errno(cdp->cd_ctfp)); + *idp = base; + + /* + * If it's just a declaration, we're not going to mark it for fix up or + * do anything else. + */ + if (decl == B_TRUE) + return (ctf_dwmap_add(cdp, base, die, B_FALSE)); + if ((ret = ctf_dwmap_add(cdp, base, die, B_TRUE)) != 0) + return (ret); + + /* + * Members are in children. However, gcc also allows empty ones. + */ + if ((ret = ctf_dwarf_child(cdp, die, &child)) != 0) + return (ret); + if (child == NULL) + return (0); + + return (0); +} + +static int +ctf_dwarf_create_array_range(ctf_die_t *cdp, Dwarf_Die range, ctf_id_t *idp, + ctf_id_t base, int isroot) +{ + int ret; + Dwarf_Die sib; + Dwarf_Unsigned val; + Dwarf_Signed sval; + ctf_arinfo_t ar; + + ctf_dprintf("creating array range\n"); + + if ((ret = ctf_dwarf_sib(cdp, range, &sib)) != 0) + return (ret); + if (sib != NULL) { + ctf_id_t id; + if ((ret = ctf_dwarf_create_array_range(cdp, sib, &id, + base, CTF_ADD_NONROOT)) != 0) + return (ret); + ar.ctr_contents = id; + } else { + ar.ctr_contents = base; + } + + if ((ar.ctr_index = ctf_dwarf_long(cdp)) == CTF_ERR) + return (ctf_errno(cdp->cd_ctfp)); + + /* + * Array bounds can be signed or unsigned, but there are several kinds + * of signless forms (data1, data2, etc) that take their sign from the + * routine that is trying to interpret them. That is, data1 can be + * either signed or unsigned, depending on whether you use the signed or + * unsigned accessor function. GCC will use the signless forms to store + * unsigned values which have their high bit set, so we need to try to + * read them first as unsigned to get positive values. We could also + * try signed first, falling back to unsigned if we got a negative + * value. + */ + if ((ret = ctf_dwarf_unsigned(cdp, range, DW_AT_upper_bound, + &val)) == 0) { + ar.ctr_nelems = val + 1; + } else if (ret != ENOENT) { + return (ret); + } else if ((ret = ctf_dwarf_signed(cdp, range, DW_AT_upper_bound, + &sval)) == 0) { + ar.ctr_nelems = sval + 1; + } else if (ret != ENOENT) { + return (ret); + } else { + ar.ctr_nelems = 0; + } + + if ((*idp = ctf_add_array(cdp->cd_ctfp, isroot, &ar)) == CTF_ERR) + return (ctf_errno(cdp->cd_ctfp)); + + return (0); +} + +/* + * Try and create an array type. First, the kind of the array is specified in + * the DW_AT_type entry. Next, the number of entries is stored in a more + * complicated form, we should have a child that has the DW_TAG_subrange type. + */ +static int +ctf_dwarf_create_array(ctf_die_t *cdp, Dwarf_Die die, ctf_id_t *idp, int isroot) +{ + int ret; + Dwarf_Die tdie, rdie; + ctf_id_t tid; + Dwarf_Half rtag; + ctf_arinfo_t ar; + + if ((ret = ctf_dwarf_refdie(cdp, die, DW_AT_type, &tdie)) != 0) + return (ret); + if ((ret = ctf_dwarf_convert_type(cdp, tdie, &tid, + CTF_ADD_NONROOT)) != 0) + return (ret); + + ar.ctr_contents = tid; + + if ((ret = ctf_dwarf_child(cdp, die, &rdie)) != 0) + return (ret); + if ((ret = ctf_dwarf_tag(cdp, rdie, &rtag)) != 0) + return (ret); + if (rtag != DW_TAG_subrange_type) { + (void) snprintf(cdp->cd_errbuf, cdp->cd_errlen, + "encountered array without DW_TAG_subrange_type child\n"); + return (ECTF_CONVBKERR); + } + + /* + * The compiler may opt to describe a multi-dimensional array as one + * giant array or it may opt to instead encode it as a series of + * subranges. If it's the latter, then for each subrange we introduce a + * type. We can always use the base type. + */ + if ((ret = ctf_dwarf_create_array_range(cdp, rdie, idp, tid, + isroot)) != 0) + return (ret); + ctf_dprintf("Got back id %d\n", *idp); + return (ctf_dwmap_add(cdp, *idp, die, B_FALSE)); +} + +static int +ctf_dwarf_create_reference(ctf_die_t *cdp, Dwarf_Die die, ctf_id_t *idp, + int kind, int isroot) +{ + int ret; + ctf_id_t id; + Dwarf_Die tdie; + char *name; + size_t namelen; + + if ((ret = ctf_dwarf_string(cdp, die, DW_AT_name, &name)) != 0 && + ret != ENOENT) + return (ret); + if (ret == ENOENT) { + name = NULL; + namelen = 0; + } else { + namelen = strlen(name); + } + + ctf_dprintf("reference kind %d %s\n", kind, name != NULL ? name : "<>"); + + if ((ret = ctf_dwarf_refdie(cdp, die, DW_AT_type, &tdie)) != 0) { + if (ret != ENOENT) { + ctf_free(name, namelen); + return (ret); + } + if ((id = ctf_dwarf_void(cdp)) == CTF_ERR) { + ctf_free(name, namelen); + return (ctf_errno(cdp->cd_ctfp)); + } + } else { + if ((ret = ctf_dwarf_convert_type(cdp, tdie, &id, + CTF_ADD_NONROOT)) != 0) { + ctf_free(name, namelen); + return (ret); + } + } + + if ((*idp = ctf_add_reftype(cdp->cd_ctfp, isroot, name, id, kind)) == + CTF_ERR) { + ctf_free(name, namelen); + return (ctf_errno(cdp->cd_ctfp)); + } + + ctf_free(name, namelen); + return (ctf_dwmap_add(cdp, *idp, die, B_FALSE)); +} + +static int +ctf_dwarf_create_enum(ctf_die_t *cdp, Dwarf_Die die, ctf_id_t *idp, int isroot) +{ + int ret; + ctf_id_t id; + Dwarf_Die child; + char *name; + + if ((ret = ctf_dwarf_string(cdp, die, DW_AT_name, &name)) != 0 && + ret != ENOENT) + return (ret); + if (ret == ENOENT) + name = NULL; + id = ctf_add_enum(cdp->cd_ctfp, isroot, name); + ctf_dprintf("added enum %s (%d)\n", name, id); + if (name != NULL) + ctf_free(name, strlen(name) + 1); + if (id == CTF_ERR) + return (ctf_errno(cdp->cd_ctfp)); + *idp = id; + if ((ret = ctf_dwmap_add(cdp, id, die, B_FALSE)) != 0) + return (ret); + + + if ((ret = ctf_dwarf_child(cdp, die, &child)) != 0) { + if (ret == ENOENT) + ret = 0; + return (ret); + } + + while (child != NULL) { + Dwarf_Half tag; + Dwarf_Signed sval; + Dwarf_Unsigned uval; + Dwarf_Die arg = child; + int eval; + + if ((ret = ctf_dwarf_sib(cdp, arg, &child)) != 0) + return (ret); + + if ((ret = ctf_dwarf_tag(cdp, arg, &tag)) != 0) + return (ret); + + if (tag != DW_TAG_enumerator) { + if ((ret = ctf_dwarf_convert_type(cdp, arg, NULL, + CTF_ADD_NONROOT)) != 0) + return (ret); + continue; + } + + if ((ret = ctf_dwarf_signed(cdp, arg, DW_AT_const_value, + &sval)) == 0) { + eval = sval; + } else if (ret != ENOENT) { + return (ret); + } else if ((ret = ctf_dwarf_unsigned(cdp, arg, + DW_AT_const_value, &uval)) == 0) { + eval = (int)uval; + } else { + (void) snprintf(cdp->cd_errbuf, cdp->cd_errlen, + "encountered enumration without constant value\n"); + return (ECTF_CONVBKERR); + } + + /* + * DWARF v4 section 5.7 tells us we'll always have names. + */ + if ((ret = ctf_dwarf_string(cdp, arg, DW_AT_name, + &name)) != 0) + return (ret); + + ret = ctf_add_enumerator(cdp->cd_ctfp, id, name, eval); + if (ret == CTF_ERR) { + (void) snprintf(cdp->cd_errbuf, cdp->cd_errlen, + "failed to add enumarator %s (%d) to %d\n", + name, eval, id); + ctf_free(name, strlen(name) + 1); + return (ctf_errno(cdp->cd_ctfp)); + } + ctf_free(name, strlen(name) + 1); + } + + return (0); +} + +/* + * For a function pointer, walk over and process all of its children, unless we + * encounter one that's just a declaration. In which case, we error on it. + */ +static int +ctf_dwarf_create_fptr(ctf_die_t *cdp, Dwarf_Die die, ctf_id_t *idp, int isroot) +{ + int ret; + Dwarf_Bool b; + ctf_funcinfo_t fi; + Dwarf_Die retdie; + ctf_id_t *argv = NULL; + + bzero(&fi, sizeof (ctf_funcinfo_t)); + + if ((ret = ctf_dwarf_boolean(cdp, die, DW_AT_declaration, &b)) != 0) { + if (ret != ENOENT) + return (ret); + } else { + if (b != 0) + return (EPROTOTYPE); + } + + /* + * Return type is in DW_AT_type, if none, it returns void. + */ + if ((ret = ctf_dwarf_refdie(cdp, die, DW_AT_type, &retdie)) != 0) { + if (ret != ENOENT) + return (ret); + if ((fi.ctc_return = ctf_dwarf_void(cdp)) == CTF_ERR) + return (ctf_errno(cdp->cd_ctfp)); + } else { + if ((ret = ctf_dwarf_convert_type(cdp, retdie, &fi.ctc_return, + CTF_ADD_NONROOT)) != 0) + return (ret); + } + + if ((ret = ctf_dwarf_function_count(cdp, die, &fi, B_TRUE)) != 0) { + return (ret); + } + + if (fi.ctc_argc != 0) { + argv = ctf_alloc(sizeof (ctf_id_t) * fi.ctc_argc); + if (argv == NULL) + return (ENOMEM); + + if ((ret = ctf_dwarf_convert_fargs(cdp, die, &fi, argv)) != 0) { + ctf_free(argv, sizeof (ctf_id_t) * fi.ctc_argc); + return (ret); + } + } + + if ((*idp = ctf_add_funcptr(cdp->cd_ctfp, isroot, &fi, argv)) == + CTF_ERR) { + ctf_free(argv, sizeof (ctf_id_t) * fi.ctc_argc); + return (ctf_errno(cdp->cd_ctfp)); + } + + ctf_free(argv, sizeof (ctf_id_t) * fi.ctc_argc); + return (ctf_dwmap_add(cdp, *idp, die, B_FALSE)); +} + +static int +ctf_dwarf_convert_type(ctf_die_t *cdp, Dwarf_Die die, ctf_id_t *idp, + int isroot) +{ + int ret; + Dwarf_Off offset; + Dwarf_Half tag; + ctf_dwmap_t lookup, *map; + ctf_id_t id; + + if (idp == NULL) + idp = &id; + + if ((ret = ctf_dwarf_offset(cdp, die, &offset)) != 0) + return (ret); + + if (offset > cdp->cd_maxoff) { + (void) snprintf(cdp->cd_errbuf, cdp->cd_errlen, + "die offset %llu beyond maximum for header %llu\n", + offset, cdp->cd_maxoff); + return (ECTF_CONVBKERR); + } + + /* + * If we've already added an entry for this offset, then we're done. + */ + lookup.cdm_off = offset; + if ((map = avl_find(&cdp->cd_map, &lookup, NULL)) != NULL) { + *idp = map->cdm_id; + return (0); + } + + if ((ret = ctf_dwarf_tag(cdp, die, &tag)) != 0) + return (ret); + + ret = ENOTSUP; + switch (tag) { + case DW_TAG_base_type: + ctf_dprintf("base\n"); + ret = ctf_dwarf_create_base(cdp, die, idp, isroot, offset); + break; + case DW_TAG_array_type: + ctf_dprintf("array\n"); + ret = ctf_dwarf_create_array(cdp, die, idp, isroot); + break; + case DW_TAG_enumeration_type: + ctf_dprintf("enum\n"); + ret = ctf_dwarf_create_enum(cdp, die, idp, isroot); + break; + case DW_TAG_pointer_type: + ctf_dprintf("pointer\n"); + ret = ctf_dwarf_create_reference(cdp, die, idp, CTF_K_POINTER, + isroot); + break; + case DW_TAG_structure_type: + ctf_dprintf("struct\n"); + ret = ctf_dwarf_create_sou(cdp, die, idp, CTF_K_STRUCT, + isroot); + break; + case DW_TAG_subroutine_type: + ctf_dprintf("fptr\n"); + ret = ctf_dwarf_create_fptr(cdp, die, idp, isroot); + break; + case DW_TAG_typedef: + ctf_dprintf("typedef\n"); + ret = ctf_dwarf_create_reference(cdp, die, idp, CTF_K_TYPEDEF, + isroot); + break; + case DW_TAG_union_type: + ctf_dprintf("union\n"); + ret = ctf_dwarf_create_sou(cdp, die, idp, CTF_K_UNION, + isroot); + break; + case DW_TAG_const_type: + ctf_dprintf("const\n"); + ret = ctf_dwarf_create_reference(cdp, die, idp, CTF_K_CONST, + isroot); + break; + case DW_TAG_volatile_type: + ctf_dprintf("volatile\n"); + ret = ctf_dwarf_create_reference(cdp, die, idp, CTF_K_VOLATILE, + isroot); + break; + case DW_TAG_restrict_type: + ctf_dprintf("restrict\n"); + ret = ctf_dwarf_create_reference(cdp, die, idp, CTF_K_RESTRICT, + isroot); + break; + default: + ctf_dprintf("ignoring tag type %x\n", tag); + ret = 0; + break; + } + ctf_dprintf("ctf_dwarf_convert_type tag specific handler returned %d\n", + ret); + + return (ret); +} + +static int +ctf_dwarf_walk_lexical(ctf_die_t *cdp, Dwarf_Die die) +{ + int ret; + Dwarf_Die child; + + if ((ret = ctf_dwarf_child(cdp, die, &child)) != 0) + return (ret); + + if (child == NULL) + return (0); + + return (ctf_dwarf_convert_die(cdp, die)); +} + +static int +ctf_dwarf_function_count(ctf_die_t *cdp, Dwarf_Die die, ctf_funcinfo_t *fip, + boolean_t fptr) +{ + int ret; + Dwarf_Die child, sib, arg; + + if ((ret = ctf_dwarf_child(cdp, die, &child)) != 0) + return (ret); + + arg = child; + while (arg != NULL) { + Dwarf_Half tag; + + if ((ret = ctf_dwarf_tag(cdp, arg, &tag)) != 0) + return (ret); + + /* + * We have to check for a varargs type decleration. This will + * happen in one of two ways. If we have a function pointer + * type, then it'll be done with a tag of type + * DW_TAG_unspecified_parameters. However, it only means we have + * a variable number of arguments, if we have more than one + * argument found so far. Otherwise, when we have a function + * type, it instead uses a formal parameter whose name is '...' + * to indicate a variable arguments member. + * + * Also, if we have a function pointer, then we have to expect + * that we might not get a name at all. + */ + if (tag == DW_TAG_formal_parameter && fptr == B_FALSE) { + char *name; + if ((ret = ctf_dwarf_string(cdp, die, DW_AT_name, + &name)) != 0) + return (ret); + if (strcmp(name, DWARF_VARARGS_NAME) == 0) + fip->ctc_flags |= CTF_FUNC_VARARG; + else + fip->ctc_argc++; + ctf_free(name, strlen(name) + 1); + } else if (tag == DW_TAG_formal_parameter) { + fip->ctc_argc++; + } else if (tag == DW_TAG_unspecified_parameters && + fip->ctc_argc > 0) { + fip->ctc_flags |= CTF_FUNC_VARARG; + } + if ((ret = ctf_dwarf_sib(cdp, arg, &sib)) != 0) + return (ret); + arg = sib; + } + + return (0); +} + +static int +ctf_dwarf_convert_fargs(ctf_die_t *cdp, Dwarf_Die die, ctf_funcinfo_t *fip, + ctf_id_t *argv) +{ + int ret; + int i = 0; + Dwarf_Die child, sib, arg; + + if ((ret = ctf_dwarf_child(cdp, die, &child)) != 0) + return (ret); + + arg = child; + while (arg != NULL) { + Dwarf_Half tag; + + if ((ret = ctf_dwarf_tag(cdp, arg, &tag)) != 0) + return (ret); + if (tag == DW_TAG_formal_parameter) { + Dwarf_Die tdie; + + if ((ret = ctf_dwarf_refdie(cdp, arg, DW_AT_type, + &tdie)) != 0) + return (ret); + + if ((ret = ctf_dwarf_convert_type(cdp, tdie, &argv[i], + CTF_ADD_ROOT)) != 0) + return (ret); + i++; + + /* + * Once we hit argc entries, we're done. This ensures we + * don't accidentally hit a varargs which should be the + * least entry. + */ + if (i == fip->ctc_argc) + break; + } + + if ((ret = ctf_dwarf_sib(cdp, arg, &sib)) != 0) + return (ret); + arg = sib; + } + + return (0); +} + +static int +ctf_dwarf_convert_function(ctf_die_t *cdp, Dwarf_Die die) +{ + int ret; + char *name; + ctf_dwfunc_t *cdf; + Dwarf_Die tdie; + + /* + * Functions that don't have a name are generally functions that have + * been inlined and thus most information about them has been lost. If + * we can't get a name, then instead of returning ENOENT, we silently + * swallow the error. + */ + if ((ret = ctf_dwarf_string(cdp, die, DW_AT_name, &name)) != 0) { + if (ret == ENOENT) + return (0); + return (ret); + } + + ctf_dprintf("beginning work on function %s\n", name); + if ((cdf = ctf_alloc(sizeof (ctf_dwfunc_t))) == NULL) { + ctf_free(name, strlen(name) + 1); + return (ENOMEM); + } + bzero(cdf, sizeof (ctf_dwfunc_t)); + cdf->cdf_name = name; + + if ((ret = ctf_dwarf_refdie(cdp, die, DW_AT_type, &tdie)) == 0) { + if ((ret = ctf_dwarf_convert_type(cdp, tdie, + &(cdf->cdf_fip.ctc_return), CTF_ADD_ROOT)) != 0) { + ctf_free(name, strlen(name) + 1); + ctf_free(cdf, sizeof (ctf_dwfunc_t)); + return (ret); + } + } else if (ret != ENOENT) { + ctf_free(name, strlen(name) + 1); + ctf_free(cdf, sizeof (ctf_dwfunc_t)); + return (ret); + } else { + if ((cdf->cdf_fip.ctc_return = ctf_dwarf_void(cdp)) == + CTF_ERR) { + ctf_free(name, strlen(name) + 1); + ctf_free(cdf, sizeof (ctf_dwfunc_t)); + return (ctf_errno(cdp->cd_ctfp)); + } + } + + /* + * A function has a number of children, some of which may not be ones we + * care about. Children that we care about have a type of + * DW_TAG_formal_parameter. We're going to do two passes, the first to + * count the arguments, the second to process them. Afterwards, we + * should be good to go ahead and add this function. + * + * Note, we already got the return type by going in and grabbing it out + * of the DW_AT_type. + */ + if ((ret = ctf_dwarf_function_count(cdp, die, &cdf->cdf_fip, + B_FALSE)) != 0) { + ctf_free(name, strlen(name) + 1); + ctf_free(cdf, sizeof (ctf_dwfunc_t)); + return (ret); + } + + ctf_dprintf("beginning to convert function arguments %s\n", name); + if (cdf->cdf_fip.ctc_argc != 0) { + uint_t argc = cdf->cdf_fip.ctc_argc; + cdf->cdf_argv = ctf_alloc(sizeof (ctf_id_t) * argc); + if (cdf->cdf_argv == NULL) { + ctf_free(name, strlen(name) + 1); + ctf_free(cdf, sizeof (ctf_dwfunc_t)); + return (ENOMEM); + } + if ((ret = ctf_dwarf_convert_fargs(cdp, die, + &cdf->cdf_fip, cdf->cdf_argv)) != 0) { + ctf_free(cdf->cdf_argv, sizeof (ctf_id_t) * argc); + ctf_free(name, strlen(name) + 1); + ctf_free(cdf, sizeof (ctf_dwfunc_t)); + return (ret); + } + } else { + cdf->cdf_argv = NULL; + } + + if ((ret = ctf_dwarf_isglobal(cdp, die, &cdf->cdf_global)) != 0) { + ctf_free(cdf->cdf_argv, sizeof (ctf_id_t) * + cdf->cdf_fip.ctc_argc); + ctf_free(name, strlen(name) + 1); + ctf_free(cdf, sizeof (ctf_dwfunc_t)); + return (ret); + } + + ctf_list_append(&cdp->cd_funcs, cdf); + return (ret); +} + +/* + * Convert variables, but only if they're not prototypes and have names. + */ +static int +ctf_dwarf_convert_variable(ctf_die_t *cdp, Dwarf_Die die) +{ + int ret; + char *name; + Dwarf_Bool b; + Dwarf_Die tdie; + ctf_id_t id; + ctf_dwvar_t *cdv; + + if ((ret = ctf_dwarf_boolean(cdp, die, DW_AT_declaration, &b)) != 0) { + if (ret != ENOENT) + return (ret); + } else if (b != 0) { + return (0); + } + + if ((ret = ctf_dwarf_string(cdp, die, DW_AT_name, &name)) != 0 && + ret != ENOENT) + return (ret); + if (ret == ENOENT) + return (0); + + if ((ret = ctf_dwarf_refdie(cdp, die, DW_AT_type, &tdie)) != 0) { + ctf_free(name, strlen(name) + 1); + return (ret); + } + + if ((ret = ctf_dwarf_convert_type(cdp, tdie, &id, + CTF_ADD_ROOT)) != 0) + return (ret); + + if ((cdv = ctf_alloc(sizeof (ctf_dwvar_t))) == NULL) { + ctf_free(name, strlen(name) + 1); + return (ENOMEM); + } + + cdv->cdv_name = name; + cdv->cdv_type = id; + + if ((ret = ctf_dwarf_isglobal(cdp, die, &cdv->cdv_global)) != 0) { + ctf_free(cdv, sizeof (ctf_dwvar_t)); + ctf_free(name, strlen(name) + 1); + return (ret); + } + + ctf_list_append(&cdp->cd_vars, cdv); + return (0); +} + +/* + * Walk through our set of top-level types and process them. + */ +static int +ctf_dwarf_walk_toplevel(ctf_die_t *cdp, Dwarf_Die die) +{ + int ret; + Dwarf_Off offset; + Dwarf_Half tag; + + if ((ret = ctf_dwarf_offset(cdp, die, &offset)) != 0) + return (ret); + + if (offset > cdp->cd_maxoff) { + (void) snprintf(cdp->cd_errbuf, cdp->cd_errlen, + "die offset %llu beyond maximum for header %llu\n", + offset, cdp->cd_maxoff); + return (ECTF_CONVBKERR); + } + + if ((ret = ctf_dwarf_tag(cdp, die, &tag)) != 0) + return (ret); + + ret = 0; + switch (tag) { + case DW_TAG_subprogram: + ctf_dprintf("top level func\n"); + ret = ctf_dwarf_convert_function(cdp, die); + break; + case DW_TAG_variable: + ctf_dprintf("top level var\n"); + ret = ctf_dwarf_convert_variable(cdp, die); + break; + case DW_TAG_lexical_block: + ctf_dprintf("top level block\n"); + ret = ctf_dwarf_walk_lexical(cdp, die); + break; + case DW_TAG_enumeration_type: + case DW_TAG_structure_type: + case DW_TAG_typedef: + case DW_TAG_union_type: + ctf_dprintf("top level type\n"); + ret = ctf_dwarf_convert_type(cdp, die, NULL, B_TRUE); + break; + default: + break; + } + + return (ret); +} + + +/* + * We're given a node. At this node we need to convert it and then proceed to + * convert any siblings that are associaed with this die. + */ +static int +ctf_dwarf_convert_die(ctf_die_t *cdp, Dwarf_Die die) +{ + while (die != NULL) { + int ret; + Dwarf_Die sib; + + if ((ret = ctf_dwarf_walk_toplevel(cdp, die)) != 0) + return (ret); + + if ((ret = ctf_dwarf_sib(cdp, die, &sib)) != 0) + return (ret); + die = sib; + } + return (0); +} + +static int +ctf_dwarf_fixup_die(ctf_die_t *cdp, boolean_t addpass) +{ + ctf_dwmap_t *map; + + for (map = avl_first(&cdp->cd_map); map != NULL; + map = AVL_NEXT(&cdp->cd_map, map)) { + int ret; + if (map->cdm_fix == B_FALSE) + continue; + if ((ret = ctf_dwarf_fixup_sou(cdp, map->cdm_die, map->cdm_id, + addpass)) != 0) + return (ret); + } + + return (0); +} + +static ctf_dwfunc_t * +ctf_dwarf_match_func(ctf_die_t *cdp, const char *file, const char *name, + int bind) +{ + ctf_dwfunc_t *cdf; + + if (bind == STB_WEAK) + return (NULL); + + /* Nothing we can do if we can't find a name to compare it to. */ + if (bind == STB_LOCAL && (file == NULL || cdp->cd_name == NULL)) + return (NULL); + + for (cdf = ctf_list_next(&cdp->cd_funcs); cdf != NULL; + cdf = ctf_list_next(cdf)) { + if (bind == STB_GLOBAL && cdf->cdf_global == B_FALSE) + continue; + if (bind == STB_LOCAL && cdf->cdf_global == B_TRUE) + continue; + if (strcmp(name, cdf->cdf_name) != 0) + continue; + if (bind == STB_LOCAL && strcmp(file, cdp->cd_name) != 0) + continue; + return (cdf); + } + + return (NULL); +} +static ctf_dwvar_t * +ctf_dwarf_match_var(ctf_die_t *cdp, const char *file, const char *name, + int bind) +{ + ctf_dwvar_t *cdv; + + /* Nothing we can do if we can't find a name to compare it to. */ + if (bind == STB_LOCAL && (file == NULL || cdp->cd_name == NULL)) + return (NULL); + ctf_dprintf("Still considering %s\n", name); + + for (cdv = ctf_list_next(&cdp->cd_vars); cdv != NULL; + cdv = ctf_list_next(cdv)) { + if (bind == STB_GLOBAL && cdv->cdv_global == B_FALSE) + continue; + if (bind == STB_LOCAL && cdv->cdv_global == B_TRUE) + continue; + if (strcmp(name, cdv->cdv_name) != 0) + continue; + if (bind == STB_LOCAL && strcmp(file, cdp->cd_name) != 0) + continue; + return (cdv); + } + + return (NULL); +} + +static int +ctf_dwarf_symtab_iter(ctf_die_t *cdp, ctf_dwarf_symtab_f *func, void *arg) +{ + int ret; + ulong_t i; + ctf_file_t *fp = cdp->cd_ctfp; + const char *file = NULL; + uintptr_t symbase = (uintptr_t)fp->ctf_symtab.cts_data; + uintptr_t strbase = (uintptr_t)fp->ctf_strtab.cts_data; + + for (i = 0; i < fp->ctf_nsyms; i++) { + const char *name; + int type; + GElf_Sym gsym; + const GElf_Sym *gsymp; + + if (fp->ctf_symtab.cts_entsize == sizeof (Elf32_Sym)) { + const Elf32_Sym *symp = (Elf32_Sym *)symbase + i; + type = ELF32_ST_TYPE(symp->st_info); + if (type == STT_FILE) { + file = (char *)(strbase + symp->st_name); + continue; + } + if (type != STT_OBJECT && type != STT_FUNC) + continue; + if (ctf_sym_valid(strbase, type, symp->st_shndx, + symp->st_value, symp->st_name) == B_FALSE) + continue; + name = (char *)(strbase + symp->st_name); + gsym.st_name = symp->st_name; + gsym.st_value = symp->st_value; + gsym.st_size = symp->st_size; + gsym.st_info = symp->st_info; + gsym.st_other = symp->st_other; + gsym.st_shndx = symp->st_shndx; + gsymp = &gsym; + } else { + const Elf64_Sym *symp = (Elf64_Sym *)symbase + i; + type = ELF64_ST_TYPE(symp->st_info); + if (type == STT_FILE) { + file = (char *)(strbase + symp->st_name); + continue; + } + if (type != STT_OBJECT && type != STT_FUNC) + continue; + if (ctf_sym_valid(strbase, type, symp->st_shndx, + symp->st_value, symp->st_name) == B_FALSE) + continue; + name = (char *)(strbase + symp->st_name); + gsymp = symp; + } + + ret = func(cdp, gsymp, i, file, name, arg); + if (ret != 0) + return (ret); + } + + return (0); +} + +static int +ctf_dwarf_conv_funcvars_cb(ctf_die_t *cdp, const GElf_Sym *symp, ulong_t idx, + const char *file, const char *name, void *arg) +{ + int ret, bind, type; + + bind = GELF_ST_BIND(symp->st_info); + type = GELF_ST_TYPE(symp->st_info); + + /* + * Come back to weak symbols in another pass + */ + if (bind == STB_WEAK) + return (0); + + if (type == STT_OBJECT) { + ctf_dwvar_t *cdv = ctf_dwarf_match_var(cdp, file, name, + bind); + ctf_dprintf("match for %s (%d): %p\n", name, idx, cdv); + if (cdv == NULL) + return (0); + ret = ctf_add_object(cdp->cd_ctfp, idx, cdv->cdv_type); + ctf_dprintf("added object %s\n", name); + } else { + ctf_dwfunc_t *cdf = ctf_dwarf_match_func(cdp, file, name, + bind); + if (cdf == NULL) + return (0); + ret = ctf_add_function(cdp->cd_ctfp, idx, &cdf->cdf_fip, + cdf->cdf_argv); + } + + if (ret == CTF_ERR) { + return (ctf_errno(cdp->cd_ctfp)); + } + + return (0); +} + +static int +ctf_dwarf_conv_funcvars(ctf_die_t *cdp) +{ + return (ctf_dwarf_symtab_iter(cdp, ctf_dwarf_conv_funcvars_cb, NULL)); +} + +/* + * Note, this comment comes from the original version of the CTF tools. + * + * If we have a weak symbol, attempt to find the strong symbol it will + * resolve to. Note: the code where this actually happens is in + * sym_process() in cmd/sgs/libld/common/syms.c + * + * Finding the matching symbol is unfortunately not trivial. For a + * symbol to be a candidate, it must: + * + * - have the same type (function, object) + * - have the same value (address) + * - have the same size + * - not be another weak symbol + * - belong to the same section (checked via section index) + * + * If such a candidate is global, then we assume we've found it. The + * linker generates the symbol table such that the curfile might be + * incorrect; this is OK for global symbols, since find_iidesc() doesn't + * need to check for the source file for the symbol. + * + * We might have found a strong local symbol, where the curfile is + * accurate and matches that of the weak symbol. We assume this is a + * reasonable match. + * + * If we've got a local symbol with a non-matching curfile, there are + * two possibilities. Either this is a completely different symbol, or + * it's a once-global symbol that was scoped to local via a mapfile. In + * the latter case, curfile is likely inaccurate since the linker does + * not preserve the needed curfile in the order of the symbol table (see + * the comments about locally scoped symbols in libld's update_osym()). + * As we can't tell this case from the former one, we use this symbol + * iff no other matching symbol is found. + * + * What we really need here is a SUNW section containing weak<->strong + * mappings that we can consume. + */ +typedef struct ctf_dwarf_weak_arg { + const GElf_Sym *cweak_symp; + const char *cweak_file; + boolean_t cweak_candidate; + ulong_t cweak_idx; +} ctf_dwarf_weak_arg_t; + +static int +ctf_dwarf_conv_check_weak(ctf_die_t *cdp, const GElf_Sym *symp, + ulong_t idx, const char *file, const char *name, void *arg) +{ + ctf_dwarf_weak_arg_t *cweak = arg; + const GElf_Sym *wsymp = cweak->cweak_symp; + + ctf_dprintf("comparing weak to %s\n", name); + + if (GELF_ST_BIND(symp->st_info) == STB_WEAK) { + return (0); + } + + if (GELF_ST_TYPE(wsymp->st_info) != GELF_ST_TYPE(symp->st_info)) { + return (0); + } + + if (wsymp->st_value != symp->st_value) { + return (0); + } + + if (wsymp->st_size != symp->st_size) { + return (0); + } + + if (wsymp->st_shndx != symp->st_shndx) { + return (0); + } + + /* + * Check if it's a weak candidate. + */ + if (GELF_ST_BIND(symp->st_info) == STB_LOCAL && + (file == NULL || cweak->cweak_file == NULL || + strcmp(file, cweak->cweak_file) != 0)) { + cweak->cweak_candidate = B_TRUE; + cweak->cweak_idx = idx; + return (0); + } + + /* + * Found a match, break. + */ + cweak->cweak_idx = idx; + return (1); +} + +static int +ctf_dwarf_duplicate_sym(ctf_die_t *cdp, ulong_t idx, ulong_t matchidx) +{ + ctf_id_t id = ctf_lookup_by_symbol(cdp->cd_ctfp, matchidx); + + /* + * If we matched something that for some reason didn't have type data, + * we don't consider that a fatal error and silently swallow it. + */ + if (id == CTF_ERR) { + if (ctf_errno(cdp->cd_ctfp) == ECTF_NOTYPEDAT) + return (0); + else + return (ctf_errno(cdp->cd_ctfp)); + } + + if (ctf_add_object(cdp->cd_ctfp, idx, id) == CTF_ERR) + return (ctf_errno(cdp->cd_ctfp)); + + return (0); +} + +static int +ctf_dwarf_duplicate_func(ctf_die_t *cdp, ulong_t idx, ulong_t matchidx) +{ + int ret; + ctf_funcinfo_t fip; + ctf_id_t *args = NULL; + + if (ctf_func_info(cdp->cd_ctfp, matchidx, &fip) == CTF_ERR) { + if (ctf_errno(cdp->cd_ctfp) == ECTF_NOFUNCDAT) + return (0); + else + return (ctf_errno(cdp->cd_ctfp)); + } + + if (fip.ctc_argc != 0) { + args = ctf_alloc(sizeof (ctf_id_t) * fip.ctc_argc); + if (args == NULL) + return (ENOMEM); + + if (ctf_func_args(cdp->cd_ctfp, matchidx, fip.ctc_argc, args) == + CTF_ERR) { + ctf_free(args, sizeof (ctf_id_t) * fip.ctc_argc); + return (ctf_errno(cdp->cd_ctfp)); + } + } + + ret = ctf_add_function(cdp->cd_ctfp, idx, &fip, args); + if (args != NULL) + ctf_free(args, sizeof (ctf_id_t) * fip.ctc_argc); + if (ret == CTF_ERR) + return (ctf_errno(cdp->cd_ctfp)); + + return (0); +} + +static int +ctf_dwarf_conv_weaks_cb(ctf_die_t *cdp, const GElf_Sym *symp, + ulong_t idx, const char *file, const char *name, void *arg) +{ + int ret, type; + ctf_dwarf_weak_arg_t cweak; + + /* + * We only care about weak symbols. + */ + if (GELF_ST_BIND(symp->st_info) != STB_WEAK) + return (0); + + type = GELF_ST_TYPE(symp->st_info); + ASSERT(type == STT_OBJECT || type == STT_FUNC); + + /* + * For each weak symbol we encounter, we need to do a second iteration + * to try and find a match. We should probably think about other + * techniques to try and save us time in the future. + */ + cweak.cweak_symp = symp; + cweak.cweak_file = file; + cweak.cweak_candidate = B_FALSE; + cweak.cweak_idx = 0; + + ctf_dprintf("Trying to find weak equiv for %s\n", name); + + ret = ctf_dwarf_symtab_iter(cdp, ctf_dwarf_conv_check_weak, &cweak); + VERIFY(ret == 0 || ret == 1); + + /* + * Nothing was ever found, we're not going to add anything for this + * entry. + */ + if (ret == 0 && cweak.cweak_candidate == B_FALSE) { + ctf_dprintf("found no weak match for %s\n", name); + return (0); + } + + /* + * Now, finally go and add the type based on the match. + */ + if (type == STT_OBJECT) { + ret = ctf_dwarf_duplicate_sym(cdp, idx, cweak.cweak_idx); + } else { + ret = ctf_dwarf_duplicate_func(cdp, idx, cweak.cweak_idx); + } + + return (ret); +} + +static int +ctf_dwarf_conv_weaks(ctf_die_t *cdp) +{ + return (ctf_dwarf_symtab_iter(cdp, ctf_dwarf_conv_weaks_cb, NULL)); +} + +/* ARGSUSED */ +static int +ctf_dwarf_convert_one(void *arg, void *unused) +{ + int ret; + ctf_file_t *dedup; + ctf_die_t *cdp = arg; + + ctf_dprintf("converting die: %s\n", cdp->cd_name); + ctf_dprintf("max offset: %x\n", cdp->cd_maxoff); + VERIFY(cdp != NULL); + + ret = ctf_dwarf_convert_die(cdp, cdp->cd_cu); + ctf_dprintf("ctf_dwarf_convert_die (%s) returned %d\n", cdp->cd_name, + ret); + if (ret != 0) { + return (ret); + } + if (ctf_update(cdp->cd_ctfp) != 0) { + return (ctf_dwarf_error(cdp, cdp->cd_ctfp, 0, + "failed to update output ctf container")); + } + + ret = ctf_dwarf_fixup_die(cdp, B_FALSE); + ctf_dprintf("ctf_dwarf_fixup_die (%s) returned %d\n", cdp->cd_name, + ret); + if (ret != 0) { + return (ret); + } + if (ctf_update(cdp->cd_ctfp) != 0) { + return (ctf_dwarf_error(cdp, cdp->cd_ctfp, 0, + "failed to update output ctf container")); + } + + ret = ctf_dwarf_fixup_die(cdp, B_TRUE); + ctf_dprintf("ctf_dwarf_fixup_die (%s) returned %d\n", cdp->cd_name, + ret); + if (ret != 0) { + return (ret); + } + if (ctf_update(cdp->cd_ctfp) != 0) { + return (ctf_dwarf_error(cdp, cdp->cd_ctfp, 0, + "failed to update output ctf container")); + } + + + if ((ret = ctf_dwarf_conv_funcvars(cdp)) != 0) { + return (ctf_dwarf_error(cdp, NULL, ret, + "failed to convert strong functions and variables")); + } + + if (ctf_update(cdp->cd_ctfp) != 0) { + return (ctf_dwarf_error(cdp, cdp->cd_ctfp, 0, + "failed to update output ctf container")); + } + + if ((ret = ctf_dwarf_conv_weaks(cdp)) != 0) { + return (ctf_dwarf_error(cdp, NULL, ret, + "failed to convert weak functions and variables")); + } + + if (ctf_update(cdp->cd_ctfp) != 0) { + return (ctf_dwarf_error(cdp, cdp->cd_ctfp, 0, + "failed to update output ctf container")); + } + + ctf_phase_dump(cdp->cd_ctfp, "pre-dedup"); + ctf_dprintf("adding inputs for dedup\n"); + if ((ret = ctf_merge_add(cdp->cd_cmh, cdp->cd_ctfp)) != 0) { + return (ctf_dwarf_error(cdp, NULL, ret, + "failed to add inputs for merge")); + } + + ctf_dprintf("starting merge\n"); + if ((ret = ctf_merge_dedup(cdp->cd_cmh, &dedup)) != 0) { + return (ctf_dwarf_error(cdp, NULL, ret, + "failed to deduplicate die")); + } + ctf_close(cdp->cd_ctfp); + cdp->cd_ctfp = dedup; + + return (0); +} + +/* + * Note, we expect that if we're returning a ctf_file_t from one of the dies, + * say in the single node case, it's been saved and the entry here has been set + * to NULL, which ctf_close happily ignores. + */ +static void +ctf_dwarf_free_die(ctf_die_t *cdp) +{ + ctf_dwfunc_t *cdf, *ndf; + ctf_dwvar_t *cdv, *ndv; + ctf_dwbitf_t *cdb, *ndb; + ctf_dwmap_t *map; + void *cookie; + Dwarf_Error derr; + + ctf_dprintf("Beginning to free die: %p\n", cdp); + cdp->cd_elf = NULL; + ctf_dprintf("Trying to free name: %p\n", cdp->cd_name); + if (cdp->cd_name != NULL) + ctf_free(cdp->cd_name, strlen(cdp->cd_name) + 1); + ctf_dprintf("Trying to free merge handle: %p\n", cdp->cd_cmh); + if (cdp->cd_cmh != NULL) { + ctf_merge_fini(cdp->cd_cmh); + cdp->cd_cmh = NULL; + } + + ctf_dprintf("Trying to free functions\n"); + for (cdf = ctf_list_next(&cdp->cd_funcs); cdf != NULL; cdf = ndf) { + ndf = ctf_list_next(cdf); + ctf_free(cdf->cdf_name, strlen(cdf->cdf_name) + 1); + if (cdf->cdf_fip.ctc_argc != 0) { + ctf_free(cdf->cdf_argv, + sizeof (ctf_id_t) * cdf->cdf_fip.ctc_argc); + } + ctf_free(cdf, sizeof (ctf_dwfunc_t)); + } + + ctf_dprintf("Trying to free variables\n"); + for (cdv = ctf_list_next(&cdp->cd_vars); cdv != NULL; cdv = ndv) { + ndv = ctf_list_next(cdv); + ctf_free(cdv->cdv_name, strlen(cdv->cdv_name) + 1); + ctf_free(cdv, sizeof (ctf_dwvar_t)); + } + + ctf_dprintf("Trying to free bitfields\n"); + for (cdb = ctf_list_next(&cdp->cd_bitfields); cdb != NULL; cdb = ndb) { + ndb = ctf_list_next(cdb); + ctf_free(cdb, sizeof (ctf_dwbitf_t)); + } + + /* How do we clean up die usage? */ + ctf_dprintf("Trying to clean up dwarf_t: %p\n", cdp->cd_dwarf); + (void) dwarf_finish(cdp->cd_dwarf, &derr); + cdp->cd_dwarf = NULL; + ctf_close(cdp->cd_ctfp); + + cookie = NULL; + while ((map = avl_destroy_nodes(&cdp->cd_map, &cookie)) != NULL) { + ctf_free(map, sizeof (ctf_dwmap_t)); + } + avl_destroy(&cdp->cd_map); + cdp->cd_errbuf = NULL; +} + +static void +ctf_dwarf_free_dies(ctf_die_t *cdies, int ndies) +{ + int i; + + ctf_dprintf("Beginning to free dies\n"); + for (i = 0; i < ndies; i++) { + ctf_dwarf_free_die(&cdies[i]); + } + + ctf_free(cdies, sizeof (ctf_die_t) * ndies); +} + +static int +ctf_dwarf_count_dies(Dwarf_Debug dw, Dwarf_Error *derr, int *ndies, + char *errbuf, size_t errlen) +{ + int ret; + Dwarf_Half vers; + Dwarf_Unsigned nexthdr; + + while ((ret = dwarf_next_cu_header(dw, NULL, &vers, NULL, NULL, + &nexthdr, derr)) != DW_DLV_NO_ENTRY) { + if (ret != DW_DLV_OK) { + (void) snprintf(errbuf, errlen, + "file does not contain valid DWARF data: %s\n", + dwarf_errmsg(*derr)); + return (ECTF_CONVBKERR); + } + + if (vers != DWARF_VERSION_TWO) { + (void) snprintf(errbuf, errlen, + "unsupported DWARF version: %d\n", vers); + return (ECTF_CONVBKERR); + } + *ndies = *ndies + 1; + } + + if (*ndies == 0) { + (void) snprintf(errbuf, errlen, + "file does not contain valid DWARF data: %s\n", + dwarf_errmsg(*derr)); + return (ECTF_CONVBKERR); + } + + return (0); +} + +/* + * Iterate over all of the dies and create a ctf_die_t for each of them. This is + * used to determine if we have zero, one, or multiple dies to convert. If we + * have zero, that's an error. If there's only one die, that's the simple case. + * No merge needed and only a single Dwarf_Debug as well. + */ +static int +ctf_dwarf_init_die(int fd, Elf *elf, ctf_die_t *cdp, int ndie, char *errbuf, + size_t errlen) +{ + int ret; + Dwarf_Unsigned hdrlen, abboff, nexthdr; + Dwarf_Half addrsz; + Dwarf_Unsigned offset = 0; + Dwarf_Error derr; + + while ((ret = dwarf_next_cu_header(cdp->cd_dwarf, &hdrlen, NULL, + &abboff, &addrsz, &nexthdr, &derr)) != DW_DLV_NO_ENTRY) { + char *name; + Dwarf_Die cu, child; + + /* Based on the counting above, we should be good to go */ + VERIFY(ret == DW_DLV_OK); + if (ndie > 0) { + ndie--; + offset = nexthdr; + continue; + } + + /* + * Compilers are apparently inconsistent. Some emit no DWARF for + * empty files and others emit empty compilation unit. + */ + cdp->cd_voidtid = CTF_ERR; + cdp->cd_longtid = CTF_ERR; + cdp->cd_elf = elf; + cdp->cd_maxoff = nexthdr - 1; + cdp->cd_ctfp = ctf_fdcreate(fd, &ret); + if (cdp->cd_ctfp == NULL) { + ctf_free(cdp, sizeof (ctf_die_t)); + return (ret); + } + avl_create(&cdp->cd_map, ctf_dwmap_comp, sizeof (ctf_dwmap_t), + offsetof(ctf_dwmap_t, cdm_avl)); + cdp->cd_errbuf = errbuf; + cdp->cd_errlen = errlen; + bzero(&cdp->cd_vars, sizeof (ctf_list_t)); + bzero(&cdp->cd_funcs, sizeof (ctf_list_t)); + bzero(&cdp->cd_bitfields, sizeof (ctf_list_t)); + + if ((ret = ctf_dwarf_die_elfenc(elf, cdp, errbuf, + errlen)) != 0) { + avl_destroy(&cdp->cd_map); + ctf_free(cdp, sizeof (ctf_die_t)); + return (ret); + } + + if ((ret = ctf_dwarf_sib(cdp, NULL, &cu)) != 0) { + avl_destroy(&cdp->cd_map); + ctf_free(cdp, sizeof (ctf_die_t)); + return (ret); + } + if (cu == NULL) { + (void) snprintf(errbuf, errlen, + "file does not contain DWARF data\n"); + avl_destroy(&cdp->cd_map); + ctf_free(cdp, sizeof (ctf_die_t)); + return (ECTF_CONVBKERR); + } + + if ((ret = ctf_dwarf_child(cdp, cu, &child)) != 0) { + avl_destroy(&cdp->cd_map); + ctf_free(cdp, sizeof (ctf_die_t)); + return (ret); + } + if (child == NULL) { + (void) snprintf(errbuf, errlen, + "file does not contain DWARF data\n"); + avl_destroy(&cdp->cd_map); + ctf_free(cdp, sizeof (ctf_die_t)); + return (ECTF_CONVBKERR); + } + + cdp->cd_cuoff = offset; + cdp->cd_cu = child; + + if ((cdp->cd_cmh = ctf_merge_init(fd, &ret)) == NULL) { + avl_destroy(&cdp->cd_map); + ctf_free(cdp, sizeof (ctf_die_t)); + return (ret); + } + + if (ctf_dwarf_string(cdp, cu, DW_AT_name, &name) == 0) { + size_t len = strlen(name) + 1; + char *b = basename(name); + cdp->cd_name = strdup(b); + ctf_free(name, len); + } + break; + } + + return (0); +} + + +ctf_conv_status_t +ctf_dwarf_convert(int fd, Elf *elf, uint_t nthrs, int *errp, ctf_file_t **fpp, + char *errmsg, size_t errlen) +{ + int err, ret, ndies, i; + Dwarf_Debug dw; + Dwarf_Error derr; + ctf_die_t *cdies = NULL, *cdp; + workq_t *wqp = NULL; + + if (errp == NULL) + errp = &err; + *errp = 0; + *fpp = NULL; + + ret = dwarf_elf_init(elf, DW_DLC_READ, NULL, NULL, &dw, &derr); + if (ret != DW_DLV_OK) { + /* + * The old CTF tools used to check if we expected DWARF data + * here. In this case, if we actually have some amount of DWARF, + * but no section, for now, just go ahead and create an empty + * CTF file. + */ + if (ret == DW_DLV_NO_ENTRY || + dwarf_errno(derr) == DW_DLE_DEBUG_INFO_NULL) { + *fpp = ctf_create(errp); + return (*fpp != NULL ? CTF_CONV_SUCCESS : + CTF_CONV_ERROR); + } + (void) snprintf(errmsg, errlen, + "failed to initialize DWARF: %s\n", + dwarf_errmsg(derr)); + *errp = ECTF_CONVBKERR; + return (CTF_CONV_ERROR); + } + + ndies = 0; + ret = ctf_dwarf_count_dies(dw, &derr, &ndies, errmsg, errlen); + if (ret != 0) { + *errp = ret; + goto out; + } + + (void) dwarf_finish(dw, &derr); + cdies = ctf_alloc(sizeof (ctf_die_t) * ndies); + if (cdies == NULL) { + *errp = ENOMEM; + return (CTF_CONV_ERROR); + } + + for (i = 0; i < ndies; i++) { + cdp = &cdies[i]; + ret = dwarf_elf_init(elf, DW_DLC_READ, NULL, NULL, + &cdp->cd_dwarf, &derr); + if (ret != 0) { + ctf_free(cdies, sizeof (ctf_die_t) * ndies); + (void) snprintf(errmsg, errlen, + "failed to initialize DWARF: %s\n", + dwarf_errmsg(derr)); + *errp = ECTF_CONVBKERR; + return (CTF_CONV_ERROR); + } + + ret = ctf_dwarf_init_die(fd, elf, &cdies[i], i, errmsg, errlen); + if (ret != 0) { + *errp = ret; + goto out; + } + } + + ctf_dprintf("found %d DWARF die(s)\n", ndies); + + /* + * If we only have one die, there's no reason to use multiple threads, + * even if the user requested them. After all, they just gave us an + * upper bound. + */ + if (ndies == 1) + nthrs = 1; + + if (workq_init(&wqp, nthrs) == -1) { + *errp = errno; + goto out; + } + + for (i = 0; i < ndies; i++) { + cdp = &cdies[i]; + ctf_dprintf("adding die %s: %p, %x %x\n", cdp->cd_name, + cdp->cd_cu, cdp->cd_cuoff, cdp->cd_maxoff); + if (workq_add(wqp, cdp) == -1) { + *errp = errno; + goto out; + } + } + + ret = workq_work(wqp, ctf_dwarf_convert_one, NULL, errp); + if (ret == WORKQ_ERROR) { + *errp = errno; + goto out; + } else if (ret == WORKQ_UERROR) { + ctf_dprintf("internal convert failed: %s\n", + ctf_errmsg(*errp)); + goto out; + } + + ctf_dprintf("Determining next phase: have %d dies\n", ndies); + if (ndies != 1) { + ctf_merge_t *cmp; + + cmp = ctf_merge_init(fd, &ret); + if (cmp == NULL) { + *errp = ret; + goto out; + } + + ctf_dprintf("setting threads\n"); + if ((ret = ctf_merge_set_nthreads(cmp, nthrs)) != 0) { + ctf_merge_fini(cmp); + *errp = ret; + goto out; + } + + ctf_dprintf("adding dies\n"); + for (i = 0; i < ndies; i++) { + cdp = &cdies[i]; + if ((ret = ctf_merge_add(cmp, cdp->cd_ctfp)) != 0) { + ctf_merge_fini(cmp); + *errp = ret; + goto out; + } + } + + ctf_dprintf("performing merge\n"); + ret = ctf_merge_merge(cmp, fpp); + if (ret != 0) { + ctf_dprintf("failed merge!\n"); + *fpp = NULL; + ctf_merge_fini(cmp); + *errp = ret; + goto out; + } + ctf_merge_fini(cmp); + *errp = 0; + ctf_dprintf("successfully converted!\n"); + } else { + *errp = 0; + *fpp = cdies->cd_ctfp; + cdies->cd_ctfp = NULL; + ctf_dprintf("successfully converted!\n"); + } + +out: + workq_fini(wqp); + ctf_dwarf_free_dies(cdies, ndies); + return (*fpp != NULL ? CTF_CONV_SUCCESS : CTF_CONV_ERROR); +} diff --git a/usr/src/lib/libctf/common/ctf_elfwrite.c b/usr/src/lib/libctf/common/ctf_elfwrite.c index c8a4d22cf7..4d7c10aeec 100644 --- a/usr/src/lib/libctf/common/ctf_elfwrite.c +++ b/usr/src/lib/libctf/common/ctf_elfwrite.c @@ -30,7 +30,7 @@ * Routines for writing ctf data to elf files, originally from the ctf tools. */ -#include <ctf_impl.h> +#include <libctf_impl.h> #include <libctf.h> #include <gelf.h> #include <sys/stat.h> @@ -39,7 +39,6 @@ #include <errno.h> #include <unistd.h> #include <libelf.h> -#include <sys/zmod.h> static int ctf_write_elf(ctf_file_t *fp, Elf *src, Elf *dst, int flags) @@ -52,37 +51,54 @@ ctf_write_elf(ctf_file_t *fp, Elf *src, Elf *dst, int flags) off_t new_offset = 0; off_t ctfnameoff = 0; int compress = (flags & CTF_ELFWRITE_F_COMPRESS); - int *secxlate; + int *secxlate = NULL; int srcidx, dstidx, pad, i; int curnmoff = 0; int changing = 0; + int ret; size_t nshdr, nphdr, strndx; + void *strdatabuf = NULL, *symdatabuf = NULL; + size_t strdatasz = 0, symdatasz = 0; void *cdata = NULL; + size_t elfsize, asize; - if ((flags & ~(CTF_ELFWRITE_F_COMPRESS)) != 0) - return (ctf_set_errno(fp, EINVAL)); - - if (gelf_newehdr(dst, gelf_getclass(src)) == NULL) - return (ctf_set_errno(fp, ECTF_ELF)); + if ((flags & ~(CTF_ELFWRITE_F_COMPRESS)) != 0) { + ret = ctf_set_errno(fp, EINVAL); + goto out; + } - if (gelf_getehdr(src, &sehdr) == NULL) - return (ctf_set_errno(fp, ECTF_ELF)); + if (gelf_newehdr(dst, gelf_getclass(src)) == NULL) { + ret = ctf_set_errno(fp, ECTF_ELF); + goto out; + } + if (gelf_getehdr(src, &sehdr) == NULL) { + ret = ctf_set_errno(fp, ECTF_ELF); + goto out; + } (void) memcpy(&dehdr, &sehdr, sizeof (GElf_Ehdr)); - if (gelf_update_ehdr(dst, &dehdr) == 0) - return (ctf_set_errno(fp, ECTF_ELF)); + if (gelf_update_ehdr(dst, &dehdr) == 0) { + ret = ctf_set_errno(fp, ECTF_ELF); + goto out; + } /* * Use libelf to get the number of sections and the string section to * deal with ELF files that may have a large number of sections. We just * always use this to make our live easier. */ - if (elf_getphdrnum(src, &nphdr) != 0) - return (ctf_set_errno(fp, ECTF_ELF)); - if (elf_getshdrnum(src, &nshdr) != 0) - return (ctf_set_errno(fp, ECTF_ELF)); - if (elf_getshdrstrndx(src, &strndx) != 0) - return (ctf_set_errno(fp, ECTF_ELF)); + if (elf_getphdrnum(src, &nphdr) != 0) { + ret = ctf_set_errno(fp, ECTF_ELF); + goto out; + } + if (elf_getshdrnum(src, &nshdr) != 0) { + ret = ctf_set_errno(fp, ECTF_ELF); + goto out; + } + if (elf_getshdrstrndx(src, &strndx) != 0) { + ret = ctf_set_errno(fp, ECTF_ELF); + goto out; + } /* * Neither the existing debug sections nor the SUNW_ctf sections (new or @@ -92,16 +108,22 @@ ctf_write_elf(ctf_file_t *fp, Elf *src, Elf *dst, int flags) */ if (nphdr != 0) { (void) elf_flagelf(dst, ELF_C_SET, ELF_F_LAYOUT); - if (gelf_newphdr(dst, nphdr) == NULL) - return (ctf_set_errno(fp, ECTF_ELF)); + if (gelf_newphdr(dst, nphdr) == NULL) { + ret = ctf_set_errno(fp, ECTF_ELF); + goto out; + } for (i = 0; i < nphdr; i++) { GElf_Phdr phdr; - if (gelf_getphdr(src, i, &phdr) == NULL) - return (ctf_set_errno(fp, ECTF_ELF)); - if (gelf_update_phdr(dst, i, &phdr) == 0) - return (ctf_set_errno(fp, ECTF_ELF)); + if (gelf_getphdr(src, i, &phdr) == NULL) { + ret = ctf_set_errno(fp, ECTF_ELF); + goto out; + } + if (gelf_update_phdr(dst, i, &phdr) == 0) { + ret = ctf_set_errno(fp, ECTF_ELF); + goto out; + } } } @@ -112,13 +134,13 @@ ctf_write_elf(ctf_file_t *fp, Elf *src, Elf *dst, int flags) char *sname; if (gelf_getshdr(scn, &shdr) == NULL) { - ctf_free(secxlate, sizeof (int) * nshdr); - return (ctf_set_errno(fp, ECTF_ELF)); + ret = ctf_set_errno(fp, ECTF_ELF); + goto out; } sname = elf_strptr(src, strndx, shdr.sh_name); if (sname == NULL) { - ctf_free(secxlate, sizeof (int) * nshdr); - return (ctf_set_errno(fp, ECTF_ELF)); + ret = ctf_set_errno(fp, ECTF_ELF); + goto out; } if (strcmp(sname, CTF_ELF_SCN_NAME) == 0) { @@ -136,8 +158,8 @@ ctf_write_elf(ctf_file_t *fp, Elf *src, Elf *dst, int flags) sscn = elf_getscn(src, srcidx); if (gelf_getshdr(sscn, &shdr) == NULL) { - ctf_free(secxlate, sizeof (int) * nshdr); - return (ctf_set_errno(fp, ECTF_ELF)); + ret = ctf_set_errno(fp, ECTF_ELF); + goto out; } if (secxlate[srcidx] == -1) { @@ -147,8 +169,8 @@ ctf_write_elf(ctf_file_t *fp, Elf *src, Elf *dst, int flags) dscn = elf_newscn(dst); if (dscn == NULL) { - ctf_free(secxlate, sizeof (int) * nshdr); - return (ctf_set_errno(fp, ECTF_ELF)); + ret = ctf_set_errno(fp, ECTF_ELF); + goto out; } /* @@ -173,28 +195,28 @@ ctf_write_elf(ctf_file_t *fp, Elf *src, Elf *dst, int flags) sname = elf_strptr(src, strndx, shdr.sh_name); if (sname == NULL) { - ctf_free(secxlate, sizeof (int) * nshdr); - return (ctf_set_errno(fp, ECTF_ELF)); + ret = ctf_set_errno(fp, ECTF_ELF); + goto out; } if ((sdata = elf_getdata(sscn, NULL)) == NULL) { - ctf_free(secxlate, sizeof (int) * nshdr); - return (ctf_set_errno(fp, ECTF_ELF)); + ret = ctf_set_errno(fp, ECTF_ELF); + goto out; } if ((ddata = elf_newdata(dscn)) == NULL) { - ctf_free(secxlate, sizeof (int) * nshdr); - return (ctf_set_errno(fp, ECTF_ELF)); + ret = ctf_set_errno(fp, ECTF_ELF); + goto out; } bcopy(sdata, ddata, sizeof (Elf_Data)); if (srcidx == strndx) { char seclen = strlen(CTF_ELF_SCN_NAME); - ddata->d_buf = ctf_alloc(ddata->d_size + shdr.sh_size + - seclen + 1); + strdatasz = ddata->d_size + shdr.sh_size + + seclen + 1; + ddata->d_buf = strdatabuf = ctf_alloc(strdatasz); if (ddata->d_buf == NULL) { - ctf_free(secxlate, - sizeof (int) * nshdr); - return (ctf_set_errno(fp, ECTF_ELF)); + ret = ctf_set_errno(fp, ECTF_ELF); + goto out; } bcopy(sdata->d_buf, ddata->d_buf, shdr.sh_size); (void) strcpy((caddr_t)ddata->d_buf + shdr.sh_size, @@ -212,11 +234,11 @@ ctf_write_elf(ctf_file_t *fp, Elf *src, Elf *dst, int flags) symtab_idx = secxlate[srcidx]; - ddata->d_buf = ctf_alloc(shdr.sh_size); + symdatasz = shdr.sh_size; + ddata->d_buf = symdatabuf = ctf_alloc(symdatasz); if (ddata->d_buf == NULL) { - ctf_free(secxlate, - sizeof (int) * nshdr); - return (ctf_set_errno(fp, ECTF_ELF)); + ret = ctf_set_errno(fp, ECTF_ELF); + goto out; } (void) bcopy(sdata->d_buf, ddata->d_buf, shdr.sh_size); @@ -236,19 +258,17 @@ ctf_write_elf(ctf_file_t *fp, Elf *src, Elf *dst, int flags) if (gelf_update_sym(ddata, i, &sym) == 0) { - ctf_free(secxlate, - sizeof (int) * - nshdr); - return (ctf_set_errno(fp, - ECTF_ELF)); + ret = ctf_set_errno(fp, + ECTF_ELF); + goto out; } } } } if (gelf_update_shdr(dscn, &shdr) == NULL) { - ctf_free(secxlate, sizeof (int) * nshdr); - return (ctf_set_errno(fp, ECTF_ELF)); + ret = ctf_set_errno(fp, ECTF_ELF); + goto out; } new_offset = (off_t)shdr.sh_offset; @@ -257,18 +277,18 @@ ctf_write_elf(ctf_file_t *fp, Elf *src, Elf *dst, int flags) } if (symtab_idx == -1) { - ctf_free(secxlate, sizeof (int) * nshdr); - return (ctf_set_errno(fp, ECTF_ELF)); + ret = ctf_set_errno(fp, ECTF_ELF); + goto out; } /* Add the ctf section */ if ((dscn = elf_newscn(dst)) == NULL) { - ctf_free(secxlate, sizeof (int) * nshdr); - return (ctf_set_errno(fp, ECTF_ELF)); + ret = ctf_set_errno(fp, ECTF_ELF); + goto out; } if (gelf_getshdr(dscn, &shdr) == NULL) { - ctf_free(secxlate, sizeof (int) * nshdr); - return (ctf_set_errno(fp, ECTF_ELF)); + ret = ctf_set_errno(fp, ECTF_ELF); + goto out; } shdr.sh_name = ctfnameoff; shdr.sh_type = SHT_PROGBITS; @@ -286,36 +306,24 @@ ctf_write_elf(ctf_file_t *fp, Elf *src, Elf *dst, int flags) } if ((ddata = elf_newdata(dscn)) == NULL) { - ctf_free(secxlate, sizeof (int) * nshdr); - return (ctf_set_errno(fp, ECTF_ELF)); + ret = ctf_set_errno(fp, ECTF_ELF); + goto out; } if (compress != 0) { - size_t dlen; - ctf_header_t *cthp; int err; if (ctf_zopen(&err) == NULL) { - ctf_free(secxlate, sizeof (int) * nshdr); - return (ctf_set_errno(fp, err)); + ret = ctf_set_errno(fp, err); + goto out; } - dlen = fp->ctf_size; - cdata = ctf_data_alloc(dlen); - bcopy(fp->ctf_base, cdata, sizeof (ctf_header_t)); - cthp = cdata; - cthp->cth_flags |= CTF_F_COMPRESS; - dlen -= sizeof (ctf_header_t); - if (z_compress((void *)((uintptr_t)cdata + - sizeof (ctf_header_t)), &dlen, - fp->ctf_base + sizeof (ctf_header_t), - fp->ctf_size - sizeof (ctf_header_t)) != Z_OK) { - ctf_data_free(cdata, fp->ctf_size); - ctf_free(secxlate, sizeof (int) * nshdr); - return (ctf_set_errno(fp, ECTF_ZLIB)); + if ((err = ctf_compress(fp, &cdata, &asize, &elfsize)) != 0) { + ret = ctf_set_errno(fp, err); + goto out; } ddata->d_buf = cdata; - ddata->d_size = dlen + sizeof (ctf_header_t); + ddata->d_size = elfsize; } else { ddata->d_buf = (void *)fp->ctf_base; ddata->d_size = fp->ctf_size; @@ -323,10 +331,8 @@ ctf_write_elf(ctf_file_t *fp, Elf *src, Elf *dst, int flags) ddata->d_align = shdr.sh_addralign; if (gelf_update_shdr(dscn, &shdr) == 0) { - if (cdata != NULL) - ctf_data_free(cdata, fp->ctf_size); - ctf_free(secxlate, sizeof (int) * nshdr); - return (ctf_set_errno(fp, ECTF_ELF)); + ret = ctf_set_errno(fp, ECTF_ELF); + goto out; } /* update the section header location */ @@ -346,23 +352,27 @@ ctf_write_elf(ctf_file_t *fp, Elf *src, Elf *dst, int flags) else dehdr.e_shstrndx = secxlate[sehdr.e_shstrndx]; if (gelf_update_ehdr(dst, &dehdr) == NULL) { - if (cdata != NULL) - ctf_data_free(cdata, fp->ctf_size); - ctf_free(secxlate, sizeof (int) * nshdr); - return (ctf_set_errno(fp, ECTF_ELF)); + ret = ctf_set_errno(fp, ECTF_ELF); + goto out; } if (elf_update(dst, ELF_C_WRITE) < 0) { - if (cdata != NULL) - ctf_data_free(cdata, fp->ctf_size); - ctf_free(secxlate, sizeof (int) * nshdr); - return (ctf_set_errno(fp, ECTF_ELF)); + ret = ctf_set_errno(fp, ECTF_ELF); + goto out; } + ret = 0; + +out: + if (strdatabuf != NULL) + ctf_free(strdatabuf, strdatasz); + if (symdatabuf != NULL) + ctf_free(symdatabuf, symdatasz); if (cdata != NULL) ctf_data_free(cdata, fp->ctf_size); - ctf_free(secxlate, sizeof (int) * nshdr); + if (secxlate != NULL) + ctf_free(secxlate, sizeof (int) * nshdr); - return (0); + return (ret); } int diff --git a/usr/src/lib/libctf/common/ctf_lib.c b/usr/src/lib/libctf/common/ctf_lib.c index 4658fc8ddc..6b637ba663 100644 --- a/usr/src/lib/libctf/common/ctf_lib.c +++ b/usr/src/lib/libctf/common/ctf_lib.c @@ -38,6 +38,7 @@ #include <gelf.h> #include <zlib.h> #include <zone.h> +#include <sys/debug.h> #ifdef _LP64 static const char *_libctf_zlib = "/usr/lib/64/libz.so.1"; @@ -57,6 +58,18 @@ static struct { static size_t _PAGESIZE; static size_t _PAGEMASK; +static uint64_t ctf_phase = 0; + +#define CTF_COMPRESS_CHUNK (64*1024) + +typedef struct ctf_zdata { + void *czd_buf; + void *czd_next; + ctf_file_t *czd_ctfp; + size_t czd_allocsz; + z_stream czd_zstr; +} ctf_zdata_t; + #pragma init(_libctf_init) void _libctf_init(void) @@ -145,6 +158,182 @@ z_strerror(int err) return (zlib.z_error(err)); } +static int +ctf_zdata_init(ctf_zdata_t *czd, ctf_file_t *fp) +{ + int err; + ctf_header_t *cthp; + + bzero(czd, sizeof (ctf_zdata_t)); + + czd->czd_allocsz = fp->ctf_size; + czd->czd_buf = ctf_data_alloc(czd->czd_allocsz); + if (czd->czd_buf == MAP_FAILED) + return (ctf_set_errno(fp, ENOMEM)); + + bcopy(fp->ctf_base, czd->czd_buf, sizeof (ctf_header_t)); + czd->czd_ctfp = fp; + cthp = czd->czd_buf; + cthp->cth_flags |= CTF_F_COMPRESS; + czd->czd_next = (void *)((uintptr_t)czd->czd_buf + + sizeof (ctf_header_t)); + + if ((err = zlib.z_initcomp(&czd->czd_zstr, Z_BEST_COMPRESSION, + ZLIB_VERSION, sizeof (z_stream))) != Z_OK) + return (ctf_set_errno(fp, ECTF_ZLIB)); + + return (0); +} + +static int +ctf_zdata_grow(ctf_zdata_t *czd) +{ + size_t off; + size_t newsz; + void *ndata; + + off = (uintptr_t)czd->czd_next - (uintptr_t)czd->czd_buf; + newsz = czd->czd_allocsz + CTF_COMPRESS_CHUNK; + ndata = ctf_data_alloc(newsz); + if (ndata == MAP_FAILED) { + return (ctf_set_errno(czd->czd_ctfp, ENOMEM)); + } + + bcopy(czd->czd_buf, ndata, off); + ctf_data_free(czd->czd_buf, czd->czd_allocsz); + czd->czd_allocsz = newsz; + czd->czd_buf = ndata; + czd->czd_next = (void *)((uintptr_t)ndata + off); + + czd->czd_zstr.next_out = (Bytef *)czd->czd_next; + czd->czd_zstr.avail_out = CTF_COMPRESS_CHUNK; + return (0); +} + +static int +ctf_zdata_compress_buffer(ctf_zdata_t *czd, const void *buf, size_t bufsize) +{ + int err; + + czd->czd_zstr.next_out = czd->czd_next; + czd->czd_zstr.avail_out = czd->czd_allocsz - + (czd->czd_next - czd->czd_buf); + czd->czd_zstr.next_in = (Bytef *)buf; + czd->czd_zstr.avail_in = bufsize; + + while (czd->czd_zstr.avail_in != 0) { + if (czd->czd_zstr.avail_out == 0) { + czd->czd_next = czd->czd_zstr.next_out; + if ((err = ctf_zdata_grow(czd)) != 0) { + return (err); + } + } + + if ((err = zlib.z_compress(&czd->czd_zstr, Z_NO_FLUSH)) != Z_OK) + return (ctf_set_errno(czd->czd_ctfp, ECTF_ZLIB)); + } + czd->czd_next = czd->czd_zstr.next_out; + + return (0); +} + +static int +ctf_zdata_flush(ctf_zdata_t *czd, boolean_t finish) +{ + int err; + int flag = finish == B_TRUE ? Z_FINISH : Z_FULL_FLUSH; + int bret = finish == B_TRUE ? Z_STREAM_END : Z_BUF_ERROR; + + for (;;) { + if (czd->czd_zstr.avail_out == 0) { + czd->czd_next = czd->czd_zstr.next_out; + if ((err = ctf_zdata_grow(czd)) != 0) { + return (err); + } + } + + err = zlib.z_compress(&czd->czd_zstr, flag); + if (err == bret) { + break; + } + if (err != Z_OK) + return (ctf_set_errno(czd->czd_ctfp, ECTF_ZLIB)); + + } + + czd->czd_next = czd->czd_zstr.next_out; + + return (0); +} + +static int +ctf_zdata_end(ctf_zdata_t *czd) +{ + int ret; + + if ((ret = ctf_zdata_flush(czd, B_TRUE)) != 0) + return (ret); + + if ((ret = zlib.z_finicomp(&czd->czd_zstr)) != 0) + return (ctf_set_errno(czd->czd_ctfp, ECTF_ZLIB)); + + return (0); +} + +static void +ctf_zdata_cleanup(ctf_zdata_t *czd) +{ + ctf_data_free(czd->czd_buf, czd->czd_allocsz); + (void) zlib.z_finicomp(&czd->czd_zstr); +} + +/* + * Compress our CTF data and return both the size of the compressed data and the + * size of the allocation. These may be different due to the nature of + * compression. + * + * In addition, we flush the compression inbetween our two phases such that we + * maintain a different dictionary bbetween the CTF data and the string section. + */ +int +ctf_compress(ctf_file_t *fp, void **buf, size_t *allocsz, size_t *elfsize) +{ + int err; + ctf_zdata_t czd; + ctf_header_t *cthp = (ctf_header_t *)fp->ctf_base; + + if ((err = ctf_zdata_init(&czd, fp)) != 0) + return (err); + + if ((err = ctf_zdata_compress_buffer(&czd, fp->ctf_buf, + cthp->cth_stroff)) != 0) { + ctf_zdata_cleanup(&czd); + return (err); + } + + if ((err = ctf_zdata_flush(&czd, B_FALSE)) != 0) { + ctf_zdata_cleanup(&czd); + return (err); + } + + if ((err = ctf_zdata_compress_buffer(&czd, + fp->ctf_buf + cthp->cth_stroff, cthp->cth_strlen)) != 0) { + ctf_zdata_cleanup(&czd); + return (err); + } + + if ((err = ctf_zdata_end(&czd)) != 0) { + ctf_zdata_cleanup(&czd); + return (err); + } + + *buf = czd.czd_buf; + *allocsz = czd.czd_allocsz; + *elfsize = (uintptr_t)(czd.czd_next - czd.czd_buf); + + return (0); +} + int z_compress(void *dst, size_t *dstlen, const void *src, size_t srclen) { @@ -576,3 +765,25 @@ ctf_version(int version) return (_libctf_version); } + +/* + * A utility function for folks debugging CTF conversion and merging. + */ +void +ctf_phase_dump(ctf_file_t *fp, const char *phase) +{ + int fd; + static char *base; + char path[MAXPATHLEN]; + + if (base == NULL && (base = getenv("LIBCTF_WRITE_PHASES")) == NULL) + return; + + (void) snprintf(path, sizeof (path), "%s/libctf.%s.%d.ctf", base, + phase != NULL ? phase : "", + ctf_phase); + if ((fd = open(path, O_CREAT | O_TRUNC | O_RDWR, 0777)) < 0) + return; + (void) ctf_write(fp, fd); + (void) close(fd); +} diff --git a/usr/src/lib/libctf/common/ctf_merge.c b/usr/src/lib/libctf/common/ctf_merge.c index 70676bb0fd..9611c50acc 100644 --- a/usr/src/lib/libctf/common/ctf_merge.c +++ b/usr/src/lib/libctf/common/ctf_merge.c @@ -31,16 +31,15 @@ * we should take care to do the merge in the same way every time. */ -#include <ctf_impl.h> -#include <libctf.h> +#include <libctf_impl.h> #include <sys/debug.h> #include <sys/list.h> #include <stddef.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> - -#include <stdio.h> +#include <mergeq.h> +#include <errno.h> typedef struct ctf_merge_tinfo { uint16_t cmt_map; /* Map to the type in out */ @@ -56,6 +55,7 @@ typedef struct ctf_merge_types { ctf_file_t *cm_out; /* Output CTF file */ ctf_file_t *cm_src; /* Input CTF file */ ctf_merge_tinfo_t *cm_tmap; /* Type state information */ + boolean_t cm_dedup; /* Are we doing a dedup? */ } ctf_merge_types_t; typedef struct ctf_merge_objmap { @@ -80,11 +80,13 @@ typedef struct ctf_merge_input { ctf_file_t *cmi_input; list_t cmi_omap; list_t cmi_fmap; + boolean_t cmi_created; } ctf_merge_input_t; struct ctf_merge_handle { - ctf_file_t *cmh_output; /* Final output */ list_t cmh_inputs; /* Input list */ + uint_t cmh_ninputs; /* Number of inputs */ + uint_t cmh_nthreads; /* Number of threads to use */ ctf_file_t *cmh_unique; /* ctf to uniquify against */ boolean_t cmh_msyms; /* Should we merge symbols/funcs? */ int cmh_ofd; /* FD for output file */ @@ -95,6 +97,22 @@ struct ctf_merge_handle { static int ctf_merge_add_type(ctf_merge_types_t *, ctf_id_t); +static ctf_id_t +ctf_merge_gettype(ctf_merge_types_t *cmp, ctf_id_t id) +{ + if (cmp->cm_dedup == B_FALSE) { + VERIFY(cmp->cm_tmap[id].cmt_map != 0); + return (cmp->cm_tmap[id].cmt_map); + } + + while (cmp->cm_tmap[id].cmt_missing == B_FALSE) { + VERIFY(cmp->cm_tmap[id].cmt_map != 0); + id = cmp->cm_tmap[id].cmt_map; + } + VERIFY(cmp->cm_tmap[id].cmt_map != 0); + return (cmp->cm_tmap[id].cmt_map); +} + static void ctf_merge_diffcb(ctf_file_t *ifp, ctf_id_t iid, boolean_t same, ctf_file_t *ofp, ctf_id_t oid, void *arg) @@ -108,6 +126,8 @@ ctf_merge_diffcb(ctf_file_t *ifp, ctf_id_t iid, boolean_t same, ctf_file_t *ofp, VERIFY(cmt[oid].cmt_map == 0); cmt[oid].cmt_map = iid; cmt[oid].cmt_forward = B_TRUE; + ctf_dprintf("merge diff forward mapped %d->%d\n", oid, + iid); return; } @@ -119,9 +139,11 @@ ctf_merge_diffcb(ctf_file_t *ifp, ctf_id_t iid, boolean_t same, ctf_file_t *ofp, if (cmt[oid].cmt_map != 0) return; cmt[oid].cmt_map = iid; + ctf_dprintf("merge diff mapped %d->%d\n", oid, iid); } else if (ifp == cmp->cm_src) { VERIFY(cmt[iid].cmt_map == 0); cmt[iid].cmt_missing = B_TRUE; + ctf_dprintf("merge diff said %d is missing\n", iid); } } @@ -176,7 +198,7 @@ ctf_merge_add_array(ctf_merge_types_t *cmp, ctf_id_t id) return (ret); ASSERT(cmp->cm_tmap[ar.ctr_contents].cmt_map != 0); } - ar.ctr_contents = cmp->cm_tmap[ar.ctr_contents].cmt_map; + ar.ctr_contents = ctf_merge_gettype(cmp, ar.ctr_contents); if (cmp->cm_tmap[ar.ctr_index].cmt_map == 0) { ret = ctf_merge_add_type(cmp, ar.ctr_index); @@ -184,7 +206,7 @@ ctf_merge_add_array(ctf_merge_types_t *cmp, ctf_id_t id) return (ret); ASSERT(cmp->cm_tmap[ar.ctr_index].cmt_map != 0); } - ar.ctr_index = cmp->cm_tmap[ar.ctr_index].cmt_map; + ar.ctr_index = ctf_merge_gettype(cmp, ar.ctr_index); ret = ctf_add_array(cmp->cm_out, flags, &ar); if (ret == CTF_ERR) @@ -221,7 +243,7 @@ ctf_merge_add_reftype(ctf_merge_types_t *cmp, ctf_id_t id) return (ret); ASSERT(cmp->cm_tmap[reftype].cmt_map != 0); } - reftype = cmp->cm_tmap[reftype].cmt_map; + reftype = ctf_merge_gettype(cmp, reftype); ret = ctf_add_reftype(cmp->cm_out, flags, name, reftype, ctf_type_kind(cmp->cm_src, id)); @@ -258,7 +280,7 @@ ctf_merge_add_typedef(ctf_merge_types_t *cmp, ctf_id_t id) return (ret); ASSERT(cmp->cm_tmap[reftype].cmt_map != 0); } - reftype = cmp->cm_tmap[reftype].cmt_map; + reftype = ctf_merge_gettype(cmp, reftype); ret = ctf_add_typedef(cmp->cm_out, flags, name, reftype); if (ret == CTF_ERR) @@ -346,7 +368,7 @@ ctf_merge_add_func(ctf_merge_types_t *cmp, ctf_id_t id) return (ret); ASSERT(cmp->cm_tmap[ctc.ctc_return].cmt_map != 0); } - ctc.ctc_return = cmp->cm_tmap[ctc.ctc_return].cmt_map; + ctc.ctc_return = ctf_merge_gettype(cmp, ctc.ctc_return); for (i = 0; i < ctc.ctc_argc; i++) { if (cmp->cm_tmap[argv[i]].cmt_map == 0) { @@ -355,7 +377,7 @@ ctf_merge_add_func(ctf_merge_types_t *cmp, ctf_id_t id) return (ret); ASSERT(cmp->cm_tmap[argv[i]].cmt_map != 0); } - argv[i] = cmp->cm_tmap[argv[i]].cmt_map; + argv[i] = ctf_merge_gettype(cmp, argv[i]); } ret = ctf_add_funcptr(cmp->cm_out, flags, &ctc, argv); @@ -411,6 +433,7 @@ ctf_merge_add_member(const char *name, ctf_id_t type, ulong_t offset, void *arg) VERIFY(cms->cms_cm->cm_tmap[type].cmt_map != 0); type = cms->cms_cm->cm_tmap[type].cmt_map; + ctf_dprintf("Trying to add member %s to %d\n", name, cms->cms_id); return (ctf_add_member(cms->cms_cm->cm_out, cms->cms_id, name, type, offset) == CTF_ERR); } @@ -421,7 +444,7 @@ ctf_merge_add_member(const char *name, ctf_id_t type, ulong_t offset, void *arg) * mark all structures and unions as needing to be fixed up. */ static int -ctf_merge_add_su(ctf_merge_types_t *cmp, ctf_id_t id, boolean_t forward) +ctf_merge_add_sou(ctf_merge_types_t *cmp, ctf_id_t id, boolean_t forward) { int flags, kind; const ctf_type_t *tp; @@ -451,6 +474,8 @@ ctf_merge_add_su(ctf_merge_types_t *cmp, ctf_id_t id, boolean_t forward) if (forward == B_FALSE) { VERIFY(cmp->cm_tmap[id].cmt_map == 0); cmp->cm_tmap[id].cmt_map = suid; + ctf_dprintf("added sou \"%s\" as (%d) %d->%d\n", name, kind, id, + suid); } else { VERIFY(cmp->cm_tmap[id].cmt_map == suid); } @@ -501,7 +526,7 @@ ctf_merge_add_type(ctf_merge_types_t *cmp, ctf_id_t id) break; case CTF_K_STRUCT: case CTF_K_UNION: - ret = ctf_merge_add_su(cmp, id, B_FALSE); + ret = ctf_merge_add_sou(cmp, id, B_FALSE); break; case CTF_K_UNKNOWN: /* @@ -517,22 +542,29 @@ ctf_merge_add_type(ctf_merge_types_t *cmp, ctf_id_t id) } static int -ctf_merge_fixup_su(ctf_merge_types_t *cmp, ctf_id_t id) +ctf_merge_fixup_sou(ctf_merge_types_t *cmp, ctf_id_t id) { ctf_dtdef_t *dtd; ctf_merge_su_t cms; ctf_id_t mapid; + ssize_t size; mapid = cmp->cm_tmap[id].cmt_map; VERIFY(mapid != 0); dtd = ctf_dtd_lookup(cmp->cm_out, mapid); VERIFY(dtd != NULL); + ctf_dprintf("Trying to fix up sou %d\n", id); cms.cms_cm = cmp; cms.cms_id = mapid; if (ctf_member_iter(cmp->cm_src, id, ctf_merge_add_member, &cms) != 0) return (CTF_ERR); + if ((size = ctf_type_size(cmp->cm_src, id)) == CTF_ERR) + return (CTF_ERR); + if (ctf_set_size(cmp->cm_out, mapid, size) == CTF_ERR) + return (CTF_ERR); + return (0); } @@ -545,7 +577,7 @@ ctf_merge_fixup_type(ctf_merge_types_t *cmp, ctf_id_t id) switch (kind) { case CTF_K_STRUCT: case CTF_K_UNION: - ret = ctf_merge_fixup_su(cmp, id); + ret = ctf_merge_fixup_sou(cmp, id); break; default: VERIFY(0); @@ -555,6 +587,41 @@ ctf_merge_fixup_type(ctf_merge_types_t *cmp, ctf_id_t id) return (ret); } +/* + * Now that we've successfully merged everything, we're going to clean + * up the merge type table. Traditionally if we had just two different + * files that we were working between, the types would be fully + * resolved. However, because we were comparing with ourself every step + * of the way and not our reduced self, we need to go through and update + * every mapped entry to what it now points to in the deduped file. + */ +static void +ctf_merge_fixup_dedup_map(ctf_merge_types_t *cmp) +{ + int i; + + for (i = 1; i < cmp->cm_src->ctf_typemax + 1; i++) { + ctf_id_t tid; + + /* + * Missing types always have their id updated to exactly what it + * should be. + */ + if (cmp->cm_tmap[i].cmt_missing == B_TRUE) { + VERIFY(cmp->cm_tmap[i].cmt_map != 0); + continue; + } + + tid = i; + while (cmp->cm_tmap[tid].cmt_missing == B_FALSE) { + VERIFY(cmp->cm_tmap[tid].cmt_map != 0); + tid = cmp->cm_tmap[tid].cmt_map; + } + VERIFY(cmp->cm_tmap[tid].cmt_map != 0); + cmp->cm_tmap[i].cmt_map = cmp->cm_tmap[tid].cmt_map; + } +} + /* * We're going to do three passes over the containers. @@ -571,19 +638,23 @@ ctf_merge_fixup_type(ctf_merge_types_t *cmp, ctf_id_t id) * * Importantly, we *must* call ctf_update between the second and third pass, * otherwise several of the libctf functions will not properly find the data in - * the container. + * the container. If we're doing a dedup we also fix up the type mapping. */ static int ctf_merge_common(ctf_merge_types_t *cmp) { int ret, i; + ctf_phase_dump(cmp->cm_src, "merge-common-src"); + ctf_phase_dump(cmp->cm_out, "merge-common-dest"); + /* Pass 1 */ for (i = 1; i <= cmp->cm_src->ctf_typemax; i++) { if (cmp->cm_tmap[i].cmt_forward == B_TRUE) { - ret = ctf_merge_add_su(cmp, i, B_TRUE); - if (ret != 0) + ret = ctf_merge_add_sou(cmp, i, B_TRUE); + if (ret != 0) { return (ret); + } } } @@ -591,8 +662,10 @@ ctf_merge_common(ctf_merge_types_t *cmp) for (i = 1; i <= cmp->cm_src->ctf_typemax; i++) { if (cmp->cm_tmap[i].cmt_missing == B_TRUE) { ret = ctf_merge_add_type(cmp, i); - if (ret != 0) + if (ret != 0) { + ctf_dprintf("Failed to merge type %d\n", i); return (ret); + } } } @@ -600,6 +673,11 @@ ctf_merge_common(ctf_merge_types_t *cmp) if (ret != 0) return (ret); + if (cmp->cm_dedup == B_TRUE) { + ctf_merge_fixup_dedup_map(cmp); + } + + ctf_dprintf("Beginning merge pass 3\n"); /* Pass 3 */ for (i = 1; i <= cmp->cm_src->ctf_typemax; i++) { if (cmp->cm_tmap[i].cmt_fixup == B_TRUE) { @@ -609,6 +687,10 @@ ctf_merge_common(ctf_merge_types_t *cmp) } } + if (cmp->cm_dedup == B_TRUE) { + ctf_merge_fixup_dedup_map(cmp); + } + return (0); } @@ -666,18 +748,24 @@ ctf_merge_types_fini(ctf_merge_types_t *cmp) } /* - * Merge types from targ into dest. + * Merge the types contained inside of two input files. The second input file is + * always going to be the destination. We're guaranteed that it's always + * writeable. */ static int -ctf_merge(ctf_file_t **outp, ctf_merge_input_t *cmi) +ctf_merge_types(void *arg, void *arg2, void **outp, void *unsued) { int ret; ctf_merge_types_t cm; ctf_diff_t *cdp; ctf_merge_objmap_t *cmo; ctf_merge_funcmap_t *cmf; - ctf_file_t *out = *outp; - ctf_file_t *source = cmi->cmi_input; + ctf_merge_input_t *scmi = arg; + ctf_merge_input_t *dcmi = arg2; + ctf_file_t *out = dcmi->cmi_input; + ctf_file_t *source = scmi->cmi_input; + + ctf_dprintf("merging %p->%p\n", source, out); if (!(out->ctf_flags & LCTF_RDWR)) return (ctf_set_errno(out, ECTF_RDONLY)); @@ -690,6 +778,7 @@ ctf_merge(ctf_file_t **outp, ctf_merge_input_t *cmi) cm.cm_out = out; cm.cm_src = source; + cm.cm_dedup = B_FALSE; ret = ctf_merge_types_init(&cm); if (ret != 0) { ctf_diff_fini(cdp); @@ -700,24 +789,27 @@ ctf_merge(ctf_file_t **outp, ctf_merge_input_t *cmi) if (ret != 0) goto cleanup; ret = ctf_merge_common(&cm); - if (ret == 0) + ctf_dprintf("merge common returned with %d\n", ret); + if (ret == 0) { ret = ctf_update(out); - else + ctf_dprintf("update returned with %d\n", ret); + } else { goto cleanup; + } /* * Now we need to fix up the object and function maps. */ - for (cmo = list_head(&cmi->cmi_omap); cmo != NULL; - cmo = list_next(&cmi->cmi_omap, cmo)) { + for (cmo = list_head(&scmi->cmi_omap); cmo != NULL; + cmo = list_next(&scmi->cmi_omap, cmo)) { if (cmo->cmo_tid == 0) continue; VERIFY(cm.cm_tmap[cmo->cmo_tid].cmt_map != 0); cmo->cmo_tid = cm.cm_tmap[cmo->cmo_tid].cmt_map; } - for (cmf = list_head(&cmi->cmi_fmap); cmf != NULL; - cmf = list_next(&cmi->cmi_fmap, cmf)) { + for (cmf = list_head(&scmi->cmi_fmap); cmf != NULL; + cmf = list_next(&scmi->cmi_fmap, cmf)) { int i; VERIFY(cm.cm_tmap[cmf->cmf_rtid].cmt_map != 0); @@ -728,28 +820,36 @@ ctf_merge(ctf_file_t **outp, ctf_merge_input_t *cmi) } } + /* + * Now that we've fixed things up, we need to give our function and + * object maps to the destination, such that it can continue to update + * them going forward. + */ + list_move_tail(&dcmi->cmi_fmap, &scmi->cmi_fmap); + list_move_tail(&dcmi->cmi_omap, &scmi->cmi_omap); + cleanup: + if (ret == 0) + *outp = dcmi; ctf_merge_types_fini(&cm); ctf_diff_fini(cdp); - return (ret); + if (ret != 0) + return (ctf_errno(out)); + return (0); } static int -ctf_uniquify_types(ctf_merge_t *cmh, ctf_file_t **outp) +ctf_uniquify_types(ctf_merge_t *cmh, ctf_file_t *src, ctf_file_t **outp) { int err, ret; ctf_file_t *out; ctf_merge_types_t cm; ctf_diff_t *cdp; ctf_merge_input_t *cmi; - ctf_file_t *src = cmh->cmh_output; ctf_file_t *parent = cmh->cmh_unique; *outp = NULL; - if (cmh->cmh_ofd == -1) - out = ctf_create(&err); - else - out = ctf_fdcreate(cmh->cmh_ofd, &err); + out = ctf_fdcreate(cmh->cmh_ofd, &err); if (out == NULL) return (ctf_set_errno(src, err)); @@ -773,6 +873,7 @@ ctf_uniquify_types(ctf_merge_t *cmh, ctf_file_t **outp) cm.cm_out = parent; cm.cm_src = src; + cm.cm_dedup = B_FALSE; ret = ctf_merge_types_init(&cm); if (ret != 0) { ctf_close(out); @@ -841,6 +942,9 @@ ctf_merge_fini_input(ctf_merge_input_t *cmi) ctf_free(cmf, sizeof (ctf_merge_funcmap_t) + sizeof (ctf_id_t) * cmf->cmf_argc); + if (cmi->cmi_created == B_TRUE && cmi->cmi_input != NULL) + ctf_close(cmi->cmi_input); + ctf_free(cmi, sizeof (ctf_merge_input_t)); } @@ -850,9 +954,6 @@ ctf_merge_fini(ctf_merge_t *cmh) size_t len; ctf_merge_input_t *cmi; - if (cmh->cmh_output != NULL) - ctf_close(cmh->cmh_output); - if (cmh->cmh_label != NULL) { len = strlen(cmh->cmh_label) + 1; ctf_free(cmh->cmh_label, len); @@ -891,22 +992,18 @@ ctf_merge_init(int fd, int *errp) } if (fd == -1) { - out->cmh_output = ctf_create(errp); out->cmh_msyms = B_FALSE; } else { - out->cmh_output = ctf_fdcreate(fd, errp); out->cmh_msyms = B_TRUE; } - if (out->cmh_output == NULL) { - ctf_free(out, sizeof (ctf_merge_t)); - return (NULL); - } - list_create(&out->cmh_inputs, sizeof (ctf_merge_input_t), offsetof(ctf_merge_input_t, cmi_node)); + out->cmh_ninputs = 0; + out->cmh_nthreads = 1; out->cmh_unique = NULL; out->cmh_ofd = fd; + out->cmh_flags = 0; out->cmh_label = NULL; out->cmh_pname = NULL; @@ -918,9 +1015,6 @@ ctf_merge_label(ctf_merge_t *cmh, const char *label) { char *dup; - if (cmh->cmh_output == NULL) - return (EINVAL); - if (label == NULL) return (EINVAL); @@ -983,14 +1077,17 @@ ctf_merge_add_objs(const char *name, ctf_id_t id, ulong_t idx, void *arg) return (0); } +/* + * Whenever we create an entry to merge, we then go and add a second empty + * ctf_file_t which we use for the purposes of our merging. It's not the best, + * but it's the best that we've got at the moment. + */ int ctf_merge_add(ctf_merge_t *cmh, ctf_file_t *input) { int ret; ctf_merge_input_t *cmi; - - if (cmh->cmh_output == NULL) - return (EINVAL); + ctf_file_t *empty; if (input->ctf_flags & LCTF_CHILD) return (ECTF_MCHILD); @@ -999,6 +1096,7 @@ ctf_merge_add(ctf_merge_t *cmh, ctf_file_t *input) if (cmi == NULL) return (ENOMEM); + cmi->cmi_created = B_FALSE; cmi->cmi_input = input; list_create(&cmi->cmi_fmap, sizeof (ctf_merge_funcmap_t), offsetof(ctf_merge_funcmap_t, cmf_node)); @@ -1020,6 +1118,30 @@ ctf_merge_add(ctf_merge_t *cmh, ctf_file_t *input) } list_insert_tail(&cmh->cmh_inputs, cmi); + cmh->cmh_ninputs++; + + /* And now the empty one to merge into this */ + cmi = ctf_alloc(sizeof (ctf_merge_input_t)); + if (cmi == NULL) + return (ENOMEM); + list_create(&cmi->cmi_fmap, sizeof (ctf_merge_funcmap_t), + offsetof(ctf_merge_funcmap_t, cmf_node)); + list_create(&cmi->cmi_omap, sizeof (ctf_merge_funcmap_t), + offsetof(ctf_merge_objmap_t, cmo_node)); + + empty = ctf_fdcreate(cmh->cmh_ofd, &ret); + if (empty == NULL) + return (ret); + cmi->cmi_input = empty; + cmi->cmi_created = B_TRUE; + + if (ctf_setmodel(empty, ctf_getmodel(input)) == CTF_ERR) { + return (ctf_errno(empty)); + } + + list_insert_tail(&cmh->cmh_inputs, cmi); + cmh->cmh_ninputs++; + ctf_dprintf("added containers %p and %p\n", input, empty); return (0); } @@ -1028,8 +1150,6 @@ ctf_merge_uniquify(ctf_merge_t *cmh, ctf_file_t *u, const char *pname) { char *dup; - if (cmh->cmh_output == NULL) - return (EINVAL); if (u->ctf_flags & LCTF_CHILD) return (ECTF_MCHILD); if (pname == NULL) @@ -1093,8 +1213,12 @@ found: if (cmo != NULL) { if (cmo->cmo_tid == 0) continue; - if ((err = ctf_add_object(fp, i, cmo->cmo_tid)) != 0) + if ((err = ctf_add_object(fp, i, cmo->cmo_tid)) != 0) { + ctf_dprintf("Failed to add symbol %s->%d: %s\n", + name, cmo->cmo_tid, + ctf_errmsg(ctf_errno(fp))); return (err); + } } } @@ -1161,12 +1285,14 @@ found: } int -ctf_merge_merge(ctf_merge_t *cmh, ctf_file_t **out) +ctf_merge_merge(ctf_merge_t *cmh, ctf_file_t **outp) { - int err; + int err, merr; ctf_merge_input_t *cmi; - boolean_t mset = B_FALSE; ctf_id_t ltype; + mergeq_t *mqp; + ctf_merge_input_t *final; + ctf_file_t *out; if (cmh->cmh_label != NULL && cmh->cmh_unique != NULL) { const char *label = ctf_label_topmost(cmh->cmh_unique); @@ -1176,56 +1302,248 @@ ctf_merge_merge(ctf_merge_t *cmh, ctf_file_t **out) return (ECTF_LCONFLICT); } + if (mergeq_init(&mqp, cmh->cmh_nthreads) == -1) { + return (errno); + } + /* * We should consider doing a divide and conquer and parallel merge * here. If we did, we'd want to use some number of threads to perform * this operation. */ + VERIFY(cmh->cmh_ninputs % 2 == 0); for (cmi = list_head(&cmh->cmh_inputs); cmi != NULL; cmi = list_next(&cmh->cmh_inputs, cmi)) { - if (mset == B_FALSE) { - if (ctf_setmodel(cmh->cmh_output, - ctf_getmodel(cmi->cmi_input)) != 0) { - return (ctf_errno(cmh->cmh_output)); - } - mset = B_TRUE; + if (mergeq_add(mqp, cmi) == -1) { + err = errno; + mergeq_fini(mqp); } - err = ctf_merge(&cmh->cmh_output, cmi); - if (err != 0) - return (ctf_errno(cmh->cmh_output)); } + err = mergeq_merge(mqp, ctf_merge_types, NULL, (void **)&final, &merr); + mergeq_fini(mqp); + + if (err == MERGEQ_ERROR) { + return (errno); + } else if (err == MERGEQ_UERROR) { + return (merr); + } + + /* + * Disassociate the generated ctf_file_t from the original input. That + * way when the input gets cleaned up, we don't accidentally kill the + * final reference to the ctf_file_t. If it gets uniquified then we'll + * kill it. + */ + VERIFY(final->cmi_input != NULL); + out = final->cmi_input; + final->cmi_input = NULL; + + ctf_dprintf("preparing to uniquify against: %p\n", cmh->cmh_unique); if (cmh->cmh_unique != NULL) { - err = ctf_uniquify_types(cmh, out); - if (err != 0) - return (ctf_errno(cmh->cmh_output)); - ctf_close(cmh->cmh_output); - } else { - *out = cmh->cmh_output; + ctf_file_t *u; + err = ctf_uniquify_types(cmh, out, &u); + if (err != 0) { + err = ctf_errno(out); + ctf_close(out); + return (err); + } + ctf_close(out); + out = u; } - ltype = (*out)->ctf_typemax; - if (((*out)->ctf_flags & LCTF_CHILD) && ltype != 0) + ltype = out->ctf_typemax; + if ((out->ctf_flags & LCTF_CHILD) && ltype != 0) ltype += 0x8000; - if (ctf_add_label(*out, cmh->cmh_label, ltype, 0) != 0) { - return (ctf_errno(*out)); + ctf_dprintf("trying to add the label\n"); + if (cmh->cmh_label != NULL && + ctf_add_label(out, cmh->cmh_label, ltype, 0) != 0) { + ctf_close(out); + return (ctf_errno(out)); } + ctf_dprintf("merging symbols and the like\n"); if (cmh->cmh_msyms == B_TRUE) { - err = ctf_merge_symbols(cmh, *out); - if (err != 0) - return (ctf_errno(*out)); + err = ctf_merge_symbols(cmh, out); + if (err != 0) { + ctf_close(out); + return (ctf_errno(out)); + } + + err = ctf_merge_functions(cmh, out); + if (err != 0) { + ctf_close(out); + return (ctf_errno(out)); + } + } + + err = ctf_update(out); + if (err != 0) { + ctf_close(out); + return (ctf_errno(out)); + } + + *outp = out; + return (0); +} + +/* + * When we get told that something is unique, eg. same is B_FALSE, then that + * tells us that we need to add it to the output. If same is B_TRUE, then we'll + * want to record it in the mapping table so that we know how to redirect types + * to the extant ones. + */ +static void +ctf_dedup_cb(ctf_file_t *ifp, ctf_id_t iid, boolean_t same, ctf_file_t *ofp, + ctf_id_t oid, void *arg) +{ + ctf_merge_types_t *cmp = arg; + ctf_merge_tinfo_t *cmt = cmp->cm_tmap; - err = ctf_merge_functions(cmh, *out); - if (err != 0) - return (ctf_errno(*out)); + if (same == B_TRUE) { + /* + * The output id here may itself map to something else. + * Therefore, we need to basically walk a chain and see what it + * points to until it itself points to a base type, eg. -1. + * Otherwise we'll dedup to something which no longer exists. + */ + while (cmt[oid].cmt_missing == B_FALSE) + oid = cmt[oid].cmt_map; + cmt[iid].cmt_map = oid; + ctf_dprintf("%d->%d \n", iid, oid); + } else { + VERIFY(cmt[iid].cmt_map == 0); + cmt[iid].cmt_missing = B_TRUE; + ctf_dprintf("%d is missing\n", iid); } +} - err = ctf_update(*out); - if (err != 0) - return (ctf_errno(*out)); +/* + * Dedup a CTF container. + * + * DWARF and other encoding formats that we use to create CTF data may create + * multiple copies of a given type. However, after doing a conversion, and + * before doing a merge, we'd prefer, if possible, to have every input container + * to be unique. + * + * Doing a deduplication is like a normal merge. However, when we diff the types + * in the container, rather than doing a normal diff, we instead want to diff + * against any already processed types. eg, for a given type i in a container, + * we want to diff it from 0 to i - 1. + */ +int +ctf_merge_dedup(ctf_merge_t *cmp, ctf_file_t **outp) +{ + int ret; + ctf_diff_t *cdp = NULL; + ctf_merge_input_t *cmi, *cmc; + ctf_file_t *ifp, *ofp; + ctf_merge_objmap_t *cmo; + ctf_merge_funcmap_t *cmf; + ctf_merge_types_t cm; - cmh->cmh_output = NULL; + if (cmp == NULL || outp == NULL) + return (EINVAL); + + ctf_dprintf("encountered %d inputs\n", cmp->cmh_ninputs); + if (cmp->cmh_ninputs != 2) + return (EINVAL); + + ctf_dprintf("passed argument sanity check\n"); + + cmi = list_head(&cmp->cmh_inputs); + VERIFY(cmi != NULL); + cmc = list_next(&cmp->cmh_inputs, cmi); + VERIFY(cmc != NULL); + ifp = cmi->cmi_input; + ofp = cmc->cmi_input; + VERIFY(ifp != NULL); + VERIFY(ofp != NULL); + cm.cm_src = ifp; + cm.cm_out = ofp; + cm.cm_dedup = B_TRUE; + + if ((ret = ctf_merge_types_init(&cm)) != 0) { + return (ret); + } + if ((ret = ctf_diff_init(ifp, ifp, &cdp)) != 0) + goto err; + + ctf_dprintf("Successfully initialized dedup\n"); + if ((ret = ctf_diff_self(cdp, ctf_dedup_cb, &cm)) != 0) + goto err; + + ctf_dprintf("Successfully diffed types\n"); + ret = ctf_merge_common(&cm); + ctf_dprintf("deduping types result: %d\n", ret); + if (ret == 0) + ret = ctf_update(cm.cm_out); + if (ret != 0) + goto err; + + ctf_dprintf("Successfully deduped types\n"); + ctf_phase_dump(cm.cm_out, "dedup-pre-syms"); + + + /* + * Now we need to fix up the object and function maps. + */ + for (cmo = list_head(&cmi->cmi_omap); cmo != NULL; + cmo = list_next(&cmi->cmi_omap, cmo)) { + if (cmo->cmo_tid == 0) + continue; + ctf_dprintf("mapped %s %d->%d\n", cmo->cmo_name, + cmo->cmo_tid, cm.cm_tmap[cmo->cmo_tid].cmt_map); + cmo->cmo_tid = cm.cm_tmap[cmo->cmo_tid].cmt_map; + } + + for (cmf = list_head(&cmi->cmi_fmap); cmf != NULL; + cmf = list_next(&cmi->cmi_fmap, cmf)) { + int i; + + VERIFY(cm.cm_tmap[cmf->cmf_rtid].cmt_map != 0); + cmf->cmf_rtid = cm.cm_tmap[cmf->cmf_rtid].cmt_map; + for (i = 0; i < cmf->cmf_argc; i++) { + VERIFY(cm.cm_tmap[cmf->cmf_args[i]].cmt_map != 0); + cmf->cmf_args[i] = cm.cm_tmap[cmf->cmf_args[i]].cmt_map; + } + } + + if (cmp->cmh_msyms == B_TRUE) { + ret = ctf_merge_symbols(cmp, cm.cm_out); + if (ret != 0) { + ret = ctf_errno(cm.cm_out); + ctf_dprintf("failed to dedup symbols: %s\n", + ctf_errmsg(ret)); + goto err; + } + + ret = ctf_merge_functions(cmp, cm.cm_out); + if (ret != 0) { + ret = ctf_errno(cm.cm_out); + ctf_dprintf("failed to dedup functions: %s\n", + ctf_errmsg(ret)); + goto err; + } + } + + ret = ctf_update(cm.cm_out); + if (ret == 0) { + cmc->cmi_input = NULL; + *outp = cm.cm_out; + } +err: + ctf_merge_types_fini(&cm); + ctf_diff_fini(cdp); + return (ret); +} + +int +ctf_merge_set_nthreads(ctf_merge_t *cmp, const uint_t nthrs) +{ + if (nthrs == 0) + return (EINVAL); + cmp->cmh_nthreads = nthrs; return (0); } diff --git a/usr/src/lib/libctf/common/ctf_subr.c b/usr/src/lib/libctf/common/ctf_subr.c index 467b6a8181..26f7e8c4db 100644 --- a/usr/src/lib/libctf/common/ctf_subr.c +++ b/usr/src/lib/libctf/common/ctf_subr.c @@ -24,8 +24,6 @@ * Use is subject to license terms. */ -#pragma ident "%Z%%M% %I% %E% SMI" - #include <ctf_impl.h> #include <libctf.h> #include <sys/mman.h> @@ -56,6 +54,18 @@ ctf_alloc(size_t size) return (malloc(size)); } +void * +mergeq_alloc(size_t size) +{ + return (malloc(size)); +} + +void * +workq_alloc(size_t size) +{ + return (malloc(size)); +} + /*ARGSUSED*/ void ctf_free(void *buf, size_t size) @@ -63,6 +73,20 @@ ctf_free(void *buf, size_t size) free(buf); } +/*ARGSUSED*/ +void +mergeq_free(void *buf, size_t size) +{ + free(buf); +} + +/*ARGSUSED*/ +void +workq_free(void *buf, size_t size) +{ + free(buf); +} + const char * ctf_strerror(int err) { diff --git a/usr/src/lib/libctf/common/libctf.h b/usr/src/lib/libctf/common/libctf.h index 13c7e07e99..0951ae0606 100644 --- a/usr/src/lib/libctf/common/libctf.h +++ b/usr/src/lib/libctf/common/libctf.h @@ -55,8 +55,7 @@ extern "C" { extern int _libctf_debug; typedef enum ctf_diff_flag { - CTF_DIFF_F_IGNORE_INTNAMES = 0x01, - CTF_DIFF_F_MASK = 0x01 + CTF_DIFF_F_IGNORE_INTNAMES = 0x01 } ctf_diff_flag_t; typedef struct ctf_diff ctf_diff_t; @@ -75,12 +74,18 @@ extern int ctf_diff_functions(ctf_diff_t *, ctf_diff_func_f, void *); extern int ctf_diff_objects(ctf_diff_t *, ctf_diff_obj_f, void *); extern void ctf_diff_fini(ctf_diff_t *); +#define CTF_CONVERT_F_IGNNONC 0x01 +extern ctf_file_t *ctf_fdconvert(int, const char *, uint_t, uint_t, int *, + char *, size_t); + typedef struct ctf_merge_handle ctf_merge_t; extern ctf_merge_t *ctf_merge_init(int, int *); extern int ctf_merge_add(ctf_merge_t *, ctf_file_t *); +extern int ctf_merge_set_nthreads(ctf_merge_t *, const uint_t); extern int ctf_merge_label(ctf_merge_t *, const char *); extern int ctf_merge_uniquify(ctf_merge_t *, ctf_file_t *, const char *); extern int ctf_merge_merge(ctf_merge_t *, ctf_file_t **); +extern int ctf_merge_dedup(ctf_merge_t *, ctf_file_t **); extern void ctf_merge_fini(ctf_merge_t *); #define CTF_ELFWRITE_F_COMPRESS 0x1 diff --git a/usr/src/lib/libctf/common/libctf_impl.h b/usr/src/lib/libctf/common/libctf_impl.h new file mode 100644 index 0000000000..11193e97d0 --- /dev/null +++ b/usr/src/lib/libctf/common/libctf_impl.h @@ -0,0 +1,59 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2015 Joyent, Inc. + */ + +#ifndef _LIBCTF_IMPL_H +#define _LIBCTF_IMPL_H + +/* + * Portions of libctf implementations that are only suitable for CTF's userland + * library, eg. converting and merging related routines. + */ + +#include <libelf.h> +#include <libctf.h> +#include <ctf_impl.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum ctf_conv_status { + CTF_CONV_SUCCESS = 0, + CTF_CONV_ERROR = 1, + CTF_CONV_NOTSUP = 2 +} ctf_conv_status_t; + +typedef ctf_conv_status_t (*ctf_convert_f)(int, Elf *, uint_t, int *, + ctf_file_t **, char *, size_t); +extern ctf_conv_status_t ctf_dwarf_convert(int, Elf *, uint_t, int *, + ctf_file_t **, char *, size_t); + +/* + * zlib compression routines + */ +extern int ctf_compress(ctf_file_t *fp, void **, size_t *, size_t *); + +extern int ctf_diff_self(ctf_diff_t *, ctf_diff_type_f, void *); + +/* + * Internal debugging aids + */ +extern void ctf_phase_dump(ctf_file_t *, const char *); + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBCTF_IMPL_H */ diff --git a/usr/src/lib/libctf/common/mapfile-vers b/usr/src/lib/libctf/common/mapfile-vers index 95ddfd5d6c..3c00c47a58 100644 --- a/usr/src/lib/libctf/common/mapfile-vers +++ b/usr/src/lib/libctf/common/mapfile-vers @@ -81,25 +81,31 @@ SYMBOL_VERSION SUNWprivate_1.2 { ctf_elffdwrite; ctf_elfwrite; ctf_enum_value; + ctf_fdconvert; ctf_flags; ctf_func_args_by_id; ctf_func_info_by_id; ctf_function_iter; + ctf_kind_name; ctf_label_info; ctf_label_iter; ctf_label_topmost; ctf_member_info; ctf_merge_add; + ctf_merge_dedup; ctf_merge_fini; ctf_merge_init; ctf_merge_label; ctf_merge_merge; + ctf_merge_set_nthreads; ctf_merge_uniquify; ctf_object_iter; ctf_parent_file; ctf_parent_label; ctf_parent_name; ctf_set_array; + ctf_set_root; + ctf_set_size; ctf_string_iter; ctf_symbol_name; ctf_type_align; |