hilda 3.0.1__py3-none-any.whl → 3.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
hilda/symbol.py CHANGED
@@ -1,7 +1,8 @@
1
1
  import os
2
2
  import struct
3
3
  from contextlib import contextmanager
4
- from typing import Any, Optional
4
+ from functools import cached_property
5
+ from typing import Any, Optional, Tuple
5
6
 
6
7
  from construct import FormatField
7
8
 
@@ -25,16 +26,36 @@ class SymbolFormatField(FormatField):
25
26
  return self._client.symbol(FormatField._parse(self, stream, context, path))
26
27
 
27
28
 
29
+ """
30
+ A value identifying a `HildaSymbol`.
31
+
32
+ `HildaSymbol`s are either regular (i.e., named) symbols or anonymous symbols.
33
+
34
+ Regular symbols are uniquely identified by a `HildaSymbolId` (i.e., no two
35
+ instances of `HildaSymbol` have the same ID).
36
+ Note that several regular symbols may have the same address (with different names).
37
+
38
+ Anonymous symbols are not uniquely identified by a `HildaSymbolId`.
39
+ """
40
+ HildaSymbolId = Tuple[Optional[str], int]
41
+
42
+
28
43
  class Symbol(int):
44
+ """
45
+ Hilda's class representing a symbol (not necessarily an LLDB symbol).
46
+ """
47
+
29
48
  PROXY_METHODS = ['peek', 'poke', 'peek_str', 'monitor', 'bp',
30
49
  'disass', 'po', 'objc_call']
31
50
 
32
51
  @classmethod
33
- def create(cls, value: int, client):
52
+ def create(cls, value: int, client, lldb_symbol: Optional[lldb.SBSymbol] = None,
53
+ lldb_address: Optional[lldb.SBAddress] = None, lldb_type: Optional[int] = None) -> None:
34
54
  """
35
55
  Create a Symbol object.
36
56
  :param value: Symbol address.
37
57
  :param hilda.hilda_client.HildaClient client: Hilda client.
58
+ :param lldb.SBSymbol lldb_symbol: LLDB symbol.
38
59
  :return: Symbol object.
39
60
  :rtype: Symbol
40
61
  """
@@ -56,14 +77,12 @@ class Symbol(int):
56
77
  symbol._file_address = None
57
78
 
58
79
  # getting more data out from lldb
59
- lldb_symbol = client.target.ResolveLoadAddress(int(symbol) & 0xFFFFFFFFFFFFFFFF)
60
- file_address = lldb_symbol.file_addr
61
- type_ = lldb_symbol.symbol.type
62
- filename = lldb_symbol.module.file.basename
63
-
64
- symbol._file_address = file_address
65
- symbol.type_ = type_
66
- symbol.filename = filename
80
+ if lldb_address is None:
81
+ lldb_address = client.target.ResolveLoadAddress(int(symbol) & 0xFFFFFFFFFFFFFFFF)
82
+ if lldb_type is None:
83
+ lldb_type = lldb_address.symbol.type
84
+ symbol.type_ = lldb_type
85
+ symbol.lldb_address = lldb_address
67
86
  symbol.lldb_symbol = lldb_symbol
68
87
 
69
88
  for method_name in Symbol.PROXY_METHODS:
@@ -73,12 +92,24 @@ class Symbol(int):
73
92
  return symbol
74
93
 
75
94
  @property
95
+ def id(self) -> HildaSymbolId:
96
+ return (self.lldb_name, int(self))
97
+
98
+ @property
99
+ def lldb_name(self) -> Optional[str]:
100
+ return self.lldb_symbol.GetName() if self.lldb_symbol is not None else None
101
+
102
+ @cached_property
76
103
  def file_address(self) -> int:
77
104
  """
78
105
  Get symbol file address (address without ASLR)
79
106
  :return: File address
80
107
  """
81
- return self._file_address
108
+ return self.lldb_address.file_addr
109
+
110
+ @cached_property
111
+ def filename(self):
112
+ return self.lldb_address.module.file.basename
82
113
 
83
114
  @property
84
115
  def objc_class(self) -> Class:
@@ -249,7 +280,12 @@ class Symbol(int):
249
280
  self._client.poke(self + item * self.item_size, value)
250
281
 
251
282
  def __repr__(self):
252
- return f'<{type(self).__name__}: {hex(self)}>'
283
+ address = int(self)
284
+ name = self.lldb_name
285
+ if name is not None:
286
+ return f'Symbol({name}, 0x{address:016X})'
287
+ else:
288
+ return f'AnonymousSymbol(0x{address:016X})'
253
289
 
254
290
  def __str__(self):
255
291
  return hex(self)
hilda/symbols.py ADDED
@@ -0,0 +1,595 @@
1
+ import json
2
+ import re
3
+ import shlex
4
+ from itertools import chain
5
+ from tempfile import NamedTemporaryFile
6
+ from typing import Iterator, Optional, Tuple, Union
7
+
8
+ from tqdm import tqdm
9
+
10
+ from hilda.exceptions import SymbolAbsentError
11
+ from hilda.lldb_importer import lldb
12
+ from hilda.symbol import HildaSymbolId, Symbol
13
+
14
+
15
+ class SymbolList:
16
+ """
17
+ Manager for `Symbol` objects, each one representing a symbol.
18
+
19
+ `Symbol`s are either regular (i.e., named) symbols or anonymous symbols.
20
+ Only regular symbols are managed by this class, though anonymous
21
+ symbols can be created by this class (using the function `add`).
22
+ """
23
+
24
+ def __init__(self, hilda) -> None:
25
+ """
26
+ Initialize a symbol list.
27
+
28
+ :param hilda.hilda_client.HildaClient hilda: Hilda client
29
+ """
30
+ self._hilda = hilda
31
+ self._modules = set()
32
+ self._symbols = {}
33
+ self._symbols_by_name = {}
34
+
35
+ # There should be only one "global" symbol list instance, and it should be referenced by the HildaClient class.
36
+ # The global symbols list contains (lazily) all symbols (from all modules).
37
+ if not hasattr(hilda, 'symbols'):
38
+ self._global = self
39
+ else:
40
+ self._global = hilda.symbols
41
+
42
+ def __iter__(self) -> Iterator[Symbol]:
43
+ self._populate_cache()
44
+
45
+ for symbol in self._symbols.values():
46
+ yield symbol
47
+
48
+ def __len__(self) -> int:
49
+ return sum(1 for _ in self)
50
+
51
+ def __contains__(self,
52
+ address_or_name_or_id_or_symbol: Union[int, str, HildaSymbolId, lldb.SBSymbol, Symbol]) -> bool:
53
+ return self.get(address_or_name_or_id_or_symbol) is not None
54
+
55
+ def __getitem__(self,
56
+ address_or_name_or_id_or_symbol: Union[int, str, HildaSymbolId, lldb.SBSymbol, Symbol]) -> Symbol:
57
+ """
58
+ Get a symbol by address or name or ID (or the symbol itself, though it usually makes little sense)
59
+
60
+ :param address_or_name_or_id_or_symbol: Address or name or ID (or the symbol itself)
61
+ """
62
+ symbol = self.get(address_or_name_or_id_or_symbol)
63
+ if symbol is None:
64
+ raise SymbolAbsentError(f'no such symbol: {address_or_name_or_id_or_symbol}')
65
+ # raise KeyError(address_or_name_or_id_or_symbol)
66
+ return symbol
67
+
68
+ def __delitem__(self,
69
+ address_or_name_or_id_or_symbol: Union[int, str, HildaSymbolId, lldb.SBSymbol, Symbol]) -> None:
70
+ """
71
+ Remove a symbol (unless this is the global symbol list - see remove())
72
+
73
+ :param address_or_name_or_id_or_symbol: Address or name or ID (or the symbol itself)
74
+ """
75
+ self.remove(address_or_name_or_id_or_symbol)
76
+
77
+ def __repr__(self) -> str:
78
+ if self._global is self:
79
+ return (f'<{self.__class__.__name__} GLOBAL>')
80
+ else:
81
+ return repr(list(self))
82
+
83
+ def __str__(self) -> str:
84
+ return repr(self)
85
+
86
+ def get(self, address_or_name_or_id_or_symbol: Union[int, str, HildaSymbolId, lldb.SBSymbol, Symbol]) \
87
+ -> Optional[Symbol]:
88
+ """
89
+ Get a symbol by address or name or ID (or the symbol itself, though it usually makes little sense)
90
+
91
+ :param address_or_name_or_id_or_symbol: Address or name or ID (or the symbol itself)
92
+ :return: `Symbol` if one exists, or `None` otherwise
93
+ """
94
+ symbol = self._get_lldb_symbol(address_or_name_or_id_or_symbol)
95
+ if symbol is None:
96
+ return None
97
+
98
+ lldb_symbol, lldb_address, name, address, type_ = symbol
99
+ sym_id = (name, address)
100
+ if sym_id not in self._symbols and self._global is self:
101
+ symbol = Symbol.create(address, self._hilda, lldb_symbol, lldb_address, type_)
102
+ self._add(sym_id, symbol)
103
+ return symbol
104
+
105
+ return self._symbols.get(sym_id)
106
+
107
+ def _populate_cache(self, module_uuid_filter=None) -> None:
108
+ if self._global is self:
109
+ modules = self._hilda.target.modules
110
+ modules_not_cached = [module for module in modules if module.GetUUIDString() not in self._modules]
111
+ if module_uuid_filter is not None:
112
+ modules_not_cached = [module for module in modules if module.GetUUIDString() == module_uuid_filter]
113
+ if len(modules_not_cached) != 0:
114
+ for lldb_module in tqdm(modules_not_cached, desc='Populating Hilda symbols cache'):
115
+ for lldb_symbol in lldb_module.symbols:
116
+ _ = self.get(lldb_symbol)
117
+ self._modules.add(lldb_module.GetUUIDString())
118
+
119
+ def force_refresh(self, module_range=None, module_filename_filter=''):
120
+ """
121
+ Force a refresh of symbols
122
+ :param module_range: index range for images to load in the form of [start, end]
123
+ :param module_filename_filter: filter only images containing given expression
124
+ """
125
+ self.log_debug('Force symbols')
126
+
127
+ if self._global is not self:
128
+ self._hilda.log_error('Cannot refresh a non-global symbol list')
129
+ return
130
+
131
+ for i, lldb_module in enumerate(tqdm(self._hilda.target.modules)):
132
+ filename = lldb_module.file.basename
133
+
134
+ if module_filename_filter not in filename:
135
+ continue
136
+
137
+ if module_range is not None and (i < module_range[0] or i > module_range[1]):
138
+ continue
139
+
140
+ for lldb_symbol in lldb_module.symbols:
141
+ # Getting the symbol would insert it if it does not exist
142
+ _ = self.get(lldb_symbol)
143
+
144
+ def _add(self, sym_id: HildaSymbolId, symbol: Symbol) -> None:
145
+ self._symbols[sym_id] = symbol
146
+ name, address = sym_id
147
+ if name is not None and re.match(r'^[a-zA-Z0-9_]+$', name):
148
+ ids = self._symbols_by_name.get(name)
149
+ if ids is not None:
150
+ ids.append(sym_id)
151
+ else:
152
+ self._symbols_by_name[name] = [sym_id]
153
+
154
+ def add(self, value: Union[int, Symbol], symbol_name: Optional[str] = None, symbol_type: Optional[str] = None,
155
+ symbol_size: Optional[int] = None) -> Symbol:
156
+ """
157
+ Add a symbol.
158
+ Returns existing symbol if a matching regular (i.e., non-anonymous) symbol exists.
159
+
160
+ :param value: The address of the symbol (in memory) or and existing `Symbol`.
161
+ :param symbol_name: The name of the symbol.
162
+ :param symbol_type: The type of the symbol (either 'code' of 'data', defaults to 'code').
163
+ :param symbol_size: The size of the symbol (defaults to 8 bytes).
164
+ :return: The symbol
165
+ """
166
+ # Check if we already created the symbol
167
+ if (isinstance(value, Symbol) and
168
+ (symbol_type, symbol_size) == (None, None) and
169
+ value.lldb_symbol is not None and
170
+
171
+ # TODO: Is it an error to add again, providing the same name?
172
+ (symbol_name is None or symbol_name == value.lldb_name)):
173
+ self._add(value.id, value)
174
+ return value
175
+
176
+ # Adding an existing anonymous symbol. Ignore the fact that this is actually a symbol.
177
+ if isinstance(value, Symbol) and value.lldb_name is None:
178
+ value = int(value)
179
+
180
+ # Adding an existing symbol. Do not provide any other arguments.
181
+ if isinstance(value, Symbol) and (symbol_name, symbol_type, symbol_size) != (None, None, None):
182
+ raise ValueError()
183
+
184
+ # Adding a new symbol without specifying a name. Symbol type and size are not (currently) supported.
185
+ if isinstance(value, int) and symbol_name is None and (symbol_type, symbol_size) != (None, None):
186
+ raise ValueError()
187
+
188
+ # Add
189
+
190
+ # Check if we can get a global symbol
191
+ symbol_address = value
192
+ global_symbol = self._global.get(symbol_address) if symbol_name is None else (
193
+ self._global.get((symbol_name, symbol_address)))
194
+ if global_symbol is not None:
195
+ if (symbol_type, symbol_size) != (None, None):
196
+ raise ValueError()
197
+ return self.add(global_symbol)
198
+
199
+ # Check if this is an anonymous symbol
200
+ if symbol_name is None:
201
+ # Anonymous symbols need not be added to _symbols.
202
+ return Symbol.create(symbol_address, self._hilda, None)
203
+
204
+ # Add a new global symbol
205
+ symbol = self._global._add_lldb_symbol(
206
+ symbol_name,
207
+ symbol_address,
208
+ symbol_type if symbol_type is not None else 'code',
209
+ symbol_size if symbol_size is not None else 8)
210
+ return self.add(symbol)
211
+
212
+ def _remove(self, sym_id: HildaSymbolId) -> None:
213
+ del self._symbols[sym_id]
214
+ name, address = sym_id
215
+ if name is not None and re.match(r'^[a-zA-Z0-9_]+$', name):
216
+ ids = self._symbols_by_name[name]
217
+ if len(ids) == 1:
218
+ ids.remove(sym_id)
219
+ else:
220
+ del self._symbols_by_name[name]
221
+
222
+ def remove(self, address_or_name_or_id_or_symbol: Union[int, str, HildaSymbolId, lldb.SBSymbol, Symbol]) -> None:
223
+ """
224
+ Remove a symbol.
225
+
226
+ :param address_or_name_or_id_or_symbol: Address or name or ID (or the symbol itself)
227
+ """
228
+ if self._global is self:
229
+ raise Exception('Cannot remove from the global symbols list')
230
+
231
+ symbol = self[address_or_name_or_id_or_symbol]
232
+ self._remove(symbol.id)
233
+
234
+ def items(self) -> Iterator[Tuple[HildaSymbolId, Symbol]]:
235
+ """
236
+ Get a symbol ID and symbol object tuple for every symbol
237
+ """
238
+ return ((symbol.id, symbol) for symbol in self)
239
+
240
+ def keys(self) -> Iterator[HildaSymbolId]:
241
+ """
242
+ Get the symbol ID for every symbol
243
+ """
244
+ return (symbol.id for symbol in self)
245
+
246
+ def values(self) -> Iterator[Symbol]:
247
+ """
248
+ Get the symbol object for every symbol
249
+ """
250
+ return (symbol for symbol in self)
251
+
252
+ def __getattr__(self, attribute_name: str) -> Symbol:
253
+ """
254
+ Returns a symbol appropriate to the attribute requested.
255
+
256
+ For example:
257
+ support a `symbols.malloc()` syntax.
258
+ support a `symbols.x0x11223344` syntax.
259
+ support a `symbols.x11223344` syntax.
260
+ """
261
+ match = re.fullmatch(r'x(?:0x)?([0-9a-fA-F]{6,16})', attribute_name)
262
+ if match:
263
+ address = int(match[1], base=0x10)
264
+ return self.add(address)
265
+ value = self.get(attribute_name)
266
+ if value is None:
267
+ raise SymbolAbsentError(f"SymbolList object has no attribute '{attribute_name}'")
268
+ return value
269
+
270
+ def __dir__(self):
271
+ self._populate_cache()
272
+
273
+ # Return normal attributes and symbol names
274
+ return chain(super().__dir__(), self._symbols_by_name.keys())
275
+
276
+ def _get_lldb_symbol_from_name(self, name: str, address: Optional[int] = None) \
277
+ -> Optional[Tuple[lldb.SBSymbol, lldb.SBAddress, str, int, int]]:
278
+ lldb_symbol_context_list = list(self._hilda.target.FindSymbols(name))
279
+
280
+ for lldb_symbol_context in list(lldb_symbol_context_list):
281
+ # Verify because FindSymbols finds `_Z3foov` when looking for `foo`
282
+ if lldb_symbol_context.symbol.name != name:
283
+ lldb_symbol_context_list.remove(lldb_symbol_context)
284
+ self._hilda.log_debug(f'Ignoring symbol {lldb_symbol_context.symbol.name} (similar to {name})')
285
+
286
+ if address is not None:
287
+ for lldb_symbol_context in list(lldb_symbol_context_list):
288
+ lldb_symbol_context_address = lldb_symbol_context.symbol.GetStartAddress().GetLoadAddress(
289
+ self._hilda.target)
290
+ if lldb_symbol_context_address != address:
291
+ lldb_symbol_context_list.remove(lldb_symbol_context)
292
+ self._hilda.log_debug(f'Ignoring symbol {name}@0x{lldb_symbol_context_address:016X} '
293
+ f'(beacause address is not 0x{address:016X})')
294
+
295
+ symbols = []
296
+ for lldb_symbol_context in lldb_symbol_context_list:
297
+ symbol = self._get_lldb_symbol(lldb_symbol_context.symbol)
298
+ if symbol is None:
299
+ self._hilda.log_debug(f'Ignoring symbol {lldb_symbol_context} (failed to convert)')
300
+ continue
301
+
302
+ if address is not None:
303
+ lldb_symbol, lldb_address, symbol_name, symbol_address, symbol_type = symbol
304
+ if address != symbol_address:
305
+ continue
306
+
307
+ symbols.append(symbol)
308
+
309
+ if len(symbols) == 0:
310
+ return None
311
+
312
+ # TODO: Should we really pick the first? Maybe the last? Something else?
313
+ # if len(lldb_symbols) != 1:
314
+ # # Error out if we found multiple symbols with the same name and same address
315
+ # raise KeyError((name, address))
316
+
317
+ return symbols[0]
318
+
319
+ def _get_lldb_symbol(self, value: Union[int, str, HildaSymbolId, Symbol, lldb.SBAddress, lldb.SBSymbol]) \
320
+ -> Optional[Tuple[lldb.SBSymbol, lldb.SBAddress, str, int, int]]:
321
+ if isinstance(value, Symbol):
322
+ symbol = value
323
+ return self._get_lldb_symbol(symbol.id)
324
+ elif isinstance(value, int):
325
+ address = value & 0xFFFFFFFFFFFFFFFF
326
+ lldb_address = self._hilda.target.ResolveLoadAddress(address)
327
+ return self._get_lldb_symbol(lldb_address)
328
+ elif isinstance(value, tuple): # HildaSymbolId
329
+ if len(value) != 2:
330
+ raise TypeError()
331
+ name, address = value
332
+ if not (name is None or isinstance(name, str)):
333
+ raise TypeError()
334
+ if not (isinstance(address, int)):
335
+ raise TypeError()
336
+
337
+ if name is None:
338
+ return self._get_lldb_symbol(address)
339
+ else:
340
+ return self._get_lldb_symbol_from_name(name, address)
341
+ elif isinstance(value, str):
342
+ name = value
343
+ return self._get_lldb_symbol_from_name(name)
344
+ elif isinstance(value, lldb.SBAddress):
345
+ lldb_address = value
346
+ lldb_symbol_context = self._hilda.target.ResolveSymbolContextForAddress(lldb_address,
347
+ lldb.eSymbolContextEverything)
348
+ lldb_symbol = lldb_symbol_context.symbol
349
+
350
+ address = lldb_address.GetLoadAddress(self._hilda.target)
351
+ lldb_symbol_address = lldb_symbol.GetStartAddress().GetLoadAddress(self._hilda.target)
352
+ if address != lldb_symbol_address:
353
+ return None
354
+
355
+ return self._get_lldb_symbol(lldb_symbol)
356
+ elif isinstance(value, lldb.SBSymbol):
357
+ lldb_symbol = value
358
+
359
+ # Ignore symbols not having a real name
360
+ symbol_name = lldb_symbol.GetName()
361
+ if symbol_name in ('<redacted>',):
362
+ return None
363
+
364
+ # Ignore symbols not having a real address
365
+ lldb_address = lldb_symbol.GetStartAddress()
366
+ symbol_address = lldb_address.GetLoadAddress(self._hilda.target)
367
+ if symbol_address == 0xffffffffffffffff:
368
+ return None
369
+
370
+ # Ignore symbols not having a useful type
371
+ symbol_type = lldb_symbol.GetType()
372
+ if symbol_type not in (lldb.eSymbolTypeCode,
373
+ lldb.eSymbolTypeRuntime,
374
+ lldb.eSymbolTypeData,
375
+ lldb.eSymbolTypeObjCMetaClass):
376
+ return None
377
+
378
+ return (lldb_symbol, lldb_address, symbol_name, symbol_address, symbol_type)
379
+ else:
380
+ raise TypeError()
381
+
382
+ def _add_lldb_symbol(self, symbol_name: str, symbol_address: int, symbol_type: str, symbol_size) -> lldb.SBSymbol:
383
+ with NamedTemporaryFile(mode='w+', suffix='.json') as symbols_file:
384
+ lldb_address = self._hilda.target.ResolveLoadAddress(symbol_address)
385
+ lldb_module = lldb_address.module
386
+ symbol_file_address = lldb_address.GetFileAddress()
387
+
388
+ # Create symbol file
389
+ data = {
390
+ "triple": lldb_module.GetTriple(),
391
+ "uuid": lldb_module.GetUUIDString(),
392
+ "symbols": [{
393
+ "name": symbol_name,
394
+ "type": symbol_type,
395
+ "size": symbol_size,
396
+ "address": symbol_file_address,
397
+ }],
398
+ }
399
+ json.dump(data, symbols_file)
400
+ symbols_file.flush()
401
+
402
+ # Add symbol from file
403
+ symbols_before = lldb_module.FindSymbols(symbol_name)
404
+ if len(symbols_before) != 0:
405
+ raise Exception(
406
+ f'Failed to add symbol {symbol_name} to {lldb_module.file}'
407
+ f' (symbol already exists {symbols_before})')
408
+
409
+ result = self._hilda.lldb_handle_command(f'target symbols add {shlex.quote(symbols_file.name)}',
410
+ capture_output=True)
411
+
412
+ # Verify command executed as expected
413
+ if result is None:
414
+ raise Exception(f'Failed to add symbol {symbol_name} to {lldb_module.file}')
415
+ expected_result = f"symbol file '{symbols_file.name}' has been added to '{lldb_module.file}'\n"
416
+ if expected_result != result:
417
+ raise Exception(
418
+ f'Failed to add symbol {symbol_name} to {lldb_module.file}'
419
+ f' (expected: {json.dumps(expected_result)}, output: {json.dumps(result)})')
420
+
421
+ # Verify the symbol was added
422
+ symbols_after = lldb_module.FindSymbols(symbol_name)
423
+ if len(symbols_after) != 1:
424
+ raise Exception(f'Failed to add symbol {symbol_name} to {lldb_module.file}')
425
+
426
+ return self.get((symbol_name, symbol_address))
427
+
428
+ # Actions
429
+
430
+ def bp(self, callback=None, **args):
431
+ """
432
+ Place a breakpoint on all symbols in current list.
433
+ Look for the bp command for more details.
434
+ :param callback: callback function to be executed upon an hit
435
+ :param args: optional args for the bp command
436
+ """
437
+ for v in self.values():
438
+ v.bp(callback, **args)
439
+
440
+ def monitor(self, **args):
441
+ """
442
+ Perform monitor for all symbols in current list.
443
+ See monitor command for more details.
444
+ :param args: given arguments for monitor command
445
+ """
446
+ for (name, address), symbol in self.items():
447
+ options = args.copy()
448
+ if name is None:
449
+ continue
450
+ if self._hilda.configs.objc_verbose_monitor:
451
+ arg_count = name.count(':')
452
+ if arg_count > 0:
453
+ arg_count = min(6, arg_count)
454
+ options['expr'] = {f'$arg{i + 3}': 'po' for i in range(arg_count)}
455
+ name = options.get('name', name)
456
+ address.monitor(name=name, **options)
457
+
458
+ # Filters
459
+
460
+ def __sub__(self, other: 'SymbolList') -> 'SymbolList':
461
+ retval = SymbolList(self._hilda)
462
+ for v in self.values():
463
+ if v not in other:
464
+ retval.add(v)
465
+ return retval
466
+
467
+ def __add__(self, other: 'SymbolList') -> 'SymbolList':
468
+ retval = SymbolList(self._hilda)
469
+ for v in other.values():
470
+ retval.add(v)
471
+ for v in self.values():
472
+ retval.add(v)
473
+ return retval
474
+
475
+ def filter_by_module(self, substring: str) -> 'SymbolList':
476
+ """
477
+ Filter symbols who's module name contains the provided substring
478
+ :return: reduced symbol list
479
+ """
480
+
481
+ def optimized_iter():
482
+ if self._global is self:
483
+ for lldb_module in self._hilda.target.modules:
484
+ if substring not in lldb_module.file.basename:
485
+ continue
486
+
487
+ for lldb_symbol in lldb_module.symbols:
488
+ symbol = self.get(lldb_symbol)
489
+
490
+ if symbol is None:
491
+ # This should only happen if we do not want to expose certain symbols
492
+ continue
493
+
494
+ yield symbol
495
+ else:
496
+ for symbol in self:
497
+ yield symbol
498
+
499
+ retval = SymbolList(self._hilda)
500
+ for symbol in optimized_iter():
501
+ if substring in symbol.filename:
502
+ retval.add(symbol)
503
+
504
+ return retval
505
+
506
+ def filter_symbol_type(self, lldb_type) -> 'SymbolList':
507
+ """
508
+ Filter by LLDB symbol types (for example: lldb.eSymbolTypeCode,
509
+ lldb.eSymbolTypeData, ...)
510
+ :param lldb_type: symbol type from LLDB consts
511
+ :return: symbols matching the type filter
512
+ """
513
+ retval = SymbolList(self._hilda)
514
+ for v in self.values():
515
+ if v.type_ == lldb_type:
516
+ retval.add(v)
517
+ return retval
518
+
519
+ def filter_code_symbols(self) -> 'SymbolList':
520
+ """
521
+ Filter only code symbols
522
+ :return: symbols with type lldb.eSymbolTypeCode
523
+ """
524
+ return self.filter_symbol_type(lldb.eSymbolTypeCode)
525
+
526
+ def filter_data_symbols(self):
527
+ """
528
+ Filter only data symbols
529
+ :return: symbols with type lldb.eSymbolTypeCode
530
+ """
531
+ return self.filter_symbol_type(lldb.eSymbolTypeData)
532
+
533
+ def filter_objc_classes(self):
534
+ """
535
+ Filter only objc meta classes
536
+ :return: symbols with type lldb.eSymbolTypeObjCMetaClass
537
+ """
538
+ return self.filter_symbol_type(lldb.eSymbolTypeObjCMetaClass)
539
+
540
+ def filter_startswith(self, exp, case_sensitive=True):
541
+ """
542
+ Filter only symbols with given prefix
543
+ :param exp: prefix
544
+ :param case_sensitive: is case sensitive
545
+ :return: reduced symbol list
546
+ """
547
+ if not case_sensitive:
548
+ exp = exp.lower()
549
+
550
+ retval = SymbolList(self._hilda)
551
+ for v in self.values():
552
+ name = v.lldb_name
553
+ if not case_sensitive:
554
+ name = name.lower()
555
+ if name.startswith(exp):
556
+ retval.add(v)
557
+ return retval
558
+
559
+ def filter_endswith(self, exp, case_sensitive=True):
560
+ """
561
+ Filter only symbols with given prefix
562
+ :param exp: prefix
563
+ :param case_sensitive: is case sensitive
564
+ :return: reduced symbol list
565
+ """
566
+ if not case_sensitive:
567
+ exp = exp.lower()
568
+
569
+ retval = SymbolList(self._hilda)
570
+ for v in self.values():
571
+ name = v.lldb_name
572
+ if not case_sensitive:
573
+ name = name.lower()
574
+ if name.endswith(exp):
575
+ retval.add(v)
576
+ return retval
577
+
578
+ def filter_name_contains(self, exp, case_sensitive=True):
579
+ """
580
+ Filter symbols containing a given expression
581
+ :param exp: given expression
582
+ :param case_sensitive: is case sensitive
583
+ :return: reduced symbol list
584
+ """
585
+ if not case_sensitive:
586
+ exp = exp.lower()
587
+
588
+ retval = SymbolList(self._hilda)
589
+ for v in self.values():
590
+ name = v.lldb_name
591
+ if not case_sensitive:
592
+ name = name.lower()
593
+ if exp in name:
594
+ retval.add(v)
595
+ return retval