hilda 3.0.0__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 CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '3.0.0'
21
- __version_tuple__ = version_tuple = (3, 0, 0)
20
+ __version__ = version = '3.0.1'
21
+ __version_tuple__ = version_tuple = (3, 0, 1)
hilda/hilda_client.py CHANGED
@@ -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:
@@ -128,6 +129,7 @@ class HildaClient:
128
129
  self.process = self.target.GetProcess()
129
130
  self.symbols = SymbolsJar.create(self)
130
131
  self.breakpoints = BreakpointList(self)
132
+ self.watchpoints = WatchpointList(self)
131
133
  self.captured_objects = {}
132
134
  self.registers = Registers(self)
133
135
  self.arch = self.target.GetTriple().split('-')[0]
hilda/launch_lldb.py CHANGED
@@ -61,6 +61,33 @@ class LLDBListenerThread(Thread, ABC):
61
61
  sys.stderr.write(stderr)
62
62
  stderr = self.process.GetSTDERR(1024)
63
63
 
64
+ def _process_potential_watchpoint_event(self) -> None:
65
+ stopped_threads = self._get_stopped_threads(lldb.eStopReasonWatchpoint)
66
+ for thread in stopped_threads:
67
+ watchpoint_id = thread.GetStopReasonDataAtIndex(0)
68
+ frame = thread.GetFrameAtIndex(0)
69
+ if lldb.hilda_client is not None:
70
+ lldb.hilda_client.watchpoints._dispatch_watchpoint_callback(watchpoint_id, thread, frame)
71
+
72
+ def _get_stopped_threads(self, reason: Optional[int] = None) -> list[lldb.SBThread]:
73
+ if reason is None:
74
+ stop_reasons = [
75
+ lldb.eStopReasonSignal, lldb.eStopReasonException,
76
+ lldb.eStopReasonBreakpoint, lldb.eStopReasonWatchpoint,
77
+ lldb.eStopReasonPlanComplete, lldb.eStopReasonTrace,
78
+ ]
79
+ else:
80
+ stop_reasons = [reason]
81
+
82
+ return [thread for thread in self.process if thread.GetStopReason() in stop_reasons]
83
+
84
+ def _set_selected_thread_to_stopped_thread(self) -> None:
85
+ stopped_threads = self._get_stopped_threads()
86
+ if len(stopped_threads) < 1:
87
+ return
88
+ thread = stopped_threads[0]
89
+ self.process.SetSelectedThread(thread)
90
+
64
91
  def run(self):
65
92
  event = lldb.SBEvent()
66
93
  last_state = lldb.eStateStopped
@@ -87,18 +114,8 @@ class LLDBListenerThread(Thread, ABC):
87
114
  logger.debug('Process Continued')
88
115
  elif state == lldb.eStateStopped and last_state == lldb.eStateRunning:
89
116
  logger.debug('Process Stopped')
90
- for thread in self.process:
91
- frame = thread.GetFrameAtIndex(0)
92
- stop_reason = thread.GetStopReason()
93
- logger.debug(f'tid = {hex(thread.GetThreadID())} pc = {frame.GetPC()}')
94
- if stop_reason not in [lldb.eStopReasonSignal, lldb.eStopReasonException,
95
- lldb.eStopReasonBreakpoint,
96
- lldb.eStopReasonWatchpoint, lldb.eStopReasonPlanComplete,
97
- lldb.eStopReasonTrace,
98
- lldb.eStopReasonSignal]:
99
- continue
100
- self.process.SetSelectedThread(thread)
101
- break
117
+ self._set_selected_thread_to_stopped_thread()
118
+ self._process_potential_watchpoint_event()
102
119
 
103
120
  last_state = state
104
121
 
hilda/symbol.py CHANGED
@@ -145,6 +145,9 @@ class Symbol(int):
145
145
  def monitor(self, **args):
146
146
  return self._client.monitor(self, **args)
147
147
 
148
+ def watch(self, **args):
149
+ return self._client.watchpoints.add(self, **args)
150
+
148
151
  def bp(self, callback=None, **args):
149
152
  return self._client.bp(self, callback, **args)
150
153
 
hilda/watchpoints.py ADDED
@@ -0,0 +1,237 @@
1
+ from typing import Callable, Generator, Optional, Union
2
+
3
+ from hilda.lldb_importer import lldb
4
+
5
+
6
+ class HildaWatchpoint:
7
+ """
8
+ Hilda's class representing an LLDB watchpoint, with some optional additional properties
9
+ """
10
+
11
+ def __init__(self, hilda, lldb_watchpoint: lldb.SBWatchpoint, where: Optional[int] = None) -> None:
12
+ """
13
+ Initialize a watchpoint list.
14
+
15
+ :param hilda.hilda_client.HildaClient hilda: Hilda client
16
+ """
17
+ self._hilda = hilda
18
+ self._where = where
19
+ self._callback = None
20
+
21
+ # Actual watchpoint from LLDB API
22
+ self.lldb_watchpoint = lldb_watchpoint
23
+
24
+ @property
25
+ def where(self) -> Optional[int]:
26
+ """
27
+ A value identifying where the watchpoint was set (when it was created).
28
+
29
+ It could be either an address (int) or a Hilda symbol object (Symbol, that inherits from int).
30
+ """
31
+ return self._where
32
+
33
+ @property
34
+ def id(self) -> int:
35
+ """ A number identifying the watchpoint. """
36
+ return self.lldb_watchpoint.GetID()
37
+
38
+ @property
39
+ def callback(self) -> Optional[Callable]:
40
+ """
41
+ A callback that will be executed when the watchpoint is hit.
42
+
43
+ Note that unless the callback explicitly continues (by calling `cont()`), the program will not continue.
44
+ The callback will be invoked as callback(hilda, *args), where hilda is the 'HildaClient'.
45
+ """
46
+ return self._callback
47
+
48
+ @callback.setter
49
+ def callback(self, callback: Optional[Callable]) -> None:
50
+ self._callback = callback
51
+
52
+ @property
53
+ def condition(self) -> Optional[str]:
54
+ """
55
+ An LLDB expression to make this a conditional watchpoint.
56
+ """
57
+ return self.lldb_watchpoint.GetCondition()
58
+
59
+ @condition.setter
60
+ def condition(self, condition: Optional[str]) -> None:
61
+ self.lldb_watchpoint.SetCondition(condition)
62
+
63
+ def __repr__(self) -> str:
64
+ return f'<{self.__class__.__name__} LLDB:{self.lldb_watchpoint} CALLBACK:{self.callback}>'
65
+
66
+ def __str__(self) -> str:
67
+ return repr(self)
68
+
69
+ def remove(self) -> None:
70
+ """
71
+ Remove the watchpoint.
72
+ """
73
+ self._hilda.watchpoints.remove(self)
74
+
75
+
76
+ class WatchpointList:
77
+ """
78
+ Manager for `HildaWatchpoint` objects, each one wrapping another native LLDB watchpoint.
79
+ """
80
+
81
+ def __init__(self, hilda) -> None:
82
+ """
83
+ Initialize a watchpoint list.
84
+
85
+ :param hilda.hilda_client.HildaClient hilda: Hilda client
86
+ """
87
+ self._hilda = hilda
88
+ self._watchpoints = {}
89
+
90
+ def __contains__(self, id_or_wp: Union[int, HildaWatchpoint]) -> bool:
91
+ return self.get(id_or_wp) is not None
92
+
93
+ def __iter__(self) -> Generator[HildaWatchpoint, None, None]:
94
+ for wp in self._hilda.target.watchpoint_iter():
95
+ yield self[wp.GetID()]
96
+
97
+ def __len__(self) -> int:
98
+ return self._hilda.target.GetNumWatchpoints()
99
+
100
+ def __getitem__(self, id_or_wp: Union[int, HildaWatchpoint]) -> HildaWatchpoint:
101
+ """
102
+ Get a watchpoint by ID (or the watchpoint itself, though it usually makes little sense)
103
+
104
+ :param id_or_wp: Watchpoint's ID (or the watchpoint itself)
105
+ """
106
+ wp = self.get(id_or_wp)
107
+ if wp is None:
108
+ raise KeyError(id_or_wp)
109
+
110
+ return wp
111
+
112
+ def __delitem__(self, id_or_wp: Union[int, HildaWatchpoint]):
113
+ """
114
+ Remove a watchpoint.
115
+
116
+ :param id_or_wp: Watchpoint's ID (or the watchpoint itself)
117
+ """
118
+ self.remove(id_or_wp)
119
+
120
+ def __repr__(self) -> str:
121
+ return repr(dict(self.items()))
122
+
123
+ def __str__(self) -> str:
124
+ return repr(self)
125
+
126
+ def get(self, id_or_wp: Union[int, HildaWatchpoint]) -> Optional[HildaWatchpoint]:
127
+ """
128
+ Get a watchpoint by ID or the watchpoint itself.
129
+
130
+ :param id_or_wp: Watchpoint's ID (or the watchpoint itself)
131
+ :return: `HildaWatchpoint` is one exists, or `None` otherwise
132
+ """
133
+
134
+ if isinstance(id_or_wp, int):
135
+ wp = self._hilda.target.FindWatchpointByID(id_or_wp)
136
+ elif isinstance(id_or_wp, HildaWatchpoint):
137
+ wp = id_or_wp.lldb_watchpoint
138
+ else:
139
+ raise KeyError(f'Watchpoint "{id_or_wp}" could not be found')
140
+
141
+ if not wp.IsValid():
142
+ return None
143
+
144
+ wp_id = wp.GetID()
145
+ if wp_id not in self._watchpoints:
146
+ self._hilda.log_debug(f'Found a watchpoint added outside of the Hilda API {wp}')
147
+ self._watchpoints[wp_id] = HildaWatchpoint(self._hilda, wp)
148
+
149
+ return self._watchpoints[wp_id]
150
+
151
+ def add(self, where: int, size: int = 8, read: bool = True, write: bool = True,
152
+ callback: Optional[Callable] = None, condition: str = None) -> HildaWatchpoint:
153
+ """
154
+ Add a watchpoint.
155
+
156
+ :param where: The address of the watchpoint.
157
+ :param size: The size of the watchpoint (the address span to watch).
158
+ :param read: The watchpoint should monitor reads from memory in the specified address (and size).
159
+ :param write: The watchpoint should monitor writes to memory in the specified address (and size).
160
+ :param callback: A callback that will be executed when the watchpoint is hit.
161
+ Note that unless the callback explicitly continues (by calling cont()), the program will not continue.
162
+ The callback will be invoked as callback(hilda, *args), where hilda is the 'HildaClient'.
163
+ :param condition: An LLDB expression to make this a conditional watchpoint.
164
+ :return: The new watchpoint
165
+ """
166
+
167
+ error = lldb.SBError()
168
+ wp = self._hilda.target.WatchAddress(where, size, read, write, error)
169
+ if not wp.IsValid():
170
+ raise Exception(f'Failed to create watchpoint at {where} ({error})')
171
+
172
+ wp = HildaWatchpoint(self._hilda, wp, where)
173
+ wp.callback = callback
174
+ wp.condition = condition
175
+
176
+ self._watchpoints[wp.id] = wp
177
+
178
+ self._hilda.log_info(f'Watchpoint #{wp.id} has been set')
179
+ return wp
180
+
181
+ def remove(self, id_or_wp: Union[int, HildaWatchpoint]) -> None:
182
+ """
183
+ Remove a watchpoint.
184
+
185
+ :param id_or_wp: Watchpoint's ID (or the watchpoint itself)
186
+ """
187
+ wp = self[id_or_wp]
188
+ watchpoint_id = wp.id
189
+ self._hilda.target.DeleteWatchpoint(watchpoint_id)
190
+ self._hilda.log_debug(f'Watchpoint #{watchpoint_id} has been removed')
191
+
192
+ def clear(self) -> None:
193
+ """
194
+ Remove all watchpoints
195
+ """
196
+ for wp in list(self):
197
+ self.remove(wp)
198
+
199
+ def show(self) -> None:
200
+ """ Show existing watchpoints. """
201
+ for wp in self:
202
+ message = f'🚨 Watchpoint #{wp.id}'
203
+
204
+ if wp.where is not None:
205
+ message += f'\n\tWhere: {wp.where}'
206
+
207
+ self._hilda.log_info(message)
208
+
209
+ def items(self) -> Generator[tuple[int, HildaWatchpoint], None, None]:
210
+ """
211
+ Get a watchpoint ID and watchpoint object tuple for every watchpoint.
212
+ """
213
+ return ((wp.id, wp) for wp in self)
214
+
215
+ def keys(self) -> Generator[int, None, None]:
216
+ """
217
+ Get the watchpoint ID for every watchpoint.
218
+ """
219
+ return (wp.id for wp in self)
220
+
221
+ def values(self) -> Generator[HildaWatchpoint, None, None]:
222
+ """
223
+ Get the watchpoint object for every watchpoint.
224
+ """
225
+ return (wp for wp in self)
226
+
227
+ def _dispatch_watchpoint_callback(self, watchpoint_id: int, _, frame) -> None:
228
+ """
229
+ Route the watchpoint callback the specific watchpoint callback.
230
+ """
231
+ self._hilda._bp_frame = frame
232
+ try:
233
+ callback = self[watchpoint_id].callback
234
+ if callback is not None:
235
+ callback(self._hilda, frame, None, self[watchpoint_id])
236
+ finally:
237
+ self._hilda._bp_frame = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: hilda
3
- Version: 3.0.0
3
+ Version: 3.0.1
4
4
  Summary: LLDB wrapped and empowered by iPython's features
5
5
  Author-email: doronz88 <doron88@gmail.com>, matan <matan1008@gmail.com>, netanel cohen <netanelc305@protonmail.com>
6
6
  Maintainer-email: doronz88 <doron88@gmail.com>, matan <matan1008@gmail.com>, netanel cohen <netanelc305@protonmail.com>
@@ -3,21 +3,22 @@ gifs/ui.png,sha256=iaRwNZ9qVWUkUe2TJb_6VPsTu--7HrElA2duWiyZ-Oc,131
3
3
  gifs/xpc_print_message.gif,sha256=i5S8Y9bJm9n-NtOipFTAC8_jUR4uZCM4sOap_ccJX0k,939935
4
4
  hilda/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  hilda/__main__.py,sha256=KWRqvukK4wraxCMtvH5nO25mFXLO5aWXa7z_VfAtbO8,90
6
- hilda/_version.py,sha256=feOY2rjYM8_-oXmp5ErPlMe-6zWTpyiw538tUrvFyEA,511
6
+ hilda/_version.py,sha256=LoHm4tYT8EC9S2qaIOxZkFh7b3UUZ5jNAQr7bVyLo6Q,511
7
7
  hilda/breakpoints.py,sha256=ZVszQf4WtRgjM2JAn8E-b-Ve6kzVg5VwvTHv5_wGjwQ,19198
8
8
  hilda/cli.py,sha256=PCjrI7GrERIrZCODJYmPt6eyl-nPYZviTS8fBG3oIjM,3618
9
9
  hilda/common.py,sha256=El-ih7cvCv9PJ5OWb1jkCbh4GuaRD6gqlrFC5gyY-TE,498
10
10
  hilda/exceptions.py,sha256=8L1OvOqns4O4ieiH4YlrMbZkk_PvuyCq4UyqFAodkF8,2042
11
11
  hilda/hilda_ascii_art.html,sha256=-9YCjAKdGbjtdd6uoKrxkkcJq7j16r4dGka2bZ27b4o,120119
12
- hilda/hilda_client.py,sha256=Uqp0N91gHJFkN9vZ9NN4YLkHGDXym4JGKjn-08HUZVE,38804
13
- hilda/launch_lldb.py,sha256=gU1iJmxuH7w5HV_cy1igv-JageyQn2dmqSxx8vkpY0k,8033
12
+ hilda/hilda_client.py,sha256=phSwiI3XfuLzMXlUw7-irbZbEgi7sEpDz2w7EGUUhf0,38897
13
+ hilda/launch_lldb.py,sha256=-lmxif-w53oeTCea6ZHLiYHChkMpdDDO_zgTFWpAU6E,8597
14
14
  hilda/lldb_entrypoint.py,sha256=vTiClzfiTtjorlxEfIsI-W657KEGobx74qDhaZ8nPhM,1007
15
15
  hilda/lldb_importer.py,sha256=TCGpAWwiBuyNRsbgcYawiqm35t8XQLCJwoOfEqyBeik,526
16
16
  hilda/objective_c_class.py,sha256=AFGXFDkYUoHf_LVS6GJSyvMsIIYIviLx0PCa-f4pCT8,11561
17
17
  hilda/objective_c_symbol.py,sha256=lIZHef5iZe3AeUsK0uIsDtFnzSM-Ad6i2wfdj9DwLUM,8269
18
18
  hilda/registers.py,sha256=-9kk1MuKnWlJ0Z8zZ2RPV9nGRDtX1GXhCSkEvfnCktA,848
19
- hilda/symbol.py,sha256=yv8s0vQ6Z0ZoROeVE4RVaW0LFCyACZnFgp0QC6j05nI,7535
19
+ hilda/symbol.py,sha256=UIDmnNq1tx1uORqgRNaO_gfW2y17mhyy4cz83UVFhcc,7623
20
20
  hilda/symbols_jar.py,sha256=Vqdv6iH92P6aSfcz5XiR0FbNRqKuUu48mi-GxvPr32w,6504
21
+ hilda/watchpoints.py,sha256=j5wLEORXUkxeamjI2F61QXRrSXcGsbs4XTmN1UEa_A4,7895
21
22
  hilda/ipython_extensions/events.py,sha256=w_4V8FoJJMarWArEE8uzb2UXk1mqfslmI7XCyVb_XuE,1976
22
23
  hilda/ipython_extensions/keybindings.py,sha256=W_Cnh8lG5yVdK5x0A-4UeyBsoJBufbYl54Mqrbo8OwM,1922
23
24
  hilda/ipython_extensions/magics.py,sha256=ULb63-OyIaWwvSfwRvEG_65ibaI2RTxeX8yPJK8pbc0,1300
@@ -48,9 +49,9 @@ hilda/snippets/macho/macho_load_commands.py,sha256=vUWfFM2H6o8dMglXV7rHgh-EMTzS0
48
49
  hilda/ui/colors.json,sha256=f-ITquY3IInQreviTy23JfmxfJrGM1_MivACf1GKGqM,262
49
50
  hilda/ui/ui_manager.py,sha256=BmzI1sBx0PYCQDlB9Al7wsTEAMJxaJ7NW0DS4C7g5-0,2265
50
51
  hilda/ui/views.py,sha256=bzClOgKirKYs6nhsNRXpkGNIg3oIOmFb659GLWrlTdo,7792
51
- hilda-3.0.0.dist-info/LICENSE,sha256=M-LVJ0AFAYB82eueyl8brh-QLPe-iLNVgbCi79-3TDo,1078
52
- hilda-3.0.0.dist-info/METADATA,sha256=y0HMi2hFA-E3hr-kQL_5SBYqTxQdQBwvMLXUiSxklpo,23354
53
- hilda-3.0.0.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
54
- hilda-3.0.0.dist-info/entry_points.txt,sha256=9n3O3j6V3XnVR_GcFqCWNgRAbalfukTSW2WvghsLVmA,46
55
- hilda-3.0.0.dist-info/top_level.txt,sha256=TVD7l1WkE1noT866YqPFhiQnjYCYZM5Xz54v_3EYpnI,11
56
- hilda-3.0.0.dist-info/RECORD,,
52
+ hilda-3.0.1.dist-info/LICENSE,sha256=M-LVJ0AFAYB82eueyl8brh-QLPe-iLNVgbCi79-3TDo,1078
53
+ hilda-3.0.1.dist-info/METADATA,sha256=S0GZw7zb-oTZXZGaW_zD_eBq0ba3EaA6Zs3OSZAI3qI,23354
54
+ hilda-3.0.1.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
55
+ hilda-3.0.1.dist-info/entry_points.txt,sha256=9n3O3j6V3XnVR_GcFqCWNgRAbalfukTSW2WvghsLVmA,46
56
+ hilda-3.0.1.dist-info/top_level.txt,sha256=TVD7l1WkE1noT866YqPFhiQnjYCYZM5Xz54v_3EYpnI,11
57
+ hilda-3.0.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.2)
2
+ Generator: setuptools (76.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
File without changes