NEWS: Add advisories.
[glibc.git] / scripts / dso-ordering-test.py
blob95243f85aeb7f0d7861d002ebacd8d9728bdc9c2
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.
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.
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.
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/>.
20 """Generate testcase files and Makefile fragments for DSO sorting test
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.
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.
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.
36 The format of the input test description files are:
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>
43 # Tests can be marked to be XFAIL by using 'xfail_output' instead
44 testname2: <test-description-line>
45 xfail_output: <expected-output-string>
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>
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>
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
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>
80 # When multiple outputs (with specific tunable strings) are specified,
81 # these take priority over any active 'tunable_option' settings.
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 ...
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'.
95 On the description language used, an example description line string:
97 a->b!->[cdef];c=>g=>h;{+c;%c;-c}->a
99 Each identifier represents a shared object module, currently sequences of
100 letters/digits are allowed, case-sensitive.
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.
107 The -> operator specifies a link time dependency, these can be chained for
108 convenience (e.g. a->b->c->d).
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())'
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
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.
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()
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.
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]'
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:
146 # Main program links to 'foo', and all other objects which have no
147 # dependency linked to it.
148 {}->foo,{}->*
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.
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)
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
171 This can be used to test how ld.so behaves when objects and/or the
172 main program have such a field set.
175 Strings Output by Generated Testcase Programs
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
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"
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'.
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".
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.
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
203 This produces a testcase, that when executed outputs:
204 h>a>{+c[g>c>];%c();-c[<c<g];}<a<h
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'.
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.
219 The output strings described here is the exact same form placed in
220 test description files' "output: <expected output>" line.
223 import sys
224 import re
225 import os
226 import subprocess
227 import argparse
228 from collections import OrderedDict
229 import itertools
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"
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
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 = []
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)
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")
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
298 # map of DSO object -> list of call refs
299 self.callrefs = OrderedDict()
301 # map of DSO object -> list of permutations of dependencies
302 self.dep_permutations = OrderedDict()
304 # map of DSO object -> SONAME of object (if one is specified)
305 self.soname_map = OrderedDict()
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
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'
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)
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
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
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)
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
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
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)
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)
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)
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 = ['*']
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")
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] = []
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 = ["#"]
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
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
513 elif kind == "SEMICOL":
514 curr_objs = []
515 clear_dep_state()
517 else:
518 error("unknown token '%s'" % (value))
519 return t
521 # Main routine to process each testcase description
522 def process_testcase(t):
523 global objpfx
524 assert t.test_name
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
532 if not os.path.exists(testpfx):
533 os.mkdir(testpfx)
534 if not os.path.exists(testpfx_src):
535 os.mkdir(testpfx_src)
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
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)
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)
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)
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):
597 test_name = test_descr.test_name + test_suffix
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))
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)
621 obj_visited = {}
622 for obj in t.objs:
623 dfs_rec(obj, fn, obj_visited)
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
676 dfs(test_descr, gen_link_deps)
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))
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)
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)))
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))
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 = [""]
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))
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()
824 # Open C file for writing main program
825 f = open(testpfx_src + test_name + ".c", "w")
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")
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()
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))
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])
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
944 # Build all DSOs, this needs to be in topological dependency order,
945 # or link will fail
946 dfs(test_descr, build_dso)
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)
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
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)
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()
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")
1026 # Starting part of Makefile fragment
1027 makefile.write("ifeq (yes,$(build-shared))\n")
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, "")
1035 # If testcase is XFAIL, indicate so
1036 if t.xfail:
1037 makefile.write("test-xfail-%s = yes\n" % t.test_name)
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")
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()
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
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
1094 m = re.match(r"^clear_tunables$", line)
1095 if m:
1096 default_tunable_options = []
1097 continue
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)
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")
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)
1163 # Close Makefile fragment output
1164 makefile.close()