Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F107031325
D48199.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
55 KB
Referenced Files
None
Subscribers
None
D48199.diff
View Options
diff --git a/usr.bin/Makefile b/usr.bin/Makefile
--- a/usr.bin/Makefile
+++ b/usr.bin/Makefile
@@ -26,6 +26,7 @@
comm \
compress \
csplit \
+ ctfdiff \
ctlstat \
cut \
diff \
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 <Makefile.inc>
+
+.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 <bsd.prog.mk>
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 <sys/types.h>
+
+#include <gelf.h>
+#include <stdint.h>
+
+#include "ctf_headers.h"
+#include "sys/ctf.h"
+
+#include "ctftype.hpp"
+#include "metadata.hpp"
+#include <cstdint>
+#include <memory>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+struct CtfDiff;
+struct CtfData;
+struct CtfType;
+
+using ShrCtfData = std::shared_ptr<CtfData>;
+using ShrCtfType = std::shared_ptr<CtfType>;
+
+struct CtfData {
+ public:
+ /* typedef */
+ template <typename T> 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<std::vector<ShrCtfType>>;
+ using CtfFuncIdEntry = CtfObjEntry<std::vector<uint32_t>>;
+ using CtfVarTypeEntry = CtfObjEntry<ShrCtfType>;
+ using CtfVarIdEntry = CtfObjEntry<uint32_t>;
+
+ private:
+ /* members */
+ CtfMetaData metadata;
+ size_t ctf_id_width;
+ ctf_header_t *header;
+ std::unordered_map<uint32_t, ShrCtfType> id_to_types;
+ std::vector<CtfVarIdEntry> static_variables;
+ std::vector<CtfFuncIdEntry> 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<CtfFuncTypeEntry>, std::vector<CtfFuncTypeEntry>>
+ do_diff_func(const CtfData &rhs,
+ std::unordered_map<uint64_t, bool> &cache) const;
+ std::pair<std::vector<CtfVarTypeEntry>, std::vector<CtfVarTypeEntry>>
+ do_diff_var(const CtfData &rhs,
+ std::unordered_map<uint64_t, bool> &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<CtfDiff, CtfDiff> compare_and_get_diff(
+ const CtfData &rhs) const;
+
+ bool is_available();
+ inline const std::unordered_map<uint32_t, ShrCtfType> &id_mapper() const
+ {
+ return id_to_types;
+ }
+
+ static std::shared_ptr<CtfData> create_ctf_info(CtfMetaData &&metadata);
+};
+
+struct CtfDiff {
+ std::vector<CtfData::CtfVarTypeEntry> variables;
+ std::vector<CtfData::CtfFuncTypeEntry> 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 <sys/types.h>
+#include <sys/param.h>
+#include <sys/sysmacros.h>
+
+#include <gelf.h>
+#include <libelf.h>
+#include <skein_port.h>
+#include <stdint.h>
+#include <strings.h>
+#include <unistd.h>
+#include <zconf.h>
+#include <zlib.h>
+
+#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 <cstddef>
+#include <cstdint>
+#include <functional>
+#include <iostream>
+#include <memory>
+#include <optional>
+#include <string_view>
+#include <type_traits>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+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<Bytef *>(metadata.ctfdata.data);
+ zs.avail_in = metadata.ctfdata.size;
+ zs.next_out = reinterpret_cast<Bytef *>(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_preamble_t *>(
+ 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_header_t *>(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>
+CtfData::create_ctf_info(CtfMetaData &&metadata)
+{
+ auto res = std::shared_ptr<CtfData>(
+ new CtfData(std::forward<CtfMetaData &&>(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<uint32_t>(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<CtfTypeVaArg>(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<const uint_t *>(u.ptr));
+ vlen = sizeof(uint32_t);
+ id_to_types[id] =
+ std::make_shared<CtfTypeInteger>(encoding, sym, id,
+ info->get_str_from_ref(sym->name()), info);
+ break;
+ }
+
+ case CTF_K_FLOAT: {
+ uint_t encoding = *(
+ reinterpret_cast<const uint_t *>(u.ptr));
+ vlen = sizeof(uint32_t);
+ id_to_types[id] =
+ std::make_shared<CtfTypeFloat>(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<CtfTypePtr>(type,
+ sym, id, info->get_str_from_ref(sym->name()), info);
+ break;
+ }
+
+ case CTF_K_ARRAY:
+ id_to_types[id] = std::make_shared<CtfTypeArray>(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<uint_t> 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<CtfTypeFunc>(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<CtfTypeStruct>(
+ 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<CtfTypeUnion>(
+ sym->size(), std::move(members), sym, id,
+ info->get_str_from_ref(sym->name()), info);
+ vlen = size;
+ break;
+ }
+
+ case CTF_K_ENUM: {
+ std::vector<std::pair<std::string_view, uint32_t>> 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<CtfTypeEnum>(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<CtfTypeForward>(sym,
+ id, info->get_str_from_ref(sym->name()), info);
+ break;
+ case CTF_K_TYPEDEF:
+ id_to_types[id] =
+ std::make_shared<CtfTypeTypeDef>(sym->type(), sym,
+ id, info->get_str_from_ref(sym->name()), info);
+ break;
+ case CTF_K_VOLATILE:
+ id_to_types[id] =
+ std::make_shared<CtfTypeVolatile>(sym->type(), sym,
+ id, info->get_str_from_ref(sym->name()), info);
+ break;
+ case CTF_K_CONST:
+ id_to_types[id] =
+ std::make_shared<CtfTypeConst>(sym->type(), sym, id,
+ info->get_str_from_ref(sym->name()), info);
+ break;
+ case CTF_K_RESTRICT:
+ id_to_types[id] =
+ std::make_shared<CtfTypeRestrict>(sym->type(), sym,
+ id, info->get_str_from_ref(sym->name()), info);
+ break;
+ case CTF_K_UNKNOWN:
+ id_to_types[id] = std::make_shared<CtfTypeUnknown>(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<uint_t> 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<uint32_t>(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<const char *>(
+ 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 <typename Ret, typename T>
+std::pair<std::vector<Ret>, std::vector<Ret>>
+do_diff_generic(const std::vector<T> &lhs, const std::vector<T> &rhs,
+ const std::function<bool(const typename Ret::ty_type &,
+ const typename Ret::ty_type &)> &compare,
+ const std::function<std::optional<typename Ret::ty_type>(
+ const typename T::ty_type &, int LR)> &id_to_syms)
+{
+ size_t l_idx = 0, r_idx = 0;
+ int name_diff;
+ std::vector<Ret> 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::CtfFuncTypeEntry>,
+ std::vector<CtfData::CtfFuncTypeEntry>>
+CtfData::do_diff_func(const CtfData &rhs,
+ std::unordered_map<uint64_t, bool> &cache) const
+{
+ const auto &lhs = *this;
+
+ auto get_symbol =
+ [&](const std::vector<uint32_t> &ids,
+ int LR) -> std::optional<std::vector<ShrCtfType>> {
+ std::vector<ShrCtfType> res;
+ const std::unordered_map<uint32_t, ShrCtfType> *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<ShrCtfType> &lhs,
+ const std::vector<ShrCtfType> &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<CtfFuncTypeEntry, CtfFuncIdEntry>(
+ this->functions, rhs.functions, compare, get_symbol);
+}
+
+std::pair<std::vector<CtfData::CtfVarTypeEntry>,
+ std::vector<CtfData::CtfVarTypeEntry>>
+CtfData::do_diff_var(const CtfData &rhs,
+ std::unordered_map<uint64_t, bool> &cache) const
+{
+ const auto &lhs = *this;
+
+ auto get_symbol = [&](const uint32_t &id,
+ int LR) -> std::optional<ShrCtfType> {
+ ShrCtfType res;
+ const std::unordered_map<uint32_t, ShrCtfType> *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<CtfVarTypeEntry, CtfVarIdEntry>(
+ 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<CtfDiff, CtfDiff>
+CtfData::compare_and_get_diff(const CtfData &rhs) const
+{
+ std::unordered_map<uint64_t, bool> 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,68 @@
+.\"-
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.\" Copyright (c) 2024 ShengYi Hung <aokblast@FreeBSD.org>
+.\"
+.\" 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 25, 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 <aokblast@FreeBSD.org>
+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 <getopt.h>
+#include <libelf.h>
+
+#include "sys/elf_common.h"
+
+#include "ctfdata.hpp"
+#include "metadata.hpp"
+#include "utility.hpp"
+#include <iostream>
+
+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 <options> <file1> <file2>\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 <sys/cdefs.h>
+
+#include "ctf_headers.h"
+#include "sys/ctf.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <functional>
+#include <memory>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+struct CtfDiff;
+struct CtfData;
+
+using ShrCtfData = std::shared_ptr<CtfData>;
+
+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<size_t, std::vector<MemberEntry>>
+ do_struct(const std::byte *,
+ const std::function<std::string_view(uint)> &) 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<size_t, std::vector<MemberEntry>>
+ do_struct(const std::byte *,
+ const std::function<std::string_view(uint)> &) 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<size_t, std::vector<MemberEntry>>
+ do_struct(const std::byte *,
+ const std::function<std::string_view(uint)> &) const override;
+
+ /* static function */
+ static CtfTypeParser *create_symbol(const std::byte *data);
+};
+
+struct CtfType {
+ protected:
+ using CompareFunc = std::function<bool(const CtfType &lhs,
+ const CtfType &rhs, uint32_t l_child_id, uint32_t r_child_id)>;
+ 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<uint64_t> &visited,
+ std::unordered_map<uint64_t, bool>
+ &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<uint64_t> &visited,
+ std::unordered_map<uint64_t, bool> &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<uint64_t, bool> &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<uint32_t> 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<uint32_t> &&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<uint32_t> &args() const { return args_vec; };
+ uint32_t ret() const { return ret_id; };
+};
+
+struct CtfTypeEnum : CtfType {
+ private:
+ /* member */
+ std::vector<std::pair<std::string_view, uint32_t>> members;
+
+ public:
+ /* virtual function */
+ virtual bool do_compare_impl(const CtfType &rhs,
+ const CompareFunc &comp) const override;
+
+ /* constructor */
+ CtfTypeEnum(
+ std::vector<std::pair<std::string_view, uint32_t>> &&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<MemberEntry> args;
+
+ public:
+ /* constructor */
+ CtfTypeComplex(uint32_t size, std::vector<MemberEntry> &&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<MemberEntry> &&args,
+ CtfTypeParser *parser, uint32_t id,
+ const std::string_view &name = "", ShrCtfData owned_ctf = nullptr)
+ : CtfTypeComplex(size,
+ std::forward<std::vector<MemberEntry> &&>(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<MemberEntry> &&args,
+ CtfTypeParser *parser, uint32_t id,
+ const std::string_view &name = "", ShrCtfData owned_ctf = nullptr)
+ : CtfTypeComplex(size,
+ std::forward<std::vector<MemberEntry> &&>(args), parser, id,
+ name, owned_ctf) {};
+};
+
+using CtfTypeFactory = CtfTypeParser *(*)(const std::byte *);
+using ShrCtfType = std::shared_ptr<CtfType>;
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 <sys/cdefs.h>
+#include <sys/types.h>
+
+#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 <cstddef>
+#include <cstdint>
+#include <functional>
+#include <iostream>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+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<const ctf_array_v2 *>(bytes);
+ return { arr->cta_contents, arr->cta_index, arr->cta_nelems };
+}
+
+std::pair<size_t, std::vector<MemberEntry>>
+CtfTypeParser_V2::do_struct(const std::byte *bytes,
+ const std::function<std::string_view(uint)> &get_str_by_ref) const
+{
+ std::vector<MemberEntry> res;
+ int n = vlen(), i;
+ size_t size;
+ if (this->size() >= CTF_V2_LSTRUCT_THRESH) {
+ const ctf_lmember_v2 *iter =
+ reinterpret_cast<const ctf_lmember_v2 *>(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<const ctf_member_v2 *>(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<const ctf_array_v3 *>(bytes);
+ return { arr->cta_contents, arr->cta_index, arr->cta_nelems };
+}
+
+std::pair<size_t, std::vector<MemberEntry>>
+CtfTypeParser_V3::do_struct(const std::byte *bytes,
+ const std::function<std::string_view(uint)> &get_str_by_ref) const
+{
+ std::vector<MemberEntry> res;
+ int n = vlen(), i;
+ size_t size;
+ if (this->size() >= CTF_V3_LSTRUCT_THRESH) {
+ const ctf_lmember_v3 *iter =
+ reinterpret_cast<const ctf_lmember_v3 *>(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<const ctf_member_v3 *>(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<uint64_t, bool> &cache) const
+{
+ std::unordered_set<uint64_t> 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<uint64_t> &visited,
+ std::unordered_map<uint64_t, bool> &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<const CtfTypeQualifier *>(lhs);
+ lhs =
+ lhs->get_owned()->id_mapper().find(t->ref())->second.get();
+ }
+
+ while (ignored(&typeid(*rhs))) {
+ const CtfTypeQualifier *t =
+ dynamic_cast<const CtfTypeQualifier *>(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<uint64_t> &visited,
+ std::unordered_map<uint64_t, bool> &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<const CtfTypePrimitive *>(
+ &rhs);
+ return (this->data == d->data);
+}
+
+bool
+CtfTypeArray::do_compare_impl(const CtfType &rhs, const CompareFunc &comp) const
+{
+ const CtfTypeArray *d = dynamic_cast<const CtfTypeArray *>(&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<const CtfTypeFunc *>(&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<const CtfTypeEnum *>(&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<const CtfTypeQualifier *>(
+ &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<const CtfTypeStruct *>(&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<const CtfTypeUnion *>(&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 <libelf.h>
+
+#include "utility.hpp"
+#include <string>
+#include <string_view>
+
+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 <sys/mman.h>
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <gelf.h>
+#include <libelf.h>
+#include <unistd.h>
+
+#include "ctfdata.hpp"
+#include "metadata.hpp"
+#include <cstddef>
+#include <iostream>
+
+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<std::byte *>(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<std::byte *>(
+ symsecdata->d_buf),
+ symsecdata->d_size,
+ symshdr.sh_size / symshdr.sh_entsize);
+ this->strdata = Buffer(static_cast<std::byte *>(
+ 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<std::byte *>(
+ 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 <libelf.h>
+
+#include <cstddef>
+#include <typeinfo>
+#include <vector>
+
+extern int flags;
+extern std::vector<const std::type_info *> 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 <cstdint>
+#include <typeinfo>
+
+int flags = 0;
+std::vector<const std::type_info *> ignore_ids = { &typeid(CtfTypeTypeDef) };
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Fri, Jan 10, 5:59 AM (12 h, 28 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
15740003
Default Alt Text
D48199.diff (55 KB)
Attached To
Mode
D48199: Add ctfdiff
Attached
Detach File
Event Timeline
Log In to Comment