1#------------------------------------------------------------------------------- 2# elftools: common/utils.py 3# 4# Miscellaneous utilities for elftools 5# 6# Eli Bendersky (eliben@gmail.com) 7# This code is in the public domain 8#------------------------------------------------------------------------------- 9from contextlib import contextmanager 10from .exceptions import ELFParseError, ELFError, DWARFError 11from .py3compat import int2byte 12from ..construct import ConstructError, ULInt8 13 14 15def merge_dicts(*dicts): 16 "Given any number of dicts, merges them into a new one.""" 17 result = {} 18 for d in dicts: 19 result.update(d) 20 return result 21 22 23def bytelist2string(bytelist): 24 """ Convert a list of byte values (e.g. [0x10 0x20 0x00]) to a bytes object 25 (e.g. b'\x10\x20\x00'). 26 """ 27 return b''.join(int2byte(b) for b in bytelist) 28 29 30def struct_parse(struct, stream, stream_pos=None): 31 """ Convenience function for using the given struct to parse a stream. 32 If stream_pos is provided, the stream is seeked to this position before 33 the parsing is done. Otherwise, the current position of the stream is 34 used. 35 Wraps the error thrown by construct with ELFParseError. 36 """ 37 try: 38 if stream_pos is not None: 39 stream.seek(stream_pos) 40 return struct.parse_stream(stream) 41 except ConstructError as e: 42 raise ELFParseError(str(e)) 43 44 45def parse_cstring_from_stream(stream, stream_pos=None): 46 """ Parse a C-string from the given stream. The string is returned without 47 the terminating \x00 byte. If the terminating byte wasn't found, None 48 is returned (the stream is exhausted). 49 If stream_pos is provided, the stream is seeked to this position before 50 the parsing is done. Otherwise, the current position of the stream is 51 used. 52 Note: a bytes object is returned here, because this is what's read from 53 the binary file. 54 """ 55 if stream_pos is not None: 56 stream.seek(stream_pos) 57 CHUNKSIZE = 64 58 chunks = [] 59 found = False 60 while True: 61 chunk = stream.read(CHUNKSIZE) 62 end_index = chunk.find(b'\x00') 63 if end_index >= 0: 64 chunks.append(chunk[:end_index]) 65 found = True 66 break 67 else: 68 chunks.append(chunk) 69 if len(chunk) < CHUNKSIZE: 70 break 71 return b''.join(chunks) if found else None 72 73 74def elf_assert(cond, msg=''): 75 """ Assert that cond is True, otherwise raise ELFError(msg) 76 """ 77 _assert_with_exception(cond, msg, ELFError) 78 79 80def dwarf_assert(cond, msg=''): 81 """ Assert that cond is True, otherwise raise DWARFError(msg) 82 """ 83 _assert_with_exception(cond, msg, DWARFError) 84 85 86@contextmanager 87def preserve_stream_pos(stream): 88 """ Usage: 89 # stream has some position FOO (return value of stream.tell()) 90 with preserve_stream_pos(stream): 91 # do stuff that manipulates the stream 92 # stream still has position FOO 93 """ 94 saved_pos = stream.tell() 95 yield 96 stream.seek(saved_pos) 97 98 99def roundup(num, bits): 100 """ Round up a number to nearest multiple of 2^bits. The result is a number 101 where the least significant bits passed in bits are 0. 102 """ 103 return (num - 1 | (1 << bits) - 1) + 1 104 105def read_blob(stream, length): 106 """Read length bytes from stream, return a list of ints 107 """ 108 return [struct_parse(ULInt8(''), stream) for i in range(length)] 109 110#------------------------- PRIVATE ------------------------- 111 112def _assert_with_exception(cond, msg, exception_type): 113 if not cond: 114 raise exception_type(msg) 115