]> git.saurik.com Git - apple/xnu.git/blob - tools/lldbmacros/macho.py
xnu-3789.51.2.tar.gz
[apple/xnu.git] / tools / lldbmacros / macho.py
1 import sys
2 import macholib
3 from macholib import MachO as macho
4 from collections import namedtuple
5 import re
6
7 # some fixups in macholib that are required for kext support
8 macholib.mach_o.MH_KEXT_BUNDLE = 0xB
9
10 macholib.mach_o.MH_FILETYPE_NAMES[macholib.mach_o.MH_KEXT_BUNDLE] = "kext bundle"
11 macholib.mach_o.MH_FILETYPE_SHORTNAMES[macholib.mach_o.MH_KEXT_BUNDLE] = "kext"
12
13 _old_MachOHeader_load = macho.MachOHeader.load
14 def new_load(s, fh):
15 try:
16 _old_MachOHeader_load(s, fh)
17 except ValueError as e:
18 if str(e.message).find('total_size > low_offset') >= 0:
19 pass
20 else:
21 raise
22 except Exception as e:
23 raise
24 macho.MachOHeader.load = new_load
25
26 class MemFile(object):
27 def __init__(self, memory, size):
28 self._start = 0
29 self._readp = 0
30 self._end = size
31 self._mem = memory
32
33 def tell(self):
34 return self._readp
35
36 def check_bounds(self, seek_position, operation):
37 if not (self._start <= seek_position <= self._end):
38 raise IOError("%s to offset %d failed bounds check [%d, %d]" % (
39 operation, seek_position, self._start, self._end))
40
41 def seek(self, offset, whence=0):
42 seekto = offset
43 if whence == 0:
44 seekto += self._start
45 elif whence == 1:
46 seekto += self.tell()
47 elif whence == 2:
48 seekto += self._end
49 else:
50 raise IOError("Invalid whence argument to seek: %r" % (whence,))
51 self.check_bounds(seekto, 'seek')
52 self._readp = seekto
53
54 def write(self, bytes):
55 raise NotImplementedError('write is not supported')
56
57 def read(self, size=sys.maxsize):
58 if size < 0:
59 raise ValueError("Invalid size {} while reading from {}".format(size, self._fileobj))
60 here = self.tell()
61 self.check_bounds(here, 'read')
62 bytes = min(size, self._end - here)
63 retval = self._mem[self._readp:self._readp + bytes]
64 self._readp += bytes
65 return retval
66
67 MachOSegment = namedtuple('MachOSegment', 'name vmaddr vmsize fileoff filesize')
68
69 class MemMacho(macho.MachO):
70
71 def __init__(self, memdata, size=None):
72 if size is None:
73 super(MemMacho,self).__init__(memdata)
74 return
75 #
76 # supports the ObjectGraph protocol
77 self.graphident = 'mem:%d//'.format(size)
78 self.filename = 'mem:%d//'.format(size)
79
80 # initialized by load
81 self.fat = None
82 self.headers = []
83 fp = MemFile(memdata, size)
84 self.load(fp)
85
86
87 def get_segments_with_name(self, filter_re):
88 """ param: filter_re is a compiled re which will be matched against segment name.
89 Use: '' to match anything and everything
90 returns: [ MachOSegment, MachOSegment, ... ]
91 """
92 if type(filter_re) is str:
93 filter_re = re.compile(filter_re)
94 retval = []
95 for h in self.headers:
96 for cmd in h.commands:
97 # cmds is [(load_command, segment, [sections..])]
98 (lc, segment, sections) = cmd
99 if isinstance(segment, SEGMENT_TYPES):
100 segname = segment.segname[:segment.segname.find('\x00')]
101 if filter_re.match(segname):
102 retval.append(MachOSegment(segname, segment.vmaddr, segment.vmsize, segment.fileoff, segment.filesize))
103 return retval
104
105 def get_sections_with_name(self, filter_re):
106 """ param: filter_re is a compiled re which will be matched against <segment_name>.<section_name>
107 Use: '' to match anything and everything
108 returns: [ MachOSegment, MachOSegment, ... ]
109 where each MachOSegment.name is <segment_name>.<section_name>
110 """
111 if type(filter_re) is str:
112 filter_re = re.compile(filter_re)
113 retval = []
114 for h in self.headers:
115 for cmd in h.commands:
116 # cmds is [(load_command, segment, [sections..])]
117 (lc, segment, sections) = cmd
118 if isinstance(segment, SEGMENT_TYPES):
119 segname = segment.segname[:segment.segname.find('\x00')]
120 for section in sections:
121 section_name = section.sectname[:section.sectname.find('\x00')]
122 full_section_name= "{}.{}".format(segname, section_name)
123 if filter_re.match(full_section_name):
124 retval.append(MachOSegment(full_section_name, section.addr, section.size, section.offset, section.size))
125 return retval
126
127
128 def get_uuid(self):
129 retval = ''
130 for h in self.headers:
131 for cmd in h.commands:
132 # cmds is [(load_command, segment, [sections..])]
133 (lc, segment, sections) = cmd
134 if isinstance(segment, macholib.mach_o.uuid_command):
135 retval = GetUUIDSummary(segment.uuid)
136 return retval
137
138 def get_text_segment(segments):
139 retval = None
140 for s in segments:
141 if s.name == '__TEXT_EXEC':
142 return s
143 for s in segments:
144 if s.name == '__TEXT':
145 return s
146 return retval
147
148 def get_segment_with_addr(segments, addr):
149 """ param: segments [MachOSegment, ...]
150 return: None or MachOSegment where addr is in vmaddr...(vmaddr+vmsize)
151 """
152 for s in segments:
153 if addr >= s.vmaddr and addr < (s.vmaddr + s.vmsize):
154 return s
155 return None
156
157 def GetUUIDSummary(arr):
158 data = []
159 for i in range(16):
160 data.append(ord(arr[i]))
161 return "{a[0]:02X}{a[1]:02X}{a[2]:02X}{a[3]:02X}-{a[4]:02X}{a[5]:02X}-{a[6]:02X}{a[7]:02X}-{a[8]:02X}{a[9]:02X}-{a[10]:02X}{a[11]:02X}{a[12]:02X}{a[13]:02X}{a[14]:02X}{a[15]:02X}".format(a=data)
162
163 SEGMENT_TYPES = (macholib.mach_o.segment_command_64, macholib.mach_o.segment_command)
164
165 def get_load_command_human_name(cmd):
166 """ return string name of LC_LOAD_DYLIB => "load_dylib"
167 "<unknown>" if not found
168 """
169 retval = "<unknown>"
170 if cmd in macho.LC_REGISTRY:
171 retval = macho.LC_REGISTRY[cmd].__name__
172 retval = retval.replace("_command","")
173 return retval
174
175 class VisualMachoMap(object):
176 KB_1 = 1024
177 KB_16 = 16 * 1024
178 MB_1 = 1 * 1024 * 1024
179 GB_1 = 1 * 1024 * 1024 * 1024
180
181 def __init__(self, name, width=40):
182 self.name = name
183 self.width = 40
184 self.default_side_padding = 2
185
186 def get_header_line(self):
187 return '+' + '-' * (self.width - 2) + '+'
188
189 def get_space_line(self):
190 return '|' + ' ' * (self.width - 2) + '|'
191
192 def get_dashed_line(self):
193 return '|' + '-' * (self.width - 2) + '|'
194
195 def get_dotted_line(self):
196 return '|' + '.' * (self.width - 2) + '|'
197
198 def center_text_in_line(self, line, text):
199 even_length = bool(len(text) % 2 == 0)
200 if len(text) > len(line) - 2:
201 raise ValueError("text is larger than line of text")
202
203 lbreak_pos = len(line)/2 - len(text)/2
204 if not even_length:
205 lbreak_pos -= 1
206 out = line[:lbreak_pos] + text
207 return out + line[len(out):]
208
209 def get_separator_lines(self):
210 return ['/' + ' ' * (self.width - 2) + '/', '/' + ' ' * (self.width - 2) + '/']
211
212 def printMachoMap(self, mobj):
213 MapBlock = namedtuple('MapBlock', 'name vmaddr vmsize fileoff filesize extra_info is_segment')
214 outstr = self.name + '\n'
215 other_cmds = ''
216 blocks = []
217 for hdr in mobj.headers:
218 cmd_index = 0
219 for cmd in hdr.commands:
220 # cmds is [(load_command, segment, [sections..])]
221 (lc, segment, sections) = cmd
222 lc_cmd_str = get_load_command_human_name(lc.cmd)
223 lc_str_rep = "\n\t LC: {:s} size:{:d} nsects:{:d}".format(lc_cmd_str, lc.cmdsize, len(sections))
224 # print lc_str_rep
225 if isinstance(segment, SEGMENT_TYPES):
226 segname = segment.segname[:segment.segname.find('\x00')]
227 # print "\tsegment: {:s} vmaddr: {:x} vmsize:{:d} fileoff: {:x} filesize: {:d}".format(
228 # segname, segment.vmaddr, segment.vmsize, segment.fileoff, segment.filesize)
229 blocks.append(MapBlock(segname, segment.vmaddr, segment.vmsize, segment.fileoff, segment.filesize,
230 ' LC:{} : {} init:{:#0X} max:{:#0X}'.format(lc_cmd_str, segname, segment.initprot, segment.maxprot),
231 True))
232 for section in sections:
233 section_name = section.sectname[:section.sectname.find('\x00')]
234 blocks.append(MapBlock(section_name, section.addr, section.size, section.offset,
235 section.size, 'al:{} flags:{:#0X}'.format(section.align, section.flags), False))
236 #print "\t\tsection:{:s} addr:{:x} off:{:x} size:{:d}".format(section_name, section.addr, section.offset, section.size)
237 elif isinstance(segment, macholib.mach_o.uuid_command):
238 other_cmds += "\n\t uuid: {:s}".format(GetUUIDSummary(segment.uuid))
239 elif isinstance(segment, macholib.mach_o.rpath_command):
240 other_cmds += "\n\t rpath: {:s}".format(segment.path)
241 elif isinstance(segment, macholib.mach_o.dylib_command):
242 other_cmds += "\n\t dylib: {:s} ({:s})".format(str(sections[:sections.find('\x00')]), str(segment.current_version))
243 else:
244 other_cmds += lc_str_rep
245 cmd_index += 1
246
247 # fixup the self.width param
248 for _b in blocks:
249 if self.default_side_padding + len(_b.name) + 2 > self.width:
250 self.width = self.default_side_padding + len(_b.name) + 2
251 if self.width % 2 != 0:
252 self.width += 1
253
254 sorted_blocks = sorted(blocks, key=lambda b: b.vmaddr)
255 mstr = [self.get_header_line()]
256 prev_block = MapBlock('', 0, 0, 0, 0, '', False)
257 for b in sorted_blocks:
258 # TODO add separator blocks if vmaddr is large from prev_block
259 if b.is_segment:
260 s = self.get_dashed_line()
261 else:
262 s = self.get_dotted_line()
263 s = self.center_text_in_line(s, b.name)
264 line = "{:s} {: <#020X} ({: <10d}) floff:{: <#08x} {}".format(s, b.vmaddr, b.vmsize, b.fileoff, b.extra_info)
265 if (b.vmaddr - prev_block.vmaddr) > VisualMachoMap.KB_16:
266 mstr.append(self.get_space_line())
267 mstr.append(self.get_space_line())
268
269 mstr.append(line)
270
271 if b.vmsize > VisualMachoMap.MB_1:
272 mstr.append(self.get_space_line())
273 mstr.extend(self.get_separator_lines())
274 mstr.append(self.get_space_line())
275 #mstr.append(self.get_space_line())
276 prev_block = b
277 mstr.append(self.get_space_line())
278 if prev_block.vmsize > VisualMachoMap.KB_16:
279 mstr.append(self.get_space_line())
280 mstr.append(self.get_header_line())
281 print outstr
282 print "\n".join(mstr)
283 print "\n\n=============== Other Load Commands ==============="
284 print other_cmds
285
286
287 if __name__ == '__main__':
288 import sys
289 if len(sys.argv) < 2:
290 print "Usage: {} /path/to/macho_binary".format(sys.argv[0])
291 sys.exit(1)
292 with open(sys.argv[-1], 'rb') as fp:
293 data = fp.read()
294 mobject = MemMacho(data, len(data))
295
296 p = VisualMachoMap(sys.argv[-1])
297 p.printMachoMap(mobject)
298 sys.exit(0)
299