visibility vi,
bool is_in_ksymtab = false,
const abg_compat::optional<uint64_t>& crc = {},
+ const abg_compat::optional<std::string>& ns = {},
bool is_suppressed = false);
elf_symbol(const elf_symbol&);
visibility vi,
bool is_in_ksymtab = false,
const abg_compat::optional<uint64_t>& crc = {},
+ const abg_compat::optional<std::string>& ns = {},
bool is_suppressed = false);
const environment*
void
set_crc(const abg_compat::optional<uint64_t>& crc);
+ const abg_compat::optional<std::string>&
+ get_namespace() const;
+
+ void
+ set_namespace(const abg_compat::optional<std::string>& ns);
+
bool
is_suppressed() const;
return false;
}
+/// Test if there was a function or variable namespace change.
+///
+/// @param f the first function or variable to consider.
+///
+/// @param s the second function or variable to consider.
+///
+/// @return true if the test is positive, false otherwise.
+template <typename function_or_var_decl_sptr>
+static bool
+namespace_changed(const function_or_var_decl_sptr& f,
+ const function_or_var_decl_sptr& s)
+{
+ const auto& symbol_f = f->get_symbol();
+ const auto& symbol_s = s->get_symbol();
+ if (!symbol_f || !symbol_s)
+ return false;
+ return symbol_f->get_namespace() != symbol_s->get_namespace();
+}
+
+/// Test if the current diff tree node carries a namespace change in
+/// either a function or a variable.
+///
+/// @param diff the diff tree node to consider.
+///
+/// @return true if the test is positive, false otherwise.
+static bool
+namespace_changed(const diff* diff)
+{
+ if (const function_decl_diff* d =
+ dynamic_cast<const function_decl_diff*>(diff))
+ return namespace_changed(d->first_function_decl(),
+ d->second_function_decl());
+ if (const var_diff* d = dynamic_cast<const var_diff*>(diff))
+ return namespace_changed(d->first_var(), d->second_var());
+ return false;
+}
+
/// Test if there was a function name change, but there there was no
/// change in name of the underlying symbol. IOW, if the name of a
/// function changed, but the symbol of the new function is equal to
|| non_static_data_member_added_or_removed(d)
|| base_classes_added_or_removed(d)
|| has_harmful_enum_change(d)
- || crc_changed(d)))
+ || crc_changed(d)
+ || namespace_changed(d)))
category |= SIZE_OR_OFFSET_CHANGE_CATEGORY;
if (has_virtual_mem_fn_change(d))
bool is_common_;
bool is_in_ksymtab_;
abg_compat::optional<uint64_t> crc_;
+ abg_compat::optional<std::string> namespace_;
bool is_suppressed_;
elf_symbol_wptr main_symbol_;
elf_symbol_wptr next_alias_;
is_common_(false),
is_in_ksymtab_(false),
crc_(),
+ namespace_(),
is_suppressed_(false)
{}
elf_symbol::visibility vi,
bool is_in_ksymtab,
const abg_compat::optional<uint64_t>& crc,
+ const abg_compat::optional<std::string>& ns,
bool is_suppressed)
: env_(e),
index_(i),
is_common_(c),
is_in_ksymtab_(is_in_ksymtab),
crc_(crc),
+ namespace_(ns),
is_suppressed_(is_suppressed)
{
if (!is_common_)
/// @param vi the visibility of the symbol.
///
/// @param crc the CRC (modversions) value of Linux Kernel symbols
+///
+/// @param ns the namespace of Linux Kernel symbols, if any
elf_symbol::elf_symbol(const environment* e,
size_t i,
size_t s,
visibility vi,
bool is_in_ksymtab,
const abg_compat::optional<uint64_t>& crc,
+ const abg_compat::optional<std::string>& ns,
bool is_suppressed)
: priv_(new priv(e,
i,
vi,
is_in_ksymtab,
crc,
+ ns,
is_suppressed))
{}
///
/// @param crc the CRC (modversions) value of Linux Kernel symbols
///
+/// @param ns the namespace of Linux Kernel symbols, if any
+///
/// @return a (smart) pointer to a newly created instance of @ref
/// elf_symbol.
elf_symbol_sptr
visibility vi,
bool is_in_ksymtab,
const abg_compat::optional<uint64_t>& crc,
+ const abg_compat::optional<std::string>& ns,
bool is_suppressed)
{
elf_symbol_sptr sym(new elf_symbol(e, i, s, n, t, b, d, c, ve, vi,
- is_in_ksymtab, crc, is_suppressed));
+ is_in_ksymtab, crc, ns, is_suppressed));
sym->priv_->main_symbol_ = sym;
return sym;
}
&& l.is_defined() == r.is_defined()
&& l.is_common_symbol() == r.is_common_symbol()
&& l.get_version() == r.get_version()
- && l.get_crc() == r.get_crc());
+ && l.get_crc() == r.get_crc()
+ && l.get_namespace() == r.get_namespace());
if (equals && l.is_variable())
// These are variable symbols. Let's compare their symbol size.
elf_symbol::set_crc(const abg_compat::optional<uint64_t>& crc)
{priv_->crc_ = crc;}
+/// Getter of the 'namespace' property.
+///
+/// @return the namespace for Linux Kernel symbols, if any
+const abg_compat::optional<std::string>&
+elf_symbol::get_namespace() const
+{return priv_->namespace_;}
+
+/// Setter of the 'namespace' property.
+///
+/// @param ns the new namespace for Linux Kernel symbols, if any
+void
+elf_symbol::set_namespace(const abg_compat::optional<std::string>& ns)
+{priv_->namespace_ = ns;}
+
/// Getter for the 'is-suppressed' property.
///
/// @return true iff the current symbol has been suppressed by a
if (xml_char_sptr s = XML_NODE_GET_ATTRIBUTE(node, "crc"))
e->set_crc(strtoull(CHAR_STR(s), NULL, 0));
+ if (xml_char_sptr s = XML_NODE_GET_ATTRIBUTE(node, "namespace"))
+ {
+ std::string ns;
+ xml::xml_char_sptr_to_string(s, ns);
+ e->set_namespace(ns);
+ }
+
return e;
}
out << std::noshowbase << std::dec
<< "\n";
}
+
+ const abg_compat::optional<std::string>& ns1 = symbol1->get_namespace();
+ const abg_compat::optional<std::string>& ns2 = symbol2->get_namespace();
+ if (ns1 != ns2)
+ {
+ const std::string none = "(none)";
+ out << indent << "namespace changed from ";
+ if (ns1.has_value())
+ out << "'" << ns1.value() << "'";
+ else
+ out << none;
+ out << " to ";
+ if (ns2.has_value())
+ out << "'" << ns2.value() << "'";
+ else
+ out << none;
+ out << "\n";
+ }
}
/// For a given symbol, emit a string made of its name and version.
ir::environment* env,
symbol_predicate is_suppressed)
{
+ GElf_Ehdr ehdr_mem;
+ GElf_Ehdr* header = gelf_getehdr(elf_handle, &ehdr_mem);
+ if (!header)
+ {
+ std::cerr << "Could not get ELF header: Skipping symtab load.\n";
+ return false;
+ }
Elf_Scn* symtab_section = elf_helpers::find_symbol_table_section(elf_handle);
if (!symtab_section)
return false;
}
+ // The __kstrtab_strings sections is basically an ELF strtab but does not
+ // support elf_strptr lookups. A single call to elf_getdata gives a handle to
+ // washed section data.
+ //
+ // The value of a __kstrtabns_FOO (or other similar) symbol is an address
+ // within the __kstrtab_strings section. To look up the string value, we need
+ // to translate from vmlinux load address to section offset by subtracting the
+ // base address of the section. This adjustment is not needed for loadable
+ // modules which are relocatable and so identifiable by ELF type ET_REL.
+ Elf_Scn* strings_section = elf_helpers::find_ksymtab_strings_section(elf_handle);
+ size_t strings_offset = 0;
+ const char* strings_data = nullptr;
+ size_t strings_size = 0;
+ if (strings_section)
+ {
+ GElf_Shdr strings_sheader;
+ gelf_getshdr(strings_section, &strings_sheader);
+ strings_offset = header->e_type == ET_REL ? 0 : strings_sheader.sh_addr;
+ Elf_Data* data = elf_getdata(strings_section, nullptr);
+ ABG_ASSERT(data->d_off == 0);
+ strings_data = reinterpret_cast<const char *>(data->d_buf);
+ strings_size = data->d_size;
+ }
+
const bool is_kernel = elf_helpers::is_linux_kernel(elf_handle);
std::unordered_set<std::string> exported_kernel_symbols;
std::unordered_map<std::string, uint64_t> crc_values;
+ std::unordered_map<std::string, std::string> namespaces;
for (size_t i = 0; i < number_syms; ++i)
{
ABG_ASSERT(crc_values.emplace(name.substr(6), sym->st_value).second);
continue;
}
+ if (strings_section && is_kernel && name.rfind("__kstrtabns_", 0) == 0)
+ {
+ // This symbol lives in the __ksymtab_strings section but st_value may
+ // be a vmlinux load address so we need to subtract the offset before
+ // looking it up in that section.
+ const size_t value = sym->st_value;
+ const size_t offset = value - strings_offset;
+ // check offset
+ ABG_ASSERT(offset < strings_size);
+ // find the terminating NULL
+ const char* first = strings_data + offset;
+ const char* last = strings_data + strings_size;
+ const char* limit = std::find(first, last, 0);
+ // check NULL found
+ ABG_ASSERT(limit < last);
+ // interpret the empty namespace name as no namespace name
+ if (first < limit)
+ ABG_ASSERT(namespaces.emplace(
+ name.substr(12), std::string(first, limit - first)).second);
+ continue;
+ }
// filter out uninteresting entries and only keep functions/variables for
// now. The rest might be interesting in the future though.
symbol->set_crc(crc_entry.second);
}
+ // Now add the namespaces
+ for (const auto& namespace_entry : namespaces)
+ {
+ const auto r = name_symbol_map_.find(namespace_entry.first);
+ if (r == name_symbol_map_.end())
+ continue;
+
+ for (const auto& symbol : r->second)
+ symbol->set_namespace(namespace_entry.second);
+ }
+
// sort the symbols for deterministic output
std::sort(symbols_.begin(), symbols_.end(), symbol_sort);
<< std::hex << std::showbase << sym->get_crc().value()
<< std::dec << std::noshowbase << "'";
+ if (sym->get_namespace().has_value())
+ o << " namespace='" << sym->get_namespace().value() << "'";
+
o << "/>\n";
return true;
test-abidiff/test-crc-report-0-1.txt \
test-abidiff/test-crc-report-1-0.txt \
test-abidiff/test-crc-report-1-2.txt \
+test-abidiff/test-namespace-0.xml \
+test-abidiff/test-namespace-1.xml \
+test-abidiff/test-namespace-report.txt \
test-abidiff/test-PR27985-report.txt \
test-abidiff/test-PR27985-v0.c \
test-abidiff/test-PR27985-v0.o \
--- /dev/null
+<abi-corpus path='test.o' architecture='elf-amd-x86_64'>
+ <elf-variable-symbols>
+ <elf-symbol name='v1' size='4' type='object-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+ <elf-symbol name='v2' size='4' type='object-type' binding='global-binding' visibility='default-visibility' is-defined='yes' namespace='this'/>
+ <elf-symbol name='v3' size='4' type='object-type' binding='global-binding' visibility='default-visibility' is-defined='yes' namespace='that'/>
+ <elf-symbol name='v4' size='4' type='object-type' binding='global-binding' visibility='default-visibility' is-defined='yes' namespace=''/>
+ </elf-variable-symbols>
+ <abi-instr version='1.0' address-size='64' path='test.c' comp-dir-path='/tmp' language='LANG_C89'>
+ <type-decl name='int' size-in-bits='32' id='type-id-1'/>
+ <var-decl name='v1' type-id='type-id-1' mangled-name='v1' visibility='default' filepath='test.c' line='1' column='1' elf-symbol-id='v1'/>
+ <var-decl name='v2' type-id='type-id-1' mangled-name='v2' visibility='default' filepath='test.c' line='2' column='1' elf-symbol-id='v2'/>
+ <var-decl name='v3' type-id='type-id-1' mangled-name='v3' visibility='default' filepath='test.c' line='3' column='1' elf-symbol-id='v3'/>
+ <var-decl name='v4' type-id='type-id-1' mangled-name='v4' visibility='default' filepath='test.c' line='4' column='1' elf-symbol-id='v4'/>
+ </abi-instr>
+</abi-corpus>
--- /dev/null
+<abi-corpus path='test.o' architecture='elf-amd-x86_64'>
+ <elf-variable-symbols>
+ <elf-symbol name='v1' size='4' type='object-type' binding='global-binding' visibility='default-visibility' is-defined='yes' namespace='this'/>
+ <elf-symbol name='v2' size='4' type='object-type' binding='global-binding' visibility='default-visibility' is-defined='yes' namespace='that'/>
+ <elf-symbol name='v3' size='4' type='object-type' binding='global-binding' visibility='default-visibility' is-defined='yes' namespace=''/>
+ <elf-symbol name='v4' size='4' type='object-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+ </elf-variable-symbols>
+ <abi-instr version='1.0' address-size='64' path='test.c' comp-dir-path='/tmp' language='LANG_C89'>
+ <type-decl name='int' size-in-bits='32' id='type-id-1'/>
+ <var-decl name='v1' type-id='type-id-1' mangled-name='v1' visibility='default' filepath='test.c' line='1' column='1' elf-symbol-id='v1'/>
+ <var-decl name='v2' type-id='type-id-1' mangled-name='v2' visibility='default' filepath='test.c' line='2' column='1' elf-symbol-id='v2'/>
+ <var-decl name='v3' type-id='type-id-1' mangled-name='v3' visibility='default' filepath='test.c' line='3' column='1' elf-symbol-id='v3'/>
+ <var-decl name='v4' type-id='type-id-1' mangled-name='v4' visibility='default' filepath='test.c' line='4' column='1' elf-symbol-id='v4'/>
+ </abi-instr>
+</abi-corpus>
--- /dev/null
+Functions changes summary: 0 Removed, 0 Changed, 0 Added function
+Variables changes summary: 0 Removed, 4 Changed, 0 Added variables
+
+4 Changed variables:
+
+ [C] 'int v1' was changed:
+ namespace changed from (none) to 'this'
+
+ [C] 'int v2' was changed:
+ namespace changed from 'this' to 'that'
+
+ [C] 'int v3' was changed:
+ namespace changed from 'that' to ''
+
+ [C] 'int v4' was changed:
+ namespace changed from '' to (none)
+
"data/test-abidiff/test-crc-report-1-2.txt",
"output/test-abidiff/test-crc-report-1-2.txt"
},
+ {
+ "data/test-abidiff/test-namespace-0.xml",
+ "data/test-abidiff/test-namespace-1.xml",
+ "data/test-abidiff/test-namespace-report.txt",
+ "output/test-abidiff/test-namespace-report-0-1.txt"
+ },
{
"data/test-abidiff/test-PR27616-v0.xml",
"data/test-abidiff/test-PR27616-v1.xml",