hilda 2.0.16__py3-none-any.whl → 3.0.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 +9 -4
- hilda/breakpoints.py +480 -0
- hilda/hilda_client.py +80 -305
- hilda/ipython_extensions/keybindings.py +34 -11
- hilda/launch_lldb.py +29 -12
- hilda/objective_c_class.py +2 -6
- hilda/snippets/mach/CFRunLoopServiceMachPort_hooks.py +2 -5
- hilda/snippets/macho/all_image_infos.py +7 -4
- hilda/symbol.py +3 -0
- hilda/watchpoints.py +237 -0
- {hilda-2.0.16.dist-info → hilda-3.0.1.dist-info}/METADATA +15 -15
- {hilda-2.0.16.dist-info → hilda-3.0.1.dist-info}/RECORD +16 -14
- {hilda-2.0.16.dist-info → hilda-3.0.1.dist-info}/WHEEL +1 -1
- {hilda-2.0.16.dist-info → hilda-3.0.1.dist-info}/LICENSE +0 -0
- {hilda-2.0.16.dist-info → hilda-3.0.1.dist-info}/entry_points.txt +0 -0
- {hilda-2.0.16.dist-info → hilda-3.0.1.dist-info}/top_level.txt +0 -0
hilda/hilda_client.py
CHANGED
|
@@ -5,7 +5,6 @@ import importlib.util
|
|
|
5
5
|
import json
|
|
6
6
|
import logging
|
|
7
7
|
import os
|
|
8
|
-
import pickle
|
|
9
8
|
import struct
|
|
10
9
|
import sys
|
|
11
10
|
import time
|
|
@@ -18,10 +17,9 @@ from functools import cached_property, wraps
|
|
|
18
17
|
from pathlib import Path
|
|
19
18
|
from typing import Any, Callable, Optional, Union
|
|
20
19
|
|
|
20
|
+
import click
|
|
21
21
|
import hexdump
|
|
22
22
|
import IPython
|
|
23
|
-
from humanfriendly import prompts
|
|
24
|
-
from humanfriendly.terminal.html import html_to_ansi
|
|
25
23
|
from IPython.core.magic import register_line_magic # noqa: F401
|
|
26
24
|
from pygments import highlight
|
|
27
25
|
from pygments.formatters import TerminalTrueColorFormatter
|
|
@@ -30,11 +28,13 @@ from tqdm import tqdm
|
|
|
30
28
|
from traitlets.config import Config
|
|
31
29
|
|
|
32
30
|
from hilda import objective_c_class
|
|
31
|
+
from hilda.breakpoints import BreakpointList, HildaBreakpoint, WhereType
|
|
33
32
|
from hilda.common import CfSerializable, selection_prompt
|
|
34
33
|
from hilda.exceptions import AccessingMemoryError, AccessingRegisterError, AddingLldbSymbolError, \
|
|
35
|
-
|
|
34
|
+
ConvertingFromNSObjectError, ConvertingToNsObjectError, CreatingObjectiveCSymbolError, \
|
|
36
35
|
DisableJetsamMemoryChecksError, EvaluatingExpressionError, HildaException, InvalidThreadIndexError, \
|
|
37
36
|
SymbolAbsentError
|
|
37
|
+
from hilda.ipython_extensions.keybindings import get_keybindings
|
|
38
38
|
from hilda.lldb_importer import lldb
|
|
39
39
|
from hilda.objective_c_symbol import ObjectiveCSymbol
|
|
40
40
|
from hilda.registers import Registers
|
|
@@ -42,6 +42,7 @@ from hilda.snippets.mach import CFRunLoopServiceMachPort_hooks
|
|
|
42
42
|
from hilda.symbol import Symbol
|
|
43
43
|
from hilda.symbols_jar import SymbolsJar
|
|
44
44
|
from hilda.ui.ui_manager import UiManager
|
|
45
|
+
from hilda.watchpoints import WatchpointList
|
|
45
46
|
|
|
46
47
|
lldb.KEYSTONE_SUPPORT = True
|
|
47
48
|
try:
|
|
@@ -50,31 +51,12 @@ except ImportError:
|
|
|
50
51
|
lldb.KEYSTONE_SUPPORT = False
|
|
51
52
|
print('failed to import keystone. disabling some features')
|
|
52
53
|
|
|
53
|
-
hilda_art = Path(__file__).resolve().parent.joinpath('hilda_ascii_art.html').read_text()
|
|
54
|
-
|
|
55
|
-
GREETING = f"""
|
|
56
|
-
{hilda_art}
|
|
57
|
-
|
|
58
|
-
<b>Hilda has been successfully loaded! 😎
|
|
59
|
-
Usage:
|
|
60
|
-
<span style="color: magenta">p</span> Global to access all features.
|
|
61
|
-
<span style="color: magenta">F1</span> Show UI.
|
|
62
|
-
<span style="color: magenta">F2</span> Toggle enabling of stdout & stderr.
|
|
63
|
-
<span style="color: magenta">F7</span> Step Into.
|
|
64
|
-
<span style="color: magenta">F8</span> Step Over.
|
|
65
|
-
<span style="color: magenta">F9</span> Continue.
|
|
66
|
-
<span style="color: magenta">F10</span> Stop.
|
|
67
|
-
|
|
68
|
-
Have a nice flight ✈️! Starting an IPython shell...
|
|
69
|
-
"""
|
|
70
|
-
|
|
71
54
|
|
|
72
55
|
def disable_logs() -> None:
|
|
73
56
|
logging.getLogger('asyncio').disabled = True
|
|
74
57
|
logging.getLogger('parso.cache').disabled = True
|
|
75
58
|
logging.getLogger('parso.cache.pickle').disabled = True
|
|
76
59
|
logging.getLogger('parso.python.diff').disabled = True
|
|
77
|
-
logging.getLogger('humanfriendly.prompts').disabled = True
|
|
78
60
|
logging.getLogger('blib2to3.pgen2.driver').disabled = True
|
|
79
61
|
logging.getLogger('hilda.launch_lldb').setLevel(logging.INFO)
|
|
80
62
|
|
|
@@ -82,6 +64,17 @@ def disable_logs() -> None:
|
|
|
82
64
|
SerializableSymbol = namedtuple('SerializableSymbol', 'address type_ filename')
|
|
83
65
|
|
|
84
66
|
|
|
67
|
+
@dataclass
|
|
68
|
+
class HelpSnippet:
|
|
69
|
+
""" Small snippet line to occur from `HildaClient.show_help()` """
|
|
70
|
+
|
|
71
|
+
key: str
|
|
72
|
+
description: str
|
|
73
|
+
|
|
74
|
+
def __str__(self) -> str:
|
|
75
|
+
return click.style(self.key.ljust(8), bold=True, fg='magenta') + click.style(self.description, bold=True)
|
|
76
|
+
|
|
77
|
+
|
|
85
78
|
@dataclass
|
|
86
79
|
class Configs:
|
|
87
80
|
""" Configuration settings for evaluation and monitoring. """
|
|
@@ -125,39 +118,18 @@ def stop_is_needed(func: Callable):
|
|
|
125
118
|
return wrapper
|
|
126
119
|
|
|
127
120
|
|
|
128
|
-
class HildaBreakpoint:
|
|
129
|
-
def __init__(self, hilda_client: 'HildaClient', lldb_breakpoint: lldb.SBBreakpoint,
|
|
130
|
-
address: Union[str, int], forced: bool = False, options: Optional[typing.Mapping] = None,
|
|
131
|
-
callback: Optional[Callable] = None) -> None:
|
|
132
|
-
self._hilda_client = hilda_client
|
|
133
|
-
self.address = address
|
|
134
|
-
self.forced = forced
|
|
135
|
-
self.options = options
|
|
136
|
-
self.callback = callback
|
|
137
|
-
self.lldb_breakpoint = lldb_breakpoint
|
|
138
|
-
|
|
139
|
-
def __repr__(self) -> str:
|
|
140
|
-
return (f'<{self.__class__.__name__} LLDB:{self.lldb_breakpoint} FORCED:{self.forced} OPTIONS:{self.options} '
|
|
141
|
-
f'CALLBACK:{self.callback}>')
|
|
142
|
-
|
|
143
|
-
def __str__(self) -> str:
|
|
144
|
-
return repr(self)
|
|
145
|
-
|
|
146
|
-
def remove(self) -> None:
|
|
147
|
-
self._hilda_client.remove_hilda_breakpoint(self.lldb_breakpoint.id)
|
|
148
|
-
|
|
149
|
-
|
|
150
121
|
class HildaClient:
|
|
151
122
|
RETVAL_BIT_COUNT = 64
|
|
152
123
|
|
|
153
|
-
def __init__(self, debugger: lldb.SBDebugger):
|
|
124
|
+
def __init__(self, debugger: lldb.SBDebugger) -> None:
|
|
154
125
|
self.logger = logging.getLogger(__name__)
|
|
155
126
|
self.endianness = '<'
|
|
156
127
|
self.debugger = debugger
|
|
157
128
|
self.target = debugger.GetSelectedTarget()
|
|
158
129
|
self.process = self.target.GetProcess()
|
|
159
130
|
self.symbols = SymbolsJar.create(self)
|
|
160
|
-
self.breakpoints =
|
|
131
|
+
self.breakpoints = BreakpointList(self)
|
|
132
|
+
self.watchpoints = WatchpointList(self)
|
|
161
133
|
self.captured_objects = {}
|
|
162
134
|
self.registers = Registers(self)
|
|
163
135
|
self.arch = self.target.GetTriple().split('-')[0]
|
|
@@ -177,14 +149,15 @@ class HildaClient:
|
|
|
177
149
|
self.log_info(f'Target: {self.target}')
|
|
178
150
|
self.log_info(f'Process: {self.process}')
|
|
179
151
|
|
|
180
|
-
def hd(self, buf):
|
|
152
|
+
def hd(self, buf: bytes) -> None:
|
|
181
153
|
"""
|
|
182
|
-
Print
|
|
154
|
+
Print hexdump representation for given buffer.
|
|
155
|
+
|
|
183
156
|
:param buf: buffer to print in hexdump form
|
|
184
157
|
"""
|
|
185
|
-
|
|
158
|
+
hexdump.hexdump(buf)
|
|
186
159
|
|
|
187
|
-
def lsof(self) -> dict:
|
|
160
|
+
def lsof(self) -> dict[int, Any]:
|
|
188
161
|
"""
|
|
189
162
|
Get dictionary of all open FDs
|
|
190
163
|
:return: Mapping between open FDs and their paths
|
|
@@ -201,7 +174,7 @@ class HildaClient:
|
|
|
201
174
|
if i == depth:
|
|
202
175
|
break
|
|
203
176
|
row = ''
|
|
204
|
-
row +=
|
|
177
|
+
row += click.style(f'0x{frame.addr.GetFileAddress():x} ', fg='cyan')
|
|
205
178
|
row += str(frame)
|
|
206
179
|
if i == 0:
|
|
207
180
|
# first line
|
|
@@ -222,7 +195,7 @@ class HildaClient:
|
|
|
222
195
|
if result:
|
|
223
196
|
raise DisableJetsamMemoryChecksError()
|
|
224
197
|
|
|
225
|
-
def symbol(self, address):
|
|
198
|
+
def symbol(self, address: int) -> Symbol:
|
|
226
199
|
"""
|
|
227
200
|
Get symbol object for a given address
|
|
228
201
|
:param address:
|
|
@@ -230,7 +203,7 @@ class HildaClient:
|
|
|
230
203
|
"""
|
|
231
204
|
return Symbol.create(address, self)
|
|
232
205
|
|
|
233
|
-
def objc_symbol(self, address) -> ObjectiveCSymbol:
|
|
206
|
+
def objc_symbol(self, address: int) -> ObjectiveCSymbol:
|
|
234
207
|
"""
|
|
235
208
|
Get objc symbol wrapper for given address
|
|
236
209
|
:param address:
|
|
@@ -241,11 +214,12 @@ class HildaClient:
|
|
|
241
214
|
except HildaException as e:
|
|
242
215
|
raise CreatingObjectiveCSymbolError from e
|
|
243
216
|
|
|
244
|
-
def inject(self, filename):
|
|
217
|
+
def inject(self, filename: str) -> SymbolsJar:
|
|
245
218
|
"""
|
|
246
|
-
Inject a single library into currently running process
|
|
247
|
-
|
|
248
|
-
:
|
|
219
|
+
Inject a single library into currently running process.
|
|
220
|
+
|
|
221
|
+
:param filename: library to inject (dylib)
|
|
222
|
+
:return: SymbolsJar
|
|
249
223
|
"""
|
|
250
224
|
module = self.target.FindModule(lldb.SBFileSpec(os.path.basename(filename), False))
|
|
251
225
|
if module.file.basename is not None:
|
|
@@ -459,7 +433,8 @@ class HildaClient:
|
|
|
459
433
|
|
|
460
434
|
def set_register(self, name: str, value: Union[float, int]) -> None:
|
|
461
435
|
"""
|
|
462
|
-
Set value for register by its name
|
|
436
|
+
Set value for register by its name.
|
|
437
|
+
|
|
463
438
|
:param name: Register name
|
|
464
439
|
:param value: Register value
|
|
465
440
|
"""
|
|
@@ -473,7 +448,8 @@ class HildaClient:
|
|
|
473
448
|
|
|
474
449
|
def objc_call(self, obj: int, selector: str, *params):
|
|
475
450
|
"""
|
|
476
|
-
Simulate a call to an objc selector
|
|
451
|
+
Simulate a call to an objc selector.
|
|
452
|
+
|
|
477
453
|
:param obj: obj to pass into `objc_msgSend`
|
|
478
454
|
:param selector: selector to execute
|
|
479
455
|
:param params: any other additional parameters the selector requires
|
|
@@ -489,7 +465,7 @@ class HildaClient:
|
|
|
489
465
|
with self.stopped():
|
|
490
466
|
return self.evaluate_expression(call_expression)
|
|
491
467
|
|
|
492
|
-
def call(self, address, argv: list = None):
|
|
468
|
+
def call(self, address, argv: Optional[list] = None):
|
|
493
469
|
"""
|
|
494
470
|
Call function at given address with given parameters
|
|
495
471
|
:param address:
|
|
@@ -506,107 +482,11 @@ class HildaClient:
|
|
|
506
482
|
"""
|
|
507
483
|
Monitor every time a given address is called
|
|
508
484
|
|
|
509
|
-
|
|
510
|
-
regs={reg1: format}
|
|
511
|
-
will print register values
|
|
512
|
-
|
|
513
|
-
Available formats:
|
|
514
|
-
x: hex
|
|
515
|
-
s: string
|
|
516
|
-
cf: use CFCopyDescription() to get more informative description of the object
|
|
517
|
-
po: use LLDB po command
|
|
518
|
-
std::string: for std::string
|
|
519
|
-
|
|
520
|
-
User defined function, will be called like `format_function(hilda_client, value)`.
|
|
521
|
-
|
|
522
|
-
For example:
|
|
523
|
-
regs={'x0': 'x'} -> x0 will be printed in HEX format
|
|
524
|
-
expr={lldb_expression: format}
|
|
525
|
-
lldb_expression can be for example '$x0' or '$arg1'
|
|
526
|
-
format behaves just like 'regs' option
|
|
527
|
-
retval=format
|
|
528
|
-
Print function's return value. The format is the same as regs format.
|
|
529
|
-
stop=True
|
|
530
|
-
force a stop at every hit
|
|
531
|
-
bt=True
|
|
532
|
-
print backtrace
|
|
533
|
-
cmd=[cmd1, cmd2]
|
|
534
|
-
run several LLDB commands, one by another
|
|
535
|
-
force_return=value
|
|
536
|
-
force a return from function with the specified value
|
|
537
|
-
name=some_value
|
|
538
|
-
use `some_name` instead of the symbol name automatically extracted from the calling frame
|
|
539
|
-
override=True
|
|
540
|
-
override previous break point at same location
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
:param address:
|
|
544
|
-
:param condition: set as a conditional breakpoint using an lldb expression
|
|
545
|
-
:param options:
|
|
546
|
-
:return:
|
|
485
|
+
Alias of self.breakpoints.add_monitor()
|
|
547
486
|
"""
|
|
487
|
+
return self.breakpoints.add_monitor(address, condition, **options)
|
|
548
488
|
|
|
549
|
-
|
|
550
|
-
"""
|
|
551
|
-
:param HildaClient hilda: Hilda client.
|
|
552
|
-
:param lldb.SBFrame frame: LLDB Frame object.
|
|
553
|
-
:param lldb.SBBreakpointLocation bp_loc: LLDB Breakpoint location object.
|
|
554
|
-
:param dict options: User defined options.
|
|
555
|
-
"""
|
|
556
|
-
bp = bp_loc.GetBreakpoint()
|
|
557
|
-
|
|
558
|
-
symbol = hilda.symbol(hilda.frame.addr.GetLoadAddress(hilda.target)) # type: Symbol
|
|
559
|
-
|
|
560
|
-
# by default, attempt to resolve the symbol name through lldb
|
|
561
|
-
name = str(symbol.lldb_symbol)
|
|
562
|
-
if options.get('name', False):
|
|
563
|
-
name = options['name']
|
|
564
|
-
|
|
565
|
-
log_message = f'🚨 #{bp.id} 0x{symbol:x} {name} - Thread #{self.thread.idx}:{hex(self.thread.id)}'
|
|
566
|
-
|
|
567
|
-
if 'regs' in options:
|
|
568
|
-
log_message += '\nregs:'
|
|
569
|
-
for name, fmt in options['regs'].items():
|
|
570
|
-
value = hilda.symbol(frame.FindRegister(name).unsigned)
|
|
571
|
-
log_message += f'\n\t{name} = {hilda._monitor_format_value(fmt, value)}'
|
|
572
|
-
|
|
573
|
-
if 'expr' in options:
|
|
574
|
-
log_message += '\nexpr:'
|
|
575
|
-
for name, fmt in options['expr'].items():
|
|
576
|
-
value = hilda.symbol(hilda.evaluate_expression(name))
|
|
577
|
-
log_message += f'\n\t{name} = {hilda._monitor_format_value(fmt, value)}'
|
|
578
|
-
|
|
579
|
-
force_return = options.get('force_return')
|
|
580
|
-
if force_return is not None:
|
|
581
|
-
hilda.force_return(force_return)
|
|
582
|
-
log_message += f'\nforced return: {force_return}'
|
|
583
|
-
|
|
584
|
-
if options.get('bt'):
|
|
585
|
-
# bugfix: for callstacks from xpc events
|
|
586
|
-
hilda.finish()
|
|
587
|
-
for frame in hilda.bt():
|
|
588
|
-
log_message += f'\n\t{frame[0]} {frame[1]}'
|
|
589
|
-
|
|
590
|
-
retval = options.get('retval')
|
|
591
|
-
if retval is not None:
|
|
592
|
-
# return from function
|
|
593
|
-
hilda.finish()
|
|
594
|
-
value = hilda.evaluate_expression('$arg1')
|
|
595
|
-
log_message += f'\nreturned: {hilda._monitor_format_value(retval, value)}'
|
|
596
|
-
|
|
597
|
-
hilda.log_info(log_message)
|
|
598
|
-
|
|
599
|
-
for cmd in options.get('cmd', []):
|
|
600
|
-
hilda.lldb_handle_command(cmd)
|
|
601
|
-
|
|
602
|
-
if options.get('stop', False):
|
|
603
|
-
hilda.log_info('Process remains stopped and focused on current thread')
|
|
604
|
-
else:
|
|
605
|
-
hilda.cont()
|
|
606
|
-
|
|
607
|
-
return self.bp(address, callback, condition=condition, **options)
|
|
608
|
-
|
|
609
|
-
def show_current_source(self):
|
|
489
|
+
def show_current_source(self) -> None:
|
|
610
490
|
""" print current source code if possible """
|
|
611
491
|
self.lldb_handle_command('f')
|
|
612
492
|
|
|
@@ -632,26 +512,7 @@ class HildaClient:
|
|
|
632
512
|
if self.ui_manager.active:
|
|
633
513
|
self.ui_manager.show()
|
|
634
514
|
|
|
635
|
-
def
|
|
636
|
-
"""
|
|
637
|
-
Remove all breakpoints created by Hilda
|
|
638
|
-
:param remove_forced: include removed of "forced" breakpoints
|
|
639
|
-
"""
|
|
640
|
-
breakpoints = list(self.breakpoints.items())
|
|
641
|
-
for bp_id, bp in breakpoints:
|
|
642
|
-
if remove_forced or not bp.forced:
|
|
643
|
-
self.remove_hilda_breakpoint(bp_id)
|
|
644
|
-
|
|
645
|
-
def remove_hilda_breakpoint(self, bp_id: int) -> None:
|
|
646
|
-
"""
|
|
647
|
-
Remove a single breakpoint placed by Hilda
|
|
648
|
-
:param bp_id: Breakpoint's ID
|
|
649
|
-
"""
|
|
650
|
-
self.target.BreakpointDelete(bp_id)
|
|
651
|
-
del self.breakpoints[bp_id]
|
|
652
|
-
self.log_info(f'BP #{bp_id} has been removed')
|
|
653
|
-
|
|
654
|
-
def force_return(self, value=0):
|
|
515
|
+
def force_return(self, value: int = 0) -> None:
|
|
655
516
|
"""
|
|
656
517
|
Prematurely return from a stack frame, short-circuiting exection of newer frames and optionally
|
|
657
518
|
yielding a specified value.
|
|
@@ -661,11 +522,11 @@ class HildaClient:
|
|
|
661
522
|
self.finish()
|
|
662
523
|
self.set_register('x0', value)
|
|
663
524
|
|
|
664
|
-
def proc_info(self):
|
|
525
|
+
def proc_info(self) -> None:
|
|
665
526
|
""" Print information about currently running mapped process. """
|
|
666
527
|
print(self.process)
|
|
667
528
|
|
|
668
|
-
def print_proc_entitlements(self):
|
|
529
|
+
def print_proc_entitlements(self) -> None:
|
|
669
530
|
""" Get the plist embedded inside the process' __LINKEDIT section. """
|
|
670
531
|
linkedit_section = self.target.modules[0].FindSection('__LINKEDIT')
|
|
671
532
|
linkedit_data = self.symbol(linkedit_section.GetLoadAddress(self.target)).peek(linkedit_section.size)
|
|
@@ -675,112 +536,24 @@ class HildaClient:
|
|
|
675
536
|
entitlements = str(linkedit_data[linkedit_data.find(b'<?xml'):].split(b'\xfa', 1)[0], 'utf8')
|
|
676
537
|
print(highlight(entitlements, XmlLexer(), TerminalTrueColorFormatter()))
|
|
677
538
|
|
|
678
|
-
def bp(self, address_or_name:
|
|
679
|
-
|
|
539
|
+
def bp(self, address_or_name: WhereType, callback: Optional[Callable] = None, condition: Optional[str] = None,
|
|
540
|
+
guarded: bool = False, description: Optional[str] = None, **options) -> HildaBreakpoint:
|
|
680
541
|
"""
|
|
681
542
|
Add a breakpoint
|
|
682
|
-
|
|
543
|
+
|
|
544
|
+
Alias of self.breakpoints.add()
|
|
545
|
+
|
|
546
|
+
:param address_or_name: Where to place the breakpoint
|
|
683
547
|
:param condition: set as a conditional breakpoint using lldb expression
|
|
684
548
|
:param callback: callback(hilda, *args) to be called
|
|
685
|
-
:param
|
|
686
|
-
:param
|
|
549
|
+
:param guarded: whether the breakpoint should be protected frm usual removal.
|
|
550
|
+
:param description: Attach a breakpoint description
|
|
687
551
|
:param options: can contain an `override` keyword to specify if to override an existing BP
|
|
688
552
|
:return: native LLDB breakpoint
|
|
689
553
|
"""
|
|
690
|
-
|
|
691
|
-
override = True if options.get('override', True) else False
|
|
692
|
-
if override or prompts.prompt_for_confirmation('A breakpoint already exist in given location. '
|
|
693
|
-
'Would you like to delete the previous one?', True):
|
|
694
|
-
breakpoints = list(self.breakpoints.items())
|
|
695
|
-
for bp_id, bp in breakpoints:
|
|
696
|
-
if address_or_name == bp.address:
|
|
697
|
-
self.remove_hilda_breakpoint(bp_id)
|
|
698
|
-
|
|
699
|
-
if isinstance(address_or_name, int):
|
|
700
|
-
bp = self.target.BreakpointCreateByAddress(address_or_name)
|
|
701
|
-
elif isinstance(address_or_name, str):
|
|
702
|
-
bp = self.target.BreakpointCreateByName(address_or_name)
|
|
703
|
-
|
|
704
|
-
if condition is not None:
|
|
705
|
-
bp.SetCondition(condition)
|
|
706
|
-
|
|
707
|
-
# add into Hilda's internal list of breakpoints
|
|
708
|
-
self.breakpoints[bp.id] = HildaBreakpoint(self, bp, address=address_or_name, forced=forced, options=options,
|
|
709
|
-
callback=callback)
|
|
710
|
-
|
|
711
|
-
if callback is not None:
|
|
712
|
-
bp.SetScriptCallbackFunction('lldb.hilda_client.bp_callback_router')
|
|
713
|
-
|
|
714
|
-
self.log_info(f'Breakpoint #{bp.id} has been set')
|
|
715
|
-
return self.breakpoints[bp.id]
|
|
716
|
-
|
|
717
|
-
def bp_callback_router(self, frame, bp_loc, *_):
|
|
718
|
-
"""
|
|
719
|
-
Route the breakpoint callback the specific breakpoint callback.
|
|
720
|
-
:param lldb.SBFrame frame: LLDB Frame object.
|
|
721
|
-
:param lldb.SBBreakpointLocation bp_loc: LLDB Breakpoint location object.
|
|
722
|
-
"""
|
|
723
|
-
bp_id = bp_loc.GetBreakpoint().GetID()
|
|
724
|
-
self._bp_frame = frame
|
|
725
|
-
try:
|
|
726
|
-
self.breakpoints[bp_id].callback(self, frame, bp_loc, self.breakpoints[bp_id].options)
|
|
727
|
-
finally:
|
|
728
|
-
self._bp_frame = None
|
|
729
|
-
|
|
730
|
-
def show_hilda_breakpoints(self):
|
|
731
|
-
""" Show existing breakpoints created by Hilda. """
|
|
732
|
-
for bp_id, bp in self.breakpoints.items():
|
|
733
|
-
print(f'🚨 Breakpoint #{bp_id}: Forced: {bp.forced}')
|
|
734
|
-
if isinstance(bp.address, int):
|
|
735
|
-
print(f'\tAddress: 0x{bp.address:x}')
|
|
736
|
-
elif isinstance(bp.address, str):
|
|
737
|
-
print(f'\tName: {bp.address}')
|
|
738
|
-
print(f'\tOptions: {bp.options}')
|
|
739
|
-
|
|
740
|
-
def save(self, filename=None):
|
|
741
|
-
"""
|
|
742
|
-
Save loaded symbols map (for loading later using the load() command)
|
|
743
|
-
:param filename: optional filename for where to store
|
|
744
|
-
"""
|
|
745
|
-
if filename is None:
|
|
746
|
-
filename = self._get_saved_state_filename()
|
|
747
|
-
|
|
748
|
-
self.log_info(f'saving current state info: {filename}')
|
|
749
|
-
with open(filename, 'wb') as f:
|
|
750
|
-
symbols_copy = {}
|
|
751
|
-
for k, v in self.symbols.items():
|
|
752
|
-
# converting the symbols into serializable objects
|
|
753
|
-
symbols_copy[k] = SerializableSymbol(address=int(v),
|
|
754
|
-
type_=v.type_,
|
|
755
|
-
filename=v.filename)
|
|
756
|
-
pickle.dump(symbols_copy, f)
|
|
554
|
+
return self.breakpoints.add(address_or_name, callback, condition, guarded, description=description, **options)
|
|
757
555
|
|
|
758
|
-
def
|
|
759
|
-
"""
|
|
760
|
-
Load an existing symbols map (previously saved by the save() command)
|
|
761
|
-
:param filename: filename to load from
|
|
762
|
-
"""
|
|
763
|
-
if filename is None:
|
|
764
|
-
filename = self._get_saved_state_filename()
|
|
765
|
-
|
|
766
|
-
self.log_info(f'loading current state from: {filename}')
|
|
767
|
-
with open(filename, 'rb') as f:
|
|
768
|
-
symbols_copy = pickle.load(f)
|
|
769
|
-
|
|
770
|
-
for k, v in tqdm(symbols_copy.items()):
|
|
771
|
-
self.symbols[k] = self.symbol(v.address)
|
|
772
|
-
|
|
773
|
-
# perform sanity test for symbol rand
|
|
774
|
-
if self.symbols.rand() == 0 and self.symbols.rand() == 0:
|
|
775
|
-
# rand returning 0 twice means the loaded file is probably outdated
|
|
776
|
-
raise BrokenLocalSymbolsJarError()
|
|
777
|
-
|
|
778
|
-
# assuming the first main image will always change
|
|
779
|
-
self.rebind_symbols(image_range=[0, 0])
|
|
780
|
-
self.init_dynamic_environment()
|
|
781
|
-
self._symbols_loaded = True
|
|
782
|
-
|
|
783
|
-
def po(self, expression, cast=None):
|
|
556
|
+
def po(self, expression: str, cast: Optional[str] = None) -> str:
|
|
784
557
|
"""
|
|
785
558
|
Print given object using LLDB's po command
|
|
786
559
|
|
|
@@ -804,7 +577,7 @@ class HildaClient:
|
|
|
804
577
|
raise EvaluatingExpressionError(res.GetError())
|
|
805
578
|
return res.GetOutput().strip()
|
|
806
579
|
|
|
807
|
-
def globalize_symbols(self):
|
|
580
|
+
def globalize_symbols(self) -> None:
|
|
808
581
|
"""
|
|
809
582
|
Make all symbols in python's global scope
|
|
810
583
|
"""
|
|
@@ -817,18 +590,18 @@ class HildaClient:
|
|
|
817
590
|
and '.' not in name:
|
|
818
591
|
self._add_global(name, value, reserved_names)
|
|
819
592
|
|
|
820
|
-
def jump(self, symbol: int):
|
|
593
|
+
def jump(self, symbol: int) -> None:
|
|
821
594
|
""" jump to given symbol """
|
|
822
595
|
self.lldb_handle_command(f'j *{symbol}')
|
|
823
596
|
|
|
824
|
-
def lldb_handle_command(self, cmd):
|
|
597
|
+
def lldb_handle_command(self, cmd: str) -> None:
|
|
825
598
|
"""
|
|
826
599
|
Execute an LLDB command
|
|
827
600
|
|
|
828
601
|
For example:
|
|
829
602
|
lldb_handle_command('register read')
|
|
830
603
|
|
|
831
|
-
:param cmd:
|
|
604
|
+
:param cmd: LLDB command
|
|
832
605
|
"""
|
|
833
606
|
self.debugger.HandleCommand(cmd)
|
|
834
607
|
|
|
@@ -968,7 +741,7 @@ class HildaClient:
|
|
|
968
741
|
return self.thread.GetSelectedFrame()
|
|
969
742
|
|
|
970
743
|
@contextmanager
|
|
971
|
-
def stopped(self, interval=0):
|
|
744
|
+
def stopped(self, interval: int = 0):
|
|
972
745
|
"""
|
|
973
746
|
Context-Manager for execution while process is stopped.
|
|
974
747
|
If interval is supplied, then if the device is in running state, it will sleep for the interval
|
|
@@ -988,7 +761,7 @@ class HildaClient:
|
|
|
988
761
|
self.cont()
|
|
989
762
|
|
|
990
763
|
@contextmanager
|
|
991
|
-
def safe_malloc(self, size):
|
|
764
|
+
def safe_malloc(self, size: int):
|
|
992
765
|
"""
|
|
993
766
|
Context-Manager for allocating a block of memory which is freed afterwards
|
|
994
767
|
:param size:
|
|
@@ -1097,19 +870,36 @@ class HildaClient:
|
|
|
1097
870
|
return
|
|
1098
871
|
client.finish()
|
|
1099
872
|
client.log_info(f'Desired module has been loaded: {expression}. Process remains stopped')
|
|
1100
|
-
|
|
1101
|
-
client.
|
|
873
|
+
bp_id = bp_loc.GetBreakpoint().GetID()
|
|
874
|
+
client.breakpoints.remove(bp_id)
|
|
1102
875
|
|
|
1103
876
|
self.bp('dlopen', bp)
|
|
1104
877
|
self.cont()
|
|
1105
878
|
|
|
879
|
+
def show_help(self, *_) -> None:
|
|
880
|
+
"""
|
|
881
|
+
Show banner help message
|
|
882
|
+
"""
|
|
883
|
+
help_snippets = [HelpSnippet(key='p', description='Global to access all features')]
|
|
884
|
+
for keybinding in get_keybindings(self):
|
|
885
|
+
help_snippets.append(HelpSnippet(key=keybinding.key.upper(), description=keybinding.description))
|
|
886
|
+
|
|
887
|
+
for help_snippet in help_snippets:
|
|
888
|
+
click.echo(help_snippet)
|
|
889
|
+
|
|
1106
890
|
def interact(self, additional_namespace: Optional[typing.Mapping] = None,
|
|
1107
891
|
startup_files: Optional[list[str]] = None) -> None:
|
|
1108
892
|
""" Start an interactive Hilda shell """
|
|
1109
893
|
if not self._dynamic_env_loaded:
|
|
1110
894
|
self.init_dynamic_environment()
|
|
1111
|
-
|
|
1112
|
-
|
|
895
|
+
|
|
896
|
+
# Show greeting
|
|
897
|
+
click.secho('Hilda has been successfully loaded! 😎', bold=True)
|
|
898
|
+
click.secho('Usage:', bold=True)
|
|
899
|
+
self.show_help()
|
|
900
|
+
click.echo(click.style('Have a nice flight ✈️! Starting an IPython shell...', bold=True))
|
|
901
|
+
|
|
902
|
+
# Configure and start IPython shell
|
|
1113
903
|
ipython_config = Config()
|
|
1114
904
|
ipython_config.IPCompleter.use_jedi = True
|
|
1115
905
|
ipython_config.BaseIPythonApplication.profile = 'hilda'
|
|
@@ -1215,21 +1005,6 @@ class HildaClient:
|
|
|
1215
1005
|
else:
|
|
1216
1006
|
return value[0].peek_str()
|
|
1217
1007
|
|
|
1218
|
-
def _monitor_format_value(self, fmt, value):
|
|
1219
|
-
if callable(fmt):
|
|
1220
|
-
return fmt(self, value)
|
|
1221
|
-
formatters = {
|
|
1222
|
-
'x': lambda val: f'0x{int(val):x}',
|
|
1223
|
-
's': lambda val: val.peek_str() if val else None,
|
|
1224
|
-
'cf': lambda val: val.cf_description,
|
|
1225
|
-
'po': lambda val: val.po(),
|
|
1226
|
-
'std::string': self._std_string
|
|
1227
|
-
}
|
|
1228
|
-
if fmt in formatters:
|
|
1229
|
-
return formatters[fmt](value)
|
|
1230
|
-
else:
|
|
1231
|
-
return f'{value:x} (unsupported format)'
|
|
1232
|
-
|
|
1233
1008
|
@cached_property
|
|
1234
1009
|
def _object_identifier(self) -> Symbol:
|
|
1235
1010
|
return self.symbols.objc_getClass('VMUObjectIdentifier').objc_call('alloc').objc_call(
|
|
@@ -1,24 +1,47 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Callable
|
|
3
|
+
|
|
1
4
|
from prompt_toolkit.enums import DEFAULT_BUFFER
|
|
2
5
|
from prompt_toolkit.filters import EmacsInsertMode, HasFocus, HasSelection, ViInsertMode
|
|
3
6
|
from prompt_toolkit.keys import Keys
|
|
4
7
|
|
|
5
8
|
|
|
9
|
+
@dataclass
|
|
10
|
+
class Keybinding:
|
|
11
|
+
key: str
|
|
12
|
+
description: str
|
|
13
|
+
callback: Callable
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_keybindings(hilda_client) -> list[Keybinding]:
|
|
17
|
+
"""
|
|
18
|
+
Get list of keybindings
|
|
19
|
+
|
|
20
|
+
:param hilda.hilda_client.HildaClient hilda_client: Hilda client to bind the keys to operations
|
|
21
|
+
"""
|
|
22
|
+
return [
|
|
23
|
+
Keybinding(key=Keys.F1, description='Show this help', callback=hilda_client.show_help),
|
|
24
|
+
Keybinding(key=Keys.F2, description='Show process state UI', callback=hilda_client.ui_manager.show),
|
|
25
|
+
Keybinding(key=Keys.F3, description='Toggle enabling of stdout & stderr',
|
|
26
|
+
callback=hilda_client.toggle_enable_stdout_stderr),
|
|
27
|
+
Keybinding(key=Keys.F7, description='Step Into', callback=hilda_client.step_into),
|
|
28
|
+
Keybinding(key=Keys.F8, description='Step Over', callback=hilda_client.step_over),
|
|
29
|
+
Keybinding(key=Keys.F9, description='Continue',
|
|
30
|
+
callback=lambda _: (hilda_client.log_info('Sending continue'), hilda_client.cont())),
|
|
31
|
+
Keybinding(key=Keys.F10, description='Stop',
|
|
32
|
+
callback=lambda _: (hilda_client.log_info('Sending stop'), hilda_client.stop())),
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
|
|
6
36
|
def load_ipython_extension(ipython):
|
|
7
37
|
def register_keybindings():
|
|
8
|
-
|
|
9
|
-
keys_mapping = {Keys.F1: hilda.ui_manager.show,
|
|
10
|
-
Keys.F2: hilda.toggle_enable_stdout_stderr,
|
|
11
|
-
Keys.F7: hilda.step_into,
|
|
12
|
-
Keys.F8: hilda.step_over,
|
|
13
|
-
Keys.F9: lambda _: (hilda.log_info('Sending continue'), hilda.cont()),
|
|
14
|
-
Keys.F10: lambda _: (hilda.log_info('Sending stop'), hilda.stop())}
|
|
15
|
-
|
|
38
|
+
hilda_client = ipython.user_ns['p']
|
|
16
39
|
insert_mode = ViInsertMode() | EmacsInsertMode()
|
|
17
40
|
registry = ipython.pt_app.key_bindings
|
|
18
41
|
|
|
19
|
-
for
|
|
20
|
-
registry.add_binding(
|
|
21
|
-
callback)
|
|
42
|
+
for keybind in get_keybindings(hilda_client):
|
|
43
|
+
registry.add_binding(
|
|
44
|
+
keybind.key, filter=(HasFocus(DEFAULT_BUFFER) & ~HasSelection() & insert_mode))(keybind.callback)
|
|
22
45
|
|
|
23
46
|
register_keybindings()
|
|
24
47
|
ipython.events.register('shell_initialized', register_keybindings)
|