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/_version.py +16 -3
- hilda/breakpoints.py +30 -7
- hilda/decorators.py +46 -0
- hilda/exceptions.py +1 -6
- hilda/hilda_client.py +21 -70
- hilda/ipython_extensions/events.py +14 -18
- hilda/objective_c_class.py +86 -29
- hilda/objective_c_symbol.py +5 -16
- hilda/snippets/libmalloc.py +9 -9
- hilda/symbol.py +48 -12
- hilda/symbols.py +595 -0
- hilda/watchpoints.py +41 -9
- {hilda-3.0.1.dist-info → hilda-3.2.1.dist-info}/METADATA +107 -183
- {hilda-3.0.1.dist-info → hilda-3.2.1.dist-info}/RECORD +18 -18
- {hilda-3.0.1.dist-info → hilda-3.2.1.dist-info}/WHEEL +1 -1
- hilda/hilda_ascii_art.html +0 -40
- hilda/symbols_jar.py +0 -208
- {hilda-3.0.1.dist-info → hilda-3.2.1.dist-info}/entry_points.txt +0 -0
- {hilda-3.0.1.dist-info → hilda-3.2.1.dist-info/licenses}/LICENSE +0 -0
- {hilda-3.0.1.dist-info → hilda-3.2.1.dist-info}/top_level.txt +0 -0
hilda/_version.py
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
# file generated by setuptools-scm
|
|
2
2
|
# don't change, don't track in version control
|
|
3
3
|
|
|
4
|
-
__all__ = [
|
|
4
|
+
__all__ = [
|
|
5
|
+
"__version__",
|
|
6
|
+
"__version_tuple__",
|
|
7
|
+
"version",
|
|
8
|
+
"version_tuple",
|
|
9
|
+
"__commit_id__",
|
|
10
|
+
"commit_id",
|
|
11
|
+
]
|
|
5
12
|
|
|
6
13
|
TYPE_CHECKING = False
|
|
7
14
|
if TYPE_CHECKING:
|
|
@@ -9,13 +16,19 @@ if TYPE_CHECKING:
|
|
|
9
16
|
from typing import Union
|
|
10
17
|
|
|
11
18
|
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
+
COMMIT_ID = Union[str, None]
|
|
12
20
|
else:
|
|
13
21
|
VERSION_TUPLE = object
|
|
22
|
+
COMMIT_ID = object
|
|
14
23
|
|
|
15
24
|
version: str
|
|
16
25
|
__version__: str
|
|
17
26
|
__version_tuple__: VERSION_TUPLE
|
|
18
27
|
version_tuple: VERSION_TUPLE
|
|
28
|
+
commit_id: COMMIT_ID
|
|
29
|
+
__commit_id__: COMMIT_ID
|
|
19
30
|
|
|
20
|
-
__version__ = version = '3.
|
|
21
|
-
__version_tuple__ = version_tuple = (3,
|
|
31
|
+
__version__ = version = '3.2.1'
|
|
32
|
+
__version_tuple__ = version_tuple = (3, 2, 1)
|
|
33
|
+
|
|
34
|
+
__commit_id__ = commit_id = None
|
hilda/breakpoints.py
CHANGED
|
@@ -124,12 +124,30 @@ class HildaBreakpoint:
|
|
|
124
124
|
for name in names_to_add:
|
|
125
125
|
self.lldb_breakpoint.AddName(name)
|
|
126
126
|
|
|
127
|
+
@property
|
|
128
|
+
def enabled(self) -> bool:
|
|
129
|
+
"""
|
|
130
|
+
Configures whether this breakpoint is enabled or not.
|
|
131
|
+
"""
|
|
132
|
+
return self.lldb_breakpoint.IsEnabled()
|
|
133
|
+
|
|
134
|
+
@enabled.setter
|
|
135
|
+
def enabled(self, value: bool) -> None:
|
|
136
|
+
self.lldb_breakpoint.SetEnabled(value)
|
|
137
|
+
|
|
127
138
|
def __repr__(self) -> str:
|
|
128
|
-
|
|
139
|
+
enabled_repr = 'ENABLED' if self.enabled else 'DISABLED'
|
|
140
|
+
guarded_repr = 'GUARDED' if self.guarded else 'NOT-GUARDED'
|
|
141
|
+
return (f'<{self.__class__.__name__} LLDB:{self.lldb_breakpoint} {enabled_repr} {guarded_repr} '
|
|
129
142
|
f'CALLBACK:{self.callback}>')
|
|
130
143
|
|
|
131
144
|
def __str__(self) -> str:
|
|
132
|
-
|
|
145
|
+
emoji = '🚨' if self.enabled else '🔕'
|
|
146
|
+
enabled_str = 'enabled' if self.enabled else 'disabled'
|
|
147
|
+
guarded_str = 'guarded' if self.guarded else 'not-guarded'
|
|
148
|
+
|
|
149
|
+
result = f'{emoji} Breakpoint #{self.id} ({enabled_str}, {guarded_str})\n'
|
|
150
|
+
|
|
133
151
|
if self.description is not None:
|
|
134
152
|
result += f'\tDescription: {self.description}\n'
|
|
135
153
|
|
|
@@ -137,10 +155,13 @@ class HildaBreakpoint:
|
|
|
137
155
|
result += f'\tWhere: {self.where}\n'
|
|
138
156
|
|
|
139
157
|
# A single breakpoint may be related to several locations (addresses)
|
|
158
|
+
locations = self.locations
|
|
159
|
+
if len(locations) == 0:
|
|
160
|
+
result += '\tNo locations\n'
|
|
140
161
|
for location in self.locations:
|
|
141
162
|
result += f'\tLocation {location}\n'
|
|
142
163
|
|
|
143
|
-
return result
|
|
164
|
+
return result.strip('\n')
|
|
144
165
|
|
|
145
166
|
def remove(self, remove_guarded: bool = False) -> None:
|
|
146
167
|
"""
|
|
@@ -206,6 +227,7 @@ class BreakpointList:
|
|
|
206
227
|
if it does not exist.
|
|
207
228
|
|
|
208
229
|
:param id_or_name_or_bp: Breakpoint's ID or name (or the breakpoint itself)
|
|
230
|
+
:return: `HildaBreakpoint` if one exists, or `None` otherwise
|
|
209
231
|
"""
|
|
210
232
|
|
|
211
233
|
if isinstance(id_or_name_or_bp, int):
|
|
@@ -341,14 +363,13 @@ class BreakpointList:
|
|
|
341
363
|
:param frame: LLDB frame
|
|
342
364
|
:param bp_loc: LLDB breakpoint location
|
|
343
365
|
"""
|
|
344
|
-
nonlocal
|
|
366
|
+
nonlocal name
|
|
345
367
|
bp = bp_loc.GetBreakpoint()
|
|
346
368
|
symbol = hilda.symbol(hilda.frame.addr.GetLoadAddress(hilda.target))
|
|
347
369
|
thread = hilda.thread
|
|
348
|
-
printed_name = name if name is not None else str(symbol.
|
|
370
|
+
printed_name = name if name is not None else str(symbol.lldb_address)
|
|
349
371
|
|
|
350
372
|
def format_value(fmt: Union[str, Callable], value: Symbol) -> str:
|
|
351
|
-
nonlocal hilda
|
|
352
373
|
if callable(fmt):
|
|
353
374
|
return fmt(hilda, value)
|
|
354
375
|
formatters = {
|
|
@@ -441,8 +462,10 @@ class BreakpointList:
|
|
|
441
462
|
|
|
442
463
|
def show(self) -> None:
|
|
443
464
|
""" Show existing breakpoints. """
|
|
465
|
+
if len(self) == 0:
|
|
466
|
+
self._hilda.log_info('No breakpoints')
|
|
444
467
|
for bp in self:
|
|
445
|
-
|
|
468
|
+
self._hilda.log_info(bp)
|
|
446
469
|
|
|
447
470
|
def items(self):
|
|
448
471
|
"""
|
hilda/decorators.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from hilda.breakpoints import WhereType
|
|
2
|
+
from hilda.lldb_importer import lldb
|
|
3
|
+
|
|
4
|
+
p = lldb.hilda_client
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def breakpoint(where: WhereType, enable: bool = False):
|
|
8
|
+
"""
|
|
9
|
+
A decorator to define a breakpoint, e.g.,
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
@breakpoint('symbol_name')
|
|
13
|
+
def my_breakpoint(hilda, *args):
|
|
14
|
+
print('Hit!')
|
|
15
|
+
hilda.cont()
|
|
16
|
+
|
|
17
|
+
my_breakpoint.enabled = True
|
|
18
|
+
```
|
|
19
|
+
"""
|
|
20
|
+
def decorator(callback):
|
|
21
|
+
bp = p.breakpoints.add(where, callback=callback)
|
|
22
|
+
bp.enabled = enable
|
|
23
|
+
return bp
|
|
24
|
+
|
|
25
|
+
return decorator
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def watchpoint(where: int, enable: bool = False):
|
|
29
|
+
"""
|
|
30
|
+
A decorator to define a watchpoint, e.g.,
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
@watchpoint(0x11223344)
|
|
34
|
+
def my_watchpoint(hilda, *args):
|
|
35
|
+
print('Hit!')
|
|
36
|
+
hilda.cont()
|
|
37
|
+
|
|
38
|
+
my_watchpoint.enabled = True
|
|
39
|
+
```
|
|
40
|
+
"""
|
|
41
|
+
def decorator(callback):
|
|
42
|
+
wp = p.watchpoints.add(where, callback=callback)
|
|
43
|
+
wp.enabled = enable
|
|
44
|
+
return wp
|
|
45
|
+
|
|
46
|
+
return decorator
|
hilda/exceptions.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
__all__ = ['HildaException', 'SymbolAbsentError', 'EvaluatingExpressionError', 'CreatingObjectiveCSymbolError',
|
|
2
2
|
'ConvertingToNsObjectError', 'ConvertingFromNSObjectError', 'DisableJetsamMemoryChecksError',
|
|
3
3
|
'GettingObjectiveCClassError', 'AccessingRegisterError', 'AccessingMemoryError',
|
|
4
|
-
'
|
|
4
|
+
'AddingLldbSymbolError', 'LLDBError', 'InvalidThreadIndexError']
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class HildaException(Exception):
|
|
@@ -59,11 +59,6 @@ class AccessingMemoryError(HildaException):
|
|
|
59
59
|
pass
|
|
60
60
|
|
|
61
61
|
|
|
62
|
-
class BrokenLocalSymbolsJarError(HildaException):
|
|
63
|
-
""" Raise when attempt to load an invalid symbols jar pickle """
|
|
64
|
-
pass
|
|
65
|
-
|
|
66
|
-
|
|
67
62
|
class AddingLldbSymbolError(HildaException):
|
|
68
63
|
""" Raise when failing to convert a LLDB symbol to Hilda's symbol. """
|
|
69
64
|
pass
|
hilda/hilda_client.py
CHANGED
|
@@ -10,7 +10,7 @@ import sys
|
|
|
10
10
|
import time
|
|
11
11
|
import typing
|
|
12
12
|
from collections import namedtuple
|
|
13
|
-
from contextlib import contextmanager
|
|
13
|
+
from contextlib import contextmanager
|
|
14
14
|
from dataclasses import dataclass, field
|
|
15
15
|
from datetime import datetime, timezone
|
|
16
16
|
from functools import cached_property, wraps
|
|
@@ -30,17 +30,16 @@ from traitlets.config import Config
|
|
|
30
30
|
from hilda import objective_c_class
|
|
31
31
|
from hilda.breakpoints import BreakpointList, HildaBreakpoint, WhereType
|
|
32
32
|
from hilda.common import CfSerializable, selection_prompt
|
|
33
|
-
from hilda.exceptions import AccessingMemoryError, AccessingRegisterError,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
SymbolAbsentError
|
|
33
|
+
from hilda.exceptions import AccessingMemoryError, AccessingRegisterError, ConvertingFromNSObjectError, \
|
|
34
|
+
ConvertingToNsObjectError, CreatingObjectiveCSymbolError, DisableJetsamMemoryChecksError, \
|
|
35
|
+
EvaluatingExpressionError, HildaException, InvalidThreadIndexError, SymbolAbsentError
|
|
37
36
|
from hilda.ipython_extensions.keybindings import get_keybindings
|
|
38
37
|
from hilda.lldb_importer import lldb
|
|
39
38
|
from hilda.objective_c_symbol import ObjectiveCSymbol
|
|
40
39
|
from hilda.registers import Registers
|
|
41
40
|
from hilda.snippets.mach import CFRunLoopServiceMachPort_hooks
|
|
42
41
|
from hilda.symbol import Symbol
|
|
43
|
-
from hilda.
|
|
42
|
+
from hilda.symbols import SymbolList
|
|
44
43
|
from hilda.ui.ui_manager import UiManager
|
|
45
44
|
from hilda.watchpoints import WatchpointList
|
|
46
45
|
|
|
@@ -127,7 +126,7 @@ class HildaClient:
|
|
|
127
126
|
self.debugger = debugger
|
|
128
127
|
self.target = debugger.GetSelectedTarget()
|
|
129
128
|
self.process = self.target.GetProcess()
|
|
130
|
-
self.symbols =
|
|
129
|
+
self.symbols = SymbolList(self)
|
|
131
130
|
self.breakpoints = BreakpointList(self)
|
|
132
131
|
self.watchpoints = WatchpointList(self)
|
|
133
132
|
self.captured_objects = {}
|
|
@@ -201,7 +200,7 @@ class HildaClient:
|
|
|
201
200
|
:param address:
|
|
202
201
|
:return: Hilda's symbol object
|
|
203
202
|
"""
|
|
204
|
-
return
|
|
203
|
+
return self.symbols.add(address)
|
|
205
204
|
|
|
206
205
|
def objc_symbol(self, address: int) -> ObjectiveCSymbol:
|
|
207
206
|
"""
|
|
@@ -214,18 +213,18 @@ class HildaClient:
|
|
|
214
213
|
except HildaException as e:
|
|
215
214
|
raise CreatingObjectiveCSymbolError from e
|
|
216
215
|
|
|
217
|
-
def inject(self, filename: str) ->
|
|
216
|
+
def inject(self, filename: str) -> SymbolList:
|
|
218
217
|
"""
|
|
219
218
|
Inject a single library into currently running process.
|
|
220
219
|
|
|
221
220
|
:param filename: library to inject (dylib)
|
|
222
|
-
:return:
|
|
221
|
+
:return: SymbolList
|
|
223
222
|
"""
|
|
224
223
|
module = self.target.FindModule(lldb.SBFileSpec(os.path.basename(filename), False))
|
|
225
224
|
if module.file.basename is not None:
|
|
226
225
|
self.log_warning(f'file {filename} has already been loaded')
|
|
227
226
|
|
|
228
|
-
injected =
|
|
227
|
+
injected = SymbolList(self)
|
|
229
228
|
handle = self.symbols.dlopen(filename, 10) # RTLD_GLOBAL|RTLD_NOW
|
|
230
229
|
|
|
231
230
|
if handle == 0:
|
|
@@ -247,34 +246,9 @@ class HildaClient:
|
|
|
247
246
|
# ignore unnamed symbols and those which are not: data, code or objc classes
|
|
248
247
|
continue
|
|
249
248
|
|
|
250
|
-
injected
|
|
249
|
+
injected.add(self.symbol(load_addr), name)
|
|
251
250
|
return injected
|
|
252
251
|
|
|
253
|
-
def rebind_symbols(self, image_range=None, filename_expr=''):
|
|
254
|
-
"""
|
|
255
|
-
Reparse all loaded images symbols
|
|
256
|
-
:param image_range: index range for images to load in the form of [start, end]
|
|
257
|
-
:param filename_expr: filter only images containing given expression
|
|
258
|
-
"""
|
|
259
|
-
self.log_debug('mapping symbols')
|
|
260
|
-
self._symbols_loaded = False
|
|
261
|
-
|
|
262
|
-
for i, module in enumerate(tqdm(self.target.modules)):
|
|
263
|
-
filename = module.file.basename
|
|
264
|
-
|
|
265
|
-
if filename_expr not in filename:
|
|
266
|
-
continue
|
|
267
|
-
|
|
268
|
-
if image_range is not None and (i < image_range[0] or i > image_range[1]):
|
|
269
|
-
continue
|
|
270
|
-
|
|
271
|
-
for symbol in module:
|
|
272
|
-
with suppress(AddingLldbSymbolError):
|
|
273
|
-
self.add_lldb_symbol(symbol)
|
|
274
|
-
|
|
275
|
-
globals()['symbols'] = self.symbols
|
|
276
|
-
self._symbols_loaded = True
|
|
277
|
-
|
|
278
252
|
@stop_is_needed
|
|
279
253
|
def poke(self, address, buf: bytes):
|
|
280
254
|
"""
|
|
@@ -594,7 +568,7 @@ class HildaClient:
|
|
|
594
568
|
""" jump to given symbol """
|
|
595
569
|
self.lldb_handle_command(f'j *{symbol}')
|
|
596
570
|
|
|
597
|
-
def lldb_handle_command(self, cmd: str) ->
|
|
571
|
+
def lldb_handle_command(self, cmd: str, capture_output: bool = False) -> Optional[str]:
|
|
598
572
|
"""
|
|
599
573
|
Execute an LLDB command
|
|
600
574
|
|
|
@@ -602,8 +576,15 @@ class HildaClient:
|
|
|
602
576
|
lldb_handle_command('register read')
|
|
603
577
|
|
|
604
578
|
:param cmd: LLDB command
|
|
579
|
+
:param capture_output: True if capturing the command output
|
|
580
|
+
:return: The output if capture was requested, None if not or the command failed
|
|
605
581
|
"""
|
|
606
|
-
|
|
582
|
+
if capture_output:
|
|
583
|
+
result = lldb.SBCommandReturnObject()
|
|
584
|
+
self.debugger.GetCommandInterpreter().HandleCommand(cmd, result)
|
|
585
|
+
return result.GetOutput() if result.Succeeded() else None
|
|
586
|
+
else:
|
|
587
|
+
self.debugger.HandleCommand(cmd)
|
|
607
588
|
|
|
608
589
|
def objc_get_class(self, name: str, module_name: Optional[str] = None) -> objective_c_class.Class:
|
|
609
590
|
"""
|
|
@@ -828,36 +809,6 @@ class HildaClient:
|
|
|
828
809
|
""" Log at info level """
|
|
829
810
|
self.logger.info(message)
|
|
830
811
|
|
|
831
|
-
def add_lldb_symbol(self, symbol: lldb.SBSymbol) -> Symbol:
|
|
832
|
-
"""
|
|
833
|
-
Convert an LLDB symbol into Hilda's symbol object and insert into `symbols` global
|
|
834
|
-
:param symbol: LLDB symbol
|
|
835
|
-
:return: converted symbol
|
|
836
|
-
:raise AddingLldbSymbolError: Hilda failed to convert the LLDB symbol.
|
|
837
|
-
"""
|
|
838
|
-
load_addr = symbol.addr.GetLoadAddress(self.target)
|
|
839
|
-
if load_addr == 0xffffffffffffffff:
|
|
840
|
-
# skip those not having a real address
|
|
841
|
-
raise AddingLldbSymbolError()
|
|
842
|
-
|
|
843
|
-
name = symbol.name
|
|
844
|
-
type_ = symbol.GetType()
|
|
845
|
-
|
|
846
|
-
if name in ('<redacted>',) or (type_ not in (lldb.eSymbolTypeCode,
|
|
847
|
-
lldb.eSymbolTypeRuntime,
|
|
848
|
-
lldb.eSymbolTypeData,
|
|
849
|
-
lldb.eSymbolTypeObjCMetaClass)):
|
|
850
|
-
# ignore unnamed symbols and those which are not in a really used type
|
|
851
|
-
raise AddingLldbSymbolError()
|
|
852
|
-
|
|
853
|
-
value = self.symbol(load_addr)
|
|
854
|
-
|
|
855
|
-
# add it into symbols global
|
|
856
|
-
self.symbols[name] = value
|
|
857
|
-
self.symbols[f'{name}{{{value.filename}}}'] = value
|
|
858
|
-
|
|
859
|
-
return value
|
|
860
|
-
|
|
861
812
|
def wait_for_module(self, expression: str) -> None:
|
|
862
813
|
""" Wait for a module to be loaded using `dlopen` by matching given expression """
|
|
863
814
|
self.log_info(f'Waiting for module name containing "{expression}" to be loaded')
|
|
@@ -901,7 +852,7 @@ class HildaClient:
|
|
|
901
852
|
|
|
902
853
|
# Configure and start IPython shell
|
|
903
854
|
ipython_config = Config()
|
|
904
|
-
ipython_config.IPCompleter.use_jedi =
|
|
855
|
+
ipython_config.IPCompleter.use_jedi = False
|
|
905
856
|
ipython_config.BaseIPythonApplication.profile = 'hilda'
|
|
906
857
|
ipython_config.InteractiveShellApp.extensions = ['hilda.ipython_extensions.magics',
|
|
907
858
|
'hilda.ipython_extensions.events',
|
|
@@ -3,10 +3,9 @@ import builtins
|
|
|
3
3
|
|
|
4
4
|
from IPython.terminal.interactiveshell import TerminalInteractiveShell
|
|
5
5
|
|
|
6
|
-
from hilda.exceptions import EvaluatingExpressionError
|
|
6
|
+
from hilda.exceptions import EvaluatingExpressionError
|
|
7
7
|
from hilda.hilda_client import HildaClient
|
|
8
8
|
from hilda.lldb_importer import lldb
|
|
9
|
-
from hilda.symbols_jar import SymbolsJar
|
|
10
9
|
|
|
11
10
|
|
|
12
11
|
class HIEvents:
|
|
@@ -30,22 +29,19 @@ class HIEvents:
|
|
|
30
29
|
# That are undefined
|
|
31
30
|
continue
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
except EvaluatingExpressionError:
|
|
47
|
-
self.hilda_client.log_warning(
|
|
48
|
-
f'Process is running. Pause execution in order to resolve "{node.id}"')
|
|
32
|
+
symbol = self.hilda_client.symbols.get(node.id)
|
|
33
|
+
if symbol is None:
|
|
34
|
+
continue
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
self.hilda_client._add_global(
|
|
38
|
+
node.id,
|
|
39
|
+
symbol if symbol.type_ != lldb.eSymbolTypeObjCMetaClass else self.hilda_client.objc_get_class(
|
|
40
|
+
node.id)
|
|
41
|
+
)
|
|
42
|
+
except EvaluatingExpressionError:
|
|
43
|
+
self.hilda_client.log_warning(
|
|
44
|
+
f'Process is running. Pause execution in order to resolve "{node.id}"')
|
|
49
45
|
|
|
50
46
|
|
|
51
47
|
def load_ipython_extension(ip: TerminalInteractiveShell):
|
hilda/objective_c_class.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import time
|
|
3
|
-
from collections import namedtuple
|
|
3
|
+
from collections import UserList, namedtuple
|
|
4
4
|
from dataclasses import dataclass, field
|
|
5
5
|
from functools import partial
|
|
6
|
-
from typing import Any
|
|
6
|
+
from typing import Any, Optional
|
|
7
7
|
|
|
8
8
|
from objc_types_decoder.decode import decode as decode_type
|
|
9
9
|
from objc_types_decoder.decode import decode_with_tail
|
|
@@ -12,7 +12,6 @@ from pygments.formatters import TerminalTrueColorFormatter
|
|
|
12
12
|
from pygments.lexers import ObjectiveCLexer
|
|
13
13
|
|
|
14
14
|
from hilda.exceptions import GettingObjectiveCClassError
|
|
15
|
-
from hilda.symbols_jar import SymbolsJar
|
|
16
15
|
|
|
17
16
|
Ivar = namedtuple('Ivar', 'name type_ offset')
|
|
18
17
|
Property = namedtuple('Property', 'name attributes')
|
|
@@ -88,6 +87,86 @@ class Method:
|
|
|
88
87
|
return f'{prefix} {name}; // 0x{self.address:x} (returns: {self.return_type})\n'
|
|
89
88
|
|
|
90
89
|
|
|
90
|
+
class MethodList(UserList):
|
|
91
|
+
def __init__(self, class_name: str, methods: list[Method]) -> None:
|
|
92
|
+
super().__init__()
|
|
93
|
+
self._class_name = class_name
|
|
94
|
+
self.data = methods
|
|
95
|
+
|
|
96
|
+
def get(self, name: str) -> Optional[Method]:
|
|
97
|
+
"""
|
|
98
|
+
Get a specific method implementation.
|
|
99
|
+
:param name: Method name.
|
|
100
|
+
:return: Method.
|
|
101
|
+
"""
|
|
102
|
+
for method in self.data:
|
|
103
|
+
if method.name == name:
|
|
104
|
+
return method
|
|
105
|
+
return None
|
|
106
|
+
|
|
107
|
+
# Actions
|
|
108
|
+
|
|
109
|
+
def bp(self, callback=None, **kwargs):
|
|
110
|
+
"""
|
|
111
|
+
Place a breakpoint on all symbols in the method list.
|
|
112
|
+
Look for the bp command for more details.
|
|
113
|
+
:param callback: callback function to be executed upon an hit
|
|
114
|
+
:param kwargs: optional kwargs for the bp command
|
|
115
|
+
"""
|
|
116
|
+
for method in self.data:
|
|
117
|
+
kwargs['name'] = f'[{self._class_name} {method.name}]'
|
|
118
|
+
method.imp.bp(callback, **kwargs)
|
|
119
|
+
|
|
120
|
+
def monitor(self, **kwargs):
|
|
121
|
+
"""
|
|
122
|
+
Perform monitor for all symbols in the method list.
|
|
123
|
+
See monitor command for more details.
|
|
124
|
+
:param args: given arguments for monitor command
|
|
125
|
+
"""
|
|
126
|
+
for method in self.data:
|
|
127
|
+
method.imp.monitor(**kwargs)
|
|
128
|
+
|
|
129
|
+
# Filters
|
|
130
|
+
|
|
131
|
+
def filter_startswith(self, exp, case_sensitive=True):
|
|
132
|
+
"""
|
|
133
|
+
Filter only methods with given prefix
|
|
134
|
+
:param exp: prefix
|
|
135
|
+
:param case_sensitive: is case sensitive
|
|
136
|
+
:return: reduced method list
|
|
137
|
+
"""
|
|
138
|
+
if not case_sensitive:
|
|
139
|
+
exp = exp.lower()
|
|
140
|
+
|
|
141
|
+
retval = []
|
|
142
|
+
for v in self.data:
|
|
143
|
+
name = v.name
|
|
144
|
+
if not case_sensitive:
|
|
145
|
+
name = name.lower()
|
|
146
|
+
if name.startswith(exp):
|
|
147
|
+
retval.append(v)
|
|
148
|
+
return MethodList(self._class_name, retval)
|
|
149
|
+
|
|
150
|
+
def filter_name_contains(self, exp, case_sensitive=True):
|
|
151
|
+
"""
|
|
152
|
+
Filter methods containing a given expression
|
|
153
|
+
:param exp: given expression
|
|
154
|
+
:param case_sensitive: is case sensitive
|
|
155
|
+
:return: reduced method list
|
|
156
|
+
"""
|
|
157
|
+
if not case_sensitive:
|
|
158
|
+
exp = exp.lower()
|
|
159
|
+
|
|
160
|
+
retval = []
|
|
161
|
+
for v in self.data:
|
|
162
|
+
name = v.name
|
|
163
|
+
if not case_sensitive:
|
|
164
|
+
name = name.lower()
|
|
165
|
+
if exp in name:
|
|
166
|
+
retval.append(v)
|
|
167
|
+
return MethodList(self._class_name, retval)
|
|
168
|
+
|
|
169
|
+
|
|
91
170
|
class Class:
|
|
92
171
|
"""
|
|
93
172
|
Wrapper for ObjectiveC Class object.
|
|
@@ -103,8 +182,8 @@ class Class:
|
|
|
103
182
|
self.protocols = []
|
|
104
183
|
self.ivars = []
|
|
105
184
|
self.properties = []
|
|
106
|
-
self.methods = []
|
|
107
185
|
self.name = ''
|
|
186
|
+
self.methods = MethodList(self.name, [])
|
|
108
187
|
self.super = None
|
|
109
188
|
if class_data is None:
|
|
110
189
|
self.reload()
|
|
@@ -160,16 +239,6 @@ class Class:
|
|
|
160
239
|
"""
|
|
161
240
|
return self._class_object.objc_call(sel, *args)
|
|
162
241
|
|
|
163
|
-
def get_method(self, name: str):
|
|
164
|
-
"""
|
|
165
|
-
Get a specific method implementation.
|
|
166
|
-
:param name: Method name.
|
|
167
|
-
:return: Method.
|
|
168
|
-
"""
|
|
169
|
-
for method in self.methods:
|
|
170
|
-
if method.name == name:
|
|
171
|
-
return method
|
|
172
|
-
|
|
173
242
|
def capture_self(self, sync: bool = False):
|
|
174
243
|
"""
|
|
175
244
|
Capture the first called `self` from this class.
|
|
@@ -210,15 +279,13 @@ class Class:
|
|
|
210
279
|
"""
|
|
211
280
|
Proxy for monitor command.
|
|
212
281
|
"""
|
|
213
|
-
self.
|
|
282
|
+
self.methods.monitor(**kwargs)
|
|
214
283
|
|
|
215
284
|
def bp(self, callback=None, **kwargs):
|
|
216
285
|
"""
|
|
217
286
|
Proxy for bp command.
|
|
218
287
|
"""
|
|
219
|
-
|
|
220
|
-
kwargs['name'] = f'[{self.name} {method.name}]'
|
|
221
|
-
method.imp.bp(callback, **kwargs)
|
|
288
|
+
self.methods.bp(callback, **kwargs)
|
|
222
289
|
|
|
223
290
|
def iter_supers(self):
|
|
224
291
|
"""
|
|
@@ -243,17 +310,7 @@ class Class:
|
|
|
243
310
|
Property(name=prop['name'], attributes=convert_encoded_property_attributes(prop['attributes']))
|
|
244
311
|
for prop in data['properties']
|
|
245
312
|
]
|
|
246
|
-
self.methods = [Method.from_data(method, self._client) for method in data['methods']]
|
|
247
|
-
|
|
248
|
-
@property
|
|
249
|
-
def symbols_jar(self) -> SymbolsJar:
|
|
250
|
-
""" Get a SymbolsJar object for quick operations on all methods """
|
|
251
|
-
jar = SymbolsJar.create(self._client)
|
|
252
|
-
|
|
253
|
-
for m in self.methods:
|
|
254
|
-
jar[f'[{self.name} {m.name}]'] = m.imp
|
|
255
|
-
|
|
256
|
-
return jar
|
|
313
|
+
self.methods = MethodList(self.name, [Method.from_data(method, self._client) for method in data['methods']])
|
|
257
314
|
|
|
258
315
|
def __dir__(self):
|
|
259
316
|
result = set()
|
hilda/objective_c_symbol.py
CHANGED
|
@@ -9,9 +9,8 @@ from pygments.formatters import TerminalTrueColorFormatter
|
|
|
9
9
|
from pygments.lexers import ObjectiveCLexer
|
|
10
10
|
|
|
11
11
|
from hilda.exceptions import HildaException
|
|
12
|
-
from hilda.objective_c_class import Class, Method, Property, convert_encoded_property_attributes
|
|
12
|
+
from hilda.objective_c_class import Class, Method, MethodList, Property, convert_encoded_property_attributes
|
|
13
13
|
from hilda.symbol import Symbol
|
|
14
|
-
from hilda.symbols_jar import SymbolsJar
|
|
15
14
|
|
|
16
15
|
|
|
17
16
|
class SettingIvarError(HildaException):
|
|
@@ -45,7 +44,7 @@ class ObjectiveCSymbol(Symbol):
|
|
|
45
44
|
symbol = super().create(value, client)
|
|
46
45
|
symbol.ivars = [] # type: List[Ivar]
|
|
47
46
|
symbol.properties = [] # type: List[Property]
|
|
48
|
-
symbol.methods = [] # type:
|
|
47
|
+
symbol.methods = MethodList('', []) # type: MethodList
|
|
49
48
|
symbol.class_ = None # type: Optional[Class]
|
|
50
49
|
symbol.reload()
|
|
51
50
|
return symbol
|
|
@@ -56,7 +55,7 @@ class ObjectiveCSymbol(Symbol):
|
|
|
56
55
|
"""
|
|
57
56
|
self.ivars.clear()
|
|
58
57
|
self.properties.clear()
|
|
59
|
-
self.methods
|
|
58
|
+
self.methods = MethodList('', [])
|
|
60
59
|
self.class_ = None
|
|
61
60
|
|
|
62
61
|
obj_c_code = (self._client._hilda_root / 'objective_c' / 'get_objectivec_symbol_data.m').read_text()
|
|
@@ -65,12 +64,12 @@ class ObjectiveCSymbol(Symbol):
|
|
|
65
64
|
|
|
66
65
|
self._reload_ivars(data['ivars'])
|
|
67
66
|
self._reload_properties(data['properties'])
|
|
68
|
-
self.methods = [Method.from_data(method, self._client) for method in data['methods']]
|
|
69
67
|
|
|
70
68
|
data['name'] = data['class_name']
|
|
71
69
|
data['address'] = data['class_address']
|
|
72
70
|
data['super'] = data['class_super']
|
|
73
71
|
self.class_ = Class(self._client, self._client.symbol(data['class_address']), data)
|
|
72
|
+
self.methods = MethodList(self.class_.name, [Method.from_data(method, self._client) for method in data['methods']])
|
|
74
73
|
|
|
75
74
|
def show(self, recursive: bool = False):
|
|
76
75
|
"""
|
|
@@ -141,7 +140,7 @@ class ObjectiveCSymbol(Symbol):
|
|
|
141
140
|
buf += f'@synthesize {prop.name} = {attrs.synthesize};\n'
|
|
142
141
|
|
|
143
142
|
# Add methods
|
|
144
|
-
methods = self.methods
|
|
143
|
+
methods = list(self.methods)
|
|
145
144
|
|
|
146
145
|
# Add super methods.
|
|
147
146
|
if recursive:
|
|
@@ -158,16 +157,6 @@ class ObjectiveCSymbol(Symbol):
|
|
|
158
157
|
buf += '@end'
|
|
159
158
|
return buf
|
|
160
159
|
|
|
161
|
-
@property
|
|
162
|
-
def symbols_jar(self) -> SymbolsJar:
|
|
163
|
-
""" Get a SymbolsJar object for quick operations on all methods """
|
|
164
|
-
jar = SymbolsJar.create(self._client)
|
|
165
|
-
|
|
166
|
-
for m in self.methods:
|
|
167
|
-
jar[m.name] = m.address
|
|
168
|
-
|
|
169
|
-
return jar
|
|
170
|
-
|
|
171
160
|
def __dir__(self):
|
|
172
161
|
result = set()
|
|
173
162
|
|
hilda/snippets/libmalloc.py
CHANGED
|
@@ -93,15 +93,15 @@ class NanoV2Arena:
|
|
|
93
93
|
def __init__(self, arena_base_addr):
|
|
94
94
|
self.nanov2_arena_struct = Struct(
|
|
95
95
|
# Note that this only parses 1 CPU memblock (1MB). Modify it to be 64.
|
|
96
|
-
*[
|
|
97
|
-
f"blocks_{idx}" / Array(self.blocks_per_size[idx],
|
|
98
|
-
Struct('content' /
|
|
99
|
-
Array(self.slots_per_size[idx],
|
|
100
|
-
Struct('Q' / Bytes(self.size_per_slot[idx])
|
|
101
|
-
)
|
|
102
|
-
)
|
|
103
|
-
))
|
|
104
|
-
for idx in range(len(self.blocks_per_size))
|
|
96
|
+
*[
|
|
97
|
+
f"blocks_{idx}" / Array(self.blocks_per_size[idx],
|
|
98
|
+
Struct('content' /
|
|
99
|
+
Array(self.slots_per_size[idx],
|
|
100
|
+
Struct('Q' / Bytes(self.size_per_slot[idx])
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
))
|
|
104
|
+
for idx in range(len(self.blocks_per_size))
|
|
105
105
|
]
|
|
106
106
|
)
|
|
107
107
|
self.arena_struct = self.nanov2_arena_struct.parse_stream(arena_base_addr)
|