2355ed485895d6e0403c6e53c4155826af683ae7
[pyelftools.git] / elftools / common / utils.py
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 #-------------------------------------------------------------------------------
9 from contextlib import contextmanager
10 from .exceptions import ELFParseError, ELFError, DWARFError
11 from ..construct import ConstructError, ULInt8
12 import os
13
14
15 def 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
23 def 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(bytes((b,)) for b in bytelist)
28
29
30 def 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
45 def 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
74 def elf_assert(cond, msg=''):
75 """ Assert that cond is True, otherwise raise ELFError(msg)
76 """
77 _assert_with_exception(cond, msg, ELFError)
78
79
80 def 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
87 def 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
99 def 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
105 def 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 def save_dwarf_section(section, filename):
111 """Debug helper: dump section contents into a file
112 Section is expected to be one of the debug_xxx_sec elements of DWARFInfo
113 """
114 stream = section.stream
115 pos = stream.tell()
116 stream.seek(0, os.SEEK_SET)
117 section.stream.seek(0)
118 with open(filename, 'wb') as file:
119 data = stream.read(section.size)
120 file.write(data)
121 stream.seek(pos, os.SEEK_SET)
122
123 def iterbytes(b):
124 """Return an iterator over the elements of a bytes object.
125
126 For example, for b'abc' yields b'a', b'b' and then b'c'.
127 """
128 for i in range(len(b)):
129 yield b[i:i+1]
130
131 def bytes2hex(b, sep=''):
132 if not sep:
133 return b.hex()
134 return sep.join(map('{:02x}'.format, b))
135
136 #------------------------- PRIVATE -------------------------
137
138 def _assert_with_exception(cond, msg, exception_type):
139 if not cond:
140 raise exception_type(msg)