1212from buildcheck import nativedeps
1313
1414
15- def elf64 (needed , runpath = None , glibc = (), exports = (), undefined = ()):
15+ def elf64 (needed , runpath = None , glibc = (), exports = (), undefined = (), symtab = () ):
1616 """A minimal little-endian ELF64 with a .dynstr and a .dynamic naming `needed` (and a
1717 DT_RUNPATH when given), plus a .gnu.version_r requiring the `glibc` symbol versions, plus a
18- .dynsym defining `exports` (global) and referencing `undefined`."""
18+ .dynsym defining `exports` (global) and referencing `undefined`, plus a .symtab defining the
19+ `symtab` (name, address) pairs as local objects."""
1920 strtab = b"\0 "
2021 offsets = {}
2122 for name in (list (needed ) + ([runpath ] if runpath is not None else []) + (["libc.so.6" ] if glibc else [])
22- + list (glibc ) + list (exports ) + list (undefined )):
23+ + list (glibc ) + list (exports ) + list (undefined ) + [ n for n , _ in symtab ] ):
2324 offsets [name ] = len (strtab )
2425 strtab += name .encode () + b"\0 "
2526 dynamic = b"" .join (struct .pack ("<qQ" , 1 , offsets [n ]) for n in needed )
@@ -36,16 +37,20 @@ def elf64(needed, runpath=None, glibc=(), exports=(), undefined=()):
3637 dynsym += struct .pack ("<IBBHQQ" , offsets [name ], (1 << 4 ) | 2 , 0 , 1 , 0x1000 , 8 )
3738 for name in undefined : # SHN_UNDEF: a reference, not an export
3839 dynsym += struct .pack ("<IBBHQQ" , offsets [name ], (1 << 4 ) | 2 , 0 , 0 , 0 , 0 )
40+ statics = bytes (24 ) # the null symbol
41+ for name , address in symtab : # STB_LOCAL (0) << 4 | STT_OBJECT (1), defined in section 1
42+ statics += struct .pack ("<IBBHQQ" , offsets [name ], 1 , 0 , 1 , address , 8 )
3943 header_size , section_size = 64 , 64
4044 strtab_off = header_size
4145 dynamic_off = strtab_off + len (strtab )
4246 verneed_off = dynamic_off + len (dynamic )
4347 dynsym_off = verneed_off + len (verneed )
44- shoff = dynsym_off + len (dynsym )
48+ symtab_off = dynsym_off + len (dynsym )
49+ shoff = symtab_off + (len (statics ) if symtab else 0 )
4550 header = bytearray (header_size )
4651 header [:6 ] = b"\x7f ELF\x02 \x01 "
4752 struct .pack_into ("<Q" , header , 0x28 , shoff )
48- nsections = 3 + (1 if glibc else 0 ) + (1 if exports or undefined else 0 )
53+ nsections = 3 + (1 if glibc else 0 ) + (1 if exports or undefined else 0 ) + ( 1 if symtab else 0 )
4954 struct .pack_into ("<HH" , header , 0x3A , section_size , nsections )
5055 sections = bytes (section_size ) # SHT_NULL
5156 sections += struct .pack ("<IIQQQQIIQQ" , 0 , 3 , 0 , 0 , strtab_off , len (strtab ), 0 , 0 , 1 , 0 ) # SHT_STRTAB
@@ -54,7 +59,47 @@ def elf64(needed, runpath=None, glibc=(), exports=(), undefined=()):
5459 sections += struct .pack ("<IIQQQQIIQQ" , 0 , 0x6FFFFFFE , 0 , 0 , verneed_off , len (verneed ), 1 , 1 , 4 , 0 )
5560 if exports or undefined :
5661 sections += struct .pack ("<IIQQQQIIQQ" , 0 , 11 , 0 , 0 , dynsym_off , len (dynsym ), 1 , 1 , 8 , 24 ) # SHT_DYNSYM
57- return bytes (header ) + strtab + dynamic + verneed + dynsym + sections
62+ if symtab :
63+ sections += struct .pack ("<IIQQQQIIQQ" , 0 , 2 , 0 , 0 , symtab_off , len (statics ), 1 , 1 , 8 , 24 ) # SHT_SYMTAB
64+ return (bytes (header ) + strtab + dynamic + verneed + dynsym + (statics if symtab else b"" ) + sections )
65+
66+
67+ def elf_symtab (symbols , big_endian = False , is64 = True ):
68+ """A minimal ELF with only a .strtab and a .symtab: the (name, address, shndx) triples as local
69+ objects -- in either byte order and class, s390x being big-endian ELF64."""
70+ end = ">" if big_endian else "<"
71+ strtab = b"\0 "
72+ offsets = {}
73+ for name , _ , _ in symbols :
74+ offsets [name ] = len (strtab )
75+ strtab += name .encode () + b"\0 "
76+ entry = 24 if is64 else 16
77+ table = bytes (entry )
78+ for name , address , shndx in symbols :
79+ if is64 :
80+ table += struct .pack (end + "IBBHQQ" , offsets [name ], 1 , 0 , shndx , address , 1 )
81+ else :
82+ table += struct .pack (end + "IIIBBH" , offsets [name ], address , 1 , 1 , 0 , shndx )
83+ header_size = 64 if is64 else 52
84+ section_size = 64 if is64 else 40
85+ strtab_off = header_size
86+ table_off = strtab_off + len (strtab )
87+ shoff = table_off + len (table )
88+ header = bytearray (header_size )
89+ header [:6 ] = b"\x7f ELF" + bytes ([2 if is64 else 1 , 2 if big_endian else 1 ])
90+ if is64 :
91+ struct .pack_into (end + "Q" , header , 0x28 , shoff )
92+ struct .pack_into (end + "HH" , header , 0x3A , section_size , 3 )
93+ sections = bytes (section_size )
94+ sections += struct .pack (end + "IIQQQQIIQQ" , 0 , 3 , 0 , 0 , strtab_off , len (strtab ), 0 , 0 , 1 , 0 )
95+ sections += struct .pack (end + "IIQQQQIIQQ" , 0 , 2 , 0 , 0 , table_off , len (table ), 1 , 1 , 8 , entry )
96+ else :
97+ struct .pack_into (end + "I" , header , 0x20 , shoff )
98+ struct .pack_into (end + "HH" , header , 0x2E , section_size , 3 )
99+ sections = bytes (section_size )
100+ sections += struct .pack (end + "IIIIIIIIII" , 0 , 3 , 0 , 0 , strtab_off , len (strtab ), 0 , 0 , 1 , 0 )
101+ sections += struct .pack (end + "IIIIIIIIII" , 0 , 2 , 0 , 0 , table_off , len (table ), 1 , 1 , 4 , entry )
102+ return bytes (header ) + strtab + table + sections
58103
59104
60105def elf64_segments (alignments ):
@@ -455,6 +500,68 @@ def test_the_runtimes_the_jvm_itself_needs_stay_allowed(self):
455500 self .assertIn ("libgcc_s.so.1" , nativedeps .ALLOWED [directory ], directory )
456501
457502
503+ class TypeinfoNameAlignmentTest (unittest .TestCase ):
504+ """s390x: every typeinfo name (_ZTS*) at an even address, the ABI's promise GCC's LARL relies on
505+ (.github/manylinux/s390x-typeinfo-names.ld)."""
506+
507+ def test_elf_symbols_reads_the_static_table_in_both_byte_orders (self ):
508+ symbols = [("_ZTS1X" , 0x2001 , 5 ), ("undefined" , 0 , 0 ), ("absolute" , 0x10 , 0xFFF1 ), ("_ZTSSt9exception" , 0x40 , 7 )]
509+ for big_endian in (False , True ):
510+ for is64 in (True , False ):
511+ data = elf_symtab (symbols , big_endian , is64 )
512+ self .assertEqual (nativedeps .elf_symbols (data ), [("_ZTS1X" , 0x2001 ), ("_ZTSSt9exception" , 0x40 )],
513+ (big_endian , is64 ))
514+ # the dynamic table is not the static one, and a library without .symtab has no symbols
515+ self .assertEqual (nativedeps .elf_symbols (elf64 (["libc.so.6" ], exports = ["JNI_OnLoad" ])), [])
516+ self .assertEqual (nativedeps .elf_symbols (elf64 (["libc.so.6" ], symtab = [("_ZTS1K" , 0x30 )])), [("_ZTS1K" , 0x30 )])
517+
518+ def test_only_s390x_asks_for_it (self ):
519+ self .assertEqual (nativedeps .typeinfo_name_alignment ("x/Linux/s390x/cpu/libjllama.so" ), 2 )
520+ for rel in ("x/Linux/x86_64/cpu/libjllama.so" , "x/Linux/aarch64/cpu/libjllama.so" ,
521+ "x/Linux-Android/aarch64/cpu/libjllama.so" , "libjllama.so" ):
522+ self .assertIsNone (nativedeps .typeinfo_name_alignment (rel ), rel )
523+
524+ def test_an_odd_typeinfo_name_is_named_and_an_even_one_passes (self ):
525+ rel = "x/Linux/s390x/cpu/libjllama.so"
526+ self .assertEqual (nativedeps .typeinfo_name_violations (rel , [("_ZTS1X" , 0x2000 ), ("odd_but_no_typeinfo" , 0x3 )], 2 ), [])
527+ found = nativedeps .typeinfo_name_violations (
528+ rel , [("_ZTSSt19_Sp_make_shared_tag" , 0x12419CF ), ("_ZTS1X" , 0x2000 ), ("data" , 0x1 )], 2 )
529+ self .assertEqual (len (found ), 1 , found )
530+ self .assertIn ("1 of 2 typeinfo names" , found [0 ])
531+ self .assertIn ("_ZTSSt19_Sp_make_shared_tag" , found [0 ])
532+ self .assertIn ("s390x-typeinfo-names.ld" , found [0 ])
533+ many = [(f"_ZTS{ i } X" , 2 * i + 1 ) for i in range (10 )]
534+ self .assertIn ("(10 in all)" , nativedeps .typeinfo_name_violations (rel , many , 2 )[0 ])
535+
536+ def test_a_library_without_typeinfo_names_is_never_a_pass_where_it_must_have_them (self ):
537+ found = nativedeps .typeinfo_name_violations ("x/Linux/s390x/cpu/libjllama.so" , [("JNI_OnLoad" , 0x1000 )], 2 )
538+ self .assertEqual (len (found ), 1 , found )
539+ self .assertIn ("no typeinfo name" , found [0 ])
540+ self .assertEqual (nativedeps .typeinfo_name_violations ("x/Linux/s390x/cpu/libother.so" , [], 2 , required = False ), [])
541+
542+ def _run (self , arch , symtab ):
543+ with tempfile .TemporaryDirectory () as root :
544+ d = os .path .join (root , "net/ladenthin/llama/Linux" , arch , "cpu" )
545+ os .makedirs (d )
546+ with open (os .path .join (d , "libjllama.so" ), "wb" ) as f :
547+ f .write (elf64 (["libc.so.6" ], exports = ["JNI_OnLoad" , "Java_a" ], symtab = symtab ))
548+ out , err = io .StringIO (), io .StringIO ()
549+ with contextlib .redirect_stdout (out ), contextlib .redirect_stderr (err ):
550+ rc = nativedeps .main (["x" , root ])
551+ return rc , err .getvalue ()
552+
553+ def test_main_holds_the_s390x_library_to_it_and_no_other (self ):
554+ self .assertEqual (self ._run ("s390x" , [("_ZTS1X" , 0x2000 ), ("_ZTSSt9exception" , 0x2010 )])[0 ], 0 )
555+ rc , err = self ._run ("s390x" , [("_ZTS1X" , 0x2000 ), ("_ZTSSt19_Sp_make_shared_tag" , 0x2011 )])
556+ self .assertEqual (rc , 1 )
557+ self .assertIn ("_ZTSSt19_Sp_make_shared_tag" , err )
558+ rc , err = self ._run ("s390x" , [])
559+ self .assertEqual (rc , 1 , "a stripped s390x library is not examined, which is never a pass" )
560+ self .assertIn ("no typeinfo name" , err )
561+ # x86-64's ABI promises no such thing, and its library is not held to it
562+ self .assertEqual (self ._run ("x86_64" , [("_ZTS1X" , 0x2001 )])[0 ], 0 )
563+
564+
458565class MainTest (unittest .TestCase ):
459566
460567 def test_checks_a_tree_and_reports_violations (self ):
@@ -464,7 +571,7 @@ def test_checks_a_tree_and_reports_violations(self):
464571 os .makedirs (ok )
465572 os .makedirs (bad )
466573 with open (os .path .join (ok , "libjllama.so" ), "wb" ) as f :
467- f .write (elf64 (["libc.so.6" ], exports = ["JNI_OnLoad" , "Java_a" ]))
574+ f .write (elf64 (["libc.so.6" ], exports = ["JNI_OnLoad" , "Java_a" ], symtab = [( "_ZTS1X" , 0x2000 )] ))
468575 self .assertEqual (nativedeps .main (["x" , root ]), 0 )
469576 with open (os .path .join (bad , "jllama.dll" ), "wb" ) as f :
470577 f .write (pe64 (["kernel32.dll" , "libomp140.x86_64.dll" ], exports = ["JNI_OnLoad" , "Java_a" ]))
0 commit comments