diff --git a/usr.bin/Makefile b/usr.bin/Makefile --- a/usr.bin/Makefile +++ b/usr.bin/Makefile @@ -27,6 +27,7 @@ compress \ csplit \ ctlstat \ + ctfdiff \ cut \ diff \ dirname \ diff --git a/usr.bin/ctfdiff/Makefile b/usr.bin/ctfdiff/Makefile new file mode 100644 --- /dev/null +++ b/usr.bin/ctfdiff/Makefile @@ -0,0 +1,33 @@ +.include + +.PATH: ${SRCTOP}/cddl/contrib/opensolaris/tools/ctf/common + +PACKAGE= ctf-tools +PROG_CXX= ctfdiff +SRCS= ctfdiff.cc \ + ctfdata.cc \ + ctftype.cc \ + metadata.cc\ + utility.cc \ + +CFLAGS+= -DIN_BASE +CFLAGS+= -I${SRCTOP}/sys/contrib/openzfs/include +CFLAGS+= -I${SRCTOP}/sys/contrib/openzfs/lib/libspl/include/ +CFLAGS+= -I${SRCTOP}/sys/contrib/openzfs/lib/libspl/include/os/freebsd +CFLAGS+= -I${SRCTOP}/sys +CFLAGS+= -I${SRCTOP}/cddl/compat/opensolaris/include +CFLAGS+= -I${OPENSOLARIS_USR_DISTDIR} \ + -I${OPENSOLARIS_SYS_DISTDIR} \ + -I${OPENSOLARIS_USR_DISTDIR}/head \ + -I${OPENSOLARIS_USR_DISTDIR}/cmd/mdb/tools/common \ + -I${SRCTOP}/sys/cddl/compat/opensolaris \ + -I${SRCTOP}/cddl/compat/opensolaris/include \ + -I${OPENSOLARIS_USR_DISTDIR}/tools/ctf/common \ + -I${OPENSOLARIS_SYS_DISTDIR}/uts/common + +CXXFLAGS+= -std=c++17 +CFLAGS+= -DHAVE_ISSETUGID + +LIBADD= elf z + +.include diff --git a/usr.bin/ctfdiff/Makefile.inc b/usr.bin/ctfdiff/Makefile.inc new file mode 100644 --- /dev/null +++ b/usr.bin/ctfdiff/Makefile.inc @@ -0,0 +1,12 @@ +OPENSOLARIS_USR_DISTDIR= ${.CURDIR}/../../cddl/contrib/opensolaris +OPENSOLARIS_SYS_DISTDIR= ${.CURDIR}/../../sys/cddl/contrib/opensolaris + +IGNORE_PRAGMA= YES + +CFLAGS+= -DNEED_SOLARIS_BOOLEAN +CFLAGS+= -DHAVE_STRLCAT -DHAVE_STRLCPY + +# Do not lint the CDDL stuff. It is all externally maintained and +# lint output is wasteful noise here. + +NO_LINT= diff --git a/usr.bin/ctfdiff/ctfdata.hpp b/usr.bin/ctfdiff/ctfdata.hpp new file mode 100644 --- /dev/null +++ b/usr.bin/ctfdiff/ctfdata.hpp @@ -0,0 +1,88 @@ +#pragma once + +#include + +#include +#include + +#include "ctf_headers.h" +#include "sys/ctf.h" + +#include "ctftype.hpp" +#include "metadata.hpp" +#include +#include +#include +#include +#include +#include + +struct CtfDiff; +struct CtfData; +struct CtfType; + +using ShrCtfData = std::shared_ptr; +using ShrCtfType = std::shared_ptr; + +struct CtfData { + public: + /* typedef */ + template struct CtfObjEntry { + using ty_type = T; + std::string_view name; /* name of the variable */ + T type; /* type of the variable */ + uint32_t id; /* id of the variable */ + }; + + using CtfFuncTypeEntry = CtfObjEntry>; + using CtfFuncIdEntry = CtfObjEntry>; + using CtfVarTypeEntry = CtfObjEntry; + using CtfVarIdEntry = CtfObjEntry; + + private: + /* members */ + CtfMetaData metadata; + size_t ctf_id_width; + ctf_header_t *header; + std::unordered_map id_to_types; + std::vector static_variables; + std::vector functions; + + /* member function */ + CtfTypeFactory get_type_factory(); + bool zlib_decompress(); + + std::string_view find_next_symbol_with_type(int &idx, uchar_t type); + std::string_view get_str_from_ref(uint_t ref); + bool ignore_symbol(GElf_Sym *sym, const char *name); + + std::pair, std::vector> + do_diff_func(const CtfData &rhs, + std::unordered_map &cache) const; + std::pair, std::vector> + do_diff_var(const CtfData &rhs, + std::unordered_map &cache) const; + CtfData(CtfMetaData &&metadata); + + /* static function */ + static bool do_parse_types(ShrCtfData info); + static bool do_parse_data(ShrCtfData info); + static bool do_parse_func(ShrCtfData info); + + public: + std::pair compare_and_get_diff( + const CtfData &rhs) const; + + bool is_available(); + inline const std::unordered_map &id_mapper() const + { + return id_to_types; + } + + static std::shared_ptr create_ctf_info(CtfMetaData &&metadata); +}; + +struct CtfDiff { + std::vector variables; + std::vector functions; +}; diff --git a/usr.bin/ctfdiff/ctfdata.cc b/usr.bin/ctfdiff/ctfdata.cc new file mode 100644 --- /dev/null +++ b/usr.bin/ctfdiff/ctfdata.cc @@ -0,0 +1,735 @@ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "contrib/openzfs/lib/libspl/include/sys/stdtypes.h" +#include "ctf_headers.h" +#include "sys/ctf.h" +#include "sys/elf_common.h" + +#include "ctfdata.hpp" +#include "ctftype.hpp" +#include "metadata.hpp" +#include "utility.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +bool +CtfData::is_available() +{ + return (this->header != nullptr); +} + +bool +CtfData::ignore_symbol(GElf_Sym *sym, const char *name) +{ + u_char type = GELF_ST_TYPE(sym->st_info); + + /* when symbol is anomous or undefined */ + if (sym->st_shndx == SHN_UNDEF || sym->st_name == 0) + return (true); + + if (strcmp(name, "_START_") == 0 || strcmp(name, "_END_") == 0) + return (true); + + /* ignore address == 0 and abs) */ + if (type == STT_OBJECT && sym->st_shndx == SHN_ABS && + sym->st_value == 0) + return (true); + + return (false); +} + +bool +CtfData::zlib_decompress() +{ + z_stream zs; + std::byte *buffer; + int rc; + size_t buffer_size = header->cth_stroff + header->cth_strlen; + + buffer = new std::byte[buffer_size]; + + bzero((void *)&zs, sizeof(zs)); + zs.next_in = reinterpret_cast(metadata.ctfdata.data); + zs.avail_in = metadata.ctfdata.size; + zs.next_out = reinterpret_cast(buffer); + zs.avail_out = buffer_size; + + if ((rc = inflateInit(&zs)) != Z_OK) { + std::cout << "failed to initialize zlib: " << zError(rc) + << '\n'; + return (false); + } + + if ((rc = inflate(&zs, Z_FINISH)) != Z_STREAM_END) { + std::cout << "failed to decompress CTF data: " << zError(rc) + << '\n'; + return (false); + } + + if ((rc = inflateEnd(&zs)) != Z_OK) { + std::cout << "failed to finish decompress: " << zError(rc) + << '\n'; + return (false); + } + + if (zs.total_out != buffer_size) { + std::cout << "CTF data is corrupted\n"; + return (false); + } + + metadata.ctfdata.data = buffer; + metadata.ctfdata.size = buffer_size; + + return (true); +} + +CtfTypeFactory +CtfData::get_type_factory() +{ + return header->cth_version == CTF_VERSION_2 ? + &CtfTypeParser_V2::create_symbol : + &CtfTypeParser_V3::create_symbol; +} + +CtfData::CtfData(CtfMetaData &&metadata) + : metadata(metadata) +{ + Buffer &ctf_buffer = this->metadata.ctfdata; + this->header = nullptr; + + if (ctf_buffer.size < sizeof(ctf_preamble_t)) { + std::cout << metadata.file_name() + << " does not contain a CTF preamble\n"; + return; + } + + const ctf_preamble_t *preamble = reinterpret_cast( + ctf_buffer.data); + + if (preamble->ctp_magic != CTF_MAGIC) { + std::cout << metadata.file_name() + << " does not contain a valid ctf data\n"; + return; + } + + if (preamble->ctp_version != CTF_VERSION_2 && + preamble->ctp_version != CTF_VERSION_3) { + std::cout << "CTF version " << preamble->ctp_version + << " is not available\n"; + return; + } + + if (ctf_buffer.size < sizeof(ctf_header_t)) { + std::cout << "File " << metadata.file_name() + << " contains invalid CTF header\n"; + return; + } + + this->ctf_id_width = preamble->ctp_version == CTF_VERSION_2 ? 2 : 4; + this->header = reinterpret_cast(ctf_buffer.data); + ctf_buffer.data += sizeof(ctf_header_t); + + if (header->cth_flags & CTF_F_COMPRESS) { + if (!zlib_decompress()) { + this->header = nullptr; + return; + } + } + + return; +} + +std::shared_ptr +CtfData::create_ctf_info(CtfMetaData &&metadata) +{ + auto res = std::shared_ptr( + new CtfData(std::forward(metadata))); + + if (!res->is_available()) { + return nullptr; + } + + do_parse_types(res); + do_parse_data(res); + do_parse_func(res); + + std::sort(res->functions.begin(), res->functions.end(), + [](const auto &lhs, const auto &rhs) { + return lhs.name < rhs.name; + }); + std::sort(res->static_variables.begin(), res->static_variables.end(), + [](const auto &lhs, const auto &rhs) { + return lhs.name < rhs.name; + }); + + return (res); +} + +std::string_view +CtfData::find_next_symbol_with_type(int &idx, uchar_t type) +{ + size_t i; + uchar_t sym_type; + GElf_Sym sym; + const char *name; + Elf_Data *sym_sec = metadata.symdata.elfdata; + + for (i = idx + 1; i < metadata.symdata.entries; ++i) { + if (gelf_getsym(sym_sec, i, &sym) == 0) + return (""); + + name = (const char *)(metadata.strdata.data + sym.st_name); + sym_type = GELF_ST_TYPE(sym.st_info); + + if (type != sym_type || ignore_symbol(&sym, name)) + continue; + idx = i; + return (name); + } + + return (""); +} + +bool +CtfData::do_parse_data(ShrCtfData info) +{ + auto &header = info->header; + auto &metadata = info->metadata; + auto &static_variables = info->static_variables; + auto ctf_id_width = info->ctf_id_width; + + const std::byte *iter = metadata.ctfdata.data + header->cth_objtoff; + ulong_t n = (header->cth_funcoff - header->cth_objtoff) / ctf_id_width; + + int symidx, id; + uint32_t type_id; + std::string_view name; + + for (symidx = -1, id = 0; id < (int)n; ++id) { + if (metadata.symdata.data != nullptr) + name = info->find_next_symbol_with_type(symidx, + STT_OBJECT); + else + name = ""; + + memcpy(&type_id, iter, ctf_id_width); + iter += ctf_id_width; + if (name != "") + static_variables.push_back( + { name, type_id, static_cast(id) }); + } + + return (true); +} + +bool +CtfData::do_parse_types(ShrCtfData info) +{ + auto &header = info->header; + auto &metadata = info->metadata; + auto &id_to_types = info->id_to_types; + const std::byte *iter = metadata.ctfdata.data + header->cth_typeoff; + const std::byte *end = metadata.ctfdata.data + header->cth_stroff; + auto ctf_id_width = info->ctf_id_width; + uint64_t id; + CtfTypeParser *(*type_factory)(const std::byte *); + size_t vlen, increment; + + if (header->cth_typeoff & 3) { + std::cout << "cth_typeoff is not aligned porperly\n"; + return (false); + } + + if (header->cth_typeoff >= metadata.ctfdata.size) { + std::cout << "file is truncated or cth_typeoff is corrupt\n"; + return (false); + } + + if (header->cth_stroff >= metadata.ctfdata.size) { + std::cout << "file is truncated or cth_stroff is corrupt\n"; + return (false); + } + + if (header->cth_typeoff > header->cth_stroff) { + std::cout << "file is corrupt -- cth_typeoff > cth_stroff\n"; + return (false); + } + + uint32_t version = header->cth_version; + id = 1; + if (header->cth_parname) + id += 1ul << (header->cth_version == CTF_VERSION_2 ? + CTF_V2_PARENT_SHIFT : + CTF_V3_PARENT_SHIFT); + + type_factory = info->get_type_factory(); + + id_to_types[0] = std::make_shared(nullptr, 0, "va_arg", + info); + + for (/* */; iter < end; ++id) { + CtfTypeParser *sym = type_factory(iter); + vlen = 0; + + union { + const std::byte *ptr; + struct ctf_array_v2 *ap2; + struct ctf_array_v3 *ap3; + const struct ctf_member_v2 *mp2; + const struct ctf_member_v3 *mp3; + const struct ctf_lmember_v2 *lmp2; + const struct ctf_lmember_v3 *lmp3; + const ctf_enum_t *ep; + } u; + + increment = sym->increment(); + u.ptr = iter + increment; + + switch (sym->kind()) { + case CTF_K_INTEGER: { + uint_t encoding = *( + reinterpret_cast(u.ptr)); + vlen = sizeof(uint32_t); + id_to_types[id] = + std::make_shared(encoding, sym, id, + info->get_str_from_ref(sym->name()), info); + break; + } + + case CTF_K_FLOAT: { + uint_t encoding = *( + reinterpret_cast(u.ptr)); + vlen = sizeof(uint32_t); + id_to_types[id] = + std::make_shared(encoding, sym, id, + info->get_str_from_ref(sym->name()), info); + break; + } + + case CTF_K_POINTER: { + uint_t type = sym->type(); + id_to_types[id] = std::make_shared(type, + sym, id, info->get_str_from_ref(sym->name()), info); + break; + } + + case CTF_K_ARRAY: + id_to_types[id] = std::make_shared(u.ptr, + sym, id, info->get_str_from_ref(sym->name()), info); + if (version == CTF_VERSION_2) + vlen = sizeof(struct ctf_array_v2); + else + vlen = sizeof(struct ctf_array_v3); + break; + + case CTF_K_FUNCTION: { + uint_t ret = sym->type(); + uint_t arg = 0; + int n = sym->vlen(); + std::vector args; + + for (int i = 0; i < n; ++i, u.ptr += ctf_id_width) { + memcpy(&arg, u.ptr, ctf_id_width); + args.push_back(arg); + } + + id_to_types[id] = std::make_shared(ret, + std::move(args), sym, id, + info->get_str_from_ref(sym->name()), info); + vlen = roundup2(ctf_id_width * n, 4); + break; + } + + case CTF_K_STRUCT: { + auto [size, members] = sym->do_struct(u.ptr, + std::bind(&CtfData::get_str_from_ref, info.get(), + std::placeholders::_1)); + id_to_types[id] = std::make_shared( + sym->size(), std::move(members), sym, id, + info->get_str_from_ref(sym->name()), info); + vlen = size; + break; + } + + case CTF_K_UNION: { + auto [size, members] = sym->do_struct(u.ptr, + std::bind(&CtfData::get_str_from_ref, info.get(), + std::placeholders::_1)); + id_to_types[id] = std::make_shared( + sym->size(), std::move(members), sym, id, + info->get_str_from_ref(sym->name()), info); + vlen = size; + break; + } + + case CTF_K_ENUM: { + std::vector> vec; + int n = sym->vlen(), i; + + for (i = 0; i < n; ++i, u.ep++) + vec.push_back( + { info->get_str_from_ref(u.ep->cte_name), + u.ep->cte_value }); + + id_to_types[id] = + std::make_shared(std::move(vec), sym, + id, info->get_str_from_ref(sym->name()), info); + vlen = sizeof(ctf_enum_t) * n; + break; + } + + case CTF_K_FORWARD: + id_to_types[id] = std::make_shared(sym, + id, info->get_str_from_ref(sym->name()), info); + break; + case CTF_K_TYPEDEF: + id_to_types[id] = + std::make_shared(sym->type(), sym, + id, info->get_str_from_ref(sym->name()), info); + break; + case CTF_K_VOLATILE: + id_to_types[id] = + std::make_shared(sym->type(), sym, + id, info->get_str_from_ref(sym->name()), info); + break; + case CTF_K_CONST: + id_to_types[id] = + std::make_shared(sym->type(), sym, id, + info->get_str_from_ref(sym->name()), info); + break; + case CTF_K_RESTRICT: + id_to_types[id] = + std::make_shared(sym->type(), sym, + id, info->get_str_from_ref(sym->name()), info); + break; + case CTF_K_UNKNOWN: + id_to_types[id] = std::make_shared(sym, + id, info); + break; + default: + std::cout << "Unexpected kind: " << sym->kind() << '\n'; + return (false); + } + + iter += increment + vlen; + } + + return (true); +} + +bool +CtfData::do_parse_func(ShrCtfData info) +{ + auto &header = info->header; + auto &metadata = info->metadata; + auto &functions = info->functions; + auto ctf_id_width = info->ctf_id_width; + + const std::byte *iter = metadata.ctfdata.data + header->cth_funcoff; + const std::byte *end = metadata.ctfdata.data + header->cth_typeoff; + + std::string_view name; + + int32_t id; + int symidx; + uint_t ctf_sym_info; + + for (symidx = -1, id = 0; iter < end; ++id) { + memcpy(&ctf_sym_info, iter, ctf_id_width); + iter += ctf_id_width; + ushort_t kind = header->cth_version == CTF_VERSION_2 ? + CTF_V2_INFO_KIND(ctf_sym_info) : + CTF_V3_INFO_KIND(ctf_sym_info); + ushort_t n = header->cth_version == CTF_VERSION_2 ? + CTF_V2_INFO_VLEN(ctf_sym_info) : + CTF_V3_INFO_VLEN(ctf_sym_info); + + uint_t i, arg; + + if (metadata.strdata.data != nullptr) + name = info->find_next_symbol_with_type(symidx, + STT_FUNC); + else + name = ""; + + if (kind == CTF_K_UNKNOWN && n == 0) + continue; /* padding, skip it */ + + if (kind != CTF_K_FUNCTION) + std::cout << "incorrect type for function: " << name + << '\n'; + + if (iter + n * ctf_id_width > end) + std::cout << "function out of bound: " << name << '\n'; + + if (name != "") { + /* Return value */ + std::vector args; + memcpy(&arg, iter, ctf_id_width); + iter += ctf_id_width; + args.push_back(arg); + + for (i = 0; i < n; ++i) { + memcpy(&arg, iter, ctf_id_width); + iter += ctf_id_width; + args.push_back(arg); + } + + functions.push_back({ name, std::move(args), + static_cast(id) }); + } else + iter += n * ctf_id_width + 1; + } + + return (true); +} + +std::string_view +CtfData::get_str_from_ref(uint_t ref) +{ + size_t offset = CTF_NAME_OFFSET(ref); + + const char *s = reinterpret_cast( + metadata.ctfdata.data + header->cth_stroff + offset); + + if (CTF_NAME_STID(ref) != CTF_STRTAB_0) + return ("<< ??? - name in external strtab >>"); + + if (offset >= header->cth_strlen) + return ("<< ??? - name exceeds strlab len >>"); + + if (header->cth_stroff + offset >= metadata.ctfdata.size) + return ("<< ??? - file truncated >>"); + + if (s[0] == '\n') + return ("(anon)"); + + return (s); +} + +#define L_DIFF 0 +#define R_DIFF 1 + +template +std::pair, std::vector> +do_diff_generic(const std::vector &lhs, const std::vector &rhs, + const std::function &compare, + const std::function( + const typename T::ty_type &, int LR)> &id_to_syms) +{ + size_t l_idx = 0, r_idx = 0; + int name_diff; + std::vector l_diff, r_diff; + + while (l_idx < lhs.size() && r_idx < rhs.size()) { + name_diff = lhs[l_idx].name.compare(rhs[r_idx].name); + + if (name_diff < 0) { + auto syms = id_to_syms(lhs[l_idx].type, L_DIFF); + if (syms != std::nullopt) { + std::cout << "< [" << lhs[l_idx].id << "] " + << lhs[l_idx].name << '\n'; + l_diff.push_back( + { lhs[l_idx].name, *syms, lhs[l_idx].id }); + } + ++l_idx; + } else if (name_diff > 0) { + auto syms = id_to_syms(rhs[r_idx].type, R_DIFF); + if (syms != std::nullopt) { + std::cout << "> [" << rhs[r_idx].id << "] " + << rhs[r_idx].name << '\n'; + r_diff.push_back( + { rhs[r_idx].name, *syms, rhs[r_idx].id }); + } + ++r_idx; + } else { + auto l_syms = id_to_syms(lhs[l_idx].type, L_DIFF); + auto r_syms = id_to_syms(rhs[r_idx].type, R_DIFF); + bool sym_diff = true; + + /* + * TODO: elaborate on detailed compare diff for each + * type + */ + if (l_syms != std::nullopt && r_syms != std::nullopt) { + sym_diff = !compare(*l_syms, *r_syms); + } + + if (sym_diff) { + if (l_syms != std::nullopt) { + std::cout << "< [" << lhs[l_idx].id + << "] " << lhs[l_idx].name + << '\n'; + + l_diff.push_back({ lhs[l_idx].name, + *l_syms, lhs[l_idx].id }); + } + if (r_syms != std::nullopt) { + std::cout << "> [" << rhs[r_idx].id + << "] " << rhs[r_idx].name + << '\n'; + r_diff.push_back({ rhs[r_idx].name, + *r_syms, rhs[r_idx].id }); + } + } + ++l_idx; + ++r_idx; + } + } + + while (l_idx < lhs.size()) { + auto syms = id_to_syms(lhs[l_idx].type, L_DIFF); + if (syms != std::nullopt) { + std::cout << "< [" << lhs[l_idx].id << "] " + << lhs[l_idx].name << '\n'; + l_diff.push_back( + { lhs[l_idx].name, *syms, lhs[l_idx].id }); + } + ++l_idx; + } + + while (l_idx < lhs.size()) { + auto syms = id_to_syms(rhs[r_idx].type, R_DIFF); + if (syms != std::nullopt) { + std::cout << "< [" << rhs[r_idx].id << "] " + << rhs[r_idx].name << '\n'; + + r_diff.push_back( + { rhs[r_idx].name, *syms, rhs[r_idx].id }); + } + ++r_idx; + } + + return (std::make_pair(l_diff, r_diff)); +} + +std::pair, + std::vector> +CtfData::do_diff_func(const CtfData &rhs, + std::unordered_map &cache) const +{ + const auto &lhs = *this; + + auto get_symbol = + [&](const std::vector &ids, + int LR) -> std::optional> { + std::vector res; + const std::unordered_map *converter; + + switch (LR) { + case L_DIFF: + converter = &(lhs.id_to_types); + break; + case R_DIFF: + converter = &(rhs.id_to_types); + break; + } + + for (const auto id : ids) { + auto iter = converter->find(id); + if (iter == converter->end()) + return (std::nullopt); + res.push_back(iter->second); + } + + return (std::make_optional(res)); + }; + + auto compare = [&](const std::vector &lhs, + const std::vector &rhs) { + size_t idx = 0; + + if (lhs.size() != rhs.size()) + return (false); + + for (; idx < lhs.size(); ++idx) { + if (!lhs[idx]->compare(*rhs[idx], cache)) + return (false); + } + + return (true); + }; + + return do_diff_generic( + this->functions, rhs.functions, compare, get_symbol); +} + +std::pair, + std::vector> +CtfData::do_diff_var(const CtfData &rhs, + std::unordered_map &cache) const +{ + const auto &lhs = *this; + + auto get_symbol = [&](const uint32_t &id, + int LR) -> std::optional { + ShrCtfType res; + const std::unordered_map *converter; + + switch (LR) { + case L_DIFF: + converter = &(lhs.id_to_types); + break; + case R_DIFF: + converter = &(rhs.id_to_types); + break; + } + + auto iter = converter->find(id); + if (iter == converter->end()) + return (std::nullopt); + res = iter->second; + + return (std::make_optional(res)); + }; + + auto compare = [&](const ShrCtfType &lhs, const ShrCtfType &rhs) { + if (!lhs->compare(*rhs, cache)) + return (false); + + return (true); + }; + + return do_diff_generic( + this->static_variables, rhs.static_variables, compare, get_symbol); +} + +/* + * cache work as following: + * id_pair = lhs.id << 31 | rhs.id + * if id_pair found in map, means two types have compared + * return the result directly, compare it vice versa + */ +std::pair +CtfData::compare_and_get_diff(const CtfData &rhs) const +{ + std::unordered_map cache; + auto [l_diff_funcs, r_diff_funcs] = this->do_diff_func(rhs, cache); + auto [l_diff_syms, r_diff_syms] = this->do_diff_var(rhs, cache); + + return std::make_pair(CtfDiff { l_diff_syms, l_diff_funcs }, + CtfDiff { r_diff_syms, r_diff_funcs }); +} diff --git a/usr.bin/ctfdiff/ctfdiff.1 b/usr.bin/ctfdiff/ctfdiff.1 new file mode 100644 --- /dev/null +++ b/usr.bin/ctfdiff/ctfdiff.1 @@ -0,0 +1,69 @@ +.\" +.\" Copyright (c) 2024 The FreeBSD Foundation +.\" All rights reserved. +.\" +.\" This software was developed by Rui Paulo under sponsorship from the +.\" FreeBSD Foundation. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd December 4, 2024 +.Dt CTFDIFF 1 +.Os +.Sh NAME +.Nm ctfdiff +.Nd compare the SUNW_ctf section of two ELF files +.Sh SYNOPSIS +.Nm +.Op Fl f-ignore-const +.Fl u Ar file +file +.Sh DESCRIPTION +The +.Nm +utility comapre the contents of the CTF (Compact C Type Format) data section +(SUNW_ctf) present in two ELF binary files. +This section was previously created with +.Xr ctfconvert 1 +or +.Xr ctfmerge 1 . +.Pp +The following options are available: +.Bl -tag -width indent +.It Fl f-ignore-const +Show the statistic output by libxo +.El +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr ctfconvert 1 , +.Xr ctfmerge 1 , +.Xr ctfdump 1 , +.Xr ctf 5 +.Sh HISTORY +The +.Nm +utility first appeared in +.Fx 15.0 . +.Sh AUTHORS +ShengYi Hung +The CTF utilities came from OpenSolaris. diff --git a/usr.bin/ctfdiff/ctfdiff.cc b/usr.bin/ctfdiff/ctfdiff.cc new file mode 100644 --- /dev/null +++ b/usr.bin/ctfdiff/ctfdiff.cc @@ -0,0 +1,94 @@ +#include +#include + +#include "sys/elf_common.h" + +#include "ctfdata.hpp" +#include "metadata.hpp" +#include "utility.hpp" +#include + +static struct option longopts[] = { + { "f-ignore-const", no_argument, NULL, 'c' }, { NULL, 0, NULL, 0 } +}; + +static void +print_usage() +{ + std::cout << "ctfdiff compare the SUNW_ctf section of two ELF files\n"; + std::cout << "usage: ctfdiff \n"; + std::cout << "options:\n"; + std::cout << "-f-ignore-const: ignore const decorator"; +} + +static void +do_compare_inplace(const CtfData &lhs, const CtfData &rhs) +{ + lhs.compare_and_get_diff(rhs); +} + +int +main(int argc, char *argv[]) +{ + char *l_filename = nullptr, *r_filename = nullptr; + + (void)elf_version(EV_CURRENT); + int c = 0; + + if (argc < 0) + exit(EXIT_FAILURE); + + for (opterr = 0; optind < argc; ++optind) { + while ((c = getopt_long_only(argc, argv, "c", longopts, + NULL)) != (int)EOF) { + switch (c) { + case 'c': + flags |= F_IGNORE_CONST; + break; + } + } + + if (optind < argc) { + if (l_filename != nullptr && r_filename != nullptr) { + print_usage(); + return (0); + } else if (l_filename == nullptr) { + l_filename = argv[optind]; + } else { + r_filename = argv[optind]; + } + } + } + + if (l_filename == nullptr || r_filename == nullptr) { + print_usage(); + return (1); + } + + CtfMetaData lhs(l_filename); + + if (!lhs.is_available()) { + std::cout << "Cannot parse file " << argv[1] << '\n'; + return (1); + } + + CtfMetaData rhs(r_filename); + + if (!rhs.is_available()) { + std::cout << "Cannot parse file " << argv[1] << '\n'; + return (1); + } + + auto l_info = CtfData::create_ctf_info(std::move(lhs)); + if (l_info == nullptr) + return (1); + + auto r_info = CtfData::create_ctf_info(std::move(rhs)); + if (r_info == nullptr) + return (1); + + if ((flags & F_IGNORE_CONST) != 0) + ignore_ids.push_back(&typeid(CtfTypeConst)); + + do_compare_inplace(*l_info.get(), *r_info.get()); +} diff --git a/usr.bin/ctfdiff/ctftype.hpp b/usr.bin/ctfdiff/ctftype.hpp new file mode 100644 --- /dev/null +++ b/usr.bin/ctfdiff/ctftype.hpp @@ -0,0 +1,394 @@ +#pragma once + +#include + +#include "ctf_headers.h" +#include "sys/ctf.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct CtfDiff; +struct CtfData; + +using ShrCtfData = std::shared_ptr; + +struct ArrayEntry { + uint32_t contents, index, nelems; +}; + +struct MemberEntry { + std::string_view name; /* name of the member */ + uint32_t type_id; /* type ref of the member */ + uint64_t offset; /* offset in the member */ +}; + +struct CtfTypeParser { + /* virtual function */ + virtual ~CtfTypeParser() = default; + virtual bool is_root() const = 0; + virtual int kind() const = 0; + virtual ulong_t vlen() const = 0; + virtual uint_t name() const = 0; + virtual uint_t type() const = 0; + virtual size_t increment() const = 0; + virtual size_t size() const = 0; + virtual ArrayEntry do_array(const std::byte *bytes) const = 0; + virtual std::pair> + do_struct(const std::byte *, + const std::function &) const = 0; +}; + +struct CtfTypeParser_V2 : CtfTypeParser { + private: + struct ctf_type_v2 t; + + public: + /* virtual function */ + virtual bool is_root() const override; + virtual int kind() const override; + virtual ulong_t vlen() const override; + virtual uint_t name() const override; + virtual uint_t type() const override; + virtual size_t increment() const override; + virtual size_t size() const override; + virtual ArrayEntry do_array(const std::byte *bytes) const override; + virtual std::pair> + do_struct(const std::byte *, + const std::function &) const override; + + /* static function */ + static CtfTypeParser *create_symbol(const std::byte *data); +}; + +struct CtfTypeParser_V3 : CtfTypeParser { + private: + struct ctf_type_v3 t; + + public: + /* virtual function */ + virtual bool is_root() const override; + virtual int kind() const override; + virtual ulong_t vlen() const override; + virtual uint_t name() const override; + virtual uint_t type() const override; + virtual size_t increment() const override; + virtual size_t size() const override; + virtual ArrayEntry do_array(const std::byte *bytes) const override; + virtual std::pair> + do_struct(const std::byte *, + const std::function &) const override; + + /* static function */ + static CtfTypeParser *create_symbol(const std::byte *data); +}; + +struct CtfType { + protected: + using CompareFunc = std::function; + CtfTypeParser *parser; + std::string_view name_str; + ShrCtfData owned_ctf; + uint32_t id; + + /* virtual function */ + virtual bool do_compare_impl(const CtfType &rhs, + const CompareFunc &comp) + const = 0; /* virtual function to implement each type comparasion + function */ + + /* static function */ + static bool do_compare(const CtfType &lhs, const CtfType &rhs, + std::unordered_set &visited, + std::unordered_map + &cache); /* internal function for compare two types */ + static bool do_compare_child(const CtfType &lhs, const CtfType &rhs, + uint32_t l_child_id, uint32_t r_child_id, + std::unordered_set &visited, + std::unordered_map &cache); + + public: + /* constructor */ + CtfType(CtfTypeParser *parser, uint32_t id, + const std::string_view &name, ShrCtfData owned_ctf) + : parser(parser) + , name_str(name) + , owned_ctf(owned_ctf) + , id(id) {}; + virtual ~CtfType(); + + /* member function */ + inline const std::string_view &name() const { return name_str; } + inline ShrCtfData get_owned() const { return owned_ctf; } + bool compare(const CtfType &rhs, + std::unordered_map &cache) + const; /* compare two ctftype with type cache */ +}; + +/* + * a dummpy type for va_arg as ctf record it as id 0 + */ +struct CtfTypeVaArg : CtfType { + public: + /* virtual function */ + virtual bool do_compare_impl(const CtfType &rhs, + const CompareFunc &comp) const override; + + /* constructor */ + CtfTypeVaArg(CtfTypeParser *parser, uint32_t id, + const std::string_view &name = "", ShrCtfData owned_ctf = nullptr) + : CtfType(parser, id, name, owned_ctf) {}; + virtual ~CtfTypeVaArg() = default; +}; + +struct CtfTypePrimitive : CtfType { + protected: + /* members */ + uint32_t data; + + public: + /* virtual function */ + virtual bool do_compare_impl(const CtfType &rhs, + const CompareFunc &comp) const override; + virtual uint32_t encoding() const = 0; + virtual uint32_t offset() const = 0; + virtual uint32_t width() const = 0; + + /* constructor */ + CtfTypePrimitive(uint32_t data, CtfTypeParser *parser, uint32_t id, + const std::string_view &name = "", ShrCtfData owned_ctf = nullptr) + : CtfType(parser, id, name, owned_ctf) + , data(data) {}; + virtual ~CtfTypePrimitive() = default; +}; + +struct CtfTypeInteger : CtfTypePrimitive { + /* virtual function */ + virtual uint32_t encoding() const override; + virtual uint32_t offset() const override; + virtual uint32_t width() const override; + + /* cosntructor */ + CtfTypeInteger(uint32_t data, CtfTypeParser *parser, uint32_t id, + const std::string_view &name = "", ShrCtfData owned_ctf = nullptr) + : CtfTypePrimitive(data, parser, id, name, owned_ctf) {}; +}; + +struct CtfTypeFloat : CtfTypePrimitive { + /* virtual function */ + virtual uint32_t encoding() const override; + virtual uint32_t offset() const override; + virtual uint32_t width() const override; + + /* constructor */ + CtfTypeFloat(uint32_t data, CtfTypeParser *parser, uint32_t id, + const std::string_view &name = "", ShrCtfData owned_ctf = nullptr) + : CtfTypePrimitive(data, parser, id, name, owned_ctf) {}; +}; + +struct CtfTypeArray : CtfType { + private: + /* members */ + ArrayEntry entry; + + public: + /* virtual function */ + virtual bool do_compare_impl(const CtfType &rhs, + const CompareFunc &comp) const override; + + /* constructor */ + CtfTypeArray(const std::byte *data, CtfTypeParser *parser, uint32_t id, + const std::string_view &name = "", ShrCtfData owned_ctf = nullptr) + : CtfType(parser, id, name, owned_ctf) + , entry(parser->do_array(data)) {}; + + /* member function */ + uint32_t members() const { return entry.nelems; }; +}; + +struct CtfTypeFunc : CtfType { + protected: + /* members */ + uint32_t ret_id; + std::vector args_vec; + + public: + /* virtual function */ + virtual bool do_compare_impl(const CtfType &rhs, + const CompareFunc &comp) const override; + + /* constructor */ + CtfTypeFunc(uint32_t ret_id, std::vector &&args_vec, + CtfTypeParser *parser, uint32_t id, + const std::string_view &name = "", ShrCtfData owned_ctf = nullptr) + : CtfType(parser, id, name, owned_ctf) + , ret_id(ret_id) + , args_vec(args_vec) {}; + + /* member function */ + const std::vector &args() const { return args_vec; }; + uint32_t ret() const { return ret_id; }; +}; + +struct CtfTypeEnum : CtfType { + private: + /* member */ + std::vector> members; + + public: + /* virtual function */ + virtual bool do_compare_impl(const CtfType &rhs, + const CompareFunc &comp) const override; + + /* constructor */ + CtfTypeEnum( + std::vector> &&members, + CtfTypeParser *parser, uint32_t id, + const std::string_view &name = "", ShrCtfData owned_ctf = nullptr) + : CtfType(parser, id, name, owned_ctf) + , members(members) {}; +}; + +struct CtfTypeForward : CtfType { + public: + /* virtual function */ + virtual bool do_compare_impl(const CtfType &rhs, + const CompareFunc &comp) const override; + + /* constructor */ + CtfTypeForward(CtfTypeParser *parser, uint32_t id, + const std::string_view &name = "", ShrCtfData owned_ctf = nullptr) + : CtfType(parser, id, name, owned_ctf) {}; +}; + +struct CtfTypeQualifier : CtfType { + protected: + /* members */ + uint32_t ref_id; + + public: + /* virtual function */ + virtual bool do_compare_impl(const CtfType &rhs, + const CompareFunc &comp) const override; + + /* constructor */ + CtfTypeQualifier(uint32_t ref_id, CtfTypeParser *parser, uint32_t id, + const std::string_view &name = "", ShrCtfData owned_ctf = nullptr) + : CtfType(parser, id, name, owned_ctf) + , ref_id(ref_id) {}; + + virtual ~CtfTypeQualifier() = default; + + /* member function */ + uint32_t ref() const { return this->ref_id; } +}; + +struct CtfTypePtr : CtfTypeQualifier { + public: + /* constructor */ + CtfTypePtr(uint32_t ref_id, CtfTypeParser *parser, uint32_t id, + const std::string_view &name = "", ShrCtfData owned_ctf = nullptr) + : CtfTypeQualifier(ref_id, parser, id, name, owned_ctf) {}; +}; + +struct CtfTypeTypeDef : CtfTypeQualifier { + public: + /* constructor */ + CtfTypeTypeDef(uint32_t ref_id, CtfTypeParser *parser, uint32_t id, + const std::string_view &name = "", ShrCtfData owned_ctf = nullptr) + : CtfTypeQualifier(ref_id, parser, id, name, owned_ctf) {}; +}; + +struct CtfTypeVolatile : CtfTypeQualifier { + public: + /* constructor */ + CtfTypeVolatile(uint32_t ref_id, CtfTypeParser *parser, uint32_t id, + const std::string_view &name = "", ShrCtfData owned_ctf = nullptr) + : CtfTypeQualifier(ref_id, parser, id, name, owned_ctf) {}; +}; + +struct CtfTypeConst : CtfTypeQualifier { + public: + /* constructor */ + CtfTypeConst(uint32_t ref_id, CtfTypeParser *parser, uint32_t id, + const std::string_view &name = "", ShrCtfData owned_ctf = nullptr) + : CtfTypeQualifier(ref_id, parser, id, name, owned_ctf) {}; +}; + +struct CtfTypeRestrict : CtfTypeQualifier { + public: + /* constructor */ + CtfTypeRestrict(uint32_t ref_id, CtfTypeParser *parser, uint32_t id, + const std::string_view &name = "", ShrCtfData owned_ctf = nullptr) + : CtfTypeQualifier(ref_id, parser, id, name, owned_ctf) {}; +}; + +struct CtfTypeUnknown : CtfType { + public: + /* virtual function */ + virtual bool do_compare_impl(const CtfType &rhs, + const CompareFunc &comp) const override; + + /* constructor */ + CtfTypeUnknown(CtfTypeParser *parser, uint32_t id, + ShrCtfData owned_ctf = nullptr) + : CtfType(parser, id, "", owned_ctf) {}; +}; + +struct CtfTypeComplex : CtfType { + protected: + /* members */ + uint32_t size; + std::vector args; + + public: + /* constructor */ + CtfTypeComplex(uint32_t size, std::vector &&args, + CtfTypeParser *parser, uint32_t id, + const std::string_view &name = "", ShrCtfData owned_ctf = nullptr) + : CtfType(parser, id, name, owned_ctf) + , size(size) + , args(args) {}; + virtual ~CtfTypeComplex() = default; +}; + +struct CtfTypeStruct : CtfTypeComplex { + public: + /* virtual function */ + virtual bool do_compare_impl(const CtfType &rhs, + const CompareFunc &comp) const override; + + /* constructor */ + CtfTypeStruct(uint32_t size, std::vector &&args, + CtfTypeParser *parser, uint32_t id, + const std::string_view &name = "", ShrCtfData owned_ctf = nullptr) + : CtfTypeComplex(size, + std::forward &&>(args), parser, id, + name, owned_ctf) {}; +}; + +struct CtfTypeUnion : CtfTypeComplex { + public: + /* virtual function */ + virtual bool do_compare_impl(const CtfType &rhs, + const CompareFunc &comp) const override; + + /* constructor */ + CtfTypeUnion(uint32_t size, std::vector &&args, + CtfTypeParser *parser, uint32_t id, + const std::string_view &name = "", ShrCtfData owned_ctf = nullptr) + : CtfTypeComplex(size, + std::forward &&>(args), parser, id, + name, owned_ctf) {}; +}; + +using CtfTypeFactory = CtfTypeParser *(*)(const std::byte *); +using ShrCtfType = std::shared_ptr; diff --git a/usr.bin/ctfdiff/ctftype.cc b/usr.bin/ctfdiff/ctftype.cc new file mode 100644 --- /dev/null +++ b/usr.bin/ctfdiff/ctftype.cc @@ -0,0 +1,476 @@ +#include +#include + +#include "sys/bio.h" +#include "sys/ctf.h" +#include "sys/sx.h" +#include "sys/systm.h" + +#include "ctfdata.hpp" +#include "ctftype.hpp" +#include "utility.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +bool +CtfTypeParser_V2::is_root() const +{ + return (CTF_V2_INFO_ISROOT(t.ctt_info)); +} + +int +CtfTypeParser_V2::kind() const +{ + return (CTF_V2_INFO_KIND(t.ctt_info)); +} + +ulong_t +CtfTypeParser_V2::vlen() const +{ + return (CTF_V2_INFO_VLEN(t.ctt_info)); +} + +uint_t +CtfTypeParser_V2::name() const +{ + return (t.ctt_name); +} + +uint_t +CtfTypeParser_V2::type() const +{ + return (t.ctt_type); +} + +size_t +CtfTypeParser_V2::increment() const +{ + if (t.ctt_size == CTF_V2_LSIZE_SENT) { + return (sizeof(ctf_type_v2)); + } else { + return (sizeof(ctf_stype_v2)); + } +} + +size_t +CtfTypeParser_V2::size() const +{ + if (t.ctt_size == CTF_V2_LSIZE_SENT) { + return (sizeof(ctf_type_v2)); + } else { + return (sizeof(ctf_stype_v2)); + } +} + +ArrayEntry +CtfTypeParser_V2::do_array(const std::byte *bytes) const +{ + const ctf_array_v2 *arr = reinterpret_cast(bytes); + return { arr->cta_contents, arr->cta_index, arr->cta_nelems }; +} + +std::pair> +CtfTypeParser_V2::do_struct(const std::byte *bytes, + const std::function &get_str_by_ref) const +{ + std::vector res; + int n = vlen(), i; + size_t size; + if (this->size() >= CTF_V2_LSTRUCT_THRESH) { + const ctf_lmember_v2 *iter = + reinterpret_cast(bytes); + for (i = 0; i < n; ++i, ++iter) + res.push_back({ get_str_by_ref(iter->ctlm_name), + iter->ctlm_type, CTF_LMEM_OFFSET(iter) }); + size = n * sizeof(struct ctf_lmember_v2); + } else { + const ctf_member_v2 *iter = + reinterpret_cast(bytes); + for (i = 0; i < n; ++i, ++iter) + res.push_back({ get_str_by_ref(iter->ctm_name), + iter->ctm_type, iter->ctm_offset }); + size = n * sizeof(struct ctf_member_v2); + } + + return { size, res }; +} + +CtfTypeParser * +CtfTypeParser_V2::create_symbol(const std::byte *data) +{ + CtfTypeParser_V2 *res = new CtfTypeParser_V2; + memcpy(&res->t, data, sizeof(res->t)); + return (res); +} + +bool +CtfTypeParser_V3::is_root() const +{ + return (CTF_V3_INFO_ISROOT(t.ctt_info)); +} + +int +CtfTypeParser_V3::kind() const +{ + return (CTF_V3_INFO_KIND(t.ctt_info)); +} + +ulong_t +CtfTypeParser_V3::vlen() const +{ + return (CTF_V3_INFO_VLEN(t.ctt_info)); +} + +uint_t +CtfTypeParser_V3::name() const +{ + return (t.ctt_name); +} + +uint_t +CtfTypeParser_V3::type() const +{ + return (t.ctt_type); +} + +size_t +CtfTypeParser_V3::increment() const +{ + if (t.ctt_size == CTF_V3_LSIZE_SENT) { + return (sizeof(ctf_type_v3)); + } else { + return (sizeof(ctf_stype_v3)); + } +} + +size_t +CtfTypeParser_V3::size() const +{ + if (t.ctt_size == CTF_V3_LSIZE_SENT) { + return (sizeof(ctf_type_v3)); + } else { + return (sizeof(ctf_stype_v3)); + } +} + +ArrayEntry +CtfTypeParser_V3::do_array(const std::byte *bytes) const +{ + const ctf_array_v3 *arr = reinterpret_cast(bytes); + return { arr->cta_contents, arr->cta_index, arr->cta_nelems }; +} + +std::pair> +CtfTypeParser_V3::do_struct(const std::byte *bytes, + const std::function &get_str_by_ref) const +{ + std::vector res; + int n = vlen(), i; + size_t size; + if (this->size() >= CTF_V3_LSTRUCT_THRESH) { + const ctf_lmember_v3 *iter = + reinterpret_cast(bytes); + for (i = 0; i < n; ++i, ++iter) + res.push_back({ get_str_by_ref(iter->ctlm_name), + iter->ctlm_type, CTF_LMEM_OFFSET(iter) }); + + size = n * sizeof(ctf_lmember_v3); + } else { + const ctf_member_v3 *iter = + reinterpret_cast(bytes); + for (i = 0; i < n; ++i, ++iter) + res.push_back({ get_str_by_ref(iter->ctm_name), + iter->ctm_type, iter->ctm_offset }); + + size = n * sizeof(ctf_member_v3); + } + + return { size, res }; +} + +CtfTypeParser * +CtfTypeParser_V3::create_symbol(const std::byte *data) +{ + CtfTypeParser_V3 *res = new CtfTypeParser_V3; + memcpy(&res->t, data, sizeof(res->t)); + return (res); +} + +bool +CtfType::compare(const CtfType &rhs, + std::unordered_map &cache) const +{ + std::unordered_set visited; + + return do_compare_child(*this, rhs, this->id, rhs.id, visited, cache); +} + +bool +CtfType::do_compare(const CtfType &_lhs, const CtfType &_rhs, + std::unordered_set &visited, + std::unordered_map &cache) +{ + const CtfType *lhs = &_lhs; + const CtfType *rhs = &_rhs; + + auto ignored = [&](const auto &id) { + return (std::find(ignore_ids.begin(), ignore_ids.end(), id) != + ignore_ids.end()); + }; + + /* we don't compare the type in ignore list */ + while (ignored(&typeid(*lhs))) { + const CtfTypeQualifier *t = + dynamic_cast(lhs); + lhs = + lhs->get_owned()->id_mapper().find(t->ref())->second.get(); + } + + while (ignored(&typeid(*rhs))) { + const CtfTypeQualifier *t = + dynamic_cast(rhs); + rhs = + rhs->get_owned()->id_mapper().find(t->ref())->second.get(); + } + + /* it guarentee all type should be same, so we can cast to specified + * cast in each do_compare_impl */ + if (typeid(*lhs) != typeid(*rhs)) + return (false); + + /* A type can be mutual refernce so that it will create a circle in the + * graph */ + + uint64_t visited_pair = lhs->id << 31 | rhs->id; + + bool is_visited = visited.find(visited_pair) != visited.end(); + + if (is_visited) + return (true); + + if (cache.find(visited_pair) != cache.end()) + return (cache[visited_pair]); + + visited.insert(visited_pair); + bool comp_res = (lhs->do_compare_impl(*rhs, + std::bind(CtfType::do_compare_child, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, + std::placeholders::_4, std::ref(visited), std::ref(cache)))); + + cache[visited_pair] = comp_res; + visited.erase(visited_pair); + + return (comp_res); +} + +bool +CtfType::do_compare_child(const CtfType &lhs, const CtfType &rhs, + uint32_t l_child_id, uint32_t r_child_id, + std::unordered_set &visited, + std::unordered_map &cache) +{ + auto &l_id_map = lhs.get_owned()->id_mapper(); + auto &r_id_map = rhs.get_owned()->id_mapper(); + auto l_child_iter = l_id_map.find(l_child_id); + auto r_child_iter = r_id_map.find(r_child_id); + + /* In CTF, va_args record the ... as type 0, which is not contained in + * the id_table */ + if (l_child_iter == l_id_map.end() || r_child_iter == r_id_map.end()) + return (false); + + return do_compare(*(l_child_iter->second), *(r_child_iter->second), + visited, cache); +} + +CtfType::~CtfType() +{ + delete this->parser; +} + +uint32_t +CtfTypeInteger::encoding() const +{ + return (CTF_INT_ENCODING(this->data)); +} + +uint32_t +CtfTypeInteger::offset() const +{ + return (CTF_INT_OFFSET(this->data)); +} + +uint32_t +CtfTypeInteger::width() const +{ + return (CTF_INT_BITS(this->data)); +} + +uint32_t +CtfTypeFloat::encoding() const +{ + return (CTF_FP_ENCODING(this->data)); +} + +uint32_t +CtfTypeFloat::offset() const +{ + return (CTF_FP_OFFSET(this->data)); +} + +uint32_t +CtfTypeFloat::width() const +{ + return (CTF_FP_BITS(this->data)); +} + +bool +CtfTypeVaArg::do_compare_impl(const CtfType &rhs __unused, + const CompareFunc &comp __unused) const +{ + return (true); +} + +bool +CtfTypePrimitive::do_compare_impl(const CtfType &rhs, + const CompareFunc &comp __unused) const +{ + const CtfTypePrimitive *d = dynamic_cast( + &rhs); + return (this->data == d->data); +} + +bool +CtfTypeArray::do_compare_impl(const CtfType &rhs, const CompareFunc &comp) const +{ + const CtfTypeArray *d = dynamic_cast(&rhs); + const ArrayEntry &l_ent = this->entry, &r_ent = d->entry; + + return (l_ent.nelems == r_ent.nelems && + comp(*this, rhs, l_ent.index, r_ent.index) && + comp(*this, rhs, l_ent.contents, r_ent.contents)); +} + +bool +CtfTypeFunc::do_compare_impl(const CtfType &rhs, const CompareFunc &comp) const +{ + const CtfTypeFunc *d = dynamic_cast(&rhs); + + if (this->args_vec.size() != d->args_vec.size()) + return (false); + + if (!comp(*this, rhs, this->ret_id, d->ret_id)) + return (false); + + int n = d->args_vec.size(); + + for (int i = 0; i < n; ++i) { + if (!comp(*this, rhs, this->args_vec[i], d->args_vec[i])) + return (false); + } + + return (true); +} + +bool +CtfTypeEnum::do_compare_impl(const CtfType &rhs, + const CompareFunc &comp __unused) const +{ + const CtfTypeEnum *d = dynamic_cast(&rhs); + + if (this->members.size() != d->members.size()) + return (false); + + int n = d->members.size(); + + for (int i = 0; i < n; ++i) { + if (this->members[i].first != d->members[i].first || + this->members[i].second != d->members[i].second) + return (false); + } + + return (true); +} + +bool +CtfTypeForward::do_compare_impl(const CtfType &rhs, + const CompareFunc &comp __unused) const +{ + return (this->name() == rhs.name()); +} + +bool +CtfTypeQualifier::do_compare_impl(const CtfType &rhs, + const CompareFunc &comp) const +{ + const CtfTypeQualifier *d = dynamic_cast( + &rhs); + + return (comp(*this, rhs, this->ref_id, d->ref_id)); +} + +bool +CtfTypeUnknown::do_compare_impl(const CtfType &rhs __unused, + const CompareFunc &comp __unused) const +{ + /* TODO: add unknown checker */ + return (false); +} + +bool +CtfTypeStruct::do_compare_impl(const CtfType &rhs, + const CompareFunc &comp) const +{ + const CtfTypeStruct *d = dynamic_cast(&rhs); + const auto &l_memb = this->args, &r_memb = d->args; + + if (this->size != d->size) + return (false); + + if (l_memb.size() != r_memb.size()) + return (false); + + int n = l_memb.size(), i; + + for (i = 0; i < n; ++i) { + if (l_memb[i].offset != r_memb[i].offset) + return (false); + + if (!comp(*this, rhs, l_memb[i].type_id, r_memb[i].type_id)) + return (false); + } + + return (true); +} + +bool +CtfTypeUnion::do_compare_impl(const CtfType &rhs, const CompareFunc &comp) const +{ + const CtfTypeUnion *d = dynamic_cast(&rhs); + const auto &l_memb = this->args, &r_memb = d->args; + + if (this->size != d->size) + return (false); + + if (l_memb.size() != r_memb.size()) + return (false); + + int n = l_memb.size(), i; + + for (i = 0; i < n; ++i) { + if (l_memb[i].offset != r_memb[i].offset) + return (false); + + if (!comp(*this, rhs, l_memb[i].type_id, r_memb[i].type_id)) + return (false); + } + + return (true); +} diff --git a/usr.bin/ctfdiff/metadata.hpp b/usr.bin/ctfdiff/metadata.hpp new file mode 100644 --- /dev/null +++ b/usr.bin/ctfdiff/metadata.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include "utility.hpp" +#include +#include + +struct CtfMetaData { + private: + int data_fd; + std::string filename; + Elf *elf; + + bool from_elf_file(); + bool from_raw_file(); + + public: + Buffer ctfdata{}; + Buffer symdata{}; + Buffer strdata{}; + + CtfMetaData(const std::string &filename); + ~CtfMetaData(); + + std::string_view file_name() { return this->filename; } + bool is_available(); +}; diff --git a/usr.bin/ctfdiff/metadata.cc b/usr.bin/ctfdiff/metadata.cc new file mode 100644 --- /dev/null +++ b/usr.bin/ctfdiff/metadata.cc @@ -0,0 +1,148 @@ +#include +#include + +#include +#include +#include +#include + +#include "ctfdata.hpp" +#include "metadata.hpp" +#include +#include + +static Elf_Scn * +find_section_by_name(Elf *elf, GElf_Ehdr *ehdr, const std::string &sec_name) +{ + GElf_Shdr shdr; + Elf_Scn *scn = NULL; + const char *name; + +#define NEXTSCN(elf, scn) elf_nextscn(elf, scn) + for (scn = NEXTSCN(elf, scn); scn != NULL; scn = NEXTSCN(elf, scn)) { + if (gelf_getshdr(scn, &shdr) != NULL && + (name = elf_strptr(elf, ehdr->e_shstrndx, shdr.sh_name)) && + sec_name == name) { + return (scn); + } + } +#undef NEXTSCN + + return (nullptr); +} + +bool +CtfMetaData::from_elf_file() +{ + static constexpr char ctfscn_name[] = ".SUNW_ctf"; + static constexpr char symscn_name[] = ".symtab"; + GElf_Ehdr ehdr; + GElf_Shdr ctfshdr; + + if ((this->elf = elf_begin(this->data_fd, ELF_C_READ, NULL)) == NULL || + gelf_getehdr(elf, &ehdr) == NULL) { + return (false); + } + + Elf_Scn *ctfscn = find_section_by_name(this->elf, &ehdr, ctfscn_name); + Elf_Data *ctfscn_data; + + if (ctfscn == NULL || + (ctfscn_data = elf_getdata(ctfscn, NULL)) == NULL) { + std::cout << "Cannot find " << ctfscn_name + << " in file: " << this->filename << '\n'; + return (false); + } + + this->ctfdata = Buffer(static_cast(ctfscn_data->d_buf), + ctfscn_data->d_size); + + Elf_Scn *symscn; + + if (gelf_getshdr(ctfscn, &ctfshdr) != NULL && ctfshdr.sh_link != 0) + symscn = elf_getscn(elf, ctfshdr.sh_link); + else + symscn = find_section_by_name(elf, &ehdr, symscn_name); + + if (symscn != NULL) { + GElf_Shdr symshdr; + Elf_Data *symsecdata; + Elf_Data *symstrdata; + Elf_Scn *symstrscn; + + if (gelf_getshdr(symscn, &symshdr) != NULL) { + symstrscn = elf_getscn(elf, symshdr.sh_link); + symsecdata = elf_getdata(symscn, NULL); + symstrdata = elf_getdata(symstrscn, NULL); + + this->symdata = Buffer(static_cast( + symsecdata->d_buf), + symsecdata->d_size, + symshdr.sh_size / symshdr.sh_entsize); + this->strdata = Buffer(static_cast( + symstrdata->d_buf), + symstrdata->d_size); + this->symdata.elfdata = symsecdata; + this->strdata.elfdata = symstrdata; + } + } + + return (true); +} + +bool +CtfMetaData::from_raw_file() +{ + struct stat st; + std::byte *bytes; + + if (fstat(this->data_fd, &st) == -1) { + std::cout << "Failed to do fstat\n"; + close(this->data_fd); + return (false); + } + + bytes = static_cast( + mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, this->data_fd, 0)); + + if (bytes == MAP_FAILED) { + std::cout << "Failed to do mmap\n"; + close(this->data_fd); + return (false); + } + + this->ctfdata = Buffer(bytes, st.st_size); + return (true); +} + +CtfMetaData::CtfMetaData(const std::string &filename) + : filename(filename) +{ + this->data_fd = open(filename.c_str(), O_RDONLY); + this->elf = nullptr; + + if (this->data_fd == -1) { + return; + } + + if (!this->from_elf_file()) { + if (!this->from_raw_file()) { + close(this->data_fd); + this->data_fd = -1; + } + } +} + +bool +CtfMetaData::is_available() +{ + return (this->data_fd != -1); +} + +CtfMetaData::~CtfMetaData() +{ + if (this->elf) + elf_end(this->elf); + if (this->data_fd != -1) + close(this->data_fd); +} diff --git a/usr.bin/ctfdiff/utility.hpp b/usr.bin/ctfdiff/utility.hpp new file mode 100644 --- /dev/null +++ b/usr.bin/ctfdiff/utility.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include +#include +#include + +extern int flags; +extern std::vector ignore_ids; + +enum CtfFlag { + F_IGNORE_CONST = 1, +}; + +struct Buffer { + std::byte *data; + size_t size; + size_t entries; + Elf_Data *elfdata; + + Buffer(std::byte *data, size_t size) + : data(data) + , size(size) + , entries(0) + , elfdata(nullptr) {}; + Buffer(std::byte *data, size_t size, size_t entries) + : data(data) + , size(size) + , entries(entries) + , elfdata(nullptr) {}; + + Buffer() = default; +}; diff --git a/usr.bin/ctfdiff/utility.cc b/usr.bin/ctfdiff/utility.cc new file mode 100644 --- /dev/null +++ b/usr.bin/ctfdiff/utility.cc @@ -0,0 +1,7 @@ +#include "ctftype.hpp" +#include "utility.hpp" +#include +#include + +int flags = 0; +std::vector ignore_ids = { &typeid(CtfTypeTypeDef) };