[v3,0/7] Fix some libm static issues
[platform/upstream/glibc.git] / scripts / dso-ordering-test.py
1 #!/usr/bin/python3
2 # Generate testcase files and Makefile fragments for DSO sorting test
3 # Copyright (C) 2021-2024 Free Software Foundation, Inc.
4 # This file is part of the GNU C Library.
5 #
6 # The GNU C Library is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU Lesser General Public
8 # License as published by the Free Software Foundation; either
9 # version 2.1 of the License, or (at your option) any later version.
10 #
11 # The GNU C Library is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # Lesser General Public License for more details.
15 #
16 # You should have received a copy of the GNU Lesser General Public
17 # License along with the GNU C Library; if not, see
18 # <http://www.gnu.org/licenses/>.
19
20 """Generate testcase files and Makefile fragments for DSO sorting test
21
22 This script takes a small description string language, and generates
23 testcases for displaying the ELF dynamic linker's dependency sorting
24 behavior, allowing verification.
25
26 Testcase descriptions are semicolon-separated description strings, and
27 this tool generates a testcase from the description, including main program,
28 associated modules, and Makefile fragments for including into elf/Makefile.
29
30 This allows automation of what otherwise would be very laborous manual
31 construction of complex dependency cases, however it must be noted that this
32 is only a tool to speed up testcase construction, and thus the generation
33 features are largely mechanical in nature; inconsistencies or errors may occur
34 if the input description was itself erroneous or have unforeseen interactions.
35
36 The format of the input test description files are:
37
38   # Each test description has a name, lines of description,
39   # and an expected output specification.  Comments use '#'.
40   testname1: <test-description-line>
41   output: <expected-output-string>
42
43   # Tests can be marked to be XFAIL by using 'xfail_output' instead
44   testname2: <test-description-line>
45   xfail_output: <expected-output-string>
46
47   # A default set of GLIBC_TUNABLES tunables can be specified, for which
48   # all following tests will run multiple times, once for each of the
49   # GLIBC_TUNABLES=... strings set by the 'tunable_option' command.
50   tunable_option: <glibc-tunable-string1>
51   tunable_option: <glibc-tunable-string2>
52
53   # Test descriptions can use multiple lines, which will all be merged
54   # together, so order is not important.
55   testname3: <test-description-line>
56   <test-description-line>
57   <test-description-line>
58   ...
59   output: <expected-output-string>
60
61   # 'testname3' will be run and compared two times, for both
62   # GLIBC_TUNABLES=<glibc-tunable-string1> and
63   # GLIBC_TUNABLES=<glibc-tunable-string2>.  This can be cleared and reset by the
64   # 'clear_tunables' command:
65   clear_tunables
66
67   # Multiple expected outputs can also be specified, with an associated
68   # tunable option in (), which multiple tests will be run with each
69   # GLIBC_TUNABLES=... option tried.
70   testname4:
71   <test-description-line>
72   ...
73   output(<glibc-tunable-string1>): <expected-output-string-1>
74   output(<glibc-tunable-string2>): <expected-output-string-2>
75   # Individual tunable output cases can be XFAILed, though note that
76   # this will have the effect of XFAILing the entire 'testname4' test
77   # in the final top-level tests.sum summary.
78   xfail_output(<glibc-tunable-string3>): <expected-output-string-3>
79
80   # When multiple outputs (with specific tunable strings) are specified,
81   # these take priority over any active 'tunable_option' settings.
82
83   # When a test is meant to be placed under 'xtests' (not run under
84   # "make check", but only when "make xtests" is used), the testcase name can be
85   # declared using 'xtest(<test-name>)':
86   ...
87   xtest(test-too-big1): <test-description>
88   output: <expected-output-string>
89   ...
90
91   # Do note that under current elf/Makefile organization, for such a xtest case,
92   # while the test execution is only run under 'make xtests', the associated
93   # DSOs are always built even under 'make check'.
94
95 On the description language used, an example description line string:
96
97   a->b!->[cdef];c=>g=>h;{+c;%c;-c}->a
98
99 Each identifier represents a shared object module, currently sequences of
100 letters/digits are allowed, case-sensitive.
101
102 All such shared objects have a constructor/destructor generated for them
103 that emits its name followed by a '>' for constructors, and '<' followed by
104 its name for destructors, e.g. if the name is 'obj1', then "obj1>" and "<obj1"
105 is printed by its constructor/destructor respectively.
106
107 The -> operator specifies a link time dependency, these can be chained for
108 convenience (e.g. a->b->c->d).
109
110 The => operator creates a call-reference, e.g. for a=>b, an fn_a() function
111 is created inside module 'a', which calls fn_b() in module 'b'.
112 These module functions emit 'name()' output in nested form,
113 e.g. a=>b emits 'a(b())'
114
115 For single character object names, square brackets [] in the description
116 allows specifying multiple objects; e.g. a->[bcd]->e is equivalent to
117  a->b->e;a->c->e;a->d->e
118
119 The () parenthesis construct with space separated names is also allowed for
120 specifying objects.  For names with integer suffixes a range can also be used,
121 e.g. (foo1 bar2-5), specifies DSOs foo1, bar2, bar2, bar3, bar4, bar5.
122
123 A {} construct specifies the main test program, and its link dependencies
124 are also specified using ->.  Inside {}, a few ;-separated constructs are
125 allowed:
126          +a   Loads module a using dlopen(RTLD_LAZY|RTLD_GLOBAL)
127          ^a   Loads module a using dlopen(RTLD_LAZY)
128          %a   Use dlsym() to load and call fn_a()
129          @a   Calls fn_a() directly.
130          -a   Unloads module a using dlclose()
131
132 The generated main program outputs '{' '}' with all output from above
133 constructs in between.  The other output before/after {} are the ordered
134 constructor/destructor output.
135
136 If no {} construct is present, a default empty main program is linked
137 against all objects which have no dependency linked to it. e.g. for
138 '[ab]->c;d->e', the default main program is equivalent to '{}->[abd]'
139
140 Sometimes for very complex or large testcases, besides specifying a
141 few explicit dependencies from main{}, the above default dependency
142 behavior is still useful to automatically have, but is turned off
143 upon specifying a single explicit {}->dso_name.
144 In this case, add {}->* to explicitly add this generation behavior:
145
146    # Main program links to 'foo', and all other objects which have no
147    # dependency linked to it.
148    {}->foo,{}->*
149
150 Note that '*' works not only on main{}, but can be used as the
151 dependency target of any object.  Note that it only works as a target,
152 not a dependency source.
153
154 The '!' operator after object names turns on permutation of its
155 dependencies, e.g. while a->[bcd] only generates one set of objects,
156 with 'a.so' built with a link line of "b.so c.so d.so", for a!->[bcd]
157 permutations of a's dependencies creates multiple testcases with
158 different link line orders: "b.so c.so d.so", "c.so b.so d.so",
159 "b.so d.so c.so", etc.  Note that for a <test-name> specified on
160 the script command-line, multiple <test-name_1>, <test-name_2>, etc.
161 tests will be generated (e.g. for a!->[bc]!->[de], eight tests with
162 different link orders for a, b, and c will be generated)
163
164 It is possible to specify the ELF soname field for an object or the
165 main program:
166    # DSO 'a' will be linked with the appropriate -Wl,-soname=x setting
167    a->b->c;soname(a)=x
168    # The the main program can also have a soname specified
169    soname({})=y
170
171 This can be used to test how ld.so behaves when objects and/or the
172 main program have such a field set.
173
174
175 Strings Output by Generated Testcase Programs
176
177 The text output produced by a generated testcase consists of three main
178 parts:
179   1. The constructors' output
180   2. Output from the main program
181   3. Destructors' output
182
183 To see by example, a simple test description "a->b->c" generates a testcase
184 that when run, outputs: "c>b>a>{}<a<b<c"
185
186 Each generated DSO constructor prints its name followed by a '>' character,
187 and the "c>b>a" part above is the full constructor output by all DSOs, the
188 order indicating that DSO 'c', which does not depend on any other DSO, has
189 its constructor run first, followed by 'b' and then 'a'.
190
191 Destructor output for each DSO is a '<' character followed by its name,
192 reflecting its reverse nature of constructors.  In the above example, the
193 destructor output part is "<a<b<c".
194
195 The middle "{}" part is the main program.  In this simple example, nothing
196 was specified for the main program, so by default it is implicitly linked
197 to the DSO 'a' (with no other DSOs depending on it) and only prints the
198 brackets {} with no actions inside.
199
200 To see an example with actions inside the main program, lets see an example
201 description: c->g=>h;{+c;%c;-c}->a->h
202
203 This produces a testcase, that when executed outputs:
204              h>a>{+c[g>c>];%c();-c[<c<g];}<a<h
205
206 The constructor and destructor parts display the a->h dependency as expected.
207 Inside the main program, the "+c" action triggers a dlopen() of DSO 'c',
208 causing another chain of constructors "g>c>" to be triggered.  Here it is
209 displayed inside [] brackets for each dlopen call.  The same is done for "-c",
210 a dlclose() of 'c'.
211
212 The "%c" output is due to calling to fn_c() inside DSO 'c', this comprises
213 of two parts: the '%' character is printed by the caller, here it is the main
214 program.  The 'c' character is printed from inside fn_c().  The '%' character
215 indicates that this is called by a dlsym() of "fn_c".  A '@' character would
216 mean a direct call (with a symbol reference).  These can all be controlled
217 by the main test program constructs documented earlier.
218
219 The output strings described here is the exact same form placed in
220 test description files' "output: <expected output>" line.
221 """
222
223 import sys
224 import re
225 import os
226 import subprocess
227 import argparse
228 from collections import OrderedDict
229 import itertools
230
231 # BUILD_GCC is only used under the --build option,
232 # which builds the generated testcase, including DSOs using BUILD_GCC.
233 # Mainly for testing purposes, especially debugging of this script,
234 # and can be changed here to another toolchain path if needed.
235 build_gcc = "gcc"
236
237 def get_parser():
238     parser = argparse.ArgumentParser("")
239     parser.add_argument("description",
240                          help="Description string of DSO dependency test to be "
241                          "generated (see script source for documentation of "
242                          "description language), either specified here as "
243                          "command line argument, or by input file using "
244                          "-f/--description-file option",
245                          nargs="?", default="")
246     parser.add_argument("test_name",
247                         help="Identifier for testcase being generated",
248                         nargs="?", default="")
249     parser.add_argument("--objpfx",
250                         help="Path to place generated files, defaults to "
251                         "current directory if none specified",
252                         nargs="?", default="./")
253     parser.add_argument("-m", "--output-makefile",
254                         help="File to write Makefile fragment to, defaults to "
255                         "stdout when option not present",
256                         nargs="?", default="")
257     parser.add_argument("-f", "--description-file",
258                         help="Input file containing testcase descriptions",
259                         nargs="?", default="")
260     parser.add_argument("--build", help="After C testcase generated, build it "
261                         "using gcc (for manual testing purposes)",
262                         action="store_true")
263     parser.add_argument("--debug-output",
264                         help="Prints some internal data "
265                         "structures; used for debugging of this script",
266                         action="store_true")
267     return parser
268
269 # Main script starts here.
270 cmdlineargs = get_parser().parse_args()
271 test_name = cmdlineargs.test_name
272 description = cmdlineargs.description
273 objpfx = cmdlineargs.objpfx
274 description_file = cmdlineargs.description_file
275 output_makefile = cmdlineargs.output_makefile
276 makefile = ""
277 default_tunable_options = []
278
279 current_input_lineno = 0
280 def error(msg):
281     global current_input_lineno
282     print("Error: %s%s" % ((("Line %d, " % current_input_lineno)
283                             if current_input_lineno != 0 else ""),
284                            msg))
285     exit(1)
286
287 if(test_name or description) and description_file:
288     error("both command-line testcase and input file specified")
289 if test_name and not description:
290     error("command-line testcase name without description string")
291
292 # Main class type describing a testcase.
293 class TestDescr:
294     def __init__(self):
295         self.objs = []              # list of all DSO objects
296         self.deps = OrderedDict()   # map of DSO object -> list of dependencies
297
298         # map of DSO object -> list of call refs
299         self.callrefs = OrderedDict()
300
301         # map of DSO object -> list of permutations of dependencies
302         self.dep_permutations = OrderedDict()
303
304         # map of DSO object -> SONAME of object (if one is specified)
305         self.soname_map = OrderedDict()
306
307         # list of main program operations
308         self.main_program = []
309         # set if default dependencies added to main
310         self.main_program_default_deps = True
311
312         self.test_name = ""                   # name of testcase
313         self.expected_outputs = OrderedDict() # expected outputs of testcase
314         self.xfail = False                    # set if this is a XFAIL testcase
315         self.xtest = False                    # set if this is put under 'xtests'
316
317     # Add 'object -> [object, object, ...]' relations to CURR_MAP
318     def __add_deps_internal(self, src_objs, dst_objs, curr_map):
319         for src in src_objs:
320             for dst in dst_objs:
321                 if not src in curr_map:
322                     curr_map[src] = []
323                 if not dst in curr_map[src]:
324                     curr_map[src].append(dst)
325     def add_deps(self, src_objs, dst_objs):
326         self.__add_deps_internal(src_objs, dst_objs, self.deps)
327     def add_callrefs(self, src_objs, dst_objs):
328         self.__add_deps_internal(src_objs, dst_objs, self.callrefs)
329
330 # Process commands inside the {} construct.
331 # Note that throughout this script, the main program object is represented
332 # by the '#' string.
333 def process_main_program(test_descr, mainprog_str):
334     if mainprog_str:
335         test_descr.main_program = mainprog_str.split(';')
336     for s in test_descr.main_program:
337         m = re.match(r"^([+\-%^@])([0-9a-zA-Z]+)$", s)
338         if not m:
339             error("'%s' is not recognized main program operation" % (s))
340         opr = m.group(1)
341         obj = m.group(2)
342         if not obj in test_descr.objs:
343             test_descr.objs.append(obj)
344         if opr == '%' or opr == '@':
345             test_descr.add_callrefs(['#'], [obj])
346     # We have a main program specified, turn this off
347     test_descr.main_program_default_deps = False
348
349 # For(a1 a2 b1-12) object set descriptions, expand into an object list
350 def expand_object_set_string(descr_str):
351     obj_list = []
352     descr_list = descr_str.split()
353     for descr in descr_list:
354         m = re.match(r"^([a-zA-Z][0-9a-zA-Z]*)(-[0-9]+)?$", descr)
355         if not m:
356             error("'%s' is not a valid object set description" % (descr))
357         obj = m.group(1)
358         idx_end = m.group(2)
359         if not idx_end:
360             if not obj in obj_list:
361                 obj_list.append(obj)
362         else:
363             idx_end = int(idx_end[1:])
364             m = re.match(r"^([0-9a-zA-Z][a-zA-Z]*)([0-9]+)$", obj)
365             if not m:
366                 error("object description '%s' is malformed" % (obj))
367             obj_name = m.group(1)
368             idx_start = int(m.group (2))
369             if idx_start > idx_end:
370                 error("index range %s-%s invalid" % (idx_start, idx_end))
371             for i in range(idx_start, idx_end + 1):
372                 o = obj_name + str(i)
373                 if not o in obj_list:
374                     obj_list.append(o)
375     return obj_list
376
377 # Lexer for tokens
378 tokenspec = [ ("SONAME",   r"soname\(([0-9a-zA-Z{}]+)\)=([0-9a-zA-Z]+)"),
379               ("OBJ",      r"([0-9a-zA-Z]+)"),
380               ("DEP",      r"->"),
381               ("CALLREF",  r"=>"),
382               ("OBJSET",   r"\[([0-9a-zA-Z]+)\]"),
383               ("OBJSET2",  r"\(([0-9a-zA-Z \-]+)\)"),
384               ("OBJSET3",  r"\*"),
385               ("PROG",     r"{([0-9a-zA-Z;+^\-%@]*)}"),
386               ("PERMUTE",  r"!"),
387               ("SEMICOL",  r";"),
388               ("ERROR",    r".") ]
389 tok_re = '|'.join('(?P<%s>%s)' % pair for pair in tokenspec)
390
391 # Main line parser of description language
392 def parse_description_string(t, descr_str):
393     # State used when parsing dependencies
394     curr_objs = []
395     in_dep = False
396     in_callref = False
397     def clear_dep_state():
398         nonlocal in_dep, in_callref
399         in_dep = in_callref = False
400
401     for m in re.finditer(tok_re, descr_str):
402         kind = m.lastgroup
403         value = m.group()
404         if kind == "SONAME":
405             s = re.match(r"soname\(([0-9a-zA-Z{}]+)\)=([0-9a-zA-Z]+)", value)
406             obj = s.group(1)
407             val = s.group(2)
408             if obj == "{}":
409                 if '#' in t.soname_map:
410                     error("soname of main program already set")
411                 # Adjust to internal name
412                 obj = '#'
413             else:
414                 if re.match(r"[{}]", obj):
415                     error("invalid object name '%s'" % (obj))
416                 if not obj in t.objs:
417                     error("'%s' is not name of already defined object" % (obj))
418                 if obj in t.soname_map:
419                     error("'%s' already has soname of '%s' set"
420                           % (obj, t.soname_map[obj]))
421             t.soname_map[obj] = val
422
423         elif kind == "OBJ":
424             if in_dep:
425                 t.add_deps(curr_objs, [value])
426             elif in_callref:
427                 t.add_callrefs(curr_objs, [value])
428             clear_dep_state()
429             curr_objs = [value]
430             if not value in t.objs:
431                 t.objs.append(value)
432
433         elif kind == "OBJSET":
434             objset = value[1:len(value)-1]
435             if in_dep:
436                 t.add_deps(curr_objs, list (objset))
437             elif in_callref:
438                 t.add_callrefs(curr_objs, list (objset))
439             clear_dep_state()
440             curr_objs = list(objset)
441             for o in list(objset):
442                 if not o in t.objs:
443                     t.objs.append(o)
444
445         elif kind == "OBJSET2":
446             descr_str = value[1:len(value)-1]
447             descr_str.strip()
448             objs = expand_object_set_string(descr_str)
449             if not objs:
450                 error("empty object set '%s'" % (value))
451             if in_dep:
452                 t.add_deps(curr_objs, objs)
453             elif in_callref:
454                 t.add_callrefs(curr_objs, objs)
455             clear_dep_state()
456             curr_objs = objs
457             for o in objs:
458                 if not o in t.objs:
459                     t.objs.append(o)
460
461         elif kind == "OBJSET3":
462             if in_dep:
463                 t.add_deps(curr_objs, ['*'])
464             elif in_callref:
465                 t.add_callrefs(curr_objs, ['*'])
466             else:
467                 error("non-dependence target set '*' can only be used "
468                       "as target of ->/=> operations")
469             clear_dep_state()
470             curr_objs = ['*']
471
472         elif kind == "PERMUTE":
473             if in_dep or in_callref:
474                 error("syntax error, permute operation invalid here")
475             if not curr_objs:
476                 error("syntax error, no objects to permute here")
477
478             for obj in curr_objs:
479                 if not obj in t.dep_permutations:
480                     # Signal this object has permuted dependencies
481                     t.dep_permutations[obj] = []
482
483         elif kind == "PROG":
484             if t.main_program:
485                 error("cannot have more than one main program")
486             if in_dep:
487                 error("objects cannot have dependency on main program")
488             if in_callref:
489                 # TODO: A DSO can resolve to a symbol in the main binary,
490                 # which we syntactically allow here, but haven't yet
491                 # implemented.
492                 t.add_callrefs(curr_objs, ["#"])
493             process_main_program(t, value[1:len(value)-1])
494             clear_dep_state()
495             curr_objs = ["#"]
496
497         elif kind == "DEP":
498             if in_dep or in_callref:
499                 error("syntax error, multiple contiguous ->,=> operations")
500             if '*' in curr_objs:
501                 error("non-dependence target set '*' can only be used "
502                       "as target of ->/=> operations")
503             in_dep = True
504
505         elif kind == "CALLREF":
506             if in_dep or in_callref:
507                 error("syntax error, multiple contiguous ->,=> operations")
508             if '*' in curr_objs:
509                 error("non-dependence target set '*' can only be used "
510                       "as target of ->/=> operations")
511             in_callref = True
512
513         elif kind == "SEMICOL":
514             curr_objs = []
515             clear_dep_state()
516
517         else:
518             error("unknown token '%s'" % (value))
519     return t
520
521 # Main routine to process each testcase description
522 def process_testcase(t):
523     global objpfx
524     assert t.test_name
525
526     base_test_name = t.test_name
527     test_subdir = base_test_name + "-dir"
528     testpfx = objpfx + test_subdir + "/"
529     test_srcdir = "dso-sort-tests-src/"
530     testpfx_src = objpfx + test_srcdir
531
532     if not os.path.exists(testpfx):
533         os.mkdir(testpfx)
534     if not os.path.exists(testpfx_src):
535         os.mkdir(testpfx_src)
536
537     def find_objs_not_depended_on(t):
538         objs_not_depended_on = []
539         for obj in t.objs:
540             skip = False
541             for r in t.deps.items():
542                 if obj in r[1]:
543                     skip = True
544                     break
545             if not skip:
546                 objs_not_depended_on.append(obj)
547         return objs_not_depended_on
548
549     non_dep_tgt_objs = find_objs_not_depended_on(t)
550     for obj in t.objs:
551         if obj in t.deps:
552             deps = t.deps[obj]
553             if '*' in deps:
554                 deps.remove('*')
555                 t.add_deps([obj], non_dep_tgt_objs)
556         if obj in t.callrefs:
557             deps = t.callrefs[obj]
558             if '*' in deps:
559                 deps.remove('*')
560                 t.add_callrefs([obj], non_dep_tgt_objs)
561     if "#" in t.deps:
562         deps = t.deps["#"]
563         if '*' in deps:
564             deps.remove('*')
565             t.add_deps(["#"], non_dep_tgt_objs)
566
567     # If no main program was specified in dependency description, make a
568     # default main program with deps pointing to all DSOs which are not
569     # depended by another DSO.
570     if t.main_program_default_deps:
571         main_deps = non_dep_tgt_objs
572         if not main_deps:
573             error("no objects for default main program to point "
574                   "dependency to(all objects strongly connected?)")
575         t.add_deps(["#"], main_deps)
576
577     # Some debug output
578     if cmdlineargs.debug_output:
579         print("Testcase: %s" % (t.test_name))
580         print("All objects: %s" % (t.objs))
581         print("--- Static link dependencies ---")
582         for r in t.deps.items():
583             print("%s -> %s" % (r[0], r[1]))
584         print("--- Objects whose dependencies are to be permuted ---")
585         for r in t.dep_permutations.items():
586             print("%s" % (r[0]))
587         print("--- Call reference dependencies ---")
588         for r in t.callrefs.items():
589             print("%s => %s" % (r[0], r[1]))
590         print("--- main program ---")
591         print(t.main_program)
592
593     # Main testcase generation routine, does Makefile fragment generation,
594     # testcase source generation, and if --build specified builds testcase.
595     def generate_testcase(test_descr, test_suffix):
596
597         test_name = test_descr.test_name + test_suffix
598
599         # Print out needed Makefile fragments for use in glibc/elf/Makefile.
600         module_names = ""
601         for o in test_descr.objs:
602             rule = ("$(objpfx)" + test_subdir + "/" + test_name
603                     + "-" + o + ".os: $(objpfx)" + test_srcdir
604                     + test_name + "-" + o + ".c\n"
605                     "\t$(compile.c) $(OUTPUT_OPTION)\n")
606             makefile.write (rule)
607             module_names += " " + test_subdir + "/" + test_name + "-" + o
608         makefile.write("modules-names +=%s\n" % (module_names))
609
610         # Depth-first traversal, executing FN(OBJ) in post-order
611         def dfs(t, fn):
612             def dfs_rec(obj, fn, obj_visited):
613                 if obj in obj_visited:
614                     return
615                 obj_visited[obj] = True
616                 if obj in t.deps:
617                     for dep in t.deps[obj]:
618                         dfs_rec(dep, fn, obj_visited)
619                 fn(obj)
620
621             obj_visited = {}
622             for obj in t.objs:
623                 dfs_rec(obj, fn, obj_visited)
624
625         # Generate link dependencies for all DSOs, done in a DFS fashion.
626         # Usually this doesn't need to be this complex, just listing the direct
627         # dependencies is enough.  However to support creating circular
628         # dependency situations, traversing it by DFS and tracking processing
629         # status is the natural way to do it.
630         obj_processed = {}
631         fake_created = {}
632         def gen_link_deps(obj):
633             if obj in test_descr.deps:
634                 dso = test_subdir + "/" + test_name + "-" + obj + ".so"
635                 dependencies = ""
636                 for dep in test_descr.deps[obj]:
637                     if dep in obj_processed:
638                         depstr = (" $(objpfx)" + test_subdir + "/"
639                                   + test_name + "-" + dep + ".so")
640                     else:
641                         # A circular dependency is satisfied by making a
642                         # fake DSO tagged with the correct SONAME
643                         depstr = (" $(objpfx)" + test_subdir + "/"
644                                   + test_name + "-" + dep + ".FAKE.so")
645                         # Create empty C file and Makefile fragments for fake
646                         # object.  This only needs to be done at most once for
647                         # an object name.
648                         if not dep in fake_created:
649                             f = open(testpfx_src + test_name + "-" + dep
650                                      + ".FAKE.c", "w")
651                             f.write(" \n")
652                             f.close()
653                             # Generate rule to create fake object
654                             makefile.write \
655                                 ("LDFLAGS-%s = -Wl,--no-as-needed "
656                                  "-Wl,-soname=%s\n"
657                                  % (test_name + "-" + dep + ".FAKE.so",
658                                     ("$(objpfx)" + test_subdir + "/"
659                                      + test_name + "-" + dep + ".so")))
660                             rule = ("$(objpfx)" + test_subdir + "/"
661                                     + test_name + "-" + dep + ".FAKE.os: "
662                                     "$(objpfx)" + test_srcdir
663                                     + test_name + "-" + dep + ".FAKE.c\n"
664                                     "\t$(compile.c) $(OUTPUT_OPTION)\n")
665                             makefile.write (rule)
666                             makefile.write \
667                                 ("modules-names += %s\n"
668                                  % (test_subdir + "/"
669                                     + test_name + "-" + dep + ".FAKE"))
670                             fake_created[dep] = True
671                     dependencies += depstr
672                 makefile.write("$(objpfx)%s:%s\n" % (dso, dependencies))
673             # Mark obj as processed
674             obj_processed[obj] = True
675
676         dfs(test_descr, gen_link_deps)
677
678         # Print LDFLAGS-* and *-no-z-defs
679         for o in test_descr.objs:
680             dso = test_name + "-" + o + ".so"
681             ldflags = "-Wl,--no-as-needed"
682             if o in test_descr.soname_map:
683                 soname = ("$(objpfx)" + test_subdir + "/"
684                           + test_name + "-"
685                           + test_descr.soname_map[o] + ".so")
686                 ldflags += (" -Wl,-soname=" + soname)
687             makefile.write("LDFLAGS-%s = %s\n" % (dso, ldflags))
688             if o in test_descr.callrefs:
689                 makefile.write("%s-no-z-defs = yes\n" % (dso))
690
691         # Print dependencies for main test program.
692         depstr = ""
693         if '#' in test_descr.deps:
694             for o in test_descr.deps['#']:
695                 depstr += (" $(objpfx)" + test_subdir + "/"
696                            + test_name + "-" + o + ".so")
697         makefile.write("$(objpfx)%s/%s:%s\n" % (test_subdir, test_name, depstr))
698         ldflags = "-Wl,--no-as-needed"
699         if '#' in test_descr.soname_map:
700             soname = ("$(objpfx)" + test_subdir + "/"
701                       + test_name + "-"
702                       + test_descr.soname_map['#'] + ".so")
703             ldflags += (" -Wl,-soname=" + soname)
704         makefile.write("LDFLAGS-%s = %s\n" % (test_name, ldflags))
705         rule = ("$(objpfx)" + test_subdir + "/" + test_name + ".o: "
706                 "$(objpfx)" + test_srcdir + test_name + ".c\n"
707                 "\t$(compile.c) $(OUTPUT_OPTION)\n")
708         makefile.write (rule)
709
710         # Ensure that all shared objects are built before running the
711         # test, whether there link-time dependencies or not.
712         depobjs = ["$(objpfx){}/{}-{}.so".format(test_subdir, test_name, dep)
713                    for dep in test_descr.objs]
714         makefile.write("$(objpfx){}.out: {}\n".format(
715             base_test_name, " ".join(depobjs)))
716
717         # Add main executable to test-srcs
718         makefile.write("test-srcs += %s/%s\n" % (test_subdir, test_name))
719         # Add dependency on main executable of test
720         makefile.write("$(objpfx)%s.out: $(objpfx)%s/%s\n"
721                         % (base_test_name, test_subdir, test_name))
722
723         for r in test_descr.expected_outputs.items():
724             tunable_options = []
725             specific_tunable = r[0]
726             xfail = r[1][1]
727             if specific_tunable != "":
728                 tunable_options = [specific_tunable]
729             else:
730                 tunable_options = default_tunable_options
731                 if not tunable_options:
732                     tunable_options = [""]
733
734             for tunable in tunable_options:
735                 tunable_env = ""
736                 tunable_sfx = ""
737                 exp_tunable_sfx = ""
738                 if tunable:
739                     tunable_env = "GLIBC_TUNABLES=%s " % tunable
740                     tunable_sfx = "-" + tunable.replace("=","_")
741                 if specific_tunable:
742                     tunable_sfx = "-" + specific_tunable.replace("=","_")
743                     exp_tunable_sfx = tunable_sfx
744                 tunable_descr = ("(%s)" % tunable_env.strip()
745                                  if tunable_env else "")
746                 # Write out fragment of shell script for this single test.
747                 test_descr.sh.write \
748                     ("${test_wrapper_env} ${run_program_env} %s\\\n"
749                      "${common_objpfx}support/test-run-command \\\n"
750                      "${common_objpfx}elf/ld.so \\\n"
751                      "--library-path ${common_objpfx}elf/%s:"
752                      "${common_objpfx}elf:${common_objpfx}.:"
753                      "${common_objpfx}dlfcn \\\n"
754                      "${common_objpfx}elf/%s/%s > \\\n"
755                      "  ${common_objpfx}elf/%s/%s%s.output\n"
756                      % (tunable_env ,test_subdir,
757                         test_subdir, test_name, test_subdir, test_name,
758                         tunable_sfx))
759                 # Generate a run of each test and compare with expected out
760                 test_descr.sh.write \
761                     ("if [ $? -ne 0 ]; then\n"
762                      "  echo '%sFAIL: %s%s execution test'\n"
763                      "  something_failed=true\n"
764                      "else\n"
765                      "  diff -wu ${common_objpfx}elf/%s/%s%s.output \\\n"
766                      "           ${common_objpfx}elf/%s%s%s.exp\n"
767                      "  if [ $? -ne 0 ]; then\n"
768                      "    echo '%sFAIL: %s%s expected output comparison'\n"
769                      "    something_failed=true\n"
770                      "  fi\n"
771                      "fi\n"
772                      % (("X" if xfail else ""), test_name, tunable_descr,
773                         test_subdir, test_name, tunable_sfx,
774                         test_srcdir, base_test_name, exp_tunable_sfx,
775                         ("X" if xfail else ""), test_name, tunable_descr))
776
777         # Generate C files according to dependency and calling relations from
778         # description string.
779         for obj in test_descr.objs:
780             src_name = test_name + "-" + obj + ".c"
781             f = open(testpfx_src + src_name, "w")
782             if obj in test_descr.callrefs:
783                 called_objs = test_descr.callrefs[obj]
784                 for callee in called_objs:
785                     f.write("extern void fn_%s (void);\n" % (callee))
786             if len(obj) == 1:
787                 f.write("extern int putchar(int);\n")
788                 f.write("static void __attribute__((constructor)) " +
789                          "init(void){putchar('%s');putchar('>');}\n" % (obj))
790                 f.write("static void __attribute__((destructor)) " +
791                          "fini(void){putchar('<');putchar('%s');}\n" % (obj))
792             else:
793                 f.write('extern int printf(const char *, ...);\n')
794                 f.write('static void __attribute__((constructor)) ' +
795                          'init(void){printf("%s>");}\n' % (obj))
796                 f.write('static void __attribute__((destructor)) ' +
797                          'fini(void){printf("<%s");}\n' % (obj))
798             if obj in test_descr.callrefs:
799                 called_objs = test_descr.callrefs[obj]
800                 if len(obj) != 1:
801                     f.write("extern int putchar(int);\n")
802                 f.write("void fn_%s (void) {\n" % (obj))
803                 if len(obj) == 1:
804                     f.write("  putchar ('%s');\n" % (obj));
805                     f.write("  putchar ('(');\n");
806                 else:
807                     f.write('  printf ("%s(");\n' % (obj));
808                 for callee in called_objs:
809                     f.write("  fn_%s ();\n" % (callee))
810                 f.write("  putchar (')');\n");
811                 f.write("}\n")
812             else:
813                 for callref in test_descr.callrefs.items():
814                     if obj in callref[1]:
815                         if len(obj) == 1:
816                             # We need to declare printf here in this case.
817                             f.write('extern int printf(const char *, ...);\n')
818                         f.write("void fn_%s (void) {\n" % (obj))
819                         f.write('  printf ("%s()");\n' % (obj))
820                         f.write("}\n")
821                         break
822             f.close()
823
824         # Open C file for writing main program
825         f = open(testpfx_src + test_name + ".c", "w")
826
827         # if there are some operations in main(), it means we need -ldl
828         f.write("#include <stdio.h>\n")
829         f.write("#include <stdlib.h>\n")
830         f.write("#include <dlfcn.h>\n")
831         for s in test_descr.main_program:
832             if s[0] == '@':
833                 f.write("extern void fn_%s (void);\n" % (s[1:]));
834         f.write("int main (void) {\n")
835         f.write("  putchar('{');\n")
836
837         # Helper routine for generating sanity checking code.
838         def put_fail_check(fail_cond, action_desc):
839             f.write('  if (%s) { printf ("\\n%s failed: %%s\\n", '
840                      'dlerror()); exit (1);}\n' % (fail_cond, action_desc))
841         i = 0
842         while i < len(test_descr.main_program):
843             s = test_descr.main_program[i]
844             obj = s[1:]
845             dso = test_name + "-" + obj
846             if s[0] == '+' or s[0] == '^':
847                 if s[0] == '+':
848                     dlopen_flags = "RTLD_LAZY|RTLD_GLOBAL"
849                     f.write("  putchar('+');\n");
850                 else:
851                     dlopen_flags = "RTLD_LAZY"
852                     f.write("  putchar(':');\n");
853                 if len(obj) == 1:
854                     f.write("  putchar('%s');\n" % (obj));
855                 else:
856                     f.write('  printf("%s");\n' % (obj));
857                 f.write("  putchar('[');\n");
858                 f.write('  void *%s = dlopen ("%s.so", %s);\n'
859                          % (obj, dso, dlopen_flags))
860                 put_fail_check("!%s" % (obj),
861                                 "%s.so dlopen" % (dso))
862                 f.write("  putchar(']');\n");
863             elif s[0] == '-':
864                 f.write("  putchar('-');\n");
865                 if len(obj) == 1:
866                     f.write("  putchar('%s');\n" % (obj));
867                 else:
868                     f.write('  printf("%s");\n' % (obj));
869                 f.write("  putchar('[');\n");
870                 put_fail_check("dlclose (%s) != 0" % (obj),
871                                 "%s.so dlclose" % (dso))
872                 f.write("  putchar(']');\n");
873             elif s[0] == '%':
874                 f.write("  putchar('%');\n");
875                 f.write('  void (*fn_%s)(void) = dlsym (%s, "fn_%s");\n'
876                          % (obj, obj, obj))
877                 put_fail_check("!fn_%s" % (obj),
878                                 "dlsym(fn_%s) from %s.so" % (obj, dso))
879                 f.write("  fn_%s ();\n" % (obj))
880             elif s[0] == '@':
881                 f.write("  putchar('@');\n");
882                 f.write("  fn_%s ();\n" % (obj))
883             f.write("  putchar(';');\n");
884             i += 1
885         f.write("  putchar('}');\n")
886         f.write("  return 0;\n")
887         f.write("}\n")
888         f.close()
889
890         # --build option processing: build generated sources using 'build_gcc'
891         if cmdlineargs.build:
892             # Helper routine to run a shell command, for running GCC below
893             def run_cmd(args):
894                 cmd = str.join(' ', args)
895                 if cmdlineargs.debug_output:
896                     print(cmd)
897                 p = subprocess.Popen(args)
898                 p.wait()
899                 if p.returncode != 0:
900                     error("error running command: %s" % (cmd))
901
902             # Compile individual .os files
903             for obj in test_descr.objs:
904                 src_name = test_name + "-" + obj + ".c"
905                 obj_name = test_name + "-" + obj + ".os"
906                 run_cmd([build_gcc, "-c", "-fPIC", testpfx_src + src_name,
907                           "-o", testpfx + obj_name])
908
909             obj_processed = {}
910             fake_created = {}
911             # Function to create <test_name>-<obj>.so
912             def build_dso(obj):
913                 obj_name = test_name + "-" + obj + ".os"
914                 dso_name = test_name + "-" + obj + ".so"
915                 deps = []
916                 if obj in test_descr.deps:
917                     for dep in test_descr.deps[obj]:
918                         if dep in obj_processed:
919                             deps.append(dep)
920                         else:
921                             deps.append(dep + ".FAKE")
922                             if not dep in fake_created:
923                                 base_name = testpfx + test_name + "-" + dep
924                                 src_base_name = (testpfx_src + test_name
925                                                  + "-" + dep)
926                                 cmd = [build_gcc, "-Wl,--no-as-needed",
927                                        ("-Wl,-soname=" + base_name + ".so"),
928                                        "-shared", base_name + ".FAKE.c",
929                                        "-o", src_base_name + ".FAKE.so"]
930                                 run_cmd(cmd)
931                                 fake_created[dep] = True
932                 dso_deps = map(lambda d: testpfx + test_name + "-" + d + ".so",
933                                deps)
934                 cmd = [build_gcc, "-shared", "-o", testpfx + dso_name,
935                        testpfx + obj_name, "-Wl,--no-as-needed"]
936                 if obj in test_descr.soname_map:
937                     soname = ("-Wl,-soname=" + testpfx + test_name + "-"
938                               + test_descr.soname_map[obj] + ".so")
939                     cmd += [soname]
940                 cmd += list(dso_deps)
941                 run_cmd(cmd)
942                 obj_processed[obj] = True
943
944             # Build all DSOs, this needs to be in topological dependency order,
945             # or link will fail
946             dfs(test_descr, build_dso)
947
948             # Build main program
949             deps = []
950             if '#' in test_descr.deps:
951                 deps = test_descr.deps['#']
952             main_deps = map(lambda d: testpfx + test_name + "-" + d + ".so",
953                             deps)
954             cmd = [build_gcc, "-Wl,--no-as-needed", "-o", testpfx + test_name,
955                    testpfx_src + test_name + ".c", "-L%s" % (os.getcwd()),
956                    "-Wl,-rpath-link=%s" % (os.getcwd())]
957             if '#' in test_descr.soname_map:
958                 soname = ("-Wl,-soname=" + testpfx + test_name + "-"
959                           + test_descr.soname_map['#'] + ".so")
960                 cmd += [soname]
961             cmd += list(main_deps)
962             run_cmd(cmd)
963
964     # Check if we need to enumerate permutations of dependencies
965     need_permutation_processing = False
966     if t.dep_permutations:
967         # Adjust dep_permutations into map of object -> dependency permutations
968         for r in t.dep_permutations.items():
969             obj = r[0]
970             if obj in t.deps and len(t.deps[obj]) > 1:
971                 deps = t.deps[obj]
972                 t.dep_permutations[obj] = list(itertools.permutations (deps))
973                 need_permutation_processing = True
974
975     def enum_permutations(t, perm_list):
976         test_subindex = 1
977         curr_perms = []
978         def enum_permutations_rec(t, perm_list):
979             nonlocal test_subindex, curr_perms
980             if len(perm_list) >= 1:
981                 curr = perm_list[0]
982                 obj = curr[0]
983                 perms = curr[1]
984                 if not perms:
985                     # This may be an empty list if no multiple dependencies to
986                     # permute were found, skip to next in this case
987                     enum_permutations_rec(t, perm_list[1:])
988                 else:
989                     for deps in perms:
990                         t.deps[obj] = deps
991                         permstr = "" if obj == "#" else obj + "_"
992                         permstr += str.join('', deps)
993                         curr_perms.append(permstr)
994                         enum_permutations_rec(t, perm_list[1:])
995                         curr_perms = curr_perms[0:len(curr_perms)-1]
996             else:
997                 # t.deps is now instantiated with one dependency order
998                 # permutation(across all objects that have multiple
999                 # permutations), now process a testcase
1000                 generate_testcase(t, ("_" + str (test_subindex)
1001                                        + "-" + str.join('-', curr_perms)))
1002                 test_subindex += 1
1003         enum_permutations_rec(t, perm_list)
1004
1005     # Create *.exp files with expected outputs
1006     for r in t.expected_outputs.items():
1007         sfx = ""
1008         if r[0] != "":
1009             sfx = "-" + r[0].replace("=","_")
1010         f = open(testpfx_src + t.test_name + sfx + ".exp", "w")
1011         (output, xfail) = r[1]
1012         f.write('%s' % output)
1013         f.close()
1014
1015     # Create header part of top-level testcase shell script, to wrap execution
1016     # and output comparison together.
1017     t.sh = open(testpfx_src + t.test_name + ".sh", "w")
1018     t.sh.write("#!/bin/sh\n")
1019     t.sh.write("# Test driver for %s, generated by "
1020                 "dso-ordering-test.py\n" % (t.test_name))
1021     t.sh.write("common_objpfx=$1\n")
1022     t.sh.write("test_wrapper_env=$2\n")
1023     t.sh.write("run_program_env=$3\n")
1024     t.sh.write("something_failed=false\n")
1025
1026     # Starting part of Makefile fragment
1027     makefile.write("ifeq (yes,$(build-shared))\n")
1028
1029     if need_permutation_processing:
1030         enum_permutations(t, list (t.dep_permutations.items()))
1031     else:
1032         # We have no permutations to enumerate, just process testcase normally
1033         generate_testcase(t, "")
1034
1035     # If testcase is XFAIL, indicate so
1036     if t.xfail:
1037         makefile.write("test-xfail-%s = yes\n" % t.test_name)
1038
1039     # Output end part of Makefile fragment
1040     expected_output_files = ""
1041     for r in t.expected_outputs.items():
1042         sfx = ""
1043         if r[0] != "":
1044             sfx = "-" + r[0].replace("=","_")
1045         expected_output_files += " $(objpfx)%s%s%s.exp" % (test_srcdir,
1046                                                             t.test_name, sfx)
1047     makefile.write \
1048     ("$(objpfx)%s.out: $(objpfx)%s%s.sh%s "
1049      "$(common-objpfx)support/test-run-command\n"
1050      % (t.test_name, test_srcdir, t.test_name,
1051         expected_output_files))
1052     makefile.write("\t$(SHELL) $< $(common-objpfx) '$(test-wrapper-env)' "
1053                     "'$(run-program-env)' > $@; $(evaluate-test)\n")
1054     makefile.write("ifeq ($(run-built-tests),yes)\n")
1055     if t.xtest:
1056         makefile.write("xtests-special += $(objpfx)%s.out\n" % (t.test_name))
1057     else:
1058         makefile.write("tests-special += $(objpfx)%s.out\n" % (t.test_name))
1059     makefile.write("endif\n")
1060     makefile.write("endif\n")
1061
1062     # Write ending part of shell script generation
1063     t.sh.write("if $something_failed; then\n"
1064                 "  exit 1\n"
1065                 "else\n"
1066                 "  echo '%sPASS: all tests for %s succeeded'\n"
1067                 "  exit 0\n"
1068                 "fi\n" % (("X" if t.xfail else ""),
1069                           t.test_name))
1070     t.sh.close()
1071
1072 # Description file parsing
1073 def parse_description_file(filename):
1074     global default_tunable_options
1075     global current_input_lineno
1076     f = open(filename)
1077     if not f:
1078         error("cannot open description file %s" % (filename))
1079     descrfile_lines = f.readlines()
1080     t = None
1081     for line in descrfile_lines:
1082         p = re.compile(r"#.*$")
1083         line = p.sub("", line) # Filter out comments
1084         line = line.strip() # Remove excess whitespace
1085         current_input_lineno += 1
1086
1087         m = re.match(r"^tunable_option:\s*(.*)$", line)
1088         if m:
1089             if m.group(1) == "":
1090                 error("tunable option cannot be empty")
1091             default_tunable_options.append(m.group (1))
1092             continue
1093
1094         m = re.match(r"^clear_tunables$", line)
1095         if m:
1096             default_tunable_options = []
1097             continue
1098
1099         m = re.match(r"^([^:]+):\s*(.*)$", line)
1100         if m:
1101             lhs = m.group(1)
1102             o = re.match(r"^output(.*)$", lhs)
1103             xfail = False
1104             if not o:
1105                 o = re.match(r"^xfail_output(.*)$", lhs)
1106                 if o:
1107                     xfail = True;
1108             if o:
1109                 if not t:
1110                     error("output specification without testcase description")
1111                 tsstr = ""
1112                 if o.group(1):
1113                     ts = re.match(r"^\(([a-zA-Z0-9_.=]*)\)$", o.group (1))
1114                     if not ts:
1115                         error("tunable option malformed '%s'" % o.group(1))
1116                     tsstr = ts.group(1)
1117                 t.expected_outputs[tsstr] = (m.group(2), xfail)
1118                 # Any tunable option XFAILed means entire testcase
1119                 # is XFAIL/XPASS
1120                 t.xfail |= xfail
1121             else:
1122                 if t:
1123                     # Starting a new test description, end and process
1124                     # current one.
1125                     process_testcase(t)
1126                 t = TestDescr()
1127                 x = re.match(r"^xtest\((.*)\)$", lhs)
1128                 if x:
1129                     t.xtest = True
1130                     t.test_name = x.group(1)
1131                 else:
1132                     t.test_name = lhs
1133                 descr_string = m.group(2)
1134                 parse_description_string(t, descr_string)
1135             continue
1136         else:
1137             if line:
1138                 if not t:
1139                     error("no active testcase description")
1140                 parse_description_string(t, line)
1141     # Process last completed test description
1142     if t:
1143         process_testcase(t)
1144
1145 # Setup Makefile output to file or stdout as selected
1146 if output_makefile:
1147     output_makefile_dir = os.path.dirname(output_makefile)
1148     if output_makefile_dir:
1149         os.makedirs(output_makefile_dir, exist_ok = True)
1150     makefile = open(output_makefile, "w")
1151 else:
1152     makefile = open(sys.stdout.fileno (), "w")
1153
1154 # Finally, the main top-level calling of above parsing routines.
1155 if description_file:
1156     parse_description_file(description_file)
1157 else:
1158     t = TestDescr()
1159     t.test_name = test_name
1160     parse_description_string(t, description)
1161     process_testcase(t)
1162
1163 # Close Makefile fragment output
1164 makefile.close()