/* * 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 2020 Joyent, Inc. */ /* * Collection of common utilities for CTF testing. */ #include #include #include "check-common.h" typedef struct ctftests_lookup_cb { ctf_file_t *clc_fp; ctf_id_t clc_id; const char *clc_name; } ctftests_lookup_cb_t; typedef struct ctftest_member_cb { ctf_file_t *cmc_fp; const check_member_t *cmc_members; const char *cmc_name; } ctftest_member_cb_t; static int ctftest_lookup_type_cb(ctf_id_t id, boolean_t root, void *arg) { char buf[2048]; ctftests_lookup_cb_t *clc = arg; if (ctf_type_name(clc->clc_fp, id, buf, sizeof (buf)) == NULL) return (0); if (strcmp(buf, clc->clc_name) != 0) return (0); clc->clc_id = id; return (1); } /* * This is a variant on the classic ctf_lookup_by_name(). ctf_lookup_by_name() * skips qualifiers, which makes sense given what the consumers of it are trying * to do. However, that's not what we want here. So instead we basically have to * walk the type table. */ static ctf_id_t ctftest_lookup_type(ctf_file_t *fp, const char *name) { ctftests_lookup_cb_t clc; clc.clc_fp = fp; clc.clc_id = CTF_ERR; clc.clc_name = name; (void) ctf_type_iter(fp, B_TRUE, ctftest_lookup_type_cb, &clc); return (clc.clc_id); } static int ctftest_lookup_object_cb(const char *obj, ctf_id_t type, ulong_t idx, void *arg) { ctftests_lookup_cb_t *clc = arg; if (strcmp(obj, clc->clc_name) == 0) { clc->clc_id = type; return (1); } return (0); } static ctf_id_t ctftest_lookup_symbol(ctf_file_t *fp, const char *name) { ctftests_lookup_cb_t clc; clc.clc_fp = fp; clc.clc_id = CTF_ERR; clc.clc_name = name; (void) ctf_object_iter(fp, ctftest_lookup_object_cb, &clc); return (clc.clc_id); } typedef struct ctf_function_cb { const char *cfc_name; ulong_t *cfc_symp; ctf_funcinfo_t *cfc_fip; } ctf_function_cb_t; static int ctftest_lookup_function_cb(const char *name, ulong_t symidx, ctf_funcinfo_t *fip, void *arg) { ctf_function_cb_t *cfc = arg; if (strcmp(name, cfc->cfc_name) != 0) return (0); *cfc->cfc_symp = symidx; *cfc->cfc_fip = *fip; return (1); } /* * Note, this function finds the first one with a matching name. This must not * be used when performing searches where a given name may occur more than once. */ static boolean_t ctftest_lookup_function(ctf_file_t *fp, const char *name, ulong_t *symp, ctf_funcinfo_t *fip) { ctf_function_cb_t cfc; *symp = 0; cfc.cfc_name = name; cfc.cfc_symp = symp; cfc.cfc_fip = fip; (void) ctf_function_iter(fp, ctftest_lookup_function_cb, &cfc); return (*symp == 0 ? B_FALSE : B_TRUE); } boolean_t ctftest_check_numbers(ctf_file_t *fp, const check_number_t *tests) { uint_t i; boolean_t ret = B_TRUE; for (i = 0; tests[i].cn_tname != NULL; i++) { ctf_id_t id; ctf_encoding_t enc; if (ctftest_skip(tests[i].cn_skips)) { warnx("skipping check numbers test %s due to known " "compiler issue", tests[i].cn_tname); continue; } id = ctftest_lookup_type(fp, tests[i].cn_tname); if (id == CTF_ERR) { warnx("failed to look up %s", tests[i].cn_tname); ret = B_FALSE; continue; } if (ctf_type_kind(fp, id) != tests[i].cn_kind) { warnx("type kind mismatch for %s: got %u, expected %u", tests[i].cn_tname, ctf_type_kind(fp, id), tests[i].cn_kind); ret = B_FALSE; continue; } if (ctf_type_encoding(fp, id, &enc) == CTF_ERR) { warnx("failed to get type encoding for %s: %s", tests[i].cn_tname, ctf_errmsg(ctf_errno(fp))); ret = B_FALSE; continue; } if (enc.cte_format != tests[i].cn_flags) { warnx("encoding flags mismatch for %s: got 0x%x, " "expected 0x%x", tests[i].cn_tname, enc.cte_format, tests[i].cn_flags); ret = B_FALSE; continue; } if (enc.cte_offset != tests[i].cn_offset) { warnx("encoding offset mismatch for %s: got 0x%x, " "expected 0x%x", tests[i].cn_tname, enc.cte_offset, tests[i].cn_offset); ret = B_FALSE; continue; } if (enc.cte_bits != tests[i].cn_size) { warnx("encoding size mismatch for %s: got 0x%x, " "expected 0x%x", tests[i].cn_tname, enc.cte_bits, tests[i].cn_size); ret = B_FALSE; continue; } } return (ret); } typedef struct ctftests_symbol_cb { ctf_file_t *csc_fp; boolean_t csc_ret; const check_symbol_t *csc_tests; } ctftest_symbol_cb_t; static int ctftest_check_symbol_cb(const char *obj, ctf_id_t type, ulong_t idx, void *arg) { ctftest_symbol_cb_t *cb = arg; const check_symbol_t *tests = cb->csc_tests; ctf_file_t *fp = cb->csc_fp; uint_t i; for (i = 0; tests[i].cs_symbol != NULL; i++) { ctf_id_t id; if (strcmp(obj, tests[i].cs_symbol) != 0) continue; id = ctftest_lookup_type(fp, tests[i].cs_type); if (id == CTF_ERR) { warnx("failed to lookup type %s for symbol %s", tests[i].cs_type, tests[i].cs_symbol); cb->csc_ret = B_FALSE; return (0); } if (id != type) { warnx("type mismatch for symbol %s, has type id %ld" ", but specified type %s has id %ld", tests[i].cs_symbol, type, tests[i].cs_type, id); cb->csc_ret = B_FALSE; return (0); } } return (0); } boolean_t ctftest_check_symbols(ctf_file_t *fp, const check_symbol_t *tests) { ctftest_symbol_cb_t cb; cb.csc_fp = fp; cb.csc_ret = B_TRUE; cb.csc_tests = tests; if (ctf_object_iter(fp, ctftest_check_symbol_cb, &cb) != 0) return (B_FALSE); return (cb.csc_ret); } boolean_t ctftest_check_descent(const char *symbol, ctf_file_t *fp, const check_descent_t *tests, boolean_t quiet) { ctf_id_t base; uint_t layer = 0; /* * First, find the initial type of the symbol. */ base = ctftest_lookup_symbol(fp, symbol); if (base == CTF_ERR) { warnx("failed to lookup type for symbol %s", symbol); return (B_FALSE); } while (tests->cd_tname != NULL) { ctf_id_t tid; int kind; ctf_arinfo_t ari; if (base == CTF_ERR) { if (!quiet) { warnx("encountered non-reference type at layer " "%u while still expecting type %s for " "symbol %s", layer, tests->cd_tname, symbol); } return (B_FALSE); } tid = ctftest_lookup_type(fp, tests->cd_tname); if (tid == CTF_ERR) { if (!quiet) { warnx("failed to lookup type %s", tests->cd_tname); } return (B_FALSE); } if (tid != base) { if (!quiet) { warnx("type mismatch at layer %u: found " "id %ld, but expecting type id %ld for " "type %s, symbol %s", layer, base, tid, tests->cd_tname, symbol); } return (B_FALSE); } kind = ctf_type_kind(fp, base); if (kind != tests->cd_kind) { if (!quiet) { warnx("type kind mismatch at layer %u: found " "kind %u, but expected kind %u for %s, " "symbol %s", layer, kind, tests->cd_kind, tests->cd_tname, symbol); } return (B_FALSE); } switch (kind) { case CTF_K_ARRAY: if (ctf_array_info(fp, base, &ari) == CTF_ERR) { if (!quiet) { warnx("failed to lookup array info at " "layer %ld for type %s, " "symbol %s: %s", base, tests->cd_tname, symbol, ctf_errmsg(ctf_errno(fp))); } return (B_FALSE); } if (tests->cd_nents != ari.ctr_nelems) { if (!quiet) { warnx("array element mismatch at layer " "%u for type %s, symbol %s: found " "%u, expected %u", layer, tests->cd_tname, symbol, ari.ctr_nelems, tests->cd_nents); } return (B_FALSE); } tid = ctftest_lookup_type(fp, tests->cd_contents); if (tid == CTF_ERR) { if (!quiet) { warnx("failed to look up type %s", tests->cd_contents); } return (B_FALSE); } if (ari.ctr_contents != tid) { if (!quiet) { warnx("array contents mismatch at " "layer %u for type %s, symbol %s: " "found %ld, expected %s/%ld", layer, tests->cd_tname, symbol, ari.ctr_contents, tests->cd_contents, tid); } return (B_FALSE); } base = ari.ctr_contents; break; default: base = ctf_type_reference(fp, base); break; } tests++; layer++; } if (base != CTF_ERR) { if (!quiet) { warnx("found additional type %ld in chain, " "but expected no more", base); } return (B_FALSE); } return (B_TRUE); } int ctftest_check_enum_count(const char *name, int value, void *arg) { uint_t *u = arg; *u = *u + 1; return (0); } int ctftest_check_enum_value(const char *name, int value, void *arg) { uint_t i; const check_enum_t *enums = arg; for (i = 0; enums[i].ce_name != NULL; i++) { if (strcmp(enums[i].ce_name, name) != 0) continue; if (enums[i].ce_value == (int64_t)value) return (0); warnx("enum %s value mismatch: found %d, expected %" PRId64, name, value, enums[i].ce_value); return (1); } warnx("found no matching entry for enum member %s", name); return (1); } boolean_t ctftest_check_enum(const char *type, ctf_file_t *fp, const check_enum_t *enums) { int ret; uint_t tcount, ecount; ctf_id_t base; if ((base = ctftest_lookup_type(fp, type)) == CTF_ERR) { warnx("Failed to look up type %s", type); return (B_FALSE); } if (ctf_type_kind(fp, base) != CTF_K_ENUM) { warnx("%s is not an enum", type); return (B_FALSE); } /* * First count how many entries we have. */ tcount = 0; while (enums[tcount].ce_name != NULL) { tcount++; } ecount = 0; if (ctf_enum_iter(fp, base, ctftest_check_enum_count, &ecount) != 0) { warnx("failed to walk enum %s: %s", type, ctf_errmsg(ctf_errno(fp))); return (B_FALSE); } if (tcount != ecount) { warnx("enum value mismatch: expected %u values, but found %u", tcount, ecount); return (B_FALSE); } if ((ret = ctf_enum_iter(fp, base, ctftest_check_enum_value, (void *)enums)) != 0) { if (ret == -1) { warnx("failed to walk enum %s: %s", type, ctf_errmsg(ctf_errno(fp))); } return (B_FALSE); } return (B_TRUE); } int ctftest_check_member_count(const char *mname, ctf_id_t mtype, ulong_t bitoff, void *arg) { uint_t *countp = arg; *countp = *countp + 1; return (0); } int ctftest_check_members_cb(const char *mname, ctf_id_t mtype, ulong_t bitoff, void *arg) { uint_t i; const ctftest_member_cb_t *cmc = arg; const check_member_t *members = cmc->cmc_members; ctf_file_t *fp = cmc->cmc_fp; for (i = 0; members[i].cm_name != NULL; i++) { boolean_t bad = B_FALSE; char buf[2048]; if (strcmp(mname, members[i].cm_name) != 0) continue; if (bitoff != members[i].cm_offset) { warnx("member %s of type %s has mismatched bit offset: " "found %lu, expected %lu", mname, cmc->cmc_name, bitoff, members[i].cm_offset); bad = B_TRUE; } if (ctf_type_name(fp, mtype, buf, sizeof (buf)) == NULL) { warnx("failed to obtain type name for member %s: %s", mname, ctf_errmsg(ctf_errno(fp))); bad = B_TRUE; } else if (strcmp(buf, members[i].cm_type) != 0) { warnx("member %s has bad type, found %s, expected %s", mname, buf, members[i].cm_type); bad = B_TRUE; } return (bad ? 1 : 0); } warnx("found no matching entry for member %s of type %s", mname, cmc->cmc_name); return (1); } boolean_t ctftest_check_members(const char *type, ctf_file_t *fp, int kind, size_t size, const check_member_t *members) { int ret; uint_t tcount, mcount; ctf_id_t base; ctftest_member_cb_t cmc; if ((base = ctftest_lookup_type(fp, type)) == CTF_ERR) { warnx("failed to look up type %s", type); return (B_FALSE); } if (ctf_type_kind(fp, base) != kind) { warnx("%s has kind %s, expected %s", type, ctf_kind_name(fp, ctf_type_kind(fp, base)), ctf_kind_name(fp, kind)); return (B_FALSE); } if (size != ctf_type_size(fp, base)) { warnx("%s has bad size, expected %zu, found %zd", type, size, ctf_type_size(fp, base)); return (B_FALSE); } /* * First count how many entries we have. */ tcount = 0; while (members[tcount].cm_name != NULL) { tcount++; } mcount = 0; if (ctf_member_iter(fp, base, ctftest_check_member_count, &mcount) != 0) { warnx("failed to walk members of %s: %s", type, ctf_errmsg(ctf_errno(fp))); return (B_FALSE); } if (tcount != mcount) { warnx("type member mismatch: expected %u values, but found %u", tcount, mcount); return (B_FALSE); } cmc.cmc_fp = fp; cmc.cmc_members = members; cmc.cmc_name = type; if ((ret = ctf_member_iter(fp, base, ctftest_check_members_cb, &cmc)) != 0) { if (ret == -1) { warnx("failed to walk type %s: %s", type, ctf_errmsg(ctf_errno(fp))); } return (B_FALSE); } return (B_TRUE); } boolean_t ctftest_check_function(const char *symbol, ctf_file_t *fp, const char *rtype, uint_t nargs, uint_t flags, const char **argv) { ulong_t sym; ctf_funcinfo_t fi; uint_t i; boolean_t ret = B_TRUE; ctf_id_t *args; char buf[2048]; if (!ctftest_lookup_function(fp, symbol, &sym, &fi)) { warnx("failed to look up function %s", symbol); return (B_FALSE); } if (ctf_type_name(fp, fi.ctc_return, buf, sizeof (buf)) == NULL) { warnx("failed to lookup return type name for function %s", symbol); ret = B_FALSE; } else if (strcmp(rtype, buf) != 0) { warnx("return type has wrong type: found %s, expected %s", buf, rtype); ret = B_FALSE; } if (nargs != fi.ctc_argc) { warnx("function argument mismatch: found %u, expected %u", fi.ctc_argc, nargs); ret = B_FALSE; } if (flags != fi.ctc_flags) { warnx("function flags mismatch, found 0x%x, expected 0x%x", fi.ctc_flags, flags); ret = B_FALSE; } if (!ret || fi.ctc_argc == 0) { return (ret); } if ((args = calloc(fi.ctc_argc, sizeof (ctf_id_t))) == NULL) { warnx("failed to allocate memory for function arguments"); return (B_FALSE); } if (ctf_func_args(fp, sym, fi.ctc_argc, args) != 0) { warnx("failed to get function information: %s", ctf_errmsg(ctf_errno(fp))); free(args); return (B_FALSE); } for (i = 0; i < fi.ctc_argc; i++) { if (ctf_type_name(fp, args[i], buf, sizeof (buf)) == NULL) { warnx("failed to obtain type name for argument %u: %s", i, ctf_errmsg(ctf_errno(fp))); ret = B_FALSE; break; } if (strcmp(buf, argv[i]) != 0) { warnx("argument %u has wrong type: found %s, " "expected %s", i, buf, argv[i]); ret = B_FALSE; break; } } free(args); return (ret); } boolean_t ctftest_check_fptr(const char *type, ctf_file_t *fp, const char *rtype, uint_t nargs, uint_t flags, const char **argv) { ctf_id_t tid; ctf_funcinfo_t fi; uint_t i; boolean_t ret = B_TRUE; ctf_id_t *args; char buf[2048]; if ((tid = ctf_lookup_by_name(fp, type)) == CTF_ERR) { warnx("failed to look up type %s: %s", type, ctf_errmsg(ctf_errno(fp))); return (B_FALSE); } /* * Perform two CTF type resolves, one for the function pointer and one * for the typedef that gets passed in. */ if ((tid = ctf_type_resolve(fp, tid)) == CTF_ERR) { warnx("failed to convert type %s to base type: %s", type, ctf_errmsg(ctf_errno(fp))); return (B_FALSE); } if (ctf_type_kind(fp, tid) == CTF_K_POINTER && (tid = ctf_type_reference(fp, tid)) == CTF_ERR) { warnx("failed to convert type %s to base type: %s", type, ctf_errmsg(ctf_errno(fp))); return (B_FALSE); } if (ctf_func_info_by_id(fp, tid, &fi) != 0) { warnx("failed to get function information for type %s: %s", type, ctf_errmsg(ctf_errno(fp))); return (B_FALSE); } if (ctf_type_name(fp, fi.ctc_return, buf, sizeof (buf)) == NULL) { warnx("failed to lookup return type name for function %s", type); ret = B_FALSE; } else if (strcmp(rtype, buf) != 0) { warnx("return type has wrong type: found %s, expected %s", buf, rtype); ret = B_FALSE; } if (nargs != fi.ctc_argc) { warnx("function argument mismatch: found %u, expected %u", fi.ctc_argc, nargs); ret = B_FALSE; } if (flags != fi.ctc_flags) { warnx("function flags mismatch, found 0x%x, expected 0x%x", fi.ctc_flags, flags); ret = B_FALSE; } if (!ret || fi.ctc_argc == 0) { return (ret); } if ((args = calloc(fi.ctc_argc, sizeof (ctf_id_t))) == NULL) { warnx("failed to allocate memory for function arguments"); return (B_FALSE); } if (ctf_func_args_by_id(fp, tid, fi.ctc_argc, args) != 0) { warnx("failed to get function information: %s", ctf_errmsg(ctf_errno(fp))); free(args); return (B_FALSE); } for (i = 0; i < fi.ctc_argc; i++) { if (ctf_type_name(fp, args[i], buf, sizeof (buf)) == NULL) { warnx("failed to obtain type name for argument %u: %s", i, ctf_errmsg(ctf_errno(fp))); ret = B_FALSE; break; } if (strcmp(buf, argv[i]) != 0) { warnx("argument %u has wrong type: found %s, " "expected %s", i, buf, argv[i]); ret = B_FALSE; break; } } free(args); return (ret); } boolean_t ctftest_check_size(const char *type, ctf_file_t *fp, size_t size) { ctf_id_t base; if ((base = ctftest_lookup_type(fp, type)) == CTF_ERR) { warnx("Failed to look up type %s", type); return (B_FALSE); } if (size != ctf_type_size(fp, base)) { warnx("%s has bad size, expected %zu, found %zd", type, size, ctf_type_size(fp, base)); return (B_FALSE); } return (B_TRUE); } typedef struct ctftest_duplicates { ctf_file_t *ctd_fp; char **ctd_names; size_t ctd_len; size_t ctd_curent; boolean_t ctd_ret; } ctftest_duplicates_t; static int ctftest_duplicates_cb(ctf_id_t id, boolean_t root, void *arg) { char buf[2048]; ctftest_duplicates_t *dup = arg; size_t i; if (ctf_type_name(dup->ctd_fp, id, buf, sizeof (buf)) == NULL) { warnx("failed to lookup name for id %ld", id); dup->ctd_ret = B_FALSE; return (1); } for (i = 0; i < dup->ctd_curent; i++) { if (strcmp(buf, dup->ctd_names[i]) == 0) { warnx("encountered duplicate type '%s'", buf); dup->ctd_ret = B_FALSE; /* * Don't break out of the loop and keep going in case we * find another duplicate. */ return (0); } } if (dup->ctd_curent == dup->ctd_len) { char **n; size_t newlen = dup->ctd_len * 2; n = recallocarray(dup->ctd_names, dup->ctd_len, newlen, sizeof (char *)); if (n == NULL) { warnx("failed to resize type name array"); dup->ctd_ret = B_FALSE; return (1); } dup->ctd_names = n; dup->ctd_len = newlen; } dup->ctd_names[dup->ctd_curent] = strdup(buf); if (dup->ctd_names[dup->ctd_curent] == NULL) { warn("failed to duplicate type name"); dup->ctd_ret = B_FALSE; return (1); } dup->ctd_curent++; return (0); } boolean_t ctftest_duplicates(ctf_file_t *fp) { size_t i; ctftest_duplicates_t d; bzero(&d, sizeof (d)); d.ctd_fp = fp; d.ctd_len = 4; d.ctd_ret = B_TRUE; d.ctd_names = recallocarray(NULL, 0, d.ctd_len, sizeof (char *)); if (d.ctd_names == NULL) { warnx("failed to allocate duplicate name storage"); return (B_FALSE); } (void) ctf_type_iter(fp, B_TRUE, ctftest_duplicates_cb, &d); for (i = 0; i < d.ctd_curent; i++) { free(d.ctd_names[i]); } free(d.ctd_names); return (d.ctd_ret); } boolean_t ctftest_skip(check_skip_t skip) { const char *compiler; if (skip == 0) { return (B_FALSE); } compiler = getenv("ctf_cc_type"); if (compiler == NULL) { return (B_FALSE); } if ((skip & SKIP_CLANG) != 0 && strcmp(compiler, "clang") == 0) return (B_TRUE); return (B_FALSE); }