#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 <mergeq.h>
#include <errno.h>
typedef struct ctf_merge_tinfo {
uint16_t cmt_map;
boolean_t cmt_fixup;
boolean_t cmt_forward;
boolean_t cmt_missing;
} ctf_merge_tinfo_t;
typedef struct ctf_merge_types {
ctf_file_t *cm_out;
ctf_file_t *cm_src;
ctf_merge_tinfo_t *cm_tmap;
boolean_t cm_dedup;
boolean_t cm_unique;
} ctf_merge_types_t;
typedef struct ctf_merge_objmap {
list_node_t cmo_node;
const char *cmo_name;
const char *cmo_file;
ulong_t cmo_idx;
Elf64_Sym cmo_sym;
ctf_id_t cmo_tid;
} ctf_merge_objmap_t;
typedef struct ctf_merge_funcmap {
list_node_t cmf_node;
const char *cmf_name;
const char *cmf_file;
ulong_t cmf_idx;
Elf64_Sym cmf_sym;
ctf_id_t cmf_rtid;
uint_t cmf_flags;
uint_t cmf_argc;
ctf_id_t cmf_args[];
} ctf_merge_funcmap_t;
typedef struct ctf_merge_input {
list_node_t cmi_node;
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 {
list_t cmh_inputs;
uint_t cmh_ninputs;
uint_t cmh_nthreads;
ctf_file_t *cmh_unique;
boolean_t cmh_msyms;
int cmh_ofd;
int cmh_flags;
char *cmh_label;
char *cmh_pname;
};
typedef struct ctf_merge_symbol_arg {
list_t *cmsa_objmap;
list_t *cmsa_funcmap;
ctf_file_t *cmsa_out;
boolean_t cmsa_dedup;
} ctf_merge_symbol_arg_t;
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)
{
ctf_merge_types_t *cmp = arg;
ctf_merge_tinfo_t *cmt = cmp->cm_tmap;
uint_t kind;
if (same == B_TRUE) {
if (ctf_type_kind(ifp, iid) == CTF_K_FORWARD &&
(kind = ctf_type_kind(ofp, oid)) != CTF_K_FORWARD) {
VERIFY(cmt[oid].cmt_map == 0);
if (cmp->cm_unique == B_TRUE) {
cmt[oid].cmt_missing = B_TRUE;
return;
}
cmt[oid].cmt_map = iid;
cmt[oid].cmt_forward = B_TRUE;
ctf_dprintf("merge diff forward mapped %ld->%ld (%u)\n",
oid, iid, kind);
return;
}
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);
}
}
static int
ctf_merge_add_number(ctf_merge_types_t *cmp, ctf_id_t id)
{
int ret, flags;
const ctf_type_t *tp;
const char *name;
ctf_encoding_t en;
if (ctf_type_encoding(cmp->cm_src, id, &en) != 0)
return (CTF_ERR);
tp = LCTF_INDEX_TO_TYPEPTR(cmp->cm_src, id);
name = ctf_strraw(cmp->cm_src, tp->ctt_name);
if (CTF_INFO_ISROOT(tp->ctt_info) != 0)
flags = CTF_ADD_ROOT;
else
flags = CTF_ADD_NONROOT;
ret = ctf_add_encoded(cmp->cm_out, flags, name, &en,
ctf_type_kind(cmp->cm_src, id));
if (ret == CTF_ERR)
return (ret);
VERIFY(cmp->cm_tmap[id].cmt_map == 0);
cmp->cm_tmap[id].cmt_map = ret;
return (0);
}
static int
ctf_merge_add_array(ctf_merge_types_t *cmp, ctf_id_t id)
{
int ret, flags;
const ctf_type_t *tp;
ctf_arinfo_t ar;
if (ctf_array_info(cmp->cm_src, id, &ar) == CTF_ERR)
return (CTF_ERR);
tp = LCTF_INDEX_TO_TYPEPTR(cmp->cm_src, id);
if (CTF_INFO_ISROOT(tp->ctt_info) != 0)
flags = CTF_ADD_ROOT;
else
flags = CTF_ADD_NONROOT;
if (cmp->cm_tmap[ar.ctr_contents].cmt_map == 0) {
ret = ctf_merge_add_type(cmp, ar.ctr_contents);
if (ret != 0)
return (ret);
ASSERT(cmp->cm_tmap[ar.ctr_contents].cmt_map != 0);
}
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);
if (ret != 0)
return (ret);
ASSERT(cmp->cm_tmap[ar.ctr_index].cmt_map != 0);
}
ar.ctr_index = ctf_merge_gettype(cmp, ar.ctr_index);
ret = ctf_add_array(cmp->cm_out, flags, &ar);
if (ret == CTF_ERR)
return (ret);
VERIFY(cmp->cm_tmap[id].cmt_map == 0);
cmp->cm_tmap[id].cmt_map = ret;
return (0);
}
static int
ctf_merge_add_reftype(ctf_merge_types_t *cmp, ctf_id_t id)
{
int ret, flags;
const ctf_type_t *tp;
ctf_id_t reftype;
const char *name;
tp = LCTF_INDEX_TO_TYPEPTR(cmp->cm_src, id);
name = ctf_strraw(cmp->cm_src, tp->ctt_name);
if (CTF_INFO_ISROOT(tp->ctt_info) != 0)
flags = CTF_ADD_ROOT;
else
flags = CTF_ADD_NONROOT;
reftype = ctf_type_reference(cmp->cm_src, id);
if (reftype == CTF_ERR)
return (ctf_set_errno(cmp->cm_out, ctf_errno(cmp->cm_src)));
if (cmp->cm_tmap[reftype].cmt_map == 0) {
ret = ctf_merge_add_type(cmp, reftype);
if (ret != 0)
return (ret);
ASSERT(cmp->cm_tmap[reftype].cmt_map != 0);
}
reftype = ctf_merge_gettype(cmp, reftype);
ret = ctf_add_reftype(cmp->cm_out, flags, name, reftype,
ctf_type_kind(cmp->cm_src, id));
if (ret == CTF_ERR)
return (ret);
VERIFY(cmp->cm_tmap[id].cmt_map == 0);
cmp->cm_tmap[id].cmt_map = ret;
return (0);
}
static int
ctf_merge_add_typedef(ctf_merge_types_t *cmp, ctf_id_t id)
{
int ret, flags;
const ctf_type_t *tp;
const char *name;
ctf_id_t reftype;
tp = LCTF_INDEX_TO_TYPEPTR(cmp->cm_src, id);
name = ctf_strraw(cmp->cm_src, tp->ctt_name);
if (CTF_INFO_ISROOT(tp->ctt_info) != 0)
flags = CTF_ADD_ROOT;
else
flags = CTF_ADD_NONROOT;
reftype = ctf_type_reference(cmp->cm_src, id);
if (reftype == CTF_ERR)
return (ctf_set_errno(cmp->cm_out, ctf_errno(cmp->cm_src)));
if (cmp->cm_tmap[reftype].cmt_map == 0) {
ret = ctf_merge_add_type(cmp, reftype);
if (ret != 0)
return (ret);
ASSERT(cmp->cm_tmap[reftype].cmt_map != 0);
}
reftype = ctf_merge_gettype(cmp, reftype);
ret = ctf_add_typedef(cmp->cm_out, flags, name, reftype);
if (ret == CTF_ERR)
return (ret);
VERIFY(cmp->cm_tmap[id].cmt_map == 0);
cmp->cm_tmap[id].cmt_map = ret;
return (0);
}
typedef struct ctf_merge_enum {
ctf_file_t *cme_fp;
ctf_id_t cme_id;
} ctf_merge_enum_t;
static int
ctf_merge_add_enumerator(const char *name, int value, void *arg)
{
ctf_merge_enum_t *cmep = arg;
return (ctf_add_enumerator(cmep->cme_fp, cmep->cme_id, name, value) ==
CTF_ERR);
}
static int
ctf_merge_add_enum(ctf_merge_types_t *cmp, ctf_id_t id)
{
int flags;
const ctf_type_t *tp;
const char *name;
ctf_id_t enumid;
ctf_merge_enum_t cme;
size_t size;
tp = LCTF_INDEX_TO_TYPEPTR(cmp->cm_src, id);
if (CTF_INFO_ISROOT(tp->ctt_info) != 0)
flags = CTF_ADD_ROOT;
else
flags = CTF_ADD_NONROOT;
name = ctf_strraw(cmp->cm_src, tp->ctt_name);
size = ctf_get_ctt_size(cmp->cm_src, tp, NULL, NULL);
enumid = ctf_add_enum(cmp->cm_out, flags, name, size);
if (enumid == CTF_ERR)
return (enumid);
cme.cme_fp = cmp->cm_out;
cme.cme_id = enumid;
if (ctf_enum_iter(cmp->cm_src, id, ctf_merge_add_enumerator,
&cme) != 0)
return (CTF_ERR);
VERIFY(cmp->cm_tmap[id].cmt_map == 0);
cmp->cm_tmap[id].cmt_map = enumid;
return (0);
}
static int
ctf_merge_add_func(ctf_merge_types_t *cmp, ctf_id_t id)
{
int ret, flags, i;
const ctf_type_t *tp;
ctf_funcinfo_t ctc;
ctf_id_t *argv;
tp = LCTF_INDEX_TO_TYPEPTR(cmp->cm_src, id);
if (CTF_INFO_ISROOT(tp->ctt_info) != 0)
flags = CTF_ADD_ROOT;
else
flags = CTF_ADD_NONROOT;
if (ctf_func_info_by_id(cmp->cm_src, id, &ctc) == CTF_ERR)
return (ctf_set_errno(cmp->cm_out, ctf_errno(cmp->cm_src)));
argv = ctf_alloc(sizeof (ctf_id_t) * ctc.ctc_argc);
if (argv == NULL)
return (ctf_set_errno(cmp->cm_out, ENOMEM));
if (ctf_func_args_by_id(cmp->cm_src, id, ctc.ctc_argc, argv) ==
CTF_ERR) {
ctf_free(argv, sizeof (ctf_id_t) * ctc.ctc_argc);
return (ctf_set_errno(cmp->cm_out, ctf_errno(cmp->cm_src)));
}
if (cmp->cm_tmap[ctc.ctc_return].cmt_map == 0) {
ret = ctf_merge_add_type(cmp, ctc.ctc_return);
if (ret != 0)
return (ret);
ASSERT(cmp->cm_tmap[ctc.ctc_return].cmt_map != 0);
}
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) {
ret = ctf_merge_add_type(cmp, argv[i]);
if (ret != 0)
return (ret);
ASSERT(cmp->cm_tmap[argv[i]].cmt_map != 0);
}
argv[i] = ctf_merge_gettype(cmp, argv[i]);
}
ret = ctf_add_funcptr(cmp->cm_out, flags, &ctc, argv);
ctf_free(argv, sizeof (ctf_id_t) * ctc.ctc_argc);
if (ret == CTF_ERR)
return (ret);
VERIFY(cmp->cm_tmap[id].cmt_map == 0);
cmp->cm_tmap[id].cmt_map = ret;
return (0);
}
static int
ctf_merge_add_forward(ctf_merge_types_t *cmp, ctf_id_t id, uint_t kind)
{
int ret, flags;
const ctf_type_t *tp;
const char *name;
tp = LCTF_INDEX_TO_TYPEPTR(cmp->cm_src, id);
name = ctf_strraw(cmp->cm_src, tp->ctt_name);
if (CTF_INFO_ISROOT(tp->ctt_info) != 0)
flags = CTF_ADD_ROOT;
else
flags = CTF_ADD_NONROOT;
ret = ctf_add_forward(cmp->cm_out, flags, name, kind);
if (ret == CTF_ERR)
return (CTF_ERR);
VERIFY(cmp->cm_tmap[id].cmt_map == 0);
cmp->cm_tmap[id].cmt_map = ret;
return (0);
}
typedef struct ctf_merge_su {
ctf_merge_types_t *cms_cm;
ctf_id_t cms_id;
} ctf_merge_su_t;
static int
ctf_merge_add_member(const char *name, ctf_id_t type, ulong_t offset, void *arg)
{
ctf_merge_su_t *cms = 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);
}
static int
ctf_merge_add_sou(ctf_merge_types_t *cmp, ctf_id_t id, boolean_t forward)
{
int flags, kind;
const ctf_type_t *tp;
const char *name;
ctf_id_t suid;
tp = LCTF_INDEX_TO_TYPEPTR(cmp->cm_src, id);
name = ctf_strraw(cmp->cm_src, tp->ctt_name);
if (CTF_INFO_ISROOT(tp->ctt_info) != 0)
flags = CTF_ADD_ROOT;
else
flags = CTF_ADD_NONROOT;
kind = ctf_type_kind(cmp->cm_src, id);
if (kind == CTF_K_STRUCT)
suid = ctf_add_struct(cmp->cm_out, flags, name);
else
suid = ctf_add_union(cmp->cm_out, flags, name);
ctf_dprintf("added sou \"%s\" as (%d) %d->%d\n", name, kind, id, suid);
if (suid == CTF_ERR)
return (suid);
if (forward == B_FALSE) {
VERIFY(cmp->cm_tmap[id].cmt_map == 0);
cmp->cm_tmap[id].cmt_map = suid;
} else {
if (cmp->cm_tmap[id].cmt_map != suid) {
ctf_dprintf(
"mismatch sou \"%s\" as (%d) %d->%d (exp %d)\n",
name, kind, id, suid, cmp->cm_tmap[id].cmt_map);
ctf_hash_dump("src structs",
&cmp->cm_src->ctf_structs, cmp->cm_src);
ctf_hash_dump("src unions",
&cmp->cm_src->ctf_unions, cmp->cm_src);
ctf_hash_dump("out structs",
&cmp->cm_out->ctf_structs, cmp->cm_out);
ctf_hash_dump("out unions",
&cmp->cm_out->ctf_unions, cmp->cm_out);
}
VERIFY(cmp->cm_tmap[id].cmt_map == suid);
}
cmp->cm_tmap[id].cmt_fixup = B_TRUE;
return (0);
}
static int
ctf_merge_add_type(ctf_merge_types_t *cmp, ctf_id_t id)
{
int kind, ret;
if (cmp->cm_tmap[id].cmt_map != 0)
return (0);
kind = ctf_type_kind(cmp->cm_src, id);
switch (kind) {
case CTF_K_INTEGER:
case CTF_K_FLOAT:
ret = ctf_merge_add_number(cmp, id);
break;
case CTF_K_ARRAY:
ret = ctf_merge_add_array(cmp, id);
break;
case CTF_K_POINTER:
case CTF_K_VOLATILE:
case CTF_K_CONST:
case CTF_K_RESTRICT:
ret = ctf_merge_add_reftype(cmp, id);
break;
case CTF_K_TYPEDEF:
ret = ctf_merge_add_typedef(cmp, id);
break;
case CTF_K_ENUM:
ret = ctf_merge_add_enum(cmp, id);
break;
case CTF_K_FUNCTION:
ret = ctf_merge_add_func(cmp, id);
break;
case CTF_K_FORWARD: {
const ctf_type_t *tp;
uint_t kind;
tp = LCTF_INDEX_TO_TYPEPTR(cmp->cm_src, id);
kind = tp->ctt_type;
if (kind == CTF_K_UNKNOWN || kind >= CTF_K_MAX)
kind = CTF_K_STRUCT;
ret = ctf_merge_add_forward(cmp, id, kind);
break;
}
case CTF_K_STRUCT:
case CTF_K_UNION:
ret = ctf_merge_add_sou(cmp, id, B_FALSE);
break;
case CTF_K_UNKNOWN:
return (0);
default:
abort();
}
return (ret);
}
static int
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);
}
static int
ctf_merge_fixup_type(ctf_merge_types_t *cmp, ctf_id_t id)
{
int kind, ret;
kind = ctf_type_kind(cmp->cm_src, id);
switch (kind) {
case CTF_K_STRUCT:
case CTF_K_UNION:
ret = ctf_merge_fixup_sou(cmp, id);
break;
default:
VERIFY(0);
ret = CTF_ERR;
}
return (ret);
}
static void
ctf_merge_dedup_remap(ctf_merge_types_t *cmp)
{
int i;
for (i = 1; i < cmp->cm_src->ctf_typemax + 1; i++) {
ctf_id_t tid;
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;
}
}
static int
ctf_merge_common(ctf_merge_types_t *cmp)
{
int ret, i;
ctf_phase_dump(cmp->cm_src, "merge-common-src", NULL);
ctf_phase_dump(cmp->cm_out, "merge-common-dest", NULL);
for (i = 1; i <= cmp->cm_src->ctf_typemax; i++) {
if (cmp->cm_tmap[i].cmt_forward == B_TRUE) {
ctf_dprintf("Forward %d\n", i);
ret = ctf_merge_add_sou(cmp, i, B_TRUE);
if (ret != 0) {
return (ret);
}
}
}
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) {
ctf_dprintf("Failed to merge type %d\n", i);
return (ret);
}
}
}
ret = ctf_update(cmp->cm_out);
if (ret != 0)
return (ret);
if (cmp->cm_dedup == B_TRUE) {
ctf_merge_dedup_remap(cmp);
}
ctf_dprintf("Beginning merge pass 3\n");
for (i = 1; i <= cmp->cm_src->ctf_typemax; i++) {
if (cmp->cm_tmap[i].cmt_fixup == B_TRUE) {
ret = ctf_merge_fixup_type(cmp, i);
if (ret != 0)
return (ret);
}
}
return (0);
}
static int
ctf_merge_uniquify_types(ctf_merge_types_t *cmp)
{
int i, ret;
for (i = 1; i <= cmp->cm_src->ctf_typemax; i++) {
if (cmp->cm_tmap[i].cmt_missing == B_FALSE)
continue;
ret = ctf_merge_add_type(cmp, i);
if (ret != 0)
return (ret);
}
ret = ctf_update(cmp->cm_out);
if (ret != 0)
return (ret);
for (i = 1; i <= cmp->cm_src->ctf_typemax; i++) {
if (cmp->cm_tmap[i].cmt_fixup == B_FALSE)
continue;
ret = ctf_merge_fixup_type(cmp, i);
if (ret != 0)
return (ret);
}
return (0);
}
static int
ctf_merge_types_init(ctf_merge_types_t *cmp)
{
cmp->cm_tmap = ctf_alloc(sizeof (ctf_merge_tinfo_t) *
(cmp->cm_src->ctf_typemax + 1));
if (cmp->cm_tmap == NULL)
return (ctf_set_errno(cmp->cm_out, ENOMEM));
bzero(cmp->cm_tmap, sizeof (ctf_merge_tinfo_t) *
(cmp->cm_src->ctf_typemax + 1));
return (0);
}
static void
ctf_merge_types_fini(ctf_merge_types_t *cmp)
{
ctf_free(cmp->cm_tmap, sizeof (ctf_merge_tinfo_t) *
(cmp->cm_src->ctf_typemax + 1));
}
static void
ctf_merge_fixup_symmaps(ctf_merge_types_t *cmp, ctf_merge_input_t *cmi)
{
ctf_merge_objmap_t *cmo;
ctf_merge_funcmap_t *cmf;
for (cmo = list_head(&cmi->cmi_omap); cmo != NULL;
cmo = list_next(&cmi->cmi_omap, cmo)) {
VERIFY3S(cmo->cmo_tid, !=, 0);
VERIFY(cmp->cm_tmap[cmo->cmo_tid].cmt_map != 0);
cmo->cmo_tid = cmp->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(cmp->cm_tmap[cmf->cmf_rtid].cmt_map != 0);
cmf->cmf_rtid = cmp->cm_tmap[cmf->cmf_rtid].cmt_map;
for (i = 0; i < cmf->cmf_argc; i++) {
VERIFY(cmp->cm_tmap[cmf->cmf_args[i]].cmt_map != 0);
cmf->cmf_args[i] =
cmp->cm_tmap[cmf->cmf_args[i]].cmt_map;
}
}
}
static int
ctf_merge_types(void *arg, void *arg2, void **outp, void *unsued)
{
int ret;
ctf_merge_types_t cm;
ctf_diff_t *cdp;
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));
if (ctf_getmodel(out) != ctf_getmodel(source))
return (ctf_set_errno(out, ECTF_DMODEL));
if ((ret = ctf_diff_init(out, source, &cdp)) != 0)
return (ret);
cm.cm_out = out;
cm.cm_src = source;
cm.cm_dedup = B_FALSE;
cm.cm_unique = B_FALSE;
ret = ctf_merge_types_init(&cm);
if (ret != 0) {
ctf_diff_fini(cdp);
return (ctf_set_errno(out, ret));
}
ret = ctf_diff_types(cdp, ctf_merge_diffcb, &cm);
if (ret != 0)
goto cleanup;
ret = ctf_merge_common(&cm);
ctf_dprintf("merge common returned with %d\n", ret);
if (ret == 0) {
ret = ctf_update(out);
ctf_dprintf("update returned with %d\n", ret);
} else {
goto cleanup;
}
ctf_merge_fixup_symmaps(&cm, scmi);
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);
if (ret != 0)
return (ctf_errno(out));
ctf_phase_bump();
return (0);
}
static int
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 *parent = cmh->cmh_unique;
*outp = NULL;
out = ctf_fdcreate(cmh->cmh_ofd, &err);
if (out == NULL)
return (ctf_set_errno(src, err));
out->ctf_parname = cmh->cmh_pname;
if (ctf_setmodel(out, ctf_getmodel(parent)) != 0) {
(void) ctf_set_errno(src, ctf_errno(out));
ctf_close(out);
return (CTF_ERR);
}
if (ctf_import(out, parent) != 0) {
(void) ctf_set_errno(src, ctf_errno(out));
ctf_close(out);
return (CTF_ERR);
}
if ((ret = ctf_diff_init(parent, src, &cdp)) != 0) {
ctf_close(out);
return (ctf_set_errno(src, ctf_errno(parent)));
}
cm.cm_out = parent;
cm.cm_src = src;
cm.cm_dedup = B_FALSE;
cm.cm_unique = B_TRUE;
ret = ctf_merge_types_init(&cm);
if (ret != 0) {
ctf_close(out);
ctf_diff_fini(cdp);
return (ctf_set_errno(src, ret));
}
ret = ctf_diff_types(cdp, ctf_merge_diffcb, &cm);
if (ret == 0) {
cm.cm_out = out;
ret = ctf_merge_uniquify_types(&cm);
if (ret == 0)
ret = ctf_update(out);
}
if (ret != 0) {
ctf_merge_types_fini(&cm);
ctf_diff_fini(cdp);
return (ctf_set_errno(src, ctf_errno(cm.cm_out)));
}
for (cmi = list_head(&cmh->cmh_inputs); cmi != NULL;
cmi = list_next(&cmh->cmh_inputs, cmi)) {
ctf_merge_fixup_symmaps(&cm, cmi);
}
ctf_merge_types_fini(&cm);
ctf_diff_fini(cdp);
*outp = out;
return (0);
}
static void
ctf_merge_fini_input(ctf_merge_input_t *cmi)
{
ctf_merge_objmap_t *cmo;
ctf_merge_funcmap_t *cmf;
while ((cmo = list_remove_head(&cmi->cmi_omap)) != NULL)
ctf_free(cmo, sizeof (ctf_merge_objmap_t));
while ((cmf = list_remove_head(&cmi->cmi_fmap)) != NULL)
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));
}
void
ctf_merge_fini(ctf_merge_t *cmh)
{
ctf_merge_input_t *cmi;
ctf_strfree(cmh->cmh_label);
ctf_strfree(cmh->cmh_pname);
while ((cmi = list_remove_head(&cmh->cmh_inputs)) != NULL)
ctf_merge_fini_input(cmi);
ctf_free(cmh, sizeof (ctf_merge_t));
}
ctf_merge_t *
ctf_merge_init(int fd, int *errp)
{
int err;
ctf_merge_t *out;
struct stat st;
if (errp == NULL)
errp = &err;
if (fd != -1 && fstat(fd, &st) != 0) {
*errp = EINVAL;
return (NULL);
}
out = ctf_alloc(sizeof (ctf_merge_t));
if (out == NULL) {
*errp = ENOMEM;
return (NULL);
}
if (fd == -1) {
out->cmh_msyms = B_FALSE;
} else {
out->cmh_msyms = B_TRUE;
}
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;
return (out);
}
int
ctf_merge_label(ctf_merge_t *cmh, const char *label)
{
char *dup;
if (label == NULL)
return (EINVAL);
dup = ctf_strdup(label);
if (dup == NULL)
return (EAGAIN);
ctf_strfree(cmh->cmh_label);
cmh->cmh_label = dup;
return (0);
}
static int
ctf_merge_add_function(ctf_merge_input_t *cmi, ctf_funcinfo_t *fip, ulong_t idx,
const char *file, const char *name, const Elf64_Sym *symp)
{
ctf_merge_funcmap_t *fmap;
fmap = ctf_alloc(sizeof (ctf_merge_funcmap_t) +
sizeof (ctf_id_t) * fip->ctc_argc);
if (fmap == NULL)
return (ENOMEM);
fmap->cmf_idx = idx;
fmap->cmf_sym = *symp;
fmap->cmf_rtid = fip->ctc_return;
fmap->cmf_flags = fip->ctc_flags;
fmap->cmf_argc = fip->ctc_argc;
fmap->cmf_name = name;
if (ELF64_ST_BIND(symp->st_info) == STB_LOCAL) {
fmap->cmf_file = file;
} else {
fmap->cmf_file = NULL;
}
if (ctf_func_args(cmi->cmi_input, idx, fmap->cmf_argc,
fmap->cmf_args) != 0) {
ctf_free(fmap, sizeof (ctf_merge_funcmap_t) +
sizeof (ctf_id_t) * fip->ctc_argc);
return (ctf_errno(cmi->cmi_input));
}
ctf_dprintf("added initial function %s, %lu, %s %u\n", name, idx,
fmap->cmf_file != NULL ? fmap->cmf_file : "global",
ELF64_ST_BIND(symp->st_info));
list_insert_tail(&cmi->cmi_fmap, fmap);
return (0);
}
static int
ctf_merge_add_object(ctf_merge_input_t *cmi, ctf_id_t id, ulong_t idx,
const char *file, const char *name, const Elf64_Sym *symp)
{
ctf_merge_objmap_t *cmo;
cmo = ctf_alloc(sizeof (ctf_merge_objmap_t));
if (cmo == NULL)
return (ENOMEM);
cmo->cmo_name = name;
if (ELF64_ST_BIND(symp->st_info) == STB_LOCAL) {
cmo->cmo_file = file;
} else {
cmo->cmo_file = NULL;
}
cmo->cmo_idx = idx;
cmo->cmo_tid = id;
cmo->cmo_sym = *symp;
list_insert_tail(&cmi->cmi_omap, cmo);
ctf_dprintf("added initial object %s, %lu, %ld, %s\n", name, idx, id,
cmo->cmo_file != NULL ? cmo->cmo_file : "global");
return (0);
}
static int
ctf_merge_add_symbol(const Elf64_Sym *symp, ulong_t idx, const char *file,
const char *name, boolean_t primary, void *arg)
{
ctf_merge_input_t *cmi = arg;
ctf_file_t *fp = cmi->cmi_input;
ushort_t *data, funcbase;
uint_t type;
ctf_funcinfo_t fi;
if (fp->ctf_sxlate[idx] == -1u)
return (0);
data = (ushort_t *)((uintptr_t)fp->ctf_buf + fp->ctf_sxlate[idx]);
if (*data == 0)
return (0);
type = ELF64_ST_TYPE(symp->st_info);
switch (type) {
case STT_FUNC:
funcbase = *data;
if (LCTF_INFO_KIND(fp, funcbase) != CTF_K_FUNCTION)
return (0);
data++;
fi.ctc_return = *data;
data++;
fi.ctc_argc = LCTF_INFO_VLEN(fp, funcbase);
fi.ctc_flags = 0;
if (fi.ctc_argc != 0 && data[fi.ctc_argc - 1] == 0) {
fi.ctc_flags |= CTF_FUNC_VARARG;
fi.ctc_argc--;
}
return (ctf_merge_add_function(cmi, &fi, idx, file, name,
symp));
case STT_OBJECT:
return (ctf_merge_add_object(cmi, *data, idx, file, name,
symp));
default:
return (0);
}
}
int
ctf_merge_add(ctf_merge_t *cmh, ctf_file_t *input)
{
int ret;
ctf_merge_input_t *cmi;
ctf_file_t *empty;
ctf_dprintf("adding input %p\n", input);
if (input->ctf_flags & LCTF_CHILD)
return (ECTF_MCHILD);
cmi = ctf_alloc(sizeof (ctf_merge_input_t));
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));
list_create(&cmi->cmi_omap, sizeof (ctf_merge_funcmap_t),
offsetof(ctf_merge_objmap_t, cmo_node));
if (cmh->cmh_msyms == B_TRUE) {
if ((ret = ctf_symtab_iter(input, ctf_merge_add_symbol,
cmi)) != 0) {
ctf_merge_fini_input(cmi);
return (ret);
}
}
list_insert_tail(&cmh->cmh_inputs, cmi);
cmh->cmh_ninputs++;
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);
}
int
ctf_merge_uniquify(ctf_merge_t *cmh, ctf_file_t *u, const char *pname)
{
char *dup;
if (u->ctf_flags & LCTF_CHILD)
return (ECTF_MCHILD);
if (pname == NULL)
return (EINVAL);
dup = ctf_strdup(pname);
if (dup == NULL)
return (EINVAL);
ctf_strfree(cmh->cmh_pname);
cmh->cmh_pname = dup;
cmh->cmh_unique = u;
return (0);
}
static boolean_t
ctf_merge_symbol_match(const char *ctf_file, const char *ctf_name,
const Elf64_Sym *ctf_symp, const char *symtab_file, const char *symtab_name,
const Elf64_Sym *symtab_symp, boolean_t *is_fuzzy)
{
*is_fuzzy = B_FALSE;
uint_t symtab_bind, ctf_bind;
symtab_bind = ELF64_ST_BIND(symtab_symp->st_info);
ctf_bind = ELF64_ST_BIND(ctf_symp->st_info);
ctf_dprintf("comparing merge match for %s/%s/%u->%s/%s/%u\n",
symtab_file, symtab_name, symtab_bind,
ctf_file, ctf_name, ctf_bind);
if (strcmp(ctf_name, symtab_name) != 0) {
return (B_FALSE);
}
if (symtab_bind == STB_GLOBAL && ctf_bind == STB_GLOBAL) {
return (B_TRUE);
} else if (symtab_bind == STB_GLOBAL) {
return (B_FALSE);
}
if (ctf_bind == STB_LOCAL && ctf_bind == symtab_bind &&
ctf_file != NULL && symtab_file != NULL &&
strcmp(ctf_file, symtab_file) == 0) {
return (B_TRUE);
}
if (symtab_bind == STB_WEAK && ctf_bind != STB_WEAK &&
ELF64_ST_TYPE(symtab_symp->st_info) ==
ELF64_ST_TYPE(ctf_symp->st_info) &&
symtab_symp->st_value == ctf_symp->st_value &&
symtab_symp->st_size == ctf_symp->st_size &&
symtab_symp->st_shndx == ctf_symp->st_shndx) {
if (ctf_bind == STB_GLOBAL) {
return (B_TRUE);
}
if (ctf_bind == STB_LOCAL && ctf_file != NULL &&
symtab_file != NULL && strcmp(ctf_file, symtab_file) == 0) {
*is_fuzzy = B_TRUE;
return (B_TRUE);
}
}
if (ctf_bind == STB_GLOBAL ||
(ctf_bind == STB_WEAK && symtab_bind == STB_WEAK)) {
*is_fuzzy = B_TRUE;
return (B_TRUE);
}
return (B_FALSE);
}
static int
ctf_merge_symbols(const Elf64_Sym *symp, ulong_t idx, const char *file,
const char *name, boolean_t primary, void *arg)
{
int err;
uint_t type, bind;
ctf_merge_symbol_arg_t *csa = arg;
ctf_file_t *fp = csa->cmsa_out;
type = ELF64_ST_TYPE(symp->st_info);
bind = ELF64_ST_BIND(symp->st_info);
ctf_dprintf("Trying to find match for %s/%s/%u\n", file, name,
ELF64_ST_BIND(symp->st_info));
if (type == STT_OBJECT) {
ctf_merge_objmap_t *cmo, *match = NULL;
for (cmo = list_head(csa->cmsa_objmap); cmo != NULL;
cmo = list_next(csa->cmsa_objmap, cmo)) {
boolean_t is_fuzzy = B_FALSE;
if (ctf_merge_symbol_match(cmo->cmo_file, cmo->cmo_name,
&cmo->cmo_sym, file, name, symp, &is_fuzzy)) {
if (is_fuzzy && csa->cmsa_dedup &&
bind != STB_WEAK) {
continue;
}
match = cmo;
if (is_fuzzy) {
continue;
}
break;
}
}
if (match == NULL) {
return (0);
}
if ((err = ctf_add_object(fp, idx, match->cmo_tid)) != 0) {
ctf_dprintf("Failed to add symbol %s->%d: %s\n", name,
match->cmo_tid, ctf_errmsg(ctf_errno(fp)));
return (ctf_errno(fp));
}
ctf_dprintf("mapped object into output %s/%s->%ld\n", file,
name, match->cmo_tid);
} else {
ctf_merge_funcmap_t *cmf, *match = NULL;
ctf_funcinfo_t fi;
for (cmf = list_head(csa->cmsa_funcmap); cmf != NULL;
cmf = list_next(csa->cmsa_funcmap, cmf)) {
boolean_t is_fuzzy = B_FALSE;
if (ctf_merge_symbol_match(cmf->cmf_file, cmf->cmf_name,
&cmf->cmf_sym, file, name, symp, &is_fuzzy)) {
if (is_fuzzy && csa->cmsa_dedup &&
bind != STB_WEAK) {
continue;
}
match = cmf;
if (is_fuzzy) {
continue;
}
break;
}
}
if (match == NULL) {
return (0);
}
fi.ctc_return = match->cmf_rtid;
fi.ctc_argc = match->cmf_argc;
fi.ctc_flags = match->cmf_flags;
if ((err = ctf_add_function(fp, idx, &fi, match->cmf_args)) !=
0) {
ctf_dprintf("Failed to add function %s: %s\n", name,
ctf_errmsg(ctf_errno(fp)));
return (ctf_errno(fp));
}
ctf_dprintf("mapped function into output %s/%s\n", file,
name);
}
return (0);
}
int
ctf_merge_merge(ctf_merge_t *cmh, ctf_file_t **outp)
{
int err, merr;
ctf_merge_input_t *cmi;
ctf_id_t ltype;
mergeq_t *mqp;
ctf_merge_input_t *final;
ctf_file_t *out;
ctf_dprintf("Beginning ctf_merge_merge()\n");
if (cmh->cmh_label != NULL && cmh->cmh_unique != NULL) {
const char *label = ctf_label_topmost(cmh->cmh_unique);
if (label == NULL)
return (ECTF_NOLABEL);
if (strcmp(label, cmh->cmh_label) != 0)
return (ECTF_LCONFLICT);
}
if (mergeq_init(&mqp, cmh->cmh_nthreads) == -1) {
return (errno);
}
VERIFY(cmh->cmh_ninputs % 2 == 0);
for (cmi = list_head(&cmh->cmh_inputs); cmi != NULL;
cmi = list_next(&cmh->cmh_inputs, cmi)) {
if (mergeq_add(mqp, cmi) == -1) {
err = errno;
mergeq_fini(mqp);
}
}
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);
}
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) {
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 += CTF_CHILD_START;
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) {
ctf_merge_symbol_arg_t arg;
arg.cmsa_objmap = &final->cmi_omap;
arg.cmsa_funcmap = &final->cmi_fmap;
arg.cmsa_out = out;
arg.cmsa_dedup = B_FALSE;
err = ctf_symtab_iter(out, ctf_merge_symbols, &arg);
if (err != 0) {
ctf_close(out);
return (err);
}
}
err = ctf_update(out);
if (err != 0) {
err = ctf_errno(out);
ctf_close(out);
return (err);
}
*outp = out;
return (0);
}
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;
if (same == B_TRUE) {
while (cmt[oid].cmt_missing == B_FALSE)
oid = cmt[oid].cmt_map;
cmt[iid].cmt_map = oid;
ctf_dprintf("dedup %d->%d \n", iid, oid);
} else {
VERIFY(cmt[iid].cmt_map == 0);
cmt[iid].cmt_missing = B_TRUE;
ctf_dprintf("dedup %d is missing\n", iid);
}
}
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_types_t cm;
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;
cm.cm_unique = B_FALSE;
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", NULL);
ctf_merge_fixup_symmaps(&cm, cmi);
if (cmp->cmh_msyms == B_TRUE) {
ctf_merge_symbol_arg_t arg;
arg.cmsa_objmap = &cmi->cmi_omap;
arg.cmsa_funcmap = &cmi->cmi_fmap;
arg.cmsa_out = cm.cm_out;
arg.cmsa_dedup = B_TRUE;
ret = ctf_symtab_iter(cm.cm_out, ctf_merge_symbols, &arg);
if (ret != 0) {
ctf_dprintf("failed to dedup symbols: %s\n",
ctf_errmsg(ret));
goto err;
}
}
ret = ctf_update(cm.cm_out);
if (ret == 0) {
cmc->cmi_input = NULL;
*outp = cm.cm_out;
}
ctf_phase_dump(cm.cm_out, "dedup-post-syms", NULL);
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);
}