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