def testParsedSymbols(self): """Tests the list of imported/exported symbols.""" unittest_lib.BuildELF(os.path.join(self.tempdir, 'libabc.so'), defined_symbols=['fa', 'fb', 'fc']) unittest_lib.BuildELF(os.path.join(self.tempdir, 'libxyz.so'), defined_symbols=['fx', 'fy', 'fz'], undefined_symbols=['fa', 'fb', 'fc'], used_libs=['abc']) # Test without symbols. elf = parseelf.ParseELF(self.tempdir, 'libxyz.so', self._ldpaths, parse_symbols=False) self.assertFalse('imp_sym' in elf) self.assertFalse('exp_sym' in elf) # Test with symbols by default. elf = parseelf.ParseELF(self.tempdir, 'libxyz.so', self._ldpaths) self.assertTrue('imp_sym' in elf) self.assertTrue('exp_sym' in elf) self.assertEquals(elf['imp_sym'], set(['fa', 'fb', 'fc'])) self.assertEquals( set(k for k, (_, _, st_shndx) in elf['exp_sym'].iteritems() if st_shndx == 'SHT_DYNSYM'), set(['fx', 'fy', 'fz'])) for sym in ['fx', 'fy', 'fz']: self.assertEquals('STB_GLOBAL', elf['exp_sym'][sym][0])
def testParsedSymbols(self): """Tests the list of imported/exported symbols.""" unittest_lib.BuildELF(os.path.join(self.tempdir, 'libabc.so'), defined_symbols=['fa', 'fb', 'fc']) unittest_lib.BuildELF(os.path.join(self.tempdir, 'libxyz.so'), defined_symbols=['fx', 'fy', 'fz'], undefined_symbols=['fa', 'fb', 'fc'], used_libs=['abc']) # Test without symbols. elf = parseelf.ParseELF(self.tempdir, 'libxyz.so', self._ldpaths, parse_symbols=False) self.assertFalse('imp_sym' in elf) self.assertFalse('exp_sym' in elf) # Test with symbols by default. elf = parseelf.ParseELF(self.tempdir, 'libxyz.so', self._ldpaths, parse_symbols=True) self.assertTrue('imp_sym' in elf) self.assertTrue('exp_sym' in elf) self.assertEquals(elf['imp_sym'], set(['fa', 'fb', 'fc'])) self.assertIn('fx', elf['exp_sym']) self.assertIn('fy', elf['exp_sym']) self.assertIn('fz', elf['exp_sym'])
def testUnsupportedFiles(self): """Tests unsupported files are ignored.""" osutils.WriteFile(os.path.join(self.tempdir, 'foo.so'), 'foo') self.assertEquals( None, parseelf.ParseELF(self.tempdir, 'foo.so', self._ldpaths)) osutils.WriteFile(os.path.join(self.tempdir, 'foo.so'), '\x7fELF-foo') self.assertEquals( None, parseelf.ParseELF(self.tempdir, 'foo.so', self._ldpaths))
def testLibDependencies(self): """Tests the list direct dependencies.""" # Dependencies: # u -> abc # v -> abc # prog -> u,v unittest_lib.BuildELF(os.path.join(self.tempdir, 'libabc.so'), defined_symbols=['fa', 'fb', 'fc']) unittest_lib.BuildELF(os.path.join(self.tempdir, 'libu.so'), defined_symbols=['fu'], undefined_symbols=['fa'], used_libs=['abc']) unittest_lib.BuildELF(os.path.join(self.tempdir, 'libv.so'), defined_symbols=['fv'], undefined_symbols=['fb'], used_libs=['abc']) unittest_lib.BuildELF(os.path.join(self.tempdir, 'prog'), undefined_symbols=['fu', 'fv'], used_libs=['u', 'v'], executable=True) elf_prog = parseelf.ParseELF(self.tempdir, 'prog', self._ldpaths) # Check the direct dependencies. self.assertTrue('libu.so' in elf_prog['needed']) self.assertTrue('libv.so' in elf_prog['needed']) self.assertFalse('libabc.so' in elf_prog['needed'])
def testNotIsLib(self): """Tests the 'is_lib' attribute is inferred correctly for executables.""" unittest_lib.BuildELF(os.path.join(self.tempdir, 'abc_main'), executable=True) elf = parseelf.ParseELF(self.tempdir, 'abc_main', self._ldpaths) self.assertTrue('is_lib' in elf) self.assertFalse(elf['is_lib'])
def testIsLib(self): """Tests the 'is_lib' attribute is inferred correctly for libs.""" unittest_lib.BuildELF(os.path.join(self.tempdir, 'liba.so'), ['func_a']) elf = parseelf.ParseELF(self.tempdir, 'liba.so', self._ldpaths) self.assertTrue('is_lib' in elf) self.assertTrue(elf['is_lib'])
def ParseELFWithArgs(args): """Wrapper to parseelf.ParseELF accepting a single arg. This wrapper is required to use multiprocessing.Pool.map function. Returns: A 2-tuple with the passed relative path and the result of ParseELF(). On error, when ParseELF() returns None, this function returns None. """ elf = parseelf.ParseELF(*args) if elf is None: return return args[1], elf
def testRelativeLibPaths(self): """Test that the paths reported by ParseELF are relative to root.""" unittest_lib.BuildELF(os.path.join(self.tempdir, 'liba.so'), ['fa']) unittest_lib.BuildELF(os.path.join(self.tempdir, 'prog'), undefined_symbols=['fa'], used_libs=['a'], executable=True) elf = parseelf.ParseELF(self.tempdir, 'prog', self._ldpaths) for lib in elf['libs'].values(): for path in ('realpath', 'path'): if lib[path] is None: continue self.assertFalse(lib[path].startswith('/')) self.assertFalse(lib[path].startswith(self.tempdir)) # Linked lib paths should be relative to the working directory or is the # ld dynamic loader. self.assertTrue(lib[path] == elf['interp'] or os.path.exists(os.path.join(self.tempdir, lib[path])))
def _GetTypeFromContent(self, rel_path, fobj, fmap): """Return the file path based on the file contents. This helper function detect the file type based on the contents of the file. Args: rel_path: The path to the file, used to detect the filetype from the contents of the file. fobj: a file() object for random access to rel_path. fmap: a mmap object mapping the whole rel_path file for reading. """ # Detect if the file is binary based on the presence of non-ASCII chars. We # include some the first 32 chars often used in text files but we exclude # the rest. # Python 2 creates bytes as chars when we want ints (like Python 3). # TODO(vapier): Drop this once we require Python 3 everywhere. if sys.version_info.major < 3: to_ints = lambda s: (ord(x) for x in s) else: to_ints = lambda s: s ascii_chars = set(to_ints(b'\a\b\t\n\v\f\r\x1b')) ascii_chars.update(range(32, 128)) is_binary = any( set(to_ints(chunk)) - ascii_chars for chunk in iter(lambda: fmap.read(FILE_BUFFER_SIZE), b'')) # We use the first part of the file in several checks. fmap.seek(0) first_kib = fmap.read(1024) # Binary files. if is_binary: # The elf argument was not passed, so compute it now if the file is an # ELF. if first_kib.startswith(b'\x7fELF'): return self._GetELFType( parseelf.ParseELF(self._root, rel_path, parse_symbols=False)) if first_kib.startswith(b'MZ\x90\0'): return 'binary/dos-bin' if len(first_kib) >= 512 and first_kib[510:512] == b'\x55\xaa': return 'binary/bootsector/x86' # Firmware file depend on the technical details of the device they run on, # so there's no easy way to detect them. We use the filename to guess that # case. if '/firmware/' in rel_path and ( rel_path.endswith('.fw') or rel_path[-4:] in ('.bin', '.cis', '.csp', '.dsp')): return 'binary/firmware' # TZif (timezone) files. See tzfile(5) for details. if (first_kib.startswith(b'TZif' + b'\0' * 16) or first_kib.startswith(b'TZif2' + b'\0' * 15) or first_kib.startswith(b'TZif3' + b'\0' * 15)): return 'binary/tzfile' # Whitelist some binary mime types. fobj.seek(0) # _mime.descriptor() will close the passed file descriptor. mime_type = self._mime.descriptor(os.dup(fobj.fileno())) if mime_type.startswith('image/'): return 'binary/' + mime_type if mime_type in self.MIME_TYPE_MAPPING: return self.MIME_TYPE_MAPPING[mime_type] # Other binary files. return 'binary' # Text files. # Read the first couple of lines used in the following checks. This will # only read the required lines, with the '\n' char at the end of each line # except on the last one if it is not present on that line. At this point # we know that the file is not empty, so at least one line existst. fmap.seek(0) first_lines = list(itertools.islice(iter(fmap.readline, b''), 0, 10)) head_line = first_lines[0] # #! or "shebangs". Only those files with a single line are considered # shebangs. Some files start with "#!" but are other kind of files, such # as python or bash scripts. try: prog_name, args = SplitShebang(head_line) if len(first_lines) == 1: return 'text/shebang' prog_name = os.path.basename(prog_name) args = args.split() if prog_name == 'env': # If "env" is called, we skip all the arguments passed to env (flags, # VAR=value) and treat the program name as the program to use. for i, arg in enumerate(args): if arg == '--' and (i + 1) < len(args): prog_name = args[i + 1] break if not arg or arg[0] == '-' or '=' in arg: continue prog_name = arg break # Strip the version number from comon programs like "python2.7". prog_name = prog_name.rstrip('0123456789-.') if prog_name in ('awk', 'bash', 'dash', 'ksh', 'perl', 'python', 'sh'): return 'text/script/' + prog_name # Other unknown script. return 'text/script' except ValueError: pass # PEM files. if head_line.strip() == b'-----BEGIN CERTIFICATE-----': return 'text/pem/cert' if head_line.strip() == b'-----BEGIN RSA PRIVATE KEY-----': return 'text/pem/rsa-private' # Linker script. if head_line.strip() == b'/* GNU ld script': return 'text/ld-script' # Protobuf files. if rel_path.endswith('.proto'): return 'text/proto' if len(first_lines) == 1: if re.match(br'[0-9\.]+$', head_line): return 'text/oneline/number' return 'text/oneline' return 'text'