static ctf_id_t ctf_add_generic(ctf_file_t *fp, uint_t flag, const char *name, ctf_dtdef_t **rp) { ctf_dtdef_t *dtd; ctf_id_t type; char *s = NULL; if (flag != CTF_ADD_NONROOT && flag != CTF_ADD_ROOT) return (ctf_set_errno(fp, EINVAL)); if (!(fp->ctf_flags & LCTF_RDWR)) return (ctf_set_errno(fp, ECTF_RDONLY)); if (CTF_INDEX_TO_TYPE(fp->ctf_dtnextid, 1) > CTF_MAX_TYPE) return (ctf_set_errno(fp, ECTF_FULL)); if ((dtd = ctf_alloc(sizeof (ctf_dtdef_t))) == NULL) return (ctf_set_errno(fp, EAGAIN)); if (name != NULL && (s = ctf_strdup(name)) == NULL) { ctf_free(dtd, sizeof (ctf_dtdef_t)); return (ctf_set_errno(fp, EAGAIN)); } type = fp->ctf_dtnextid++; type = CTF_INDEX_TO_TYPE(type, (fp->ctf_flags & LCTF_CHILD)); bzero(dtd, sizeof (ctf_dtdef_t)); dtd->dtd_name = s; dtd->dtd_type = type; if (s != NULL) fp->ctf_dtstrlen += strlen(s) + 1; ctf_dtd_insert(fp, dtd); fp->ctf_flags |= LCTF_DIRTY; *rp = dtd; return (type); }
/* * Find a pointer to type by looking in fp->ctf_ptrtab. If we can't find a * pointer to the given type, see if we can compute a pointer to the type * resulting from resolving the type down to its base type and use that * instead. This helps with cases where the CTF data includes "struct foo *" * but not "foo_t *" and the user accesses "foo_t *" in the debugger. */ ctf_id_t ctf_type_pointer(ctf_file_t *fp, ctf_id_t type) { ctf_file_t *ofp = fp; ctf_id_t ntype; if (ctf_lookup_by_id(&fp, type) == NULL) return (CTF_ERR); /* errno is set for us */ if ((ntype = fp->ctf_ptrtab[CTF_TYPE_TO_INDEX(type)]) != 0) return (CTF_INDEX_TO_TYPE(ntype, (fp->ctf_flags & LCTF_CHILD))); if ((type = ctf_type_resolve(fp, type)) == CTF_ERR) return (ctf_set_errno(ofp, ECTF_NOTYPE)); if (ctf_lookup_by_id(&fp, type) == NULL) return (ctf_set_errno(ofp, ECTF_NOTYPE)); if ((ntype = fp->ctf_ptrtab[CTF_TYPE_TO_INDEX(type)]) != 0) return (CTF_INDEX_TO_TYPE(ntype, (fp->ctf_flags & LCTF_CHILD))); return (ctf_set_errno(ofp, ECTF_NOTYPE)); }
/* * Iterate over every root (user-visible) type in the given CTF container. * We pass the type ID of each type to the specified callback function. */ int ctf_type_iter(ctf_file_t *fp, ctf_type_f *func, void *arg) { ctf_id_t id, max = fp->ctf_typemax; int rc, child = (fp->ctf_flags & LCTF_CHILD); for (id = 1; id <= max; id++) { const ctf_type_t *tp = LCTF_INDEX_TO_TYPEPTR(fp, id); if (CTF_INFO_ISROOT(tp->ctt_info) && (rc = func(CTF_INDEX_TO_TYPE(id, child), arg)) != 0) return (rc); } return (0); }
/* * Initialize the type ID translation table with the byte offset of each type, * and initialize the hash tables of each named type. */ static int init_types(ctf_file_t *fp, const ctf_header_t *cth) { /* LINTED - pointer alignment */ const ctf_type_t *tbuf = (ctf_type_t *)(fp->ctf_buf + cth->cth_typeoff); /* LINTED - pointer alignment */ const ctf_type_t *tend = (ctf_type_t *)(fp->ctf_buf + cth->cth_stroff); ulong_t pop[CTF_K_MAX + 1] = { 0 }; const ctf_type_t *tp; ctf_hash_t *hp; ushort_t dst; ctf_id_t id; uint_t *xp; /* * We initially determine whether the container is a child or a parent * based on the value of cth_parname. To support containers that pre- * date cth_parname, we also scan the types themselves for references * to values in the range reserved for child types in our first pass. */ int child = cth->cth_parname != 0; int nlstructs = 0, nlunions = 0; int err; /* * We make two passes through the entire type section. In this first * pass, we count the number of each type and the total number of types. */ for (tp = tbuf; tp < tend; fp->ctf_typemax++) { ushort_t kind = LCTF_INFO_KIND(fp, tp->ctt_info); ulong_t vlen = LCTF_INFO_VLEN(fp, tp->ctt_info); ssize_t size, increment; size_t vbytes; uint_t n; (void) ctf_get_ctt_size(fp, tp, &size, &increment); switch (kind) { case CTF_K_INTEGER: case CTF_K_FLOAT: vbytes = sizeof (uint_t); break; case CTF_K_ARRAY: vbytes = sizeof (ctf_array_t); break; case CTF_K_FUNCTION: vbytes = sizeof (ushort_t) * (vlen + (vlen & 1)); break; case CTF_K_STRUCT: case CTF_K_UNION: if (fp->ctf_version == CTF_VERSION_1 || size < CTF_LSTRUCT_THRESH) { ctf_member_t *mp = (ctf_member_t *) ((uintptr_t)tp + increment); vbytes = sizeof (ctf_member_t) * vlen; for (n = vlen; n != 0; n--, mp++) child |= CTF_TYPE_ISCHILD(mp->ctm_type); } else { ctf_lmember_t *lmp = (ctf_lmember_t *) ((uintptr_t)tp + increment); vbytes = sizeof (ctf_lmember_t) * vlen; for (n = vlen; n != 0; n--, lmp++) child |= CTF_TYPE_ISCHILD(lmp->ctlm_type); } break; case CTF_K_ENUM: vbytes = sizeof (ctf_enum_t) * vlen; break; case CTF_K_FORWARD: /* * For forward declarations, ctt_type is the CTF_K_* * kind for the tag, so bump that population count too. * If ctt_type is unknown, treat the tag as a struct. */ if (tp->ctt_type == CTF_K_UNKNOWN || tp->ctt_type >= CTF_K_MAX) pop[CTF_K_STRUCT]++; else pop[tp->ctt_type]++; /*FALLTHRU*/ case CTF_K_UNKNOWN: vbytes = 0; break; case CTF_K_POINTER: case CTF_K_TYPEDEF: case CTF_K_VOLATILE: case CTF_K_CONST: case CTF_K_RESTRICT: child |= CTF_TYPE_ISCHILD(tp->ctt_type); vbytes = 0; break; default: ctf_dprintf("detected invalid CTF kind -- %u\n", kind); return (ECTF_CORRUPT); } tp = (ctf_type_t *)((uintptr_t)tp + increment + vbytes); pop[kind]++; } /* * If we detected a reference to a child type ID, then we know this * container is a child and may have a parent's types imported later. */ if (child) { ctf_dprintf("CTF container %p is a child\n", (void *)fp); fp->ctf_flags |= LCTF_CHILD; } else ctf_dprintf("CTF container %p is a parent\n", (void *)fp); /* * Now that we've counted up the number of each type, we can allocate * the hash tables, type translation table, and pointer table. */ if ((err = ctf_hash_create(&fp->ctf_structs, pop[CTF_K_STRUCT])) != 0) return (err); if ((err = ctf_hash_create(&fp->ctf_unions, pop[CTF_K_UNION])) != 0) return (err); if ((err = ctf_hash_create(&fp->ctf_enums, pop[CTF_K_ENUM])) != 0) return (err); if ((err = ctf_hash_create(&fp->ctf_names, pop[CTF_K_INTEGER] + pop[CTF_K_FLOAT] + pop[CTF_K_FUNCTION] + pop[CTF_K_TYPEDEF] + pop[CTF_K_POINTER] + pop[CTF_K_VOLATILE] + pop[CTF_K_CONST] + pop[CTF_K_RESTRICT])) != 0) return (err); fp->ctf_txlate = ctf_alloc(sizeof (uint_t) * (fp->ctf_typemax + 1)); fp->ctf_ptrtab = ctf_alloc(sizeof (ushort_t) * (fp->ctf_typemax + 1)); if (fp->ctf_txlate == NULL || fp->ctf_ptrtab == NULL) return (EAGAIN); /* memory allocation failed */ xp = fp->ctf_txlate; *xp++ = 0; /* type id 0 is used as a sentinel value */ bzero(fp->ctf_txlate, sizeof (uint_t) * (fp->ctf_typemax + 1)); bzero(fp->ctf_ptrtab, sizeof (ushort_t) * (fp->ctf_typemax + 1)); /* * In the second pass through the types, we fill in each entry of the * type and pointer tables and add names to the appropriate hashes. */ for (id = 1, tp = tbuf; tp < tend; xp++, id++) { ushort_t kind = LCTF_INFO_KIND(fp, tp->ctt_info); ulong_t vlen = LCTF_INFO_VLEN(fp, tp->ctt_info); ssize_t size, increment; const char *name; size_t vbytes; ctf_helem_t *hep; ctf_encoding_t cte; (void) ctf_get_ctt_size(fp, tp, &size, &increment); name = ctf_strptr(fp, tp->ctt_name); switch (kind) { case CTF_K_INTEGER: case CTF_K_FLOAT: /* * Only insert a new integer base type definition if * this type name has not been defined yet. We re-use * the names with different encodings for bit-fields. */ if ((hep = ctf_hash_lookup(&fp->ctf_names, fp, name, strlen(name))) == NULL) { err = ctf_hash_insert(&fp->ctf_names, fp, CTF_INDEX_TO_TYPE(id, child), tp->ctt_name); if (err != 0 && err != ECTF_STRTAB) return (err); } else if (ctf_type_encoding(fp, hep->h_type, &cte) == 0 && cte.cte_bits == 0) { /* * Work-around SOS8 stabs bug: replace existing * intrinsic w/ same name if it was zero bits. */ hep->h_type = CTF_INDEX_TO_TYPE(id, child); } vbytes = sizeof (uint_t); break; case CTF_K_ARRAY: vbytes = sizeof (ctf_array_t); break; case CTF_K_FUNCTION: err = ctf_hash_insert(&fp->ctf_names, fp, CTF_INDEX_TO_TYPE(id, child), tp->ctt_name); if (err != 0 && err != ECTF_STRTAB) return (err); vbytes = sizeof (ushort_t) * (vlen + (vlen & 1)); break; case CTF_K_STRUCT: err = ctf_hash_define(&fp->ctf_structs, fp, CTF_INDEX_TO_TYPE(id, child), tp->ctt_name); if (err != 0 && err != ECTF_STRTAB) return (err); if (fp->ctf_version == CTF_VERSION_1 || size < CTF_LSTRUCT_THRESH) vbytes = sizeof (ctf_member_t) * vlen; else { vbytes = sizeof (ctf_lmember_t) * vlen; nlstructs++; } break; case CTF_K_UNION: err = ctf_hash_define(&fp->ctf_unions, fp, CTF_INDEX_TO_TYPE(id, child), tp->ctt_name); if (err != 0 && err != ECTF_STRTAB) return (err); if (fp->ctf_version == CTF_VERSION_1 || size < CTF_LSTRUCT_THRESH) vbytes = sizeof (ctf_member_t) * vlen; else { vbytes = sizeof (ctf_lmember_t) * vlen; nlunions++; } break; case CTF_K_ENUM: err = ctf_hash_define(&fp->ctf_enums, fp, CTF_INDEX_TO_TYPE(id, child), tp->ctt_name); if (err != 0 && err != ECTF_STRTAB) return (err); vbytes = sizeof (ctf_enum_t) * vlen; break; case CTF_K_TYPEDEF: err = ctf_hash_insert(&fp->ctf_names, fp, CTF_INDEX_TO_TYPE(id, child), tp->ctt_name); if (err != 0 && err != ECTF_STRTAB) return (err); vbytes = 0; break; case CTF_K_FORWARD: /* * Only insert forward tags into the given hash if the * type or tag name is not already present. */ switch (tp->ctt_type) { case CTF_K_STRUCT: hp = &fp->ctf_structs; break; case CTF_K_UNION: hp = &fp->ctf_unions; break; case CTF_K_ENUM: hp = &fp->ctf_enums; break; default: hp = &fp->ctf_structs; } if (ctf_hash_lookup(hp, fp, name, strlen(name)) == NULL) { err = ctf_hash_insert(hp, fp, CTF_INDEX_TO_TYPE(id, child), tp->ctt_name); if (err != 0 && err != ECTF_STRTAB) return (err); } vbytes = 0; break; case CTF_K_POINTER: /* * If the type referenced by the pointer is in this CTF * container, then store the index of the pointer type * in fp->ctf_ptrtab[ index of referenced type ]. */ if (CTF_TYPE_ISCHILD(tp->ctt_type) == child && CTF_TYPE_TO_INDEX(tp->ctt_type) <= fp->ctf_typemax) fp->ctf_ptrtab[ CTF_TYPE_TO_INDEX(tp->ctt_type)] = id; /*FALLTHRU*/ case CTF_K_VOLATILE: case CTF_K_CONST: case CTF_K_RESTRICT: err = ctf_hash_insert(&fp->ctf_names, fp, CTF_INDEX_TO_TYPE(id, child), tp->ctt_name); if (err != 0 && err != ECTF_STRTAB) return (err); /*FALLTHRU*/ default: vbytes = 0; break; } *xp = (uint_t)((uintptr_t)tp - (uintptr_t)fp->ctf_buf); tp = (ctf_type_t *)((uintptr_t)tp + increment + vbytes); } ctf_dprintf("%lu total types processed\n", fp->ctf_typemax); ctf_dprintf("%u enum names hashed\n", ctf_hash_size(&fp->ctf_enums)); ctf_dprintf("%u struct names hashed (%d long)\n", ctf_hash_size(&fp->ctf_structs), nlstructs); ctf_dprintf("%u union names hashed (%d long)\n", ctf_hash_size(&fp->ctf_unions), nlunions); ctf_dprintf("%u base type names hashed\n", ctf_hash_size(&fp->ctf_names)); /* * Make an additional pass through the pointer table to find pointers * that point to anonymous typedef nodes. If we find one, modify the * pointer table so that the pointer is also known to point to the * node that is referenced by the anonymous typedef node. */ for (id = 1; id <= fp->ctf_typemax; id++) { if ((dst = fp->ctf_ptrtab[id]) != 0) { tp = LCTF_INDEX_TO_TYPEPTR(fp, id); if (LCTF_INFO_KIND(fp, tp->ctt_info) == CTF_K_TYPEDEF && strcmp(ctf_strptr(fp, tp->ctt_name), "") == 0 && CTF_TYPE_ISCHILD(tp->ctt_type) == child && CTF_TYPE_TO_INDEX(tp->ctt_type) <= fp->ctf_typemax) fp->ctf_ptrtab[ CTF_TYPE_TO_INDEX(tp->ctt_type)] = dst; } } return (0); }
/* * Attempt to convert the given C type name into the corresponding CTF type ID. * It is not possible to do complete and proper conversion of type names * without implementing a more full-fledged parser, which is necessary to * handle things like types that are function pointers to functions that * have arguments that are function pointers, and fun stuff like that. * Instead, this function implements a very simple conversion algorithm that * finds the things that we actually care about: structs, unions, enums, * integers, floats, typedefs, and pointers to any of these named types. */ ctf_id_t ctf_lookup_by_name(ctf_file_t *fp, const char *name) { static const char delimiters[] = " \t\n\r\v\f*"; const ctf_lookup_t *lp; const ctf_helem_t *hp; const char *p, *q, *end; ctf_id_t type = 0; ctf_id_t ntype, ptype; if (name == NULL) return (ctf_set_errno(fp, EINVAL)); for (p = name, end = name + strlen(name); *p != '\0'; p = q) { while (isspace((unsigned char)*p)) p++; /* skip leading ws */ if (p == end) break; if ((q = strpbrk(p + 1, delimiters)) == NULL) q = end; /* compare until end */ if (*p == '*') { /* * Find a pointer to type by looking in fp->ctf_ptrtab. * If we can't find a pointer to the given type, see if * we can compute a pointer to the type resulting from * resolving the type down to its base type and use * that instead. This helps with cases where the CTF * data includes "struct foo *" but not "foo_t *" and * the user tries to access "foo_t *" in the debugger. */ ntype = fp->ctf_ptrtab[CTF_TYPE_TO_INDEX(type)]; if (ntype == 0) { ntype = ctf_type_resolve(fp, type); if (ntype == CTF_ERR || (ntype = fp->ctf_ptrtab[ CTF_TYPE_TO_INDEX(ntype)]) == 0) { (void) ctf_set_errno(fp, ECTF_NOTYPE); goto err; } } type = CTF_INDEX_TO_TYPE(ntype, (fp->ctf_flags & LCTF_CHILD)); q = p + 1; continue; } if (isqualifier(p, (size_t)(q - p))) continue; /* skip qualifier keyword */ for (lp = fp->ctf_lookups; lp->ctl_prefix != NULL; lp++) { if (lp->ctl_prefix[0] == '\0' || strncmp(p, lp->ctl_prefix, (size_t)(q - p)) == 0) { for (p += lp->ctl_len; isspace((unsigned char)*p); p++) continue; /* skip prefix and next ws */ if ((q = strchr(p, '*')) == NULL) q = end; /* compare until end */ while (isspace((unsigned char)q[-1])) q--; /* exclude trailing ws */ if ((hp = ctf_hash_lookup(lp->ctl_hash, fp, p, (size_t)(q - p))) == NULL) { (void) ctf_set_errno(fp, ECTF_NOTYPE); goto err; } type = hp->h_type; break; } } if (lp->ctl_prefix == NULL) { (void) ctf_set_errno(fp, ECTF_NOTYPE); goto err; } } if (*p != '\0' || type == 0) return (ctf_set_errno(fp, ECTF_SYNTAX)); return (type); err: if (fp->ctf_parent != NULL && (ptype = ctf_lookup_by_name(fp->ctf_parent, name)) != CTF_ERR) return (ptype); return (CTF_ERR); }
int main(int argc, char **argv) { tdata_t *mstrtd, *savetd; char *uniqfile = NULL, *uniqlabel = NULL; char *withfile = NULL; char *label = NULL; char **ifiles, **tifiles; int verbose = 0, docopy = 0; int write_fuzzy_match = 0; int require_ctf = 0; int nifiles, nielems; int c, i, idx, tidx, err; progname = basename(argv[0]); if (getenv("CTFMERGE_DEBUG_LEVEL")) debug_level = atoi(getenv("CTFMERGE_DEBUG_LEVEL")); err = 0; while ((c = getopt(argc, argv, ":cd:D:fl:L:o:tvw:s")) != EOF) { switch (c) { case 'c': docopy = 1; break; case 'd': /* Uniquify against `uniqfile' */ uniqfile = optarg; break; case 'D': /* Uniquify against label `uniqlabel' in `uniqfile' */ uniqlabel = optarg; break; case 'f': write_fuzzy_match = CTF_FUZZY_MATCH; break; case 'l': /* Label merged types with `label' */ label = optarg; break; case 'L': /* Label merged types with getenv(`label`) */ if ((label = getenv(optarg)) == NULL) label = CTF_DEFAULT_LABEL; break; case 'o': /* Place merged types in CTF section in `outfile' */ outfile = optarg; break; case 't': /* Insist *all* object files built from C have CTF */ require_ctf = 1; break; case 'v': /* More debugging information */ verbose = 1; break; case 'w': /* Additive merge with data from `withfile' */ withfile = optarg; break; case 's': /* use the dynsym rather than the symtab */ dynsym = CTF_USE_DYNSYM; break; default: usage(); exit(2); } } /* Validate arguments */ if (docopy) { if (uniqfile != NULL || uniqlabel != NULL || label != NULL || outfile != NULL || withfile != NULL || dynsym != 0) err++; if (argc - optind != 2) err++; } else { if (uniqfile != NULL && withfile != NULL) err++; if (uniqlabel != NULL && uniqfile == NULL) err++; if (outfile == NULL || label == NULL) err++; if (argc - optind == 0) err++; } if (err) { usage(); exit(2); } if (uniqfile && access(uniqfile, R_OK) != 0) { warning("Uniquification file %s couldn't be opened and " "will be ignored.\n", uniqfile); uniqfile = NULL; } if (withfile && access(withfile, R_OK) != 0) { warning("With file %s couldn't be opened and will be " "ignored.\n", withfile); withfile = NULL; } if (outfile && access(outfile, R_OK|W_OK) != 0) terminate("Cannot open output file %s for r/w", outfile); /* * This is ugly, but we don't want to have to have a separate tool * (yet) just for copying an ELF section with our specific requirements, * so we shoe-horn a copier into ctfmerge. */ if (docopy) { copy_ctf_data(argv[optind], argv[optind + 1]); exit(0); } set_terminate_cleanup(terminate_cleanup); /* Sort the input files and strip out duplicates */ nifiles = argc - optind; ifiles = xmalloc(sizeof (char *) * nifiles); tifiles = xmalloc(sizeof (char *) * nifiles); for (i = 0; i < nifiles; i++) tifiles[i] = argv[optind + i]; qsort(tifiles, nifiles, sizeof (char *), (int (*)())strcompare); ifiles[0] = tifiles[0]; for (idx = 0, tidx = 1; tidx < nifiles; tidx++) { if (strcmp(ifiles[idx], tifiles[tidx]) != 0) ifiles[++idx] = tifiles[tidx]; } nifiles = idx + 1; /* Make sure they all exist */ if ((nielems = count_files(ifiles, nifiles)) < 0) terminate("Some input files were inaccessible\n"); /* Prepare for the merge */ wq_init(&wq, nielems); start_threads(&wq); /* * Start the merge * * We're reading everything from each of the object files, so we * don't need to specify labels. */ if (read_ctf(ifiles, nifiles, NULL, merge_ctf_cb, &wq, require_ctf) == 0) { /* * If we're verifying that C files have CTF, it's safe to * assume that in this case, we're building only from assembly * inputs. */ if (require_ctf) exit(0); terminate("No ctf sections found to merge\n"); } pthread_mutex_lock(&wq.wq_queue_lock); wq.wq_nomorefiles = 1; pthread_cond_broadcast(&wq.wq_work_avail); pthread_mutex_unlock(&wq.wq_queue_lock); pthread_mutex_lock(&wq.wq_queue_lock); while (wq.wq_alldone == 0) pthread_cond_wait(&wq.wq_alldone_cv, &wq.wq_queue_lock); pthread_mutex_unlock(&wq.wq_queue_lock); join_threads(&wq); /* * All requested files have been merged, with the resulting tree in * mstrtd. savetd is the tree that will be placed into the output file. * * Regardless of whether we're doing a normal uniquification or an * additive merge, we need a type tree that has been uniquified * against uniqfile or withfile, as appropriate. * * If we're doing a uniquification, we stuff the resulting tree into * outfile. Otherwise, we add the tree to the tree already in withfile. */ assert(fifo_len(wq.wq_queue) == 1); mstrtd = fifo_remove(wq.wq_queue); if (verbose || debug_level) { debug(2, "Statistics for td %p\n", (void *)mstrtd); iidesc_stats(mstrtd->td_iihash); } if (uniqfile != NULL || withfile != NULL) { char *reffile, *reflabel = NULL; tdata_t *reftd; if (uniqfile != NULL) { reffile = uniqfile; reflabel = uniqlabel; } else reffile = withfile; if (read_ctf(&reffile, 1, reflabel, read_ctf_save_cb, &reftd, require_ctf) == 0) { terminate("No CTF data found in reference file %s\n", reffile); } savetd = tdata_new(); if (CTF_TYPE_ISCHILD(reftd->td_nextid)) terminate("No room for additional types in master\n"); savetd->td_nextid = withfile ? reftd->td_nextid : CTF_INDEX_TO_TYPE(1, TRUE); merge_into_master(mstrtd, reftd, savetd, 0); tdata_label_add(savetd, label, CTF_LABEL_LASTIDX); if (withfile) { /* * savetd holds the new data to be added to the withfile */ tdata_t *withtd = reftd; tdata_merge(withtd, savetd); savetd = withtd; } else { char uniqname[MAXPATHLEN]; labelent_t *parle; parle = tdata_label_top(reftd); savetd->td_parlabel = xstrdup(parle->le_name); strncpy(uniqname, reffile, sizeof (uniqname)); uniqname[MAXPATHLEN - 1] = '\0'; savetd->td_parname = xstrdup(basename(uniqname)); } } else { /* * No post processing. Write the merged tree as-is into the * output file. */ tdata_label_free(mstrtd); tdata_label_add(mstrtd, label, CTF_LABEL_LASTIDX); savetd = mstrtd; } tmpname = mktmpname(outfile, ".ctf"); write_ctf(savetd, outfile, tmpname, CTF_COMPRESS | write_fuzzy_match | dynsym); if (rename(tmpname, outfile) != 0) terminate("Couldn't rename output temp file %s", tmpname); free(tmpname); return (0); }