From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mail-wr1-x433.google.com (mail-wr1-x433.google.com [IPv6:2a00:1450:4864:20::433]) by sourceware.org (Postfix) with ESMTPS id 5CCCD385E838 for ; Mon, 21 Mar 2022 12:53:59 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org 5CCCD385E838 Received: by mail-wr1-x433.google.com with SMTP id a1so19182216wrh.10 for ; Mon, 21 Mar 2022 05:53:59 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:date:from:to:cc:subject:message-id:references :mime-version:content-disposition:in-reply-to; bh=D/G7xxjTaZz+WiN5jObfMjxgFpwwqAY6E4fVwdPDnas=; b=R72oeqzpp0dBrBORKIHA8Ggl7h//BPRpARN0NcR8x3DN5A0DDxW2h6U2qioeZo0epp hwW7pVlTwNjtxM824yU5KOLhzni8x9INapBR+YKt1Zcdti1brrOPmLpl6qc4loY9P6P6 H2eWX4cdwDtTCDKKteELGqAzVYhsi8TzTFMH3eOz2hct5NF1SqcwOU1ZPa2anNT1vpIC MExvZ5Wd59RFBk0N5r9JVjuBSUt/rmg6UNk8PGkhyG0+Av12IqJsevFiMvvH/e3T/RCz 9pJRLA80h8EQRz9DGrhtU28HWRbALxUy6hJJUbqE81RGz+3aILGy1PSr0d9b66KEtHXX 0p2A== X-Gm-Message-State: AOAM533Qyo/KkVdYVuE+foJeFGApxlCyF2OvTzobn7OzXlO1CKwCU/kh RhN0Wh4FSQkwc+TdooPXO7cTUA== X-Google-Smtp-Source: ABdhPJzggPAkv5S/T+WgKlH7JB26fwVgv3Bo4/oLiyMjC0qMgF/omFKYS70nEeBLuMqw8a53evM/Sg== X-Received: by 2002:adf:f28d:0:b0:203:f161:55ac with SMTP id k13-20020adff28d000000b00203f16155acmr15038140wro.209.1647867237809; Mon, 21 Mar 2022 05:53:57 -0700 (PDT) Received: from google.com ([2a00:79e0:d:210:6101:f824:c43a:96c1]) by smtp.gmail.com with ESMTPSA id r9-20020a05600c35c900b0038c7626c454sm12144216wmq.12.2022.03.21.05.53.56 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 21 Mar 2022 05:53:57 -0700 (PDT) Date: Mon, 21 Mar 2022 12:53:56 +0000 From: Matthias Maennich To: Giuliano Procida Cc: libabigail@sourceware.org, dodji@seketeli.org, kernel-team@android.com Subject: Re: [PATCH v3 4/4] add Linux kernel symbol namespace support Message-ID: References: <20220316163055.4127796-1-gprocida@google.com> <20220317163858.353762-1-gprocida@google.com> <20220317163858.353762-5-gprocida@google.com> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii; format=flowed Content-Disposition: inline In-Reply-To: <20220317163858.353762-5-gprocida@google.com> X-Spam-Status: No, score=-29.7 required=5.0 tests=BAYES_00, DKIMWL_WL_MED, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, ENV_AND_HDR_SPF_MATCH, GIT_PATCH_0, RCVD_IN_DNSWL_NONE, SPF_HELO_NONE, SPF_PASS, TXREP, T_SCC_BODY_TEXT_LINE, USER_IN_DEF_DKIM_WL, USER_IN_DEF_SPF_WL autolearn=ham autolearn_force=no version=3.4.4 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on server2.sourceware.org X-BeenThere: libabigail@sourceware.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Mailing list of the Libabigail project List-Unsubscribe: , List-Archive: List-Help: List-Subscribe: , X-List-Received-Date: Mon, 21 Mar 2022 12:54:03 -0000 On Thu, Mar 17, 2022 at 04:38:58PM +0000, Giuliano Procida wrote: >Bug 28954 - add Linux Kernel symbol namespace support > >Each Linux kernel symbol can be exported to a specified named >namespace or left in the global (nameless) namespace. > >One complexity is that the symbol values which identify a string in >the __ksymtab_strings section must be interpretated differently for >vmlinux and .ko loadable modules as the former has a fixed load >address but the latter are relocatable. For vmlinux, the section base >address needs to be subtracted to obtain a section-relative offset. > >The global namespace is explicitly represented as the empty string, at >least when it comes to the value of __kstrtabns_FOO symbols, but the >common interpretation is that such symbols lack an export namespace. > >I would rather not have to make use of "empty implies missing" in many >places, so the code here represents namespace as optional and >only the symtab reader cares about empty strings in __ksymtab_strings. > > * include/abg-ir.h (elf_symbol::elf_symbol): Add ns argument. > (elf_symbol::create): Add ns argument. > (elf_symbol::get_namespace): Declare new function. > (elf_symbol::set_namespace): Declare new function. > and set_namespace. > * src/abg-comp-filter.cc (namespace_changed): Define new > helper functions. > (categorize_harmful_diff_node): Also call namespace_changed(). > * src/abg-ir.cc (elf_symbol::priv): Add namespace_ member. > (elf_symbol::priv::priv): Add namespace_ to initialisers. > (elf_symbol::elf_symbol): Take new ns argument and pass it to > priv constructor. > (elf_symbol::create): Take new ns argument and pass it to > elf_symbol constructor. > (elf_symbol::get_namespace): Define new function. > (elf_symbol::set_namespace): Define new function. > * src/abg-reader.cc (build_elf_symbol): If namespace > attribute is present, set symbol namespace. > * src/abg-reporter-priv.cc (maybe_report_diff_for_symbol): If > symbol namespaces differ, report this. > * src/abg-symtab-reader.cc (symtab::load): Get ELF header to > distinguish vmlinux from .ko. Try to get __ksymtab_strings > metadata and data. Use these to look up __kstrtabns_FOO > namespace entries. Set symbol namespace where found. > * src/abg-writer.cc (write_elf_symbol): Emit namespace > attribute, if symbol has a namespace. > * tests/data/Makefile.am: Add new test files. > * tests/data/test-abidiff/test-namespace-0.xml: New test file. > * tests/data/test-abidiff/test-namespace-1.xml: Likewise > * tests/data/test-abidiff/test-namespace-report.txt: Likewise. > * tests/test-abidiff.cc: Add new test case. > >Signed-off-by: Giuliano Procida >--- > include/abg-ir.h | 8 +++ > src/abg-comp-filter.cc | 40 ++++++++++- > src/abg-ir.cc | 30 ++++++++- > src/abg-reader.cc | 7 ++ > src/abg-reporter-priv.cc | 18 +++++ > src/abg-symtab-reader.cc | 66 +++++++++++++++++++ > src/abg-writer.cc | 3 + > tests/data/Makefile.am | 3 + > tests/data/test-abidiff/test-namespace-0.xml | 15 +++++ > tests/data/test-abidiff/test-namespace-1.xml | 15 +++++ > .../test-abidiff/test-namespace-report.txt | 17 +++++ > tests/test-abidiff.cc | 6 ++ > 12 files changed, 225 insertions(+), 3 deletions(-) > create mode 100644 tests/data/test-abidiff/test-namespace-0.xml > create mode 100644 tests/data/test-abidiff/test-namespace-1.xml > create mode 100644 tests/data/test-abidiff/test-namespace-report.txt > >diff --git a/include/abg-ir.h b/include/abg-ir.h >index b05a8c6f..7c558828 100644 >--- a/include/abg-ir.h >+++ b/include/abg-ir.h >@@ -922,6 +922,7 @@ private: > visibility vi, > bool is_in_ksymtab = false, > const abg_compat::optional& crc = {}, >+ const abg_compat::optional& ns = {}, > bool is_suppressed = false); > > elf_symbol(const elf_symbol&); >@@ -947,6 +948,7 @@ public: > visibility vi, > bool is_in_ksymtab = false, > const abg_compat::optional& crc = {}, >+ const abg_compat::optional& ns = {}, > bool is_suppressed = false); > > const environment* >@@ -1024,6 +1026,12 @@ public: > void > set_crc(const abg_compat::optional& crc); > >+ const abg_compat::optional& >+ get_namespace() const; >+ >+ void >+ set_namespace(const abg_compat::optional& ns); >+ > bool > is_suppressed() const; > >diff --git a/src/abg-comp-filter.cc b/src/abg-comp-filter.cc >index 31590284..22da5244 100644 >--- a/src/abg-comp-filter.cc >+++ b/src/abg-comp-filter.cc >@@ -254,6 +254,43 @@ crc_changed(const diff* diff) > 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 >+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(diff)) >+ return namespace_changed(d->first_function_decl(), >+ d->second_function_decl()); >+ if (const var_diff* d = dynamic_cast(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 >@@ -1781,7 +1818,8 @@ categorize_harmful_diff_node(diff *d, bool pre) > || 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)) >diff --git a/src/abg-ir.cc b/src/abg-ir.cc >index 853b01ee..f90be2e4 100644 >--- a/src/abg-ir.cc >+++ b/src/abg-ir.cc >@@ -1728,6 +1728,7 @@ struct elf_symbol::priv > bool is_common_; > bool is_in_ksymtab_; > abg_compat::optional crc_; >+ abg_compat::optional namespace_; > bool is_suppressed_; > elf_symbol_wptr main_symbol_; > elf_symbol_wptr next_alias_; >@@ -1745,6 +1746,7 @@ struct elf_symbol::priv > is_common_(false), > is_in_ksymtab_(false), > crc_(), >+ namespace_(), > is_suppressed_(false) > {} > >@@ -1760,6 +1762,7 @@ struct elf_symbol::priv > elf_symbol::visibility vi, > bool is_in_ksymtab, > const abg_compat::optional& crc, >+ const abg_compat::optional& ns, > bool is_suppressed) > : env_(e), > index_(i), >@@ -1773,6 +1776,7 @@ struct elf_symbol::priv > is_common_(c), > is_in_ksymtab_(is_in_ksymtab), > crc_(crc), >+ namespace_(ns), > is_suppressed_(is_suppressed) > { > if (!is_common_) >@@ -1818,6 +1822,8 @@ elf_symbol::elf_symbol() > /// @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, >@@ -1830,6 +1836,7 @@ elf_symbol::elf_symbol(const environment* e, > visibility vi, > bool is_in_ksymtab, > const abg_compat::optional& crc, >+ const abg_compat::optional& ns, > bool is_suppressed) > : priv_(new priv(e, > i, >@@ -1843,6 +1850,7 @@ elf_symbol::elf_symbol(const environment* e, > vi, > is_in_ksymtab, > crc, >+ ns, > is_suppressed)) > {} > >@@ -1886,6 +1894,8 @@ elf_symbol::create() > /// > /// @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 >@@ -1901,10 +1911,11 @@ elf_symbol::create(const environment* e, > visibility vi, > bool is_in_ksymtab, > const abg_compat::optional& crc, >+ const abg_compat::optional& 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; > } >@@ -1926,7 +1937,8 @@ textually_equals(const elf_symbol&l, > && 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. >@@ -2146,6 +2158,20 @@ void > elf_symbol::set_crc(const abg_compat::optional& crc) > {priv_->crc_ = crc;} > >+/// Getter of the 'namespace' property. >+/// >+/// @return the namespace for Linux Kernel symbols, if any >+const abg_compat::optional& >+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& ns) >+{priv_->namespace_ = ns;} >+ > /// Getter for the 'is-suppressed' property. > /// > /// @return true iff the current symbol has been suppressed by a >diff --git a/src/abg-reader.cc b/src/abg-reader.cc >index 7a9ad1f9..f9e420f1 100644 >--- a/src/abg-reader.cc >+++ b/src/abg-reader.cc >@@ -3265,6 +3265,13 @@ build_elf_symbol(read_context& ctxt, const xmlNodePtr node, > 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; > } > >diff --git a/src/abg-reporter-priv.cc b/src/abg-reporter-priv.cc >index f9dd928b..9ad733f4 100644 >--- a/src/abg-reporter-priv.cc >+++ b/src/abg-reporter-priv.cc >@@ -1168,6 +1168,24 @@ maybe_report_diff_for_symbol(const elf_symbol_sptr& symbol1, > out << std::noshowbase << std::dec > << "\n"; > } >+ >+ const abg_compat::optional& ns1 = symbol1->get_namespace(); >+ const abg_compat::optional& 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. >diff --git a/src/abg-symtab-reader.cc b/src/abg-symtab-reader.cc >index b42ce87d..123ef84a 100644 >--- a/src/abg-symtab-reader.cc >+++ b/src/abg-symtab-reader.cc >@@ -204,6 +204,13 @@ symtab::load_(Elf* elf_handle, > 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 load get ELF header: Skipping symtab load.\n"; nit: "Could not load ELF header: ..." >+ return false; >+ } > > Elf_Scn* symtab_section = elf_helpers::find_symbol_table_section(elf_handle); > if (!symtab_section) >@@ -232,9 +239,34 @@ symtab::load_(Elf* elf_handle, > 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 >+ // section data. raw 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. ... 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(data->d_buf); >+ strings_size = data->d_size; Since you later assume strings_data and strings_size being valid based on the existence of strings_section, perhaps assert that the extraction worked? ABG_ASSERT(strings_data); ABG_ASSERT(strings_size > 0); // otherwise, why would there be a section? >+ } >+ > const bool is_kernel = elf_helpers::is_linux_kernel(elf_handle); > std::unordered_set exported_kernel_symbols; > std::unordered_map crc_values; >+ std::unordered_map namespaces; > > const bool is_arm32 = elf_helpers::architecture_is_arm32(elf_handle); > const bool is_ppc64 = elf_helpers::architecture_is_ppc64(elf_handle); >@@ -288,6 +320,29 @@ symtab::load_(Elf* elf_handle, > 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; odd indentation >+ 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) >+ { >+ const std::string ns(first, limit - first); >+ ABG_ASSERT(namespaces.emplace(name.substr(12), ns).second); ABG_ASSERT(namespaces.emplace(name.substr(12), std::move(ns)).second); With the few nits addressed, feel free to add Reviewed-by: Matthias Maennich Cheers, Matthias >+ } >+ continue; >+ } > > // filter out uninteresting entries and only keep functions/variables for > // now. The rest might be interesting in the future though. >@@ -402,6 +457,17 @@ symtab::load_(Elf* elf_handle, > 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); > >diff --git a/src/abg-writer.cc b/src/abg-writer.cc >index 0a1e7b66..03fb658b 100644 >--- a/src/abg-writer.cc >+++ b/src/abg-writer.cc >@@ -3136,6 +3136,9 @@ write_elf_symbol(const elf_symbol_sptr& sym, > << 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; >diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am >index 90bd1934..7388697b 100644 >--- a/tests/data/Makefile.am >+++ b/tests/data/Makefile.am >@@ -93,6 +93,9 @@ test-abidiff/test-crc-2.xml \ > 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 \ >diff --git a/tests/data/test-abidiff/test-namespace-0.xml b/tests/data/test-abidiff/test-namespace-0.xml >new file mode 100644 >index 00000000..5a9f5cd2 >--- /dev/null >+++ b/tests/data/test-abidiff/test-namespace-0.xml >@@ -0,0 +1,15 @@ >+ >+ >+ >+ >+ >+ >+ >+ >+ >+ >+ >+ >+ >+ >+ >diff --git a/tests/data/test-abidiff/test-namespace-1.xml b/tests/data/test-abidiff/test-namespace-1.xml >new file mode 100644 >index 00000000..9814844b >--- /dev/null >+++ b/tests/data/test-abidiff/test-namespace-1.xml >@@ -0,0 +1,15 @@ >+ >+ >+ >+ >+ >+ >+ >+ >+ >+ >+ >+ >+ >+ >+ >diff --git a/tests/data/test-abidiff/test-namespace-report.txt b/tests/data/test-abidiff/test-namespace-report.txt >new file mode 100644 >index 00000000..d2c421ed >--- /dev/null >+++ b/tests/data/test-abidiff/test-namespace-report.txt >@@ -0,0 +1,17 @@ >+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) >+ >diff --git a/tests/test-abidiff.cc b/tests/test-abidiff.cc >index a02bbe3d..93e44d6a 100644 >--- a/tests/test-abidiff.cc >+++ b/tests/test-abidiff.cc >@@ -128,6 +128,12 @@ static InOutSpec specs[] = > "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", >-- >2.35.1.894.gb6a874cedc-goog >