From df60b451a26db2f8ae5ed6ae80b843b8bac5060d Mon Sep 17 00:00:00 2001 From: Florian Weimer Date: Mon, 20 Jan 2025 08:12:29 +0100 Subject: [PATCH] Additional dynamic linker dependency sorting tests (RHEL-58987) Resolves: RHEL-58987 --- glibc-RHEL-58987-1.patch | 26 +++ glibc-RHEL-58987-2.patch | 333 +++++++++++++++++++++++++++++++++++++++ glibc.spec | 7 +- 3 files changed, 365 insertions(+), 1 deletion(-) create mode 100644 glibc-RHEL-58987-1.patch create mode 100644 glibc-RHEL-58987-2.patch diff --git a/glibc-RHEL-58987-1.patch b/glibc-RHEL-58987-1.patch new file mode 100644 index 0000000..e941392 --- /dev/null +++ b/glibc-RHEL-58987-1.patch @@ -0,0 +1,26 @@ +commit 062257c5d929e3c9a83a26624a09e57936ac6b5e +Author: Joseph Myers +Date: Thu Dec 5 21:40:57 2024 +0000 + + Fix typo in elf/Makefile:postclean-generated + + The postclean-generated setting in elf/Makefile lists + $(objpfx)/dso-sort-tests-2.generated-makefile twice and + $(objpfx)/dso-sort-tests-1.generated-makefile not at all, which looks + like a typo; fix it to list each once. + + Tested for x86_64. + +diff --git a/elf/Makefile b/elf/Makefile +index 96d5c290c9447c74..dfdcfc8e3d1e1770 100644 +--- a/elf/Makefile ++++ b/elf/Makefile +@@ -1140,7 +1140,7 @@ $(objpfx)$(1).generated-makefile: $(1) + endef + endif + +-postclean-generated += $(objpfx)/dso-sort-tests-2.generated-makefile \ ++postclean-generated += $(objpfx)/dso-sort-tests-1.generated-makefile \ + $(objpfx)/dso-sort-tests-2.generated-makefile + + # Generate from each testcase description file diff --git a/glibc-RHEL-58987-2.patch b/glibc-RHEL-58987-2.patch new file mode 100644 index 0000000..fcbf4ba --- /dev/null +++ b/glibc-RHEL-58987-2.patch @@ -0,0 +1,333 @@ +commit d7f587398cfda79a48cde94a38c4eee660781d30 +Author: Joseph Myers +Date: Thu Dec 19 18:56:04 2024 +0000 + + Add further DSO dependency sorting tests + + The current DSO dependency sorting tests are for a limited number of + specific cases, including some from particular bug reports. + + Add tests that systematically cover all possible DAGs for an + executable and the shared libraries it depends on, directly or + indirectly, up to four objects (an executable and three shared + libraries). (For this kind of DAG - ones with a single source vertex + from which all others are reachable, and an ordering on the edges from + each vertex - there are 57 DAGs on four vertices, 3399 on five + vertices and 1026944 on six vertices; see + https://arxiv.org/pdf/2303.14710 for more details on this enumeration. + I've tested that the 3399 cases with five vertices do all pass if + enabled.) + + These tests are replicating the sorting logic from the dynamic linker + (thereby, for example, asserting that it doesn't accidentally change); + I'm not claiming that the logic in the dynamic linker is in some + abstract sense optimal. Note that these tests do illustrate how in + some cases the two sorting algorithms produce different results for a + DAG (I think all the existing tests for such differences are ones + involving cycles, and the motivation for the new algorithm was also to + improve the handling of cycles): + + tst-dso-ordering-all4-44: a->[bc];{}->[cba] + output(glibc.rtld.dynamic_sort=1): c>b>a>{}c>a>{}[abc] + output: c>b>a>{}[cb];{}->[cba] + output: c>b>a>{} $@ ++ ++$(objpfx)dso-sort-tests-all3.def: dso-sort-tests-all.py ++ $(PYTHON) $< 3 > $@ ++ ++$(objpfx)dso-sort-tests-all4.def: dso-sort-tests-all.py ++ $(PYTHON) $< 4 > $@ ++ ++$(eval $(call include_dsosort_tests_objpfx,dso-sort-tests-all2.def)) ++$(eval $(call include_dsosort_tests_objpfx,dso-sort-tests-all3.def)) ++$(eval $(call include_dsosort_tests_objpfx,dso-sort-tests-all4.def)) ++endif # $(have-tunables) + + check-abi: $(objpfx)check-abi-ld.out + tests-special += $(objpfx)check-abi-ld.out +diff --git a/elf/dso-sort-tests-all.py b/elf/dso-sort-tests-all.py +new file mode 100755 +index 0000000000000000..703e7d2eddae3482 +--- /dev/null ++++ b/elf/dso-sort-tests-all.py +@@ -0,0 +1,218 @@ ++#!/usr/bin/env python3 ++# Generate all DAGs for dependency ordering of a given number of objects. ++# Copyright (C) 2024 Free Software Foundation, Inc. ++# This file is part of the GNU C Library. ++# ++# The GNU C Library is free software; you can redistribute it and/or ++# modify it under the terms of the GNU Lesser General Public ++# License as published by the Free Software Foundation; either ++# version 2.1 of the License, or (at your option) any later version. ++# ++# The GNU C Library is distributed in the hope that it will be useful, ++# but WITHOUT ANY WARRANTY; without even the implied warranty of ++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++# Lesser General Public License for more details. ++# ++# You should have received a copy of the GNU Lesser General Public ++# License along with the GNU C Library; if not, see ++# . ++ ++import argparse ++import sys ++ ++ ++def print_dag(state, dag, postorder, postorder_new): ++ """Print a DAG in the form used by dso-ordering-test.py.""" ++ out = [] ++ for i in range(len(dag)): ++ if dag[i]: ++ if i == len(dag) - 1: ++ name = '{}' ++ else: ++ name = chr(ord('a') + len(dag) - 2 - i) ++ this_deps = [chr(ord('a') + len(dag) - 2 - j) for j in dag[i]] ++ this_out = ('[%s]' % (''.join(this_deps)) ++ if len(this_deps) > 1 ++ else this_deps[0]) ++ out.append('%s->%s' % (name, this_out)) ++ output_old = ( ++ '%s>{}<%s' % ++ ('>'.join(chr(ord('a') + i) for i in range(len(dag) - 2, -1, -1)), ++ '<'.join(chr(ord('a') + i) for i in range(0, len(dag) - 1)))) ++ if postorder == postorder_new: ++ print('tst-dso-ordering-all%d-%d: %s\n' ++ 'output: %s\n' ++ % (len(dag), state['num_dags'], ';'.join(out), output_old)) ++ else: ++ names_new = [chr(ord('a') + len(dag) - 2 - x) ++ for x in postorder_new[:-1]] ++ output_new = '%s>{}<%s' % ('>'.join(names_new), ++ '<'.join(reversed(names_new))) ++ print('tst-dso-ordering-all%d-%d: %s\n' ++ 'output(glibc.rtld.dynamic_sort=1): %s\n' ++ 'output(glibc.rtld.dynamic_sort=2): %s\n' ++ % (len(dag), state['num_dags'], ';'.join(out), output_old, ++ output_new)) ++ state['num_dags'] += 1 ++ ++ ++def gen_postorder_old(dag, postorder): ++ """Generate a postorder traversal of the vertices of the given ++ DAG, in the particular choice of ordering that corresponds to how ++ the dynamic linker sorts constructor executions (old algorithm).""" ++ # First list all the vertices, breadth-first. ++ postorder.append(len(dag) - 1) ++ for i in range(len(dag)): ++ for v in dag[postorder[i]]: ++ if v not in postorder: ++ postorder.append(v) ++ # Now move any vertex with an edge from a later one to just after ++ # the last vertex with an edge to it (emulating the older dynamic ++ # linker algorithm). ++ changed = True ++ while changed: ++ changed = False ++ i = 0 ++ while i < len(dag): ++ move_past = None ++ for k in range(len(dag) - 1, i, -1): ++ if postorder[i] in dag[postorder[k]]: ++ move_past = k ++ break ++ if move_past is None: ++ i += 1 ++ else: ++ changed = True ++ postorder[i:k+1] = postorder[i+1:k+1] + [postorder[i]] ++ # Finally, reverse the list. ++ postorder.reverse() ++ ++ ++def gen_postorder_dfs(dag, postorder, v): ++ """Traverse the dependencies of a vertex as part of generating a ++ postorder traversal of the given DAG (new algorithm).""" ++ if v in postorder: ++ return ++ for d in dag[v]: ++ gen_postorder_dfs(dag, postorder, d) ++ postorder.append(v) ++ ++ ++def gen_postorder_new(dag, postorder): ++ """Generate a postorder traversal of the vertices of the given ++ DAG, in the particular choice of ordering that corresponds to how ++ the dynamic linker sorts constructor executions (new algorithm).""" ++ # First list all the vertices, breadth-first. ++ tmp = [] ++ tmp.append(len(dag) - 1) ++ for i in range(len(dag)): ++ for v in dag[tmp[i]]: ++ if v not in tmp: ++ tmp.append(v) ++ # Starting at the end of the breadth-first list, do depth-first ++ # traversal of dependencies to add to the final ordering. ++ for v in reversed(tmp): ++ gen_postorder_dfs(dag, postorder, v) ++ ++ ++def gen_orderings_rec_sub(state, dag, num_done, num_swaps_done): ++ """Generate possible orderings for the edges out from each vertex ++ of a DAG and test whether a postorder traversal yields the ++ vertices in order, where orderings have already been generated for ++ some number of vertices and some number of initial edges have been ++ chosen in the ordering for the next vertex.""" ++ if num_swaps_done >= len(dag[num_done]) - 1: ++ gen_orderings_rec(state, dag, num_done + 1) ++ else: ++ for i in range(num_swaps_done, len(dag[num_done])): ++ ndag = dag ++ if i != num_swaps_done: ++ ndag = ndag.copy() ++ ndag[num_done] = ndag[num_done].copy() ++ first = ndag[num_done][num_swaps_done] ++ second = ndag[num_done][i] ++ ndag[num_done][i] = first ++ ndag[num_done][num_swaps_done] = second ++ gen_orderings_rec_sub(state, ndag, num_done, num_swaps_done + 1) ++ ++def gen_orderings_rec(state, dag, num_done): ++ """Generate possible orderings for the edges out from each vertex ++ of a DAG and test whether a postorder traversal yields the ++ vertices in order, where orderings have already been generated for ++ some number of vertices.""" ++ if num_done == len(dag): ++ postorder = [] ++ gen_postorder_old(dag, postorder) ++ if postorder == sorted(postorder): ++ postorder_new = [] ++ gen_postorder_new(dag, postorder_new) ++ print_dag(state, dag, postorder, postorder_new) ++ else: ++ gen_orderings_rec_sub(state, dag, num_done, 0) ++ ++ ++def gen_orderings(state, dag): ++ """Generate possible orderings for the edges out from each vertex ++ of a DAG and test whether a postorder traversal yields the ++ vertices in order.""" ++ gen_orderings_rec(state, dag, 0) ++ ++ ++def gen_dags_rec_sub(state, partial_dag, num_vertices, num_done_last): ++ """Generate DAGs, where a partial DAG for an initial subsequence ++ of the vertices, and partial information about edges from the last ++ vertex, are passed in.""" ++ if num_done_last == len(partial_dag) - 1: ++ gen_dags_rec(state, partial_dag, num_vertices) ++ else: ++ # Recurse with an edge to vertex num_done_last. ++ new_dag = partial_dag.copy() ++ new_dag[-1] = new_dag[-1].copy() ++ new_dag[-1].append(num_done_last) ++ gen_dags_rec_sub(state, new_dag, num_vertices, num_done_last + 1) ++ # Recurse without an edge to vertex num_done_last, unless this is ++ # the last vertex and num_done_last is not otherwise reachable. ++ can_recurse_without = len(partial_dag) < num_vertices ++ if not can_recurse_without: ++ for i in range(num_done_last + 1, len(partial_dag) - 1): ++ if num_done_last in partial_dag[i]: ++ can_recurse_without = True ++ break ++ if can_recurse_without: ++ gen_dags_rec_sub(state, partial_dag, num_vertices, ++ num_done_last + 1) ++ ++ ++def gen_dags_rec(state, partial_dag, num_vertices): ++ """Generate DAGs, where a partial DAG for an initial subsequence ++ of the vertices is passed in.""" ++ if len(partial_dag) == num_vertices: ++ gen_orderings(state, partial_dag) ++ else: ++ partial_dag = partial_dag.copy() ++ partial_dag.append([]) ++ gen_dags_rec_sub(state, partial_dag, num_vertices, 0) ++ ++ ++def gen_dags(state, num_vertices): ++ """Generate DAGs with the given number of vertices, last vertex a ++ distinguished root vertex from which all the others can be ++ reached, order of edges from each vertex considered significant, ++ such that a postorder traversal (corresponding to the order in ++ which DSO dependency constructors are executed) yields the ++ vertices in order.""" ++ gen_dags_rec(state, [[]], num_vertices) ++ ++ ++def main(argv): ++ """The main entry point.""" ++ parser = argparse.ArgumentParser( ++ description='Generate DAGs to test DSO dependency ordering.') ++ parser.add_argument('num_objects', help='number of objects in DAG') ++ print('tunable_option: glibc.rtld.dynamic_sort=1\n' ++ 'tunable_option: glibc.rtld.dynamic_sort=2\n') ++ gen_dags({'num_dags': 0}, int(parser.parse_args(argv).num_objects)) ++ ++ ++if __name__ == '__main__': ++ main(sys.argv[1:]) diff --git a/glibc.spec b/glibc.spec index 259b7b3..0f034eb 100644 --- a/glibc.spec +++ b/glibc.spec @@ -157,7 +157,7 @@ end \ Summary: The GNU libc libraries Name: glibc Version: %{glibcversion} -Release: 152%{?dist} +Release: 153%{?dist} # In general, GPLv2+ is used by programs, LGPLv2+ is used for # libraries. @@ -1074,6 +1074,8 @@ Patch766: glibc-RHEL-62716-2.patch Patch767: glibc-RHEL-68857.patch Patch768: glibc-RHEL-69633-1.patch Patch769: glibc-RHEL-69633-2.patch +Patch770: glibc-RHEL-58987-1.patch +Patch771: glibc-RHEL-58987-2.patch ############################################################################## # Continued list of core "glibc" package information: @@ -3067,6 +3069,9 @@ update_gconv_modules_cache () %endif %changelog +* Mon Jan 20 2025 Florian Weimer - 2.34-153 +- Additional dynamic linker dependency sorting tests (RHEL-58987) + * Fri Jan 10 2025 Frédéric Bérat - 2.34-152 - Additional TLS test cases (RHEL-58989) - Additional mremap test cases (RHEL-62716)