44
55from elftools .elf .elffile import ELFFile
66from macholib import mach_o
7- from macholib import SymbolTable
87from macholib .MachO import MachO
98
9+ ELF_MAGIC = b"\x7f ELF"
10+ MACHO_MAGICS = (
11+ b"\xce \xfa \xed \xfe " , # 32-bit big-endian
12+ b"\xcf \xfa \xed \xfe " , # 64-bit big-endian
13+ b"\xfe \xed \xfa \xce " , # 32-bit little-endian
14+ b"\xfe \xed \xfa \xcf " , # 64-bit little-endian
15+ )
16+
1017
1118class SharedLibLoadingTest (unittest .TestCase ):
1219 def test_shared_library_linking (self ):
@@ -18,6 +25,8 @@ def test_shared_library_linking(self):
1825 self .fail (f"Import failed and could not find module spec: { e } " )
1926
2027 info = self ._get_linking_info (spec .origin )
28+
29+ # Give a useful error message for debugging.
2130 self .fail (
2231 f"Failed to import adder extension.\n "
2332 f"Original error: { e } \n "
@@ -34,9 +43,9 @@ def test_shared_library_linking(self):
3443 with open (adder_path , "rb" ) as f :
3544 magic_bytes = f .read (4 )
3645
37- if magic_bytes == b" \x7f ELF" :
46+ if magic_bytes == ELF_MAGIC :
3847 self ._assert_elf_linking (adder_path )
39- elif magic_bytes in ( b" \xce \xfa \xed \xfe " , b" \xcf \xfa \xed \xfe " , b" \xfe \xed \xfa \xce " , b" \xfe \xed \xfa \xcf " ) :
48+ elif magic_bytes in MACHO_MAGICS :
4049 self ._assert_macho_linking (adder_path )
4150 else :
4251 self .fail (f"Unsupported file format for adder: magic bytes { magic_bytes !r} " )
@@ -46,76 +55,60 @@ def test_shared_library_linking(self):
4655
4756 def _get_linking_info (self , path ):
4857 """Parses a shared library and returns its rpaths and dependencies."""
49- info = {"rpaths" : [], "needed" : []}
5058 path = os .path .realpath (path )
5159 with open (path , "rb" ) as f :
5260 magic_bytes = f .read (4 )
5361
54- if magic_bytes == b"\x7f ELF" :
55- with open (path , "rb" ) as f :
56- elf = ELFFile (f )
57- dynamic = elf .get_section_by_name (".dynamic" )
58- if not dynamic :
59- return info
62+ if magic_bytes == ELF_MAGIC :
63+ return self ._get_elf_info (path )
64+ elif magic_bytes in MACHO_MAGICS :
65+ return self ._get_macho_info (path )
66+ return {}
67+
68+ def _get_elf_info (self , path ):
69+ """Extracts linking information from an ELF file."""
70+ info = {"rpaths" : [], "needed" : [], "undefined_symbols" : []}
71+ with open (path , "rb" ) as f :
72+ elf = ELFFile (f )
73+ dynamic = elf .get_section_by_name (".dynamic" )
74+ if dynamic :
6075 for tag in dynamic .iter_tags ():
6176 if tag .entry .d_tag == "DT_NEEDED" :
6277 info ["needed" ].append (tag .needed )
6378 elif tag .entry .d_tag in ("DT_RPATH" , "DT_RUNPATH" ):
6479 info ["rpaths" ].append (tag .rpath )
65- elif magic_bytes in (b"\xce \xfa \xed \xfe " , b"\xcf \xfa \xed \xfe " , b"\xfe \xed \xfa \xce " , b"\xfe \xed \xfa \xcf " ):
66- macho = MachO (path )
67- for header in macho .headers :
68- for cmd_load , cmd , data in header .commands :
69- if cmd_load .cmd == mach_o .LC_LOAD_DYLIB :
70- info ["needed" ].append (data .decode ().strip ("\x00 " ))
71- elif cmd_load .cmd == mach_o .LC_RPATH :
72- info ["rpaths" ].append (data .decode ().strip ("\x00 " ))
80+
81+ dynsym = elf .get_section_by_name (".dynsym" )
82+ if dynsym :
83+ info ["undefined_symbols" ] = [
84+ s .name
85+ for s in dynsym .iter_symbols ()
86+ if s .entry ["st_shndx" ] == "SHN_UNDEF"
87+ ]
88+ return info
89+
90+ def _get_macho_info (self , path ):
91+ """Extracts linking information from a Mach-O file."""
92+ info = {"rpaths" : [], "needed" : []}
93+ macho = MachO (path )
94+ for header in macho .headers :
95+ for cmd_load , cmd , data in header .commands :
96+ if cmd_load .cmd == mach_o .LC_LOAD_DYLIB :
97+ info ["needed" ].append (data .decode ().strip ("\x00 " ))
98+ elif cmd_load .cmd == mach_o .LC_RPATH :
99+ info ["rpaths" ].append (data .decode ().strip ("\x00 " ))
73100 return info
74101
75102 def _assert_elf_linking (self , path ):
76103 """Asserts dynamic linking properties for an ELF file."""
77- with open (path , "rb" ) as f :
78- elf = ELFFile (f )
79-
80- # Check that the adder module depends on the increment library.
81- needed = []
82- dynamic_section = elf .get_section_by_name (".dynamic" )
83- self .assertIsNotNone (dynamic_section )
84- for tag in dynamic_section .iter_tags ("DT_NEEDED" ):
85- needed .append (tag .needed )
86- self .assertIn ("libincrement.so" , needed )
87-
88- # Check that the 'increment' symbol is undefined.
89- dynsym_section = elf .get_section_by_name (".dynsym" )
90- self .assertIsNotNone (dynsym_section )
91- undefined_symbols = [
92- s .name
93- for s in dynsym_section .iter_symbols ()
94- if s .entry ["st_shndx" ] == "SHN_UNDEF"
95- ]
96- self .assertIn ("increment" , undefined_symbols )
104+ info = self ._get_elf_info (path )
105+ self .assertIn ("libincrement.so" , info ["needed" ])
106+ self .assertIn ("increment" , info ["undefined_symbols" ])
97107
98108 def _assert_macho_linking (self , path ):
99109 """Asserts dynamic linking properties for a Mach-O file."""
100- macho = MachO (path )
101-
102- # Check dependency on the increment library.
103- loaded_dylibs = [
104- data .decode ().strip ("\x00 " )
105- for header in macho .headers
106- for cmd_load , cmd , data in header .commands
107- if cmd_load .cmd == mach_o .LC_LOAD_DYLIB
108- ]
109- self .assertIn ("@rpath/libincrement.dylib" , loaded_dylibs )
110-
111- # Check that the 'increment' symbol is undefined.
112- symtab = SymbolTable (macho )
113- undefined_symbols = [
114- s .n_name .decode ()
115- for s in symtab .nlists
116- if s .n_type & 0x01 and s .n_sect == 0 # N_EXT and NO_SECT
117- ]
118- self .assertIn ("_increment" , undefined_symbols )
110+ info = self ._get_macho_info (path )
111+ self .assertIn ("@rpath/libincrement.dylib" , info ["needed" ])
119112
120113
121114if __name__ == "__main__" :
0 commit comments