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